summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/compose/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/compose/test
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/compose/test')
-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
63 files changed, 8833 insertions, 0 deletions
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]