summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/compose
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--comm/mailnews/compose/.eslintrc.js5
-rw-r--r--comm/mailnews/compose/content/sendProgress.js174
-rw-r--r--comm/mailnews/compose/content/sendProgress.xhtml65
-rw-r--r--comm/mailnews/compose/moz.build11
-rw-r--r--comm/mailnews/compose/public/moz.build30
-rw-r--r--comm/mailnews/compose/public/nsIMsgAttachment.idl144
-rw-r--r--comm/mailnews/compose/public/nsIMsgCompFields.idl104
-rw-r--r--comm/mailnews/compose/public/nsIMsgCompUtils.idl43
-rw-r--r--comm/mailnews/compose/public/nsIMsgCompose.idl305
-rw-r--r--comm/mailnews/compose/public/nsIMsgComposeParams.idl87
-rw-r--r--comm/mailnews/compose/public/nsIMsgComposeProgressParams.idl16
-rw-r--r--comm/mailnews/compose/public/nsIMsgComposeSecure.idl145
-rw-r--r--comm/mailnews/compose/public/nsIMsgComposeService.idl169
-rw-r--r--comm/mailnews/compose/public/nsIMsgCopy.idl38
-rw-r--r--comm/mailnews/compose/public/nsIMsgQuote.idl35
-rw-r--r--comm/mailnews/compose/public/nsIMsgQuotingOutputStreamListener.idl16
-rw-r--r--comm/mailnews/compose/public/nsIMsgSend.idl374
-rw-r--r--comm/mailnews/compose/public/nsIMsgSendLater.idl66
-rw-r--r--comm/mailnews/compose/public/nsIMsgSendLaterListener.idl86
-rw-r--r--comm/mailnews/compose/public/nsIMsgSendListener.idl79
-rw-r--r--comm/mailnews/compose/public/nsIMsgSendReport.idl47
-rw-r--r--comm/mailnews/compose/public/nsISmtpServer.idl151
-rw-r--r--comm/mailnews/compose/public/nsISmtpService.idl134
-rw-r--r--comm/mailnews/compose/public/nsISmtpUrl.idl115
-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
-rw-r--r--comm/mailnews/compose/test/moz.build8
-rw-r--r--comm/mailnews/compose/test/unit/data/429891_testcase.eml384
-rw-r--r--comm/mailnews/compose/test/unit/data/binary-after-plain.txtbin0 -> 1527 bytes
-rw-r--r--comm/mailnews/compose/test/unit/data/listexpansion.sql126
-rw-r--r--comm/mailnews/compose/test/unit/data/message1.eml7
-rw-r--r--comm/mailnews/compose/test/unit/data/shift-jis.eml13
-rw-r--r--comm/mailnews/compose/test/unit/data/test-ISO-2022-JP.txt1
-rw-r--r--comm/mailnews/compose/test/unit/data/test-KOI8-R.txt2
-rw-r--r--comm/mailnews/compose/test/unit/data/test-SHIFT_JIS.txt1
-rw-r--r--comm/mailnews/compose/test/unit/data/test-UTF-16BE.txtbin0 -> 60 bytes
-rw-r--r--comm/mailnews/compose/test/unit/data/test-UTF-16LE.txtbin0 -> 60 bytes
-rw-r--r--comm/mailnews/compose/test/unit/data/test-UTF-8.txt1
-rw-r--r--comm/mailnews/compose/test/unit/data/test-windows-1252.txt2
-rw-r--r--comm/mailnews/compose/test/unit/head_compose.js280
-rw-r--r--comm/mailnews/compose/test/unit/test_accountKey.js80
-rw-r--r--comm/mailnews/compose/test/unit/test_attachment.js171
-rw-r--r--comm/mailnews/compose/test/unit/test_attachment_intl.js42
-rw-r--r--comm/mailnews/compose/test/unit/test_autoReply.js254
-rw-r--r--comm/mailnews/compose/test/unit/test_bcc.js330
-rw-r--r--comm/mailnews/compose/test/unit/test_bug155172.js140
-rw-r--r--comm/mailnews/compose/test/unit/test_bug474774.js253
-rw-r--r--comm/mailnews/compose/test/unit/test_createAndSendMessage.js170
-rw-r--r--comm/mailnews/compose/test/unit/test_createRFC822Message.js68
-rw-r--r--comm/mailnews/compose/test/unit/test_detectAttachmentCharset.js79
-rw-r--r--comm/mailnews/compose/test/unit/test_expandMailingLists.js115
-rw-r--r--comm/mailnews/compose/test/unit/test_fcc2.js47
-rw-r--r--comm/mailnews/compose/test/unit/test_fccReply.js140
-rw-r--r--comm/mailnews/compose/test/unit/test_longLines.js232
-rw-r--r--comm/mailnews/compose/test/unit/test_mailTelemetry.js79
-rw-r--r--comm/mailnews/compose/test/unit/test_mailtoURL.js810
-rw-r--r--comm/mailnews/compose/test/unit/test_messageBody.js206
-rw-r--r--comm/mailnews/compose/test/unit/test_messageHeaders.js812
-rw-r--r--comm/mailnews/compose/test/unit/test_nsIMsgCompFields.js62
-rw-r--r--comm/mailnews/compose/test/unit/test_nsMsgCompose1.js137
-rw-r--r--comm/mailnews/compose/test/unit/test_nsMsgCompose2.js132
-rw-r--r--comm/mailnews/compose/test/unit/test_nsMsgCompose3.js92
-rw-r--r--comm/mailnews/compose/test/unit/test_nsSmtpService1.js127
-rw-r--r--comm/mailnews/compose/test/unit/test_saveDraft.js15
-rw-r--r--comm/mailnews/compose/test/unit/test_sendBackground.js223
-rw-r--r--comm/mailnews/compose/test/unit/test_sendMailAddressIDN.js231
-rw-r--r--comm/mailnews/compose/test/unit/test_sendMailMessage.js189
-rw-r--r--comm/mailnews/compose/test/unit/test_sendMessageFile.js172
-rw-r--r--comm/mailnews/compose/test/unit/test_sendMessageLater.js261
-rw-r--r--comm/mailnews/compose/test/unit/test_sendMessageLater2.js301
-rw-r--r--comm/mailnews/compose/test/unit/test_sendMessageLater3.js188
-rw-r--r--comm/mailnews/compose/test/unit/test_sendObserver.js52
-rw-r--r--comm/mailnews/compose/test/unit/test_smtp8bitMime.js105
-rw-r--r--comm/mailnews/compose/test/unit/test_smtpAuthMethods.js166
-rw-r--r--comm/mailnews/compose/test/unit/test_smtpClient.js136
-rw-r--r--comm/mailnews/compose/test/unit/test_smtpPassword.js97
-rw-r--r--comm/mailnews/compose/test/unit/test_smtpPassword2.js59
-rw-r--r--comm/mailnews/compose/test/unit/test_smtpPasswordFailure1.js151
-rw-r--r--comm/mailnews/compose/test/unit/test_smtpPasswordFailure2.js178
-rw-r--r--comm/mailnews/compose/test/unit/test_smtpPasswordFailure3.js154
-rw-r--r--comm/mailnews/compose/test/unit/test_smtpProtocols.js63
-rw-r--r--comm/mailnews/compose/test/unit/test_smtpProxy.js49
-rw-r--r--comm/mailnews/compose/test/unit/test_smtpServer.js104
-rw-r--r--comm/mailnews/compose/test/unit/test_smtpURL.js30
-rw-r--r--comm/mailnews/compose/test/unit/test_splitRecipients.js163
-rw-r--r--comm/mailnews/compose/test/unit/test_staleTemporaryFileCleanup.js57
-rw-r--r--comm/mailnews/compose/test/unit/test_telemetry_compose.js109
-rw-r--r--comm/mailnews/compose/test/unit/test_temporaryFilesRemoved.js123
-rw-r--r--comm/mailnews/compose/test/unit/xpcshell.ini54
133 files changed, 31727 insertions, 0 deletions
diff --git a/comm/mailnews/compose/.eslintrc.js b/comm/mailnews/compose/.eslintrc.js
new file mode 100644
index 0000000000..5816519fbb
--- /dev/null
+++ b/comm/mailnews/compose/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/valid-jsdoc"],
+};
diff --git a/comm/mailnews/compose/content/sendProgress.js b/comm/mailnews/compose/content/sendProgress.js
new file mode 100644
index 0000000000..fbc05451a7
--- /dev/null
+++ b/comm/mailnews/compose/content/sendProgress.js
@@ -0,0 +1,174 @@
+/* -*- Mode: C; tab-width: 4; 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/. */
+
+// dialog is just an array we'll use to store various properties from the dialog document...
+var dialog;
+
+// the msgProgress is a nsIMsgProgress object
+var msgProgress = null;
+
+// random global variables...
+var itsASaveOperation = false;
+var gBundle;
+
+window.addEventListener("DOMContentLoaded", onLoad);
+window.addEventListener("unload", onUnload);
+document.addEventListener("dialogcancel", onCancel);
+
+// all progress notifications are done through the nsIWebProgressListener implementation...
+var progressListener = {
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
+ // Set progress meter to show indeterminate.
+ dialog.progress.removeAttribute("value");
+ dialog.progressText.value = "";
+ }
+
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ if (Components.isSuccessCode(aStatus)) {
+ // we are done sending/saving the message...
+ // Indicate completion in status area.
+ let msg;
+ if (itsASaveOperation) {
+ msg = gBundle.GetStringFromName("messageSaved");
+ } else {
+ msg = gBundle.GetStringFromName("messageSent");
+ }
+ dialog.status.setAttribute("value", msg);
+
+ // Put progress meter at 100%.
+ dialog.progress.setAttribute("value", 100);
+ dialog.progressText.setAttribute(
+ "value",
+ gBundle.formatStringFromName("percentMsg", [100])
+ );
+ }
+
+ // Note: Without some delay closing the window the "msg" string above may
+ // never be visible. Example: setTimeout(() => window.close(), 1000);
+ // Windows requires other delays. The delays also cause test failures.
+ window.close();
+ }
+ },
+
+ onProgressChange(
+ aWebProgress,
+ aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress
+ ) {
+ // Calculate percentage.
+ var percent;
+ if (aMaxTotalProgress > 0) {
+ percent = Math.round((aCurTotalProgress / aMaxTotalProgress) * 100);
+ if (percent > 100) {
+ percent = 100;
+ }
+
+ // Advance progress meter.
+ dialog.progress.value = percent;
+
+ // Update percentage label on progress meter.
+ dialog.progressText.value = gBundle.formatStringFromName("percentMsg", [
+ percent,
+ ]);
+ } else {
+ // Have progress meter show indeterminate with denominator <= 0.
+ dialog.progress.removeAttribute("value");
+ dialog.progressText.value = "";
+ }
+ },
+
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ // we can ignore this notification
+ },
+
+ onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
+ if (aMessage != "") {
+ dialog.status.setAttribute("value", aMessage);
+ }
+ },
+
+ onSecurityChange(aWebProgress, aRequest, state) {
+ // we can ignore this notification
+ },
+
+ onContentBlockingEvent(aWebProgress, aRequest, aEvent) {
+ // we can ignore this notification
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+};
+
+function onLoad() {
+ // Set global variables.
+ gBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messengercompose/sendProgress.properties"
+ );
+
+ msgProgress = window.arguments[0];
+ if (!msgProgress) {
+ console.error("Invalid argument to sendProgress.xhtml.");
+ window.close();
+ return;
+ }
+
+ let subject = "";
+ if (window.arguments[1]) {
+ let progressParams = window.arguments[1].QueryInterface(
+ Ci.nsIMsgComposeProgressParams
+ );
+ if (progressParams) {
+ itsASaveOperation =
+ progressParams.deliveryMode != Ci.nsIMsgCompDeliverMode.Now;
+ subject = progressParams.subject;
+ }
+ }
+
+ if (subject) {
+ let title = itsASaveOperation
+ ? "titleSaveMsgSubject"
+ : "titleSendMsgSubject";
+ document.title = gBundle.formatStringFromName(title, [subject]);
+ } else {
+ let title = itsASaveOperation ? "titleSaveMsg" : "titleSendMsg";
+ document.title = gBundle.GetStringFromName(title);
+ }
+
+ dialog = {};
+ dialog.status = document.getElementById("dialog.status");
+ dialog.progress = document.getElementById("dialog.progress");
+ dialog.progressText = document.getElementById("dialog.progressText");
+
+ // set our web progress listener on the helper app launcher
+ msgProgress.registerListener(progressListener);
+}
+
+function onUnload() {
+ if (msgProgress) {
+ try {
+ msgProgress.unregisterListener(progressListener);
+ msgProgress = null;
+ } catch (e) {}
+ }
+}
+
+// If the user presses cancel, tell the app launcher and close the dialog...
+function onCancel(event) {
+ // Cancel app launcher.
+ try {
+ msgProgress.processCanceledByUser = true;
+ } catch (e) {
+ return;
+ }
+
+ // Don't close up dialog, the backend will close the dialog when everything will be aborted.
+ event.preventDefault();
+}
diff --git a/comm/mailnews/compose/content/sendProgress.xhtml b/comm/mailnews/compose/content/sendProgress.xhtml
new file mode 100644
index 0000000000..cf46d0c8cc
--- /dev/null
+++ b/comm/mailnews/compose/content/sendProgress.xhtml
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/colors.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/themeableDialog.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger/locale/messengercompose/sendProgress.dtd">
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ style="min-width: 56ch; min-height: 8em"
+ lightweightthemes="true"
+ scrolling="false"
+>
+ <head>
+ <title>&sendDialog.title;</title>
+ <link rel="localization" href="branding/brand.ftl" />
+ <script
+ defer="defer"
+ src="chrome://messenger/content/dialogShadowDom.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/messengercompose/sendProgress.js"
+ ></script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <dialog id="sendProgress" buttons="cancel">
+ <hbox flex="1">
+ <vbox align="end">
+ <hbox flex="1" align="center">
+ <label value="&status.label;" />
+ </hbox>
+ <hbox flex="1" align="center">
+ <label value="&progress.label;" />
+ </hbox>
+ </vbox>
+ <vbox flex="1">
+ <hbox flex="1" align="center">
+ <label id="dialog.status" crop="center" />
+ </hbox>
+ <hbox
+ class="thin-separator"
+ flex="1"
+ style="display: flex; align-items: center"
+ >
+ <html:progress
+ id="dialog.progress"
+ value="0"
+ max="100"
+ style="flex: 1"
+ />
+ <label id="dialog.progressText" value="" />
+ </hbox>
+ </vbox>
+ </hbox>
+ </dialog>
+ </html:body>
+</html>
diff --git a/comm/mailnews/compose/moz.build b/comm/mailnews/compose/moz.build
new file mode 100644
index 0000000000..a49689ab64
--- /dev/null
+++ b/comm/mailnews/compose/moz.build
@@ -0,0 +1,11 @@
+# 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/.
+
+DIRS += [
+ "public",
+ "src",
+]
+
+TEST_DIRS += ["test"]
diff --git a/comm/mailnews/compose/public/moz.build b/comm/mailnews/compose/public/moz.build
new file mode 100644
index 0000000000..b4e027ef05
--- /dev/null
+++ b/comm/mailnews/compose/public/moz.build
@@ -0,0 +1,30 @@
+# 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/.
+
+XPIDL_SOURCES += [
+ "nsIMsgAttachment.idl",
+ "nsIMsgCompFields.idl",
+ "nsIMsgCompose.idl",
+ "nsIMsgComposeParams.idl",
+ "nsIMsgComposeProgressParams.idl",
+ "nsIMsgComposeSecure.idl",
+ "nsIMsgComposeService.idl",
+ "nsIMsgCompUtils.idl",
+ "nsIMsgCopy.idl",
+ "nsIMsgQuote.idl",
+ "nsIMsgQuotingOutputStreamListener.idl",
+ "nsIMsgSend.idl",
+ "nsIMsgSendLater.idl",
+ "nsIMsgSendLaterListener.idl",
+ "nsIMsgSendListener.idl",
+ "nsIMsgSendReport.idl",
+ "nsISmtpServer.idl",
+ "nsISmtpService.idl",
+ "nsISmtpUrl.idl",
+]
+
+XPIDL_MODULE = "msgcompose"
+
+EXPORTS += []
diff --git a/comm/mailnews/compose/public/nsIMsgAttachment.idl b/comm/mailnews/compose/public/nsIMsgAttachment.idl
new file mode 100644
index 0000000000..71356d6686
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgAttachment.idl
@@ -0,0 +1,144 @@
+/* -*- Mode: idl; 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 "nsISupports.idl"
+
+[scriptable, uuid(d17d2d60-ec3a-46de-8bd1-24c77dd9b87b)]
+interface nsIMsgAttachment : nsISupports {
+
+ /**
+ * name attribute
+ *
+ * @Attachment real name, will be sent with the attachment's header.
+ * @If no name has been provided, a name will be generated using the url.
+ */
+ attribute AString name;
+
+ /**
+ * url attribute
+ *
+ * @specify where the attachment live (locally or remotely)
+ */
+ attribute AUTF8String url;
+
+ /**
+ * msgUri attribute
+ *
+ * @specify the uri of the message this attachment belongs to
+ */
+ attribute AUTF8String msgUri;
+
+ /**
+ * urlCharset attribute
+ *
+ * @specify the Charset of url (used to convert url to Unicode after
+ * unescaping)
+ */
+ attribute ACString urlCharset;
+
+
+ /**
+ * temporary attribute
+ *
+ * @If set to true, the file pointed by the url will be destroyed when this object is destroyed.
+ * @This is only for local attachment.
+ */
+ attribute boolean temporary;
+
+ /**
+ * Are we storing this attachment via a cloud provider and linking to it?
+ */
+ attribute boolean sendViaCloud;
+
+ /**
+ * Cloud provider account key for this attachment, if any.
+ */
+ attribute ACString cloudFileAccountKey;
+
+ /**
+ * A data string stored in the x-mozilla-cloud-part header of draft messages,
+ * to be able to restore cloudFile information of re-opened drafts.
+ */
+ attribute AUTF8String cloudPartHeaderData;
+
+ /**
+ * This allows the compose front end code to put whatever html annotation
+ * it wants for the cloud part, e.g., with expiration time, etc.
+ */
+ attribute AUTF8String htmlAnnotation;
+
+ /**
+ * contentLocation attribute
+ *
+ * @Specify the origin url of the attachment, used normally when attaching
+ * a locally saved html document, but also used for cloud files and to store
+ * the original mailbox:// url of attachments, after they have been saves as
+ * temporary files.
+ */
+ attribute ACString contentLocation;
+
+ /**
+ * contentType attribute
+ *
+ * @Specify the content-type of the attachment, this does not include extra content-type parameters. If
+ * @you need to specify extra information, use contentTypeParam, charset, macType or macCreator.
+ * @If omitted, it will be determined base on either the name, the url or the content of the file.
+ */
+ attribute string contentType;
+
+ /**
+ * contentTypeParam attribute
+ *
+ * @Specify the any content-type parameter (other than the content-type itself, charset, macType or macCreator).
+ * @It will be added to the content-type during the send/save operation.
+ */
+ attribute string contentTypeParam;
+
+ /**
+ * Content-ID for embedded attachments inside a multipart/related container.
+ */
+ attribute AUTF8String contentId;
+
+ /**
+ * charset attribute
+ *
+ * @Specify the charset of the attachment. It will be added to the content-type during the
+ * @send/save operation
+ * @If omitted, will be determined automatically (if possible).
+ */
+ attribute string charset;
+
+ /**
+ * size attribute
+ *
+ * @Specify the size of the attachment.
+ */
+ attribute int64_t size;
+
+ /**
+ * macType attribute
+ *
+ * @Specify the Mac file type of the attachment. It will be added to the content-type during the
+ * @send/save operation
+ * @If omitted, will be determined automatically on Macintosh OS.
+ */
+ attribute string macType;
+
+ /**
+ * macCreator attribute
+ *
+ * @Specify the Mac file creator of the attachment. It will be added to the content-type during the
+ * @send/save operation
+ * @If omitted, will be determined automatically on Macintosh OS.
+ */
+ attribute string macCreator;
+
+ /**
+ * equalsUrl
+ *
+ * @ determines if both attachments have the same url.
+ */
+ boolean equalsUrl(in nsIMsgAttachment attachment);
+};
diff --git a/comm/mailnews/compose/public/nsIMsgCompFields.idl b/comm/mailnews/compose/public/nsIMsgCompFields.idl
new file mode 100644
index 0000000000..7ed51f4737
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgCompFields.idl
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 4; 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 "msgIStructuredHeaders.idl"
+
+interface nsIMsgAttachment;
+interface nsIMsgComposeSecure;
+
+/**
+ * A collection of headers and other attributes for building a mail message.
+ */
+[scriptable, uuid(10928477-4F24-4357-9397-FBD847F46F0A)]
+interface nsIMsgCompFields : msgIWritableStructuredHeaders {
+
+ attribute AString from;
+ attribute AString replyTo;
+ attribute AString to;
+ attribute AString cc;
+ attribute AString bcc;
+ readonly attribute bool hasRecipients;
+
+ attribute AString fcc;
+ attribute AString fcc2;
+
+ attribute AString newsgroups;
+ attribute string newspostUrl;
+ attribute AString followupTo;
+
+ attribute AString subject;
+
+ attribute AString organization;
+ attribute string references;
+ attribute string priority;
+ attribute string messageId;
+
+ attribute AString templateName;
+ // The so-called draft/template ID is a URI in reality.
+ attribute AUTF8String draftId;
+ attribute AUTF8String templateId;
+
+ attribute boolean returnReceipt;
+ attribute long receiptHeaderType;
+ attribute boolean DSN;
+ attribute boolean attachVCard;
+ attribute boolean forcePlainText;
+ attribute boolean useMultipartAlternative;
+ attribute boolean bodyIsAsciiOnly;
+ attribute boolean forceMsgEncoding;
+ /// Status of manually-activated attachment reminder.
+ attribute boolean attachmentReminder;
+ /// Delivery format for the mail being composed
+ /// (auto = 4, text = 1, html = 2, text and html = 3).
+ attribute long deliveryFormat;
+ attribute string contentLanguage;
+ /// This is populated with the key of the identity which created the draft or template.
+ attribute string creatorIdentityKey;
+
+ /**
+ * Beware that when setting this property, your body must be properly wrapped,
+ * and the line endings must match MSG_LINEBREAK, namely "\r\n" on Windows
+ * and "\n" on Linux and OSX.
+ */
+ attribute AString body;
+
+ readonly attribute Array<nsIMsgAttachment> attachments;
+ void addAttachment(in nsIMsgAttachment attachment);
+ void removeAttachment(in nsIMsgAttachment attachment);
+ void removeAttachments();
+
+ /**
+ * Values for other headers. Headers in order, from the
+ * mail.compose.other.header pref.
+ */
+ attribute Array<AString> otherHeaders;
+
+ /**
+ * This function will split the recipients into an array.
+ *
+ * @param aRecipients The recipients list to split.
+ * @param aEmailAddressOnly Set to true to drop display names from the results
+ * array.
+ *
+ * @return An array of the recipients.
+ */
+ Array<AString> splitRecipients(in AString aRecipients,
+ in boolean aEmailAddressOnly);
+
+ void ConvertBodyToPlainText();
+
+ /**
+ * Indicates whether we need to check if the current |DocumentCharset|
+ * can represent all the characters in the message body. It should be
+ * initialized to true and set to false when 'Send Anyway' is selected
+ * by a user. (bug 249530)
+ */
+ attribute boolean needToCheckCharset;
+
+ /**
+ * Object implementing encryption/signing functionality (e.g. S/MIME, PGP/MIME)
+ */
+ attribute nsIMsgComposeSecure composeSecure;
+};
diff --git a/comm/mailnews/compose/public/nsIMsgCompUtils.idl b/comm/mailnews/compose/public/nsIMsgCompUtils.idl
new file mode 100644
index 0000000000..18749e1f9d
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgCompUtils.idl
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 4; 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 "nsISupports.idl"
+#include "nsIMsgIdentity.idl"
+
+[scriptable, uuid(00b4569a-077e-4236-b993-980fd82bb948)]
+interface nsIMsgCompUtils : nsISupports {
+ string mimeMakeSeparator(in string prefix);
+
+ /**
+ * Try to use the provided identity and/or host name to generate a message ID.
+ *
+ * To identify the host name to use in the message ID, this will:
+ * - if the attribute "FQDN" of the identity is set to a valid host name, use it
+ * - otherwise, if the provided host name is valid, use it
+ * - otherwise, if the identity's email address includes a valid host name after
+ * an '@' symbol, use it
+ * - otherwise, bail without generating a message ID (returns with an empty value)
+ *
+ * @param nsIMsgIdentity The identity to use to generate the message ID.
+ * @param string The host to use to generate the message ID. Ignored if empty.
+ *
+ * @returns A message ID usable in a Message-ID header, or an empty string
+ * if no message ID could be generated.
+ */
+ AUTF8String msgGenerateMessageId(in nsIMsgIdentity identity, in AUTF8String host);
+
+ readonly attribute boolean msgMimeConformToStandard;
+
+ /**
+ * Detect the text encoding of an input string. This is a wrapper of
+ * mozilla::EncodingDetector to be used by JavaScript code. For C++, use
+ * MsgDetectCharsetFromFile from nsMsgUtils.cpp instead.
+ *
+ * @param aContent The string to detect charset.
+ *
+ * @returns Detected charset.
+ */
+ ACString detectCharset(in ACString aContent);
+};
diff --git a/comm/mailnews/compose/public/nsIMsgCompose.idl b/comm/mailnews/compose/public/nsIMsgCompose.idl
new file mode 100644
index 0000000000..7af2405e84
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgCompose.idl
@@ -0,0 +1,305 @@
+/* -*- Mode: idl; 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 "nsISupports.idl"
+#include "nsIMsgCompFields.idl"
+#include "nsIMsgComposeParams.idl"
+#include "nsIMsgSendListener.idl"
+
+%{C++
+#include "nsString.h"
+%}
+
+interface nsIMsgSend;
+interface nsIMsgIdentity;
+interface nsIMsgProgress;
+interface nsIDocShell;
+interface mozIDOMWindowProxy;
+interface nsIEditor;
+interface nsIMsgWindow;
+
+webidl Element;
+
+typedef long MSG_ComposeSaveType;
+
+[scriptable, uuid(6953e50a-7531-11d3-85fe-006008948010)]
+interface nsIMsgCompSaveType : nsISupports {
+ const long File = 0;
+ const long Template = 1;
+ const long Draft = 2;
+};
+
+typedef long MSG_DeliverMode;
+
+[scriptable, uuid(a9f27dd7-8f89-4de3-8fbf-41b789c16ee5)]
+interface nsIMsgCompDeliverMode : nsISupports {
+ const long Now = 0;
+ const long Later = 1;
+ const long Save = 2;
+ const long SaveAs = 3;
+ const long SaveAsDraft = 4;
+ const long SaveAsTemplate = 5;
+ const long SendUnsent = 6;
+ const long AutoSaveAsDraft = 7;
+ const long Background = 8;
+};
+
+[scriptable, uuid(f38ea280-e090-11d3-a449-e3153319347c)]
+interface nsIMsgCompSendFormat : nsISupports {
+ /* Send only plain text if the message is free of any rich formatting or
+ * inserted elements. Otherwise send both a HTML part and plain text
+ * alternative part. */
+ const long Auto = 0;
+ /* Only send a plain text part, losing any rich formatting or inserted
+ * elements. */
+ const long PlainText = 1;
+ /* Only send a HTML part. */
+ const long HTML = 2;
+ /* Send both the HTML part and the plain text alternative part. */
+ const long Both = 3;
+ /* An unset value, to be set with mail.default_send_format on loading the
+ * message in the compose window. */
+ const long Unset = 4;
+};
+
+[scriptable, uuid(9638af92-1dd1-11b2-bef1-ca5fee0abc62)]
+interface nsIMsgCompConvertible : nsISupports /*ToTXT*/ {
+ const long Plain = 1; // Like 4.x: Only <html>, <p>, <br>, ...
+ const long Yes = 2; // *Minor* alterations of the look: <ol>, <dd>, ...
+ const long Altering = 3; /* Look altered: <strong>, <i>, <h1>, ...
+ Can be expressed in plaintext, but not in
+ the way it looked in the HTML composer. */
+ const long No = 4; /* Will lose data: <font>, ...
+ Really *requires* visual formatting or
+ is not supported by our HTML->TXT converter. */
+ /* The values here have meaning, they are "levels":
+ convertible({a; b}) == max(convertible({a}), convertible({b}))
+ must be true, i.e. the higher value counts. */
+};
+
+[scriptable, uuid(6ce49b2a-07dc-4783-b307-9a355423163f)]
+interface nsIMsgComposeStateListener : nsISupports
+{
+ /* ... */
+ void NotifyComposeFieldsReady();
+ void ComposeProcessDone(in nsresult aResult);
+ void SaveInFolderDone(in string folderName);
+ void NotifyComposeBodyReady();
+};
+
+[scriptable, uuid(061aae23-7e0a-4818-9a15-1b5db3ceb7f4)]
+interface nsIMsgComposeNotificationType : nsISupports
+{
+ const long ComposeFieldsReady = 0;
+ const long ComposeProcessDone = 1;
+ const long SaveInFolderDone = 2;
+ const long ComposeBodyReady = 3;
+};
+
+native nsString(nsString);
+[ref] native nsStringRef(nsString);
+
+[scriptable, uuid(c6544b6b-06dd-43ac-89b5-949d7c81bb7b)]
+interface nsIMsgCompose : nsIMsgSendListener {
+
+ /**
+ * Initializes the msg compose object.
+ *
+ * @param aParams An nsIMsgComposeParams object containing the initial
+ * details for the compose.
+ * @param aWindow The optional window associated with this compose object.
+ * @param aDocShell The optional docShell of the editor element that is used
+ * for composing.
+ */
+ void initialize(in nsIMsgComposeParams aParams,
+ [optional] in mozIDOMWindowProxy aWindow,
+ [optional] in nsIDocShell aDocShell);
+
+ /* ... */
+ void RegisterStateListener(in nsIMsgComposeStateListener stateListener);
+
+ /* ... */
+ void UnregisterStateListener(in nsIMsgComposeStateListener stateListener);
+
+ /* ... */
+ Promise sendMsg(in MSG_DeliverMode deliverMode, in nsIMsgIdentity identity, in string accountKey, in nsIMsgWindow aMsgWindow, in nsIMsgProgress progress);
+
+ /**
+ * After all Compose preparations are complete, send the prepared message to
+ * the server. This exists primarily to allow an override of the sending to
+ * use a non-SMTP method for send.
+ *
+ * @param deliverMode One of the nsIMsgCompDeliverMode values.
+ * @param identity The message identity.
+ * @param accountKey The message account key.
+ */
+ Promise sendMsgToServer(in MSG_DeliverMode deliverMode,
+ in nsIMsgIdentity identity,
+ in string accountKey);
+
+ /* ... */
+ void CloseWindow();
+
+ /* ... */
+ void abort();
+
+ /* ... */
+ void quoteMessage(in AUTF8String msgURI);
+
+ /*
+ AttachmentPrettyName will return only the leafName if the it's a file URL.
+ It will also convert the filename to Unicode assuming it's in the file system
+ charset. In case of URL, |charset| parameter will be used in the conversion.
+ This UI utility function should probably go into it's own class
+ */
+ AUTF8String AttachmentPrettyName(in AUTF8String url, in string charset);
+
+ /**
+ * Expand all mailing lists in the relevant compose fields to include the
+ * members of their output. This method will additionally update the
+ * popularity field of cards in the addressing header.
+ */
+ void expandMailingLists();
+
+ /**
+ * The level of "convertibility" of the message body (whole HTML document)
+ * to plaintext.
+ *
+ * @return a value from nsIMsgCompConvertible.
+ */
+ long bodyConvertible();
+
+ /**
+ * The level of "convertibility" of the provided node to plaintext.
+ *
+ * @return a value from nsIMsgCompConvertible.
+ */
+ long nodeTreeConvertible(in Element aNode);
+
+ /**
+ * The identity currently selected for the message compose object. When set
+ * this may change the signature on a message being composed. Note that
+ * typically SendMsg will be called with the same identity as is set here, but
+ * if it is different the SendMsg version will overwrite this identity.
+ */
+ attribute nsIMsgIdentity identity;
+
+ /* Check if the composing mail headers (and identity) can be converted to a mail charset.
+ */
+ boolean checkCharsetConversion(in nsIMsgIdentity identity, out string fallbackCharset);
+
+ /* The message send object. This is created by default to be the SMTP server
+ * in sendMsgToServer, but if that method is overridden, set the actual
+ * value used here.
+ */
+ attribute nsIMsgSend messageSend;
+
+ /*
+ * Clear the messageSend object to break any circular references
+ */
+ void clearMessageSend();
+
+ /* ... */
+ attribute nsIEditor editor;
+
+ /* ... */
+ readonly attribute mozIDOMWindowProxy domWindow;
+
+ /* ... */
+ readonly attribute nsIMsgCompFields compFields;
+
+ /* ... */
+ readonly attribute boolean composeHTML;
+
+ /* ... */
+ attribute MSG_ComposeType type;
+
+ /* ... */
+ readonly attribute long wrapLength;
+
+ /* by reading this value, you can determine if yes or not the message has been modified
+ by the user. When you set this value to false, you reset the modification count
+ of the body to 0 (clean).
+ */
+ attribute boolean bodyModified;
+
+ /**
+ * Init the editor THIS USED TO BE [noscript]
+ * Now, this is called after editor is created,
+ * which is triggered by loading startup url from JS.
+ * The completion of document loading is detected by observing
+ * the "obs_documentCreated" command
+ */
+ void initEditor(in nsIEditor editor, in mozIDOMWindowProxy contentWindow);
+
+ /* The following functions are for internal use, essentially for the listener */
+
+ /* ... */
+ [noscript] void setCiteReference(in nsString citeReference);
+
+ /* Set the URI of the folder where the message has been saved */
+ attribute AUTF8String savedFolderURI;
+
+ /* Append the signature defined in the identity to the msgBody */
+ [noscript] void processSignature(in nsIMsgIdentity identity,
+ in boolean aQuoted,
+ inout nsString aMsgBody);
+
+ /* set any reply flags on the original message's folder */
+ [noscript] void processReplyFlags();
+ [noscript] void rememberQueuedDisposition();
+
+ /* ... */
+ [noscript]
+ void convertAndLoadComposeWindow(in nsStringRef aPrefix,
+ in nsStringRef aBuf,
+ in nsStringRef aSignature,
+ in boolean aQuoted,
+ in boolean aHTMLEditor);
+
+ /* Tell the doc state listeners that the doc state has changed
+ * aNotificationType is from nsIMsgComposeNotificationType
+ */
+ [noscript] void notifyStateListeners(in long aNotificationType, in nsresult aResult);
+
+ /* Retrieve the progress object */
+ readonly attribute nsIMsgProgress progress;
+
+ /* ... */
+ [noscript]
+ void buildBodyMessageAndSignature();
+
+ /* ... */
+ [noscript] void buildQuotedMessageAndSignature();
+
+ /* ... */
+ [noscript] void getQuotingToFollow(out boolean quotingToFollow);
+
+ readonly attribute AUTF8String originalMsgURI;
+
+ attribute boolean deleteDraft;
+
+ /** Set to true when remote content can load in the editor. E.g for pasting. */
+ attribute boolean allowRemoteContent;
+
+ /* for easier use of nsIMsgSendListener */
+ void addMsgSendListener(in nsIMsgSendListener sendListener);
+
+ /* for easier use of nsIMsgSendListener */
+ void removeMsgSendListener(in nsIMsgSendListener sendListener);
+
+ /// Access during mail-set-sender observer if needed, see nsIMsgCompDeliverMode.
+ readonly attribute MSG_DeliverMode deliverMode;
+
+};
+
+/* send listener interface */
+[scriptable, uuid(ad6ee068-b225-47f9-a50e-8e48440282ca)]
+interface nsIMsgComposeSendListener : nsISupports {
+
+ void setMsgCompose(in nsIMsgCompose msgCompose);
+ void setDeliverMode(in MSG_DeliverMode deliverMode);
+
+};
diff --git a/comm/mailnews/compose/public/nsIMsgComposeParams.idl b/comm/mailnews/compose/public/nsIMsgComposeParams.idl
new file mode 100644
index 0000000000..bafe7ef4e0
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgComposeParams.idl
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 4; 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 "nsISupports.idl"
+#include "nsIMsgIdentity.idl"
+#include "nsIMsgCompFields.idl"
+#include "nsIMsgSendListener.idl"
+
+interface nsIMsgDBHdr;
+typedef long MSG_ComposeType;
+
+[scriptable, uuid(c7035852-7531-11d3-9a73-006008948010)]
+interface nsIMsgCompType : nsISupports {
+ const long New = 0;
+ const long Reply = 1;
+ const long ReplyAll = 2;
+ const long ForwardAsAttachment = 3;
+ const long ForwardInline = 4;
+ const long NewsPost = 5;
+ const long ReplyToSender = 6;
+ const long ReplyToGroup = 7;
+ const long ReplyToSenderAndGroup = 8;
+ const long Draft = 9;
+ const long Template = 10; // New message from template.
+ const long MailToUrl = 11;
+ const long ReplyWithTemplate = 12;
+ const long ReplyToList = 13;
+
+ /**
+ * Will resend the original message keeping the Subject and the body the
+ * same, and will set the Reply-To: header to the sender of the original
+ * message. This gets the redirector "out of the loop" because replies
+ * to the message will go to the original sender. This is not the same
+ * as the Resent mechanism described in section 3.6.6 of RFC 2822, and
+ * so therefore does not use Resent-* headers.
+ */
+ const long Redirect = 14;
+
+ /**
+ * Used to compose a new message from an existing message. Links
+ * are sanitized since the message could be from external sources.
+ */
+ const long EditAsNew = 15;
+
+ /**
+ * Used to edit an existing template.
+ */
+ const long EditTemplate = 16;
+};
+
+
+typedef long MSG_ComposeFormat;
+
+[scriptable, uuid(a28325e8-7531-11d3-8f1c-006008948010)]
+interface nsIMsgCompFormat : nsISupports{
+ const long Default = 0;
+ const long HTML = 1;
+ const long PlainText = 2;
+ const long OppositeOfDefault = 3;
+};
+
+
+[scriptable, uuid(930895f2-d610-43f4-9e3c-25e1d1fe4143)]
+interface nsIMsgComposeParams : nsISupports {
+ attribute MSG_ComposeType type;
+ attribute MSG_ComposeFormat format;
+ attribute AUTF8String originalMsgURI;
+ attribute nsIMsgIdentity identity;
+
+ attribute nsIMsgCompFields composeFields;
+ attribute boolean bodyIsLink;
+
+ attribute nsIMsgSendListener sendListener;
+ attribute AString smtpPassword;
+ attribute nsIMsgDBHdr origMsgHdr;
+ attribute boolean autodetectCharset;
+
+ /**
+ * HTML-formatted content to quote in the body of the message.
+ * Set this to get different content than what would normally
+ * appear in the body, e.g. the original message body in a reply.
+ */
+ attribute AUTF8String htmlToQuote;
+};
diff --git a/comm/mailnews/compose/public/nsIMsgComposeProgressParams.idl b/comm/mailnews/compose/public/nsIMsgComposeProgressParams.idl
new file mode 100644
index 0000000000..9a77012c5c
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgComposeProgressParams.idl
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 4; 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 "nsISupports.idl"
+#include "nsIMsgCompose.idl"
+
+[scriptable, uuid(1e0e7c00-3e4c-11d5-9daa-f88d288130fc)]
+interface nsIMsgComposeProgressParams: nsISupports {
+
+ /* message subject */
+ attribute wstring subject;
+
+ /* delivery mode */
+ attribute MSG_DeliverMode deliveryMode;
+};
diff --git a/comm/mailnews/compose/public/nsIMsgComposeSecure.idl b/comm/mailnews/compose/public/nsIMsgComposeSecure.idl
new file mode 100644
index 0000000000..cbebdb9495
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgComposeSecure.idl
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 4; 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 "nsIMsgSendReport.idl"
+#include "nsISupports.idl"
+
+interface nsIMsgCompFields;
+interface nsIMsgIdentity;
+interface nsIOutputStream;
+interface nsIX509Cert;
+
+/**
+ * Callback type for use with asyncFindCertByEmailAddr.
+ */
+[scriptable, function, uuid(6149d7d3-14bf-4280-8451-60fb48263894)]
+interface nsIDoneFindCertForEmailCallback : nsISupports {
+ /**
+ * Called after a searching for a certificate is done.
+ *
+ * @param emailAddress - The email address that was used as the key
+ * to find this certificate.
+ * @param cert - The valid certificate that was found,
+ * or null, if no valid cert was found.
+ */
+ void findCertDone(in AUTF8String emailAddress,
+ in nsIX509Cert cert);
+};
+
+/**
+ * An instance of this type is related to exactly one email message
+ * while the user is composing it.
+ * Besides remembering flags and providing helper code, it is used to
+ * cache information about valid S/MIME encryption certificates that
+ * were found and which may be used at send time.
+ */
+[scriptable, uuid(245f2adc-410e-4bdb-91e2-a7bb42d61787)]
+interface nsIMsgComposeSecure : nsISupports
+{
+ /**
+ * Set to true if the outgoing message shall be signed.
+ */
+ attribute boolean signMessage;
+
+ /**
+ * Set to true if the outgoing message shall be encrypted.
+ */
+ attribute boolean requireEncryptMessage;
+
+ /***************************************************************************
+ * The following functions are called during message creation by nsMsgSend,
+ * after the message source is completely prepared.
+ ***************************************************************************/
+
+ /**
+ * Determine if encryption and/or signing is required.
+ *
+ * @param aIdentity - The sender's identity
+ * @param compFields - Attributes of the composed message
+ *
+ * @return - Returns true if the creation of the message requires us to go through
+ * some encryption work, and false otherwise.
+ */
+ boolean requiresCryptoEncapsulation(in nsIMsgIdentity aIdentity, in nsIMsgCompFields aCompFields);
+
+ /**
+ * Start encryption work. Called before the encrypted data is processed.
+ *
+ * @param aStream - Output stream that takes the resulting data
+ * @param aRecipients - RFC 2047-encoded list of all recipients (To:, Cc:, Bcc:, ... fields), separated by "," or ", "
+ * Recipients contain name and email addresses, just like they will be put into the message headers
+ * @param compFields - Attributes of the composed message
+ * @param aIdentity - The sender's identity
+ * @param sendReport - Report feedback to the user
+ * @param aIsDraft - True if send operation saves draft/template/etc., false if message is really sent (or sent later)
+ */
+ void beginCryptoEncapsulation(in nsIOutputStream aStream, in string aRecipients, in nsIMsgCompFields aCompFields, in nsIMsgIdentity aIdentity, in nsIMsgSendReport sendReport, in boolean aIsDraft);
+
+ /**
+ * Process a part of the message data. Called multiple times, usually for every
+ * line of the data to be encrypted
+ *
+ * @param aBuf - Buffer holding the data to be processed
+ * @param aLen - Length of the buffer (number of characters)
+ */
+ void mimeCryptoWriteBlock(in string aBuf, in long aLen);
+
+ /**
+ * End encryption work. Called after the encrypted data is processed.
+ *
+ * @param aAbort - True if the send operation was aborted
+ * @param sendReport - Report feedback to the user
+ */
+ void finishCryptoEncapsulation(in boolean aAbort, in nsIMsgSendReport sendReport);
+
+ /**
+ * Is information about a valid encryption certificate for the given
+ * email address already available in the cache?
+ *
+ * @param emailAddress - The email address to check.
+ *
+ * @return - True if a valid cert is known by the cache.
+ */
+ boolean haveValidCertForEmail(in AUTF8String emailAddress);
+
+ /**
+ * If a valid encryption certificate for the given email address
+ * is already known by the cache, then return the NSS database
+ * key of that certificate.
+ *
+ * @param emailAddress - The email address to check.
+ *
+ * @return - NSS db key of the valid cert.
+ */
+ ACString getCertDBKeyForEmail(in AUTF8String emailAddress);
+
+ /**
+ * Remember the given certificate database key in our cache. The
+ * given certDBey (as used with nsIX509CertDB) must reference a
+ * valid encryption certificate for the given email address.
+ *
+ * @param emailAddress - The email address that is related to
+ * the given certDBKey.
+ * @param certDBKey - The certificate database key.
+ */
+ void cacheValidCertForEmail(in AUTF8String emailAddress,
+ in ACString certDBKey);
+
+ /*
+ * Asynchronously find an encryption certificate by email address. Calls
+ * `findCertDone` function on the provided `nsIDoneFindCertForEmailCallback`
+ * with the results of the operation.
+ *
+ * @param emailAddress - The email address to be used as the key
+ * to find the certificate.
+ * @param callback - A callback of type nsIDoneFindCertForEmailCallback,
+ * function findCertDone will be called with
+ * the result of the operation.
+ */
+ [must_use]
+ void asyncFindCertByEmailAddr(in AUTF8String emailAddress,
+ in nsIDoneFindCertForEmailCallback callback);
+};
diff --git a/comm/mailnews/compose/public/nsIMsgComposeService.idl b/comm/mailnews/compose/public/nsIMsgComposeService.idl
new file mode 100644
index 0000000000..068bb7bd46
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgComposeService.idl
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 4; 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 "nsISupports.idl"
+#include "nsIMsgCompose.idl"
+#include "nsIMsgComposeParams.idl"
+
+interface nsIURI;
+interface nsIDocShell;
+interface nsIMsgWindow;
+interface nsIMsgIdentity;
+interface nsIMsgIncomingServer;
+interface nsIMsgDBHdr;
+
+webidl Selection;
+
+[scriptable, uuid(041782bf-e523-444b-a268-d90868fd2b50)]
+interface nsIMsgComposeService : nsISupports {
+
+ /**
+ * Open a compose window given a mailto url and other options.
+ *
+ * @param msgComposeWindowURL Can be null in most cases. If you have your
+ * own chrome url you want to use in bringing up a
+ * compose window, pass it in here.
+ * @param msgHdr The header of the original message.
+ * @param originalMsgURI The URI of the original message.
+ * @param type The message compose type: new/reply/forward/..
+ * @param format The message compose format: text/html/..
+ * @param identity The identity to send the message from.
+ * @param from The email address of the sender.
+ * @param aMsgWindow The message window to use.
+ * @param suppressReplyQuote An optional boolean flag to ignore or include
+ selected content in aMsgWindow as quote in the
+ new compose window.
+ */
+ [can_run_script]
+ void OpenComposeWindow(in AUTF8String msgComposeWindowURL,
+ in nsIMsgDBHdr msgHdr,
+ in AUTF8String originalMsgURI,
+ in MSG_ComposeType type,
+ in MSG_ComposeFormat format,
+ in nsIMsgIdentity identity,
+ in AUTF8String from,
+ in nsIMsgWindow aMsgWindow,
+ [optional] in Selection aSelection,
+ [optional] in boolean autodetectCharset);
+
+ /**
+ * Open a compose window given a mailto url and (optionally) an identity.
+ *
+ * @param aMsgComposeWindowURL Can be null in most cases. If you have your
+ * own chrome url you want to use in bringing up a
+ * compose window, pass it in here.
+ * @param aURI The mailto url you want to use as the
+ * foundation for the data inside the compose
+ * window.
+ * @param aIdentity An optional identity to send the message from.
+ */
+ void OpenComposeWindowWithURI(in string msgComposeWindowURL,
+ in nsIURI aURI,
+ [optional] in nsIMsgIdentity aIdentity);
+
+ /* ... */
+ void OpenComposeWindowWithParams(in string msgComposeWindowURL, in nsIMsgComposeParams params);
+
+ /**
+ * Creates an nsIMsgCompose instance and initializes it.
+ *
+ * @param aParams An nsIMsgComposeParams object containing the initial
+ * details for the compose.
+ * @param aWindow The optional window associated with this compose object.
+ * @param aDocShell The optional docShell of the editor element that is used
+ * for composing.
+ */
+ nsIMsgCompose initCompose(in nsIMsgComposeParams aParams,
+ [optional] in mozIDOMWindowProxy aWindow,
+ [optional] in nsIDocShell aDocShell);
+
+ /**
+ * defaultIdentity
+ *
+ * @return the default identity, in case no identity has been setup yet, will return null
+ */
+ readonly attribute nsIMsgIdentity defaultIdentity;
+
+ /* This function is use for debugging purpose only and may go away at anytime without warning */
+ void TimeStamp(in string label, in boolean resetTime);
+
+ /* This attribute is use for debugging purposes for determining whether to PR_LOG or not */
+ readonly attribute boolean logComposePerformance;
+
+ [noscript] boolean determineComposeHTML(in nsIMsgIdentity aIdentity, in MSG_ComposeFormat aFormat);
+
+ /**
+ * given a mailto url, parse the attributes and turn them into a nsIMsgComposeParams object
+ * @return nsIMsgComposeParams which corresponds to the passed in mailto url
+ */
+ nsIMsgComposeParams getParamsForMailto(in nsIURI aURI);
+
+ /**
+ * @{
+ * These constants control how to forward messages in forwardMessage.
+ * kForwardAsDefault uses value of pref "mail.forward_message_mode".
+ */
+ const unsigned long kForwardAsDefault = 0;
+ const unsigned long kForwardAsAttachment = 1;
+ const unsigned long kForwardInline = 2;
+ /** @} */
+
+ /**
+ * Allow filters to automatically forward a message to the given address(es).
+ * @param forwardTo the address(es) to forward to
+ * @param msgHdr the header of the message being replied to
+ * @param msgWindow message window to use
+ * @param server server to use for determining which account to send from
+ * @param aForwardType - How to forward the message one of 3 values:
+ * kForwardAsDefault, kForwardInline, or
+ * kForwardAsAttachment.
+ */
+ void forwardMessage(in AString forwardTo, in nsIMsgDBHdr msgHdr,
+ in nsIMsgWindow msgWindow, in nsIMsgIncomingServer server,
+ in unsigned long aForwardType);
+
+ /**
+ * Allow filters to automatically reply to a message. The reply message is
+ * based on the given template.
+ * @param msgHdr the header of the message being replied to
+ * @param templateUri uri of the template to base ther reply on
+ * @param msgWindow message window to use
+ * @param server server to use for determining which account to send from
+ */
+ void replyWithTemplate(in nsIMsgDBHdr msgHdr, in AUTF8String templateUri,
+ in nsIMsgWindow msgWindow, in nsIMsgIncomingServer server);
+
+ /**
+ * The docShell of each editor element used for composing should be registered
+ * with this service. docShells passed to initCompose get registered
+ * automatically. The registrations are typically used to get the msgCompose
+ * window when determining what remote content to allow to be displayed.
+ *
+ * @param aDocShell The nsIDocShell of the editor element.
+ * @param aMsgCompose The compose object associated with the compose window
+ */
+ void registerComposeDocShell(in nsIDocShell aDocShell,
+ in nsIMsgCompose aMsgCompose);
+
+ /**
+ * When an editor docShell is being closed, you should
+ * unregister it from this service. nsIMsgCompose normally calls this
+ * automatically for items passed to initCompose.
+ *
+ * @param aDocShell The nsIDocShell of the editor element.
+ */
+ void unregisterComposeDocShell(in nsIDocShell aDocShell);
+
+ /**
+ * For a given docShell, returns the nsIMsgCompose object associated with it.
+ *
+ * @param aDocShell The nsIDocShell of the editor element.
+ *
+ * @return NS_ERROR_FAILURE if we could not find a nsIMsgCompose for
+ * the passed in docShell.
+ */
+ nsIMsgCompose getMsgComposeForDocShell(in nsIDocShell aDocShell);
+};
diff --git a/comm/mailnews/compose/public/nsIMsgCopy.idl b/comm/mailnews/compose/public/nsIMsgCopy.idl
new file mode 100644
index 0000000000..338e7234de
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgCopy.idl
@@ -0,0 +1,38 @@
+/* -*- Mode: IDL; tab-width: 4; 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 "nsISupports.idl"
+#include "nsIMsgFolder.idl"
+#include "nsIMsgIdentity.idl"
+#include "nsIMsgSend.idl"
+
+/**
+ * The contract ID for this component is @mozilla.org/messengercompose/msgcopy;1.
+ */
+[scriptable, uuid(de03b16f-3a41-40d0-a487-ca21abcf2bee)]
+interface nsIMsgCopy : nsISupports {
+ /**
+ * Start the process of copying a message file to a message folder. The
+ * destinationfolder depends on pref and deliver mode.
+ *
+ * @param aUserIdentity The identity of the sender
+ * @param aFile The message file
+ * @param aMode The deliver mode
+ * @param aMsgSendObj The nsIMsgSend instance that listens to copy events
+ * @param aSavePref The folder uri on server
+ * @param aMsgToReplace The message to replace when copying
+ */
+ void startCopyOperation(in nsIMsgIdentity aUserIdentity,
+ in nsIFile aFile,
+ in nsMsgDeliverMode aMode,
+ in nsIMsgSend aMsgSendObj,
+ in AUTF8String aSavePref,
+ in nsIMsgDBHdr aMsgToReplace);
+
+ /**
+ * Destination folder of the copy operation. Used when aborting copy operation.
+ */
+ readonly attribute nsIMsgFolder dstFolder;
+};
diff --git a/comm/mailnews/compose/public/nsIMsgQuote.idl b/comm/mailnews/compose/public/nsIMsgQuote.idl
new file mode 100644
index 0000000000..ae8fef2ab9
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgQuote.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; 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 "nsISupports.idl"
+#include "nsIMsgQuotingOutputStreamListener.idl"
+#include "nsIChannel.idl"
+#include "nsIMimeStreamConverter.idl"
+
+interface nsIMsgDBHdr;
+
+[scriptable, uuid(f79b1d55-f546-4ed5-9f75-9428e35c4eff)]
+interface nsIMsgQuote : nsISupports {
+
+ /**
+ * Quote a particular message specified by its URI.
+ *
+ * @param charset optional parameter - if set, force the message to be
+ * quoted using this particular charset
+ */
+ void quoteMessage(in AUTF8String msgURI, in boolean quoteHeaders,
+ in nsIMsgQuotingOutputStreamListener streamListener,
+ in bool autodetectCharset, in boolean headersOnly,
+ in nsIMsgDBHdr aOrigHdr);
+
+ readonly attribute nsIMimeStreamConverterListener quoteListener;
+ readonly attribute nsIChannel quoteChannel;
+ readonly attribute nsIMsgQuotingOutputStreamListener streamListener;
+};
+
+[scriptable, uuid(1EC75AD9-88DE-11d3-989D-001083010E9B)]
+interface nsIMsgQuoteListener : nsIMimeStreamConverterListener
+{
+ attribute nsIMsgQuote msgQuote;
+};
diff --git a/comm/mailnews/compose/public/nsIMsgQuotingOutputStreamListener.idl b/comm/mailnews/compose/public/nsIMsgQuotingOutputStreamListener.idl
new file mode 100644
index 0000000000..ac86361ab5
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgQuotingOutputStreamListener.idl
@@ -0,0 +1,16 @@
+/* -*- 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/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIMimeHeaders;
+
+[scriptable, uuid(1fe345e6-2428-4a43-a0c6-d2acea0d4da4)]
+interface nsIMsgQuotingOutputStreamListener : nsIStreamListener {
+
+ // The headers are used to fill in the reply's compose fields
+ void setMimeHeaders(in nsIMimeHeaders headers);
+
+};
diff --git a/comm/mailnews/compose/public/nsIMsgSend.idl b/comm/mailnews/compose/public/nsIMsgSend.idl
new file mode 100644
index 0000000000..9d4639925d
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgSend.idl
@@ -0,0 +1,374 @@
+/* -*- 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/. */
+
+/*
+ * The nsIMsgSend method will create an RFC822 message and send it all in one operation
+ * as well as providing the ability to save disk files for later use. The mode of delivery
+ * can also be specified for the "Send Later", "Drafts" and "Templates" operations. (NOTE:
+ * This method could easily be broken in to a few different calls. Currently, this method
+ * does several functions depending on the arguments passed in, but this could easily lead
+ * to confusion. This is something that very well may change as time allows).
+ */
+#include "nsISupports.idl"
+#include "nsrootidl.idl"
+#include "nsIMsgIdentity.idl"
+#include "nsIMsgCompFields.idl"
+#include "nsIMsgSendListener.idl"
+#include "nsIMsgSendReport.idl"
+#include "domstubs.idl"
+#include "nsIPrompt.idl"
+#include "MailNewsTypes2.idl"
+#include "nsIMsgComposeParams.idl"
+
+interface nsIMsgProgress;
+interface nsIURI;
+interface nsIRequest;
+interface nsIMsgDBHdr;
+interface nsIMsgHdr;
+interface nsIFile;
+interface nsIOutputStream;
+interface nsIMsgComposeSecure;
+interface nsIMsgStatusFeedback;
+interface nsIEditor;
+interface mozIDOMWindowProxy;
+
+typedef long nsMsgDeliverMode;
+
+[scriptable, uuid(c658cd1f-dc4a-43c0-911c-c6d3e569ca7e)]
+interface nsIMsgAttachmentData : nsISupports
+{
+ /// The URL to attach.
+ attribute nsIURI url;
+
+ /**
+ * 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.
+ */
+ attribute ACString desiredType;
+
+ /**
+ * The type of the URL if known, otherwise empty. For example, if
+ * you were attaching a temp file which was known to contain HTML data,
+ * you would pass in TEXT_HTML as the realType, to override whatever type
+ * the name of the tmp file might otherwise indicate.
+ */
+ attribute ACString realType;
+
+ /// Goes along with real_type.
+ attribute ACString realEncoding;
+
+ /**
+ * 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.
+ */
+ attribute ACString realName;
+ /**
+ * 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.
+ */
+ attribute ACString description;
+
+ /// mac-specific info
+ attribute ACString xMacType;
+
+ /// mac-specific info
+ attribute ACString xMacCreator;
+};
+
+/**
+ * When we have downloaded a URL to a tmp file for attaching, this
+ * represents everything we learned about it (and did to it) in the
+ * process.
+ */
+[scriptable, uuid(c552345d-c74b-40b0-a673-79bb461e920b)]
+interface nsIMsgAttachedFile : nsISupports
+{
+ /// Where it came from on the network (or even elsewhere on the local disk.)
+ attribute nsIURI origUrl;
+
+ /// The tmp file in which the (possibly converted) data now resides.
+ attribute nsIFile tmpFile;
+
+ /// The type of the data in file_name (not necessarily the same as the type of orig_url.)
+ attribute ACString type;
+
+ /**
+ * 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.
+ */
+ attribute ACString encoding;
+ /// For Content-Description header.
+ attribute ACString description;
+
+ /// X-Mozilla-Cloud-Part, if any.
+ attribute ACString cloudPartInfo;
+
+ attribute ACString xMacType; // mac-specific info
+ attribute ACString xMacCreator; // mac-specific info
+ attribute ACString 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.)
+ */
+ attribute unsigned long size;
+ attribute unsigned long unprintableCount;
+ attribute unsigned long highbitCount;
+ attribute unsigned long ctlCount;
+ attribute unsigned long nullCount;
+ attribute unsigned long maxLineLength;
+};
+
+/**
+ * This interface is used by Outlook import to shuttle embedded
+ * image information over to nsIMsgSend's createRFC822Message method via
+ * the aEmbbeddedObjects parameter.
+ */
+[scriptable, uuid(5d2c6554-b4c8-4d68-b864-50e0df929707)]
+interface nsIMsgEmbeddedImageData : nsISupports
+{
+ attribute nsIURI uri;
+ attribute ACString cid;
+ attribute ACString name;
+};
+
+[ptr] native nsMsgAttachedFile(nsMsgAttachedFile);
+
+[scriptable, uuid(747fdfa2-1754-4282-ab26-1e55fd8de13c)]
+interface nsIMsgSend : nsISupports
+{
+ //
+ // This is the primary interface for creating and sending RFC822 messages
+ // in the new architecture. Currently, this method supports many arguments
+ // that change the behavior of the operation. This will change in time to
+ // be separate calls that will be more singluar in nature.
+ //
+ // NOTE: when aEditor is non-null, a multipart related MHTML message will
+ // be created
+ //
+
+ /// Send the message straight away.
+ const nsMsgDeliverMode nsMsgDeliverNow = 0;
+ /**
+ * Queue the message for sending later, but then wait for the user to
+ * request to send it.
+ */
+ const nsMsgDeliverMode nsMsgQueueForLater = 1;
+ const nsMsgDeliverMode nsMsgSave = 2;
+ const nsMsgDeliverMode nsMsgSaveAs = 3;
+ const nsMsgDeliverMode nsMsgSaveAsDraft = 4;
+ const nsMsgDeliverMode nsMsgSaveAsTemplate = 5;
+ const nsMsgDeliverMode nsMsgSendUnsent = 6;
+
+ /// Queue the message in the unsent folder and send it in the background.
+ const nsMsgDeliverMode nsMsgDeliverBackground = 8;
+
+ /**
+ * Create an rfc822 message and send it.
+ * @param aEditor nsIEditor instance that contains message. May be a dummy,
+ * especially in the case of import.
+ * @param aUserIdentity identity to send from.
+ * @param aAccountKey account we're sending message from. May be null.
+ * @param aFields composition fields from addressing widget
+ * @param aIsDigest is this a digest message?
+ * @param aDontDeliver Set to false by the import code - used when we're
+ * trying to create a message from parts.
+ * @param aMode delivery mode
+ * @param aMsgToReplace e.g., when saving a draft over an old draft. May be 0
+ * @param aBodyType content type of message body
+ * @param aBody message body text (should have native line endings)
+ * @param aParentWindow compose window; may be null.
+ * @param aProgress where to send progress info; may be null.
+ * @param aListener optional listener for send progress
+ * @param aPassword optional smtp server password
+ * @param aOriginalMsgURI may be null.
+ * @param aType see nsIMsgComposeParams.idl
+ */
+ Promise createAndSendMessage(in nsIEditor aEditor,
+ in nsIMsgIdentity aUserIdentity,
+ in string aAccountKey,
+ in nsIMsgCompFields aFields,
+ in boolean aIsDigest,
+ in boolean aDontDeliver,
+ in nsMsgDeliverMode aMode,
+ in nsIMsgDBHdr aMsgToReplace,
+ in string aBodyType,
+ in AString aBody,
+ in mozIDOMWindowProxy aParentWindow,
+ in nsIMsgProgress aProgress,
+ in nsIMsgSendListener aListener,
+ in AString aPassword,
+ in AUTF8String aOriginalMsgURI,
+ in MSG_ComposeType aType);
+
+ /**
+ * Creates a file containing an rfc822 message, using the passed information.
+ * aListener's OnStopSending method will get called with the file the message
+ * was stored in. OnStopSending may be called sync or async, depending on
+ * content, so you need to handle both cases.
+ *
+ * @param aUserIdentity The user identity to use for sending this email.
+ * @param aFields An nsIMsgCompFields object containing information
+ * on who to send the message to.
+ * @param aBodyType content type of message body
+ * @param aBody message body text (should have native line endings)
+ * @param aCreateAsDraft If true, this message will be put in a drafts folder
+ * @param aAttachments Array of nsIMsgAttachedFile objects
+ * @param aEmbeddedObjects Array of nsIMsgEmbeddedImageData objects for
+ * MHTML messages.
+ * @param aListener listener for msg creation progress and resulting file.
+ */
+ void createRFC822Message(in nsIMsgIdentity aUserIdentity,
+ in nsIMsgCompFields aFields,
+ in string aBodyType,
+ in ACString aBody,
+ in boolean aCreateAsDraft,
+ in Array<nsIMsgAttachedFile> aAttachments,
+ in Array<nsIMsgEmbeddedImageData> aEmbeddedObjects,
+ in nsIMsgSendListener aListener);
+
+ /**
+ * Sends a file to the specified composition fields, via the user identity
+ * provided.
+ *
+ * @param aUserIdentity The user identity to use for sending this email.
+ * @param aAccountKey The key of the account that this message relates
+ * to.
+ * @param aFields An nsIMsgCompFields object containing information
+ * on who to send the message to.
+ * @param aSendIFile A reference to the file to send.
+ * @param aDeleteSendFileOnCompletion
+ * Set to true if you want the send file deleted once
+ * the message has been sent.
+ * @param aDigest If this is a multipart message, this param
+ * specifies whether the message is in digest or mixed
+ * format.
+ * @param aMode The delivery mode for sending the message (see
+ * above for values).
+ * @param aMsgToReplace A message header representing a message to be
+ * replaced by the one sent, this param may be null.
+ * @param aListener An nsIMsgSendListener to receive feedback on the
+ * current send status. This parameter can also
+ * support the nsIMsgCopyServiceListener interface to
+ * receive notifications of copy finishing e.g. after
+ * saving a message to the sent mail folder.
+ * This param may be null.
+ * @param aStatusFeedback A feedback listener for slightly different feedback
+ * on the message send status. This param may be null.
+ * @param aPassword Pass this in to prevent a dialog if the password
+ * is needed for secure transmission.
+ */
+ Promise sendMessageFile(in nsIMsgIdentity aUserIdentity,
+ in string aAccountKey,
+ in nsIMsgCompFields aFields,
+ in nsIFile aSendIFile,
+ in boolean aDeleteSendFileOnCompletion,
+ in boolean aDigest,
+ in nsMsgDeliverMode aMode,
+ in nsIMsgDBHdr aMsgToReplace,
+ in nsIMsgSendListener aListener,
+ in nsIMsgStatusFeedback aStatusFeedback,
+ in wstring aPassword
+ );
+
+ /* Abort current send/save operation */
+ void abort();
+
+ /**
+ * Report a send failure.
+ *
+ * @param aFailureCode The failure code of the send operation. See
+ * nsComposeStrings.h for possible values. NS_OK is a possible
+ * value as well; if passed, the function won't prompt the user * but will still about the session.
+ * @param aErrorMsg The appropriate error string for the failure.
+ * @result A modified result value in the case a user action results in
+ * a different way to handle the failure.
+ */
+ nsresult fail(in nsresult aFailureCode, in wstring aErrorMsg);
+
+ /* Disable UI notification (alert message) */
+ void setGUINotificationState(in boolean aEnableFlag);
+
+ /* Crypto */
+ void BeginCryptoEncapsulation();
+
+ /* retrieve the last send process report*/
+ readonly attribute nsIMsgSendReport sendReport;
+
+ /* methods for send listener ... */
+ void notifyListenerOnStartSending(in string aMsgID, in unsigned long aMsgSize);
+ void notifyListenerOnProgress(in string aMsgID, in unsigned long aProgress, in unsigned long aProgressMax);
+ void notifyListenerOnStatus(in string aMsgID, in wstring aMsg);
+ void notifyListenerOnStopSending(in string aMsgID, in nsresult aStatus, in wstring aMsg, in nsIFile returnFile);
+ void notifyListenerOnTransportSecurityError(in string msgID, in nsresult status, in nsITransportSecurityInfo secInfo, in ACString location);
+ void deliverAsMailExit(in nsIURI aUrl, in nsresult aExitCode);
+ void deliverAsNewsExit(in nsIURI aUrl, in nsresult aExitCode);
+
+ void sendDeliveryCallback(in nsIURI aUrl, in boolean inIsNewsDelivery, in nsresult aExitCode);
+
+ /* methods for copy listener ... */
+ void notifyListenerOnStartCopy();
+ void notifyListenerOnProgressCopy(in unsigned long aProgress, in unsigned long aProgressMax);
+ void notifyListenerOnStopCopy(in nsresult aStatus);
+ void getMessageId(out ACString messageID);
+ /// When saving as draft, the folder uri we saved to.
+ readonly attribute AUTF8String folderUri;
+
+ /**
+ * After a draft is saved, use this to get the mime part number for the dom
+ * node in the editor embedded object list with the passed in index.
+ *
+ * @param aDomIndex - index in the editor dom embedded object list of
+ * the part we're interested in. These are generally images.
+ *
+ * @return the mime part number for that object.
+ */
+ ACString getPartForDomIndex(in long aDomIndex);
+
+ attribute nsMsgKey messageKey;
+
+ /* process attachment */
+ void gatherMimeAttachments();
+ readonly attribute boolean processAttachmentsSynchronously;
+ readonly attribute unsigned long attachmentCount;
+ attribute unsigned long pendingAttachmentCount;
+ readonly attribute nsMsgDeliverMode deliveryMode;
+
+ nsIMsgProgress getProgress();
+
+ nsIOutputStream getOutputStream();
+
+ attribute nsIRequest runningRequest;
+
+ attribute nsresult status;
+
+ attribute nsIMsgComposeSecure cryptoclosure;
+
+ /// Access the local copy of the composition fields.
+ readonly attribute nsIMsgCompFields sendCompFields;
+
+ /// The message body.
+ readonly attribute AString sendBody;
+
+ /// The type of the message body (typically text/plain or text/html).
+ readonly attribute ACString sendBodyType;
+
+ /// The identity to use to send the message.
+ readonly attribute nsIMsgIdentity identity;
+
+ /// The folder name to which the message will be saved,
+ /// used by error reporting.
+ attribute AString savedToFolderName;
+
+ /// Should we deliver this message (versus saving as a file)?
+ attribute boolean dontDeliver;
+
+};
diff --git a/comm/mailnews/compose/public/nsIMsgSendLater.idl b/comm/mailnews/compose/public/nsIMsgSendLater.idl
new file mode 100644
index 0000000000..65fecebced
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgSendLater.idl
@@ -0,0 +1,66 @@
+/* -*- 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 "nsIStreamListener.idl"
+
+interface nsIMsgStatusFeedback;
+interface nsIMsgIdentity;
+interface nsIMsgSendLaterListener;
+interface nsIMsgFolder;
+
+/**
+ * nsIMsgSendLater is a service used for sending messages in the background.
+ * Messages should be saved to an identity's unsent messages folder, and then
+ * can be sent by calling sendUnsentMessages.
+ *
+ * Although the service supports passing identities as parameters, until bug
+ * 317803 is fixed, all identities use the same folder, and hence the option
+ * currently doesn't work.
+ */
+[scriptable, uuid(fa324a4b-4b87-4e9a-a3c0-af9071a358df)]
+interface nsIMsgSendLater : nsIStreamListener
+{
+ /// Used to obtain status feedback for when messages are sent.
+ attribute nsIMsgStatusFeedback statusFeedback;
+
+ /**
+ * Sends any unsent messages in the identity's unsent messages folder.
+ *
+ * @param aIdentity The identity to send messages for.
+ */
+ void sendUnsentMessages(in nsIMsgIdentity aIdentity);
+
+ /**
+ * Adds an listener to the service to receive notifications.
+ *
+ * @param aListener The listener to add.
+ */
+ void addListener(in nsIMsgSendLaterListener aListener);
+
+ /**
+ * Removes a listener from the service.
+ *
+ * @param aListener The listener to remove.
+ * @exception NS_ERROR_INVALID_ARG If the listener was not already added to
+ * the service.
+ */
+ void removeListener(in nsIMsgSendLaterListener aListener);
+
+ /**
+ * Returns the unsent messages folder for the identity.
+ */
+ nsIMsgFolder getUnsentMessagesFolder(in nsIMsgIdentity userIdentity);
+
+ /**
+ * Returns true if there are any unsent messages to send.
+ *
+ * @param aIdentity The identity whose folder to check for unsent messages.
+ * If not specified, all unsent message folders are checked.
+ */
+ boolean hasUnsentMessages([optional] in nsIMsgIdentity aIdentity);
+
+ /// Returns true if the service is currently sending messages.
+ readonly attribute boolean sendingMessages;
+};
diff --git a/comm/mailnews/compose/public/nsIMsgSendLaterListener.idl b/comm/mailnews/compose/public/nsIMsgSendLaterListener.idl
new file mode 100644
index 0000000000..6e6e29a794
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgSendLaterListener.idl
@@ -0,0 +1,86 @@
+/* -*- 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 "nsISupports.idl"
+
+interface nsIMsgDBHdr;
+interface nsIMsgIdentity;
+
+/**
+ * Implement this interface and add to nsIMsgSendLater to receive notifications
+ * of send later actions.
+ */
+[scriptable, uuid(a7bc603b-d0da-4959-a82f-4b99c138b9f4)]
+interface nsIMsgSendLaterListener : nsISupports {
+ /**
+ * Notify the observer that the operation of sending messages later has
+ * started.
+ *
+ * @param aTotalMessageCount Number of messages to be sent. This will not
+ * change over the time we are doing this sequence.
+ */
+ void onStartSending(in unsigned long aTotalMessageCount);
+
+ /**
+ * Notify the observer that the next message send/copy is starting and
+ * provide details about the message.
+ *
+ * @param aCurrentMessage The current message number that is being sent.
+ * @param aTotalMessageCount The total number of messages that we are
+ * trying to send.
+ * @param aMessageHeader The header information for the message that is
+ * being sent.
+ * @param aMessageIdentity The identity being used to send the message.
+ */
+ void onMessageStartSending(in unsigned long aCurrentMessage,
+ in unsigned long aTotalMessageCount,
+ in nsIMsgDBHdr aMessageHeader,
+ in nsIMsgIdentity aIdentity);
+
+ /**
+ * Notify the observer of the current progress of sending a message. The one
+ * function covers sending the message over the network and copying to the
+ * appropriate sent folder.
+ *
+ * @param aCurrentMessage The current message number that is being sent.
+ * @param aTotalMessageCount The total number of messages that we are
+ * trying to send.
+ * @param aMessageSendPercent The percentage of the message sent (0 to 100)
+ * @param aMessageCopyPercent The percentage of the copy completed (0 to
+ * 100). If there is no copy for this message,
+ * this may be set to 100 at the same time as
+ * aMessageSendPercent.
+ */
+ void onMessageSendProgress(in unsigned long aCurrentMessage,
+ in unsigned long aTotalMessageCount,
+ in unsigned long aMessageSendPercent,
+ in unsigned long aMessageCopyPercent);
+
+ /**
+ * Notify the observer of an error in the send message later function.
+ *
+ * @param aCurrentMessage The current message number that is being sent.
+ * @param aMessageHeader The header information for the message that is
+ * being sent.
+ * @param aStatus The error status code.
+ * @param aMsg A text string describing the error.
+ */
+ void onMessageSendError(in unsigned long aCurrentMessage,
+ in nsIMsgDBHdr aMessageHeader,
+ in nsresult aStatus,
+ in wstring aMsg);
+
+ /**
+ * Notify the observer that the send unsent messages operation has finished.
+ * This is called regardless of the success/failure of the operation.
+ *
+ * @param aStatus Status code for the message send.
+ * @param aMsg A text string describing the error.
+ * @param aTotalTried Total number of messages that were attempted to be sent.
+ * @param aSuccessful How many messages were successfully sent.
+ */
+ void onStopSending(in nsresult aStatus, in wstring aMsg,
+ in unsigned long aTotalTried, in unsigned long aSuccessful);
+};
diff --git a/comm/mailnews/compose/public/nsIMsgSendListener.idl b/comm/mailnews/compose/public/nsIMsgSendListener.idl
new file mode 100644
index 0000000000..0bca8cceb0
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgSendListener.idl
@@ -0,0 +1,79 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsITransportSecurityInfo;
+
+[scriptable, uuid(D34DC178-5E78-45E8-8658-A8F52D9CCF5F)]
+interface nsIMsgSendListener : nsISupports {
+
+ /**
+ * Notify the observer that the message has started to be delivered. This method is
+ * called only once, at the beginning of a message send operation.
+ *
+ * @return The return value is currently ignored. In the future it may be
+ * used to cancel the URL load..
+ */
+ void onStartSending(in string aMsgID, in uint32_t aMsgSize);
+
+ /**
+ * Notify the observer that progress as occurred for the message send
+ */
+ void onProgress(in string aMsgID, in uint32_t aProgress, in uint32_t aProgressMax);
+
+ /**
+ * Notify the observer with a status message for the message send
+ */
+ void onStatus(in string aMsgID, in wstring aMsg);
+
+ /**
+ * Notify the observer that the message has been sent. This method is
+ * called once when the networking library has finished processing the
+ * message.
+ *
+ * This method is called regardless of whether the the operation was successful.
+ * aMsgID The message id for the mail message
+ * status Status code for the message send.
+ * msg A text string describing the error.
+ * returnFileSpec The returned file spec for save to file operations.
+ */
+ void onStopSending(in string aMsgID, in nsresult aStatus, in wstring aMsg,
+ in nsIFile aReturnFile);
+
+ /**
+ * Notify the observer with the message id and the folder uri before the draft
+ * is copied.
+ */
+ void onGetDraftFolderURI(in string aMsgID, in AUTF8String aFolderURI);
+
+ /**
+ * Notify the observer when the user aborts the send without actually doing the send
+ * eg : by closing the compose window without Send.
+ */
+ void onSendNotPerformed(in string aMsgID, in nsresult aStatus);
+
+ /**
+ * Notify that an NSS security error has occurred during the send
+ * (e.g. Bad Certificate or SSL version failure).
+ * This callback is invoked before onStopSending(), in case a listener
+ * needs the securityInfo - most likely to get at a failed certificate,
+ * allowing the user to add an exception.
+ * onStopSending() will still be called after this, so a listener
+ * which doesn't need special NSS handling can just leave this callback as
+ * an empty function and leave the handling to onStopSending().
+ *
+ * @param {string} msgID - The message ID.
+ * @param {nsresult} status - The error code (it will be in the NSS error
+ * code range).
+ * @param {nsITransportSecurityInfo} secInfo
+ * - Security info for the failed operation.
+ * @param {ACString} location - The location of the failed operation
+ * ("<host>:<port>")
+ */
+ void onTransportSecurityError(in string msgID, in nsresult status, in nsITransportSecurityInfo secInfo, in ACString location);
+
+};
diff --git a/comm/mailnews/compose/public/nsIMsgSendReport.idl b/comm/mailnews/compose/public/nsIMsgSendReport.idl
new file mode 100644
index 0000000000..bcdf8e7265
--- /dev/null
+++ b/comm/mailnews/compose/public/nsIMsgSendReport.idl
@@ -0,0 +1,47 @@
+/* -*- Mode: idl; 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/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+
+[scriptable, uuid(2ec81175-bc65-44b9-ba87-462bc3f938db)]
+interface nsIMsgProcessReport : nsISupports {
+
+ attribute boolean proceeded;
+ attribute nsresult error;
+ attribute wstring message;
+
+ void reset();
+};
+
+[scriptable, uuid(428c5bde-29f5-4bfe-830a-ec795a1c2975)]
+interface nsIMsgSendReport : nsISupports {
+
+ const long process_Current = -1;
+ const long process_BuildMessage = 0;
+ const long process_NNTP = 1;
+ const long process_SMTP = 2;
+ const long process_Copy = 3;
+ const long process_Filter = 4;
+ const long process_FCC = 5;
+
+ attribute long deliveryMode; /* see nsMsgDeliverMode in nsIMsgSend.idl for valid value */
+ attribute long currentProcess;
+
+ void reset();
+
+ void setProceeded(in long process, in boolean proceeded);
+ void setError(in long process, in nsresult error, in boolean overwriteError);
+ void setMessage(in long process, in wstring message, in boolean overwriteMessage);
+
+ nsIMsgProcessReport getProcessReport(in long process);
+
+ /* Display Report will ananlyze data collected during the send and will show the most appropriate error.
+ Also it will return the error code. In case of no error or if the error has been canceld, it will return
+ NS_OK.
+ */
+ nsresult displayReport(in mozIDOMWindowProxy prompt, in boolean showErrorOnly, in boolean dontShowReportTwice);
+};
diff --git a/comm/mailnews/compose/public/nsISmtpServer.idl b/comm/mailnews/compose/public/nsISmtpServer.idl
new file mode 100644
index 0000000000..6180e9289d
--- /dev/null
+++ b/comm/mailnews/compose/public/nsISmtpServer.idl
@@ -0,0 +1,151 @@
+/* -*- 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 "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIAuthPrompt;
+interface nsIUrlListener;
+interface nsIURI;
+interface nsIMsgWindow;
+
+/**
+ * This interface represents a single SMTP Server. A SMTP server instance may be
+ * created/obtained from nsIMsgAccountManager.
+ *
+ * Most of the attributes will set/get preferences from the main preferences
+ * file.
+ */
+[scriptable, uuid(a53dce6c-cd81-495c-83bc-45a65df1f08e)]
+interface nsISmtpServer : nsISupports {
+
+ /// A unique identifier for the server.
+ attribute string key;
+
+ /**
+ * A unique identifier for this server that can be used for the same
+ * server synced across multiple profiles. Auto-generated on first use.
+ */
+ attribute AUTF8String UID;
+
+ /// A user supplied description for the server.
+ attribute AUTF8String description;
+
+ /// The server's hostname.
+ attribute AUTF8String hostname;
+
+ /// The server's port.
+ attribute int32_t port;
+
+ /// The username to access the server with (if required)
+ attribute ACString username;
+
+ /**
+ * The CLIENTID to use for this server (if required).
+ * @see https://tools.ietf.org/html/draft-storey-smtp-client-id-05
+ */
+ attribute ACString clientid;
+
+ /**
+ * Whether the CLIENTID feature above is enabled.
+ */
+ attribute boolean clientidEnabled;
+
+ /**
+ * The password to access the server with (if required).
+ *
+ * @note this is stored within the server instance but not within preferences.
+ * It can be specified/saved here to avoid prompting the user constantly for
+ * the sending password.
+ */
+ attribute AString password;
+
+ /// Returns a displayname of the format hostname:port or just hostname
+ readonly attribute string displayname;
+
+ /**
+ * Authentication mechanism.
+ *
+ * @see nsMsgAuthMethod (in MailNewsTypes2.idl)
+ * Same as "mail.smtpserver...authMethod" pref
+ *
+ * Compatibility note: This attribute had a different meaning in TB < 3.1
+ */
+ attribute nsMsgAuthMethodValue authMethod;
+
+ /**
+ * Whether to SSL or STARTTLS or not
+ *
+ * @see nsMsgSocketType (in MailNewsTypes2.idl)
+ * Same as "mail.smtpserver...try_ssl" pref
+ */
+ attribute nsMsgSocketTypeValue socketType;
+
+ /**
+ * May contain an alternative argument to EHLO or HELO to provide to the
+ * server. Reflects the value of the mail.smtpserver.*.hello_argument pref.
+ * This is mainly useful where ISPs don't bother providing PTR records for
+ * their servers and therefore users get an error on sending. See bug 244030
+ * for more discussion.
+ */
+ readonly attribute ACString helloArgument;
+
+ /// Returns the URI of the server (smtp:///)
+ readonly attribute AUTF8String serverURI;
+
+ /** Limit of concurrent connections to a server. */
+ attribute long maximumConnectionsNumber;
+
+ /** Close cached server connections. */
+ void closeCachedConnections();
+
+ /**
+ * Gets a password for this server, using a UI prompt if necessary.
+ *
+ * @param promptString The string to prompt the user with when asking for
+ * the password.
+ * @param promptTitle The title of the prompt.
+ * @return The password to use (may be null if no password was
+ * obtained).
+ */
+ AString getPasswordWithUI(in wstring promptString, in wstring promptTitle);
+
+ /**
+ * Gets a username and password for this server, using a UI prompt if
+ * necessary.
+ *
+ * @param promptString The string to prompt the user with when asking for
+ * the password.
+ * @param promptTitle The title of the prompt.
+ * @param netPrompt An nsIAuthPrompt instance to use for the password
+ * prompt.
+ * @param userid The username to use (may be null if no password was
+ * obtained).
+ * @param password The password to use (may be empty if no password was
+ * obtained).
+ */
+ void getUsernamePasswordWithUI(in wstring promptString, in wstring promptTitle,
+ in nsIAuthPrompt netPrompt, out ACString userid,
+ out AString password);
+
+ /**
+ * Calling this will *remove* the saved password for this server from the
+ * password manager and from the stored value.
+ */
+ void forgetPassword();
+
+ /**
+ * Verify that we can logon
+ *
+ * @param aPassword - password to use
+ * @param aUrlListener - gets called back with success or failure.
+ * @return - the url that we run.
+ *
+ */
+ nsIURI verifyLogon(in nsIUrlListener aUrlListener, in nsIMsgWindow aMsgWindow);
+
+ /// Call this to clear all preference values for this server.
+ void clearAllValues();
+};
diff --git a/comm/mailnews/compose/public/nsISmtpService.idl b/comm/mailnews/compose/public/nsISmtpService.idl
new file mode 100644
index 0000000000..0fc306fe11
--- /dev/null
+++ b/comm/mailnews/compose/public/nsISmtpService.idl
@@ -0,0 +1,134 @@
+/* -*- 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 "nsISupports.idl"
+
+interface nsISmtpServer;
+interface nsIURI;
+interface nsIUrlListener;
+interface nsIMsgIdentity;
+interface nsIInterfaceRequestor;
+interface nsIFile;
+interface nsIMsgStatusFeedback;
+interface nsIRequest;
+interface nsIMsgWindow;
+
+[scriptable, uuid(1b11b532-1527-4fc0-a00f-4ce7e6886419)]
+interface nsISmtpService : nsISupports {
+ /**
+ * Sends a mail message via the given parameters. This function builds an
+ * SMTP URL and makes an SMTP connection, and then runs the url.
+ * The SMTP server defined
+ * in the aSenderIdentity object (see nsIMsgIdentity) will be used to send
+ * the message. If there is no SMTP server defined in aSenderIdentity, the
+ * default SMTP server will be used.
+ *
+ * @note The file to send must be in the format specified by RFC 2822 for
+ * sending data. This includes having the correct CRLF line endings
+ * throughout the file, and the <CRLF>.<CRLF> at the end of the file.
+ * sendMailMessage does no processing/additions on the file.
+ *
+ * @param aFilePath The file to send.
+ * @param aRecipients A comma delimited list of recipients.
+ * @param aSenderIdentity The identity of the sender.
+ * @param aSender The senders email address.
+ * @param aPassword Pass this in to prevent a dialog if the
+ * password is needed for secure transmission.
+ * @param aUrlListener A listener to listen to the URL being run,
+ * this parameter may be null.
+ * @param aStatusListener A feedback listener for slightly different
+ * feedback on the message send status. This
+ * parameter may be null.
+ * @param aNotificationCallbacks More notification callbacks
+ * @param aRequestDSN Pass true to request Delivery Status
+ * Notification.
+ * @param aMessageId The message id can be used as ENVID for DSN.
+ * @param aURL Provides a handle on the running url. You
+ * can later interrupt the action by asking the
+ * netlib service manager to interrupt the url
+ * you are given back. This parameter may be
+ * null.
+ * @param aRequest Provides a handle to the running request.
+ * This parameter may be null.
+ */
+ void sendMailMessage(in nsIFile aFilePath, in string aRecipients,
+ in nsIMsgIdentity aSenderIdentity,
+ in string aSender,
+ in AString aPassword,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgStatusFeedback aStatusListener,
+ in nsIInterfaceRequestor aNotificationCallbacks,
+ in boolean aRequestDSN,
+ in ACString aMessageId,
+ out nsIURI aURL,
+ out nsIRequest aRequest);
+
+ /**
+ * Verifies that we can logon to the server with given password
+ *
+ * @param aSmtpServer Server to try to logon to.
+ * @param aUrlListener Listener that will get notified whether logon
+ * was successful or not.
+ * @param aMsgWindow nsIMsgWindow to use for notification callbacks.
+ * @return - the url that we run.
+ */
+ nsIURI verifyLogon(in nsISmtpServer aServer, in nsIUrlListener aListener,
+ in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Return the SMTP server that is associated with an identity.
+ * @param aSenderIdentity the identity
+ */
+ nsISmtpServer getServerByIdentity(in nsIMsgIdentity aSenderIdentity);
+
+ /**
+ * A copy of the array of SMTP servers, as stored in the preferences
+ */
+ readonly attribute Array<nsISmtpServer> servers;
+
+ /**
+ * The default server, across sessions of the app
+ * (eventually there will be a session default which does not
+ * persist past shutdown)
+ */
+ attribute nsISmtpServer defaultServer;
+
+ /**
+ * The "session default" server - this is never saved, and only used
+ * for the current session. Always falls back to the default server
+ * unless explicitly set.
+ */
+ attribute nsISmtpServer sessionDefaultServer;
+
+ /**
+ * Create a new SMTP server.
+ * Use this instead of createInstance(), so that the SMTP Service can
+ * be aware of this server
+ */
+ nsISmtpServer createServer();
+
+ /**
+ * Find the first server with the given hostname and/or username.
+ * Note: if either username or hostname is empty, then that parameter will
+ * not be used in the matching process.
+ * @param username the username for the server
+ * @param hostname the hostname of the server
+ * @returns null if no server is found
+ */
+ nsISmtpServer findServer(in string username, in string hostname);
+
+ /**
+ * Look up the server with the given key.
+ */
+ nsISmtpServer getServerByKey(in string key);
+
+ /**
+ * Delete the given server from the server list - does nothing if the server
+ * does not exist
+ * @param server the server to delete. Use findServer() if you only know
+ * the hostname
+ */
+ void deleteServer(in nsISmtpServer server);
+};
diff --git a/comm/mailnews/compose/public/nsISmtpUrl.idl b/comm/mailnews/compose/public/nsISmtpUrl.idl
new file mode 100644
index 0000000000..c03bc7f4e6
--- /dev/null
+++ b/comm/mailnews/compose/public/nsISmtpUrl.idl
@@ -0,0 +1,115 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIMsgComposeParams.idl"
+
+interface nsIMsgIdentity;
+interface nsIPrompt;
+interface nsIAuthPrompt;
+interface nsISmtpServer;
+interface nsIInterfaceRequestor;
+interface nsIFile;
+
+[scriptable, uuid(da22b8ac-059d-4f82-bf99-f5f3d3c8202d)]
+interface nsISmtpUrl : nsISupports {
+ /**
+ * SMTP Parse specific getters.
+ * These retrieve various parts from the url.
+ */
+
+ /**
+ * This list is a list of all recipients to send the email to.
+ * Each name is NULL terminated.
+ */
+ attribute string recipients;
+
+ attribute boolean PostMessage;
+
+ /**
+ * The message can be stored in a file, to allow accessors for getting and
+ * setting the file name to post.
+ */
+ attribute nsIFile postMessageFile;
+
+ attribute boolean requestDSN;
+
+ /**
+ * The envid which is used in the DSN.
+ */
+ attribute ACString dsnEnvid;
+
+ /**
+ * The sender of this mail (can be different from identity).
+ */
+ attribute string sender;
+
+ /**
+ * SMTP Url instance specific getters and setters
+ * Information the protocol needs to know in order to run the url.
+ * These are NOT event sinks which are things the caller needs to know.
+ */
+
+ /**
+ * By default the url is really a bring up the compose window mailto url.
+ * You need to call this function if you want to force the message to be
+ * posted to the mailserver.
+ */
+
+ /**
+ * The user's full name and user's email address are encapsulated in the
+ * senderIdentity.
+ * (the user's domain name can be glopped from the user's email address)
+ *
+ * NOTE: the SMTP username and SMTP server are in the smtp url
+ * smtp://sspitzer@tintin/...
+ */
+ attribute nsIMsgIdentity senderIdentity;
+ attribute nsIPrompt prompt;
+ attribute nsIAuthPrompt authPrompt;
+ attribute nsIInterfaceRequestor notificationCallbacks;
+ attribute nsISmtpServer smtpServer;
+
+ attribute boolean verifyLogon; // we're just verifying the ability to logon
+
+ /// Constant for the default SMTP port number
+ const int32_t DEFAULT_SMTP_PORT = 25;
+
+ /// Constant for the default SMTP over ssl port number
+ const int32_t DEFAULT_SMTPS_PORT = 465;
+};
+
+[scriptable, uuid(87c36c23-4bc2-4992-b338-69f88f6ed0a1)]
+interface nsIMailtoUrl : nsISupports {
+ /**
+ * mailto: parse specific getters
+ *
+ * All of these fields are things we can effectively extract from a
+ * mailto url if it contains all of these values
+ *
+ * Note: Attachments aren't available because that would expose a potential
+ * security hole (see bug 99055).
+ *
+ * These items are in one function as we only ever get them from the one
+ * place and all at the same time.
+ */
+ void getMessageContents(out AUTF8String aToPart, out AUTF8String aCcPart,
+ out AUTF8String aBccPart, out AUTF8String aSubjectPart,
+ out AUTF8String aBodyPart, out AUTF8String aHtmlPart,
+ out ACString aReferencePart,
+ out AUTF8String aNewsgroupPart,
+ out MSG_ComposeFormat aFormat);
+
+ /**
+ * These attributes are available should mailnews or extensions want them
+ * but aren't used by standard in mailnews.
+ */
+ readonly attribute AUTF8String fromPart;
+ readonly attribute AUTF8String followUpToPart;
+ readonly attribute AUTF8String organizationPart;
+ readonly attribute AUTF8String replyToPart;
+ readonly attribute AUTF8String priorityPart;
+ readonly attribute AUTF8String newsHostPart;
+};
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__
diff --git a/comm/mailnews/compose/test/moz.build b/comm/mailnews/compose/test/moz.build
new file mode 100644
index 0000000000..512bee25fc
--- /dev/null
+++ b/comm/mailnews/compose/test/moz.build
@@ -0,0 +1,8 @@
+# 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/.
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "unit/xpcshell.ini",
+]
diff --git a/comm/mailnews/compose/test/unit/data/429891_testcase.eml b/comm/mailnews/compose/test/unit/data/429891_testcase.eml
new file mode 100644
index 0000000000..b4fb4164c9
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/data/429891_testcase.eml
@@ -0,0 +1,384 @@
+From: Invalid User <from_A@foo.invalid>
+To: =?UTF-8?B?RnLDqcOpZGxlLCBUZXN0?= <to_A@foo.invalid>
+Subject: Big email
+
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
diff --git a/comm/mailnews/compose/test/unit/data/binary-after-plain.txt b/comm/mailnews/compose/test/unit/data/binary-after-plain.txt
new file mode 100644
index 0000000000..cec0697428
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/data/binary-after-plain.txt
Binary files differ
diff --git a/comm/mailnews/compose/test/unit/data/listexpansion.sql b/comm/mailnews/compose/test/unit/data/listexpansion.sql
new file mode 100644
index 0000000000..6c4f6491be
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/data/listexpansion.sql
@@ -0,0 +1,126 @@
+-- Address book with nested mailing lists for use in test_expandMailingLists.js.
+PRAGMA user_version = 1;
+
+CREATE TABLE cards (uid TEXT PRIMARY KEY, localId INTEGER);
+CREATE TABLE properties (card TEXT, name TEXT, value TEXT);
+CREATE TABLE lists (uid TEXT PRIMARY KEY, localId INTEGER, name TEXT, nickName TEXT, description TEXT);
+CREATE TABLE list_cards (list TEXT, card TEXT, PRIMARY KEY(list, card));
+
+INSERT INTO cards (uid, localId) VALUES
+ ('813155c6-924d-4751-95d0-70d8e64f16bc', 1), -- homer
+ ('b2cc8395-d959-45e4-9516-17457adb16fa', 2), -- marge
+ ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 3), -- bart
+ ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 4), -- lisa
+ ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 5), -- maggie
+ ('5ec12f1d-7ee9-403c-a617-48596dacbc18', 6), --simpson
+ ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', 7), --marge
+ ('ad305609-3535-4d51-8c96-cd82d93aed46', 8), --family
+ ('4808121d-ebad-4564-864d-8f1149aa053b', 9), --kids
+ ('4926ff7a-e929-475a-8aa8-2baac994390c', 10), --parents
+ ('84fa4513-9b60-4379-ade7-1e4b48d67c84', 11), --older-kids
+ ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', 12), --bad-kids
+ ('34e60324-4fb6-4f10-ab1b-333b07680228', 13); --bad-younger-kids
+
+INSERT INTO properties (card, name, value) VALUES
+ ('813155c6-924d-4751-95d0-70d8e64f16bc', 'PrimaryEmail', 'homer@example.com'),
+ ('813155c6-924d-4751-95d0-70d8e64f16bc', 'PhotoType', 'generic'),
+ ('813155c6-924d-4751-95d0-70d8e64f16bc', 'LowercasePrimaryEmail', 'homer@example.com'),
+ ('813155c6-924d-4751-95d0-70d8e64f16bc', 'DisplayName', 'Simpson'),
+ ('813155c6-924d-4751-95d0-70d8e64f16bc', 'LastModifiedDate', '1473722922'),
+ ('813155c6-924d-4751-95d0-70d8e64f16bc', 'PopularityIndex', '0'),
+ ('813155c6-924d-4751-95d0-70d8e64f16bc', 'PreferMailFormat', '0'),
+ ('813155c6-924d-4751-95d0-70d8e64f16bc', 'PreferDisplayName', '1'),
+
+ ('b2cc8395-d959-45e4-9516-17457adb16fa', 'DisplayName', 'Marge'),
+ ('b2cc8395-d959-45e4-9516-17457adb16fa', 'PrimaryEmail', 'marge@example.com'),
+ ('b2cc8395-d959-45e4-9516-17457adb16fa', 'PhotoType', 'generic'),
+ ('b2cc8395-d959-45e4-9516-17457adb16fa', 'LowercasePrimaryEmail', 'marge@example.com'),
+ ('b2cc8395-d959-45e4-9516-17457adb16fa', 'LastModifiedDate', '1473723020'),
+ ('b2cc8395-d959-45e4-9516-17457adb16fa', 'PopularityIndex', '0'),
+ ('b2cc8395-d959-45e4-9516-17457adb16fa', 'PreferMailFormat', '0'),
+ ('b2cc8395-d959-45e4-9516-17457adb16fa', 'PreferDisplayName', '1'),
+
+ ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'PhotoType', 'generic'),
+ ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'PopularityIndex', '0'),
+ ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'PreferMailFormat', '0'),
+ ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'PreferDisplayName', '1'),
+ ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'DisplayName', 'Bart'),
+ ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'PrimaryEmail', 'bart@foobar.invalid'),
+ ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'LowercasePrimaryEmail', 'bart@foobar.invalid'),
+ ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'SecondEmail', 'bart@example.com'),
+ ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'LowercaseSecondEmail', 'bart@example.com'),
+ ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'LastModifiedDate', '1473716192'),
+
+ ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'PrimaryEmail', 'lisa@example.com'),
+ ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'PhotoType', 'generic'),
+ ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'LowercasePrimaryEmail', 'lisa@example.com'),
+ ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'DisplayName', 'lisa@example.com'),
+ ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'PopularityIndex', '0'),
+ ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'PreferMailFormat', '0'),
+ ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'LastModifiedDate', '0'),
+ ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'PreferDisplayName', '1'),
+
+ ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'DisplayName', 'Maggie'),
+ ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'LastModifiedDate', '1473723047'),
+ ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'PrimaryEmail', 'maggie@example.com'),
+ ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'PhotoType', 'generic'),
+ ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'LowercasePrimaryEmail', 'maggie@example.com'),
+ ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'PopularityIndex', '0'),
+ ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'PreferMailFormat', '0'),
+ ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'PreferDisplayName', '1'),
+
+ ('5ec12f1d-7ee9-403c-a617-48596dacbc18', 'DisplayName', 'simpson'),
+ ('5ec12f1d-7ee9-403c-a617-48596dacbc18', 'PrimaryEmail', 'simpson'),
+ ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', 'DisplayName', 'marge'),
+ ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', 'PrimaryEmail', 'marge'),
+ ('ad305609-3535-4d51-8c96-cd82d93aed46', 'DisplayName', 'family'),
+ ('ad305609-3535-4d51-8c96-cd82d93aed46', 'PrimaryEmail', 'family'),
+ ('4808121d-ebad-4564-864d-8f1149aa053b', 'DisplayName', 'kids'),
+ ('4808121d-ebad-4564-864d-8f1149aa053b', 'PrimaryEmail', 'kids'),
+ ('4926ff7a-e929-475a-8aa8-2baac994390c', 'DisplayName', 'parents'),
+ ('4926ff7a-e929-475a-8aa8-2baac994390c', 'PrimaryEmail', 'parents'),
+ ('84fa4513-9b60-4379-ade7-1e4b48d67c84', 'PrimaryEmail', 'older-kids'),
+ ('84fa4513-9b60-4379-ade7-1e4b48d67c84', 'DisplayName', 'older-kids'),
+ ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', 'DisplayName', 'bad-kids'),
+ ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', 'PrimaryEmail', 'bad-kids'),
+ ('34e60324-4fb6-4f10-ab1b-333b07680228', 'DisplayName', 'bad-younger-kids'),
+ ('34e60324-4fb6-4f10-ab1b-333b07680228', 'PrimaryEmail', 'bad-younger-kids');
+
+INSERT INTO lists (uid, localId, name, nickName, description) VALUES
+ ('5ec12f1d-7ee9-403c-a617-48596dacbc18', 1, 'simpson', '', ''),
+ ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', 2, 'marge', '', 'marges own list'),
+ ('ad305609-3535-4d51-8c96-cd82d93aed46', 3, 'family', '', ''),
+ ('4808121d-ebad-4564-864d-8f1149aa053b', 4, 'kids', '', ''),
+ ('4926ff7a-e929-475a-8aa8-2baac994390c', 5, 'parents', '', ''),
+ ('84fa4513-9b60-4379-ade7-1e4b48d67c84', 6, 'older-kids', '', ''),
+ ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', 7, 'bad-kids', '', ''),
+ ('34e60324-4fb6-4f10-ab1b-333b07680228', 8, 'bad-younger-kids', '', '');
+
+INSERT INTO list_cards (list, card) VALUES
+ -- simpson
+ ('5ec12f1d-7ee9-403c-a617-48596dacbc18', '813155c6-924d-4751-95d0-70d8e64f16bc'), -- homer
+ ('5ec12f1d-7ee9-403c-a617-48596dacbc18', 'b2cc8395-d959-45e4-9516-17457adb16fa'), -- marge
+ ('5ec12f1d-7ee9-403c-a617-48596dacbc18', '979f194e-49f2-4bbb-b364-598cdc6a7d11'), -- bart
+ ('5ec12f1d-7ee9-403c-a617-48596dacbc18', '4dd13a79-b70c-4b43-bdba-bacd4e977c1b'), -- lisa
+ -- marge
+ ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', '813155c6-924d-4751-95d0-70d8e64f16bc'), -- homer
+ ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', 'b2cc8395-d959-45e4-9516-17457adb16fa'), -- marge
+ -- family
+ ('ad305609-3535-4d51-8c96-cd82d93aed46', '4926ff7a-e929-475a-8aa8-2baac994390c'), -- parents
+ ('ad305609-3535-4d51-8c96-cd82d93aed46', '4808121d-ebad-4564-864d-8f1149aa053b'), -- kids
+ -- parents
+ ('4926ff7a-e929-475a-8aa8-2baac994390c', '813155c6-924d-4751-95d0-70d8e64f16bc'), -- homer
+ ('4926ff7a-e929-475a-8aa8-2baac994390c', 'b2cc8395-d959-45e4-9516-17457adb16fa'), -- marge
+ ('4926ff7a-e929-475a-8aa8-2baac994390c', '4926ff7a-e929-475a-8aa8-2baac994390c'), -- parents
+ -- kids
+ ('4808121d-ebad-4564-864d-8f1149aa053b', '84fa4513-9b60-4379-ade7-1e4b48d67c84'), -- older-kids
+ ('4808121d-ebad-4564-864d-8f1149aa053b', 'c96402d7-1c7b-4242-a35c-b92c8ec9dfa2'), -- maggie
+ -- older-kids
+ ('84fa4513-9b60-4379-ade7-1e4b48d67c84', '4dd13a79-b70c-4b43-bdba-bacd4e977c1b'), -- lisa
+ ('84fa4513-9b60-4379-ade7-1e4b48d67c84', '979f194e-49f2-4bbb-b364-598cdc6a7d11'), -- bart
+ -- bad-kids
+ ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', '84fa4513-9b60-4379-ade7-1e4b48d67c84'), -- older-kids
+ ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', '34e60324-4fb6-4f10-ab1b-333b07680228'), -- bad-younger-kids
+ -- bad-younger-kids
+ ('34e60324-4fb6-4f10-ab1b-333b07680228', 'c96402d7-1c7b-4242-a35c-b92c8ec9dfa2'), -- maggie
+ ('34e60324-4fb6-4f10-ab1b-333b07680228', '8e88b9a4-2500-48e0-bcea-b1fa4eab6b72'); -- bad-kids
diff --git a/comm/mailnews/compose/test/unit/data/message1.eml b/comm/mailnews/compose/test/unit/data/message1.eml
new file mode 100644
index 0000000000..7913f5f262
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/data/message1.eml
@@ -0,0 +1,7 @@
+From: from_B@foo.invalid
+To: to_B@foo.invalid
+Subject: test mail
+
+this email is in dos format because that is what the interface requires
+
+test message
diff --git a/comm/mailnews/compose/test/unit/data/shift-jis.eml b/comm/mailnews/compose/test/unit/data/shift-jis.eml
new file mode 100644
index 0000000000..58f583907d
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/data/shift-jis.eml
@@ -0,0 +1,13 @@
+To: test@example.com
+From: test@example.com
+Subject: ISO-2022-JP and 7bit containing =67 and hence looking like quoted-printable
+Message-ID: <10a2aa17-e92f-417c-864e-575d4e371702@example.com>
+Date: Tue, 3 Apr 2018 19:09:16 +0900
+User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101
+ Thunderbird/52.6.0
+MIME-Version: 1.0
+Content-Type: text/plain; charset=SHIFT-JIS; format=flowed
+Content-Language: ja-JP
+Content-Transfer-Encoding: 7bit
+
+
diff --git a/comm/mailnews/compose/test/unit/data/test-ISO-2022-JP.txt b/comm/mailnews/compose/test/unit/data/test-ISO-2022-JP.txt
new file mode 100644
index 0000000000..cd370be3f8
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/data/test-ISO-2022-JP.txt
@@ -0,0 +1 @@
+$B%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H(B
diff --git a/comm/mailnews/compose/test/unit/data/test-KOI8-R.txt b/comm/mailnews/compose/test/unit/data/test-KOI8-R.txt
new file mode 100644
index 0000000000..91f77cae45
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/data/test-KOI8-R.txt
@@ -0,0 +1,2 @@
+ , , , ,
+ .
diff --git a/comm/mailnews/compose/test/unit/data/test-SHIFT_JIS.txt b/comm/mailnews/compose/test/unit/data/test-SHIFT_JIS.txt
new file mode 100644
index 0000000000..7a7f267540
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/data/test-SHIFT_JIS.txt
@@ -0,0 +1 @@
+Shift_JIS̃eLXgt@CłB
diff --git a/comm/mailnews/compose/test/unit/data/test-UTF-16BE.txt b/comm/mailnews/compose/test/unit/data/test-UTF-16BE.txt
new file mode 100644
index 0000000000..dd5fd39ed2
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/data/test-UTF-16BE.txt
Binary files differ
diff --git a/comm/mailnews/compose/test/unit/data/test-UTF-16LE.txt b/comm/mailnews/compose/test/unit/data/test-UTF-16LE.txt
new file mode 100644
index 0000000000..a13a8f09e1
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/data/test-UTF-16LE.txt
Binary files differ
diff --git a/comm/mailnews/compose/test/unit/data/test-UTF-8.txt b/comm/mailnews/compose/test/unit/data/test-UTF-8.txt
new file mode 100644
index 0000000000..b5e9df9a45
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/data/test-UTF-8.txt
@@ -0,0 +1 @@
+测试文件
diff --git a/comm/mailnews/compose/test/unit/data/test-windows-1252.txt b/comm/mailnews/compose/test/unit/data/test-windows-1252.txt
new file mode 100644
index 0000000000..a98046517a
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/data/test-windows-1252.txt
@@ -0,0 +1,2 @@
+Buenos das - Franois a t Paris - Budapester Strae, Berlin.
+This is text in windows-1252.
diff --git a/comm/mailnews/compose/test/unit/head_compose.js b/comm/mailnews/compose/test/unit/head_compose.js
new file mode 100644
index 0000000000..6f874335e5
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/head_compose.js
@@ -0,0 +1,280 @@
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+var { mailTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MailTestUtils.jsm"
+);
+var { localAccountUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/LocalAccountUtils.jsm"
+);
+
+var CC = Components.Constructor;
+
+// WebApps.jsm called by ProxyAutoConfig (PAC) requires a valid nsIXULAppInfo.
+var { getAppInfo, newAppInfo, updateAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+updateAppInfo();
+
+// Ensure the profile directory is set up
+do_get_profile();
+
+var gDEPTH = "../../../../";
+
+// Import the required setup scripts.
+
+/* import-globals-from ../../../test/resources/abSetup.js */
+load("../../../resources/abSetup.js");
+
+// Import the smtp server scripts
+var {
+ nsMailServer,
+ gThreadManager,
+ fsDebugNone,
+ fsDebugAll,
+ fsDebugRecv,
+ fsDebugRecvSend,
+} = ChromeUtils.import("resource://testing-common/mailnews/Maild.jsm");
+var { SmtpDaemon, SMTP_RFC2821_handler } = ChromeUtils.import(
+ "resource://testing-common/mailnews/Smtpd.jsm"
+);
+var { AuthPLAIN, AuthLOGIN, AuthCRAM } = ChromeUtils.import(
+ "resource://testing-common/mailnews/Auth.jsm"
+);
+
+var gDraftFolder;
+
+// Setup the daemon and server
+function setupServerDaemon(handler) {
+ if (!handler) {
+ handler = function (d) {
+ return new SMTP_RFC2821_handler(d);
+ };
+ }
+ var server = new nsMailServer(handler, new SmtpDaemon());
+ return server;
+}
+
+function getBasicSmtpServer(port = 1, hostname = "localhost") {
+ let server = localAccountUtils.create_outgoing_server(
+ port,
+ "user",
+ "password",
+ hostname
+ );
+
+ // Override the default greeting so we get something predicitable
+ // in the ELHO message
+ Services.prefs.setCharPref("mail.smtpserver.default.hello_argument", "test");
+
+ return server;
+}
+
+function getSmtpIdentity(senderName, smtpServer) {
+ // Set up the identity
+ let identity = MailServices.accounts.createIdentity();
+ identity.email = senderName;
+ identity.smtpServerKey = smtpServer.key;
+
+ return identity;
+}
+
+var test;
+
+function do_check_transaction(real, expected) {
+ if (Array.isArray(real)) {
+ real = real.at(-1);
+ }
+ // real.them may have an extra QUIT on the end, where the stream is only
+ // closed after we have a chance to process it and not them. We therefore
+ // excise this from the list
+ if (real.them[real.them.length - 1] == "QUIT") {
+ real.them.pop();
+ }
+
+ Assert.equal(real.them.join(","), expected.join(","));
+ dump("Passed test " + test + "\n");
+}
+
+// This listener is designed just to call OnStopCopy() when its OnStopCopy
+// function is called - the rest of the functions are unneeded for a lot of
+// tests (but we can't use asyncCopyListener because we need the
+// nsIMsgSendListener interface as well).
+var copyListener = {
+ // nsIMsgSendListener
+ onStartSending(aMsgID, aMsgSize) {},
+ onProgress(aMsgID, aProgress, aProgressMax) {},
+ onStatus(aMsgID, aMsg) {},
+ onStopSending(aMsgID, aStatus, aMsg, aReturnFile) {},
+ onGetDraftFolderURI(aMsgID, aFolderURI) {},
+ onSendNotPerformed(aMsgID, aStatus) {},
+ onTransportSecurityError(msgID, status, secInfo, location) {},
+
+ // nsIMsgCopyServiceListener
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {},
+ GetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ /* globals OnStopCopy */
+ OnStopCopy(aStatus);
+ },
+
+ // QueryInterface
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIMsgSendListener",
+ "nsIMsgCopyServiceListener",
+ ]),
+};
+
+var progressListener = {
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ this.resolve(gDraftFolder && mailTestUtils.firstMsgHdr(gDraftFolder));
+ }
+ },
+
+ onProgressChange(
+ aWebProgress,
+ aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress
+ ) {},
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {},
+ onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {},
+ onSecurityChange(aWebProgress, aRequest, state) {},
+ onContentBlockingEvent(aWebProgress, aRequest, aEvent) {},
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+};
+
+function createMessage(aAttachment) {
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ fields.from = "Nobody <nobody@tinderbox.test>";
+
+ let attachments = [];
+ if (aAttachment) {
+ let attachment = Cc[
+ "@mozilla.org/messengercompose/attachment;1"
+ ].createInstance(Ci.nsIMsgAttachment);
+ if (aAttachment instanceof Ci.nsIFile) {
+ attachment.url = "file://" + aAttachment.path;
+ attachment.contentType = "text/plain";
+ attachment.name = aAttachment.leafName;
+ } else {
+ attachment.url = "data:,sometext";
+ attachment.name = aAttachment;
+ }
+ attachments = [attachment];
+ }
+ return richCreateMessage(fields, attachments);
+}
+
+function richCreateMessage(
+ fields,
+ attachments = [],
+ identity = null,
+ account = null
+) {
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ params.composeFields = fields;
+
+ let msgCompose = MailServices.compose.initCompose(params);
+ if (identity === null) {
+ identity = getSmtpIdentity(null, getBasicSmtpServer());
+ }
+
+ let rootFolder = localAccountUtils.rootFolder;
+ gDraftFolder = null;
+ // Make sure the drafts folder is empty
+ try {
+ gDraftFolder = rootFolder.getChildNamed("Drafts");
+ } catch (e) {
+ // we don't have to remove the folder because it doesn't exist yet
+ gDraftFolder = rootFolder.createLocalSubfolder("Drafts");
+ }
+ // Clear all messages
+ let msgs = [...gDraftFolder.msgDatabase.enumerateMessages()];
+ if (msgs.length > 0) {
+ gDraftFolder.deleteMessages(msgs, null, true, false, null, false);
+ }
+
+ // Set attachment
+ fields.removeAttachments();
+ for (let attachment of attachments) {
+ fields.addAttachment(attachment);
+ }
+
+ let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance(
+ Ci.nsIMsgProgress
+ );
+ let promise = new Promise((resolve, reject) => {
+ progressListener.resolve = resolve;
+ progressListener.reject = reject;
+ });
+ progress.registerListener(progressListener);
+ msgCompose.sendMsg(
+ Ci.nsIMsgSend.nsMsgSaveAsDraft,
+ identity,
+ account ? account.key : "",
+ null,
+ progress
+ );
+ return promise;
+}
+
+function getAttachmentFromContent(aContent) {
+ function getBoundaryStringFromContent() {
+ let found = aContent.match(
+ /Content-Type: multipart\/mixed;\s+boundary="(.*?)"/
+ );
+ Assert.notEqual(found, null);
+ Assert.equal(found.length, 2);
+
+ return found[1];
+ }
+
+ let boundary = getBoundaryStringFromContent(aContent);
+ let regex = new RegExp(
+ "\\r\\n\\r\\n--" +
+ boundary +
+ "\\r\\n" +
+ "([\\s\\S]*?)\\r\\n" +
+ "--" +
+ boundary +
+ "--",
+ "m"
+ );
+ let attachments = aContent.match(regex);
+ Assert.notEqual(attachments, null);
+ Assert.equal(attachments.length, 2);
+ return attachments[1];
+}
+
+/**
+ * Get the body part of an MIME message.
+ *
+ * @param {string} content - The message content.
+ * @returns {string}
+ */
+function getMessageBody(content) {
+ let separatorIndex = content.indexOf("\r\n\r\n");
+ Assert.equal(content.slice(-2), "\r\n", "Should end with a line break.");
+ return content.slice(separatorIndex + 4, -2);
+}
+
+registerCleanupFunction(function () {
+ load(gDEPTH + "mailnews/resources/mailShutdown.js");
+});
diff --git a/comm/mailnews/compose/test/unit/test_accountKey.js b/comm/mailnews/compose/test/unit/test_accountKey.js
new file mode 100644
index 0000000000..440b2ea78a
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_accountKey.js
@@ -0,0 +1,80 @@
+/* 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/. */
+
+let MockNntpService = {
+ QueryInterface: ChromeUtils.generateQI(["nsINntpService"]),
+ postMessage(messageFile, groupNames, accountKey, urlListener, msgWindow) {
+ this.messageFile = messageFile;
+ this.groupNames = groupNames;
+ this.accountKey = accountKey;
+ },
+};
+
+let MockNntpServiceFactory = {
+ createInstance(aIID) {
+ return MockNntpService;
+ },
+};
+
+add_setup(async function () {
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(
+ Components.ID("{4816dd44-fe15-4719-8cfb-a2f8ee46d787}"),
+ "Mock NntpService",
+ "@mozilla.org/messenger/nntpservice;1",
+ MockNntpServiceFactory
+ );
+});
+
+/**
+ * Test that when accountKey is not passed to sendMessageFile, MessageSend can
+ * get the right account key from identity.
+ */
+add_task(async function testAccountKey() {
+ // Set up the servers.
+ let server = setupServerDaemon();
+ localAccountUtils.loadLocalMailAccount();
+ server.start();
+ let smtpServer = getBasicSmtpServer(server.port);
+ let identity = getSmtpIdentity("from@foo.invalid", smtpServer);
+ let account = MailServices.accounts.createAccount();
+ account.addIdentity(identity);
+ account.incomingServer = MailServices.accounts.createIncomingServer(
+ "test",
+ "localhost",
+ "pop3"
+ );
+
+ // Init nsIMsgSend and fields.
+ let msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance(
+ Ci.nsIMsgSend
+ );
+ let compFields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ compFields.from = identity.email;
+ // Set the newsgroups filed so that the message will be passed to NntpService.
+ compFields.newsgroups = "foo.test";
+
+ let testFile = do_get_file("data/message1.eml");
+ // Notice the second argument is accountKey.
+ await msgSend.sendMessageFile(
+ identity,
+ "",
+ compFields,
+ testFile,
+ false,
+ false,
+ Ci.nsIMsgSend.nsMsgDeliverNow,
+ null,
+ copyListener,
+ null,
+ null
+ );
+
+ // Make sure the messageFile passed to NntpService is the file we set above.
+ equal(MockNntpService.messageFile, testFile);
+ // Test accountKey passed to NntpService is correct.
+ equal(MockNntpService.accountKey, account.key);
+});
diff --git a/comm/mailnews/compose/test/unit/test_attachment.js b/comm/mailnews/compose/test/unit/test_attachment.js
new file mode 100644
index 0000000000..f0c5a4d91d
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_attachment.js
@@ -0,0 +1,171 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for attachment file name.
+ */
+
+var input0 =
+ " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" +
+ "`abcdefghijklmnopqrstuvwxyz{|}~" +
+ "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf" +
+ "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf" +
+ "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf" +
+ "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf" +
+ "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef" +
+ "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff.txt";
+
+// ascii only
+var input1 =
+ "x!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" +
+ "`abcdefghijklmnopqrstuvwxyz{|}~.txt";
+
+var expectedCD0 = [
+ "Content-Disposition: attachment;",
+ " filename*0*=UTF-8''%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%30%31;",
+ " filename*1*=%32%33%34%35%36%37%38%39%3A%3B%3C%3D%3E%3F%40%41%42%43%44%45;",
+ " filename*2*=%46%47%48%49%4A%4B%4C%4D%4E%4F%50%51%52%53%54%55%56%57%58%59;",
+ " filename*3*=%5A%5B%5C%5D%5E%5F%60%61%62%63%64%65%66%67%68%69%6A%6B%6C%6D;",
+ " filename*4*=%6E%6F%70%71%72%73%74%75%76%77%78%79%7A%7B%7C%7D%7E%C2%A0;",
+ " filename*5*=%C2%A1%C2%A2%C2%A3%C2%A4%C2%A5%C2%A6%C2%A7%C2%A8%C2%A9%C2%AA;",
+ " filename*6*=%C2%AB%C2%AC%C2%AD%C2%AE%C2%AF%C2%B0%C2%B1%C2%B2%C2%B3%C2%B4;",
+ " filename*7*=%C2%B5%C2%B6%C2%B7%C2%B8%C2%B9%C2%BA%C2%BB%C2%BC%C2%BD%C2%BE;",
+ " filename*8*=%C2%BF%C3%80%C3%81%C3%82%C3%83%C3%84%C3%85%C3%86%C3%87%C3%88;",
+ " filename*9*=%C3%89%C3%8A%C3%8B%C3%8C%C3%8D%C3%8E%C3%8F%C3%90%C3%91%C3%92;",
+ " filename*10*=%C3%93%C3%94%C3%95%C3%96%C3%97%C3%98%C3%99%C3%9A%C3%9B;",
+ " filename*11*=%C3%9C%C3%9D%C3%9E%C3%9F%C3%A0%C3%A1%C3%A2%C3%A3%C3%A4;",
+ " filename*12*=%C3%A5%C3%A6%C3%A7%C3%A8%C3%A9%C3%AA%C3%AB%C3%AC%C3%AD;",
+ " filename*13*=%C3%AE%C3%AF%C3%B0%C3%B1%C3%B2%C3%B3%C3%B4%C3%B5%C3%B6;",
+ " filename*14*=%C3%B7%C3%B8%C3%B9%C3%BA%C3%BB%C3%BC%C3%BD%C3%BE%C3%BF%2E;",
+ " filename*15*=%74%78%74",
+ "",
+].join("\r\n");
+
+var expectedCD1 =
+ "Content-Disposition: attachment;\r\n" +
+ ' filename*0="x!\\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ";\r\n' +
+ ' filename*1="[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~.txt"\r\n';
+
+var ParamFoldingPref = {
+ // RFC2047: 0,
+ RFC2047WithCRLF: 1,
+ RFC2231: 2,
+};
+
+var expectedCTList0 = {
+ RFC2047:
+ "Content-Type: text/plain; charset=UTF-8;\r\n" +
+ ' name="=?UTF-8?B?ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJ?=' +
+ "=?UTF-8?Q?JKLMNOPQRSTUVWXYZ=5b=5c=5d=5e=5f=60abcdefghijklmnopqrstuvwx?=" +
+ "=?UTF-8?B?eXp7fH1+wqDCocKiwqPCpMKlwqbCp8KowqnCqsKrwqzCrcKuwq/CsMKx?=" +
+ "=?UTF-8?B?wrLCs8K0wrXCtsK3wrjCucK6wrvCvMK9wr7Cv8OAw4HDgsODw4TDhcOG?=" +
+ "=?UTF-8?B?w4fDiMOJw4rDi8OMw43DjsOPw5DDkcOSw5PDlMOVw5bDl8OYw5nDmsOb?=" +
+ "=?UTF-8?B?w5zDncOew5/DoMOhw6LDo8Okw6XDpsOnw6jDqcOqw6vDrMOtw67Dr8Ow?=" +
+ '=?UTF-8?B?w7HDssOzw7TDtcO2w7fDuMO5w7rDu8O8w73DvsO/LnR4dA==?="\r\n',
+
+ RFC2047WithCRLF:
+ "Content-Type: text/plain; charset=UTF-8;\r\n" +
+ ' name="=?UTF-8?B?ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJ?=\r\n' +
+ " =?UTF-8?Q?JKLMNOPQRSTUVWXYZ=5b=5c=5d=5e=5f=60abcdefghijklmnopqrstuvwx?=\r\n" +
+ " =?UTF-8?B?eXp7fH1+wqDCocKiwqPCpMKlwqbCp8KowqnCqsKrwqzCrcKuwq/CsMKx?=\r\n" +
+ " =?UTF-8?B?wrLCs8K0wrXCtsK3wrjCucK6wrvCvMK9wr7Cv8OAw4HDgsODw4TDhcOG?=\r\n" +
+ " =?UTF-8?B?w4fDiMOJw4rDi8OMw43DjsOPw5DDkcOSw5PDlMOVw5bDl8OYw5nDmsOb?=\r\n" +
+ " =?UTF-8?B?w5zDncOew5/DoMOhw6LDo8Okw6XDpsOnw6jDqcOqw6vDrMOtw67Dr8Ow?=\r\n" +
+ ' =?UTF-8?B?w7HDssOzw7TDtcO2w7fDuMO5w7rDu8O8w73DvsO/LnR4dA==?="\r\n',
+
+ RFC2231: "Content-Type: text/plain; charset=UTF-8\r\n",
+};
+
+var expectedCTList1 = {
+ RFC2047:
+ "Content-Type: text/plain; charset=UTF-8;\r\n" +
+ ' name="x!\\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~.txt"\r\n',
+
+ RFC2047WithCRLF:
+ "Content-Type: text/plain; charset=UTF-8;\r\n" +
+ ' name="x!\\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~.txt"\r\n',
+
+ RFC2231: "Content-Type: text/plain; charset=UTF-8\r\n",
+};
+
+function checkAttachment(expectedCD, expectedCT) {
+ let msgData = mailTestUtils.loadMessageToString(
+ gDraftFolder,
+ mailTestUtils.firstMsgHdr(gDraftFolder)
+ );
+ let pos = msgData.indexOf("Content-Disposition:");
+ Assert.notEqual(pos, -1);
+ let contentDisposition = msgData.substr(pos);
+ pos = 0;
+ do {
+ pos = contentDisposition.indexOf("\n", pos);
+ Assert.notEqual(pos, -1);
+ pos++;
+ } while (contentDisposition.startsWith(" ", pos));
+ contentDisposition = contentDisposition.substr(0, pos);
+ Assert.equal(contentDisposition, expectedCD);
+
+ pos = msgData.indexOf("Content-Type:"); // multipart
+ Assert.notEqual(pos, -1);
+ msgData = msgData.substr(pos + 13);
+ pos = msgData.indexOf("Content-Type:"); // body
+ Assert.notEqual(pos, -1);
+ msgData = msgData.substr(pos + 13);
+ pos = msgData.indexOf("Content-Type:"); // first attachment
+ Assert.notEqual(pos, -1);
+ var contentType = msgData.substr(pos);
+ pos = 0;
+ do {
+ pos = contentType.indexOf("\n", pos);
+ Assert.notEqual(pos, -1);
+ pos++;
+ } while (contentType.startsWith(" ", pos));
+ contentType = contentType.substr(0, pos);
+ Assert.equal(contentType.toLowerCase(), expectedCT.toLowerCase());
+}
+
+async function testInput0() {
+ for (let folding in ParamFoldingPref) {
+ Services.prefs.setIntPref(
+ "mail.strictly_mime.parm_folding",
+ ParamFoldingPref[folding]
+ );
+ await createMessage(input0);
+ checkAttachment(expectedCD0, expectedCTList0[folding]);
+ }
+}
+
+async function testInput1() {
+ for (let folding in ParamFoldingPref) {
+ Services.prefs.setIntPref(
+ "mail.strictly_mime.parm_folding",
+ ParamFoldingPref[folding]
+ );
+ await createMessage(input1);
+ checkAttachment(expectedCD1, expectedCTList1[folding]);
+ }
+}
+
+var tests = [testInput0, testInput1];
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+ tests.forEach(x => add_task(x));
+ run_next_test();
+}
+
+/**
+ * Test that the full attachment content is used to pick the CTE.
+ */
+add_task(async function testBinaryAfterPlainTextAttachment() {
+ let testFile = do_get_file("data/binary-after-plain.txt");
+ await createMessage(testFile);
+ let msgData = mailTestUtils.loadMessageToString(
+ gDraftFolder,
+ mailTestUtils.firstMsgHdr(gDraftFolder)
+ );
+ // If only the first few chars are used, encoding will be incorrectly 7bit.
+ Assert.ok(
+ msgData.includes(
+ 'Content-Disposition: attachment; filename="binary-after-plain.txt"\r\nContent-Transfer-Encoding: base64\r\n'
+ )
+ );
+});
diff --git a/comm/mailnews/compose/test/unit/test_attachment_intl.js b/comm/mailnews/compose/test/unit/test_attachment_intl.js
new file mode 100644
index 0000000000..6ce352d4df
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_attachment_intl.js
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * attachment test using non-ascii character
+ */
+
+let nonAsciiUrl = "http://\u65e5\u672c\u8a9e.jp";
+let prettyResult = "\u65e5\u672c\u8a9e.jp";
+
+function doAttachmentUrlTest() {
+ // handles non-ascii url in nsIMsgAttachment
+
+ let attachment = Cc[
+ "@mozilla.org/messengercompose/attachment;1"
+ ].createInstance(Ci.nsIMsgAttachment);
+ attachment.url = nonAsciiUrl;
+
+ Assert.equal(attachment.url, nonAsciiUrl);
+}
+
+function doPrettyNameTest() {
+ // handles non-ascii url in nsIMsgCompose
+
+ let msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance(
+ Ci.nsIMsgCompose
+ );
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ msgCompose.initialize(params);
+
+ Assert.equal(
+ msgCompose.AttachmentPrettyName(nonAsciiUrl, null),
+ prettyResult
+ );
+}
+
+function run_test() {
+ doAttachmentUrlTest();
+ doPrettyNameTest();
+
+ do_test_finished();
+}
diff --git a/comm/mailnews/compose/test/unit/test_autoReply.js b/comm/mailnews/compose/test/unit/test_autoReply.js
new file mode 100644
index 0000000000..a81dc7bcef
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_autoReply.js
@@ -0,0 +1,254 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests messages generated by ReplyWithTemplate.
+ */
+
+var { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.jsm");
+
+load("../../../resources/logHelper.js"); // watch for errors in the error console
+
+const kSender = "from@foo.invalid";
+
+var gIncomingMailFile = do_get_file("../../../data/bugmail10"); // mail to reply to
+// reply-filter-testmail: mail to reply to (but not really)
+var gIncomingMailFile2 = do_get_file("../../../data/reply-filter-testmail");
+// mail to reply to (but not really, no from)
+var gIncomingMailFile3 = do_get_file("../../../data/mail-without-from");
+var gTemplateMailFile = do_get_file("../../../data/template-latin1"); // template
+var gTemplateMailFile2 = do_get_file("../../../data/template-utf8"); // template2
+var gTemplateFolder;
+
+var gServer;
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+ gTemplateFolder =
+ localAccountUtils.rootFolder.createLocalSubfolder("Templates");
+
+ gServer = setupServerDaemon();
+ gServer.start();
+
+ run_next_test();
+}
+
+add_task(async function copy_gIncomingMailFile() {
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ // Copy gIncomingMailFile into the Inbox.
+ MailServices.copy.copyFileMessage(
+ gIncomingMailFile,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ promiseCopyListener,
+ null
+ );
+ await promiseCopyListener.promise;
+});
+
+add_task(async function copy_gIncomingMailFile2() {
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ // Copy gIncomingMailFile2 into the Inbox.
+ MailServices.copy.copyFileMessage(
+ gIncomingMailFile2,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ promiseCopyListener,
+ null
+ );
+ await promiseCopyListener.promise;
+});
+
+add_task(async function copy_gIncomingMailFile3() {
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ // Copy gIncomingMailFile3 into the Inbox.
+ MailServices.copy.copyFileMessage(
+ gIncomingMailFile3,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ promiseCopyListener,
+ null
+ );
+ await promiseCopyListener.promise;
+});
+
+add_task(async function copy_gTemplateMailFile() {
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ // Copy gTemplateMailFile into the Templates folder.
+ MailServices.copy.copyFileMessage(
+ gTemplateMailFile,
+ gTemplateFolder,
+ null,
+ true,
+ 0,
+ "",
+ promiseCopyListener,
+ null
+ );
+ await promiseCopyListener.promise;
+});
+
+add_task(async function copy_gTemplateMailFile2() {
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ // Copy gTemplateMailFile2 into the Templates folder.
+ MailServices.copy.copyFileMessage(
+ gTemplateMailFile2,
+ gTemplateFolder,
+ null,
+ true,
+ 0,
+ "",
+ promiseCopyListener,
+ null
+ );
+ await promiseCopyListener.promise;
+});
+
+// Test that a reply is NOT sent when the message is not addressed to "me".
+add_task(async function testReplyingToUnaddressedFails() {
+ try {
+ await testReply(0); // mail 0 is not to us!
+ do_throw("Replied to a message not addressed to us!");
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_ABORT) {
+ throw e;
+ }
+ // Ok! We didn't reply to the message not specifically addressed to
+ // us (from@foo.invalid).
+ }
+});
+
+// Test that a reply is sent when the message is addressed to "me".
+add_task(async function testReplyingToAdressedWorksLatin1() {
+ try {
+ await testReply(1); // mail 1 is addressed to us, using template-latin1
+ } catch (e) {
+ do_throw("Didn't reply properly to a message addressed to us! " + e);
+ }
+});
+
+// Test that a reply is sent when the message is addressed to "me".
+add_task(async function testReplyingToAdressedWorksUTF8() {
+ try {
+ await testReply(1, 1); // mail 1 is addressed to us, template-utf8
+ } catch (e) {
+ do_throw("Didn't reply properly to a message addressed to us! " + e);
+ }
+});
+
+// Test that a reply is NOT even tried when the message has no From.
+add_task(async function testReplyingToMailWithNoFrom() {
+ try {
+ await testReply(2); // mail 2 has no From
+ do_throw(
+ "Shouldn't even have tried to reply reply to the message " +
+ "with no From and no Reply-To"
+ );
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_FAILURE) {
+ throw e;
+ }
+ }
+});
+
+// Test reply with template.
+async function testReply(aHrdIdx, aTemplateHdrIdx = 0) {
+ let smtpServer = getBasicSmtpServer();
+ smtpServer.port = gServer.port;
+
+ let identity = getSmtpIdentity(kSender, smtpServer);
+ localAccountUtils.msgAccount.addIdentity(identity);
+
+ let msgHdr = mailTestUtils.getMsgHdrN(localAccountUtils.inboxFolder, aHrdIdx);
+ info(
+ "Msg#" +
+ aHrdIdx +
+ " author=" +
+ msgHdr.author +
+ ", recipients=" +
+ msgHdr.recipients
+ );
+ let templateHdr = mailTestUtils.getMsgHdrN(gTemplateFolder, aTemplateHdrIdx);
+
+ // See <method name="getTemplates"> in searchWidgets.xml
+ let msgTemplateUri =
+ gTemplateFolder.URI +
+ "?messageId=" +
+ templateHdr.messageId +
+ "&subject=" +
+ templateHdr.mime2DecodedSubject;
+ MailServices.compose.replyWithTemplate(
+ msgHdr,
+ msgTemplateUri,
+ null,
+ localAccountUtils.incomingServer
+ );
+
+ await TestUtils.waitForCondition(() => gServer._daemon.post);
+ let headers, body;
+ [headers, body] = MimeParser.extractHeadersAndBody(gServer._daemon.post);
+ Assert.ok(headers.get("Subject").startsWith("Auto: "));
+ Assert.equal(headers.get("Auto-submitted"), "auto-replied");
+ Assert.equal(headers.get("In-Reply-To"), "<" + msgHdr.messageId + ">");
+ Assert.equal(headers.get("References"), "<" + msgHdr.messageId + ">");
+ // XXX: something's wrong with how the fake server gets the data.
+ // The text gets converted to UTF-8 (regardless of what it is) at some point.
+ // Suspect a bug with how BinaryInputStream handles the strings.
+ if (templateHdr.charset == "windows-1252") {
+ // XXX: should really check for "åäö xlatin1"
+ if (!body.includes("åäö xlatin1")) {
+ // template-latin1 contains this
+ do_throw(
+ "latin1 body didn't go through! hdr msgid=" +
+ templateHdr.messageId +
+ ", msgbody=" +
+ body
+ );
+ }
+ } else if (templateHdr.charset == "utf-8") {
+ // XXX: should really check for "åäö xutf8"
+ if (!body.includes("åäö xutf8")) {
+ // template-utf8 contains this
+ do_throw(
+ "utf8 body didn't go through! hdr msgid=" +
+ templateHdr.messageId +
+ ", msgbody=" +
+ body
+ );
+ }
+ } else if (templateHdr.charset) {
+ do_throw(
+ "unexpected msg charset: " +
+ templateHdr.charset +
+ ", hdr msgid=" +
+ templateHdr.messageId
+ );
+ } else {
+ do_throw("didn't find a msg charset! hdr msgid=" + templateHdr.messageId);
+ }
+ gServer.resetTest();
+}
+
+add_task(function teardown() {
+ // fake server cleanup
+ gServer.stop();
+});
diff --git a/comm/mailnews/compose/test/unit/test_bcc.js b/comm/mailnews/compose/test/unit/test_bcc.js
new file mode 100644
index 0000000000..3689b920e7
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_bcc.js
@@ -0,0 +1,330 @@
+/* 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/. */
+
+/**
+ * Test that when bcc field is set, bcc header should not exist in the sent
+ * mail, but should exist in the mail copy (e.g. Sent folder).
+ */
+
+var { PromiseUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PromiseUtils.sys.mjs"
+);
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var gServer;
+var gSentFolder;
+
+function cleanUpSent() {
+ let messages = [...gSentFolder.msgDatabase.enumerateMessages()];
+ if (messages.length) {
+ gSentFolder.deleteMessages(messages, null, true, false, null, false);
+ }
+}
+
+/**
+ * Load local mail account and start fake SMTP server.
+ */
+add_setup(async function setup() {
+ localAccountUtils.loadLocalMailAccount();
+ gServer = setupServerDaemon();
+ gServer.start();
+ registerCleanupFunction(() => {
+ gServer.stop();
+ });
+ gSentFolder = localAccountUtils.rootFolder.createLocalSubfolder("Sent");
+});
+
+/**
+ * Send a msg with bcc field set, then check the sent mail doesn't contain bcc
+ * header, but the mail saved to the Sent folder contains bcc header.
+ */
+add_task(async function testBcc() {
+ gServer.resetTest();
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer(gServer.port)
+ );
+
+ // Prepare the comp fields, including the bcc field.
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ fields.to = "Nobody <to@tinderbox.invalid>";
+ fields.subject = "Test bcc";
+ fields.bcc = "bcc@tinderbox.invalid";
+ fields.body = "A\r\nBcc: \r\n mail body\r\n.";
+
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ params.composeFields = fields;
+
+ // Send the mail.
+ let msgCompose = MailServices.compose.initCompose(params);
+ msgCompose.type = Ci.nsIMsgCompType.New;
+ let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance(
+ Ci.nsIMsgProgress
+ );
+ let promise = new Promise((resolve, reject) => {
+ progressListener.resolve = resolve;
+ progressListener.reject = reject;
+ });
+ progress.registerListener(progressListener);
+ msgCompose.sendMsg(
+ Ci.nsIMsgSend.nsMsgDeliverNow,
+ identity,
+ "",
+ null,
+ progress
+ );
+ await promise;
+
+ let expectedBody = `\r\n\r\n${fields.body}`;
+ // Should not contain extra \r\n between head and body.
+ let notExpectedBody = `\r\n\r\n\r\n${fields.body}`;
+
+ Assert.ok(gServer._daemon.post.includes("Subject: Test bcc"));
+ // Check that bcc header doesn't exist in the sent mail.
+ Assert.ok(!gServer._daemon.post.includes("Bcc: bcc@tinderbox.invalid"));
+ Assert.ok(gServer._daemon.post.includes(expectedBody));
+ Assert.ok(!gServer._daemon.post.includes(notExpectedBody));
+
+ let msgData = mailTestUtils.loadMessageToString(
+ gSentFolder,
+ mailTestUtils.getMsgHdrN(gSentFolder, 0)
+ );
+ Assert.ok(msgData.includes("Subject: Test bcc"));
+ // Check that bcc header exists in the mail copy.
+ Assert.ok(msgData.includes("Bcc: bcc@tinderbox.invalid"));
+ Assert.ok(msgData.includes(fields.body));
+ Assert.ok(msgData.includes(expectedBody));
+ Assert.ok(!msgData.includes(notExpectedBody));
+});
+
+/**
+ * Test that non-utf8 eml attachment is intact after sent to a bcc recipient.
+ */
+add_task(async function testBccWithNonUtf8EmlAttachment() {
+ gServer.resetTest();
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer(gServer.port)
+ );
+
+ // Prepare the comp fields, including the bcc field.
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ fields.to = "Nobody <to@tinderbox.invalid>";
+ fields.subject = "Test bcc with non-utf8 eml attachment";
+ fields.bcc = "bcc@tinderbox.invalid";
+
+ let testFile = do_get_file("data/shift-jis.eml");
+ let attachment = Cc[
+ "@mozilla.org/messengercompose/attachment;1"
+ ].createInstance(Ci.nsIMsgAttachment);
+ attachment.url = "file://" + testFile.path;
+ attachment.contentType = "message/rfc822";
+ attachment.name = testFile.leafName;
+ fields.addAttachment(attachment);
+
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ params.composeFields = fields;
+
+ // Send the mail.
+ let msgCompose = MailServices.compose.initCompose(params);
+ msgCompose.type = Ci.nsIMsgCompType.New;
+ let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance(
+ Ci.nsIMsgProgress
+ );
+ let promise = new Promise((resolve, reject) => {
+ progressListener.resolve = resolve;
+ progressListener.reject = reject;
+ });
+ progress.registerListener(progressListener);
+ msgCompose.sendMsg(
+ Ci.nsIMsgSend.nsMsgDeliverNow,
+ identity,
+ "",
+ null,
+ progress
+ );
+ await promise;
+
+ Assert.ok(
+ gServer._daemon.post.includes(
+ "Subject: Test bcc with non-utf8 eml attachment"
+ )
+ );
+ // \x8C\xBB\x8B\xB5 is 現況 in SHIFT-JIS.
+ Assert.ok(gServer._daemon.post.includes("\r\n\r\n\x8C\xBB\x8B\xB5\r\n"));
+});
+
+add_task(async function testBccWithSendLater() {
+ gServer.resetTest();
+ cleanUpSent();
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer(gServer.port)
+ );
+ let account = MailServices.accounts.createAccount();
+ account.addIdentity(identity);
+
+ // Prepare the comp fields, including the bcc field.
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ fields.to = "Nobody <to@tinderbox.invalid>";
+ fields.subject = "Test bcc with send later";
+ fields.bcc = "bcc@tinderbox.invalid";
+ fields.body = "A\r\nBcc: \r\n mail body\r\n.";
+
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ params.composeFields = fields;
+
+ // Queue the mail to send later.
+ let msgCompose = MailServices.compose.initCompose(params);
+ msgCompose.type = Ci.nsIMsgCompType.New;
+ let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance(
+ Ci.nsIMsgProgress
+ );
+ let promise = new Promise((resolve, reject) => {
+ progressListener.resolve = resolve;
+ progressListener.reject = reject;
+ });
+ progress.registerListener(progressListener);
+ msgCompose.sendMsg(
+ Ci.nsIMsgSend.nsMsgQueueForLater,
+ identity,
+ "",
+ null,
+ progress
+ );
+ await promise;
+
+ let onStopSendingPromise = PromiseUtils.defer();
+ let msgSendLater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService(
+ Ci.nsIMsgSendLater
+ );
+ let sendLaterListener = {
+ onStartSending() {},
+ onMessageStartSending() {},
+ onMessageSendProgress() {},
+ onMessageSendError() {},
+ onStopSending() {
+ let expectedBody = `\r\n\r\n${fields.body}`;
+ // Should not contain extra \r\n between head and body.
+ let notExpectedBody = `\r\n\r\n\r\n${fields.body}`;
+
+ Assert.ok(gServer._daemon.post.includes(`Subject: ${fields.subject}`));
+ // Check that bcc header doesn't exist in the sent mail.
+ Assert.ok(!gServer._daemon.post.includes("Bcc: bcc@tinderbox.invalid"));
+ Assert.ok(gServer._daemon.post.includes(expectedBody));
+ Assert.ok(!gServer._daemon.post.includes(notExpectedBody));
+
+ let msgData = mailTestUtils.loadMessageToString(
+ gSentFolder,
+ mailTestUtils.getMsgHdrN(gSentFolder, 0)
+ );
+ Assert.ok(msgData.includes(`Subject: ${fields.subject}`));
+ // Check that bcc header exists in the mail copy.
+ Assert.ok(msgData.includes("Bcc: bcc@tinderbox.invalid"));
+ Assert.ok(msgData.includes(fields.body));
+ Assert.ok(msgData.includes(expectedBody));
+ Assert.ok(!msgData.includes(notExpectedBody));
+
+ msgSendLater.removeListener(sendLaterListener);
+ onStopSendingPromise.resolve();
+ },
+ };
+
+ msgSendLater.addListener(sendLaterListener);
+
+ // Actually send the message.
+ msgSendLater.sendUnsentMessages(identity);
+ await onStopSendingPromise.promise;
+});
+
+/**
+ * Test that sending bcc only message from Outbox works. With a bcc only
+ * message, nsMsgSendLater passes `To: undisclosed-recipients: ;` to
+ * SmtpService, but it should not be sent to the SMTP server.
+ */
+add_task(async function testBccOnlyWithSendLater() {
+ gServer.resetTest();
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer(gServer.port)
+ );
+ let account = MailServices.accounts.createAccount();
+ account.addIdentity(identity);
+
+ // Prepare the comp fields, including the bcc field.
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ fields.subject = "Test bcc only with send later";
+ fields.bcc = "bcc@tinderbox.invalid";
+ fields.body = "A\r\nBcc: \r\n mail body\r\n.";
+
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ params.composeFields = fields;
+
+ // Queue the mail to send later.
+ let msgCompose = MailServices.compose.initCompose(params);
+ msgCompose.type = Ci.nsIMsgCompType.New;
+ let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance(
+ Ci.nsIMsgProgress
+ );
+ let promise = new Promise((resolve, reject) => {
+ progressListener.resolve = resolve;
+ progressListener.reject = reject;
+ });
+ progress.registerListener(progressListener);
+ msgCompose.sendMsg(
+ Ci.nsIMsgSend.nsMsgQueueForLater,
+ identity,
+ "",
+ null,
+ progress
+ );
+ await promise;
+
+ let onStopSendingPromise = PromiseUtils.defer();
+ let msgSendLater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService(
+ Ci.nsIMsgSendLater
+ );
+ let sendLaterListener = {
+ onStartSending() {},
+ onMessageStartSending() {},
+ onMessageSendProgress() {},
+ onMessageSendError() {},
+ onStopSending() {
+ // Should not include RCPT TO:<undisclosed-recipients: ;>
+ do_check_transaction(gServer.playTransaction(), [
+ "EHLO test",
+ `MAIL FROM:<from@tinderbox.invalid> BODY=8BITMIME SIZE=${gServer._daemon.post.length}`,
+ "RCPT TO:<bcc@tinderbox.invalid>",
+ "DATA",
+ ]);
+
+ msgSendLater.removeListener(sendLaterListener);
+ onStopSendingPromise.resolve();
+ },
+ };
+
+ msgSendLater.addListener(sendLaterListener);
+
+ // Actually send the message.
+ msgSendLater.sendUnsentMessages(identity);
+ await onStopSendingPromise.promise;
+});
diff --git a/comm/mailnews/compose/test/unit/test_bug155172.js b/comm/mailnews/compose/test/unit/test_bug155172.js
new file mode 100644
index 0000000000..06c14416a7
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_bug155172.js
@@ -0,0 +1,140 @@
+/**
+ * Authentication tests for SMTP.
+ */
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+/* import-globals-from ../../../test/resources/passwordStorage.js */
+load("../../../resources/alertTestUtils.js");
+load("../../../resources/passwordStorage.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gNewPassword = null;
+
+// for alertTestUtils.js
+function confirmExPS(
+ parent,
+ aDialogTitle,
+ aText,
+ aButtonFlags,
+ aButton0Title,
+ aButton1Title,
+ aButton2Title,
+ aCheckMsg,
+ aCheckState
+) {
+ // Just return 2 which will is pressing button 2 - enter a new password.
+ return 2;
+}
+
+function promptPasswordPS(
+ aParent,
+ aDialogTitle,
+ aText,
+ aPassword,
+ aCheckMsg,
+ aCheckState
+) {
+ aPassword.value = gNewPassword;
+ return true;
+}
+
+var server;
+
+var kIdentityMail = "identity@foo.invalid";
+var kSender = "from@foo.invalid";
+var kTo = "to@foo.invalid";
+var kUsername = "test.smtp@fakeserver";
+// kPasswordSaved is the one defined in signons-smtp.json, the other one
+// is intentionally wrong.
+var kPasswordWrong = "wrong";
+var kPasswordSaved = "smtptest";
+
+add_task(async function () {
+ registerAlertTestUtils();
+
+ function createHandler(d) {
+ var handler = new SMTP_RFC2821_handler(d);
+ // Username needs to match the login information stored in the signons json
+ // file.
+ handler.kUsername = kUsername;
+ handler.kPassword = kPasswordWrong;
+ handler.kAuthRequired = true;
+ handler.kAuthSchemes = ["PLAIN", "LOGIN"]; // make match expected transaction below
+ return handler;
+ }
+
+ server = setupServerDaemon(createHandler);
+ server.setDebugLevel(fsDebugAll);
+
+ // Prepare files for passwords (generated by a script in bug 1018624).
+ await setupForPassword("signons-smtp.json");
+
+ // Test file
+ var testFile = do_get_file("data/message1.eml");
+
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ // Start the fake SMTP server
+ server.start();
+ var smtpServer = getBasicSmtpServer(server.port);
+ var identity = getSmtpIdentity(kIdentityMail, smtpServer);
+
+ // This time with auth
+ test = "Auth sendMailMessage";
+
+ smtpServer.authMethod = Ci.nsMsgAuthMethod.passwordCleartext;
+ smtpServer.socketType = Ci.nsMsgSocketType.plain;
+ smtpServer.username = kUsername;
+
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ kTo,
+ identity,
+ kSender,
+ null,
+ urlListener,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+
+ // Set the new password for when we get a prompt
+ gNewPassword = kPasswordWrong;
+
+ await urlListener.promise;
+
+ var transaction = server.playTransaction();
+ do_check_transaction(transaction, [
+ "EHLO test",
+ "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kPasswordSaved),
+ "AUTH LOGIN",
+ "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kPasswordWrong),
+ "MAIL FROM:<" + kSender + "> BODY=8BITMIME SIZE=159",
+ "RCPT TO:<" + kTo + ">",
+ "DATA",
+ ]);
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+});
diff --git a/comm/mailnews/compose/test/unit/test_bug474774.js b/comm/mailnews/compose/test/unit/test_bug474774.js
new file mode 100644
index 0000000000..ba0c20667c
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_bug474774.js
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Tests bug 474774 - assertions when saving send later and when sending with
+ * FCC switched off.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var server;
+var smtpServer;
+var originalData;
+var finished = false;
+var identity = null;
+
+var testFile = do_get_file("data/429891_testcase.eml");
+
+var kTestFileSender = "from_A@foo.invalid";
+var kTestFileRecipient = "to_A@foo.invalid";
+
+var kIdentityMail = "identity@foo.invalid";
+
+var msgSendLater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService(
+ Ci.nsIMsgSendLater
+);
+
+// This listener handles the post-sending of the actual message and checks the
+// sequence and ensures the data is correct.
+function msll() {}
+
+msll.prototype = {
+ _initialTotal: 0,
+
+ // nsIMsgSendLaterListener
+ onStartSending(aTotalMessageCount) {
+ this._initialTotal = 1;
+ Assert.equal(msgSendLater.sendingMessages, true);
+ },
+ onMessageStartSending(
+ aCurrentMessage,
+ aTotalMessageCount,
+ aMessageHeader,
+ aIdentity
+ ) {},
+ onMessageSendProgress(
+ aCurrentMessage,
+ aTotalMessageCount,
+ aMessageSendPercent,
+ aMessageCopyPercent
+ ) {
+ // XXX Enable this function
+ },
+ onMessageSendError(aCurrentMessage, aMessageHeader, aStatus, aMsg) {
+ do_throw(
+ "onMessageSendError should not have been called, status: " + aStatus
+ );
+ },
+ onStopSending(aStatus, aMsg, aTotalTried, aSuccessful) {
+ print("msll onStopSending\n");
+ try {
+ Assert.equal(aSuccessful, 1);
+ Assert.equal(aStatus, 0);
+ Assert.equal(aTotalTried, 1);
+ Assert.equal(this._initialTotal, 1);
+ Assert.equal(msgSendLater.sendingMessages, false);
+
+ do_check_transaction(server.playTransaction(), [
+ "EHLO test",
+ "MAIL FROM:<" +
+ kTestFileSender +
+ "> BODY=8BITMIME SIZE=" +
+ originalData.length,
+ "RCPT TO:<" + kTestFileRecipient + ">",
+ "DATA",
+ ]);
+
+ // Compare data file to what the server received
+ Assert.equal(originalData, server._daemon.post);
+
+ // Now wait till the copy is finished for the sent message
+ do_test_pending();
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+ do_test_finished();
+ },
+};
+
+/* exported OnStopCopy */
+// for head_compose.js
+function OnStopCopy(aStatus) {
+ do_test_finished();
+
+ try {
+ Assert.equal(aStatus, 0);
+
+ // Check this is false before we start sending
+ Assert.equal(msgSendLater.sendingMessages, false);
+
+ let folder = msgSendLater.getUnsentMessagesFolder(identity);
+
+ // Check we have a message in the unsent message folder
+ Assert.equal(folder.getTotalMessages(false), 1);
+
+ // Now do a comparison of what is in the sent mail folder
+ let msgData = mailTestUtils.loadMessageToString(
+ folder,
+ mailTestUtils.firstMsgHdr(folder)
+ );
+
+ // Skip the headers etc that mailnews adds
+ var pos = msgData.indexOf("From:");
+ Assert.notEqual(pos, -1);
+
+ msgData = msgData.substr(pos);
+
+ // Check the data is matching.
+ Assert.equal(originalData, msgData);
+
+ do_test_pending();
+ sendMessageLater();
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ finished = true;
+ }
+}
+
+// This function does the actual send later
+function sendMessageLater() {
+ do_test_finished();
+
+ // Set up the SMTP server.
+ server = setupServerDaemon();
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ // Start the fake SMTP server
+ server.start();
+ smtpServer.port = server.port;
+
+ // A test to check that we are sending files correctly, including checking
+ // what the server receives and what we output.
+ test = "sendMessageLater";
+
+ var messageListener = new msll();
+
+ msgSendLater.addListener(messageListener);
+
+ // Send the unsent message
+ msgSendLater.sendUnsentMessages(identity);
+
+ server.performTest();
+
+ do_timeout(10000, function () {
+ if (!finished) {
+ do_throw("Notifications of message send/copy not received");
+ }
+ });
+
+ do_test_pending();
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+}
+
+add_task(async function run_the_test() {
+ // Test file - for bug 429891
+ originalData = await IOUtils.readUTF8(testFile.path);
+
+ // Ensure we have a local mail account, an normal account and appropriate
+ // servers and identities.
+ localAccountUtils.loadLocalMailAccount();
+
+ MailServices.accounts.setSpecialFolders();
+
+ let account = MailServices.accounts.createAccount();
+ let incomingServer = MailServices.accounts.createIncomingServer(
+ "test",
+ "localhost",
+ "pop3"
+ );
+
+ smtpServer = getBasicSmtpServer(0);
+ identity = getSmtpIdentity(kIdentityMail, smtpServer);
+
+ account.addIdentity(identity);
+ account.defaultIdentity = identity;
+ account.incomingServer = incomingServer;
+ MailServices.accounts.defaultAccount = account;
+
+ localAccountUtils.rootFolder.createLocalSubfolder("Sent");
+
+ identity.doFcc = false;
+
+ // Now prepare to actually "send" the message later, i.e. dump it in the
+ // unsent messages folder.
+
+ var compFields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+
+ // Setting the compFields sender and recipient to any value is required to
+ // survive mime_sanity_check_fields in nsMsgCompUtils.cpp.
+ // Sender and recipient are required for sendMessageFile but SMTP
+ // transaction values will be used directly from mail body.
+ compFields.from = "irrelevant@foo.invalid";
+ compFields.to = "irrelevant@foo.invalid";
+
+ var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance(
+ Ci.nsIMsgSend
+ );
+
+ msgSend.sendMessageFile(
+ identity,
+ "",
+ compFields,
+ testFile,
+ false,
+ false,
+ Ci.nsIMsgSend.nsMsgQueueForLater,
+ null,
+ copyListener,
+ null,
+ null
+ );
+
+ // Now we wait till we get copy notification of completion.
+ do_test_pending();
+});
diff --git a/comm/mailnews/compose/test/unit/test_createAndSendMessage.js b/comm/mailnews/compose/test/unit/test_createAndSendMessage.js
new file mode 100644
index 0000000000..41daecf2cc
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_createAndSendMessage.js
@@ -0,0 +1,170 @@
+/* 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/. */
+
+/**
+ * Test createAndSendMessage creates a mail file when not using the editor.
+ */
+
+var server;
+var sentFolder;
+const originalData = "createAndSendMessage utf-8 test åäöÅÄÖ";
+// This is the originalData converted to a byte string.
+const expectedData = "createAndSendMessage utf-8 test åäöÃ\x85Ã\x84Ã\x96";
+const expectedContentTypeHeaders =
+ "Content-Type: text/plain; charset=UTF-8; format=flowed\r\nContent-Transfer-Encoding: 8bit\r\n\r\n";
+var finished = false;
+
+var kSender = "from@foo.invalid";
+var kTo = "to@foo.invalid";
+
+function checkData(msgData) {
+ // Skip the headers etc that mailnews adds
+ var pos = msgData.indexOf("Content-Type:");
+ Assert.notEqual(pos, -1);
+
+ msgData = msgData.substr(pos);
+
+ Assert.equal(msgData, expectedContentTypeHeaders + expectedData + "\r\n");
+}
+
+function MessageListener() {}
+
+MessageListener.prototype = {
+ // nsIMsgSendListener
+ onStartSending(aMsgID, aMsgSize) {},
+ onProgress(aMsgID, aProgress, aProgressMax) {},
+ onStatus(aMsgID, aMsg) {},
+ onStopSending(aMsgID, aStatus, aMsg, aReturnFile) {
+ try {
+ Assert.equal(aStatus, 0);
+
+ // Compare data file to what the server received
+ checkData(server._daemon.post);
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(false);
+ }
+ }
+ },
+ onGetDraftFolderURI(aMsgID, aFolderURI) {},
+ onSendNotPerformed(aMsgID, aStatus) {},
+ onTransportSecurityError(msgID, status, secInfo, location) {},
+
+ // nsIMsgCopyServiceListener
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {},
+ GetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ Assert.equal(aStatus, 0);
+ try {
+ // Now do a comparison of what is in the sent mail folder
+ let msgData = mailTestUtils.loadMessageToString(
+ sentFolder,
+ mailTestUtils.firstMsgHdr(sentFolder)
+ );
+
+ checkData(msgData);
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ finished = true;
+ do_test_finished();
+ }
+ },
+
+ // QueryInterface
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIMsgSendListener",
+ "nsIMsgCopyServiceListener",
+ ]),
+};
+
+/**
+ * Call createAndSendMessage, expect onStopSending to be called.
+ */
+add_task(async function testCreateAndSendMessage() {
+ server = setupServerDaemon();
+
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+
+ MailServices.accounts.setSpecialFolders();
+
+ server.start();
+ var smtpServer = getBasicSmtpServer(server.port);
+ var identity = getSmtpIdentity(kSender, smtpServer);
+
+ sentFolder = localAccountUtils.rootFolder.createLocalSubfolder("Sent");
+
+ Assert.equal(identity.doFcc, true);
+
+ var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance(
+ Ci.nsIMsgSend
+ );
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ // A test to check that we are sending files correctly, including checking
+ // what the server receives and what we output.
+ test = "sendMessageFile";
+
+ // Msg Comp Fields
+
+ var compFields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+
+ compFields.from = identity.email;
+ compFields.to = kTo;
+
+ var messageListener = new MessageListener();
+
+ msgSend.createAndSendMessage(
+ null,
+ identity,
+ "",
+ compFields,
+ false,
+ false,
+ Ci.nsIMsgSend.nsMsgDeliverNow,
+ null,
+ "text/plain",
+ // The following parameter is the message body, test that utf-8 is handled
+ // correctly.
+ originalData,
+ null,
+ null,
+ messageListener,
+ null,
+ null,
+ Ci.nsIMsgCompType.New
+ );
+
+ server.performTest();
+
+ do_timeout(10000, function () {
+ if (!finished) {
+ do_throw("Notifications of message send/copy not received");
+ }
+ });
+
+ do_test_pending();
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+});
diff --git a/comm/mailnews/compose/test/unit/test_createRFC822Message.js b/comm/mailnews/compose/test/unit/test_createRFC822Message.js
new file mode 100644
index 0000000000..9502031484
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_createRFC822Message.js
@@ -0,0 +1,68 @@
+/* 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/. */
+
+/**
+ * Test createRFC822Message creates a mail file.
+ */
+
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+
+let customSendListener = {
+ ...copyListener,
+ OnStopCopy() {},
+
+ /**
+ * Test a mail file is created and has correct content.
+ */
+ async onStopSending(msgId, status, msg, returnFile) {
+ ok(returnFile.exists(), "createRFC822Message should create a mail file");
+ let content = await IOUtils.read(returnFile.path);
+ content = String.fromCharCode(...content);
+ ok(
+ content.includes("Subject: Test createRFC822Message\r\n"),
+ "Mail file should contain correct subject line"
+ );
+ ok(
+ content.includes(
+ "createRFC822Message is used by nsImportService \xe4\xe9"
+ ),
+ "Mail file should contain correct body"
+ );
+ do_test_finished();
+ },
+};
+
+/**
+ * Call createRFC822Message, expect onStopSending to be called.
+ */
+add_task(async function testCreateRFC822Message() {
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ fields.from = "Somebody <somebody@tinderbox.invalid>";
+ fields.to = "Nobody <nobody@tinderbox.invalid>";
+ fields.subject = "Test createRFC822Message";
+
+ let msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance(
+ Ci.nsIMsgSend
+ );
+ msgSend.createRFC822Message(
+ identity,
+ fields,
+ "text/plain",
+ // The following parameter is the message body that can contain arbitrary
+ // binary data, let's try some windows-1252 data (äé).
+ "createRFC822Message is used by nsImportService \xe4\xe9",
+ true, // isDraft
+ [],
+ [],
+ customSendListener
+ );
+ do_test_pending();
+});
diff --git a/comm/mailnews/compose/test/unit/test_detectAttachmentCharset.js b/comm/mailnews/compose/test/unit/test_detectAttachmentCharset.js
new file mode 100644
index 0000000000..27e879d018
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_detectAttachmentCharset.js
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for auto-detecting attachment file charset.
+ */
+
+function checkAttachmentCharset(expectedCharset) {
+ let msgData = mailTestUtils.loadMessageToString(
+ gDraftFolder,
+ mailTestUtils.firstMsgHdr(gDraftFolder)
+ );
+ let attachmentData = getAttachmentFromContent(msgData);
+
+ Assert.equal(expectedCharset, getContentCharset(attachmentData));
+}
+
+function getContentCharset(aContent) {
+ let found = aContent.match(/^Content-Type: text\/plain; charset=(.*?);/);
+ if (found) {
+ Assert.equal(found.length, 2);
+ return found[1];
+ }
+ return null;
+}
+
+async function testUTF8() {
+ await createMessage(do_get_file("data/test-UTF-8.txt"));
+ checkAttachmentCharset("UTF-8");
+}
+
+async function testUTF16BE() {
+ await createMessage(do_get_file("data/test-UTF-16BE.txt"));
+ checkAttachmentCharset("UTF-16BE");
+}
+
+async function testUTF16LE() {
+ await createMessage(do_get_file("data/test-UTF-16LE.txt"));
+ checkAttachmentCharset("UTF-16LE");
+}
+
+async function testShiftJIS() {
+ await createMessage(do_get_file("data/test-SHIFT_JIS.txt"));
+ checkAttachmentCharset("Shift_JIS");
+}
+
+async function testISO2022JP() {
+ await createMessage(do_get_file("data/test-ISO-2022-JP.txt"));
+ checkAttachmentCharset("ISO-2022-JP");
+}
+
+async function testKOI8R() {
+ // NOTE: KOI8-R is detected as KOI8-U which is a superset covering both
+ // Russian and Ukrainian (a few box-drawing characters are repurposed).
+ await createMessage(do_get_file("data/test-KOI8-R.txt"));
+ checkAttachmentCharset("KOI8-U");
+}
+
+async function testWindows1252() {
+ await createMessage(do_get_file("data/test-windows-1252.txt"));
+ checkAttachmentCharset("windows-1252");
+}
+
+var tests = [
+ testUTF8,
+ testUTF16BE,
+ testUTF16LE,
+ testShiftJIS,
+ testISO2022JP,
+ testKOI8R,
+ testWindows1252,
+];
+
+function run_test() {
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+ Services.prefs.setIntPref("mail.strictly_mime.parm_folding", 0);
+
+ tests.forEach(x => add_task(x));
+ run_next_test();
+}
diff --git a/comm/mailnews/compose/test/unit/test_expandMailingLists.js b/comm/mailnews/compose/test/unit/test_expandMailingLists.js
new file mode 100644
index 0000000000..aa5998196f
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_expandMailingLists.js
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/**
+ * Tests nsMsgCompose expandMailingLists.
+ */
+
+var MsgComposeContractID = "@mozilla.org/messengercompose/compose;1";
+var MsgComposeParamsContractID =
+ "@mozilla.org/messengercompose/composeparams;1";
+var MsgComposeFieldsContractID =
+ "@mozilla.org/messengercompose/composefields;1";
+var nsIMsgCompose = Ci.nsIMsgCompose;
+var nsIMsgComposeParams = Ci.nsIMsgComposeParams;
+var nsIMsgCompFields = Ci.nsIMsgCompFields;
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+/**
+ * Helper to check population worked as expected.
+ *
+ * @param {string} aTo - Text in the To field.
+ * @param {string} aCheckTo - The expected To addresses (after possible list population).
+ */
+function checkPopulate(aTo, aCheckTo) {
+ let msgCompose = Cc[MsgComposeContractID].createInstance(nsIMsgCompose);
+
+ // Set up some basic fields for compose.
+ let fields = Cc[MsgComposeFieldsContractID].createInstance(nsIMsgCompFields);
+
+ fields.to = aTo;
+
+ // Set up some params
+ let params =
+ Cc[MsgComposeParamsContractID].createInstance(nsIMsgComposeParams);
+
+ params.composeFields = fields;
+
+ msgCompose.initialize(params);
+
+ msgCompose.expandMailingLists();
+ equal(fields.to, aCheckTo);
+}
+
+function run_test() {
+ loadABFile("data/listexpansion", kPABData.fileName);
+
+ // XXX Getting all directories ensures we create all ABs because mailing
+ // lists need help initialising themselves
+ MailServices.ab.directories;
+
+ // Test expansion of list with no description.
+ checkPopulate(
+ "simpson <simpson>",
+ 'Simpson <homer@example.com>, Marge <marge@example.com>, Bart <bart@foobar.invalid>, "lisa@example.com" <lisa@example.com>'
+ );
+
+ // Test expansion fo list with description.
+ checkPopulate(
+ "marge <marges own list>",
+ "Simpson <homer@example.com>, Marge <marge@example.com>"
+ );
+
+ // Special tests for bug 1287726: Lists in list. This is what the data looks like:
+ // 1) family (list) = parents (list) + kids (list).
+ // 2) parents (list) = homer + marge + parents (list recursion).
+ // 3) kids (list) = older-kids (list) + maggie.
+ // 4) older-kids (list) = bart + lisa.
+ // 5) bad-kids (list) = older-kids + bad-younger-kids (list).
+ // 6) bad-younger-kids (list) = maggie + bad-kids (list recursion).
+ checkPopulate(
+ "family <family>",
+ "Simpson <homer@example.com>, Marge <marge@example.com>, " +
+ '"lisa@example.com" <lisa@example.com>, Bart <bart@foobar.invalid>, Maggie <maggie@example.com>'
+ );
+ checkPopulate(
+ "parents <parents>",
+ "Simpson <homer@example.com>, Marge <marge@example.com>"
+ );
+ checkPopulate(
+ "kids <kids>",
+ '"lisa@example.com" <lisa@example.com>, Bart <bart@foobar.invalid>, ' +
+ "Maggie <maggie@example.com>"
+ );
+ checkPopulate(
+ "older-kids <older-kids>",
+ '"lisa@example.com" <lisa@example.com>, Bart <bart@foobar.invalid>'
+ );
+ checkPopulate(
+ "bad-kids <bad-kids>",
+ '"lisa@example.com" <lisa@example.com>, Bart <bart@foobar.invalid>, ' +
+ "Maggie <maggie@example.com>"
+ );
+ checkPopulate(
+ "bad-younger-kids <bad-younger-kids>",
+ "Maggie <maggie@example.com>, " +
+ '"lisa@example.com" <lisa@example.com>, Bart <bart@foobar.invalid>'
+ );
+
+ // Test we don't mistake an email address for a list, with a few variations.
+ checkPopulate("Simpson <homer@example.com>", "Simpson <homer@example.com>");
+ checkPopulate("simpson <homer@example.com>", "simpson <homer@example.com>");
+ checkPopulate(
+ "simpson <homer@not-in-ab.invalid>",
+ "simpson <homer@not-in-ab.invalid>"
+ );
+
+ checkPopulate("Marge <marge@example.com>", "Marge <marge@example.com>");
+ checkPopulate("marge <marge@example.com>", "marge <marge@example.com>");
+ checkPopulate(
+ "marge <marge@not-in-ab.invalid>",
+ "marge <marge@not-in-ab.invalid>"
+ );
+}
diff --git a/comm/mailnews/compose/test/unit/test_fcc2.js b/comm/mailnews/compose/test/unit/test_fcc2.js
new file mode 100644
index 0000000000..e7f8d7aadf
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_fcc2.js
@@ -0,0 +1,47 @@
+/* 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/. */
+
+/*
+ * Test that when fcc2 field is set, the mail is copied to the fcc2 folder.
+ */
+
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+
+let fcc2Folder;
+
+add_setup(async function () {
+ localAccountUtils.loadLocalMailAccount();
+ fcc2Folder = localAccountUtils.rootFolder.createLocalSubfolder("fcc2");
+});
+
+/**
+ * Send a message with the fcc2 field set, then check the message in the fcc2
+ * folder.
+ */
+add_task(async function testFcc2() {
+ let CompFields = CC(
+ "@mozilla.org/messengercompose/composefields;1",
+ Ci.nsIMsgCompFields
+ );
+ let fields = new CompFields();
+ fields.to = "Nobody <nobody@tinderbox.invalid>";
+ fields.subject = "Test fcc2";
+ fields.fcc2 = fcc2Folder.URI;
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+ await richCreateMessage(fields, [], identity);
+
+ // Check the message shows up correctly in the fcc2 folder.
+ let msgData = mailTestUtils.loadMessageToString(
+ fcc2Folder,
+ mailTestUtils.firstMsgHdr(fcc2Folder)
+ );
+ Assert.ok(msgData.includes("Subject: Test fcc2"));
+});
+
+add_task(async function cleanup() {
+ fcc2Folder.deleteSelf(null);
+});
diff --git a/comm/mailnews/compose/test/unit/test_fccReply.js b/comm/mailnews/compose/test/unit/test_fccReply.js
new file mode 100644
index 0000000000..b7e44bce17
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_fccReply.js
@@ -0,0 +1,140 @@
+/* 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/. */
+
+/*
+ * Test that when nsIMsgIdentity.fccReplyFollowsParent is true, the reply mail
+ * is copied to the same folder as the original mail.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+var gServer;
+
+/**
+ * Send a reply to originalMsgURI.
+ */
+async function sendReply(identity, fields, originalMsgURI, compType) {
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ params.composeFields = fields;
+ params.originalMsgURI = originalMsgURI;
+ let msgCompose = MailServices.compose.initCompose(params);
+ msgCompose.type = compType;
+ let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance(
+ Ci.nsIMsgProgress
+ );
+ let promise = new Promise((resolve, reject) => {
+ progressListener.resolve = resolve;
+ progressListener.reject = reject;
+ });
+ progress.registerListener(progressListener);
+ msgCompose.sendMsg(
+ Ci.nsIMsgSend.nsMsgDeliverNow,
+ identity,
+ "",
+ null,
+ progress
+ );
+ return promise;
+}
+
+/**
+ * Load local mail account and start fake SMTP server.
+ */
+add_setup(function () {
+ localAccountUtils.loadLocalMailAccount();
+ gServer = setupServerDaemon();
+ gServer.start();
+ registerCleanupFunction(() => {
+ gServer.stop();
+ });
+});
+
+/**
+ * With fccReplyFollowsParent enabled, send a few replies then check the replies
+ * exists in the Inbox folder.
+ */
+add_task(async function testFccReply() {
+ // Turn on fccReplyFollowsParent.
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer(gServer.port)
+ );
+ identity.fccReplyFollowsParent = true;
+
+ // Copy a test mail into the Inbox.
+ let file = do_get_file("data/message1.eml"); // mail to reply to
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFileMessage(
+ file,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ promiseCopyListener,
+ null
+ );
+ await promiseCopyListener.promise;
+
+ let CompFields = CC(
+ "@mozilla.org/messengercompose/composefields;1",
+ Ci.nsIMsgCompFields
+ );
+ let msgHdr = mailTestUtils.firstMsgHdr(localAccountUtils.inboxFolder);
+ let originalMsgURI = msgHdr.folder.getUriForMsg(msgHdr);
+
+ // Test nsIMsgCompFields.Reply.
+ let fields = new CompFields();
+ fields.to = "Nobody <nobody@tinderbox.invalid>";
+ fields.subject = "Test fcc reply";
+ await sendReply(identity, fields, originalMsgURI, Ci.nsIMsgCompType.Reply);
+ await TestUtils.waitForCondition(() => gServer._daemon.post);
+ let msgData = mailTestUtils.loadMessageToString(
+ localAccountUtils.inboxFolder,
+ mailTestUtils.getMsgHdrN(localAccountUtils.inboxFolder, 1)
+ );
+ Assert.ok(msgData.includes("Subject: Test fcc reply"));
+
+ // Test nsIMsgCompFields.ReplyToGroup.
+ gServer.resetTest();
+ fields.subject = "Test fccReplyToGroup";
+ await sendReply(
+ identity,
+ fields,
+ originalMsgURI,
+ Ci.nsIMsgCompType.ReplyToGroup
+ );
+ await TestUtils.waitForCondition(() => gServer._daemon.post);
+ msgData = mailTestUtils.loadMessageToString(
+ localAccountUtils.inboxFolder,
+ mailTestUtils.getMsgHdrN(localAccountUtils.inboxFolder, 2)
+ );
+ Assert.ok(msgData.includes("Subject: Test fccReplyToGroup"));
+
+ // Test nsIMsgCompFields.ReplyToList.
+ gServer.resetTest();
+ fields.subject = "Test fccReplyToList";
+ await sendReply(
+ identity,
+ fields,
+ originalMsgURI,
+ Ci.nsIMsgCompType.ReplyToList
+ );
+ await TestUtils.waitForCondition(() => gServer._daemon.post);
+ msgData = mailTestUtils.loadMessageToString(
+ localAccountUtils.inboxFolder,
+ mailTestUtils.getMsgHdrN(localAccountUtils.inboxFolder, 3)
+ );
+ Assert.ok(msgData.includes("Subject: Test fccReplyToList"));
+});
diff --git a/comm/mailnews/compose/test/unit/test_longLines.js b/comm/mailnews/compose/test/unit/test_longLines.js
new file mode 100644
index 0000000000..cd75e75d38
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_longLines.js
@@ -0,0 +1,232 @@
+/*
+ * Test ensuring that messages with "long lines" are transmitted correctly.
+ * Most of this test was copied from test_messageHeaders.js.
+ */
+
+const { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.jsm");
+
+var CompFields = CC(
+ "@mozilla.org/messengercompose/composefields;1",
+ Ci.nsIMsgCompFields
+);
+
+// Copied from jsmime.js.
+function stringToTypedArray(buffer) {
+ var typedarray = new Uint8Array(buffer.length);
+ for (var i = 0; i < buffer.length; i++) {
+ typedarray[i] = buffer.charCodeAt(i);
+ }
+ return typedarray;
+}
+
+function checkDraftHeadersAndBody(
+ expectedHeaders,
+ expectedBody,
+ charset = "UTF-8"
+) {
+ let msgData = mailTestUtils.loadMessageToString(
+ gDraftFolder,
+ mailTestUtils.firstMsgHdr(gDraftFolder)
+ );
+ checkMessageHeaders(msgData, expectedHeaders);
+
+ // Get the message body, decode from base64 and check.
+ let endOfHeaders = msgData.indexOf("\r\n\r\n");
+ let body = msgData.slice(endOfHeaders + 4);
+ let endOfBody = body.indexOf("\r\n\r\n");
+
+ if (endOfBody > 0) {
+ body = body.slice(0, endOfBody);
+ } else {
+ body = body.slice(0, body.length);
+ }
+
+ // Remove line breaks and decode from base64 if required.
+ if (expectedHeaders["Content-Transfer-Encoding"] == "base64") {
+ body = atob(body.replace(/\r\n/g, ""));
+ }
+
+ if (charset == "UTF-8") {
+ let expectedBinary = String.fromCharCode.apply(
+ undefined,
+ new TextEncoder("UTF-8").encode(expectedBody)
+ );
+ Assert.equal(body, expectedBinary);
+ } else {
+ let strView = stringToTypedArray(body);
+ let decodedBody = new TextDecoder(charset).decode(strView);
+ Assert.equal(decodedBody, expectedBody);
+ }
+}
+
+function checkMessageHeaders(msgData, expectedHeaders, partNum = "") {
+ let seen = false;
+ let handler = {
+ startPart(part, headers) {
+ if (part != partNum) {
+ return;
+ }
+ seen = true;
+ for (let header in expectedHeaders) {
+ let expected = expectedHeaders[header];
+ if (expected === undefined) {
+ Assert.ok(!headers.has(header));
+ } else {
+ let value = headers.getRawHeader(header);
+ Assert.equal(value.length, 1);
+ value[0] = value[0].replace(/boundary=[^;]*(;|$)/, "boundary=.");
+ Assert.equal(value[0], expected);
+ }
+ }
+ },
+ };
+ MimeParser.parseSync(msgData, handler, {
+ onerror(e) {
+ throw e;
+ },
+ });
+ Assert.ok(seen);
+}
+
+// Create a line with 600 letters 'a' with acute accent, encoded as
+// two bytes c3a1 in UTF-8.
+let longMultibyteLine = "\u00E1".repeat(600);
+
+// And here a line with a Korean character, encoded as three bytes
+// ec9588 in UTF-8.
+let longMultibyteLineCJK = "안".repeat(400);
+
+// And some Japanese.
+let longMultibyteLineJapanese = "語".repeat(450);
+
+async function testBodyWithLongLine() {
+ // Lines in the message body are split by CRLF according to RFC 5322, should
+ // be independent of the system.
+ let newline = "\r\n";
+
+ let fields = new CompFields();
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+ identity.fullName = "Me";
+ identity.organization = "World Destruction Committee";
+ fields.from = "Nobody <nobody@tinderbox.invalid>";
+ fields.to = "Nobody <nobody@tinderbox.invalid>";
+ fields.subject = "Message with 1200 byte line in body";
+ let htmlMessage =
+ "<html><head>" +
+ '<meta http-equiv="content-type" content="text/html; charset=utf-8">' +
+ "</head><body>" +
+ longMultibyteLine +
+ "</body></html>\r\n\r\n";
+ fields.body = htmlMessage;
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeadersAndBody(
+ {
+ "Content-Type": "text/html; charset=UTF-8",
+ "Content-Transfer-Encoding": "base64",
+ },
+ htmlMessage
+ );
+
+ // Again, but this time as plain text.
+ fields.body = htmlMessage;
+ fields.forcePlainText = true;
+ fields.useMultipartAlternative = false;
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeadersAndBody(
+ {
+ "Content-Type": "text/plain; charset=UTF-8; format=flowed",
+ "Content-Transfer-Encoding": "base64",
+ },
+ longMultibyteLine + " " + newline + newline // Expected body: The message without the tags.
+ );
+
+ // Now CJK.
+ fields.forcePlainText = false;
+ htmlMessage =
+ "<html><head>" +
+ '<meta http-equiv="content-type" content="text/html; charset=utf-8">' +
+ "</head><body>" +
+ longMultibyteLineCJK +
+ "</body></html>\r\n\r\n";
+ fields.body = htmlMessage;
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeadersAndBody(
+ {
+ "Content-Type": "text/html; charset=UTF-8",
+ "Content-Transfer-Encoding": "base64",
+ },
+ htmlMessage
+ );
+
+ // Again, but this time as plain text.
+ fields.body = htmlMessage;
+ fields.forcePlainText = true;
+ fields.useMultipartAlternative = false;
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeadersAndBody(
+ {
+ "Content-Type": "text/plain; charset=UTF-8; format=flowed",
+ "Content-Transfer-Encoding": "base64",
+ },
+ longMultibyteLineCJK + " " + newline + newline // Expected body: The message without the tags.
+ );
+
+ // Now a test for ISO-2022-JP.
+ fields.forcePlainText = false;
+ htmlMessage =
+ "<html><head>" +
+ '<meta http-equiv="content-type" content="text/html; charset=ISO-2022-JP">' +
+ "</head><body>" +
+ longMultibyteLineJapanese +
+ "</body></html>\r\n\r\n";
+ fields.body = htmlMessage;
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeadersAndBody(
+ {
+ "Content-Type": "text/html; charset=UTF-8",
+ "Content-Transfer-Encoding": "base64",
+ },
+ htmlMessage
+ );
+
+ // Again, but this time as plain text.
+ fields.body = htmlMessage;
+ fields.forcePlainText = true;
+ fields.useMultipartAlternative = false;
+ await richCreateMessage(fields, [], identity);
+
+ let expectedBody = longMultibyteLineJapanese + " " + newline + newline;
+
+ checkDraftHeadersAndBody(
+ {
+ "Content-Type": "text/plain; charset=UTF-8; format=flowed",
+ "Content-Transfer-Encoding": "base64",
+ },
+ expectedBody
+ );
+
+ // Again, but this time not flowed.
+ fields.body = htmlMessage;
+ Services.prefs.setBoolPref("mailnews.send_plaintext_flowed", false);
+
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeadersAndBody(
+ {
+ "Content-Type": "text/plain; charset=UTF-8",
+ "Content-Transfer-Encoding": "base64",
+ },
+ expectedBody.replace(/ /g, "") // No spaces expected this time.
+ );
+}
+
+var tests = [testBodyWithLongLine];
+
+function run_test() {
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+ tests.forEach(x => add_task(x));
+ run_next_test();
+}
diff --git a/comm/mailnews/compose/test/unit/test_mailTelemetry.js b/comm/mailnews/compose/test/unit/test_mailTelemetry.js
new file mode 100644
index 0000000000..28959d508f
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_mailTelemetry.js
@@ -0,0 +1,79 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test telemetry related to mails sent.
+ */
+
+let { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+let server;
+
+let kIdentityMail = "identity@foo.invalid";
+let kSender = "from@foo.invalid";
+let kTo = "to@foo.invalid";
+
+const NUM_MAILS = 3;
+
+let deliveryListener = {
+ count: 0,
+ OnStartRunningUrl() {},
+ OnStopRunningUrl() {
+ if (++this.count == NUM_MAILS) {
+ let scalars = TelemetryTestUtils.getProcessScalars("parent");
+ Assert.equal(
+ scalars["tb.mails.sent"],
+ NUM_MAILS,
+ "Count of mails sent must be correct."
+ );
+ }
+ },
+};
+
+/**
+ * Check that we're counting mails sent.
+ */
+add_task(async function test_mails_sent() {
+ Services.telemetry.clearScalars();
+
+ server = setupServerDaemon();
+ registerCleanupFunction(() => {
+ server.stop();
+ });
+
+ // Test file
+ let testFile = do_get_file("data/message1.eml");
+
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ // Start the fake SMTP server
+ server.start();
+ let smtpServer = getBasicSmtpServer(server.port);
+ let identity = getSmtpIdentity(kIdentityMail, smtpServer);
+
+ for (let i = 0; i < NUM_MAILS; i++) {
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ kTo,
+ identity,
+ kSender,
+ null,
+ deliveryListener,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+ }
+ } catch (e) {
+ do_throw(e);
+ }
+});
diff --git a/comm/mailnews/compose/test/unit/test_mailtoURL.js b/comm/mailnews/compose/test/unit/test_mailtoURL.js
new file mode 100644
index 0000000000..a793c99974
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_mailtoURL.js
@@ -0,0 +1,810 @@
+/*
+ * Test suite for mailto: URLs
+ */
+
+var COMPOSE_HTML = Ci.nsIMsgCompFormat.HTML;
+var COMPOSE_DEFAULT = Ci.nsIMsgCompFormat.Default;
+
+function run_test() {
+ function test(aTest) {
+ var uri = Services.io.newURI(aTest.url);
+ uri = uri.QueryInterface(Ci.nsIMailtoUrl);
+
+ var to = {},
+ cc = {},
+ bcc = {},
+ subject = {},
+ body = {},
+ html = {},
+ reference = {},
+ newsgroup = {},
+ composeformat = {};
+ uri.getMessageContents(
+ to,
+ cc,
+ bcc,
+ subject,
+ body,
+ html,
+ reference,
+ newsgroup,
+ composeformat
+ );
+ Assert.equal(aTest.to, to.value);
+ Assert.equal(aTest.cc, cc.value);
+ Assert.equal(aTest.bcc, bcc.value);
+ Assert.equal(aTest.subject, subject.value);
+ Assert.equal(aTest.body, body.value);
+ Assert.equal(aTest.html, html.value);
+ Assert.equal(aTest.reference, reference.value);
+ Assert.equal(aTest.newsgroup, newsgroup.value);
+ Assert.equal(aTest.composeformat, composeformat.value);
+ Assert.equal(aTest.from, uri.fromPart);
+ Assert.equal(aTest.followupto, uri.followUpToPart);
+ Assert.equal(aTest.organization, uri.organizationPart);
+ Assert.equal(aTest.replyto, uri.replyToPart);
+ Assert.equal(aTest.priority, uri.priorityPart);
+ Assert.equal(aTest.newshost, uri.newsHostPart);
+ Assert.ok(uri.equals(uri));
+ }
+
+ for (var i = 0; i < tests.length; i++) {
+ test(tests[i]);
+ }
+
+ // Test cloning reparses the url by checking the to field.
+ let uri = Services.io.newURI(tests[0].url).QueryInterface(Ci.nsIMailtoUrl);
+ var to = {},
+ cc = {},
+ bcc = {},
+ subject = {},
+ body = {},
+ html = {},
+ reference = {},
+ newsgroup = {},
+ composeformat = {};
+ uri.getMessageContents(
+ to,
+ cc,
+ bcc,
+ subject,
+ body,
+ html,
+ reference,
+ newsgroup,
+ composeformat
+ );
+ Assert.equal(to.value, tests[0].to);
+}
+
+var tests = [
+ {
+ url: "mailto:one@example.com",
+ to: "one@example.com",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:two@example.com?",
+ to: "two@example.com",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ /* the heirarchical-part address shouldn't be mime-decoded */
+ {
+ url: "mailto:%3D%3FUTF-8%3FQ%3Fthree%3F%3D@example.com",
+ to: "=?UTF-8?Q?three?=@example.com",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ /* a to=address should be mime-decoded */
+ {
+ url: "mailto:?to=%3D%3FUTF-8%3FQ%3Ffour%3F%3D@example.com",
+ to: "four@example.com",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:fivea@example.com?to=%3D%3FUTF-8%3FQ%3Ffiveb%3F%3D@example.com",
+ to: "fivea@example.com, fiveb@example.com",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:sixa@example.com?to=sixb@example.com&to=sixc@example.com",
+ to: "sixa@example.com, sixb@example.com, sixc@example.com",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?cc=seven@example.com",
+ to: "",
+ cc: "seven@example.com",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?cc=%3D%3FUTF-8%3FQ%3Feight%3F%3D@example.com",
+ to: "",
+ cc: "eight@example.com",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?bcc=nine@example.com",
+ to: "",
+ cc: "",
+ bcc: "nine@example.com",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?bcc=%3D%3FUTF-8%3FQ%3Ften%3F%3D@example.com",
+ to: "",
+ cc: "",
+ bcc: "ten@example.com",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?subject=foo",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "foo",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?subject=%62%61%72",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "bar",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?subject=%3D%3Futf-8%3FQ%3F%3DC2%3DA1encoded_subject%21%3F%3D",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "¡encoded subject!",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?body=one%20body",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "one body",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?body=two%20bodies&body=two%20lines",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "two bodies\ntwo lines",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?html-part=html%20part",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "html part",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_HTML,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?html-body=html%20body",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "html body",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_HTML,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?html-part=html%20part&html-body=html-body%20trumps%20earlier%20html-part",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "html-body trumps earlier html-part",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_HTML,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?references=%3Cref1%40example.com%3E",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "<ref1@example.com>",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?in-reply-to=%3Crepl1%40example.com%3E",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "<repl1@example.com>",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url:
+ "mailto:?references=%3Cref2%40example.com%3E" +
+ "&in-reply-to=%3Crepl2%40example.com%3E",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "<ref2@example.com> <repl2@example.com>",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url:
+ "mailto:?references=%3Cref3%40example.com%3E%20%3Crepl3%40example.com%3E" +
+ "&in-reply-to=%3Crepl3%40example.com%3E",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "<ref3@example.com> <repl3@example.com>",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?newsgroups=mozilla.dev.apps.thunderbird",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "mozilla.dev.apps.thunderbird",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?newsgroups=%3D%3FUTF-8%3FQ%3Fmozilla.test.multimedia%3F%3D",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "mozilla.test.multimedia",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?from=notlikely@example.com",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "notlikely@example.com",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?from=%3D%3FUTF-8%3FQ%3Fme@example.com%3F%3D",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "me@example.com",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?followup-to=mozilla.dev.planning",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "mozilla.dev.planning",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?followup-to=%3D%3FUTF-8%3FQ%3Fmozilla.test%3F%3D",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "mozilla.test",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?organization=very%20little",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "very little",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?organization=%3D%3FUTF-8%3FQ%3Fmicroscopic%3F%3D",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "microscopic",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?reply-to=notme@example.com",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "notme@example.com",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?reply-to=%3D%3FUTF-8%3FB%3Fw4VrZQ%3D%3D%3F%3D%20%3Cake@example.org%3E",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "Åke <ake@example.org>",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?priority=1%20(People%20Are%20Dying!!1!)",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "1 (People Are Dying!!1!)",
+ newshost: "",
+ },
+ {
+ url: "mailto:?priority=%3D%3FUTF-8%3FQ%3F4%3F%3D",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "4",
+ newshost: "",
+ },
+ {
+ url: "mailto:?newshost=news.mozilla.org",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "news.mozilla.org",
+ },
+ {
+ url: "mailto:?newshost=%3D%3FUTF-8%3FQ%3Fnews.example.org%3F%3D",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "news.example.org",
+ },
+ {
+ url: "mailto:?%74%4F=to&%73%55%62%4A%65%43%74=subject&%62%4F%64%59=body&%63%43=cc&%62%43%63=bcc",
+ to: "to",
+ cc: "cc",
+ bcc: "bcc",
+ subject: "subject",
+ body: "body",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:to1?%74%4F=to2&to=to3&subject=&%73%55%62%4A%65%43%74=subject&%62%4F%64%59=line1&body=line2&%63%43=cc1&cc=cc2&%62%43%63=bcc1&bcc=bcc2",
+ to: "to1, to2, to3",
+ cc: "cc1, cc2",
+ bcc: "bcc1, bcc2",
+ subject: "subject",
+ body: "line1\nline2",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url: "mailto:?nto=1&nsubject=2&nbody=3&ncc=4&nbcc=5",
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ body: "",
+ html: "",
+ reference: "",
+ newsgroup: "",
+ composeformat: COMPOSE_DEFAULT,
+ from: "",
+ followupto: "",
+ organization: "",
+ replyto: "",
+ priority: "",
+ newshost: "",
+ },
+ {
+ url:
+ "mailto:%CE%B1?cc=%CE%B2&bcc=%CE%B3&subject=%CE%B4&body=%CE%B5" +
+ "&html-body=%CE%BE&newsgroups=%CE%B6&from=%CE%B7&followup-to=%CE%B8" +
+ "&organization=%CE%B9&reply-to=%CE%BA&priority=%CE%BB&newshost=%CE%BC",
+ to: "α",
+ cc: "β",
+ bcc: "γ",
+ subject: "δ",
+ body: "ε",
+ html: "ξ",
+ reference: "", // we expect this field to be ASCII-only
+ newsgroup: "ζ",
+ composeformat: COMPOSE_HTML,
+ from: "η",
+ followupto: "θ",
+ organization: "ι",
+ replyto: "κ",
+ priority: "λ",
+ newshost: "μ",
+ },
+];
diff --git a/comm/mailnews/compose/test/unit/test_messageBody.js b/comm/mailnews/compose/test/unit/test_messageBody.js
new file mode 100644
index 0000000000..14c44b59f8
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_messageBody.js
@@ -0,0 +1,206 @@
+/* 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/. */
+
+/**
+ * Test suite for message body.
+ */
+
+localAccountUtils.loadLocalMailAccount();
+
+/**
+ * Test trailing whitespace is QP encoded.
+ */
+add_task(async function testQP() {
+ // Together with fields.forceMsgEncoding, force quote-printable encoding.
+ Services.prefs.setBoolPref("mail.strictly_mime", true);
+
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+ let CompFields = CC(
+ "@mozilla.org/messengercompose/composefields;1",
+ Ci.nsIMsgCompFields
+ );
+
+ // Test QP works for ascii text.
+
+ let fields = new CompFields();
+ fields.forceMsgEncoding = true;
+ fields.to = "Nobody <nobody@tinderbox.invalid>";
+ fields.subject = "Test QP encoding for trailing whitespace";
+ fields.body = "A line with trailing whitespace\t ";
+ await richCreateMessage(fields, [], identity);
+
+ let msgData = mailTestUtils.loadMessageToString(
+ gDraftFolder,
+ mailTestUtils.firstMsgHdr(gDraftFolder)
+ );
+ Assert.ok(
+ msgData.includes("A line with trailing whitespace\t=20"),
+ "QP for ascii should work"
+ );
+
+ // Test QP works for non-ascii text.
+
+ fields = new CompFields();
+ fields.forceMsgEncoding = true;
+ fields.to = "Nobody <nobody@tinderbox.invalid>";
+ fields.subject = "Test QP encoding for non-ascii and trailing tab";
+ fields.body = "記: base64 is used if unprintable > 10% \t";
+ await richCreateMessage(fields, [], identity);
+
+ msgData = mailTestUtils.loadMessageToString(
+ gDraftFolder,
+ mailTestUtils.firstMsgHdr(gDraftFolder)
+ );
+ Assert.ok(
+ msgData.includes("=E8=A8=98: base64 is used if unprintable > 10% =09"),
+ "QP for non-ascii should work"
+ );
+
+ // Test leading space is preserved.
+
+ fields = new CompFields();
+ fields.forceMsgEncoding = true;
+ fields.to = "Nobody <nobody@tinderbox.invalid>";
+ fields.subject = "Leading space is valid in a quoted printable message";
+ fields.body = "123456789" + " 123456789".repeat(6) + "1234 56789";
+ await richCreateMessage(fields, [], identity);
+
+ msgData = mailTestUtils.loadMessageToString(
+ gDraftFolder,
+ mailTestUtils.firstMsgHdr(gDraftFolder)
+ );
+ let endOfHeaders = msgData.indexOf("\r\n\r\n");
+ let body = msgData.slice(endOfHeaders + 4);
+
+ Assert.equal(
+ body.trimRight("\r\n"),
+ "123456789 123456789 123456789 123456789 123456789 123456789 1234567891234=\r\n 56789"
+ );
+
+ Services.prefs.clearUserPref("mail.strictly_mime");
+});
+
+/**
+ * Test QP is not used together with format=flowed.
+ */
+add_task(async function testNoQPWithFormatFlowed() {
+ // Together with fields.forceMsgEncoding, force quote-printable encoding.
+ Services.prefs.setBoolPref("mail.strictly_mime", true);
+
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ fields.forceMsgEncoding = true;
+ fields.forcePlainText = true;
+ fields.to = "Nobody <nobody@tinderbox.invalid>";
+ fields.subject = "Test QP encoding for trailing whitespace";
+ fields.body = "A line with trailing whitespace\t ";
+ await richCreateMessage(fields, [], identity);
+
+ let msgData = mailTestUtils.loadMessageToString(
+ gDraftFolder,
+ mailTestUtils.firstMsgHdr(gDraftFolder)
+ );
+ Assert.ok(
+ msgData.includes(
+ "Content-Type: text/plain; charset=UTF-8; format=flowed\r\nContent-Transfer-Encoding: base64"
+ ),
+ "format=flowed should be used"
+ );
+ Assert.ok(
+ !msgData.includes("quoted-printable"),
+ "quoted-printable should not be used"
+ );
+
+ Services.prefs.clearUserPref("mail.strictly_mime");
+});
+
+/**
+ * Test plain text body is wrapped correctly with different mailnews.wraplength
+ * pref value.
+ */
+add_task(async function testWrapLength() {
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+ let CompFields = CC(
+ "@mozilla.org/messengercompose/composefields;1",
+ Ci.nsIMsgCompFields
+ );
+
+ let word = "abcd ";
+ let body = word.repeat(20);
+
+ let fields = new CompFields();
+ fields.to = "Nobody <nobody@tinderbox.invalid>";
+ fields.subject = "Test text wrapping";
+ fields.body = `<html><body>${body}</body></html>`;
+ fields.forcePlainText = true;
+ await richCreateMessage(fields, [], identity);
+
+ let msgData = mailTestUtils.loadMessageToString(
+ gDraftFolder,
+ mailTestUtils.firstMsgHdr(gDraftFolder)
+ );
+ Assert.equal(
+ getMessageBody(msgData),
+ // Default wrap length is 72.
+ word.repeat(14) + "\r\n" + word.repeat(6).trim(),
+ "Text wraps at 72 by default"
+ );
+
+ // 0 means no wrap.
+ Services.prefs.setIntPref("mailnews.wraplength", 0);
+
+ await richCreateMessage(fields, [], identity);
+
+ msgData = mailTestUtils.loadMessageToString(
+ gDraftFolder,
+ mailTestUtils.firstMsgHdr(gDraftFolder)
+ );
+ Assert.equal(
+ getMessageBody(msgData),
+ body.trim(),
+ "Should not wrap when wraplength is 0"
+ );
+
+ Services.prefs.clearUserPref("mailnews.wraplength");
+});
+
+/**
+ * Test handling of trailing NBSP.
+ */
+add_task(async function testNBSP() {
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ fields.to = "Nobody <nobody@tinderbox.invalid>";
+ fields.subject = "Test text wrapping";
+ // The character after `test` is NBSP.
+ fields.body = "<html><body>åäö test <br></body></html>";
+ fields.forcePlainText = true;
+ await richCreateMessage(fields, [], identity);
+
+ let msgData = mailTestUtils.loadMessageToUTF16String(
+ gDraftFolder,
+ mailTestUtils.firstMsgHdr(gDraftFolder)
+ );
+ Assert.equal(
+ getMessageBody(msgData),
+ "åäö test",
+ "Trailing NBSP should be removed"
+ );
+});
diff --git a/comm/mailnews/compose/test/unit/test_messageHeaders.js b/comm/mailnews/compose/test/unit/test_messageHeaders.js
new file mode 100644
index 0000000000..58765e219f
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_messageHeaders.js
@@ -0,0 +1,812 @@
+/*
+ * Test suite for ensuring that the headers of messages are set properly.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.jsm");
+
+var CompFields = CC(
+ "@mozilla.org/messengercompose/composefields;1",
+ Ci.nsIMsgCompFields
+);
+
+function makeAttachment(opts = {}) {
+ let attachment = Cc[
+ "@mozilla.org/messengercompose/attachment;1"
+ ].createInstance(Ci.nsIMsgAttachment);
+ for (let key in opts) {
+ attachment[key] = opts[key];
+ }
+ return attachment;
+}
+
+function sendMessage(fieldParams, identity, opts = {}, attachments = []) {
+ // Initialize compose fields
+ let fields = new CompFields();
+ for (let key in fieldParams) {
+ fields[key] = fieldParams[key];
+ }
+ for (let attachment of attachments) {
+ fields.addAttachment(attachment);
+ }
+
+ // Initialize compose params
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ params.composeFields = fields;
+ for (let key in opts) {
+ params[key] = opts[key];
+ }
+
+ // Send the message
+ let msgCompose = MailServices.compose.initCompose(params);
+ let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance(
+ Ci.nsIMsgProgress
+ );
+ let promise = new Promise((resolve, reject) => {
+ progressListener.resolve = resolve;
+ progressListener.reject = reject;
+ });
+ progress.registerListener(progressListener);
+ msgCompose.sendMsg(
+ Ci.nsIMsgSend.nsMsgDeliverNow,
+ identity,
+ "",
+ null,
+ progress
+ );
+ return promise;
+}
+
+function checkDraftHeaders(expectedHeaders, partNum = "") {
+ let msgData = mailTestUtils.loadMessageToString(
+ gDraftFolder,
+ mailTestUtils.firstMsgHdr(gDraftFolder)
+ );
+ checkMessageHeaders(msgData, expectedHeaders, partNum);
+}
+
+function checkMessageHeaders(msgData, expectedHeaders, partNum = "") {
+ let seen = false;
+ let handler = {
+ startPart(part, headers) {
+ if (part != partNum) {
+ return;
+ }
+ seen = true;
+ for (let header in expectedHeaders) {
+ let expected = expectedHeaders[header];
+ if (expected === undefined) {
+ Assert.ok(
+ !headers.has(header),
+ `Should not have header named "${header}"`
+ );
+ } else {
+ let value = headers.getRawHeader(header);
+ Assert.equal(
+ value && value.length,
+ 1,
+ `Should have exactly one header named "${header}"`
+ );
+ value[0] = value[0].replace(/boundary=[^;]*(;|$)/, "boundary=.");
+ Assert.equal(value[0], expected);
+ }
+ }
+ },
+ };
+ MimeParser.parseSync(msgData, handler, {
+ onerror(e) {
+ throw e;
+ },
+ });
+ Assert.ok(seen);
+}
+
+async function testEnvelope() {
+ let fields = new CompFields();
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+ identity.fullName = "Me";
+ identity.organization = "World Destruction Committee";
+ fields.from = "Nobody <nobody@tinderbox.invalid>";
+ fields.to = "Nobody <nobody@tinderbox.invalid>";
+ fields.cc = "Alex <alex@tinderbox.invalid>";
+ fields.bcc = "Boris <boris@tinderbox.invalid>";
+ fields.replyTo = "Charles <charles@tinderbox.invalid>";
+ fields.organization = "World Salvation Committee";
+ fields.subject = "This is an obscure reference";
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ // As of bug 87987, the identity does not override the from header.
+ From: "Nobody <nobody@tinderbox.invalid>",
+ // The identity should override the organization field here.
+ Organization: "World Destruction Committee",
+ To: "Nobody <nobody@tinderbox.invalid>",
+ Cc: "Alex <alex@tinderbox.invalid>",
+ Bcc: "Boris <boris@tinderbox.invalid>",
+ "Reply-To": "Charles <charles@tinderbox.invalid>",
+ Subject: "This is an obscure reference",
+ });
+}
+
+async function testI18NEnvelope() {
+ let fields = new CompFields();
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+ identity.fullName = "ケツァルコアトル";
+ identity.organization = "Comité de la destruction du monde";
+ fields.to = "Émile <nobody@tinderbox.invalid>";
+ fields.cc = "André Chopin <alex@tinderbox.invalid>";
+ fields.bcc = "Étienne <boris@tinderbox.invalid>";
+ fields.replyTo = "Frédéric <charles@tinderbox.invalid>";
+ fields.subject = "Ceci n'est pas un référence obscure";
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ From: "=?UTF-8?B?44Kx44OE44Kh44Or44Kz44Ki44OI44Or?= <from@tinderbox.invalid>",
+ Organization: "=?UTF-8?Q?Comit=C3=A9_de_la_destruction_du_monde?=",
+ To: "=?UTF-8?B?w4ltaWxl?= <nobody@tinderbox.invalid>",
+ Cc: "=?UTF-8?Q?Andr=C3=A9_Chopin?= <alex@tinderbox.invalid>",
+ Bcc: "=?UTF-8?Q?=C3=89tienne?= <boris@tinderbox.invalid>",
+ "Reply-To": "=?UTF-8?B?RnLDqWTDqXJpYw==?= <charles@tinderbox.invalid>",
+ Subject: "=?UTF-8?Q?Ceci_n=27est_pas_un_r=C3=A9f=C3=A9rence_obscure?=",
+ });
+}
+
+async function testIDNEnvelope() {
+ let fields = new CompFields();
+ let domain = "ケツァルコアトル.invalid";
+ // We match against rawHeaderText, so we need to encode the string as a binary
+ // string instead of a unicode string.
+ let utf8Domain = String.fromCharCode.apply(
+ undefined,
+ new TextEncoder("UTF-8").encode(domain)
+ );
+ // Bug 1034658: nsIMsgIdentity doesn't like IDN in its email addresses.
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+ fields.to = "Nobody <nobody@" + domain + ">";
+ fields.cc = "Alex <alex@" + domain + ">";
+ fields.bcc = "Boris <boris@" + domain + ">";
+ fields.replyTo = "Charles <charles@" + domain + ">";
+ fields.subject = "This is an obscure reference";
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ // The identity sets the from field here.
+ From: "from@tinderbox.invalid",
+ To: "Nobody <nobody@" + utf8Domain + ">",
+ Cc: "Alex <alex@" + utf8Domain + ">",
+ Bcc: "Boris <boris@" + utf8Domain + ">",
+ "Reply-To": "Charles <charles@" + utf8Domain + ">",
+ Subject: "This is an obscure reference",
+ });
+}
+
+async function testDraftInfo() {
+ let fields = new CompFields();
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ FCC: identity.fccFolder,
+ "X-Identity-Key": identity.key,
+ "X-Mozilla-Draft-Info":
+ "internal/draft; " +
+ "vcard=0; receipt=0; DSN=0; uuencode=0; attachmentreminder=0; deliveryformat=4",
+ });
+
+ fields.attachVCard = true;
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ "X-Mozilla-Draft-Info":
+ "internal/draft; " +
+ "vcard=1; receipt=0; DSN=0; uuencode=0; attachmentreminder=0; deliveryformat=4",
+ });
+
+ fields.returnReceipt = true;
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ "X-Mozilla-Draft-Info":
+ "internal/draft; " +
+ "vcard=1; receipt=1; DSN=0; uuencode=0; attachmentreminder=0; deliveryformat=4",
+ });
+
+ fields.DSN = true;
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ "X-Mozilla-Draft-Info":
+ "internal/draft; " +
+ "vcard=1; receipt=1; DSN=1; uuencode=0; attachmentreminder=0; deliveryformat=4",
+ });
+
+ fields.attachmentReminder = true;
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ "X-Mozilla-Draft-Info":
+ "internal/draft; " +
+ "vcard=1; receipt=1; DSN=1; uuencode=0; attachmentreminder=1; deliveryformat=4",
+ });
+
+ fields.deliveryFormat = Ci.nsIMsgCompSendFormat.Both;
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ "X-Mozilla-Draft-Info":
+ "internal/draft; " +
+ "vcard=1; receipt=1; DSN=1; uuencode=0; attachmentreminder=1; deliveryformat=3",
+ });
+}
+
+async function testOtherHeadersAgentParam(sendAgent, minimalAgent) {
+ Services.prefs.setBoolPref("mailnews.headers.sendUserAgent", sendAgent);
+ if (sendAgent) {
+ Services.prefs.setBoolPref(
+ "mailnews.headers.useMinimalUserAgent",
+ minimalAgent
+ );
+ }
+
+ let fields = new CompFields();
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+ fields.priority = "high";
+ fields.references = "<fake@tinderbox.invalid> <more@test.invalid>";
+ fields.setHeader("X-Fake-Header", "124");
+ let before = Date.now();
+ let msgHdr = await richCreateMessage(fields, [], identity);
+ let after = Date.now();
+ let msgData = mailTestUtils.loadMessageToString(msgHdr.folder, msgHdr);
+ let expectedAgent = undefined; // !sendAgent
+ if (sendAgent) {
+ if (minimalAgent) {
+ expectedAgent = Services.strings
+ .createBundle("chrome://branding/locale/brand.properties")
+ .GetStringFromName("brandFullName");
+ } else {
+ expectedAgent = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+ ].getService(Ci.nsIHttpProtocolHandler).userAgent;
+ }
+ }
+ checkMessageHeaders(msgData, {
+ "Mime-Version": "1.0",
+ "User-Agent": expectedAgent,
+ "X-Priority": "2 (High)",
+ References: "<fake@tinderbox.invalid> <more@test.invalid>",
+ "In-Reply-To": "<more@test.invalid>",
+ "X-Fake-Header": "124",
+ });
+
+ // Check headers with dynamic content
+ let headers = MimeParser.extractHeaders(msgData);
+ Assert.ok(headers.has("Message-Id"));
+ Assert.ok(
+ headers.getRawHeader("Message-Id")[0].endsWith("@tinderbox.invalid>")
+ );
+ // This is a very special crafted check. We don't know when the message was
+ // actually created, but we have bounds on it, from above. From
+ // experimentation, there are a few ways you can create dates that Date.parse
+ // can't handle (specifically related to how 2-digit years). However, the
+ // optimal RFC 5322 form is supported by Date.parse. If Date.parse fails, we
+ // have a form that we shouldn't be using anyways.
+ let date = new Date(headers.getRawHeader("Date")[0]);
+ // If we have clock skew within the test, then our results are going to be
+ // meaningless. Hopefully, this is only rarely the case.
+ if (before > after) {
+ info("Clock skew detected, skipping date check");
+ } else {
+ // In case this all took place within one second, remove sub-millisecond
+ // timing (Date headers only carry second-level precision).
+ before = before - (before % 1000);
+ after = after - (after % 1000);
+ info(before + " <= " + date + " <= " + after + "?");
+ Assert.ok(before <= date && date <= after);
+ }
+
+ // We truncate too-long References. Check this.
+ let references = [];
+ for (let i = 0; i < 100; i++) {
+ references.push("<" + i + "@test.invalid>");
+ }
+ let expected = references.slice(47);
+ expected.unshift(references[0]);
+ fields.references = references.join(" ");
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ References: expected.join(" "),
+ "In-Reply-To": references[references.length - 1],
+ });
+}
+
+/**
+ * Tests that the domain for the Message-Id header defaults to the domain of the
+ * identity's address.
+ */
+async function testMessageIdUseIdentityAddress() {
+ const expectedMessageIdHostname = "tinderbox.test";
+
+ const identity = getSmtpIdentity(
+ `from@${expectedMessageIdHostname}`,
+ getBasicSmtpServer()
+ );
+
+ await createMsgAndCompareMessageId(identity, null, expectedMessageIdHostname);
+}
+
+/**
+ * Tests that if a custom address (with a custom domain) is used when composing a
+ * message, the domain in this address takes precendence over the domain of the
+ * identity's address to generate the value for the Message-Id header.
+ */
+async function testMessageIdUseFromDomain() {
+ const expectedMessageIdHostname = "another-tinderbox.test";
+
+ const identity = getSmtpIdentity("from@tinderbox.test", getBasicSmtpServer());
+
+ // Set the From header to an address that uses a different domain than
+ // the identity.
+ const fields = new CompFields();
+ fields.from = `Nobody <nobody@${expectedMessageIdHostname}>`;
+
+ await createMsgAndCompareMessageId(
+ identity,
+ fields,
+ expectedMessageIdHostname
+ );
+}
+
+/**
+ * Tests that if the identity has a "FQDN" attribute, it takes precedence to use as the
+ * domain for the Message-Id header over any other domain or address.
+ */
+async function testMessageIdUseIdentityAttribute() {
+ const expectedMessageIdHostname = "my-custom-fqdn.test";
+
+ const identity = getSmtpIdentity("from@tinderbox.test", getBasicSmtpServer());
+ identity.setCharAttribute("FQDN", expectedMessageIdHostname);
+
+ // Set the From header to an address that uses a different domain than
+ // the identity.
+ const fields = new CompFields();
+ fields.from = "Nobody <nobody@another-tinderbox.test>";
+
+ await createMsgAndCompareMessageId(
+ identity,
+ fields,
+ expectedMessageIdHostname
+ );
+}
+
+/**
+ * Util function to create a message using the given identity and fields,
+ * and test that the message ID that was generated for it has the correct
+ * host name.
+ *
+ * @param {nsIMsgIdentity} identity - The identity to use to create the message.
+ * @param {?nsIMsgCompFields} fields - The compose fields to use. If not provided,
+ * default fields are used.
+ * @param {string} expectedMessageIdHostname - The expected host name of the
+ * Message-Id header.
+ */
+async function createMsgAndCompareMessageId(
+ identity,
+ fields,
+ expectedMessageIdHostname
+) {
+ if (!fields) {
+ fields = new CompFields();
+ }
+
+ let msgHdr = await richCreateMessage(fields, [], identity);
+ let msgData = mailTestUtils.loadMessageToString(msgHdr.folder, msgHdr);
+ let headers = MimeParser.extractHeaders(msgData);
+
+ // As of bug 1727181, the identity does not override the message-id header.
+ Assert.ok(headers.has("Message-Id"), "the message has a Message-Id header");
+ Assert.ok(
+ headers
+ .getRawHeader("Message-Id")[0]
+ .endsWith(`@${expectedMessageIdHostname}>`),
+ `the hostname for the Message-Id header should be ${expectedMessageIdHostname}`
+ );
+}
+
+async function testOtherHeadersFullAgent() {
+ await testOtherHeadersAgentParam(true, false);
+}
+
+async function testOtherHeadersMinimalAgent() {
+ await testOtherHeadersAgentParam(true, true);
+}
+
+async function testOtherHeadersNoAgent() {
+ await testOtherHeadersAgentParam(false, undefined);
+}
+
+async function testNewsgroups() {
+ let fields = new CompFields();
+ let nntpServer = localAccountUtils.create_incoming_server(
+ "nntp",
+ 534,
+ "",
+ ""
+ );
+ nntpServer
+ .QueryInterface(Ci.nsINntpIncomingServer)
+ .subscribeToNewsgroup("mozilla.test");
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+ fields.newsgroups = "mozilla.test, mozilla.test.multimedia";
+ fields.followupTo = "mozilla.test";
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ // The identity should override the compose fields here.
+ Newsgroups: "mozilla.test,mozilla.test.multimedia",
+ "Followup-To": "mozilla.test",
+ "X-Mozilla-News-Host": "localhost",
+ });
+}
+
+async function testSendHeaders() {
+ let fields = new CompFields();
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+ identity.setCharAttribute("headers", "bah,humbug");
+ identity.setCharAttribute(
+ "header.bah",
+ "X-Custom-1: A header value: with a colon"
+ );
+ identity.setUnicharAttribute("header.humbug", "X-Custom-2: Enchanté");
+ identity.setCharAttribute("subscribed_mailing_lists", "list@test.invalid");
+ identity.setCharAttribute(
+ "replyto_mangling_mailing_lists",
+ "replyto@test.invalid"
+ );
+ fields.to = "list@test.invalid";
+ fields.cc = "not-list@test.invalid";
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ "X-Custom-1": "A header value: with a colon",
+ "X-Custom-2": "=?UTF-8?B?RW5jaGFudMOp?=",
+ "Mail-Followup-To": "list@test.invalid, not-list@test.invalid",
+ "Mail-Reply-To": undefined,
+ });
+
+ // Don't set the M-F-T header if there's no list.
+ fields.to = "replyto@test.invalid";
+ fields.cc = "";
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ "X-Custom-1": "A header value: with a colon",
+ "X-Custom-2": "=?UTF-8?B?RW5jaGFudMOp?=",
+ "Mail-Reply-To": "from@tinderbox.invalid",
+ "Mail-Followup-To": undefined,
+ });
+}
+
+async function testContentHeaders() {
+ // Disable RFC 2047 fallback
+ Services.prefs.setIntPref("mail.strictly_mime.parm_folding", 2);
+ let fields = new CompFields();
+ fields.body = "A body";
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ "Content-Type": "text/html; charset=UTF-8",
+ "Content-Transfer-Encoding": "7bit",
+ });
+
+ // non-ASCII body should be 8-bit...
+ fields.body = "Archæologist";
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ "Content-Type": "text/html; charset=UTF-8",
+ "Content-Transfer-Encoding": "8bit",
+ });
+
+ // Attachments
+ fields.body = "";
+ let plainAttachment = makeAttachment({
+ url: "data:text/plain,oïl",
+ name: "attachment.txt",
+ });
+ let plainAttachmentHeaders = {
+ "Content-Type": "text/plain; charset=UTF-8",
+ "Content-Transfer-Encoding": "base64",
+ "Content-Disposition": 'attachment; filename="attachment.txt"',
+ };
+ await richCreateMessage(fields, [plainAttachment], identity);
+ checkDraftHeaders(
+ {
+ "Content-Type": "text/html; charset=UTF-8",
+ "Content-Transfer-Encoding": "7bit",
+ },
+ "1"
+ );
+ checkDraftHeaders(plainAttachmentHeaders, "2");
+
+ plainAttachment.name = "oïl.txt";
+ plainAttachmentHeaders["Content-Disposition"] =
+ "attachment; filename*=UTF-8''%6F%C3%AF%6C%2E%74%78%74";
+ await richCreateMessage(fields, [plainAttachment], identity);
+ checkDraftHeaders(plainAttachmentHeaders, "2");
+
+ plainAttachment.name = "\ud83d\udca9.txt";
+ plainAttachmentHeaders["Content-Disposition"] =
+ "attachment; filename*=UTF-8''%F0%9F%92%A9%2E%74%78%74";
+ await richCreateMessage(fields, [plainAttachment], identity);
+ checkDraftHeaders(plainAttachmentHeaders, "2");
+
+ let httpAttachment = makeAttachment({
+ url: "data:text/html,<html></html>",
+ name: "attachment.html",
+ });
+ let httpAttachmentHeaders = {
+ "Content-Type": "text/html; charset=UTF-8",
+ "Content-Disposition": 'attachment; filename="attachment.html"',
+ "Content-Location": "data:text/html,<html></html>",
+ };
+ await richCreateMessage(fields, [httpAttachment], identity);
+ checkDraftHeaders(
+ {
+ "Content-Location": undefined,
+ },
+ "1"
+ );
+ checkDraftHeaders(httpAttachmentHeaders, "2");
+
+ let cloudAttachment = makeAttachment({
+ url: Services.io.newFileURI(do_get_file("data/test-UTF-8.txt")).spec,
+ sendViaCloud: true,
+ htmlAnnotation:
+ "<html><body>This is an html placeholder file.</body></html>",
+ cloudFileAccountKey: "akey",
+ cloudPartHeaderData: "0123456789ABCDE",
+ name: "attachment.html",
+ contentLocation: "http://localhost.invalid/",
+ });
+ let cloudAttachmentHeaders = {
+ "Content-Type": "text/html; charset=utf-8",
+ "X-Mozilla-Cloud-Part":
+ "cloudFile; " +
+ "url=http://localhost.invalid/; " +
+ "provider=akey; " +
+ 'data="0123456789ABCDE"',
+ };
+ await richCreateMessage(fields, [cloudAttachment], identity);
+ checkDraftHeaders(cloudAttachmentHeaders, "2");
+
+ // Cloud attachment with non-ascii file name.
+ cloudAttachment = makeAttachment({
+ url: Services.io.newFileURI(do_get_file("data/test-UTF-8.txt")).spec,
+ sendViaCloud: true,
+ htmlAnnotation:
+ "<html><body>This is an html placeholder file.</body></html>",
+ cloudFileAccountKey: "akey",
+ cloudPartHeaderData: "0123456789ABCDE",
+ name: "ファイル.txt",
+ contentLocation: "http://localhost.invalid/",
+ });
+ cloudAttachmentHeaders = {
+ "Content-Type": "text/html; charset=utf-8",
+ "X-Mozilla-Cloud-Part":
+ "cloudFile; " +
+ "url=http://localhost.invalid/; " +
+ "provider=akey; " +
+ 'data="0123456789ABCDE"',
+ };
+ await richCreateMessage(fields, [cloudAttachment], identity);
+ checkDraftHeaders(cloudAttachmentHeaders, "2");
+
+ // Some multipart/alternative tests.
+ fields.body = "Some text";
+ fields.forcePlainText = false;
+ fields.useMultipartAlternative = true;
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ "Content-Type": "multipart/alternative; boundary=.",
+ });
+ checkDraftHeaders(
+ {
+ "Content-Type": "text/plain; charset=UTF-8; format=flowed",
+ "Content-Transfer-Encoding": "7bit",
+ },
+ "1"
+ );
+ checkDraftHeaders(
+ {
+ "Content-Type": "text/html; charset=UTF-8",
+ "Content-Transfer-Encoding": "7bit",
+ },
+ "2"
+ );
+
+ // multipart/mixed
+ // + multipart/alternative
+ // + text/plain
+ // + text/html
+ // + text/plain attachment
+ await richCreateMessage(fields, [plainAttachment], identity);
+ checkDraftHeaders({
+ "Content-Type": "multipart/mixed; boundary=.",
+ });
+ checkDraftHeaders(
+ {
+ "Content-Type": "multipart/alternative; boundary=.",
+ },
+ "1"
+ );
+ checkDraftHeaders(
+ {
+ "Content-Type": "text/plain; charset=UTF-8; format=flowed",
+ "Content-Transfer-Encoding": "7bit",
+ },
+ "1.1"
+ );
+ checkDraftHeaders(
+ {
+ "Content-Type": "text/html; charset=UTF-8",
+ "Content-Transfer-Encoding": "7bit",
+ },
+ "1.2"
+ );
+ checkDraftHeaders(plainAttachmentHeaders, "2");
+
+ // Three attachments, and a multipart/alternative. Oh the humanity!
+ await richCreateMessage(
+ fields,
+ [plainAttachment, httpAttachment, cloudAttachment],
+ identity
+ );
+ checkDraftHeaders({
+ "Content-Type": "multipart/mixed; boundary=.",
+ });
+ checkDraftHeaders(
+ {
+ "Content-Type": "multipart/alternative; boundary=.",
+ },
+ "1"
+ );
+ checkDraftHeaders(
+ {
+ "Content-Type": "text/plain; charset=UTF-8; format=flowed",
+ "Content-Transfer-Encoding": "7bit",
+ },
+ "1.1"
+ );
+ checkDraftHeaders(
+ {
+ "Content-Type": "text/html; charset=UTF-8",
+ "Content-Transfer-Encoding": "7bit",
+ },
+ "1.2"
+ );
+ checkDraftHeaders(plainAttachmentHeaders, "2");
+ checkDraftHeaders(httpAttachmentHeaders, "3");
+ checkDraftHeaders(cloudAttachmentHeaders, "4");
+
+ // Test a request for plain text with text/html.
+ fields.forcePlainText = true;
+ fields.useMultipartAlternative = false;
+ await richCreateMessage(fields, [], identity);
+ checkDraftHeaders({
+ "Content-Type": "text/plain; charset=UTF-8; format=flowed",
+ "Content-Transfer-Encoding": "7bit",
+ });
+}
+
+async function testSentMessage() {
+ let server = setupServerDaemon();
+ let daemon = server._daemon;
+ server.start();
+ try {
+ let localserver = getBasicSmtpServer(server.port);
+ let identity = getSmtpIdentity("test@tinderbox.invalid", localserver);
+ await sendMessage(
+ {
+ to: "Nobody <nobody@tinderbox.invalid>",
+ cc: "Alex <alex@tinderbox.invalid>",
+ bcc: "Boris <boris@tinderbox.invalid>",
+ replyTo: "Charles <charles@tinderbox.invalid>",
+ },
+ identity,
+ {},
+ []
+ );
+ checkMessageHeaders(daemon.post, {
+ From: "test@tinderbox.invalid",
+ To: "Nobody <nobody@tinderbox.invalid>",
+ Cc: "Alex <alex@tinderbox.invalid>",
+ Bcc: undefined,
+ "Reply-To": "Charles <charles@tinderbox.invalid>",
+ "X-Mozilla-Status": undefined,
+ "X-Mozilla-Keys": undefined,
+ "X-Mozilla-Draft-Info": undefined,
+ Fcc: undefined,
+ });
+ server.resetTest();
+ await sendMessage({ bcc: "Somebody <test@tinderbox.invalid" }, identity);
+ checkMessageHeaders(daemon.post, {
+ To: "undisclosed-recipients: ;",
+ });
+ server.resetTest();
+ await sendMessage(
+ {
+ to: "Somebody <test@tinderbox.invalid>",
+ returnReceipt: true,
+ receiptHeaderType: Ci.nsIMsgMdnGenerator.eDntRrtType,
+ },
+ identity
+ );
+ checkMessageHeaders(daemon.post, {
+ "Disposition-Notification-To": "test@tinderbox.invalid",
+ "Return-Receipt-To": "test@tinderbox.invalid",
+ });
+ server.resetTest();
+ let cloudAttachment = makeAttachment({
+ url: Services.io.newFileURI(do_get_file("data/test-UTF-8.txt")).spec,
+ sendViaCloud: true,
+ htmlAnnotation:
+ "<html><body>This is an html placeholder file.</body></html>",
+ cloudFileAccountKey: "akey",
+ cloudPartHeaderData: "0123456789ABCDE",
+ name: "attachment.html",
+ contentLocation: "http://localhost.invalid/",
+ });
+ await sendMessage({ to: "test@tinderbox.invalid" }, identity, {}, [
+ cloudAttachment,
+ ]);
+ checkMessageHeaders(
+ daemon.post,
+ {
+ "Content-Type": "text/html; charset=utf-8",
+ "X-Mozilla-Cloud-Part": "cloudFile; url=http://localhost.invalid/",
+ },
+ "2"
+ );
+ } finally {
+ server.stop();
+ }
+}
+
+var tests = [
+ testEnvelope,
+ testI18NEnvelope,
+ testIDNEnvelope,
+ testDraftInfo,
+ testOtherHeadersFullAgent,
+ testOtherHeadersMinimalAgent,
+ testOtherHeadersNoAgent,
+ testNewsgroups,
+ testSendHeaders,
+ testContentHeaders,
+ testSentMessage,
+ testMessageIdUseIdentityAddress,
+ testMessageIdUseFromDomain,
+ testMessageIdUseIdentityAttribute,
+];
+
+function run_test() {
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+ tests.forEach(x => add_task(x));
+ run_next_test();
+}
diff --git a/comm/mailnews/compose/test/unit/test_nsIMsgCompFields.js b/comm/mailnews/compose/test/unit/test_nsIMsgCompFields.js
new file mode 100644
index 0000000000..69c1b753b6
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_nsIMsgCompFields.js
@@ -0,0 +1,62 @@
+/* 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/. */
+
+// Test that nsIMsgCompFields works properly
+
+var nsMsgCompFields = Components.Constructor(
+ "@mozilla.org/messengercompose/composefields;1",
+ Ci.nsIMsgCompFields
+);
+
+function check_headers(enumerator, container) {
+ let checkValues = new Set(container.map(header => header.toLowerCase()));
+ for (let value of enumerator) {
+ value = value.toLowerCase();
+ Assert.ok(checkValues.has(value));
+ checkValues.delete(value);
+ }
+ Assert.equal(checkValues.size, 0);
+}
+
+function run_test() {
+ let fields = new nsMsgCompFields();
+ Assert.ok(fields instanceof Ci.nsIMsgCompFields);
+ Assert.ok(fields instanceof Ci.msgIStructuredHeaders);
+ Assert.ok(fields instanceof Ci.msgIWritableStructuredHeaders);
+ check_headers(fields.headerNames, []);
+ Assert.ok(!fields.hasRecipients);
+
+ // Try some basic headers
+ fields.setHeader("From", [{ name: "", email: "a@test.invalid" }]);
+ let from = fields.getHeader("from");
+ Assert.equal(from.length, 1);
+ Assert.equal(from[0].email, "a@test.invalid");
+ check_headers(fields.headerNames, ["From"]);
+ Assert.ok(!fields.hasRecipients);
+
+ // Add a To header
+ fields.setHeader("To", [{ name: "", email: "b@test.invalid" }]);
+ check_headers(fields.headerNames, ["From", "To"]);
+ Assert.ok(fields.hasRecipients);
+
+ // Delete a header...
+ fields.deleteHeader("from");
+ Assert.equal(fields.getHeader("From"), undefined);
+ check_headers(fields.headerNames, ["To"]);
+
+ // Subject should work and not convert to RFC 2047.
+ fields.subject = "\u79c1\u306f\u4ef6\u540d\u5348\u524d";
+ Assert.equal(fields.subject, "\u79c1\u306f\u4ef6\u540d\u5348\u524d");
+ Assert.equal(
+ fields.getHeader("Subject"),
+ "\u79c1\u306f\u4ef6\u540d\u5348\u524d"
+ );
+
+ // Check header synchronization.
+ fields.from = "a@test.invalid";
+ Assert.equal(fields.from, "a@test.invalid");
+ Assert.equal(fields.getHeader("From")[0].email, "a@test.invalid");
+ fields.from = null;
+ Assert.equal(fields.getHeader("From"), undefined);
+}
diff --git a/comm/mailnews/compose/test/unit/test_nsMsgCompose1.js b/comm/mailnews/compose/test/unit/test_nsMsgCompose1.js
new file mode 100644
index 0000000000..700232b46e
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_nsMsgCompose1.js
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/**
+ * Tests nsMsgCompose expandMailingLists.
+ */
+
+/**
+ * Helper to check population worked as expected.
+ *
+ * @param {string} aTo - Text in the To field.
+ * @param {string} aCheckTo - The expected To addresses (after possible list population)
+ */
+function checkPopulate(aTo, aCheckTo) {
+ var msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance(
+ Ci.nsIMsgCompose
+ );
+
+ // Set up some basic fields for compose.
+ var fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+
+ fields.to = aTo;
+
+ // Set up some params
+ var params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+
+ params.composeFields = fields;
+
+ msgCompose.initialize(params);
+
+ msgCompose.expandMailingLists();
+ let addresses = fields.getHeader("To");
+ let checkEmails = MailServices.headerParser.parseDecodedHeader(aCheckTo);
+ Assert.equal(addresses.length, checkEmails.length);
+ for (let i = 0; i < addresses.length; i++) {
+ Assert.equal(addresses[i].name, checkEmails[i].name);
+ Assert.equal(addresses[i].email, checkEmails[i].email);
+ }
+}
+
+function run_test() {
+ loadABFile("../../../data/abLists1", kPABData.fileName);
+ loadABFile("../../../data/abLists2", kCABData.fileName);
+
+ // Test - Check we can initialize with fewest specified
+ // parameters and don't fail/crash like we did in bug 411646.
+
+ var msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance(
+ Ci.nsIMsgCompose
+ );
+
+ // Set up some params
+ var params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+
+ msgCompose.initialize(params);
+
+ // Test - expandMailingLists basic functionality.
+
+ // Re-initialize
+ msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance(
+ Ci.nsIMsgCompose
+ );
+
+ // Set up some basic fields for compose.
+ var fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+
+ // These aren't in the address book copied above.
+ fields.from = "test1@foo1.invalid";
+ fields.to = "test2@foo1.invalid";
+ fields.cc = "test3@foo1.invalid";
+ fields.bcc = "test4@foo1.invalid";
+
+ // Set up some params
+ params = Cc["@mozilla.org/messengercompose/composeparams;1"].createInstance(
+ Ci.nsIMsgComposeParams
+ );
+
+ params.composeFields = fields;
+
+ msgCompose.initialize(params);
+
+ msgCompose.expandMailingLists();
+ Assert.equal(fields.to, "test2@foo1.invalid");
+ Assert.equal(fields.cc, "test3@foo1.invalid");
+ Assert.equal(fields.bcc, "test4@foo1.invalid");
+
+ // Test - expandMailingLists with plain text.
+
+ checkPopulate("test4@foo.invalid", "test4@foo.invalid");
+
+ // Test - expandMailingLists with html.
+
+ checkPopulate("test5@foo.invalid", "test5@foo.invalid");
+
+ // Test - expandMailingLists with a list of three items.
+
+ checkPopulate(
+ "TestList1 <TestList1>",
+ "test1@foo.invalid,test2@foo.invalid,test3@foo.invalid"
+ );
+
+ // Test - expandMailingLists with a list of one item.
+
+ checkPopulate("TestList2 <TestList2>", "test4@foo.invalid");
+
+ checkPopulate("TestList3 <TestList3>", "test5@foo.invalid");
+
+ // Test - expandMailingLists with items from multiple address books.
+
+ checkPopulate(
+ "TestList1 <TestList1>, test3@com.invalid",
+ "test1@foo.invalid,test2@foo.invalid,test3@foo.invalid,test3@com.invalid"
+ );
+
+ checkPopulate(
+ "TestList2 <TestList2>, ListTest2 <ListTest2>",
+ "test4@foo.invalid,test4@com.invalid"
+ );
+
+ checkPopulate(
+ "TestList3 <TestList3>, ListTest1 <ListTest1>",
+ "test5@foo.invalid,test1@com.invalid,test2@com.invalid,test3@com.invalid"
+ );
+
+ // test bug 254519 rfc 2047 encoding
+ checkPopulate(
+ "=?iso-8859-1?Q?Sure=F6name=2C_Forename_Dr=2E?= <pb@bieringer.invalid>",
+ '"Sure\u00F6name, Forename Dr." <pb@bieringer.invalid>'
+ );
+}
diff --git a/comm/mailnews/compose/test/unit/test_nsMsgCompose2.js b/comm/mailnews/compose/test/unit/test_nsMsgCompose2.js
new file mode 100644
index 0000000000..5f234444b8
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_nsMsgCompose2.js
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for nsMsgCompose functions relating to send listeners.
+ */
+
+let gMsgCompose = null;
+let numSendListenerFunctions = 7;
+
+let gSLAll = new Array(numSendListenerFunctions + 1);
+
+function sendListener() {}
+
+sendListener.prototype = {
+ mReceived: 0,
+ mAutoRemoveItem: 0,
+
+ onStartSending(aMsgID, aMsgSize) {
+ this.mReceived |= 0x01;
+ if (this.mAutoRemoveItem == 0x01) {
+ gMsgCompose.removeMsgSendListener(this);
+ }
+ },
+ onProgress(aMsgID, aProgress, aProgressMax) {
+ this.mReceived |= 0x02;
+ if (this.mAutoRemoveItem == 0x02) {
+ gMsgCompose.removeMsgSendListener(this);
+ }
+ },
+ onStatus(aMsgID, aMsg) {
+ this.mReceived |= 0x04;
+ if (this.mAutoRemoveItem == 0x04) {
+ gMsgCompose.removeMsgSendListener(this);
+ }
+ },
+ onStopSending(aMsgID, aStatus, aMsg, aReturnFile) {
+ this.mReceived |= 0x08;
+ if (this.mAutoRemoveItem == 0x08) {
+ gMsgCompose.removeMsgSendListener(this);
+ }
+ },
+ onGetDraftFolderURI(aMsgID, aFolderURI) {
+ this.mReceived |= 0x10;
+ if (this.mAutoRemoveItem == 0x10) {
+ gMsgCompose.removeMsgSendListener(this);
+ }
+ },
+ onSendNotPerformed(aMsgID, aStatus) {
+ this.mReceived |= 0x20;
+ if (this.mAutoRemoveItem == 0x20) {
+ gMsgCompose.removeMsgSendListener(this);
+ }
+ },
+ onTransportSecurityError(msgID, status, secInfo, location) {
+ this.mReceived |= 0x40;
+ if (this.mAutoRemoveItem == 0x40) {
+ gMsgCompose.removeMsgSendListener(this);
+ }
+ },
+};
+
+function NotifySendListeners() {
+ gMsgCompose.onStartSending(null, null);
+ gMsgCompose.onProgress(null, null, null);
+ gMsgCompose.onStatus(null, null);
+ gMsgCompose.onStopSending(null, null, null, null);
+ gMsgCompose.onGetDraftFolderURI(null, null);
+ gMsgCompose.onSendNotPerformed(null, null);
+ gMsgCompose.onTransportSecurityError(null, null, null, "");
+}
+
+function run_test() {
+ gMsgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance(
+ Ci.nsIMsgCompose
+ );
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ gMsgCompose.initialize(params);
+
+ Assert.ok(gMsgCompose != null);
+
+ // Test - Add a listener
+
+ for (let i = 0; i < numSendListenerFunctions + 1; ++i) {
+ gSLAll[i] = new sendListener();
+ gMsgCompose.addMsgSendListener(gSLAll[i]);
+ }
+
+ // Test - Notify all listeners
+
+ NotifySendListeners();
+
+ const bitMask = (1 << numSendListenerFunctions) - 1;
+ for (let i = 0; i < numSendListenerFunctions + 1; ++i) {
+ Assert.equal(gSLAll[i].mReceived, bitMask);
+ gSLAll[i].mReceived = 0;
+
+ // And prepare for test 3.
+ gSLAll[i].mAutoRemoveItem = 1 << i;
+ }
+
+ // Test - Remove some listeners as we go
+
+ NotifySendListeners();
+
+ let currentReceived = 0;
+
+ for (let i = 0; i < numSendListenerFunctions + 1; ++i) {
+ if (i < numSendListenerFunctions) {
+ currentReceived += 1 << i;
+ }
+
+ Assert.equal(gSLAll[i].mReceived, currentReceived);
+ gSLAll[i].mReceived = 0;
+ }
+
+ // Test - Ensure the listeners have been removed.
+
+ NotifySendListeners();
+
+ for (let i = 0; i < numSendListenerFunctions + 1; ++i) {
+ if (i < numSendListenerFunctions) {
+ Assert.equal(gSLAll[i].mReceived, 0);
+ } else {
+ Assert.equal(gSLAll[i].mReceived, bitMask);
+ }
+ }
+
+ // Test - Remove main listener
+
+ gMsgCompose.removeMsgSendListener(gSLAll[numSendListenerFunctions]);
+}
diff --git a/comm/mailnews/compose/test/unit/test_nsMsgCompose3.js b/comm/mailnews/compose/test/unit/test_nsMsgCompose3.js
new file mode 100644
index 0000000000..71521abff4
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_nsMsgCompose3.js
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for increasing the popularity of contacts via
+ * expandMailingLists.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var TESTS = [
+ {
+ email: "em@test.invalid",
+ // TB 2 stored popularity as hex, so we need to check correct handling.
+ prePopularity: "a",
+ postPopularity: "11",
+ },
+ {
+ email: "e@test.invalid",
+ prePopularity: "0",
+ postPopularity: "1",
+ },
+ {
+ email: "e@test.invalid",
+ prePopularity: "1",
+ postPopularity: "2",
+ },
+ {
+ email: "em@test.invalid",
+ prePopularity: "11",
+ postPopularity: "12",
+ },
+];
+
+function checkPopulate(aTo, aCheckTo) {
+ let msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance(
+ Ci.nsIMsgCompose
+ );
+
+ // Set up some basic fields for compose.
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+
+ fields.to = aTo;
+
+ // Set up some params
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+
+ params.composeFields = fields;
+
+ msgCompose.initialize(params);
+
+ Assert.ok(!msgCompose.expandMailingLists());
+
+ Assert.equal(fields.to, aCheckTo);
+}
+
+function run_test() {
+ loadABFile("../../../data/tb2hexpopularity", kPABData.fileName);
+
+ // Check the popularity index on a couple of cards.
+ let AB = MailServices.ab.getDirectory(kPABData.URI);
+
+ for (let i = 0; i < TESTS.length; ++i) {
+ let card = AB.cardForEmailAddress(TESTS[i].email);
+ Assert.ok(!!card);
+
+ // Thunderbird 2 stored its popularityIndexes as hex, hence when we read it
+ // now we're going to get a hex value. The AB has a value of "a".
+ Assert.equal(
+ card.getProperty("PopularityIndex", -1),
+ TESTS[i].prePopularity
+ );
+
+ // Call the check populate function.
+ checkPopulate(TESTS[i].email, TESTS[i].email);
+
+ // Now we've run check populate, check the popularityIndex has increased.
+ card = AB.cardForEmailAddress(TESTS[i].email);
+ Assert.ok(!!card);
+
+ // Thunderbird 2 stored its popularityIndexes as hex, hence when we read it
+ // now we're going to get a hex value. The AB has a value of "a".
+ Assert.equal(
+ card.getProperty("PopularityIndex", -1),
+ TESTS[i].postPopularity
+ );
+ }
+}
diff --git a/comm/mailnews/compose/test/unit/test_nsSmtpService1.js b/comm/mailnews/compose/test/unit/test_nsSmtpService1.js
new file mode 100644
index 0000000000..d062ef3f1e
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_nsSmtpService1.js
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for nsSmtpService
+ */
+
+var SmtpServiceContractID = "@mozilla.org/messengercompose/smtp;1";
+var nsISmtpService = Ci.nsISmtpService;
+
+function run_test() {
+ var smtpService = Cc[SmtpServiceContractID].getService(nsISmtpService);
+
+ // Test - no servers
+
+ var smtpServers = smtpService.servers;
+ Assert.equal(smtpServers.length, 0);
+
+ Assert.equal(smtpService.defaultServer, null);
+
+ // Test - add single server, and check
+
+ var smtpServer = smtpService.createServer();
+
+ smtpServer.hostname = "localhost";
+ smtpServer.description = "test";
+
+ smtpService.defaultServer = smtpServer;
+
+ // Test - Check to see there is only one element in the server list
+ smtpServers = smtpService.servers;
+ Assert.ok(smtpServers.length == 1);
+
+ // Test - Find the server in different ways
+ Assert.equal(smtpServer, smtpService.findServer("", "localhost"));
+ Assert.equal(smtpServer, smtpService.getServerByKey(smtpServer.key));
+
+ // Test - Try finding one that doesn't exist.
+ Assert.equal(null, smtpService.findServer("", "test"));
+
+ // Test - Check default server is still ok
+ Assert.equal(smtpServer, smtpService.defaultServer);
+
+ // Test - Delete the only server
+ smtpService.deleteServer(smtpServer);
+
+ smtpServers = smtpService.servers;
+ Assert.ok(smtpServers.length == 0);
+
+ // do_check_eq(null, smtpService.defaultServer);
+
+ // Test - add multiple servers
+
+ var smtpServerArray = new Array(3);
+
+ for (let i = 0; i < 3; ++i) {
+ smtpServerArray[i] = smtpService.createServer();
+ }
+
+ smtpServerArray[0].hostname = "localhost";
+ smtpServerArray[0].description = "test";
+ smtpServerArray[0].username = "user";
+
+ smtpServerArray[1].hostname = "localhost";
+ smtpServerArray[1].description = "test1";
+ smtpServerArray[1].username = "user1";
+
+ smtpServerArray[2].hostname = "localhost1";
+ smtpServerArray[2].description = "test2";
+ smtpServerArray[2].username = "";
+
+ // Now check them
+ smtpServers = smtpService.servers;
+
+ var found = [false, false, false];
+
+ for (smtpServer of smtpServers) {
+ for (let i = 0; i < 3; ++i) {
+ if (smtpServer.key == smtpServerArray[i].key) {
+ found[i] = true;
+ }
+ }
+ }
+
+ Assert.equal(found, "true,true,true");
+
+ // Test - Find the servers.
+
+ Assert.equal(
+ smtpServerArray[0].key,
+ smtpService.findServer("user", "localhost").key
+ );
+ Assert.equal(
+ smtpServerArray[1].key,
+ smtpService.findServer("user1", "localhost").key
+ );
+ Assert.equal(
+ smtpServerArray[2].key,
+ smtpService.findServer("", "localhost1").key
+ );
+
+ Assert.equal(null, smtpService.findServer("user2", "localhost"));
+
+ // XXX: FIXME
+ // do_check_eq(null, smtpService.findServer("", "localhost"));
+
+ for (let i = 0; i < 3; ++i) {
+ Assert.equal(
+ smtpServerArray[i].key,
+ smtpService.getServerByKey(smtpServerArray[i].key).key
+ );
+ }
+
+ smtpService.defaultServer = smtpServerArray[2];
+ Assert.equal(
+ smtpService.defaultServer.key,
+ smtpServerArray[2].key,
+ "Default server should be correctly set"
+ );
+
+ // Test - Delete the servers
+
+ for (let i = 0; i < 3; ++i) {
+ smtpService.deleteServer(smtpServerArray[i]);
+ }
+
+ smtpServers = smtpService.servers;
+ Assert.ok(smtpServers.length == 0);
+}
diff --git a/comm/mailnews/compose/test/unit/test_saveDraft.js b/comm/mailnews/compose/test/unit/test_saveDraft.js
new file mode 100644
index 0000000000..b3f7029bab
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_saveDraft.js
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for checking correctly saved as draft with unread.
+ */
+
+add_task(async function checkDraft() {
+ await createMessage();
+ Assert.equal(gDraftFolder.getTotalMessages(false), 1);
+ Assert.equal(gDraftFolder.getNumUnread(false), 1);
+});
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+ run_next_test();
+}
diff --git a/comm/mailnews/compose/test/unit/test_sendBackground.js b/comm/mailnews/compose/test/unit/test_sendBackground.js
new file mode 100644
index 0000000000..6d0a59f4f9
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_sendBackground.js
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Tests sending a message in the background (checks auto-send works).
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var server;
+var originalData;
+var finished = false;
+var identity = null;
+var testFile1 = do_get_file("data/429891_testcase.eml");
+var testFile2 = do_get_file("data/message1.eml");
+
+var kTestFile1Sender = "from_A@foo.invalid";
+var kTestFile1Recipient = "to_A@foo.invalid";
+
+var kIdentityMail = "identity@foo.invalid";
+
+var gMsgSendLater;
+
+// This listener handles the post-sending of the actual message and checks the
+// sequence and ensures the data is correct.
+function msll() {}
+
+msll.prototype = {
+ _initialTotal: 0,
+
+ // nsIMsgSendLaterListener
+ onStartSending(aTotal) {
+ this._initialTotal = 1;
+ Assert.equal(gMsgSendLater.sendingMessages, true);
+ Assert.equal(aTotal, 1);
+ },
+ onMessageStartSending(
+ aCurrentMessage,
+ aTotalMessageCount,
+ aMessageHeader,
+ aIdentity
+ ) {},
+ onMessageSendProgress(
+ aCurrentMessage,
+ aTotalMessageCount,
+ aMessageSendPercent,
+ aMessageCopyPercent
+ ) {},
+ onMessageSendError(aCurrentMessage, aMessageHeader, aStatus, aMsg) {
+ do_throw(
+ "onMessageSendError should not have been called, status: " + aStatus
+ );
+ },
+ onStopSending(aStatus, aMsg, aTotalTried, aSuccessful) {
+ do_test_finished();
+ print("msll onStopSending\n");
+ try {
+ Assert.equal(aStatus, 0);
+ Assert.equal(aTotalTried, 1);
+ Assert.equal(aSuccessful, 1);
+ Assert.equal(this._initialTotal, 1);
+ Assert.equal(gMsgSendLater.sendingMessages, false);
+
+ do_check_transaction(server.playTransaction(), [
+ "EHLO test",
+ "MAIL FROM:<" +
+ kTestFile1Sender +
+ "> BODY=8BITMIME SIZE=" +
+ originalData.length,
+ "RCPT TO:<" + kTestFile1Recipient + ">",
+ "DATA",
+ ]);
+
+ // Compare data file to what the server received
+ Assert.equal(originalData, server._daemon.post);
+
+ // check there's still one message left in the folder
+ Assert.equal(
+ gMsgSendLater.getUnsentMessagesFolder(null).getTotalMessages(false),
+ 1
+ );
+
+ finished = true;
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+ },
+};
+
+add_task(async function run_the_test() {
+ // The point of this test - send in background.
+ Services.prefs.setBoolPref("mailnews.sendInBackground", true);
+
+ // Ensure we have a local mail account, an normal account and appropriate
+ // servers and identities.
+ localAccountUtils.loadLocalMailAccount();
+
+ // Now load (and internally initialize) the send later service
+ gMsgSendLater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService(
+ Ci.nsIMsgSendLater
+ );
+
+ // Test file - for bug 429891
+ originalData = await IOUtils.readUTF8(testFile1.path);
+
+ // Check that the send later service thinks we don't have messages to send
+ Assert.equal(gMsgSendLater.hasUnsentMessages(identity), false);
+
+ MailServices.accounts.setSpecialFolders();
+
+ let account = MailServices.accounts.createAccount();
+ let incomingServer = MailServices.accounts.createIncomingServer(
+ "test",
+ "localhost",
+ "pop3"
+ );
+
+ // Start the fake SMTP server
+ server = setupServerDaemon();
+ server.start();
+ var smtpServer = getBasicSmtpServer(server.port);
+ identity = getSmtpIdentity(kIdentityMail, smtpServer);
+
+ account.addIdentity(identity);
+ account.defaultIdentity = identity;
+ account.incomingServer = incomingServer;
+ MailServices.accounts.defaultAccount = account;
+
+ localAccountUtils.rootFolder.createLocalSubfolder("Sent");
+
+ Assert.equal(identity.doFcc, true);
+
+ // Now prepare to actually "send" the message later, i.e. dump it in the
+ // unsent messages folder.
+
+ var compFields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+
+ // Setting the compFields sender and recipient to any value is required to
+ // survive mime_sanity_check_fields in nsMsgCompUtils.cpp.
+ // Sender and recipient are required for sendMessageFile but SMTP
+ // transaction values will be used directly from mail body.
+ compFields.from = "irrelevant@foo.invalid";
+ compFields.to = "irrelevant@foo.invalid";
+
+ var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance(
+ Ci.nsIMsgSend
+ );
+ var msgSend2 = Cc["@mozilla.org/messengercompose/send;1"].createInstance(
+ Ci.nsIMsgSend
+ );
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ // A test to check that we are sending files correctly, including checking
+ // what the server receives and what we output.
+ test = "sendMessageLater";
+
+ var messageListener = new msll();
+
+ gMsgSendLater.addListener(messageListener);
+
+ // Send this message later - it shouldn't get sent
+ msgSend.sendMessageFile(
+ identity,
+ "",
+ compFields,
+ testFile2,
+ false,
+ false,
+ Ci.nsIMsgSend.nsMsgQueueForLater,
+ null,
+ null,
+ null,
+ null
+ );
+
+ // Send the unsent message in the background, because we have
+ // mailnews.sendInBackground set, nsMsgSendLater should just send it for
+ // us.
+ msgSend2.sendMessageFile(
+ identity,
+ "",
+ compFields,
+ testFile1,
+ false,
+ false,
+ Ci.nsIMsgSend.nsMsgDeliverBackground,
+ null,
+ null,
+ null,
+ null
+ );
+
+ server.performTest();
+
+ do_timeout(10000, function () {
+ if (!finished) {
+ do_throw("Notifications of message send/copy not received");
+ }
+ });
+
+ do_test_pending();
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+});
diff --git a/comm/mailnews/compose/test/unit/test_sendMailAddressIDN.js b/comm/mailnews/compose/test/unit/test_sendMailAddressIDN.js
new file mode 100644
index 0000000000..56ab77c303
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_sendMailAddressIDN.js
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Tests sending messages to addresses with non-ASCII characters.
+ */
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/alertTestUtils.js");
+
+var test = null;
+var server;
+var finished = false;
+
+var sentFolder;
+
+var kSender = "from@foo.invalid";
+var kToASCII = "to@foo.invalid";
+var kToValid = "to@v\u00E4lid.foo.invalid";
+var kToValidACE = "to@xn--vlid-loa.foo.invalid";
+var kToInvalid = "b\u00F8rken.to@invalid.foo.invalid";
+var kToInvalidWithoutDomain = "b\u00F8rken.to";
+var NS_ERROR_ILLEGAL_LOCALPART = 0x80553139;
+
+// for alertTestUtils.js
+let resolveAlert;
+function alertPS(parent, aDialogText, aText) {
+ var composeProps = Services.strings.createBundle(
+ "chrome://messenger/locale/messengercompose/composeMsgs.properties"
+ );
+ var expectedAlertMessage =
+ composeProps.GetStringFromName("sendFailed") +
+ "\n" +
+ composeProps
+ .GetStringFromName("errorIllegalLocalPart2")
+ // Without the domain, we currently don't display any name in the
+ // message part.
+ .replace("%s", test == kToInvalidWithoutDomain ? "" : test);
+
+ // we should only get here for the kToInvalid test case
+ Assert.equal(aText, expectedAlertMessage);
+ resolveAlert();
+}
+
+// message listener implementations
+function MsgSendListener(aRecipient, originalData) {
+ this.rcpt = aRecipient;
+ this.originalData = originalData;
+}
+
+/**
+ * @implements {nsIMsgSendListener}
+ * @implements {nsIMsgCopyServiceListener}
+ */
+MsgSendListener.prototype = {
+ // nsIMsgSendListener
+ onStartSending(aMsgID, aMsgSize) {},
+ onProgress(aMsgID, aProgress, aProgressMax) {},
+ onStatus(aMsgID, aMsg) {},
+ onStopSending(aMsgID, aStatus, aMsg, aReturnFile) {
+ try {
+ if (test == kToValid || test == kToASCII) {
+ Assert.equal(aStatus, 0);
+ do_check_transaction(server.playTransaction(), [
+ "EHLO test",
+ "MAIL FROM:<" +
+ kSender +
+ "> BODY=8BITMIME SIZE=" +
+ this.originalData.length,
+ "RCPT TO:<" + this.rcpt + ">",
+ "DATA",
+ ]);
+ // Compare data file to what the server received
+ Assert.equal(this.originalData, server._daemon.post);
+ } else {
+ Assert.equal(aStatus, NS_ERROR_ILLEGAL_LOCALPART);
+ do_check_transaction(server.playTransaction(), ["EHLO test"]);
+ // Local address (before the @) has non-ascii char(s) or the @ is
+ // missing from the address. An alert is triggered after the EHLO is
+ // sent. Nothing else occurs so we "finish" the test to avoid
+ // NS_ERROR_ABORT test failure due to timeout waiting for the send
+ // (which doesn't occurs) to complete.
+ }
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(false);
+ }
+ do_test_finished();
+ }
+ },
+ onGetDraftFolderURI(aMsgID, aFolderURI) {},
+ onSendNotPerformed(aMsgID, aStatus) {},
+ onTransportSecurityError(msgID, status, secInfo, location) {},
+
+ // nsIMsgCopyServiceListener
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {},
+ GetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ Assert.equal(aStatus, 0);
+ try {
+ // Now do a comparison of what is in the sent mail folder
+ let msgData = mailTestUtils.loadMessageToString(
+ sentFolder,
+ mailTestUtils.firstMsgHdr(sentFolder)
+ );
+ // Skip the headers etc that mailnews adds
+ var pos = msgData.indexOf("From:");
+ Assert.notEqual(pos, -1);
+ msgData = msgData.substr(pos);
+ Assert.equal(this.originalData, msgData);
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ finished = true;
+ }
+ },
+
+ // QueryInterface
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIMsgSendListener",
+ "nsIMsgCopyServiceListener",
+ ]),
+};
+
+async function doSendTest(aRecipient, aRecipientExpected, waitForPrompt) {
+ info(`Testing send to ${aRecipient} will get sent to ${aRecipientExpected}`);
+ let promiseAlertReceived = new Promise(resolve => {
+ resolveAlert = resolve;
+ });
+ test = aRecipient;
+ server = setupServerDaemon();
+ server.start();
+ var smtpServer = getBasicSmtpServer(server.port);
+ var identity = getSmtpIdentity(kSender, smtpServer);
+ Assert.equal(identity.doFcc, true);
+
+ // Random test file with data we don't actually care about. ;-)
+ var testFile = do_get_file("data/message1.eml");
+ var originalData = await IOUtils.readUTF8(testFile.path);
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ do_test_pending();
+ var compFields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ compFields.from = identity.email;
+ compFields.to = aRecipient;
+
+ var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance(
+ Ci.nsIMsgSend
+ );
+ msgSend.sendMessageFile(
+ identity,
+ "",
+ compFields,
+ testFile,
+ false,
+ false,
+ Ci.nsIMsgSend.nsMsgDeliverNow,
+ null,
+ new MsgSendListener(aRecipientExpected, originalData),
+ null,
+ null
+ );
+
+ server.performTest();
+ do_timeout(10000, function () {
+ if (!finished) {
+ do_throw("Notifications of message send/copy not received");
+ }
+ });
+ if (waitForPrompt) {
+ await promiseAlertReceived;
+ }
+ } catch (e) {
+ Assert.ok(false, "Send fail: " + e);
+ } finally {
+ server.stop();
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+}
+
+add_setup(function () {
+ registerAlertTestUtils();
+
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+ MailServices.accounts.setSpecialFolders();
+ sentFolder = localAccountUtils.rootFolder.createLocalSubfolder("Sent");
+});
+
+add_task(async function plainASCIIRecipient() {
+ // Test 1:
+ // Plain ASCII recipient address.
+ await doSendTest(kToASCII, kToASCII, false);
+});
+
+add_task(async function domainContainsNonAscii() {
+ // Test 2:
+ // The recipient's domain part contains a non-ASCII character, hence the
+ // address needs to be converted to ACE before sending.
+ // The old code would just strip the non-ASCII character and try to send
+ // the message to the remaining - wrong! - address.
+ // The new code will translate the domain part to ACE for the SMTP
+ // transaction (only), i.e. the To: header will stay as stated by the sender.
+ await doSendTest(kToValid, kToValidACE, false);
+});
+
+add_task(async function localContainsNonAscii() {
+ // Test 3:
+ // The recipient's local part contains a non-ASCII character, which is not
+ // allowed with unextended SMTP.
+ // The old code would just strip the invalid character and try to send the
+ // message to the remaining - wrong! - address.
+ // The new code will present an informational message box and deny sending.
+ await doSendTest(kToInvalid, kToInvalid, true);
+});
+
+add_task(async function invalidCharNoAt() {
+ // Test 4:
+ // Bug 856506. invalid char without '@' causes crash.
+ await doSendTest(kToInvalidWithoutDomain, kToInvalidWithoutDomain, true);
+});
diff --git a/comm/mailnews/compose/test/unit/test_sendMailMessage.js b/comm/mailnews/compose/test/unit/test_sendMailMessage.js
new file mode 100644
index 0000000000..ea294a0b92
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_sendMailMessage.js
@@ -0,0 +1,189 @@
+/**
+ * Protocol tests for SMTP.
+ *
+ * This test currently consists of verifying the correct protocol sequence
+ * between mailnews and SMTP server. It does not check the data of the message
+ * either side of the link, it will be extended later to do that.
+ */
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var server;
+
+var kIdentityMail = "identity@foo.invalid";
+var kSender = "from@foo.invalid";
+var kTo = "to@foo.invalid";
+var kUsername = "testsmtp";
+var kPassword = "smtptest";
+
+async function test_RFC2821() {
+ // Test file
+ var testFile = do_get_file("data/message1.eml");
+
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+
+ server.start();
+ var smtpServer = getBasicSmtpServer(server.port);
+ var identity = getSmtpIdentity(kIdentityMail, smtpServer);
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ // Just a basic test to check we're sending mail correctly.
+ test = "Basic sendMailMessage";
+
+ // First do test with identity email address used for smtp MAIL FROM.
+ Services.prefs.setBoolPref("mail.smtp.useSenderForSmtpMailFrom", false);
+
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ kTo,
+ identity,
+ kSender,
+ null,
+ urlListener,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+
+ await urlListener.promise;
+
+ var transaction = server.playTransaction();
+ do_check_transaction(transaction, [
+ "EHLO test",
+ "MAIL FROM:<" + kIdentityMail + "> BODY=8BITMIME SIZE=159",
+ "RCPT TO:<" + kTo + ">",
+ "DATA",
+ ]);
+
+ smtpServer.closeCachedConnections();
+ server.resetTest();
+
+ // Now do the same test with sender's email address used for smtp MAIL FROM.
+ Services.prefs.setBoolPref("mail.smtp.useSenderForSmtpMailFrom", true);
+
+ urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ kTo,
+ identity,
+ kSender,
+ null,
+ urlListener,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+
+ await urlListener.promise;
+
+ transaction = server.playTransaction();
+ do_check_transaction(transaction, [
+ "EHLO test",
+ "MAIL FROM:<" + kSender + "> BODY=8BITMIME SIZE=159",
+ "RCPT TO:<" + kTo + ">",
+ "DATA",
+ ]);
+
+ smtpServer.closeCachedConnections();
+ server.resetTest();
+
+ // This time with auth.
+ test = "Auth sendMailMessage";
+
+ smtpServer.authMethod = Ci.nsMsgAuthMethod.passwordCleartext;
+ smtpServer.socketType = Ci.nsMsgSocketType.plain;
+ smtpServer.username = kUsername;
+ smtpServer.password = kPassword;
+
+ // First do test with identity email address used for smtp MAIL FROM.
+ Services.prefs.setBoolPref("mail.smtp.useSenderForSmtpMailFrom", false);
+
+ urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ kTo,
+ identity,
+ kSender,
+ null,
+ urlListener,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+
+ await urlListener.promise;
+
+ transaction = server.playTransaction();
+ do_check_transaction(transaction, [
+ "EHLO test",
+ "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kPassword),
+ "MAIL FROM:<" + kIdentityMail + "> BODY=8BITMIME SIZE=159",
+ "RCPT TO:<" + kTo + ">",
+ "DATA",
+ ]);
+
+ smtpServer.closeCachedConnections();
+ server.resetTest();
+
+ // Now do the same test with sender's email address used for smtp MAIL FROM.
+ Services.prefs.setBoolPref("mail.smtp.useSenderForSmtpMailFrom", true);
+
+ urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ kTo,
+ identity,
+ kSender,
+ null,
+ urlListener,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+
+ await urlListener.promise;
+
+ transaction = server.playTransaction();
+ do_check_transaction(transaction, [
+ "EHLO test",
+ "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kPassword),
+ "MAIL FROM:<" + kSender + "> BODY=8BITMIME SIZE=159",
+ "RCPT TO:<" + kTo + ">",
+ "DATA",
+ ]);
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+}
+
+add_task(async function run() {
+ server = setupServerDaemon();
+ await test_RFC2821();
+});
diff --git a/comm/mailnews/compose/test/unit/test_sendMessageFile.js b/comm/mailnews/compose/test/unit/test_sendMessageFile.js
new file mode 100644
index 0000000000..cb2882e88f
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_sendMessageFile.js
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Protocol tests for SMTP.
+ *
+ * This test verifies:
+ * - Sending a message to an SMTP server (which is also covered elsewhere).
+ * - Correct reception of the message by the SMTP server.
+ * - Correct saving of the message to the sent folder.
+ *
+ * Originally written to test bug 429891 where saving to the sent folder was
+ * mangling the message.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var server;
+var sentFolder;
+var originalData;
+var finished = false;
+
+var kSender = "from@foo.invalid";
+var kTo = "to@foo.invalid";
+
+function msl() {}
+
+msl.prototype = {
+ // nsIMsgSendListener
+ onStartSending(aMsgID, aMsgSize) {},
+ onProgress(aMsgID, aProgress, aProgressMax) {},
+ onStatus(aMsgID, aMsg) {},
+ onStopSending(aMsgID, aStatus, aMsg, aReturnFile) {
+ try {
+ Assert.equal(aStatus, 0);
+
+ do_check_transaction(server.playTransaction(), [
+ "EHLO test",
+ "MAIL FROM:<" + kSender + "> BODY=8BITMIME SIZE=" + originalData.length,
+ "RCPT TO:<" + kTo + ">",
+ "DATA",
+ ]);
+
+ // Compare data file to what the server received
+ Assert.equal(originalData, server._daemon.post);
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(false);
+ }
+ }
+ },
+ onGetDraftFolderURI(aMsgID, aFolderURI) {},
+ onSendNotPerformed(aMsgID, aStatus) {},
+ onTransportSecurityError(msgID, status, secInfo, location) {},
+
+ // nsIMsgCopyServiceListener
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {},
+ GetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ Assert.equal(aStatus, 0);
+ try {
+ // Now do a comparison of what is in the sent mail folder
+ let msgData = mailTestUtils.loadMessageToString(
+ sentFolder,
+ mailTestUtils.firstMsgHdr(sentFolder)
+ );
+
+ // Skip the headers etc that mailnews adds
+ var pos = msgData.indexOf("From:");
+ Assert.notEqual(pos, -1);
+
+ msgData = msgData.substr(pos);
+
+ Assert.equal(originalData, msgData);
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ finished = true;
+ do_test_finished();
+ }
+ },
+
+ // QueryInterface
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIMsgSendListener",
+ "nsIMsgCopyServiceListener",
+ ]),
+};
+
+add_task(async function run_the_test() {
+ server = setupServerDaemon();
+
+ // Test file - for bug 429891
+ var testFile = do_get_file("data/429891_testcase.eml");
+ originalData = await IOUtils.readUTF8(testFile.path);
+
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+
+ MailServices.accounts.setSpecialFolders();
+
+ server.start();
+ var smtpServer = getBasicSmtpServer(server.port);
+ var identity = getSmtpIdentity(kSender, smtpServer);
+
+ sentFolder = localAccountUtils.rootFolder.createLocalSubfolder("Sent");
+
+ Assert.equal(identity.doFcc, true);
+
+ var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance(
+ Ci.nsIMsgSend
+ );
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ // A test to check that we are sending files correctly, including checking
+ // what the server receives and what we output.
+ test = "sendMessageFile";
+
+ // Msg Comp Fields
+
+ var compFields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+
+ compFields.from = identity.email;
+ compFields.to = kTo;
+
+ var messageListener = new msl();
+
+ msgSend.sendMessageFile(
+ identity,
+ "",
+ compFields,
+ testFile,
+ false,
+ false,
+ Ci.nsIMsgSend.nsMsgDeliverNow,
+ null,
+ messageListener,
+ null,
+ null
+ );
+
+ server.performTest();
+
+ do_timeout(10000, function () {
+ if (!finished) {
+ do_throw("Notifications of message send/copy not received");
+ }
+ });
+
+ do_test_pending();
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+});
diff --git a/comm/mailnews/compose/test/unit/test_sendMessageLater.js b/comm/mailnews/compose/test/unit/test_sendMessageLater.js
new file mode 100644
index 0000000000..7dcaf8ec32
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_sendMessageLater.js
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Protocol tests for SMTP.
+ *
+ * This test verifies:
+ * - Sending a message to an SMTP server (which is also covered elsewhere).
+ * - Correct reception of the message by the SMTP server.
+ * - Correct saving of the message to the sent folder.
+ *
+ * Originally written to test bug 429891 where saving to the sent folder was
+ * mangling the message.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var server;
+var smtpServer;
+var originalData;
+var finished = false;
+var identity = null;
+var testFile = do_get_file("data/429891_testcase.eml");
+var kTestFileSender = "from_A@foo.invalid";
+var kTestFileRecipient = "to_A@foo.invalid";
+
+var kIdentityMail = "identity@foo.invalid";
+
+var msgSendLater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService(
+ Ci.nsIMsgSendLater
+);
+
+// This listener handles the post-sending of the actual message and checks the
+// sequence and ensures the data is correct.
+function msll() {}
+
+msll.prototype = {
+ _initialTotal: 0,
+ _startedSending: false,
+
+ // nsIMsgSendLaterListener
+ onStartSending(aTotalMessageCount) {
+ this._initialTotal = 1;
+ Assert.equal(msgSendLater.sendingMessages, true);
+ },
+ onMessageStartSending(
+ aCurrentMessage,
+ aTotalMessageCount,
+ aMessageHeader,
+ aIdentity
+ ) {
+ this._startedSending = true;
+ },
+ onMessageSendProgress(
+ aCurrentMessage,
+ aTotalMessageCount,
+ aMessageSendPercent,
+ aMessageCopyPercent
+ ) {
+ // XXX Enable this function
+ },
+ onMessageSendError(aCurrentMessage, aMessageHeader, aStatus, aMsg) {
+ do_throw(
+ "onMessageSendError should not have been called, status: " + aStatus
+ );
+ },
+ onStopSending(aStatus, aMsg, aTotalTried, aSuccessful) {
+ do_test_finished();
+ print("msll onStopSending\n");
+ try {
+ Assert.equal(this._startedSending, true);
+ Assert.equal(aStatus, 0);
+ Assert.equal(aTotalTried, 1);
+ Assert.equal(aSuccessful, 1);
+ Assert.equal(this._initialTotal, 1);
+ Assert.equal(msgSendLater.sendingMessages, false);
+
+ do_check_transaction(server.playTransaction(), [
+ "EHLO test",
+ "MAIL FROM:<" +
+ kTestFileSender +
+ "> BODY=8BITMIME SIZE=" +
+ originalData.length,
+ "RCPT TO:<" + kTestFileRecipient + ">",
+ "DATA",
+ ]);
+
+ // Compare data file to what the server received
+ Assert.equal(originalData, server._daemon.post);
+
+ finished = true;
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+ },
+};
+
+/* exported OnStopCopy */
+// for head_compose.js
+function OnStopCopy(aStatus) {
+ dump("OnStopCopy()\n");
+
+ try {
+ Assert.equal(aStatus, 0);
+
+ // Check this is false before we start sending
+ Assert.equal(msgSendLater.sendingMessages, false);
+
+ let folder = msgSendLater.getUnsentMessagesFolder(identity);
+
+ // Check we have a message in the unsent message folder
+ Assert.equal(folder.getTotalMessages(false), 1);
+
+ // Check that the send later service thinks we have messages to send
+ Assert.equal(msgSendLater.hasUnsentMessages(identity), true);
+
+ // Now do a comparison of what is in the sent mail folder
+ let msgData = mailTestUtils.loadMessageToString(
+ folder,
+ mailTestUtils.firstMsgHdr(folder)
+ );
+ // Skip the headers etc that mailnews adds
+ var pos = msgData.indexOf("From:");
+ Assert.notEqual(pos, -1);
+
+ msgData = msgData.substr(pos);
+
+ // Check the data is matching.
+ Assert.equal(originalData, msgData);
+
+ sendMessageLater();
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ finished = true;
+ }
+}
+
+// This function does the actual send later
+function sendMessageLater() {
+ // Set up the SMTP server.
+ server = setupServerDaemon();
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ // Start the fake SMTP server
+ server.start();
+ smtpServer.port = server.port;
+
+ // A test to check that we are sending files correctly, including checking
+ // what the server receives and what we output.
+ test = "sendMessageLater";
+
+ var messageListener = new msll();
+
+ msgSendLater.addListener(messageListener);
+
+ // Send the unsent message
+ msgSendLater.sendUnsentMessages(identity);
+
+ server.performTest();
+
+ do_timeout(10000, function () {
+ if (!finished) {
+ do_throw("Notifications of message send/copy not received");
+ }
+ });
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+}
+
+add_task(async function run_the_test() {
+ // Test file - for bug 429891
+ originalData = await IOUtils.readUTF8(testFile.path);
+
+ // Ensure we have a local mail account, an normal account and appropriate
+ // servers and identities.
+ localAccountUtils.loadLocalMailAccount();
+
+ // Check that the send later service thinks we don't have messages to send
+ Assert.equal(msgSendLater.hasUnsentMessages(identity), false);
+
+ MailServices.accounts.setSpecialFolders();
+
+ let account = MailServices.accounts.createAccount();
+ let incomingServer = MailServices.accounts.createIncomingServer(
+ "test",
+ "localhost",
+ "pop3"
+ );
+
+ smtpServer = getBasicSmtpServer(1);
+ identity = getSmtpIdentity(kIdentityMail, smtpServer);
+
+ account.addIdentity(identity);
+ account.defaultIdentity = identity;
+ account.incomingServer = incomingServer;
+ MailServices.accounts.defaultAccount = account;
+
+ localAccountUtils.rootFolder.createLocalSubfolder("Sent");
+
+ Assert.equal(identity.doFcc, true);
+
+ // Now prepare to actually "send" the message later, i.e. dump it in the
+ // unsent messages folder.
+
+ var compFields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+
+ // Setting the compFields sender and recipient to any value is required to
+ // survive mime_sanity_check_fields in nsMsgCompUtils.cpp.
+ // Sender and recipient are required for sendMessageFile but SMTP
+ // transaction values will be used directly from mail body.
+ compFields.from = "irrelevant@foo.invalid";
+ compFields.to = "irrelevant@foo.invalid";
+
+ var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance(
+ Ci.nsIMsgSend
+ );
+
+ msgSend.sendMessageFile(
+ identity,
+ "",
+ compFields,
+ testFile,
+ false,
+ false,
+ Ci.nsIMsgSend.nsMsgQueueForLater,
+ null,
+ copyListener,
+ null,
+ null
+ );
+
+ // Now we wait till we get copy notification of completion.
+ do_test_pending();
+});
diff --git a/comm/mailnews/compose/test/unit/test_sendMessageLater2.js b/comm/mailnews/compose/test/unit/test_sendMessageLater2.js
new file mode 100644
index 0000000000..bd0b974400
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_sendMessageLater2.js
@@ -0,0 +1,301 @@
+/* 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/. */
+
+/**
+ * Complex test for the send message later function - including sending multiple
+ * times in the same session.
+ *
+ * XXX: This test is intended to additionally test sending of multiple messages
+ * from one send later instance, however due to the fact we use one connection
+ * per message sent, it is very difficult to consistently get the fake server
+ * reconnected in time for the next connection. Thus, sending of multiple
+ * messages is currently disabled (but commented out for local testing if
+ * required), when we fix bug 136871 we should be able to enable the multiple
+ * messages option.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { PromiseUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PromiseUtils.sys.mjs"
+);
+
+var server = null;
+var smtpServer;
+var gSentFolder;
+var identity = null;
+var gMsgFile = [
+ do_get_file("data/message1.eml"),
+ do_get_file("data/429891_testcase.eml"),
+];
+var kTestFileSender = ["from_B@foo.invalid", "from_A@foo.invalid"];
+var kTestFileRecipient = ["to_B@foo.invalid", "to_A@foo.invalid"];
+
+var gMsgFileData = [];
+var gMsgOrder = [];
+var gLastSentMessage = 0;
+
+var kIdentityMail = "identity@foo.invalid";
+
+var msgSendLater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService(
+ Ci.nsIMsgSendLater
+);
+
+var messageListener;
+var onStopCopyPromise = PromiseUtils.defer();
+
+/* exported OnStopCopy */
+// for head_compose.js
+// This function is used to find out when the copying of the message to the
+// unsent message folder is completed, and hence can fire off the actual
+// sending of the message.
+function OnStopCopy(aStatus) {
+ Assert.equal(aStatus, 0);
+
+ // Check this is false before we start sending.
+ Assert.equal(msgSendLater.sendingMessages, false);
+
+ // Check that the send later service thinks we have messages to send.
+ Assert.equal(msgSendLater.hasUnsentMessages(identity), true);
+
+ // Check we have a message in the unsent message folder.
+ Assert.equal(gSentFolder.getTotalMessages(false), gMsgOrder.length);
+
+ // Start the next step after a brief time so that functions can finish
+ // properly.
+ onStopCopyPromise.resolve();
+}
+
+add_setup(async function () {
+ // Load in the test files so we have a record of length and their data.
+ for (var i = 0; i < gMsgFile.length; ++i) {
+ gMsgFileData[i] = await IOUtils.readUTF8(gMsgFile[i].path);
+ }
+
+ // Ensure we have a local mail account, an normal account and appropriate
+ // servers and identities.
+ localAccountUtils.loadLocalMailAccount();
+
+ // Check that the send later service thinks we don't have messages to send.
+ Assert.equal(msgSendLater.hasUnsentMessages(identity), false);
+
+ MailServices.accounts.setSpecialFolders();
+
+ let account = MailServices.accounts.createAccount();
+ let incomingServer = MailServices.accounts.createIncomingServer(
+ "test",
+ "localhost",
+ "pop3"
+ );
+
+ smtpServer = getBasicSmtpServer(1);
+ identity = getSmtpIdentity(kIdentityMail, smtpServer);
+
+ account.addIdentity(identity);
+ account.defaultIdentity = identity;
+ account.incomingServer = incomingServer;
+ MailServices.accounts.defaultAccount = account;
+
+ localAccountUtils.rootFolder.createLocalSubfolder("Sent");
+
+ gSentFolder = msgSendLater.getUnsentMessagesFolder(identity);
+
+ // Don't copy messages to sent folder for this test.
+ identity.doFcc = false;
+
+ // Create and add a listener.
+ messageListener = new MsgSendLaterListener();
+
+ msgSendLater.addListener(messageListener);
+
+ // Set up the server.
+ server = setupServerDaemon();
+ server.setDebugLevel(fsDebugRecv);
+});
+
+add_task(async function test_sendMessageLater2_message1() {
+ // Copy Message from file to folder.
+ await sendMessageLater(0);
+
+ // Send unsent message.
+ await sendUnsentMessages();
+
+ // Check sent folder is now empty.
+ Assert.equal(gSentFolder.getTotalMessages(false), 0);
+
+ // Reset the server.
+ server.stop();
+ server.resetTest();
+
+ // Reset counts.
+ resetCounts();
+});
+
+add_task(async function test_sendMessageLater2_429891_testcase() {
+ // Copy more messages.
+ await sendMessageLater(1);
+
+ // XXX Only do one the second time round, as described at the start of the
+ // file.
+ // await sendMessageLater(0);
+
+ // Test send again.
+ await sendUnsentMessages();
+});
+
+async function sendMessageLater(aTestFileIndex) {
+ gMsgOrder.push(aTestFileIndex);
+
+ // Prepare to actually "send" the message later, i.e. dump it in the
+ // unsent messages folder.
+
+ var compFields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+
+ // Setting the compFields sender and recipient to any value is required to
+ // survive mime_sanity_check_fields in nsMsgCompUtils.cpp.
+ // Sender and recipient are required for sendMessageFile but SMTP
+ // transaction values will be used directly from mail body.
+ compFields.from = "irrelevant@foo.invalid";
+ compFields.to = "irrelevant@foo.invalid";
+
+ var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance(
+ Ci.nsIMsgSend
+ );
+
+ msgSend.sendMessageFile(
+ identity,
+ "",
+ compFields,
+ gMsgFile[aTestFileIndex],
+ false,
+ false,
+ Ci.nsIMsgSend.nsMsgQueueForLater,
+ null,
+ copyListener,
+ null,
+ null
+ );
+ await onStopCopyPromise.promise;
+ // Reset onStopCopyPromise.
+ onStopCopyPromise = PromiseUtils.defer();
+}
+
+function resetCounts() {
+ gMsgOrder = [];
+ gLastSentMessage = 0;
+}
+
+// This function does the actual send later.
+async function sendUnsentMessages() {
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ // Start the fake SMTP server.
+ server.start();
+ smtpServer.port = server.port;
+
+ // Send the unsent message.
+ msgSendLater.sendUnsentMessages(identity);
+ } catch (e) {
+ throw new Error(e);
+ }
+ await messageListener.promise;
+ messageListener.deferPromise();
+}
+
+// This listener handles the post-sending of the actual message and checks the
+// sequence and ensures the data is correct.
+class MsgSendLaterListener {
+ constructor() {
+ this._deferredPromise = PromiseUtils.defer();
+ }
+
+ checkMessageSend(aCurrentMessage) {
+ do_check_transaction(server.playTransaction(), [
+ "EHLO test",
+ "MAIL FROM:<" +
+ kTestFileSender[gMsgOrder[aCurrentMessage - 1]] +
+ "> BODY=8BITMIME SIZE=" +
+ gMsgFileData[gMsgOrder[aCurrentMessage - 1]].length,
+ "RCPT TO:<" + kTestFileRecipient[gMsgOrder[aCurrentMessage - 1]] + ">",
+ "DATA",
+ ]);
+
+ // Compare data file to what the server received.
+ Assert.equal(
+ gMsgFileData[gMsgOrder[aCurrentMessage - 1]],
+ server._daemon.post
+ );
+ }
+
+ // nsIMsgSendLaterListener
+ onStartSending(aTotalMessageCount) {
+ Assert.equal(aTotalMessageCount, gMsgOrder.length);
+ Assert.equal(msgSendLater.sendingMessages, true);
+ }
+ onMessageStartSending(
+ aCurrentMessage,
+ aTotalMessageCount,
+ aMessageHeader,
+ aIdentity
+ ) {
+ if (gLastSentMessage > 0) {
+ this.checkMessageSend(aCurrentMessage);
+ }
+ Assert.equal(gLastSentMessage + 1, aCurrentMessage);
+ gLastSentMessage = aCurrentMessage;
+ }
+ onMessageSendProgress(
+ aCurrentMessage,
+ aTotalMessageCount,
+ aMessageSendPercent,
+ aMessageCopyPercent
+ ) {
+ Assert.equal(aTotalMessageCount, gMsgOrder.length);
+ Assert.equal(gLastSentMessage, aCurrentMessage);
+ Assert.equal(msgSendLater.sendingMessages, true);
+ }
+ onMessageSendError(aCurrentMessage, aMessageHeader, aStatus, aMsg) {
+ throw new Error(
+ "onMessageSendError should not have been called, status: " + aStatus
+ );
+ }
+ onStopSending(aStatus, aMsg, aTotalTried, aSuccessful) {
+ try {
+ Assert.equal(aStatus, 0);
+ Assert.equal(aTotalTried, aSuccessful);
+ Assert.equal(msgSendLater.sendingMessages, false);
+
+ // Check that the send later service now thinks we don't have messages to
+ // send.
+ Assert.equal(msgSendLater.hasUnsentMessages(identity), false);
+
+ this.checkMessageSend(gLastSentMessage);
+ } catch (e) {
+ throw new Error(e);
+ }
+ // The extra timeout here is to work around an issue where sometimes
+ // the sendUnsentMessages is completely synchronous up until onStopSending
+ // and sometimes it isn't. This protects us for the synchronous case to
+ // allow the sendUnsentMessages function to complete and exit before we
+ // resolve the promise.
+ PromiseTestUtils.promiseDelay(0).then(resolve => {
+ this._deferredPromise.resolve(true);
+ });
+ }
+
+ deferPromise() {
+ this._deferredPromise = PromiseUtils.defer();
+ }
+
+ get promise() {
+ return this._deferredPromise.promise;
+ }
+}
diff --git a/comm/mailnews/compose/test/unit/test_sendMessageLater3.js b/comm/mailnews/compose/test/unit/test_sendMessageLater3.js
new file mode 100644
index 0000000000..08e32481c6
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_sendMessageLater3.js
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Protocol tests for SMTP.
+ *
+ * For trying to send a message later with no server connected, this test
+ * verifies:
+ * - A correct status response.
+ * - A correct state at the end of attempting to send.
+ */
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/alertTestUtils.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var originalData;
+var identity = null;
+var testFile = do_get_file("data/429891_testcase.eml");
+
+var kSender = "from@foo.invalid";
+var kTo = "to@foo.invalid";
+
+var msgSendLater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService(
+ Ci.nsIMsgSendLater
+);
+
+// for alertTestUtils.js
+function alertPS(parent, aDialogTitle, aText) {
+ dump("Hiding Alert {\n" + aText + "\n} End Alert\n");
+}
+
+// This listener handles the post-sending of the actual message and checks the
+// sequence and ensures the data is correct.
+function msll() {}
+
+msll.prototype = {
+ _initialTotal: 0,
+ _errorRaised: false,
+
+ // nsIMsgSendLaterListener
+ onStartSending(aTotal) {
+ this._initialTotal = 1;
+ Assert.equal(msgSendLater.sendingMessages, true);
+ },
+ onMessageStartSending(
+ aCurrentMessage,
+ aTotalMessageCount,
+ aMessageHeader,
+ aIdentity
+ ) {},
+ onMessageSendProgress(
+ aCurrentMessage,
+ aTotalMessageCount,
+ aMessageSendPercent,
+ aMessageCopyPercent
+ ) {},
+ onMessageSendError(aCurrentMessage, aMessageHeader, aStatus, aMsg) {
+ this._errorRaised = true;
+ },
+ onStopSending(aStatus, aMsg, aTotal, aSuccessful) {
+ print("msll onStopSending\n");
+
+ // NS_ERROR_SMTP_SEND_FAILED_REFUSED is 2153066798
+ Assert.equal(aStatus, 2153066798);
+ Assert.equal(aTotal, 1);
+ Assert.equal(aSuccessful, 0);
+ Assert.equal(this._initialTotal, 1);
+ Assert.equal(this._errorRaised, true);
+ Assert.equal(msgSendLater.sendingMessages, false);
+ // Check that the send later service still thinks we have messages to send.
+ Assert.equal(msgSendLater.hasUnsentMessages(identity), true);
+
+ do_test_finished();
+ },
+};
+
+/* exported OnStopCopy */
+// for head_compose.js
+function OnStopCopy(aStatus) {
+ Assert.equal(aStatus, 0);
+
+ // Check this is false before we start sending
+ Assert.equal(msgSendLater.sendingMessages, false);
+
+ let folder = msgSendLater.getUnsentMessagesFolder(identity);
+
+ // Check that the send later service thinks we have messages to send.
+ Assert.equal(msgSendLater.hasUnsentMessages(identity), true);
+
+ // Check we have a message in the unsent message folder
+ Assert.equal(folder.getTotalMessages(false), 1);
+
+ // Now do a comparison of what is in the unsent mail folder
+ let msgData = mailTestUtils.loadMessageToString(
+ folder,
+ mailTestUtils.firstMsgHdr(folder)
+ );
+
+ // Skip the headers etc that mailnews adds
+ var pos = msgData.indexOf("From:");
+ Assert.notEqual(pos, -1);
+
+ msgData = msgData.substr(pos);
+
+ // Check the data is matching.
+ Assert.equal(originalData, msgData);
+
+ do_timeout(0, sendMessageLater);
+}
+
+// This function does the actual send later
+function sendMessageLater() {
+ // No server for this test, just attempt to send unsent and wait.
+ var messageListener = new msll();
+
+ msgSendLater.addListener(messageListener);
+
+ // Send the unsent message
+ msgSendLater.sendUnsentMessages(identity);
+}
+
+add_task(async function run_the_test() {
+ registerAlertTestUtils();
+
+ // Test file - for bug 429891
+ originalData = await IOUtils.readUTF8(testFile.path);
+
+ // Ensure we have a local mail account, an normal account and appropriate
+ // servers and identities.
+ localAccountUtils.loadLocalMailAccount();
+
+ // Check that the send later service thinks we don't have messages to send.
+ Assert.equal(msgSendLater.hasUnsentMessages(identity), false);
+
+ MailServices.accounts.setSpecialFolders();
+
+ let account = MailServices.accounts.createAccount();
+ let incomingServer = MailServices.accounts.createIncomingServer(
+ "test",
+ "localhost",
+ "pop3"
+ );
+
+ var smtpServer = getBasicSmtpServer();
+ identity = getSmtpIdentity(kSender, smtpServer);
+
+ account.addIdentity(identity);
+ account.defaultIdentity = identity;
+ account.incomingServer = incomingServer;
+ MailServices.accounts.defaultAccount = account;
+
+ localAccountUtils.rootFolder.createLocalSubfolder("Sent");
+
+ identity.doFcc = false;
+
+ // Now prepare to actually "send" the message later, i.e. dump it in the
+ // unsent messages folder.
+
+ var compFields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+
+ compFields.from = identity.email;
+ compFields.to = kTo;
+
+ var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance(
+ Ci.nsIMsgSend
+ );
+
+ msgSend.sendMessageFile(
+ identity,
+ "",
+ compFields,
+ testFile,
+ false,
+ false,
+ Ci.nsIMsgSend.nsMsgQueueForLater,
+ null,
+ copyListener,
+ null,
+ null
+ );
+
+ // Now we wait till we get copy notification of completion.
+ do_test_pending();
+});
diff --git a/comm/mailnews/compose/test/unit/test_sendObserver.js b/comm/mailnews/compose/test/unit/test_sendObserver.js
new file mode 100644
index 0000000000..3640d1ca02
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_sendObserver.js
@@ -0,0 +1,52 @@
+/*
+ * Tests that the mail-set-sender observer, used by extensions to modify the
+ * outgoing server, works.
+ *
+ * This is adapted from test_messageHeaders.js
+ */
+
+var CompFields = CC(
+ "@mozilla.org/messengercompose/composefields;1",
+ Ci.nsIMsgCompFields
+);
+
+// nsIObserver implementation.
+var gData = "";
+var observer = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "mail-set-sender") {
+ Assert.ok(aSubject instanceof Ci.nsIMsgCompose);
+ gData = aData;
+ }
+ },
+};
+
+add_task(async function testObserver() {
+ let fields = new CompFields();
+ let identity = getSmtpIdentity(
+ "from@tinderbox.invalid",
+ getBasicSmtpServer()
+ );
+ identity.fullName = "Observer Tester";
+ fields.to = "Emile <nobody@tinderbox.invalid>";
+ fields.cc = "Alex <alex@tinderbox.invalid>";
+ fields.subject = "Let's test the observer";
+
+ await richCreateMessage(fields, [], identity);
+ // observer data should have:
+ // (no account), Ci.nsIMsgSend.nsMsgSaveAsDraft, identity.key
+ Assert.equal(gData, ",4,id1");
+
+ // Now try with an account
+ await richCreateMessage(fields, [], identity, localAccountUtils.msgAccount);
+ // observer data should have:
+ // (local account key), Ci.nsIMsgSend.nsMsgSaveAsDraft, identity.key
+ Assert.equal(gData, "account1,4,id1");
+});
+
+function run_test() {
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+ Services.obs.addObserver(observer, "mail-set-sender");
+ run_next_test();
+}
diff --git a/comm/mailnews/compose/test/unit/test_smtp8bitMime.js b/comm/mailnews/compose/test/unit/test_smtp8bitMime.js
new file mode 100644
index 0000000000..d763947154
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_smtp8bitMime.js
@@ -0,0 +1,105 @@
+/**
+ * 8BITMIME tests for SMTP.
+ *
+ * This test verifies that 8BITMIME is sent to the server only if the server
+ * advertises it AND if mail.strictly_mime doesn't force us to send 7bit.
+ * It does not check the data of the message on either side of the link.
+ */
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var server;
+
+var kIdentityMail = "identity@foo.invalid";
+var kSender = "from@foo.invalid";
+var kTo = "to@foo.invalid";
+
+// aStrictMime: Test if mail.strictly_mime omits the BODY=8BITMIME attribute.
+// aServer8bit: Test if BODY=8BITMIME is only sent if advertised by the server.
+
+async function test_8bitmime(aStrictMime, aServer8bit) {
+ // Test file
+ var testFile = do_get_file("data/message1.eml");
+
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+
+ server.start();
+ var smtpServer = getBasicSmtpServer(server.port);
+ var identity = getSmtpIdentity(kIdentityMail, smtpServer);
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ test =
+ "Strictly MIME" +
+ (aStrictMime ? "on (7bit" : "off (8bit") +
+ ", 8BITMIME " +
+ (aServer8bit ? "" : "not ") +
+ "advertised)";
+
+ Services.prefs.setBoolPref("mail.strictly_mime", aStrictMime);
+
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ kTo,
+ identity,
+ kSender,
+ null,
+ urlListener,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+
+ await urlListener.promise;
+
+ var transaction = server.playTransaction();
+ do_check_transaction(transaction, [
+ "EHLO test",
+ "MAIL FROM:<" +
+ kSender +
+ (!aStrictMime && aServer8bit
+ ? "> BODY=8BITMIME SIZE=159"
+ : "> SIZE=159"),
+ "RCPT TO:<" + kTo + ">",
+ "DATA",
+ ]);
+
+ server.resetTest();
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+}
+
+add_task(async function run() {
+ // The default SMTP server advertises 8BITMIME capability.
+ server = setupServerDaemon();
+ await test_8bitmime(true, true);
+ await test_8bitmime(false, true);
+
+ // Now we need a server which does not advertise 8BITMIME capability.
+ function createHandler(d) {
+ var handler = new SMTP_RFC2821_handler(d);
+ handler.kCapabilities = ["SIZE"];
+ return handler;
+ }
+ server = setupServerDaemon(createHandler);
+ await test_8bitmime(true, false);
+ await test_8bitmime(false, false);
+});
diff --git a/comm/mailnews/compose/test/unit/test_smtpAuthMethods.js b/comm/mailnews/compose/test/unit/test_smtpAuthMethods.js
new file mode 100644
index 0000000000..24d5c6d554
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_smtpAuthMethods.js
@@ -0,0 +1,166 @@
+/**
+ * Authentication tests for SMTP.
+ *
+ * Test code <copied from="test_pop3AuthMethods.js">
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var server;
+var kAuthSchemes;
+var smtpServer;
+var testFile;
+var identity;
+
+var kUsername = "fred";
+var kPassword = "wilma";
+var kIdentityMail = "identity@foo.invalid";
+var kSender = "from@foo.invalid";
+var kTo = "to@foo.invalid";
+var MAILFROM = "MAIL FROM:<" + kSender + "> BODY=8BITMIME SIZE=159";
+var RCPTTO = "RCPT TO:<" + kTo + ">";
+var AUTHPLAIN = "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kPassword);
+
+var tests = [
+ {
+ title:
+ "Cleartext password, with server supporting AUTH PLAIN, LOGIN, and CRAM",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"],
+ expectSuccess: true,
+ transaction: ["EHLO test", AUTHPLAIN, MAILFROM, RCPTTO, "DATA"],
+ },
+ {
+ title: "Cleartext password, with server only supporting AUTH LOGIN",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: ["LOGIN"],
+ expectSuccess: true,
+ transaction: ["EHLO test", "AUTH LOGIN", MAILFROM, RCPTTO, "DATA"],
+ },
+ {
+ title:
+ "Encrypted password, with server supporting AUTH PLAIN, LOGIN and CRAM",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordEncrypted,
+ serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"],
+ expectSuccess: true,
+ transaction: ["EHLO test", "AUTH CRAM-MD5", MAILFROM, RCPTTO, "DATA"],
+ },
+ {
+ title:
+ "Encrypted password, with server only supporting AUTH PLAIN (must fail)",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordEncrypted,
+ serverAuthMethods: ["PLAIN"],
+ expectSuccess: false,
+ transaction: ["EHLO test"],
+ },
+ {
+ title:
+ "Any secure method, with server supporting AUTH PLAIN, LOGIN and CRAM",
+ clientAuthMethod: Ci.nsMsgAuthMethod.secure,
+ serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"],
+ expectSuccess: true,
+ transaction: ["EHLO test", "AUTH CRAM-MD5", MAILFROM, RCPTTO, "DATA"],
+ },
+ {
+ title:
+ "Any secure method, with server only supporting AUTH PLAIN (must fail)",
+ clientAuthMethod: Ci.nsMsgAuthMethod.secure,
+ serverAuthMethods: ["PLAIN"],
+ expectSuccess: false,
+ transaction: ["EHLO test"],
+ },
+];
+
+function nextTest() {
+ if (tests.length == 0) {
+ // this is sync, so we run into endTest() at the end of run_test() now
+ return;
+ }
+ server.resetTest();
+
+ var curTest = tests.shift();
+ test = curTest.title;
+ dump("NEXT test: " + curTest.title + "\n");
+
+ // Adapt to curTest
+ kAuthSchemes = curTest.serverAuthMethods;
+ smtpServer.authMethod = curTest.clientAuthMethod;
+
+ // Run test
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ kTo,
+ identity,
+ kSender,
+ null,
+ urlListener,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+ let resolved = false;
+ urlListener.promise.catch(e => {}).finally(() => (resolved = true));
+ Services.tm.spinEventLoopUntil("wait for sending", () => resolved);
+
+ do_check_transaction(server.playTransaction(), curTest.transaction);
+
+ smtpServer.closeCachedConnections();
+ nextTest();
+}
+
+function run_test() {
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ function createHandler(d) {
+ var handler = new SMTP_RFC2821_handler(d);
+ handler.kUsername = kUsername;
+ handler.kPassword = kPassword;
+ handler.kAuthRequired = true;
+ handler.kAuthSchemes = kAuthSchemes;
+ return handler;
+ }
+ server = setupServerDaemon(createHandler);
+ dump("AUTH PLAIN = " + AUTHPLAIN + "\n");
+ server.start();
+
+ localAccountUtils.loadLocalMailAccount();
+ smtpServer = getBasicSmtpServer(server.port);
+ smtpServer.socketType = Ci.nsMsgSocketType.plain;
+ smtpServer.username = kUsername;
+ smtpServer.password = kPassword;
+ identity = getSmtpIdentity(kIdentityMail, smtpServer);
+
+ testFile = do_get_file("data/message1.eml");
+
+ nextTest();
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ endTest();
+ }
+}
+
+function endTest() {
+ dump("endTest()\n");
+ server.stop();
+
+ dump("emptying event loop\n");
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ dump("next event\n");
+ thread.processNextEvent(true);
+ }
+}
diff --git a/comm/mailnews/compose/test/unit/test_smtpClient.js b/comm/mailnews/compose/test/unit/test_smtpClient.js
new file mode 100644
index 0000000000..b06ec48560
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_smtpClient.js
@@ -0,0 +1,136 @@
+/* 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 { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+let server = setupServerDaemon();
+server.start();
+registerCleanupFunction(() => {
+ server.stop();
+});
+
+/**
+ * Test sending is aborted when alwaysSTARTTLS is set, but the server doesn't
+ * support STARTTLS.
+ */
+add_task(async function testAbort() {
+ server.resetTest();
+ let smtpServer = getBasicSmtpServer(server.port);
+ let identity = getSmtpIdentity("identity@foo.invalid", smtpServer);
+ // Set to always use STARTTLS.
+ smtpServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
+
+ do_test_pending();
+
+ let urlListener = {
+ OnStartRunningUrl(url) {},
+ OnStopRunningUrl(url, status) {
+ // Test sending is aborted with NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS.
+ Assert.equal(status, 0x80553126);
+ do_test_finished();
+ },
+ };
+
+ // Send a message.
+ let testFile = do_get_file("data/message1.eml");
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ "to@foo.invalid",
+ identity,
+ "from@foo.invalid",
+ null,
+ urlListener,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+ server.performTest();
+});
+
+/**
+ * Test client identity extension works.
+ */
+add_task(async function testClientIdentityExtension() {
+ server.resetTest();
+ let smtpServer = getBasicSmtpServer(server.port);
+ let identity = getSmtpIdentity("identity@foo.invalid", smtpServer);
+ // Enable and set clientid to the smtp server.
+ smtpServer.clientidEnabled = true;
+ smtpServer.clientid = "uuid-111";
+
+ // Send a message.
+ let asyncUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ let testFile = do_get_file("data/message1.eml");
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ "to@foo.invalid",
+ identity,
+ "from@foo.invalid",
+ null,
+ asyncUrlListener,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+
+ await asyncUrlListener.promise;
+
+ // Check CLIENTID command is sent.
+ let transaction = server.playTransaction();
+ do_check_transaction(transaction, [
+ "EHLO test",
+ "CLIENTID UUID uuid-111",
+ "MAIL FROM:<from@foo.invalid> BODY=8BITMIME SIZE=159",
+ "RCPT TO:<to@foo.invalid>",
+ "DATA",
+ ]);
+});
+
+/**
+ * Test that when To and Cc/Bcc contain the same address, should send only
+ * one RCPT TO per address.
+ */
+add_task(async function testDeduplicateRecipients() {
+ server.resetTest();
+ let smtpServer = getBasicSmtpServer(server.port);
+ let identity = getSmtpIdentity("identity@foo.invalid", smtpServer);
+
+ // Send a message, notice to1 appears twice in the recipients argument.
+ let asyncUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ let testFile = do_get_file("data/message1.eml");
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ "to1@foo.invalid,to2@foo.invalid,to1@foo.invalid",
+ identity,
+ "from@foo.invalid",
+ null,
+ asyncUrlListener,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+
+ await asyncUrlListener.promise;
+
+ // Check only one RCPT TO is sent for to1.
+ let transaction = server.playTransaction();
+ do_check_transaction(transaction, [
+ "EHLO test",
+ "MAIL FROM:<from@foo.invalid> BODY=8BITMIME SIZE=159",
+ "RCPT TO:<to1@foo.invalid>",
+ "RCPT TO:<to2@foo.invalid>",
+ "DATA",
+ ]);
+});
diff --git a/comm/mailnews/compose/test/unit/test_smtpPassword.js b/comm/mailnews/compose/test/unit/test_smtpPassword.js
new file mode 100644
index 0000000000..f4b8515df7
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_smtpPassword.js
@@ -0,0 +1,97 @@
+/**
+ * Authentication tests for SMTP.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+/* import-globals-from ../../../test/resources/passwordStorage.js */
+load("../../../resources/passwordStorage.js");
+
+var server;
+
+var kIdentityMail = "identity@foo.invalid";
+var kSender = "from@foo.invalid";
+var kTo = "to@foo.invalid";
+var kUsername = "testsmtp";
+// Password needs to match the login information stored in the signons json
+// file.
+var kPassword = "smtptest";
+
+add_task(async function () {
+ function createHandler(d) {
+ var handler = new SMTP_RFC2821_handler(d);
+ // Username needs to match the login information stored in the signons json
+ // file.
+ handler.kUsername = kUsername;
+ handler.kPassword = kPassword;
+ handler.kAuthRequired = true;
+ return handler;
+ }
+ server = setupServerDaemon(createHandler);
+
+ // Prepare files for passwords (generated by a script in bug 1018624).
+ await setupForPassword("signons-mailnews1.8.json");
+
+ // Test file
+ var testFile = do_get_file("data/message1.eml");
+
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ // Start the fake SMTP server
+ server.start();
+ var smtpServer = getBasicSmtpServer(server.port);
+ var identity = getSmtpIdentity(kIdentityMail, smtpServer);
+
+ // This time with auth
+ test = "Auth sendMailMessage";
+
+ smtpServer.authMethod = Ci.nsMsgAuthMethod.passwordCleartext;
+ smtpServer.socketType = Ci.nsMsgSocketType.plain;
+ smtpServer.username = kUsername;
+
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ kTo,
+ identity,
+ kSender,
+ null,
+ urlListener,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+
+ await urlListener.promise;
+
+ var transaction = server.playTransaction();
+ do_check_transaction(transaction, [
+ "EHLO test",
+ "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kPassword),
+ "MAIL FROM:<" + kSender + "> BODY=8BITMIME SIZE=159",
+ "RCPT TO:<" + kTo + ">",
+ "DATA",
+ ]);
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+});
diff --git a/comm/mailnews/compose/test/unit/test_smtpPassword2.js b/comm/mailnews/compose/test/unit/test_smtpPassword2.js
new file mode 100644
index 0000000000..a0445ad0a3
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_smtpPassword2.js
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Extra tests for SMTP passwords (forgetPassword)
+ */
+
+/* import-globals-from ../../../test/resources/passwordStorage.js */
+load("../../../resources/passwordStorage.js");
+
+var kUser1 = "testsmtp";
+var kUser2 = "testsmtpa";
+var kProtocol = "smtp";
+var kHostname = "localhost";
+var kServerUrl = kProtocol + "://" + kHostname;
+
+add_task(async function () {
+ // Prepare files for passwords (generated by a script in bug 1018624).
+ await setupForPassword("signons-mailnews1.8-multiple.json");
+
+ // Set up the basic accounts and folders.
+ localAccountUtils.loadLocalMailAccount();
+
+ var smtpServer1 = getBasicSmtpServer();
+ var smtpServer2 = getBasicSmtpServer();
+
+ smtpServer1.authMethod = 3;
+ smtpServer1.username = kUser1;
+ smtpServer2.authMethod = 3;
+ smtpServer2.username = kUser2;
+
+ // Test - Check there are two logins to begin with.
+ let logins = Services.logins.findLogins(kServerUrl, null, kServerUrl);
+
+ Assert.equal(logins.length, 2);
+
+ // These will either be one way around or the other.
+ if (logins[0].username == kUser1) {
+ Assert.equal(logins[1].username, kUser2);
+ } else {
+ Assert.equal(logins[0].username, kUser2);
+ Assert.equal(logins[1].username, kUser1);
+ }
+
+ // Test - Remove a login via the incoming server
+ smtpServer1.forgetPassword();
+
+ logins = Services.logins.findLogins(kServerUrl, null, kServerUrl);
+
+ // should be one login left for kUser2
+ Assert.equal(logins.length, 1);
+ Assert.equal(logins[0].username, kUser2);
+
+ // Test - Remove the other login via the incoming server
+ smtpServer2.forgetPassword();
+
+ logins = Services.logins.findLogins(kServerUrl, null, kServerUrl);
+
+ // There should be no login left.
+ Assert.equal(logins.length, 0);
+});
diff --git a/comm/mailnews/compose/test/unit/test_smtpPasswordFailure1.js b/comm/mailnews/compose/test/unit/test_smtpPasswordFailure1.js
new file mode 100644
index 0000000000..b7d7f1ef43
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_smtpPasswordFailure1.js
@@ -0,0 +1,151 @@
+/**
+ * This test checks to see if the smtp password failure is handled correctly.
+ * The steps are:
+ * - Have an invalid password in the password database.
+ * - Check we get a prompt asking what to do.
+ * - Check retry does what it should do.
+ * - Check cancel does what it should do.
+ *
+ * XXX Due to problems with the fakeserver + smtp not using one connection for
+ * multiple sends, the rest of this test is in test_smtpPasswordFailure2.js.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+/* import-globals-from ../../../test/resources/passwordStorage.js */
+load("../../../resources/alertTestUtils.js");
+load("../../../resources/passwordStorage.js");
+
+var server;
+var attempt = 0;
+
+var kIdentityMail = "identity@foo.invalid";
+var kSender = "from@foo.invalid";
+var kTo = "to@foo.invalid";
+var kUsername = "testsmtp";
+// Login information needs to match the login information stored in the signons
+// json file.
+var kInvalidPassword = "smtptest";
+var kValidPassword = "smtptest1";
+
+/* exported alert, confirmEx */
+// for alertTestUtils.js
+function alert(aDialogText, aText) {
+ // The first few attempts may prompt about the password problem, the last
+ // attempt shouldn't.
+ Assert.ok(attempt < 4);
+
+ // Log the fact we've got an alert, but we don't need to test anything here.
+ dump("Alert Title: " + aDialogText + "\nAlert Text: " + aText + "\n");
+}
+
+function confirmExPS(
+ parent,
+ aDialogTitle,
+ aText,
+ aButtonFlags,
+ aButton0Title,
+ aButton1Title,
+ aButton2Title,
+ aCheckMsg,
+ aCheckState
+) {
+ switch (++attempt) {
+ // First attempt, retry.
+ case 1:
+ dump("\nAttempting retry\n");
+ return 0;
+ // Second attempt, cancel.
+ case 2:
+ dump("\nCancelling login attempt\n");
+ return 1;
+ default:
+ do_throw("unexpected attempt number " + attempt);
+ return 1;
+ }
+}
+
+add_task(async function () {
+ function createHandler(d) {
+ var handler = new SMTP_RFC2821_handler(d);
+ // Username needs to match the login information stored in the signons json
+ // file.
+ handler.kUsername = kUsername;
+ handler.kPassword = kValidPassword;
+ handler.kAuthRequired = true;
+ return handler;
+ }
+ server = setupServerDaemon(createHandler);
+
+ // Prepare files for passwords (generated by a script in bug 1018624).
+ await setupForPassword("signons-mailnews1.8.json");
+
+ registerAlertTestUtils();
+
+ // Test file
+ var testFile = do_get_file("data/message1.eml");
+
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+
+ server.start();
+ var smtpServer = getBasicSmtpServer(server.port);
+ var identity = getSmtpIdentity(kIdentityMail, smtpServer);
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ // This time with auth
+ test = "Auth sendMailMessage";
+
+ smtpServer.authMethod = Ci.nsMsgAuthMethod.passwordCleartext;
+ smtpServer.socketType = Ci.nsMsgSocketType.plain;
+ smtpServer.username = kUsername;
+
+ dump("Send\n");
+
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ kTo,
+ identity,
+ kSender,
+ null,
+ null,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+
+ server.performTest();
+
+ dump("End Send\n");
+
+ Assert.equal(attempt, 2);
+
+ // Check that we haven't forgetton the login even though we've retried and cancelled.
+ let logins = Services.logins.findLogins(
+ "smtp://localhost",
+ null,
+ "smtp://localhost"
+ );
+
+ Assert.equal(logins.length, 1);
+ Assert.equal(logins[0].username, kUsername);
+ Assert.equal(logins[0].password, kInvalidPassword);
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+});
diff --git a/comm/mailnews/compose/test/unit/test_smtpPasswordFailure2.js b/comm/mailnews/compose/test/unit/test_smtpPasswordFailure2.js
new file mode 100644
index 0000000000..f394db434d
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_smtpPasswordFailure2.js
@@ -0,0 +1,178 @@
+/**
+ * This test checks to see if the pop3 password failure is handled correctly.
+ * The steps are:
+ * - Have an invalid password in the password database.
+ * - Re-initiate connection, this time select enter new password, check that
+ * we get a new password prompt and can enter the password.
+ *
+ * XXX Due to problems with the fakeserver + smtp not using one connection for
+ * multiple sends, the first part of this test is in
+ * test_smtpPasswordFailure2.js.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+/* import-globals-from ../../../test/resources/passwordStorage.js */
+load("../../../resources/alertTestUtils.js");
+load("../../../resources/passwordStorage.js");
+
+var server;
+var attempt = 0;
+
+var kIdentityMail = "identity@foo.invalid";
+var kSender = "from@foo.invalid";
+var kTo = "to@foo.invalid";
+var kUsername = "testsmtp";
+// Password needs to match the login information stored in the signons json
+// file.
+var kInvalidPassword = "smtptest";
+var kValidPassword = "smtptest1";
+
+function confirmExPS(
+ aDialogTitle,
+ aText,
+ aButtonFlags,
+ aButton0Title,
+ aButton1Title,
+ aButton2Title,
+ aCheckMsg,
+ aCheckState
+) {
+ switch (++attempt) {
+ // First attempt, retry.
+ case 1:
+ dump("\nAttempting Retry\n");
+ return 0;
+ // Second attempt, enter a new password.
+ case 2:
+ dump("\nEnter new password\n");
+ return 2;
+ default:
+ do_throw("unexpected attempt number " + attempt);
+ return 1;
+ }
+}
+
+function promptPasswordPS(
+ aParent,
+ aDialogTitle,
+ aText,
+ aPassword,
+ aCheckMsg,
+ aCheckState
+) {
+ if (attempt == 2) {
+ aPassword.value = kValidPassword;
+ aCheckState.value = true;
+ return true;
+ }
+ return false;
+}
+
+add_task(async function () {
+ function createHandler(d) {
+ var handler = new SMTP_RFC2821_handler(d);
+ // Username needs to match the login information stored in the signons json
+ // file.
+ handler.kUsername = kUsername;
+ handler.kPassword = kValidPassword;
+ handler.kAuthRequired = true;
+ handler.kAuthSchemes = ["PLAIN", "LOGIN"]; // make match expected transaction below
+ return handler;
+ }
+ server = setupServerDaemon(createHandler);
+
+ // Prepare files for passwords (generated by a script in bug 1018624).
+ await setupForPassword("signons-mailnews1.8.json");
+
+ registerAlertTestUtils();
+
+ // Test file
+ var testFile = do_get_file("data/message1.eml");
+
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ // Start the fake SMTP server
+ server.start();
+ var smtpServer = getBasicSmtpServer(server.port);
+ var identity = getSmtpIdentity(kIdentityMail, smtpServer);
+
+ // This time with auth
+ test = "Auth sendMailMessage";
+
+ smtpServer.authMethod = Ci.nsMsgAuthMethod.passwordCleartext;
+ smtpServer.socketType = Ci.nsMsgSocketType.plain;
+ smtpServer.username = kUsername;
+
+ dump("Send\n");
+
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ kTo,
+ identity,
+ kSender,
+ null,
+ urlListener,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+
+ await urlListener.promise;
+
+ dump("End Send\n");
+
+ Assert.equal(attempt, 2);
+
+ var transaction = server.playTransaction();
+ do_check_transaction(transaction, [
+ "EHLO test",
+ // attempt 3 invalid password
+ "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kInvalidPassword),
+ "AUTH LOGIN",
+ // attempt 4 which retries
+ "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kInvalidPassword),
+ "AUTH LOGIN",
+ // then we enter the correct password
+ "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kValidPassword),
+ "MAIL FROM:<" + kSender + "> BODY=8BITMIME SIZE=159",
+ "RCPT TO:<" + kTo + ">",
+ "DATA",
+ ]);
+
+ // Now check the new one has been saved.
+ let logins = Services.logins.findLogins(
+ "smtp://localhost",
+ null,
+ "smtp://localhost"
+ );
+
+ Assert.equal(logins.length, 1);
+ Assert.equal(logins[0].username, kUsername);
+ Assert.equal(logins[0].password, kValidPassword);
+ do_test_finished();
+ } catch (e) {
+ do_throw(e);
+ } finally {
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+});
diff --git a/comm/mailnews/compose/test/unit/test_smtpPasswordFailure3.js b/comm/mailnews/compose/test/unit/test_smtpPasswordFailure3.js
new file mode 100644
index 0000000000..27312b47a4
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_smtpPasswordFailure3.js
@@ -0,0 +1,154 @@
+/**
+ * This test checks to see if the smtp password failure is handled correctly
+ * when the server drops the connection on an authentication error.
+ * The steps are:
+ * - Have an invalid password in the password database.
+ * - Re-initiate connection, this time select enter new password, check that
+ * we get a new password prompt and can enter the password.
+ *
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+/* import-globals-from ../../../test/resources/passwordStorage.js */
+load("../../../resources/alertTestUtils.js");
+load("../../../resources/passwordStorage.js");
+
+var server;
+var attempt = 0;
+
+var kIdentityMail = "identity@foo.invalid";
+var kSender = "from@foo.invalid";
+var kTo = "to@foo.invalid";
+var kUsername = "testsmtp";
+// Password needs to match the login information stored in the signons json
+// file.
+var kValidPassword = "smtptest1";
+
+function confirmExPS(
+ aDialogTitle,
+ aText,
+ aButtonFlags,
+ aButton0Title,
+ aButton1Title,
+ aButton2Title,
+ aCheckMsg,
+ aCheckState
+) {
+ switch (++attempt) {
+ // First attempt, retry.
+ case 1:
+ dump("\nAttempting Retry\n");
+ return 0;
+ // Second attempt, enter a new password.
+ case 2:
+ dump("\nEnter new password\n");
+ return 2;
+ default:
+ do_throw("unexpected attempt number " + attempt);
+ return 1;
+ }
+}
+
+function promptPasswordPS(
+ aParent,
+ aDialogTitle,
+ aText,
+ aPassword,
+ aCheckMsg,
+ aCheckState
+) {
+ if (attempt == 2) {
+ aPassword.value = kValidPassword;
+ aCheckState.value = true;
+ return true;
+ }
+ return false;
+}
+
+add_task(async function () {
+ function createHandler(d) {
+ var handler = new SMTP_RFC2821_handler(d);
+ handler.dropOnAuthFailure = true;
+ // Username needs to match the login information stored in the signons json
+ // file.
+ handler.kUsername = kUsername;
+ handler.kPassword = kValidPassword;
+ handler.kAuthRequired = true;
+ handler.kAuthSchemes = ["PLAIN", "LOGIN"]; // make match expected transaction below
+ return handler;
+ }
+ server = setupServerDaemon(createHandler);
+
+ // Prepare files for passwords (generated by a script in bug 1018624).
+ await setupForPassword("signons-mailnews1.8.json");
+
+ registerAlertTestUtils();
+
+ // Test file
+ var testFile = do_get_file("data/message1.eml");
+
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+
+ // Start the fake SMTP server
+ server.start();
+ var smtpServer = getBasicSmtpServer(server.port);
+ var identity = getSmtpIdentity(kIdentityMail, smtpServer);
+
+ // This time with auth
+ test = "Auth sendMailMessage";
+
+ smtpServer.authMethod = Ci.nsMsgAuthMethod.passwordCleartext;
+ smtpServer.socketType = Ci.nsMsgSocketType.plain;
+ smtpServer.username = kUsername;
+
+ do_test_pending();
+
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ kTo,
+ identity,
+ kSender,
+ null,
+ URLListener,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+
+ server.performTest();
+});
+
+var URLListener = {
+ OnStartRunningUrl(url) {},
+ OnStopRunningUrl(url, rc) {
+ // Check for ok status.
+ Assert.equal(rc, 0);
+ // Now check the new password has been saved.
+ let logins = Services.logins.findLogins(
+ "smtp://localhost",
+ null,
+ "smtp://localhost"
+ );
+
+ Assert.equal(logins.length, 1);
+ Assert.equal(logins[0].username, kUsername);
+ Assert.equal(logins[0].password, kValidPassword);
+
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_test_finished();
+ },
+};
diff --git a/comm/mailnews/compose/test/unit/test_smtpProtocols.js b/comm/mailnews/compose/test/unit/test_smtpProtocols.js
new file mode 100644
index 0000000000..bba7d55b6b
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_smtpProtocols.js
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for getting smtp urls via the protocol handler.
+ */
+
+var defaultProtocolFlags =
+ Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
+ Ci.nsIProtocolHandler.URI_NON_PERSISTABLE |
+ Ci.nsIProtocolHandler.ALLOWS_PROXY |
+ Ci.nsIProtocolHandler.URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT;
+
+var protocols = [
+ {
+ protocol: "smtp",
+ urlSpec: "smtp://user@localhost/",
+ defaultPort: Ci.nsISmtpUrl.DEFAULT_SMTP_PORT,
+ },
+ {
+ protocol: "smtps",
+ urlSpec: "smtps://user@localhost/",
+ defaultPort: Ci.nsISmtpUrl.DEFAULT_SMTPS_PORT,
+ },
+];
+
+function run_test() {
+ for (var part = 0; part < protocols.length; ++part) {
+ print("protocol: " + protocols[part].protocol);
+
+ var pH = Cc[
+ "@mozilla.org/network/protocol;1?name=" + protocols[part].protocol
+ ].createInstance(Ci.nsIProtocolHandler);
+
+ Assert.equal(pH.scheme, protocols[part].protocol);
+ Assert.equal(
+ Services.io.getDefaultPort(pH.scheme),
+ protocols[part].defaultPort
+ );
+ Assert.equal(Services.io.getProtocolFlags(pH.scheme), defaultProtocolFlags);
+
+ // Whip through some of the ports to check we get the right results.
+ for (let i = 0; i < 1024; ++i) {
+ Assert.equal(pH.allowPort(i, ""), i == protocols[part].defaultPort);
+ }
+
+ // Check we get a URI when we ask for one
+ var uri = Services.io.newURI(protocols[part].urlSpec);
+
+ uri.QueryInterface(Ci.nsISmtpUrl);
+
+ Assert.equal(uri.spec, protocols[part].urlSpec);
+
+ try {
+ // This call should throw NS_ERROR_NOT_IMPLEMENTED. If it doesn't,
+ // then we should implement a new test for it.
+ pH.newChannel(uri, null);
+ // If it didn't throw, then shout about it.
+ do_throw("newChannel not throwing NS_ERROR_NOT_IMPLEMENTED.");
+ } catch (ex) {
+ Assert.equal(ex.result, Cr.NS_ERROR_NOT_IMPLEMENTED);
+ }
+ }
+}
diff --git a/comm/mailnews/compose/test/unit/test_smtpProxy.js b/comm/mailnews/compose/test/unit/test_smtpProxy.js
new file mode 100644
index 0000000000..7a008be001
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_smtpProxy.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+// Tests that SMTP over a SOCKS proxy works.
+
+const { NetworkTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/NetworkTestUtils.jsm"
+);
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+const PORT = 25;
+var daemon, localserver, server;
+
+add_setup(function () {
+ localAccountUtils.loadLocalMailAccount();
+ server = setupServerDaemon();
+ daemon = server._daemon;
+ server.start();
+ NetworkTestUtils.configureProxy("smtp.tinderbox.invalid", PORT, server.port);
+ localserver = getBasicSmtpServer(PORT, "smtp.tinderbox.invalid");
+});
+
+add_task(async function sendMessage() {
+ equal(daemon.post, undefined);
+ let identity = getSmtpIdentity("test@tinderbox.invalid", localserver);
+ var testFile = do_get_file("data/message1.eml");
+ var urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.smtp.sendMailMessage(
+ testFile,
+ "somebody@example.org",
+ identity,
+ "me@example.org",
+ null,
+ urlListener,
+ null,
+ null,
+ false,
+ "",
+ {},
+ {}
+ );
+ await urlListener.promise;
+ notEqual(daemon.post, "");
+});
+
+add_task(async function cleanUp() {
+ NetworkTestUtils.shutdownServers();
+});
diff --git a/comm/mailnews/compose/test/unit/test_smtpServer.js b/comm/mailnews/compose/test/unit/test_smtpServer.js
new file mode 100644
index 0000000000..5e252a44f0
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_smtpServer.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests for nsISmtpServer implementation.
+ */
+
+/**
+ * Test that cached server password is cleared when password storage changed.
+ */
+add_task(async function test_passwordmgr_change() {
+ // Create an nsISmtpServer instance and set a password.
+ let server = Cc["@mozilla.org/messenger/smtp/server;1"].createInstance(
+ Ci.nsISmtpServer
+ );
+ server.password = "smtp-pass";
+ equal(server.password, "smtp-pass", "Password should be cached.");
+
+ // Trigger the change event of password manager.
+ Services.logins.setLoginSavingEnabled("smtp://localhost", false);
+ equal(server.password, "", "Password should be cleared.");
+});
+
+/**
+ * Test getter/setter of attributes.
+ */
+add_task(async function test_attributes() {
+ // Create an nsISmtpServer instance and set a password.
+ let server = Cc["@mozilla.org/messenger/smtp/server;1"].createInstance(
+ Ci.nsISmtpServer
+ );
+
+ server.description = "アイウ";
+ equal(server.description, "アイウ", "Description should be correctly set.");
+
+ server.hostname = "サービス.jp";
+ equal(server.hostname, "サービス.jp", "Hostname should be correctly set.");
+});
+
+/**
+ * Tests the UID attribute of servers.
+ */
+add_task(async function testUID() {
+ const UUID_REGEXP =
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
+
+ // Create a server and check it the UID is set when accessed.
+
+ let serverA = MailServices.smtp.createServer();
+ Assert.stringMatches(
+ serverA.UID,
+ UUID_REGEXP,
+ "server A's UID should exist and be a UUID"
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`mail.smtpserver.${serverA.key}.uid`),
+ serverA.UID,
+ "server A's UID should be saved to the preferences"
+ );
+ Assert.throws(
+ () => (serverA.UID = "00001111-2222-3333-4444-555566667777"),
+ /NS_ERROR_ABORT/,
+ "server A's UID should be unchangeable after it is set"
+ );
+
+ // Create a second server and check the two UIDs don't match.
+
+ let serverB = MailServices.smtp.createServer();
+ Assert.stringMatches(
+ serverB.UID,
+ UUID_REGEXP,
+ "server B's UID should exist and be a UUID"
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`mail.smtpserver.${serverB.key}.uid`),
+ serverB.UID,
+ "server B's UID should be saved to the preferences"
+ );
+ Assert.notEqual(
+ serverB.UID,
+ serverA.UID,
+ "server B's UID should not be the same as server A's"
+ );
+
+ // Create a third server and set the UID before it is accessed.
+
+ let serverC = MailServices.smtp.createServer();
+ serverC.UID = "11112222-3333-4444-5555-666677778888";
+ Assert.equal(
+ serverC.UID,
+ "11112222-3333-4444-5555-666677778888",
+ "server C's UID set correctly"
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`mail.smtpserver.${serverC.key}.uid`),
+ "11112222-3333-4444-5555-666677778888",
+ "server C's UID should be saved to the preferences"
+ );
+ Assert.throws(
+ () => (serverC.UID = "22223333-4444-5555-6666-777788889999"),
+ /NS_ERROR_ABORT/,
+ "server C's UID should be unchangeable after it is set"
+ );
+});
diff --git a/comm/mailnews/compose/test/unit/test_smtpURL.js b/comm/mailnews/compose/test/unit/test_smtpURL.js
new file mode 100644
index 0000000000..833ca91817
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_smtpURL.js
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for checking SMTP URLs are working as expected.
+ * XXX this test needs extending as we fix up nsSmtpUrl.
+ */
+
+var smtpURLs = [
+ {
+ url: "smtp://user@localhost/",
+ spec: "smtp://user@localhost/",
+ username: "user",
+ },
+ {
+ url: "smtps://user@localhost/",
+ spec: "smtps://user@localhost/",
+ username: "user",
+ },
+];
+
+function run_test() {
+ var url;
+ for (var part = 0; part < smtpURLs.length; ++part) {
+ print("url: " + smtpURLs[part].url);
+
+ url = Services.io.newURI(smtpURLs[part].url);
+
+ Assert.equal(url.spec, smtpURLs[part].spec);
+ Assert.equal(url.username, smtpURLs[part].username);
+ }
+}
diff --git a/comm/mailnews/compose/test/unit/test_splitRecipients.js b/comm/mailnews/compose/test/unit/test_splitRecipients.js
new file mode 100644
index 0000000000..b51da1c7a2
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_splitRecipients.js
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for nsMsgCompFields functions.
+ * Currently only tests nsIMsgCompFields::SplitRecipients
+ */
+
+var splitRecipientsTests = [
+ {
+ recipients: "me@foo.invalid",
+ emailAddressOnly: false,
+ count: 1,
+ result: ["me@foo.invalid"],
+ },
+ {
+ recipients: "me@foo.invalid, me2@foo.invalid",
+ emailAddressOnly: false,
+ count: 2,
+ result: ["me@foo.invalid", "me2@foo.invalid"],
+ },
+ {
+ recipients: '"foo bar" <me@foo.invalid>',
+ emailAddressOnly: false,
+ count: 1,
+ result: ["foo bar <me@foo.invalid>"],
+ },
+ {
+ recipients: '"foo bar" <me@foo.invalid>',
+ emailAddressOnly: true,
+ count: 1,
+ result: ["me@foo.invalid"],
+ },
+ {
+ recipients: '"foo bar" <me@foo.invalid>, "bar foo" <me2@foo.invalid>',
+ emailAddressOnly: false,
+ count: 2,
+ result: ["foo bar <me@foo.invalid>", "bar foo <me2@foo.invalid>"],
+ },
+ {
+ recipients: '"foo bar" <me@foo.invalid>, "bar foo" <me2@foo.invalid>',
+ emailAddressOnly: true,
+ count: 2,
+ result: ["me@foo.invalid", "me2@foo.invalid"],
+ },
+ {
+ recipients:
+ "A Group:Ed Jones <c@a.invalid>,joe@where.invalid,John <jdoe@one.invalid>;",
+ emailAddressOnly: false,
+ count: 3,
+ result: [
+ "Ed Jones <c@a.invalid>",
+ "joe@where.invalid",
+ "John <jdoe@one.invalid>",
+ ],
+ },
+ {
+ recipients:
+ "mygroup:;, empty:;, foo@foo.invalid, othergroup:bar@foo.invalid, bar2@foo.invalid;, y@y.invalid, empty:;",
+ emailAddressOnly: true,
+ count: 4,
+ result: [
+ "foo@foo.invalid",
+ "bar@foo.invalid",
+ "bar2@foo.invalid",
+ "y@y.invalid",
+ ],
+ },
+ {
+ recipients: "Undisclosed recipients:;;;;;;;;;;;;;;;;,,,,,,,,,,,,,,,,",
+ emailAddressOnly: true,
+ count: 0,
+ result: [],
+ },
+ {
+ recipients: "a@xxx.invalid; b@xxx.invalid",
+ emailAddressOnly: true,
+ count: 2,
+ result: ["a@xxx.invalid", "b@xxx.invalid"],
+ },
+ {
+ recipients: "a@xxx.invalid; B <b@xxx.invalid>",
+ emailAddressOnly: false,
+ count: 2,
+ result: ["a@xxx.invalid", "B <b@xxx.invalid>"],
+ },
+ {
+ recipients: '"A " <a@xxx.invalid>; b@xxx.invalid',
+ emailAddressOnly: false,
+ count: 2,
+ result: ["A <a@xxx.invalid>", "b@xxx.invalid"],
+ },
+ {
+ recipients: "A <a@xxx.invalid>; B <b@xxx.invalid>",
+ emailAddressOnly: false,
+ count: 2,
+ result: ["A <a@xxx.invalid>", "B <b@xxx.invalid>"],
+ },
+ {
+ recipients:
+ "A (this: is, a comment;) <a.invalid>; g: (this: is, <a> comment;) C <c.invalid>, d.invalid;",
+ emailAddressOnly: false,
+ count: 3,
+ result: [
+ "A (this: is, a comment;) <a.invalid>",
+ "(this: is, <a> comment;) C <c.invalid>",
+ "d.invalid <>",
+ ],
+ },
+ {
+ recipients:
+ 'Mary Smith <mary@x.invalid>, extra:;, group:jdoe@example.invalid; Who? <one@y.invalid>; <boss@nil.invalid>, "Giant; \\"Big\\" Box" <sysservices@example.invalid>, ',
+ emailAddressOnly: false,
+ count: 5,
+ result: [
+ "Mary Smith <mary@x.invalid>",
+ "jdoe@example.invalid",
+ "Who? <one@y.invalid>",
+ "boss@nil.invalid",
+ 'Giant; "Big" Box <sysservices@example.invalid>',
+ ],
+ },
+ {
+ recipients: "Undisclosed recipients: a@foo.invalid ;;extra:;",
+ emailAddressOnly: true,
+ count: 1,
+ result: ["a@foo.invalid"],
+ },
+ {
+ recipients: "Undisclosed recipients:;;extra:a@foo.invalid;",
+ emailAddressOnly: true,
+ count: 1,
+ result: ["a@foo.invalid"],
+ },
+ {
+ recipients: "",
+ emailAddressOnly: false,
+ count: 0,
+ result: [],
+ },
+];
+
+function run_test() {
+ var fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+
+ // As most of SplitRecipients functionality is in the nsIMsgHeaderParser
+ // functionality, here (at least initially), we're just interested in checking
+ // the basic argument/return combinations.
+
+ for (var part = 0; part < splitRecipientsTests.length; ++part) {
+ print("Test: " + splitRecipientsTests[part].recipients);
+ var result = fields.splitRecipients(
+ splitRecipientsTests[part].recipients,
+ splitRecipientsTests[part].emailAddressOnly
+ );
+
+ Assert.equal(splitRecipientsTests[part].count, result.length);
+
+ for (var item = 0; item < result.length; ++item) {
+ Assert.equal(splitRecipientsTests[part].result[item], result[item]);
+ }
+ }
+}
diff --git a/comm/mailnews/compose/test/unit/test_staleTemporaryFileCleanup.js b/comm/mailnews/compose/test/unit/test_staleTemporaryFileCleanup.js
new file mode 100644
index 0000000000..427c101914
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_staleTemporaryFileCleanup.js
@@ -0,0 +1,57 @@
+/* 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/. */
+
+/*
+ * Test that stale temporary files are cleaned up when the msg compose service
+ * is initialized.
+ */
+
+var gExpectedFiles;
+
+function create_temporary_files_for(name) {
+ let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ file.append(name);
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+ return file;
+}
+
+function collect_expected_temporary_files() {
+ let files = [];
+
+ files.push(create_temporary_files_for("nsmail.tmp"));
+ files.push(create_temporary_files_for("nsmail.tmp"));
+ files.push(create_temporary_files_for("nsmail.tmp"));
+ files.push(create_temporary_files_for("nsemail.eml"));
+ files.push(create_temporary_files_for("nsemail.tmp"));
+ files.push(create_temporary_files_for("nsqmail.tmp"));
+ files.push(create_temporary_files_for("nscopy.tmp"));
+ files.push(create_temporary_files_for("nscopy.tmp"));
+
+ return files;
+}
+
+function check_files_not_exist(files) {
+ files.forEach(function (file) {
+ Assert.ok(!file.exists());
+ });
+}
+
+function run_test() {
+ gExpectedFiles = collect_expected_temporary_files();
+ registerCleanupFunction(function () {
+ gExpectedFiles.forEach(function (file) {
+ if (file.exists()) {
+ file.remove(false);
+ }
+ });
+ });
+
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+ MailServices.compose; // Initialise the compose service.
+ do_test_pending();
+ check_files_not_exist(gExpectedFiles);
+ do_test_finished();
+}
diff --git a/comm/mailnews/compose/test/unit/test_telemetry_compose.js b/comm/mailnews/compose/test/unit/test_telemetry_compose.js
new file mode 100644
index 0000000000..f48db07293
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_telemetry_compose.js
@@ -0,0 +1,109 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test telemetry related to message composition.
+ */
+
+ChromeUtils.defineESModuleGetters(this, {
+ TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
+});
+
+const HTML_SCALAR = "tb.compose.format_html";
+const PLAIN_TEXT_SCALAR = "tb.compose.format_plain_text";
+
+/**
+ * Check that we're counting HTML or Plain text when composing.
+ */
+add_task(async function test_compose_format() {
+ Services.telemetry.clearScalars();
+
+ // Bare-bones code to initiate composing a message in given format.
+ let createCompose = function (fmt) {
+ let msgCompose = Cc[
+ "@mozilla.org/messengercompose/compose;1"
+ ].createInstance(Ci.nsIMsgCompose);
+
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+
+ params.format = fmt;
+ msgCompose.initialize(params);
+ };
+
+ // Start composing arbitrary numbers of messages in each format.
+ const NUM_HTML = 7;
+ const NUM_PLAIN = 13;
+ for (let i = 0; i < NUM_HTML; i++) {
+ createCompose(Ci.nsIMsgCompFormat.HTML);
+ }
+ for (let i = 0; i < NUM_PLAIN; i++) {
+ createCompose(Ci.nsIMsgCompFormat.PlainText);
+ }
+
+ // Did we count them correctly?
+ const scalars = TelemetryTestUtils.getProcessScalars("parent");
+ Assert.equal(
+ scalars[HTML_SCALAR],
+ NUM_HTML,
+ HTML_SCALAR + " must have the correct value."
+ );
+ Assert.equal(
+ scalars[PLAIN_TEXT_SCALAR],
+ NUM_PLAIN,
+ PLAIN_TEXT_SCALAR + " must have the correct value."
+ );
+});
+
+/**
+ * Check that we're counting compose type (new/reply/fwd etc) when composing.
+ */
+add_task(async function test_compose_type() {
+ // Bare-bones code to initiate composing a message in given type.
+ let createCompose = function (type) {
+ let msgCompose = Cc[
+ "@mozilla.org/messengercompose/compose;1"
+ ].createInstance(Ci.nsIMsgCompose);
+
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+
+ params.type = type;
+ msgCompose.initialize(params);
+ };
+ const histogram = TelemetryTestUtils.getAndClearHistogram("TB_COMPOSE_TYPE");
+
+ // Start composing arbitrary numbers of messages in each format.
+ const NUM_NEW = 4;
+ const NUM_DRAFT = 7;
+ const NUM_EDIT_TEMPLATE = 3;
+ for (let i = 0; i < NUM_NEW; i++) {
+ createCompose(Ci.nsIMsgCompType.New);
+ }
+ for (let i = 0; i < NUM_DRAFT; i++) {
+ createCompose(Ci.nsIMsgCompType.Draft);
+ }
+ for (let i = 0; i < NUM_EDIT_TEMPLATE; i++) {
+ createCompose(Ci.nsIMsgCompType.EditTemplate);
+ }
+
+ // Did we count them correctly?
+ const snapshot = histogram.snapshot();
+ Assert.equal(
+ snapshot.values[Ci.nsIMsgCompType.New],
+ NUM_NEW,
+ "nsIMsgCompType.New count must be correct"
+ );
+ Assert.equal(
+ snapshot.values[Ci.nsIMsgCompType.Draft],
+ NUM_DRAFT,
+ "nsIMsgCompType.Draft count must be correct"
+ );
+ Assert.equal(
+ snapshot.values[Ci.nsIMsgCompType.EditTemplate],
+ NUM_EDIT_TEMPLATE,
+ "nsIMsgCompType.EditTemplate count must be correct"
+ );
+});
diff --git a/comm/mailnews/compose/test/unit/test_temporaryFilesRemoved.js b/comm/mailnews/compose/test/unit/test_temporaryFilesRemoved.js
new file mode 100644
index 0000000000..6a350ac64e
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/test_temporaryFilesRemoved.js
@@ -0,0 +1,123 @@
+/* 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/. */
+
+/*
+ * Test that temporary files for draft are surely removed.
+ */
+
+var gMsgCompose;
+var gExpectedFiles;
+
+var progressListener = {
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ do_timeout(0, checkResult);
+ }
+ },
+
+ onProgressChange(
+ aWebProgress,
+ aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress
+ ) {},
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {},
+ onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {},
+ onSecurityChange(aWebProgress, aRequest, state) {},
+ onContentBlockingEvent(aWebProgress, aRequest, aEvent) {},
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+};
+
+/**
+ * Get the count of temporary files. Because nsIFile.createUnique creates a random
+ * file name, we iterate the tmp dir and count the files that match filename
+ * patterns.
+ */
+async function getTemporaryFilesCount() {
+ let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile).path;
+ let entries = await IOUtils.getChildren(tmpDir);
+ let tempFiles = {
+ "nsmail.tmp": 0,
+ "nscopy.tmp": 0,
+ "nsemail.eml": 0,
+ "nsemail.tmp": 0,
+ "nsqmail.tmp": 0,
+ };
+ for (const path of entries) {
+ for (let pattern of Object.keys(tempFiles)) {
+ let [name, extName] = pattern.split(".");
+ if (PathUtils.filename(path).startsWith(name) && path.endsWith(extName)) {
+ tempFiles[pattern]++;
+ }
+ }
+ }
+ return tempFiles;
+}
+
+/**
+ * Temp files should be deleted as soon as the draft is finished saving, so the
+ * counts should be the same as before.
+ */
+async function checkResult() {
+ let filesCount = await getTemporaryFilesCount();
+ for (let [pattern, count] of Object.entries(filesCount)) {
+ Assert.equal(
+ count,
+ gExpectedFiles[pattern],
+ `${pattern} should not exists`
+ );
+ }
+ do_test_finished();
+}
+
+add_task(async function () {
+ gExpectedFiles = await getTemporaryFilesCount();
+
+ // Ensure we have at least one mail account
+ localAccountUtils.loadLocalMailAccount();
+
+ gMsgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance(
+ Ci.nsIMsgCompose
+ );
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+
+ fields.from = "Nobody <nobody@tinderbox.test>";
+ fields.body = "body text";
+ fields.useMultipartAlternative = true;
+
+ params.composeFields = fields;
+ params.format = Ci.nsIMsgCompFormat.HTML;
+
+ gMsgCompose.initialize(params, null, null);
+
+ let identity = getSmtpIdentity(null, getBasicSmtpServer());
+
+ localAccountUtils.rootFolder.createLocalSubfolder("Drafts");
+
+ let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance(
+ Ci.nsIMsgProgress
+ );
+ progress.registerListener(progressListener);
+
+ do_test_pending();
+
+ gMsgCompose.sendMsg(
+ Ci.nsIMsgSend.nsMsgSaveAsDraft,
+ identity,
+ "",
+ null,
+ progress
+ );
+});
diff --git a/comm/mailnews/compose/test/unit/xpcshell.ini b/comm/mailnews/compose/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..687259c42a
--- /dev/null
+++ b/comm/mailnews/compose/test/unit/xpcshell.ini
@@ -0,0 +1,54 @@
+[DEFAULT]
+head = head_compose.js
+tail =
+support-files = data/*
+
+[test_accountKey.js]
+[test_attachment.js]
+[test_attachment_intl.js]
+[test_autoReply.js]
+skip-if = os == 'mac'
+[test_bcc.js]
+[test_bug155172.js]
+[test_bug474774.js]
+[test_createAndSendMessage.js]
+[test_createRFC822Message.js]
+[test_detectAttachmentCharset.js]
+[test_expandMailingLists.js]
+[test_fcc2.js]
+[test_fccReply.js]
+[test_longLines.js]
+[test_mailTelemetry.js]
+[test_mailtoURL.js]
+[test_messageBody.js]
+[test_messageHeaders.js]
+[test_nsIMsgCompFields.js]
+[test_nsMsgCompose1.js]
+[test_nsMsgCompose2.js]
+[test_nsMsgCompose3.js]
+[test_nsSmtpService1.js]
+[test_saveDraft.js]
+[test_sendBackground.js]
+[test_sendMailAddressIDN.js]
+[test_sendMailMessage.js]
+[test_sendMessageFile.js]
+[test_sendMessageLater.js]
+[test_sendMessageLater2.js]
+[test_sendMessageLater3.js]
+[test_sendObserver.js]
+[test_smtp8bitMime.js]
+[test_smtpAuthMethods.js]
+[test_smtpClient.js]
+[test_smtpPassword.js]
+[test_smtpPassword2.js]
+[test_smtpPasswordFailure1.js]
+[test_smtpPasswordFailure2.js]
+[test_smtpPasswordFailure3.js]
+[test_smtpProtocols.js]
+[test_smtpProxy.js]
+[test_smtpServer.js]
+[test_smtpURL.js]
+[test_splitRecipients.js]
+[test_telemetry_compose.js]
+[test_staleTemporaryFileCleanup.js]
+[test_temporaryFilesRemoved.js]