summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/mime/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/mime/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/mime/test')
-rw-r--r--comm/mailnews/mime/test/TestMimeCrash.cpp63
-rw-r--r--comm/mailnews/mime/test/moz.build6
-rw-r--r--comm/mailnews/mime/test/unit/custom_header.js11
-rw-r--r--comm/mailnews/mime/test/unit/head_mime.js62
-rw-r--r--comm/mailnews/mime/test/unit/test_EncodeMimePartIIStr_UTF8.js39
-rw-r--r--comm/mailnews/mime/test/unit/test_alternate_p7m_handling.js58
-rw-r--r--comm/mailnews/mime/test/unit/test_attachment_size.js322
-rw-r--r--comm/mailnews/mime/test/unit/test_badContentType.js115
-rw-r--r--comm/mailnews/mime/test/unit/test_bug493544.js106
-rw-r--r--comm/mailnews/mime/test/unit/test_handlerRegistration.js64
-rw-r--r--comm/mailnews/mime/test/unit/test_hidden_attachments.js221
-rw-r--r--comm/mailnews/mime/test/unit/test_jsmime_charset.js43
-rw-r--r--comm/mailnews/mime/test/unit/test_message_attachment.js168
-rw-r--r--comm/mailnews/mime/test/unit/test_mimeContentType.js82
-rw-r--r--comm/mailnews/mime/test/unit/test_mimeStreaming.js88
-rw-r--r--comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser1.js52
-rw-r--r--comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser2.js86
-rw-r--r--comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser3.js115
-rw-r--r--comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser4.js199
-rw-r--r--comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser5.js104
-rw-r--r--comm/mailnews/mime/test/unit/test_openpgp_decrypt.js415
-rw-r--r--comm/mailnews/mime/test/unit/test_parser.js322
-rw-r--r--comm/mailnews/mime/test/unit/test_rfc822_body.js94
-rw-r--r--comm/mailnews/mime/test/unit/test_smime_decrypt.js701
-rw-r--r--comm/mailnews/mime/test/unit/test_smime_decrypt_allow_sha1.js717
-rw-r--r--comm/mailnews/mime/test/unit/test_smime_perm_decrypt.js274
-rw-r--r--comm/mailnews/mime/test/unit/test_structured_headers.js249
-rw-r--r--comm/mailnews/mime/test/unit/test_text_attachment.js89
-rw-r--r--comm/mailnews/mime/test/unit/xpcshell.ini35
29 files changed, 4900 insertions, 0 deletions
diff --git a/comm/mailnews/mime/test/TestMimeCrash.cpp b/comm/mailnews/mime/test/TestMimeCrash.cpp
new file mode 100644
index 0000000000..f041d8f910
--- /dev/null
+++ b/comm/mailnews/mime/test/TestMimeCrash.cpp
@@ -0,0 +1,63 @@
+// This is a crash test for Bug 556351
+
+#include "nsCOMPtr.h"
+#include "nsIMimeConverter.h"
+#include "nsServiceManagerUtils.h"
+
+#include "prshma.h"
+#include "prsystem.h"
+
+#include "TestHarness.h"
+
+nsresult mime_encoder_output_fn(const char* buf, int32_t size, void* closure) {
+ return NS_OK;
+}
+
+nsresult do_test(const char* aBuffer, const uint32_t aSize) {
+ nsresult rv;
+ MimeEncoderData* encodeData = nullptr;
+ int32_t written = 0;
+
+ nsCOMPtr<nsIMimeConverter> converter =
+ do_GetService("@mozilla.org/messenger/mimeconverter;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = converter->QPEncoderInit(mime_encoder_output_fn, nullptr, &encodeData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = converter->EncoderWrite(encodeData, aBuffer, aSize, &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = converter->EncoderDestroy(encodeData, false);
+ return rv;
+}
+
+int main(int argc, char** argv) {
+ ScopedXPCOM xpcom("TestMimeCrash");
+ if (xpcom.failed()) return 1;
+
+ // We cannot use malloc() since this crashes depends on memory allocation.
+ // By using mmap()/PR_MemMap(), end of buffer that is last in the page
+ // sets LF.
+
+ uint32_t bufsize = PR_GetPageSize();
+ PRFileMap* fm = PR_OpenAnonFileMap(".", bufsize, PR_PROT_READWRITE);
+ if (!fm) return 1;
+ char* addr = (char*)PR_MemMap(fm, 0, bufsize);
+ if (!addr) return 1;
+ memset(addr, '\r', bufsize);
+
+ nsresult rv = do_test(addr, bufsize);
+
+ PR_MemUnmap(addr, bufsize);
+ PR_CloseFileMap(fm);
+
+ if (NS_FAILED(rv)) {
+ fail("cannot use nsIMimeConverter error=%08x\n", rv);
+ return -1;
+ }
+
+ passed("no crash");
+
+ return 0;
+}
diff --git a/comm/mailnews/mime/test/moz.build b/comm/mailnews/mime/test/moz.build
new file mode 100644
index 0000000000..6b37fdbe09
--- /dev/null
+++ b/comm/mailnews/mime/test/moz.build
@@ -0,0 +1,6 @@
+# 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/mime/test/unit/custom_header.js b/comm/mailnews/mime/test/unit/custom_header.js
new file mode 100644
index 0000000000..af2af97780
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/custom_header.js
@@ -0,0 +1,11 @@
+// Custom header support for testing structured_headers.js
+
+/* globals jsmime */
+
+jsmime.headerparser.addStructuredDecoder("X-Unusual", function (hdrs) {
+ return Number.parseInt(hdrs[hdrs.length - 1], 16);
+});
+
+jsmime.headeremitter.addStructuredEncoder("X-Unusual", function (val) {
+ this.addUnstructured(val.toString(16));
+});
diff --git a/comm/mailnews/mime/test/unit/head_mime.js b/comm/mailnews/mime/test/unit/head_mime.js
new file mode 100644
index 0000000000..868a7ceace
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/head_mime.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/. */
+
+/**
+ * Utility code for converting encoded MIME data.
+ */
+
+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 { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+var CC = Components.Constructor;
+
+// Ensure the profile directory is set up
+do_get_profile();
+
+var gDEPTH = "../../../../";
+
+registerCleanupFunction(function () {
+ load(gDEPTH + "mailnews/resources/mailShutdown.js");
+});
+
+function apply_mime_conversion(msgUri, smimeHeaderSink) {
+ let service = MailServices.messageServiceFromURI(msgUri);
+
+ // This is what we listen on in the end.
+ let listener = new PromiseTestUtils.PromiseStreamListener();
+
+ // Make the underlying channel--we need this for the converter parameter.
+ let url = service.getUrlForUri(msgUri);
+
+ let channel = Services.io.newChannelFromURI(
+ url,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+ channel.QueryInterface(Ci.nsIMailChannel).smimeHeaderSink = smimeHeaderSink;
+
+ // Make the MIME converter, using the listener we first set up.
+ let converter = Cc["@mozilla.org/streamConverters;1"]
+ .getService(Ci.nsIStreamConverterService)
+ .asyncConvertData("message/rfc822", "text/html", listener, channel);
+
+ // Now load the message, run it through the converter, and wait for all the
+ // data to stream through.
+ channel.asyncOpen(converter);
+ return listener;
+}
diff --git a/comm/mailnews/mime/test/unit/test_EncodeMimePartIIStr_UTF8.js b/comm/mailnews/mime/test/unit/test_EncodeMimePartIIStr_UTF8.js
new file mode 100644
index 0000000000..71d31da9ac
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_EncodeMimePartIIStr_UTF8.js
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This tests minimal mime encoding fixed in bug 458685
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ var i;
+
+ var checks = [
+ ["", false, ""],
+ ["\u0436", false, "=?UTF-8?B?0LY=?="], // CYRILLIC SMALL LETTER ZHE
+ ["IamASCII", false, "IamASCII"],
+ // Although an invalid email, we shouldn't crash on it (bug 479206)
+ ["crash test@foo.invalid>", true, '"crash test"@foo.invalid'],
+ [
+ "MXR now displays links to Github log & Blame for\r\n Gaia/Rust/Servo",
+ false,
+ "MXR now displays links to Github log & Blame for\r\n Gaia/Rust/Servo",
+ ],
+ ["-----------------------:", false, "-----------------------:"],
+ ];
+
+ for (i = 0; i < checks.length; ++i) {
+ Assert.equal(
+ MailServices.mimeConverter.encodeMimePartIIStr_UTF8(
+ checks[i][0],
+ checks[i][1],
+ "Subject".length,
+ 72
+ ),
+ checks[i][2]
+ );
+ }
+}
diff --git a/comm/mailnews/mime/test/unit/test_alternate_p7m_handling.js b/comm/mailnews/mime/test/unit/test_alternate_p7m_handling.js
new file mode 100644
index 0000000000..2dfeabf7ff
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_alternate_p7m_handling.js
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { MsgHdrToMimeMessage } = ChromeUtils.import(
+ "resource:///modules/gloda/MimeMessage.jsm"
+);
+
+const P7M_ATTACHMENT = "dGhpcyBpcyBub3QgYSByZWFsIHMvbWltZSBwN20gZW50aXR5";
+var messageGenerator = new MessageGenerator();
+var messageInjection = new MessageInjection({ mode: "local" });
+var inbox = messageInjection.getInboxFolder();
+var msgHdr;
+
+add_setup(async function () {
+ // Create a message with a p7m attachment.
+ let synMsg = messageGenerator.makeMessage({
+ attachments: [
+ {
+ body: P7M_ATTACHMENT,
+ filename: "test.txt.p7m",
+ contentType: "application/pkcs7-mime",
+ format: "",
+ encoding: "base64",
+ },
+ ],
+ });
+ let synSet = new SyntheticMessageSet([synMsg]);
+ await messageInjection.addSetsToFolders([inbox], [synSet]);
+ msgHdr = synSet.getMsgHdr(0);
+});
+
+add_task(async function test_mime_p7m_external_foo_pref() {
+ Services.prefs.setBoolPref("mailnews.p7m_external", true);
+
+ await new Promise(resolve => {
+ MsgHdrToMimeMessage(msgHdr, null, function (aMsgHdr, aMimeMsg) {
+ Assert.ok(aMimeMsg.allUserAttachments.length == 1);
+ resolve();
+ });
+ });
+});
+add_task(async function test_mime_p7m_external_all_external_pref() {
+ Services.prefs.setBoolPref("mailnews.p7m_external", false);
+
+ await new Promise(resolve => {
+ MsgHdrToMimeMessage(msgHdr, null, function (aMsgHdr, aMimeMsg) {
+ Assert.ok(aMimeMsg.allUserAttachments.length == 1);
+ resolve();
+ });
+ });
+});
diff --git a/comm/mailnews/mime/test/unit/test_attachment_size.js b/comm/mailnews/mime/test/unit/test_attachment_size.js
new file mode 100644
index 0000000000..cf6e68f0bf
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_attachment_size.js
@@ -0,0 +1,322 @@
+/* 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/. */
+
+/**
+ * This test creates some messages with attachments of different types and
+ * checks that libmime reports the expected size for each of them.
+ */
+
+var {
+ MessageGenerator,
+ SyntheticPartLeaf,
+ SyntheticPartMultiMixed,
+ SyntheticMessageSet,
+} = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// Somehow we hit the blocklist service, and that needs appInfo defined
+const { updateAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+updateAppInfo();
+
+var messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+
+// Create a message generator
+var msgGen = new MessageGenerator();
+var messageInjection = new MessageInjection({ mode: "local" });
+var inbox = messageInjection.getInboxFolder();
+
+/* Today's gory details (thanks to Jonathan Protzenko): libmime somehow
+ * counts the trailing newline for an attachment MIME part. Most of the time,
+ * assuming attachment has N bytes (no matter what's inside, newlines or
+ * not), libmime will return N + 1 bytes. On Linux and Mac, this always
+ * holds. However, on Windows, if the attachment is not encoded (that is, is
+ * inline text), libmime will return N + 2 bytes.
+ */
+const EPSILON = "@mozilla.org/windows-registry-key;1" in Cc ? 4 : 2;
+
+const TEXT_ATTACHMENT =
+ "Can't make the frug contest, Helen; stomach's upset. I'll fix you, " +
+ "Ubik! Ubik drops you back in the thick of things fast. Taken as " +
+ "directed, Ubik speeds relief to head and stomach. Remember: Ubik is " +
+ "only seconds away. Avoid prolonged use.";
+
+const BINARY_ATTACHMENT = TEXT_ATTACHMENT;
+
+const IMAGE_ATTACHMENT =
+ "iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAAAlwS" +
+ "FlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAA" +
+ "A5SURBVCiRY/z//z8DKYCJJNXkaGBgYGD4D8NQ5zUgiTVAxeBqSLaBkVRPM0KtIhrQ3km0jwe" +
+ "SNQAAlmAY+71EgFoAAAAASUVORK5CYII=";
+const IMAGE_SIZE = 188;
+
+const UU_ATTACHMENT =
+ "begin 644 /home/jvporter/Desktop/out.txt\n" +
+ 'M0V%N)W0@;6%K92!T:&4@9G)U9R!C;VYT97-T+"!(96QE;CL@<W1O;6%C:"=S\n' +
+ "M('5P<V5T+B!))VQL(&9I>\"!Y;W4L(%5B:6LA(%5B:6L@9')O<',@>6]U(&)A\n" +
+ "M8VL@:6X@=&AE('1H:6-K(&]F('1H:6YG<R!F87-T+B!486ME;B!A<R!D:7)E\n" +
+ "M8W1E9\"P@56)I:R!S<&5E9',@<F5L:65F('1O(&AE860@86YD('-T;VUA8V@N\n" +
+ "M(%)E;65M8F5R.B!58FEK(&ES(&]N;'D@<V5C;VYD<R!A=V%Y+B!!=F]I9\"!P\n" +
+ ".<F]L;VYG960@=7-E+@H`\n" +
+ "`\n" +
+ "end";
+
+const YENC_TEXT =
+ "Hello there --\n" +
+ "=ybegin line=128 size=174 name=jane\n" +
+ "\x76\x99\x98\x91\x9e\x8f\x97\x9a\x9d\x56\x4a\x94\x8f\x4a\x97\x8f" +
+ "\x4a\x9d\x9f\x93\x9d\x4a\x8d\x99\x9f\x8d\x92\xed\xd3\x4a\x8e\x8f" +
+ "\x4a\x8c\x99\x98\x98\x8f\x4a\x92\x8f\x9f\x9c\x8f\x58\x4a\x7a\x8b" +
+ "\x9c\x90\x99\x93\x9d\x56\x4a\xed\xca\x4a\x9a\x8f\x93\x98\x8f\x4a" +
+ "\x97\x8b\x4a\x8c\x99\x9f\x91\x93\x8f\x4a\xed\xd3\x9e\x8f\x93\x98" +
+ "\x9e\x8f\x56\x4a\x97\x8f\x9d\x4a\xa3\x8f\x9f\xa2\x4a\x9d\x8f\x4a" +
+ "\x90\x8f\x9c\x97\x8b\x93\x8f\x98\x9e\x4a\x9d\x93\x4a\xa0\x93\x9e" +
+ "\x8f\x4a\x9b\x9f\x8f\x4a\x94\x8f\x4a\x98\x51\x8b\xa0\x8b\x93\x9d" +
+ "\x0d\x0a\x4a\x9a\x8b\x9d\x4a\x96\x8f\x4a\x9e\x8f\x97\x9a\x9d\x4a" +
+ "\x8e\x8f\x4a\x97\x8f\x4a\x8e\x93\x9c\x8f\x4a\x64\x4a\xec\xd5\x4a" +
+ "\x74\x8f\x4a\x97\x51\x8f\x98\x8e\x99\x9c\x9d\x58\x4a\xec\xe5\x34" +
+ "\x0d\x0a" +
+ "=yend size=174 crc32=7efccd8e\n";
+const YENC_SIZE = 174;
+
+const PART_HTML = new SyntheticPartLeaf(
+ "<html><head></head><body>I am HTML! Woo! </body></html>",
+ {
+ contentType: "text/html",
+ }
+);
+
+var attachedMessage1 = msgGen.makeMessage({ body: { body: TEXT_ATTACHMENT } });
+var attachedMessage2 = msgGen.makeMessage({
+ body: { body: TEXT_ATTACHMENT },
+ attachments: [
+ {
+ body: IMAGE_ATTACHMENT,
+ contentType: "application/x-ubik",
+ filename: "ubik",
+ encoding: "base64",
+ format: "",
+ },
+ ],
+});
+
+add_task(async function test_text_attachment() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: TEXT_ATTACHMENT,
+ filename: "ubik.txt",
+ format: "",
+ },
+ ],
+ size: TEXT_ATTACHMENT.length,
+ });
+});
+
+// (inline) image attachment
+add_task(async function test_inline_image_attachment() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: IMAGE_ATTACHMENT,
+ contentType: "image/png",
+ filename: "lines.png",
+ encoding: "base64",
+ format: "",
+ },
+ ],
+ size: IMAGE_SIZE,
+ });
+});
+
+// binary attachment, no encoding
+add_task(async function test_binary_attachment_no_encoding() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: BINARY_ATTACHMENT,
+ contentType: "application/x-ubik",
+ filename: "ubik",
+ format: "",
+ },
+ ],
+ size: BINARY_ATTACHMENT.length,
+ });
+});
+
+// binary attachment, b64 encoding
+add_task(async function test_binary_attachment_b64_encoding() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: IMAGE_ATTACHMENT,
+ contentType: "application/x-ubik",
+ filename: "ubik",
+ encoding: "base64",
+ format: "",
+ },
+ ],
+ size: IMAGE_SIZE,
+ });
+});
+
+// uuencoded attachment
+add_task(async function test_uuencoded_attachment() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: UU_ATTACHMENT,
+ contentType: "application/x-uuencode",
+ filename: "ubik",
+ format: "",
+ encoding: "uuencode",
+ },
+ ],
+ size: TEXT_ATTACHMENT.length,
+ });
+});
+
+// yencoded attachment
+add_task(async function test_yencoded_attachment() {
+ await test_message_attachments({
+ bodyPart: new SyntheticPartLeaf("I am text! Woo!\n\n" + YENC_TEXT, {
+ contentType: "",
+ }),
+ subject: 'yEnc-Prefix: "jane" 174 yEnc bytes - yEnc test (1)',
+ size: YENC_SIZE,
+ });
+});
+
+// an attached eml that used to return a size that's -1
+add_task(async function test_incorrect_attached_eml() {
+ await test_message_attachments({
+ bodyPart: new SyntheticPartMultiMixed([PART_HTML, attachedMessage1]),
+ size: get_message_size(attachedMessage1),
+ });
+});
+
+// this is an attached message that itself has an attachment
+add_task(async function test_recursive_attachment() {
+ await test_message_attachments({
+ bodyPart: new SyntheticPartMultiMixed([PART_HTML, attachedMessage2]),
+ size: get_message_size(attachedMessage2),
+ });
+});
+
+// an "attachment" that's really the body of the message
+add_task(async function test_body_attachment() {
+ await test_message_attachments({
+ body: {
+ body: TEXT_ATTACHMENT,
+ contentType: "application/x-ubik; name=attachment.ubik",
+ },
+ size: TEXT_ATTACHMENT.length,
+ });
+});
+
+// a message/rfc822 "attachment" that's really the body of the message
+add_task(async function test_rfc822_attachment() {
+ await test_message_attachments({
+ bodyPart: attachedMessage1,
+ size: get_message_size(attachedMessage1),
+ });
+});
+
+// an external http link attachment (as constructed for feed enclosures) - no 'size' parm.
+add_task(async function test_external_http_link_without_size() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: "This MIME attachment is stored separately from the message.",
+ contentType: 'application/unknown; name="somefile"',
+ extraHeaders: {
+ "X-Mozilla-External-Attachment-URL": "http://myblog.com/somefile",
+ },
+ disposition: 'attachment; filename="somefile"',
+ },
+ ],
+ size: -1,
+ });
+});
+
+// an external http link attachment (as constructed for feed enclosures) - file with 'size' parm.
+add_task(async function test_external_http_link_wit_file_size() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: "This MIME attachment is stored separately from the message.",
+ contentType: 'audio/mpeg; name="file.mp3"; size=123456789',
+ extraHeaders: {
+ "X-Mozilla-External-Attachment-URL": "https://myblog.com/file.mp3",
+ },
+ disposition: 'attachment; name="file.mp3"',
+ },
+ ],
+ size: 123456789,
+ });
+});
+
+add_task(function endTest() {
+ messageInjection.teardownMessageInjection();
+});
+
+async function test_message_attachments(info) {
+ let synMsg = msgGen.makeMessage(info);
+ let synSet = new SyntheticMessageSet([synMsg]);
+ await messageInjection.addSetsToFolders([inbox], [synSet]);
+
+ let msgURI = synSet.getMsgURI(0);
+ let msgService = MailServices.messageServiceFromURI(msgURI);
+ await PromiseTestUtils.promiseDelay(200);
+ let streamListener = new PromiseTestUtils.PromiseStreamListener({
+ onStopRequest(request) {
+ request.QueryInterface(Ci.nsIMailChannel);
+ for (let attachment of request.attachments) {
+ let attachmentSize = parseInt(attachment.get("X-Mozilla-PartSize"));
+ dump(
+ "*** Size is " + attachmentSize + " (expecting " + info.size + ")\n"
+ );
+ Assert.ok(Math.abs(attachmentSize - info.size) <= EPSILON);
+ break;
+ }
+ },
+ });
+ msgService.streamMessage(
+ msgURI,
+ streamListener,
+ null,
+ null,
+ true, // have them create the converter
+ // additional uri payload, note that "header=" is prepended automatically
+ "filter",
+ false
+ );
+
+ await streamListener.promise;
+}
+
+/**
+ * Return the size of a synthetic message. Much like the above comment, libmime
+ * counts bytes differently on Windows, where it counts newlines (\r\n) as 2
+ * bytes. Mac and Linux treats them as 1 byte.
+ *
+ * @param message a synthetic message from makeMessage()
+ * @returns the message's size in bytes
+ */
+function get_message_size(message) {
+ let messageString = message.toMessageString();
+ if (EPSILON == 4) {
+ // Windows
+ return messageString.length;
+ }
+ return messageString.replace(/\r\n/g, "\n").length;
+}
diff --git a/comm/mailnews/mime/test/unit/test_badContentType.js b/comm/mailnews/mime/test/unit/test_badContentType.js
new file mode 100644
index 0000000000..1202f3319b
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_badContentType.js
@@ -0,0 +1,115 @@
+/* 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/. */
+
+/**
+ * This test checks handling of bad content type of the
+ * type reported in bug 659355.
+ * Adapted from test_attachment_size.js
+ */
+
+var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+var messageGenerator = new MessageGenerator();
+var messageInjection = new MessageInjection({ mode: "local" });
+var inbox = messageInjection.getInboxFolder();
+
+const IMAGE_ATTACHMENT =
+ "iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAAAlwS" +
+ "FlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAA" +
+ "A5SURBVCiRY/z//z8DKYCJJNXkaGBgYGD4D8NQ5zUgiTVAxeBqSLaBkVRPM0KtIhrQ3km0jwe" +
+ "SNQAAlmAY+71EgFoAAAAASUVORK5CYII=";
+
+add_task(async function test_image_attachment_normal_content_type() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: IMAGE_ATTACHMENT,
+ contentType: "image/png",
+ filename: "lines.png",
+ encoding: "base64",
+ format: "",
+ },
+ ],
+ testContentType: "image/png",
+ });
+});
+
+add_task(async function test_image_attachment_bad_content_type() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: IMAGE_ATTACHMENT,
+ contentType: "=?windows-1252?q?application/pdf",
+ filename: "lines.pdf",
+ encoding: "base64",
+ format: "",
+ },
+ ],
+ testContentType: "application/pdf",
+ });
+});
+
+add_task(function endTest() {
+ messageInjection.teardownMessageInjection();
+});
+
+async function test_message_attachments(info) {
+ let synMsg = messageGenerator.makeMessage(info);
+ let synSet = new SyntheticMessageSet([synMsg]);
+ await messageInjection.addSetsToFolders([inbox], [synSet]);
+
+ let msgURI = synSet.getMsgURI(0);
+ let msgService = MailServices.messageServiceFromURI(msgURI);
+
+ let streamListener = new PromiseTestUtils.PromiseStreamListener({
+ onStopRequest(request, statusCode) {
+ request.QueryInterface(Ci.nsIMailChannel);
+ let msgHdrSinkContentType =
+ request.attachments[0].getProperty("contentType");
+ Assert.equal(msgHdrSinkContentType, info.testContentType);
+ },
+ });
+ msgService.streamMessage(
+ msgURI,
+ streamListener,
+ null,
+ null,
+ true, // have them create the converter
+ // additional uri payload, note that "header=" is prepended automatically
+ "filter",
+ false
+ );
+ await streamListener.promise;
+}
+
+function MsgHeaderSinkHandleAttachments() {
+ this._promise = new Promise(resolve => {
+ this._resolve = resolve;
+ });
+}
+
+MsgHeaderSinkHandleAttachments.prototype = {
+ handleAttachment(
+ aContentType,
+ aUrl,
+ aDisplayName,
+ aUri,
+ aIsExternalAttachment
+ ) {
+ this._resolve(aContentType);
+ },
+
+ get promise() {
+ return this._promise;
+ },
+};
diff --git a/comm/mailnews/mime/test/unit/test_bug493544.js b/comm/mailnews/mime/test/unit/test_bug493544.js
new file mode 100644
index 0000000000..f0da7ef167
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_bug493544.js
@@ -0,0 +1,106 @@
+//
+// Tests if a multi-line MIME header is parsed even if it violates RFC 2047
+//
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ const headers = [
+ {
+ // Bug 833028
+ encoded:
+ "Subject: AAA =?UTF-8?Q?bbb?= CCC =?UTF-8?Q?ddd?= EEE =?UTF-8?Q?fff?= GGG",
+ defaultCharset: "UTF-8",
+ overrideCharset: false,
+ eatContinuation: false,
+ decoded: "Subject: AAA bbb CCC ddd EEE fff GGG",
+ },
+ {
+ // Bug 493544
+ encoded:
+ "Subject: =?UTF-8?B?4oiAICDiiIEgIOKIgiAg4oiDICDiiIQgIOKIhSAg4oiGICDiiIcgIOKIiCAg?=\n" +
+ " =?UTF-8?B?4oiJICDiiIogIOKIiyAg4oiMICDiiI0gIOKIjiAg4oiP?=",
+ defaultCharset: "UTF-8",
+ overrideCharset: false,
+ eatContinuation: false,
+ decoded: "Subject: ∀ ∁ ∂ ∃ ∄ ∅ ∆ ∇ ∈ ∉ ∊ ∋ ∌ ∍ ∎ ∏",
+ },
+ {
+ // Bug 476741
+ encoded:
+ "Subject: =?utf-8?Q?=E2=88=80__=E2=88=81__=E2=88=82__=E2=88=83__=E2=88=84__=E2?=\n" +
+ " =?utf-8?Q?=88=85__=E2=88=86__=E2=88=87__=E2=88=88__=E2=88=89__=E2=88?=\n" +
+ " =?utf-8?Q?=8A__=E2=88=8B__=E2=88=8C__=E2=88=8D__=E2=88=8E__=E2=88=8F?=",
+ defaultCharset: "UTF-8",
+ overrideCharset: false,
+ eatContinuation: false,
+ decoded: "Subject: ∀ ∁ ∂ ∃ ∄ ∅ ∆ ∇ ∈ ∉ ∊ ∋ ∌ ∍ ∎ ∏",
+ },
+ {
+ // Normal case
+ encoded:
+ "Subject: =?UTF-8?B?4oiAICDiiIEgIOKIgiAg4oiDICDiiIQgIOKIhSAg4oiGICDiiIcgIOKIiA==?=\n" +
+ " =?UTF-8?B?ICDiiIkgIOKIiiAg4oiLICDiiIwgIOKIjSAg4oiOICDiiI8=?=",
+ defaultCharset: "UTF-8",
+ overrideCharset: false,
+ eatContinuation: false,
+ decoded: "Subject: ∀ ∁ ∂ ∃ ∄ ∅ ∆ ∇ ∈ ∉ ∊ ∋ ∌ ∍ ∎ ∏",
+ },
+ {
+ // Normal case with the encoding character in lower case
+ encoded:
+ "Subject: =?UTF-8?b?4oiAICDiiIEgIOKIgiAg4oiDICDiiIQgIOKIhSAg4oiGICDiiIcgIOKIiA==?=\n" +
+ " =?UTF-8?b?ICDiiIkgIOKIiiAg4oiLICDiiIwgIOKIjSAg4oiOICDiiI8=?=",
+ defaultCharset: "UTF-8",
+ overrideCharset: false,
+ eatContinuation: false,
+ decoded: "Subject: ∀ ∁ ∂ ∃ ∄ ∅ ∆ ∇ ∈ ∉ ∊ ∋ ∌ ∍ ∎ ∏",
+ },
+ {
+ // Normal case
+ encoded:
+ "Subject: =?utf-8?Q?=E2=88=80__=E2=88=81__=E2=88=82__=E2=88=83__=E2=88=84__?=\n" +
+ " =?utf-8?Q?=E2=88=85__=E2=88=86__=E2=88=87__=E2=88=88__=E2=88=89__?=\n" +
+ " =?utf-8?Q?=E2=88=8A__=E2=88=8B__=E2=88=8C__=E2=88=8D__=E2=88=8E__?=\n" +
+ " =?utf-8?Q?=E2=88=8F?=",
+ defaultCharset: "UTF-8",
+ overrideCharset: false,
+ eatContinuation: false,
+ decoded: "Subject: ∀ ∁ ∂ ∃ ∄ ∅ ∆ ∇ ∈ ∉ ∊ ∋ ∌ ∍ ∎ ∏",
+ },
+ {
+ // Normal case with the encoding character in lower case
+ encoded:
+ "Subject: =?utf-8?q?=E2=88=80__=E2=88=81__=E2=88=82__=E2=88=83__=E2=88=84__?=\n" +
+ " =?utf-8?q?=E2=88=85__=E2=88=86__=E2=88=87__=E2=88=88__=E2=88=89__?=\n" +
+ " =?utf-8?q?=E2=88=8A__=E2=88=8B__=E2=88=8C__=E2=88=8D__=E2=88=8E__?=\n" +
+ " =?utf-8?q?=E2=88=8F?=",
+ defaultCharset: "UTF-8",
+ overrideCharset: false,
+ eatContinuation: false,
+ decoded: "Subject: ∀ ∁ ∂ ∃ ∄ ∅ ∆ ∇ ∈ ∉ ∊ ∋ ∌ ∍ ∎ ∏",
+ },
+ {
+ // Regression test for bug 227290
+ encoded:
+ "Subject: =?UTF-8?B?4oiAICDiiIEgIOKIgiAg4oiDICDiiIQgIOKIhSAg4oiGICDiiIcgIOKIiA===?=\n" +
+ " =?UTF-8?B?ICDiiIkgIOKIiiAg4oiLICDiiIwgIOKIjSAg4oiOICDiiI8=?=",
+ defaultCharset: "UTF-8",
+ overrideCharset: false,
+ eatContinuation: false,
+ decoded: "Subject: ∀ ∁ ∂ ∃ ∄ ∅ ∆ ∇ ∈ ∉ ∊ ∋ ∌ ∍ ∎ ∏",
+ },
+ ];
+
+ for (let i = 0; i < headers.length; ++i) {
+ let decoded = MailServices.mimeConverter.decodeMimeHeader(
+ headers[i].encoded,
+ headers[i].defaultCharset,
+ headers[i].overrideCharset,
+ headers[i].eatContinuation
+ );
+ Assert.equal(decoded, headers[i].decoded);
+ }
+}
diff --git a/comm/mailnews/mime/test/unit/test_handlerRegistration.js b/comm/mailnews/mime/test/unit/test_handlerRegistration.js
new file mode 100644
index 0000000000..5a2aef8799
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_handlerRegistration.js
@@ -0,0 +1,64 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+var { EnigmailVerify } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/mimeVerify.jsm"
+);
+
+/**
+ * Tests switching content-type handlers on demand.
+ */
+add_task(function () {
+ const CONTRACT_ID = "@mozilla.org/mimecth;1?type=multipart/signed";
+ const INTERFACE = Ci.nsIMimeContentTypeHandler;
+
+ Assert.ok(
+ !Components.manager.isContractIDRegistered(CONTRACT_ID),
+ "no factory is registered initially"
+ );
+
+ EnigmailVerify.registerPGPMimeHandler();
+ Assert.equal(
+ Cc[CONTRACT_ID].number,
+ EnigmailVerify.pgpMimeFactory.classID.number,
+ "pgpMimeFactory is the registered factory"
+ );
+ Assert.ok(
+ Cc[CONTRACT_ID].createInstance(INTERFACE),
+ "pgpMimeFactory successfully created an instance"
+ );
+
+ EnigmailVerify.unregisterPGPMimeHandler();
+ Assert.ok(
+ !Components.manager.isContractIDRegistered(CONTRACT_ID),
+ "pgpMimeFactory has been unregistered"
+ );
+ Assert.throws(
+ () => Cc[CONTRACT_ID].createInstance(INTERFACE),
+ /NS_ERROR_XPC_CI_RETURNED_FAILURE/,
+ "exception correctly thrown"
+ );
+
+ EnigmailVerify.registerPGPMimeHandler();
+ Assert.equal(
+ Cc[CONTRACT_ID].number,
+ EnigmailVerify.pgpMimeFactory.classID.number,
+ "pgpMimeFactory is the registered factory"
+ );
+ Assert.ok(
+ Cc[CONTRACT_ID].createInstance(INTERFACE),
+ "pgpMimeFactory successfully created an instance"
+ );
+
+ EnigmailVerify.unregisterPGPMimeHandler();
+ Assert.ok(
+ !Components.manager.isContractIDRegistered(CONTRACT_ID),
+ "pgpMimeFactory has been unregistered"
+ );
+ Assert.throws(
+ () => Cc[CONTRACT_ID].createInstance(INTERFACE),
+ /NS_ERROR_XPC_CI_RETURNED_FAILURE/,
+ "exception correctly thrown"
+ );
+});
diff --git a/comm/mailnews/mime/test/unit/test_hidden_attachments.js b/comm/mailnews/mime/test/unit/test_hidden_attachments.js
new file mode 100644
index 0000000000..1203c9a8ea
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_hidden_attachments.js
@@ -0,0 +1,221 @@
+/* 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/. */
+
+/**
+ * This test creates some messages with attachments of different types and
+ * checks that libmime emits (or doesn't emit) the attachments as appropriate.
+ */
+
+var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+var messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+var messageGenerator = new MessageGenerator();
+var messageInjection = new MessageInjection({ mode: "local" });
+var inbox = messageInjection.getInboxFolder();
+
+add_task(async function test_without_attachment() {
+ await test_message_attachments({});
+});
+
+/* Attachments with Content-Disposition: attachment */
+// inline-able attachment with a name
+add_task(
+ async function test_content_disposition_attachment_inlineable_attachment_with_name() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: "attachment",
+ filename: "ubik.txt",
+ disposition: "attachment",
+ format: "",
+ shouldShow: true,
+ },
+ ],
+ });
+ }
+);
+
+/* Attachments with Content-Disposition: attachment */
+// inline-able attachment with no name
+add_task(
+ async function test_content_disposition_attachment_inlineable_attachment_no_name() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: "attachment",
+ filename: "",
+ disposition: "attachment",
+ format: "",
+ shouldShow: true,
+ },
+ ],
+ });
+ }
+);
+
+/* Attachments with Content-Disposition: attachment */
+// non-inline-able attachment with a name
+add_task(
+ async function test_content_disposition_attachment_non_inlineable_attachment_with_name() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: "attachment",
+ filename: "ubik.ubk",
+ disposition: "attachment",
+ contentType: "application/x-ubik",
+ format: "",
+ shouldShow: true,
+ },
+ ],
+ });
+ }
+);
+
+/* Attachments with Content-Disposition: attachment */
+// non-inline-able attachment with no name
+add_task(
+ async function test_content_disposition_attachment_non_inlineable_attachment_no_name() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: "attachment",
+ filename: "",
+ disposition: "attachment",
+ contentType: "application/x-ubik",
+ format: "",
+ shouldShow: true,
+ },
+ ],
+ });
+ }
+);
+
+/* Attachments with Content-Disposition: inline */
+// inline-able attachment with a name
+add_task(
+ async function test_content_disposition_inline_inlineable_attachment_with_name() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: "attachment",
+ filename: "ubik.txt",
+ disposition: "inline",
+ format: "",
+ shouldShow: true,
+ },
+ ],
+ });
+ }
+);
+
+/* Attachments with Content-Disposition: inline */
+// inline-able attachment with no name
+add_task(
+ async function test_content_disposition_inline_inlineable_attachment_no_name() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: "attachment",
+ filename: "",
+ disposition: "inline",
+ format: "",
+ shouldShow: false,
+ },
+ ],
+ });
+ }
+);
+
+/* Attachments with Content-Disposition: inline */
+// non-inline-able attachment with a name
+add_task(
+ async function test_content_disposition_inline_non_inlineable_attachment_with_name() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: "attachment",
+ filename: "ubik.ubk",
+ disposition: "inline",
+ contentType: "application/x-ubik",
+ format: "",
+ shouldShow: true,
+ },
+ ],
+ });
+ }
+);
+
+/* Attachments with Content-Disposition: inline */
+// non-inline-able attachment with no name
+add_task(
+ async function test_content_disposition_inline_non_inlineable_attachment_no_name() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: "attachment",
+ filename: "",
+ disposition: "inline",
+ contentType: "application/x-ubik",
+ format: "",
+ shouldShow: true,
+ },
+ ],
+ });
+ }
+);
+
+async function test_message_attachments(info) {
+ let synMsg = messageGenerator.makeMessage(info);
+ let synSet = new SyntheticMessageSet([synMsg]);
+ await messageInjection.addSetsToFolders([inbox], [synSet]);
+
+ let msgURI = synSet.getMsgURI(0);
+ let msgService = MailServices.messageServiceFromURI(msgURI);
+
+ let streamListener = new PromiseTestUtils.PromiseStreamListener({
+ onStopRequest(request, status) {
+ request.QueryInterface(Ci.nsIMailChannel);
+ let expectedAttachments = (info.attachments || [])
+ .filter(i => i.shouldShow)
+ .map(i => i.filename);
+ Assert.equal(request.attachments.length, expectedAttachments.length);
+
+ for (let i = 0; i < request.attachments.length; i++) {
+ // If the expected attachment's name is empty, we probably generated a
+ // name like "Part 1.2", so don't bother checking that the names match
+ // (they won't).
+ if (expectedAttachments[i]) {
+ Assert.equal(
+ request.attachments[i].getProperty("displayName"),
+ expectedAttachments[i]
+ );
+ }
+ }
+ },
+ });
+ msgService.streamMessage(
+ msgURI,
+ streamListener,
+ null,
+ null,
+ true, // have them create the converter
+ // additional uri payload, note that "header=" is prepended automatically
+ "filter",
+ false
+ );
+
+ await streamListener.promise;
+}
diff --git a/comm/mailnews/mime/test/unit/test_jsmime_charset.js b/comm/mailnews/mime/test/unit/test_jsmime_charset.js
new file mode 100644
index 0000000000..865c8ae02f
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_jsmime_charset.js
@@ -0,0 +1,43 @@
+/* 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/. */
+
+// This tests that the charset decoding uses nsICharsetDecoder instead of
+// TextDecoder, to get some extra charsets.
+
+const { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm");
+
+var tests = [
+ ["=?UTF-7?Q?+AKM-1?=", "\u00A31"],
+ ["=?UTF-7?Q?+AK?= =?UTF-7?Q?M-1?=", "\u00A31"],
+ ["=?UTF-8?Q?=C2?=", "\uFFFD"], // Replacement character for invalid input.
+ ["=?NotARealCharset?Q?text?=", "=?NotARealCharset?Q?text?="],
+ ["\xC2\xA31", "\u00A31", "ISO-8859-2"],
+ ["\xA31", "\u01411", "ISO-8859-2"],
+ ["\xC21", "\u00C21", "ISO-8859-1"],
+ // "Here comes the text." in Japanese encoded in Shift_JIS, also using Thunderbird's alias cp932.
+ [
+ "=?shift_jis?Q?=82=b1=82=b1=82=c9=96=7b=95=b6=82=aa=82=ab=82=dc=82=b7=81=42?=",
+ "ここに本文がきます。",
+ ],
+ ["=?shift_jis?B?grGCsYLJlnuVtoKqgquC3IK3gUI=?=", "ここに本文がきます。"],
+ [
+ "=?cp932?Q?=82=b1=82=b1=82=c9=96=7b=95=b6=82=aa=82=ab=82=dc=82=b7=81=42?=",
+ "ここに本文がきます。",
+ ],
+ ["=?cp932?B?grGCsYLJlnuVtoKqgquC3IK3gUI=?=", "ここに本文がきます。"],
+];
+
+function run_test() {
+ for (let test of tests) {
+ dump("Testing message " + test[0]);
+ let value = test[0];
+ if (test.length > 2) {
+ value = jsmime.headerparser.convert8BitHeader(value, test[2]);
+ }
+ Assert.equal(
+ jsmime.headerparser.parseStructuredHeader("Subject", value),
+ test[1]
+ );
+ }
+}
diff --git a/comm/mailnews/mime/test/unit/test_message_attachment.js b/comm/mailnews/mime/test/unit/test_message_attachment.js
new file mode 100644
index 0000000000..653a7078af
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_message_attachment.js
@@ -0,0 +1,168 @@
+/* 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/. */
+
+/**
+ * This test verifies that we generate proper attachment filenames.
+ */
+
+var {
+ MessageGenerator,
+ SyntheticMessageSet,
+ SyntheticPartMultiMixed,
+ SyntheticPartLeaf,
+} = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+
+// Create a message generator
+var msgGen = new MessageGenerator();
+var messageInjection = new MessageInjection({ mode: "local" });
+var inbox = messageInjection.getInboxFolder();
+var msgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+);
+
+// The attachments need to have some content or the stream converter won't
+// display them inline. In the case of the email attachment it must have
+// trailing CRLFs or it will fail to parse.
+const TEXT_ATTACHMENT = "inline text attachment";
+const EMAIL_ATTACHMENT = "Subject: fake email\r\n\r\n";
+const HTML_ATTACHMENT = "<html><body></body></html>";
+
+add_setup(function () {
+ Services.prefs.setBoolPref("mail.inline_attachments.text", true);
+});
+
+// Unnamed email attachment.
+add_task(async function test_unnamed_email_attachment() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: TEXT_ATTACHMENT,
+ filename: "test.txt",
+ format: "",
+ },
+ {
+ body: EMAIL_ATTACHMENT,
+ expectedFilename: "ForwardedMessage.eml",
+ contentType: "message/rfc822",
+ },
+ ],
+ });
+});
+
+// Named email attachment.
+add_task(async function test_named_email_attachment() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: TEXT_ATTACHMENT,
+ filename: "test.txt",
+ format: "",
+ },
+ {
+ body: EMAIL_ATTACHMENT,
+ filename: "Attached Message",
+ contentType: "message/rfc822",
+ },
+ ],
+ });
+});
+
+// Escaped html attachment.
+add_task(async function test_foo() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: TEXT_ATTACHMENT,
+ filename: "test.html",
+ format: "",
+ },
+ {
+ body: HTML_ATTACHMENT,
+ filename:
+ "<iframe src=&quote;http://www.example.com&quote></iframe>.htm",
+ expectedFilename:
+ "&lt;iframe src=&amp;quote;http://www.example.com&amp;quote&gt;&lt;/iframe&gt;.htm",
+ contentType: "text/html;",
+ },
+ ],
+ });
+});
+
+// No named email attachment with subject header.
+add_task(async function test_no_named_email_attachment_with_subject_header() {
+ await test_message_attachments({
+ attachments: [
+ {
+ body: "",
+ expectedFilename: "testSubject.eml",
+ },
+ ],
+ bodyPart: new SyntheticPartMultiMixed([
+ new SyntheticPartLeaf("plain body text"),
+ msgGen.makeMessage({
+ subject: "=?UTF-8?B?dGVzdFN1YmplY3Q=?=", // This string is 'testSubject'.
+ charset: "UTF-8",
+ }),
+ ]),
+ });
+});
+
+async function test_message_attachments(info) {
+ let synMsg = msgGen.makeMessage(info);
+ let synSet = new SyntheticMessageSet([synMsg]);
+ await messageInjection.addSetsToFolders([inbox], [synSet]);
+
+ let msgURI = synSet.getMsgURI(0);
+ let msgService = MailServices.messageServiceFromURI(msgURI);
+
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+
+ msgService.streamMessage(
+ msgURI,
+ streamListener,
+ msgWindow,
+ null,
+ true, // Have them create the converter.
+ "header=filter",
+ false
+ );
+
+ let streamedData = await streamListener.promise;
+
+ // Check that the attachments' filenames are as expected. Just use a regex
+ // here because it's simple.
+ let regex1 =
+ /<legend class="moz-mime-attachment-header-name">(.*?)<\/legend>/gi;
+
+ for (let attachment of info.attachments) {
+ let match = regex1.exec(streamedData);
+ Assert.notEqual(match, null);
+ Assert.equal(match[1], attachment.expectedFilename || attachment.filename);
+ }
+ Assert.equal(regex1.exec(streamedData), null);
+
+ // Check the attachments' filenames are listed for printing.
+ let regex2 = /<td class="moz-mime-attachment-file">(.*?)<\/td>/gi;
+
+ for (let attachment of info.attachments) {
+ let match = regex2.exec(streamedData);
+ Assert.notEqual(match, null);
+ Assert.equal(match[1], attachment.expectedFilename || attachment.filename);
+ }
+ Assert.equal(regex2.exec(streamedData), null);
+}
+
+add_task(function endTest() {
+ messageInjection.teardownMessageInjection();
+});
diff --git a/comm/mailnews/mime/test/unit/test_mimeContentType.js b/comm/mailnews/mime/test/unit/test_mimeContentType.js
new file mode 100644
index 0000000000..fb40549f35
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_mimeContentType.js
@@ -0,0 +1,82 @@
+/* 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/. */
+
+function run_test() {
+ const headers = [
+ {
+ header:
+ "Content-Type: text/plain\r\n" +
+ "Content-Disposition: inline\r\n" +
+ "\r\n",
+ result: "text/plain",
+ },
+ {
+ header:
+ "Content-Type:\r\n" +
+ "\tapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet\r\n" +
+ "Content-Transfer-Encoding: base64\r\n" +
+ 'Content-Disposition: attachment; filename="List.xlsx"\r\n' +
+ "\r\n",
+ result:
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ },
+ {
+ header:
+ "Content-Type: \r\n" +
+ " application/vnd.openxmlformats-officedocument.presentationml.presentation;\r\n" +
+ ' name="Presentation.pptx"\r\n' +
+ "\r\n",
+ result:
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation;" +
+ ' name="Presentation.pptx"',
+ },
+ {
+ header:
+ "Content-Type:\r\n" +
+ "text/plain; charset=utf-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "Content-Disposition: inline\r\n" +
+ "\r\n",
+ result: "",
+ },
+ {
+ header: "Content-Type:\r\n\r\n",
+ result: "",
+ },
+ {
+ /* possible crash case for Bug 574961 */
+ header:
+ "Content-Type: \r\n" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " \r\n",
+ result: "",
+ },
+ ];
+
+ let mimeHdr = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(
+ Ci.nsIMimeHeaders
+ );
+
+ for (let i = 0; i < headers.length; i++) {
+ mimeHdr.initialize(headers[i].header);
+ let receivedHeader = mimeHdr.extractHeader("Content-Type", false);
+
+ dump(
+ "\nTesting Content-Type: " +
+ receivedHeader +
+ " == " +
+ headers[i].result +
+ "\n"
+ );
+
+ Assert.equal(receivedHeader, headers[i].result);
+ }
+}
diff --git a/comm/mailnews/mime/test/unit/test_mimeStreaming.js b/comm/mailnews/mime/test/unit/test_mimeStreaming.js
new file mode 100644
index 0000000000..3e82fe51db
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_mimeStreaming.js
@@ -0,0 +1,88 @@
+/* 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/. */
+
+/**
+ * This test iterates over the test files in gTestFiles, and streams
+ * each as a message and makes sure the streaming doesn't assert or crash.
+ */
+const { localAccountUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/LocalAccountUtils.jsm"
+);
+
+var gTestFiles = ["../../../data/bug505221", "../../../data/bug513543"];
+
+var gMessages;
+
+var gMessenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+
+var gUrlListener = {
+ OnStartRunningUrl(aUrl) {},
+ OnStopRunningUrl(aUrl, aExitCode) {
+ do_test_finished();
+ },
+};
+
+localAccountUtils.loadLocalMailAccount();
+
+add_task(async function run_the_test() {
+ do_test_pending();
+ localAccountUtils.inboxFolder.QueryInterface(Ci.nsIMsgLocalMailFolder);
+ for (let fileName of gTestFiles) {
+ localAccountUtils.inboxFolder.addMessage(
+ await IOUtils.readUTF8(do_get_file(fileName).path)
+ );
+ }
+ gMessages = [
+ ...localAccountUtils.inboxFolder.msgDatabase.enumerateMessages(),
+ ];
+ doNextTest();
+});
+
+function streamMsg(msgHdr) {
+ let msgURI = localAccountUtils.inboxFolder.getUriForMsg(msgHdr);
+ let msgService = MailServices.messageServiceFromURI(msgURI);
+ msgService.streamMessage(
+ msgURI,
+ gStreamListener,
+ null,
+ gUrlListener,
+ true, // have them create the converter
+ // additional uri payload, note that "header=" is prepended automatically
+ "filter",
+ true
+ );
+}
+
+var gStreamListener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]),
+ _stream: null,
+ // nsIRequestObserver part
+ onStartRequest(aRequest) {},
+ onStopRequest(aRequest, aStatusCode) {
+ doNextTest();
+ },
+
+ /* okay, our onDataAvailable should actually never be called. the stream
+ converter is actually eating everything except the start and stop
+ notification. */
+ // nsIStreamListener part
+ onDataAvailable(aRequest, aInputStream, aOffset, aCount) {
+ if (this._stream === null) {
+ this._stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ this._stream.init(aInputStream);
+ }
+ this._stream.read(aCount);
+ },
+};
+
+function doNextTest() {
+ if (gMessages.length > 0) {
+ let msgHdr = gMessages.shift();
+ streamMsg(msgHdr);
+ } else {
+ do_test_finished();
+ }
+}
diff --git a/comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser1.js b/comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser1.js
new file mode 100644
index 0000000000..4636136f17
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser1.js
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for nsIMsgHeaderParser functions.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ var checks = [
+ ["", "test@foo.invalid", "test@foo.invalid"],
+ ["Test", "test@foo.invalid", "Test <test@foo.invalid>"],
+ ["Test", '"abc!x.yz"@foo.invalid', 'Test <"abc!x.yz"@foo.invalid>'],
+ ["Test", "test.user@foo.invalid", "Test <test.user@foo.invalid>"],
+ ["Test", "test@[xyz!]", "Test <test@[xyz!]>"],
+ // Based on RFC 2822 A.1.1
+ ["John Doe", "jdoe@machine.example", "John Doe <jdoe@machine.example>"],
+ // Next 2 tests Based on RFC 2822 A.1.2
+ [
+ "Joe Q. Public",
+ "john.q.public@example.com",
+ '"Joe Q. Public" <john.q.public@example.com>',
+ ],
+ [
+ 'Giant; "Big" Box',
+ "sysservices@example.net",
+ '"Giant; \\"Big\\" Box" <sysservices@example.net>',
+ ],
+ ["trailing", "t1@example.com ", "trailing <t1@example.com>"],
+ ["leading", " t2@example.com", "leading <t2@example.com>"],
+ [
+ "leading trailing",
+ " t3@example.com ",
+ "leading trailing <t3@example.com>",
+ ],
+ ["", " t4@example.com ", "t4@example.com"],
+ ];
+
+ // Test - empty strings
+
+ Assert.equal(MailServices.headerParser.makeMimeAddress("", ""), "");
+
+ // Test - makeMimeAddress
+
+ for (let i = 0; i < checks.length; ++i) {
+ Assert.equal(
+ MailServices.headerParser.makeMimeAddress(checks[i][0], checks[i][1]),
+ checks[i][2]
+ );
+ }
+}
diff --git a/comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser2.js b/comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser2.js
new file mode 100644
index 0000000000..5d1df70330
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser2.js
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for nsIMsgHeaderParser functions:
+ * extractHeaderAddressMailboxes
+ * extractFirstName
+ * parseDecodedHeader
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ // In this array, the sub arrays consist of the following elements:
+ // 0: input string (a comma separated list of recipients)
+ // 1: expected output from extractHeaderAddressMailboxes
+ // 2: list of recipient names in the string
+ // 3: first recipient name in the string
+ const checks = [
+ [
+ "abc@foo.invalid",
+ "abc@foo.invalid",
+ "abc@foo.invalid",
+ "abc@foo.invalid",
+ ],
+ ["foo <ghj@foo.invalid>", "ghj@foo.invalid", "foo", "foo"],
+ [
+ "abc@foo.invalid, foo <ghj@foo.invalid>",
+ "abc@foo.invalid, ghj@foo.invalid",
+ "abc@foo.invalid, foo",
+ "abc@foo.invalid",
+ ],
+ ["foo bar <foo@bar.invalid>", "foo@bar.invalid", "foo bar", "foo bar"],
+ [
+ "foo bar <foo@bar.invalid>, abc@foo.invalid, foo <ghj@foo.invalid>",
+ "foo@bar.invalid, abc@foo.invalid, ghj@foo.invalid",
+ "foo bar, abc@foo.invalid, foo",
+ "foo bar",
+ ],
+ // UTF-8 names
+ [
+ "foo\u00D0 bar <foo@bar.invalid>, \u00F6foo <ghj@foo.invalid>",
+ "foo@bar.invalid, ghj@foo.invalid",
+ "foo\u00D0 bar, \u00F6foo",
+ "foo\u00D0 bar",
+ ],
+ // More complicated examples drawn from RFC 2822
+ [
+ '"Joe Q. Public" <john.q.public@example.com>,Test <"abc!x.yz"@foo.invalid>, Test <test@[xyz!]>,"Giant; \\"Big\\" Box" <sysservices@example.net>',
+ 'john.q.public@example.com, "abc!x.yz"@foo.invalid, test@[xyz!], sysservices@example.net',
+ 'Joe Q. Public, Test, Test, Giant; "Big" Box',
+ // extractFirstName returns unquoted names, hence the difference.
+ "Joe Q. Public",
+ ],
+ // Bug 549931
+ [
+ "Undisclosed recipients:;",
+ "", // Mailboxes
+ "", // Address Names
+ "",
+ ], // Address Name
+ ];
+
+ // Test - empty strings
+
+ Assert.equal(MailServices.headerParser.extractHeaderAddressMailboxes(""), "");
+ Assert.equal(MailServices.headerParser.extractFirstName(""), "");
+
+ // Test - extractHeaderAddressMailboxes
+
+ for (let i = 0; i < checks.length; ++i) {
+ Assert.equal(
+ MailServices.headerParser.extractHeaderAddressMailboxes(checks[i][0]),
+ checks[i][1]
+ );
+ let _names = MailServices.headerParser
+ .parseDecodedHeader(checks[i][0])
+ .map(addr => addr.name || addr.email)
+ .join(", ");
+ Assert.equal(_names, checks[i][2]);
+ Assert.equal(
+ MailServices.headerParser.extractFirstName(checks[i][0]),
+ checks[i][3]
+ );
+ }
+}
diff --git a/comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser3.js b/comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser3.js
new file mode 100644
index 0000000000..06be599d93
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser3.js
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for nsIMsgHeaderParser function removeDuplicateAddresses:
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ const checks = [
+ {
+ addrs: "test@foo.invalid",
+ otherAddrs: "",
+ expectedResult: "test@foo.invalid",
+ },
+ {
+ addrs: "foo bar <test@foo.invalid>",
+ otherAddrs: "",
+ expectedResult: "foo bar <test@foo.invalid>",
+ },
+ {
+ addrs: "foo bar <test@foo.invalid>, abc@foo.invalid",
+ otherAddrs: "",
+ expectedResult: "foo bar <test@foo.invalid>, abc@foo.invalid",
+ },
+ {
+ addrs:
+ "foo bar <test@foo.invalid>, abc@foo.invalid, test <test@foo.invalid>",
+ otherAddrs: "",
+ expectedResult: "foo bar <test@foo.invalid>, abc@foo.invalid",
+ },
+ {
+ addrs: "foo bar <test@foo.invalid>",
+ otherAddrs: "abc@foo.invalid",
+ expectedResult: "foo bar <test@foo.invalid>",
+ },
+ {
+ addrs: "foo bar <test@foo.invalid>",
+ otherAddrs: "foo bar <test@foo.invalid>",
+ expectedResult: "",
+ },
+ {
+ addrs: "foo bar <test@foo.invalid>, abc@foo.invalid",
+ otherAddrs: "foo bar <test@foo.invalid>",
+ expectedResult: "abc@foo.invalid",
+ },
+ {
+ addrs: "foo bar <test@foo.invalid>, abc@foo.invalid",
+ otherAddrs: "abc@foo.invalid",
+ expectedResult: "foo bar <test@foo.invalid>",
+ },
+ {
+ addrs:
+ "foo bar <test@foo.invalid>, abc@foo.invalid, test <test@foo.invalid>",
+ otherAddrs: "abc@foo.invalid",
+ expectedResult: "foo bar <test@foo.invalid>",
+ },
+ // UTF-8 names
+ {
+ addrs: "foo\u00D0 bar <foo@bar.invalid>, \u00F6foo <ghj@foo.invalid>",
+ otherAddrs: "",
+ expectedResult:
+ "foo\u00D0 bar <foo@bar.invalid>, \u00F6foo <ghj@foo.invalid>",
+ },
+ {
+ addrs: "foo\u00D0 bar <foo@bar.invalid>, \u00F6foo <ghj@foo.invalid>",
+ otherAddrs: "foo\u00D0 bar <foo@bar.invalid>",
+ expectedResult: "\u00F6foo <ghj@foo.invalid>",
+ },
+ {
+ addrs:
+ "foo\u00D0 bar <foo@bar.invalid>, \u00F6foo <ghj@foo.invalid>, foo\u00D0 bar <foo@bar.invalid>",
+ otherAddrs: "\u00F6foo <ghj@foo.invalid>",
+ expectedResult: "foo\u00D0 bar <foo@bar.invalid>",
+ },
+ // Test email groups
+ {
+ addrs: "A group: foo bar <foo@bar.invalid>, foo <ghj@foo.invalid>;",
+ otherAddrs: "foo <ghj@foo.invalid>",
+ expectedResult: "A group: foo bar <foo@bar.invalid>;",
+ },
+ {
+ addrs: "A group: foo bar <foo@bar.invalid>, foo <ghj@foo.invalid>;",
+ otherAddrs: "foo bar <ghj@foo.invalid>",
+ expectedResult: "A group: foo bar <foo@bar.invalid>;",
+ },
+ {
+ addrs: "A group: foo bar <foo@bar.invalid>;, foo <ghj@foo.invalid>",
+ otherAddrs: "foo <foo@bar.invalid>",
+ expectedResult: "A group: ; , foo <ghj@foo.invalid>",
+ },
+ ];
+
+ // Test - empty strings
+
+ Assert.equal(MailServices.headerParser.removeDuplicateAddresses("", ""), "");
+ Assert.equal(
+ MailServices.headerParser.removeDuplicateAddresses("", "test@foo.invalid"),
+ ""
+ );
+
+ // Test - removeDuplicateAddresses
+
+ for (let i = 0; i < checks.length; ++i) {
+ dump("Test " + i + "\n");
+ Assert.equal(
+ MailServices.headerParser.removeDuplicateAddresses(
+ checks[i].addrs,
+ checks[i].otherAddrs
+ ),
+ checks[i].expectedResult
+ );
+ }
+}
diff --git a/comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser4.js b/comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser4.js
new file mode 100644
index 0000000000..e4573ae37a
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser4.js
@@ -0,0 +1,199 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/**
+ * Test suite for nsIMsgHeaderParser::makeFromDisplayAddress.
+ * This is what is used to parse in the user input from addressing fields.
+ */
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ const checks = [
+ { displayString: "", addresses: [] },
+ {
+ displayString: "test@foo.invalid",
+ addresses: [["", "test@foo.invalid"]],
+ },
+ {
+ displayString: "test@foo.invalid, test2@foo.invalid",
+ addresses: [
+ ["", "test@foo.invalid"],
+ ["", "test2@foo.invalid"],
+ ],
+ },
+ {
+ displayString: "John Doe <test@foo.invalid>",
+ addresses: [["John Doe", "test@foo.invalid"]],
+ },
+ // Trim spaces.
+ {
+ displayString: " John Doe <test@foo.invalid>",
+ addresses: [["John Doe", "test@foo.invalid"]],
+ },
+ // No space before the email address.
+ {
+ displayString: " John Doe<test@foo.invalid>",
+ addresses: [["John Doe", "test@foo.invalid"]],
+ },
+ // Additional text after the email address to be ignored.
+ {
+ displayString: " John Doe<test@foo.invalid> Junior",
+ addresses: [["John Doe", "test@foo.invalid"]],
+ },
+ {
+ displayString: "Doe, John <test@foo.invalid>",
+ addresses: [["Doe, John", "test@foo.invalid"]],
+ },
+ {
+ displayString:
+ "Doe, John <test@foo.invalid>, Bond, James <test2@foo.invalid>",
+ addresses: [
+ ["Doe, John", "test@foo.invalid"],
+ ["Bond, James", "test2@foo.invalid"],
+ ],
+ },
+ // Additional text after the email address to be ignored, multiple addresses.
+ {
+ displayString:
+ "Doe, John <test@foo.invalid>Junior, Bond, James <test2@foo.invalid>007",
+ addresses: [
+ ["Doe, John", "test@foo.invalid"],
+ ["Bond, James", "test2@foo.invalid"],
+ ],
+ },
+ // Multiple commas
+ {
+ displayString:
+ "Doe,, John <test@foo.invalid>,, Bond, James <test2@foo.invalid>, , Gold Finger <goldfinger@example.com> ,, ",
+ addresses: [
+ ["Doe,, John", "test@foo.invalid"],
+ ["Bond, James", "test2@foo.invalid"],
+ ["Gold Finger", "goldfinger@example.com"],
+ ],
+ },
+ // More tests where the user forgot to close the quote or added extra quotes.
+ {
+ displayString: '"Yatter King1 <a@a.a.a>',
+ addresses: [['"Yatter King1', "a@a.a.a"]],
+ },
+ {
+ displayString: 'Yatter King2" <a@a.a.a>',
+ addresses: [['Yatter King2"', "a@a.a.a"]],
+ },
+ {
+ displayString: '"Yatter King3" <a@a.a.a>',
+ addresses: [['"Yatter King3"', "a@a.a.a"]],
+ },
+ {
+ displayString: 'Yatter "XXX" King4 <a@a.a.a>',
+ addresses: [['Yatter "XXX" King4', "a@a.a.a"]],
+ },
+ {
+ displayString: '"Yatter "XXX" King5" <a@a.a.a>',
+ addresses: [['"Yatter "XXX" King5"', "a@a.a.a"]],
+ },
+ {
+ displayString: '"Yatter King6 <a@a.a.a>"',
+ addresses: [["Yatter King6", "a@a.a.a"]],
+ },
+ {
+ displayString: '"Yatter King7 <a@a.a.a>" <b@b.b.b>',
+ addresses: [['"Yatter King7 <a@a.a.a>"', "b@b.b.b"]],
+ },
+ // Handle invalid mailbox separation with semicolons gracefully.
+ {
+ displayString:
+ 'Bart <bart@example.com> ; lisa@example.com; "Homer, J; President" <pres@example.com>, Marge <marge@example.com>; ',
+ addresses: [
+ ["Bart", "bart@example.com"],
+ ["", "lisa@example.com"],
+ ['"Homer, J; President"', "pres@example.com"],
+ ["Marge", "marge@example.com"],
+ ],
+ },
+ // Junk after a bracketed email address to be ignored.
+ {
+ displayString: "<attacker@example.com>friend@example.com",
+ addresses: [["", "attacker@example.com"]],
+ },
+ {
+ displayString:
+ "<attacker2@example.com><friend2@example.com>,foo <attacker3@example.com><friend3@example.com>",
+ addresses: [
+ ["", "attacker2@example.com"],
+ ["foo", "attacker3@example.com"],
+ ],
+ },
+ {
+ displayString:
+ 'jay "bad" ass <name@evil.com> <someone-else@bad.com> <name@evil.commercial.org>',
+ addresses: [['jay "bad" ass', "name@evil.com"]],
+ },
+
+ {
+ displayString:
+ 'me "you" (via foo@example.com) <attacker2@example.com> friend@example.com,',
+ addresses: [['me "you" (via foo@example.com)', "attacker2@example.com"]],
+ },
+
+ // An uncompleted autocomplete...
+ {
+ displayString: "me >> test <joe@examp.com>",
+ addresses: [["me >> test", "joe@examp.com"]],
+ },
+
+ // A mail list.
+ {
+ displayString: "Holmes and Watson <Tenants221B>, foo@example.com",
+ addresses: [
+ ["Holmes and Watson", "Tenants221B"],
+ ["", "foo@example.com"],
+ ],
+ },
+
+ // A mail list with a space in the name.
+ {
+ displayString: 'Watson and Holmes <"Quoted Tenants221B">',
+ addresses: [["Watson and Holmes", '"Quoted Tenants221B"']],
+ },
+
+ // Mail Merge template
+ {
+ displayString: "{{PrimaryEmail}} <>",
+ addresses: [["{{PrimaryEmail}}", ""]],
+ },
+
+ // Quoted heart.
+ {
+ displayString: 'Marge "<3" S <qheart@example.com>',
+ addresses: [['Marge "<3" S', "qheart@example.com"]],
+ },
+
+ // Heart.
+ {
+ displayString: "Maggie <3 S <heart@example.com>",
+ addresses: [["Maggie <3 S", "heart@example.com"]],
+ },
+
+ // Unbalanced quotes.
+ {
+ displayString: 'Homer <3 "B>" "J <unb@example.com>',
+ addresses: [['Homer <3 "B>" "J', "unb@example.com"]],
+ },
+ ];
+
+ // Test - strings
+
+ for (let i = 0; i < checks.length; ++i) {
+ let addrs = MailServices.headerParser.makeFromDisplayAddress(
+ checks[i].displayString
+ );
+ let checkaddrs = checks[i].addresses;
+ Assert.equal(addrs.length, checkaddrs.length, "Number of parsed addresses");
+ for (let j = 0; j < addrs.length; j++) {
+ Assert.equal(addrs[j].name, checkaddrs[j][0], "Parsed name");
+ Assert.equal(addrs[j].email, checkaddrs[j][1], "Parsed email");
+ }
+ }
+}
diff --git a/comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser5.js b/comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser5.js
new file mode 100644
index 0000000000..a118d44641
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_nsIMsgHeaderParser5.js
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for nsIMsgHeaderParser functions:
+ * parseDecodedHeader
+ * parseEncodedHeader
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function equalArrays(arr1, arr2) {
+ Assert.equal(arr1.length, arr2.length);
+ for (let i = 0; i < arr1.length; i++) {
+ Assert.equal(arr1[i].name, arr2[i].name);
+ Assert.equal(arr1[i].email, arr2[i].email);
+ }
+}
+
+function run_test() {
+ // In this array, the sub arrays consist of the following elements:
+ // 0: input string
+ // 1: expected output from parseDecodedHeader
+ // 2: expected output from parseEncodedHeader
+ const checks = [
+ [
+ "abc@foo.invalid",
+ [{ name: "", email: "abc@foo.invalid" }],
+ [{ name: "", email: "abc@foo.invalid" }],
+ ],
+ [
+ "foo <ghj@foo.invalid>",
+ [{ name: "foo", email: "ghj@foo.invalid" }],
+ [{ name: "foo", email: "ghj@foo.invalid" }],
+ ],
+ [
+ "abc@foo.invalid, foo <ghj@foo.invalid>",
+ [
+ { name: "", email: "abc@foo.invalid" },
+ { name: "foo", email: "ghj@foo.invalid" },
+ ],
+ [
+ { name: "", email: "abc@foo.invalid" },
+ { name: "foo", email: "ghj@foo.invalid" },
+ ],
+ ],
+ // UTF-8 names
+ [
+ "foo\u00D0 bar <foo@bar.invalid>, \u00C3\u00B6foo <ghj@foo.invalid>",
+ [
+ { name: "foo\u00D0 bar", email: "foo@bar.invalid" },
+ { name: "\u00C3\u00B6foo", email: "ghj@foo.invalid" },
+ ],
+ [
+ { name: "foo\uFFFD bar", email: "foo@bar.invalid" },
+ { name: "\u00F6foo", email: "ghj@foo.invalid" },
+ ],
+ ],
+ // Bug 961564
+ [
+ "someone <>",
+ [{ name: "someone", email: "" }],
+ [{ name: "someone", email: "" }],
+ ],
+ // Bug 1423432: Encoded strings with null bytes,
+ // in base64 a single null byte can be encoded as AA== to AP==.
+ // parseEncodedHeader will remove the nullbyte.
+ [
+ '"null=?UTF-8?Q?=00?=byte" <nullbyte@example.com>',
+ [{ name: "null=?UTF-8?Q?=00?=byte", email: "nullbyte@example.com" }],
+ [{ name: "nullbyte", email: "nullbyte@example.com" }],
+ ],
+ [
+ '"null=?UTF-8?B?AA==?=byte" <nullbyte@example.com>',
+ [{ name: "null=?UTF-8?B?AA==?=byte", email: "nullbyte@example.com" }],
+ [{ name: "nullbyte", email: "nullbyte@example.com" }],
+ ],
+ ["", [], []],
+ [" \r\n\t", [], []],
+ [
+ // This used to cause memory read overruns.
+ '" "@a a;b',
+ [
+ { name: "", email: '" "@a a' },
+ { name: "b", email: "" },
+ ],
+ [
+ { name: "", email: "@a a" },
+ { name: "b", email: "" },
+ ],
+ ],
+ ];
+
+ for (let check of checks) {
+ equalArrays(
+ MailServices.headerParser.parseDecodedHeader(check[0]),
+ check[1]
+ );
+ equalArrays(
+ MailServices.headerParser.parseEncodedHeader(check[0], "UTF-8"),
+ check[2]
+ );
+ }
+}
diff --git a/comm/mailnews/mime/test/unit/test_openpgp_decrypt.js b/comm/mailnews/mime/test/unit/test_openpgp_decrypt.js
new file mode 100644
index 0000000000..0e5748fd9c
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_openpgp_decrypt.js
@@ -0,0 +1,415 @@
+/* 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/. */
+
+/**
+ * Tests to ensure signed and/or encrypted OpenPGP messages are
+ * processed correctly by mime.
+ */
+
+const { PromiseUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PromiseUtils.sys.mjs"
+);
+const { OpenPGPTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mozmill/OpenPGPTestUtils.jsm"
+);
+const { EnigmailSingletons } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/singletons.jsm"
+);
+const { EnigmailVerify } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/mimeVerify.jsm"
+);
+const { EnigmailConstants } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/constants.jsm"
+);
+const { EnigmailDecryption } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/decryption.jsm"
+);
+
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+
+var messageInjection = new MessageInjection({ mode: "local" });
+let gInbox = messageInjection.getInboxFolder();
+
+const keyDir = "../../../../mail/test/browser/openpgp/data/keys/";
+const browserEMLDir = "../../../../mail/test/browser/openpgp/data/eml/";
+
+const contents = "Sundays are nothing without callaloo.";
+
+/**
+ * This implements some of the methods of Enigmail.hdrView.headerPane so we can
+ * intercept and record the calls to updateSecurityStatus().
+ */
+const headerSink = {
+ expectResults(maxLen) {
+ this._deferred = PromiseUtils.defer();
+ this.expectedCount = maxLen;
+ this.countReceived = 0;
+ this.results = [];
+ EnigmailSingletons.messageReader = this;
+ return this._deferred.promise;
+ },
+ isCurrentMessage() {
+ return true;
+ },
+ isMultipartRelated() {
+ return false;
+ },
+ displaySubPart() {
+ return true;
+ },
+ hasUnauthenticatedParts() {
+ return false;
+ },
+ processDecryptionResult() {},
+ updateSecurityStatus(
+ unusedUriSpec,
+ exitCode,
+ statusFlags,
+ extStatusFlags,
+ keyId,
+ userId,
+ sigDetails,
+ errorMsg,
+ blockSeparation,
+ uri,
+ extraDetails,
+ mimePartNumber
+ ) {
+ if (statusFlags & EnigmailConstants.PGP_MIME_SIGNED) {
+ this.results.push({
+ type: "signed",
+ status: statusFlags,
+ keyId,
+ });
+ } else if (statusFlags & EnigmailConstants.PGP_MIME_ENCRYPTED) {
+ this.results.push({
+ type: "encrypted",
+ status: statusFlags,
+ keyId,
+ });
+ }
+
+ this.countReceived++;
+ this.checkFinished();
+ },
+ modifyMessageHeaders() {},
+
+ checkFinished() {
+ if (this.countReceived == this.expectedCount) {
+ this._deferred.resolve(this.results);
+ }
+ },
+};
+
+/**
+ * @name Test
+ * @property {string} filename - Name of the eml file found in ${browserEMLDir}.
+ * @property {string} contents - Contents to expect in the file.
+ * @property {string} from - The email address the message is from.
+ * @property {string} [keyId] - The key id to expect the message from.
+ * @property {boolean} sig - If true, indicates the message is signed.
+ * @property {boolean} enc - If true, indicates the message is encrypted.
+ * @property {string[]} flags - A list of flags corresponding to those found in
+ * EnigmailConstants that we should expect the processed message to posses.
+ * Prefix a flag with "-" to indicate it should not be present.
+ * @property {boolean} [skip] - If true, the test will be skipped.
+ */
+
+/**
+ * All the tests we are going to run.
+ *
+ * @type Test[]
+ */
+const tests = [
+ {
+ description:
+ "signed, unencrypted message, with key attached, from verified sender",
+ filename:
+ "signed-by-0xfbfcc82a015e7330-to-0xf231550c4f47e38e-unencrypted-with-key.eml",
+ contents,
+ from: "bob@openpgp.example",
+ keyId: OpenPGPTestUtils.BOB_KEY_ID,
+ sig: true,
+ flags: ["GOOD_SIGNATURE", "-DECRYPTION_OKAY"],
+ },
+ {
+ description: "signed, unencrypted message, from verified sender",
+ filename:
+ "signed-by-0xfbfcc82a015e7330-to-0xf231550c4f47e38e-unencrypted.eml",
+ contents,
+ from: "bob@openpgp.example",
+ keyId: OpenPGPTestUtils.BOB_KEY_ID,
+ sig: true,
+ flags: ["GOOD_SIGNATURE", "-DECRYPTION_OKAY"],
+ },
+ {
+ description:
+ "unsigned, encrypted message, with key attached, from verified sender",
+ filename:
+ "unsigned-encrypted-to-0xf231550c4f47e38e-from-0xfbfcc82a015e7330-with-key.eml",
+ contents,
+ from: "bob@openpgp.example",
+ enc: true,
+ flags: ["DECRYPTION_OKAY", "-GOOD_SIGNATURE"],
+ },
+ {
+ description: "unsigned, encrypted message, from verified sender",
+ filename:
+ "unsigned-encrypted-to-0xf231550c4f47e38e-from-0xfbfcc82a015e7330.eml",
+ contents,
+ from: "bob@openpgp.example",
+ enc: true,
+ flags: ["DECRYPTION_OKAY", "-GOOD_SIGNATURE"],
+ },
+ {
+ description:
+ "signed, encrypted message, with key attached from verified sender",
+ filename:
+ "signed-by-0xfbfcc82a015e7330-encrypted-to-0xf231550c4f47e38e-with-key.eml",
+ from: "bob@openpgp.example",
+ keyId: OpenPGPTestUtils.BOB_KEY_ID,
+ contents,
+ enc: true,
+ sig: true,
+ flags: ["DECRYPTION_OKAY", "GOOD_SIGNATURE"],
+ },
+ {
+ description: "signed, encrypted message, from verified sender",
+ filename:
+ "signed-by-0xfbfcc82a015e7330-encrypted-to-0xf231550c4f47e38e.eml",
+ from: "bob@openpgp.example",
+ keyId: OpenPGPTestUtils.BOB_KEY_ID,
+ contents,
+ enc: true,
+ sig: true,
+ flags: ["DECRYPTION_OKAY", "GOOD_SIGNATURE"],
+ },
+ // Sender with no public key registered or accepted.
+ {
+ description:
+ "signed, unencrypted message, with key attached from sender not in database",
+ filename:
+ "signed-by-0x3099ff1238852b9f-to-0xf231550c4f47e38e-unencrypted-with-key.eml",
+ contents,
+ from: "carol@openpgp.example",
+ keyId: OpenPGPTestUtils.CAROL_KEY_ID,
+ sig: true,
+ flags: ["-GOOD_SIGNATURE", "UNCERTAIN_SIGNATURE", "NO_PUBKEY"],
+ },
+ {
+ description: "signed, unencrypted message, from sender not in database",
+ filename:
+ "signed-by-0x3099ff1238852b9f-to-0xf231550c4f47e38e-unencrypted.eml",
+ contents,
+ from: "carol@openpgp.example",
+ keyId: OpenPGPTestUtils.CAROL_KEY_ID,
+ sig: true,
+ flags: ["-GOOD_SIGNATURE", "UNCERTAIN_SIGNATURE", "NO_PUBKEY"],
+ },
+ {
+ description:
+ "unsigned, encrypted message, with key attached, from sender not in database",
+ filename:
+ "unsigned-encrypted-to-0xf231550c4f47e38e-from-0x3099ff1238852b9f-with-key.eml",
+ contents,
+ from: "carol@openpgp.example",
+ enc: true,
+ flags: ["DECRYPTION_OKAY", "-GOOD_SIGNATURE"],
+ },
+ {
+ description: "unsigned, encrypted message, from sender not in database",
+ filename:
+ "unsigned-encrypted-to-0xf231550c4f47e38e-from-0x3099ff1238852b9f.eml",
+ contents,
+ from: "carol@openpgp.example",
+ enc: true,
+ flags: ["DECRYPTION_OKAY", "-GOOD_SIGNATURE"],
+ },
+ {
+ description:
+ "signed, encrypted message, with key attached, from sender not in database",
+ filename:
+ "signed-by-0x3099ff1238852b9f-encrypted-to-0xf231550c4f47e38e-with-key.eml",
+ contents,
+ from: "carol@openpgp.example",
+ keyId: OpenPGPTestUtils.CAROL_KEY_ID,
+ enc: true,
+ sig: true,
+ resultCount: 1,
+ flags: ["-DECRYPTION_FAILED", "-GOOD_SIGNATURE", "UNCERTAIN_SIGNATURE"],
+ },
+ {
+ description: "signed, encrypted message, from sender not in database",
+ filename:
+ "signed-by-0x3099ff1238852b9f-encrypted-to-0xf231550c4f47e38e.eml",
+ contents,
+ from: "carol@openpgp.example",
+ keyId: OpenPGPTestUtils.CAROL_KEY_ID,
+ enc: true,
+ sig: true,
+ resultCount: 1,
+ flags: ["-DECRYPTION_FAILED", "-GOOD_SIGNATURE", "UNCERTAIN_SIGNATURE"],
+ },
+ // Last two characters of signature swapped.
+ {
+ description: "signed message, signature damaged",
+ filename: "bob-to-alice-signed-damaged-signature.eml",
+ from: "bob@openpgp.example",
+ contents,
+ sig: true,
+ flags: ["-GOOD_SIGNATURE", "BAD_SIGNATURE"],
+ },
+];
+
+/**
+ * Initialize OpenPGP, import Alice and Bob's keys, then install the messages
+ * we are going to test.
+ */
+add_setup(async function () {
+ await OpenPGPTestUtils.initOpenPGP();
+
+ await OpenPGPTestUtils.importPrivateKey(
+ null,
+ do_get_file(`${keyDir}alice@openpgp.example-0xf231550c4f47e38e-secret.asc`)
+ );
+
+ await OpenPGPTestUtils.importPublicKey(
+ null,
+ do_get_file(`${keyDir}bob@openpgp.example-0xfbfcc82a015e7330-pub.asc`)
+ );
+
+ for (let test of tests) {
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+
+ MailServices.copy.copyFileMessage(
+ do_get_file(`${browserEMLDir}${test.filename}`),
+ gInbox,
+ null,
+ true,
+ 0,
+ "",
+ promiseCopyListener,
+ null
+ );
+
+ await promiseCopyListener.promise;
+ promiseCopyListener = null;
+ }
+});
+
+/**
+ * This executes a test for each entry in the tests array. We test mostly
+ * that the contents are correct and updateSecurityStatus() repoorts the
+ * status flags the test specifies.
+ */
+add_task(async function testMimeDecryptOpenPGPMessages() {
+ let hdrIndex = 0;
+ for (let test of tests) {
+ if (test.skip) {
+ info(`Skipped test: ${test.description}`);
+ continue;
+ }
+
+ info(`Running test: ${test.description}`);
+
+ let testPrefix = `${test.filename}:`;
+ let expectedResultCount =
+ test.resultCount || (test.enc && test.sig) ? 2 : 1;
+ let hdr = mailTestUtils.getMsgHdrN(gInbox, hdrIndex);
+ let uri = hdr.folder.getUriForMsg(hdr);
+ let sinkPromise = headerSink.expectResults(expectedResultCount);
+
+ // Set the message window so displayStatus() invokes the hooks we are
+ // interested in.
+ EnigmailVerify.lastWindow = {};
+
+ // Stub this function so verifyDetached() can get the correct email.
+ EnigmailDecryption.getFromAddr = () => test.from;
+
+ // Trigger the actual mime work.
+ let conversion = apply_mime_conversion(uri, headerSink);
+
+ await conversion.promise;
+
+ let msgBody = conversion._data;
+
+ if (!test.sig || test.flags.indexOf("GOOD_SIGNATURE")) {
+ Assert.ok(
+ msgBody.includes(test.contents),
+ `${testPrefix} message contents match`
+ );
+ }
+
+ // Check that we're also using the display output.
+ Assert.ok(
+ msgBody.includes("<html>"),
+ `${testPrefix} message displayed as html`
+ );
+ await sinkPromise;
+
+ let idx = 0;
+ let { results } = headerSink;
+
+ Assert.equal(
+ results.length,
+ expectedResultCount,
+ `${testPrefix} updateSecurityStatus() called ${expectedResultCount} time(s)`
+ );
+
+ if (test.enc) {
+ Assert.equal(
+ results[idx].type,
+ "encrypted",
+ `${testPrefix} message recognized as encrypted`
+ );
+
+ if (expectedResultCount > 1) {
+ idx++;
+ }
+ }
+
+ if (test.sig) {
+ Assert.equal(
+ results[idx].type,
+ "signed",
+ `${testPrefix} message recognized as signed`
+ );
+ }
+
+ if (test.keyId) {
+ Assert.equal(
+ results[idx].keyId,
+ test.keyId,
+ `${testPrefix}key ids match`
+ );
+ }
+
+ // Test the expected message flags match the message status.
+ // We combine the signed and encrypted flags via bitwise OR to
+ // test in one place.
+ if (test.flags) {
+ for (let flag of test.flags) {
+ let flags = results.reduce((prev, curr) => prev | curr.status, 0);
+ let negative = flag[0] === "-";
+ flag = negative ? flag.slice(1) : flag;
+
+ if (negative) {
+ Assert.ok(
+ !(flags & EnigmailConstants[flag]),
+ `${testPrefix} status flag "${flag}" not detected`
+ );
+ } else {
+ Assert.ok(
+ flags & EnigmailConstants[flag],
+ `${testPrefix} status flag "${flag}" detected`
+ );
+ }
+ }
+ }
+
+ hdrIndex++;
+ }
+});
diff --git a/comm/mailnews/mime/test/unit/test_parser.js b/comm/mailnews/mime/test/unit/test_parser.js
new file mode 100644
index 0000000000..979e50975c
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_parser.js
@@ -0,0 +1,322 @@
+/* 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/. */
+
+// This file is used to test the mime parser implemented in JS, mostly by means
+// of creating custom emitters and verifying that the methods on that emitter
+// are called in the correct order. This also tests that the various
+// HeaderParser methods are run correctly.
+
+const { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.jsm");
+
+// Utility method to compare objects
+function compare_objects(real, expected) {
+ // real is a Map; convert it into an object for uneval purposes
+ if (typeof real == "object") {
+ var newreal = {};
+ for (let [k, v] of real) {
+ newreal[k] = v;
+ }
+ real = newreal;
+ }
+ var a = uneval(real),
+ b = uneval(expected);
+ // Very long strings don't get printed out fully (unless they're wrong)
+ if ((a.length > 100 || b.length > 100) && a == b) {
+ Assert.ok(a == b);
+ } else {
+ Assert.equal(a, b);
+ }
+}
+
+// Returns and deletes object[field] if present, or undefined if not.
+function extract_field(object, field) {
+ if (field in object) {
+ var result = object[field];
+ delete object[field];
+ return result;
+ }
+ return undefined;
+}
+
+// A file cache for read_file.
+var file_cache = {};
+
+/**
+ * Read a file into a string (all line endings become CRLF).
+ */
+async function read_file(file, start, end) {
+ if (!(file in file_cache)) {
+ var realFile = do_get_file("../../../data/" + file);
+ file_cache[file] = (await IOUtils.readUTF8(realFile.path)).split(
+ /\r\n|[\r\n]/
+ );
+ }
+ var contents = file_cache[file];
+ if (start !== undefined) {
+ contents = contents.slice(start - 1, end - 1);
+ }
+ return contents.join("\r\n");
+}
+
+/**
+ * Helper for body tests.
+ *
+ * Some extra options are listed too:
+ * _split: The contents of the file will be passed in packets split by this
+ * regex. Be sure to include the split delimiter in a group so that they
+ * are included in the output packets!
+ * _eol: The CRLFs in the input file will be replaced with the given line
+ * ending instead.
+ *
+ * @param test The name of test
+ * @param file The name of the file to read (relative to mailnews/data)
+ * @param opts Options for the mime parser, as well as a few extras detailed
+ * above.
+ * @param partspec An array of [partnum, line start, line end] detailing the
+ * expected parts in the body. It will be expected that the
+ * accumulated body part data for partnum would be the contents
+ * of the file from [line start, line end) [1-based lines]
+ */
+async function make_body_test(test, file, opts, partspec) {
+ let results = [];
+ for (let p of partspec) {
+ results.push([p[0], await read_file(file, p[1], p[2])]);
+ }
+
+ let msgcontents = await read_file(file);
+ return [test, msgcontents, opts, results];
+}
+
+async function make_bodydecode_test(test, file, opts, expected) {
+ let msgcontents = await read_file(file);
+ return [test, msgcontents, opts, expected];
+}
+
+// This is the expected part specifier for the multipart-complex1 test file,
+// specified here because it is used in several cases.
+var mpart_complex1 = [
+ ["1", 8, 10],
+ ["2", 14, 16],
+ ["3.1", 22, 24],
+ ["4", 29, 31],
+ ["5", 33, 35],
+];
+
+// Format of tests:
+// entry[0] = name of the test
+// entry[1] = message (a string or an array of packets)
+// entry[2] = options for the MIME parser
+// entry[3] = A checker result:
+// either a {partnum: header object} (to check headers)
+// or a [[partnum body], [partnum body], ...] (to check bodies)
+// (the partnums refer to the expected part numbers of the MIME test)
+// For body tests, unless you're testing decoding, use make_body_test.
+// For decoding tests, use make_bodydecode_test
+var parser_tests = [
+ // Body tests from data
+ // (Note: line numbers are 1-based. Also, to capture trailing EOF, add 2 to
+ // the last line number of the file).
+ make_body_test("Basic body", "basic1", {}, [["", 3, 5]]),
+ make_body_test("Basic multipart", "multipart1", {}, [["1", 10, 12]]),
+ make_body_test("Basic multipart", "multipart2", {}, [["1", 8, 11]]),
+ make_body_test("Complex multipart", "multipart-complex1", {}, mpart_complex1),
+ make_body_test("Truncated multipart", "multipart-complex2", {}, [
+ ["1.1.1.1", 21, 25],
+ ["2", 27, 57],
+ ["3", 60, 62],
+ ]),
+ make_body_test("No LF multipart", "multipartmalt-detach", {}, [
+ ["1", 20, 21],
+ ["2.1", 27, 38],
+ ["2.2", 42, 43],
+ ["2.3", 47, 48],
+ ]),
+ make_body_test("Raw body", "multipart1", { bodyformat: "raw" }, [
+ ["", 4, 14],
+ ]),
+ make_bodydecode_test(
+ "Base64 decode 1",
+ "base64-1",
+ { bodyformat: "decode" },
+ [
+ [
+ "",
+ "\r\nHello, world! (Again...)\r\n\r\nLet's see how well base64 text" +
+ " is handled. Yay, lots of spaces! There" +
+ "'s even a CRLF at the end and one at the beginning, but the output" +
+ " shouldn't have it.\r\n",
+ ],
+ ]
+ ),
+ make_bodydecode_test(
+ "Base64 decode 2",
+ "base64-2",
+ { bodyformat: "decode" },
+ [
+ [
+ "",
+ "<html><body>This is base64 encoded HTML text, and the tags shouldn" +
+ "'t be stripped.\r\n<b>Bold text is bold!</b></body></html>\r\n",
+ ],
+ ]
+ ),
+ make_body_test("Base64 nodecode", "base64-1", {}, [["", 4, 9]]),
+ make_bodydecode_test(
+ "QP decode",
+ "bug505221",
+ { pruneat: "1", bodyformat: "decode" },
+ [
+ [
+ "1",
+ '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r' +
+ '\n<HTML><HEAD>\r\n<META HTTP-EQUIV="Content-Type" CONTENT="text/h' +
+ 'tml; charset=us-ascii">\r\n\r\n\r\n<META content="MSHTML 6.00.600' +
+ '0.16735" name=GENERATOR></HEAD>\r\n<BODY> bbb\r\n</BODY></HTML>',
+ ],
+ ]
+ ),
+
+ // Comprehensive tests from the torture test
+ make_body_test("Torture regular body", "mime-torture", {}, [
+ ["1", 17, 21],
+ ["2$.1", 58, 75],
+ ["2$.2.1", 83, 97],
+ ["2$.3", 102, 130],
+ ["3$", 155, 7742],
+ ["4", 7747, 8213],
+ ["5", 8218, 8242],
+ ["6$.1.1", 8284, 8301],
+ ["6$.1.2", 8306, 8733],
+ ["6$.2.1", 8742, 9095],
+ ["6$.2.2", 9100, 9354],
+ ["6$.2.3", 9357, 11794],
+ ["6$.2.4", 11797, 12155],
+ ["6$.3", 12161, 12809],
+ ["7$.1", 12844, 12845],
+ ["7$.2", 12852, 13286],
+ ["7$.3", 13288, 13297],
+ ["8$.1", 13331, 13358],
+ ["8$.2", 13364, 13734],
+ ["9$", 13757, 20179],
+ ["10", 20184, 21200],
+ ["11$.1", 21223, 22031],
+ ["11$.2", 22036, 22586],
+ ["12$.1", 22607, 23469],
+ ["12$.2", 23474, 23774],
+ ["12$.3$.1", 23787, 23795],
+ ["12$.3$.2.1", 23803, 23820],
+ ["12$.3$.2.2", 23825, 24633],
+ ["12$.3$.3", 24640, 24836],
+ ["12$.3$.4$", 24848, 25872],
+ ]),
+ make_body_test("Torture pruneat", "mime-torture", { pruneat: "4" }, [
+ ["4", 7747, 8213],
+ ]),
+];
+
+function test_parser(message, opts, results) {
+ var checkingHeaders = !(results instanceof Array);
+ var calls = 0,
+ dataCalls = 0;
+ var fusingParts = extract_field(opts, "_nofuseparts") === undefined;
+ var emitter = {
+ stack: [],
+ startMessage: function emitter_startMsg() {
+ Assert.equal(this.stack.length, 0, "no stack at start");
+ calls++;
+ this.partData = "";
+ },
+ endMessage: function emitter_endMsg() {
+ Assert.equal(this.stack.length, 0, "no stack at end");
+ calls++;
+ },
+ startPart: function emitter_startPart(partNum, headers) {
+ this.stack.push(partNum);
+ if (checkingHeaders) {
+ Assert.ok(partNum in results);
+ compare_objects(headers, results[partNum]);
+ if (fusingParts) {
+ Assert.equal(this.partData, "");
+ }
+ }
+ },
+ deliverPartData: function emitter_partData(partNum, data) {
+ Assert.equal(this.stack[this.stack.length - 1], partNum);
+ try {
+ if (!checkingHeaders) {
+ if (fusingParts) {
+ this.partData += data;
+ } else {
+ Assert.equal(partNum, results[dataCalls][0]);
+ compare_objects(data, results[dataCalls][1]);
+ }
+ }
+ } finally {
+ if (!fusingParts) {
+ dataCalls++;
+ }
+ }
+ },
+ endPart: function emitter_endPart(partNum) {
+ if (this.partData != "") {
+ Assert.equal(partNum, results[dataCalls][0]);
+ compare_objects(this.partData, results[dataCalls][1]);
+ dataCalls++;
+ this.partData = "";
+ }
+ Assert.equal(this.stack.pop(), partNum);
+ },
+ };
+ opts.onerror = function (e) {
+ throw e;
+ };
+ MimeParser.parseSync(message, emitter, opts);
+ Assert.equal(calls, 2);
+ if (!checkingHeaders) {
+ Assert.equal(dataCalls, results.length);
+ }
+}
+
+// Format of tests:
+// entry[0] = header
+// entry[1] = flags
+// entry[2] = result to match
+var header_tests = [
+ // Parameter passing
+ ["multipart/related", MimeParser.HEADER_PARAMETER, ["multipart/related", {}]],
+ ["a ; b=v", MimeParser.HEADER_PARAMETER, ["a", { b: "v" }]],
+ ["a ; b='v'", MimeParser.HEADER_PARAMETER, ["a", { b: "'v'" }]],
+ ['a; b = "v"', MimeParser.HEADER_PARAMETER, ["a", { b: "v" }]],
+ ["a;b=1;b=2", MimeParser.HEADER_PARAMETER, ["a", { b: "1" }]],
+ ["a;b=2;b=1", MimeParser.HEADER_PARAMETER, ["a", { b: "2" }]],
+ ['a;b="a;b"', MimeParser.HEADER_PARAMETER, ["a", { b: "a;b" }]],
+ ['a;b="\\\\"', MimeParser.HEADER_PARAMETER, ["a", { b: "\\" }]],
+ ['a;b="a\\b\\c"', MimeParser.HEADER_PARAMETER, ["a", { b: "abc" }]],
+ ["a;b=1;c=2", MimeParser.HEADER_PARAMETER, ["a", { b: "1", c: "2" }]],
+ ['a;b="a\\', MimeParser.HEADER_PARAMETER, ["a", { b: "a" }]],
+ ["a;b", MimeParser.HEADER_PARAMETER, ["a", {}]],
+ ['a;b=";";c=d', MimeParser.HEADER_PARAMETER, ["a", { b: ";", c: "d" }]],
+];
+
+function test_header(headerValue, flags, expected) {
+ let result = MimeParser.parseHeaderField(headerValue, flags);
+ Assert.equal(result.preSemi, expected[0]);
+ compare_objects(result, expected[1]);
+}
+
+add_task(async function testit() {
+ for (let test of parser_tests) {
+ test = await test;
+ dump("Testing message " + test[0]);
+ if (test[1] instanceof Array) {
+ dump(" using " + test[1].length + " packets");
+ }
+ dump("\n");
+ test_parser(test[1], test[2], test[3]);
+ }
+ for (let test of header_tests) {
+ dump("Testing value ->" + test[0] + "<- with flags " + test[1] + "\n");
+ test_header(test[0], test[1], test[2]);
+ }
+});
diff --git a/comm/mailnews/mime/test/unit/test_rfc822_body.js b/comm/mailnews/mime/test/unit/test_rfc822_body.js
new file mode 100644
index 0000000000..8a64e68d42
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_rfc822_body.js
@@ -0,0 +1,94 @@
+/* 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/. */
+
+/**
+ * This test verifies that we emit a message/rfc822 body part as an attachment
+ * whether or not mail.inline_attachments is true.
+ */
+
+var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+var msgGen = new MessageGenerator();
+var messageInjection = new MessageInjection({ mode: "local" });
+var inbox = messageInjection.getInboxFolder();
+
+add_task(async function test_rfc822_body_display_inline() {
+ Services.prefs.setBoolPref("mail.inline_attachments", true);
+ await help_test_rfc822_body({
+ // a message whose body is itself a message
+ bodyPart: msgGen.makeMessage(),
+ attachmentCount: 1,
+ });
+ await help_test_rfc822_body({
+ // a message whose body is itself a message, and which has an attachment
+ bodyPart: msgGen.makeMessage({
+ attachments: [
+ {
+ body: "I'm an attachment!",
+ filename: "attachment.txt",
+ format: "",
+ },
+ ],
+ }),
+ attachmentCount: 2,
+ });
+});
+
+add_task(async function test_rfc822_body_no_display_inline() {
+ Services.prefs.setBoolPref("mail.inline_attachments", false);
+ await help_test_rfc822_body({
+ // a message whose body is itself a message
+ bodyPart: msgGen.makeMessage(),
+ attachmentCount: 1,
+ });
+ await help_test_rfc822_body({
+ // a message whose body is itself a message, and which has an attachment
+ bodyPart: msgGen.makeMessage({
+ attachments: [
+ {
+ body: "I'm an attachment!",
+ filename: "attachment.txt",
+ format: "",
+ },
+ ],
+ }),
+ attachmentCount: 1,
+ });
+});
+
+async function help_test_rfc822_body(info) {
+ let synMsg = msgGen.makeMessage(info);
+ let synSet = new SyntheticMessageSet([synMsg]);
+ await messageInjection.addSetsToFolders([inbox], [synSet]);
+
+ let msgURI = synSet.getMsgURI(0);
+ let msgService = MailServices.messageServiceFromURI(msgURI);
+
+ let streamListener = new PromiseTestUtils.PromiseStreamListener({
+ onStopRequest(request, statusCode) {
+ request.QueryInterface(Ci.nsIMailChannel);
+ Assert.equal(request.attachments.length, info.attachmentCount);
+ },
+ });
+ msgService.streamMessage(
+ msgURI,
+ streamListener,
+ null,
+ null,
+ true, // have them create the converter
+ // additional uri payload, note that "header=" is prepended automatically
+ "filter",
+ false
+ );
+
+ await streamListener.promise;
+}
diff --git a/comm/mailnews/mime/test/unit/test_smime_decrypt.js b/comm/mailnews/mime/test/unit/test_smime_decrypt.js
new file mode 100644
index 0000000000..815f786224
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_smime_decrypt.js
@@ -0,0 +1,701 @@
+/* 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/. */
+
+/**
+ * Tests to ensure signed and/or encrypted S/MIME messages are
+ * processed correctly, and the signature status is treated as good
+ * or bad as expected.
+ */
+
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { PromiseUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PromiseUtils.sys.mjs"
+);
+var { SmimeUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/smimeUtils.jsm"
+);
+
+add_setup(function () {
+ let messageInjection = new MessageInjection({ mode: "local" });
+ gInbox = messageInjection.getInboxFolder();
+ SmimeUtils.ensureNSS();
+
+ SmimeUtils.loadPEMCertificate(
+ do_get_file(smimeDataDirectory + "TestCA.pem"),
+ Ci.nsIX509Cert.CA_CERT
+ );
+ SmimeUtils.loadCertificateAndKey(
+ do_get_file(smimeDataDirectory + "Alice.p12"),
+ "nss"
+ );
+ SmimeUtils.loadCertificateAndKey(
+ do_get_file(smimeDataDirectory + "Bob.p12"),
+ "nss"
+ );
+ SmimeUtils.loadCertificateAndKey(
+ do_get_file(smimeDataDirectory + "Dave.p12"),
+ "nss"
+ );
+});
+
+add_task(async function verifyTestCertsStillValid() {
+ // implementation of nsIDoneFindCertForEmailCallback
+ var doneFindCertForEmailCallback = {
+ findCertDone(email, cert) {
+ Assert.notEqual(cert, null);
+ if (!cert) {
+ Assert.ok(
+ false,
+ "The S/MIME test certificates are invalid today.\n" +
+ "Please look at the expiration date in file comm/mailnews/test/data/smime/expiration.txt\n" +
+ "If that date is in the past, new certificates need to be generated and committed.\n" +
+ "Follow the instructions in comm/mailnews/test/data/smime/README.md\n" +
+ "If that date is in the future, the test failure is unrelated to expiration and indicates " +
+ "an error in certificate validation."
+ );
+ }
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIDoneFindCertForEmailCallback"]),
+ };
+
+ let composeSecure = Cc[
+ "@mozilla.org/messengercompose/composesecure;1"
+ ].createInstance(Ci.nsIMsgComposeSecure);
+ composeSecure.asyncFindCertByEmailAddr(
+ "Alice@example.com",
+ doneFindCertForEmailCallback
+ );
+});
+
+var gInbox;
+
+var smimeDataDirectory = "../../../data/smime/";
+
+let smimeHeaderSink = {
+ expectResults(maxLen) {
+ // dump("Restarting for next test\n");
+ this._deferred = PromiseUtils.defer();
+ this._expectedEvents = maxLen;
+ this.countReceived = 0;
+ this._results = [];
+ this.haveSignedBad = false;
+ this.haveEncryptionBad = false;
+ this.resultSig = null;
+ this.resultEnc = null;
+ this.resultSigFirst = undefined;
+ return this._deferred.promise;
+ },
+ signedStatus(aNestingLevel, aSignedStatus, aSignerCert) {
+ console.log("signedStatus " + aSignedStatus + " level " + aNestingLevel);
+ // dump("Signed message\n");
+ Assert.equal(aNestingLevel, 1);
+ if (!this.haveSignedBad) {
+ // override with newer allowed
+ this.resultSig = {
+ type: "signed",
+ status: aSignedStatus,
+ certificate: aSignerCert,
+ };
+ if (aSignedStatus != 0) {
+ this.haveSignedBad = true;
+ }
+ if (this.resultSigFirst == undefined) {
+ this.resultSigFirst = true;
+ }
+ }
+ this.countReceived++;
+ this.checkFinished();
+ },
+ encryptionStatus(aNestingLevel, aEncryptedStatus, aRecipientCert) {
+ console.log(
+ "encryptionStatus " + aEncryptedStatus + " level " + aNestingLevel
+ );
+ // dump("Encrypted message\n");
+ Assert.equal(aNestingLevel, 1);
+ if (!this.haveEncryptionBad) {
+ // override with newer allowed
+ this.resultEnc = {
+ type: "encrypted",
+ status: aEncryptedStatus,
+ certificate: aRecipientCert,
+ };
+ if (aEncryptedStatus != 0) {
+ this.haveEncryptionBad = true;
+ }
+ if (this.resultSigFirst == undefined) {
+ this.resultSigFirst = false;
+ }
+ }
+ this.countReceived++;
+ this.checkFinished();
+ },
+ checkFinished() {
+ if (this.countReceived == this._expectedEvents) {
+ if (this.resultSigFirst) {
+ this._results.push(this.resultSig);
+ if (this.resultEnc != null) {
+ this._results.push(this.resultEnc);
+ }
+ } else {
+ this._results.push(this.resultEnc);
+ if (this.resultSig != null) {
+ this._results.push(this.resultSig);
+ }
+ }
+ this._deferred.resolve(this._results);
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIMsgSMIMEHeaderSink"]),
+};
+
+/**
+ * Note on FILENAMES taken from the NSS test suite:
+ * - env: CMS enveloped (encrypted)
+ * - dsig: CMS detached signature (with multipart MIME)
+ * - sig: CMS opaque signature (content embedded inside signature)
+ * - bad: message text does not match signature
+ * - mismatch: embedded content is different
+ *
+ * Control variables used for checking results:
+ * - env: If true, we expect a report to encryptionStatus() that message
+ * is encrypted.
+ * - sig: If true, we expect a report to signedStatus() that message
+ * is signed.
+ * - sig_good: If true, we expect that the reported signature has a
+ * good status.
+ * If false, we expect a report of bad status.
+ * Because of the sequential processing caused by nested
+ * messages, additional calls to signedStatus() might
+ * override an earlier decision.
+ * (An earlier bad status report cannot be overridden by a
+ * later report of a good status.)
+ * - extra: If set to a number > 0, we expect that nested processing of
+ * MIME parts will trigger the given number of additional
+ * status calls.
+ * (default is 0.)
+ * - dave: If true, we expect that the outermost message was done by
+ * Dave's certificate.
+ * (default is false, which means we expect Alice's cert.)
+ */
+
+var gMessages = [
+ {
+ filename: "alice.env.eml",
+ enc: true,
+ sig: false,
+ sig_good: false,
+ check_text: true,
+ },
+ {
+ filename: "alice.dsig.SHA1.multipart.bad.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.dsig.SHA1.multipart.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: false,
+ check_text: true,
+ },
+ {
+ filename: "alice.dsig.SHA1.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ check_text: true,
+ },
+ {
+ filename: "alice.dsig.SHA1.multipart.mismatch-econtent.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.dsig.SHA256.multipart.bad.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.dsig.SHA256.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.future.dsig.SHA256.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.dsig.SHA256.multipart.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.dsig.SHA256.multipart.mismatch-econtent.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.dsig.SHA384.multipart.bad.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.dsig.SHA384.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.dsig.SHA384.multipart.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.dsig.SHA384.multipart.mismatch-econtent.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.dsig.SHA512.multipart.bad.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.dsig.SHA512.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.dsig.SHA512.multipart.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.dsig.SHA512.multipart.mismatch-econtent.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.sig.SHA1.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ check_text: true,
+ },
+ {
+ filename: "alice.sig.SHA1.opaque.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: false,
+ check_text: true,
+ },
+ {
+ filename: "alice.sig.SHA256.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.sig.SHA256.opaque.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.sig.SHA384.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.sig.SHA384.opaque.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.sig.SHA512.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.sig.SHA512.opaque.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: true,
+ },
+
+ // encrypt-then-sign
+ {
+ filename: "alice.env.sig.SHA1.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA1.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.env.sig.SHA256.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA256.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.sig.SHA384.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA384.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.sig.SHA512.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA512.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ extra: 1,
+ },
+
+ // encrypt-then-sign, then sign again
+ {
+ filename: "alice.env.sig.SHA1.opaque.dave.sig.SHA1.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA1.multipart.dave.sig.SHA1.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.sig.SHA256.opaque.dave.sig.SHA256.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA256.multipart.dave.sig.SHA256.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.sig.SHA384.opaque.dave.sig.SHA384.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA384.multipart.dave.sig.SHA384.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.sig.SHA512.opaque.dave.sig.SHA512.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA512.multipart.dave.sig.SHA512.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+
+ // sign, then sign again
+ {
+ filename: "alice.plain.sig.SHA1.opaque.dave.sig.SHA1.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.dsig.SHA1.multipart.dave.sig.SHA1.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.sig.SHA256.opaque.dave.sig.SHA256.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.dsig.SHA256.multipart.dave.sig.SHA256.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.sig.SHA384.opaque.dave.sig.SHA384.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.dsig.SHA384.multipart.dave.sig.SHA384.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.sig.SHA512.opaque.dave.sig.SHA512.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.dsig.SHA512.multipart.dave.sig.SHA512.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+
+ {
+ filename: "alice.plain.sig.SHA1.opaque.dave.dsig.SHA1.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ },
+ {
+ filename: "alice.plain.dsig.SHA1.multipart.dave.dsig.SHA1.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.sig.SHA256.opaque.dave.dsig.SHA256.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename:
+ "alice.plain.dsig.SHA256.multipart.dave.dsig.SHA256.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.sig.SHA384.opaque.dave.dsig.SHA384.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename:
+ "alice.plain.dsig.SHA384.multipart.dave.dsig.SHA384.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.sig.SHA512.opaque.dave.dsig.SHA512.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename:
+ "alice.plain.dsig.SHA512.multipart.dave.dsig.SHA512.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+];
+
+let gCopyWaiter = PromiseUtils.defer();
+
+add_task(async function copy_messages() {
+ for (let msg of gMessages) {
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+
+ MailServices.copy.copyFileMessage(
+ do_get_file(smimeDataDirectory + msg.filename),
+ gInbox,
+ null,
+ true,
+ 0,
+ "",
+ promiseCopyListener,
+ null
+ );
+
+ await promiseCopyListener.promise;
+ promiseCopyListener = null;
+ }
+ gCopyWaiter.resolve();
+});
+
+add_task(async function check_smime_message() {
+ await gCopyWaiter.promise;
+
+ let hdrIndex = 0;
+
+ for (let msg of gMessages) {
+ console.log("checking " + msg.filename);
+
+ let numExpected = 1;
+ if (msg.enc && msg.sig) {
+ numExpected++;
+ }
+
+ let eventsExpected = numExpected;
+ if ("extra" in msg) {
+ eventsExpected += msg.extra;
+ }
+
+ let hdr = mailTestUtils.getMsgHdrN(gInbox, hdrIndex);
+ let uri = hdr.folder.getUriForMsg(hdr);
+ let sinkPromise = smimeHeaderSink.expectResults(eventsExpected);
+
+ let conversion = apply_mime_conversion(uri, smimeHeaderSink);
+ await conversion.promise;
+
+ let contents = conversion._data;
+ // dump("contents: " + contents + "\n");
+
+ if (!msg.sig || msg.sig_good || "check_text" in msg) {
+ let expected = "This is a test message from Alice to Bob.";
+ Assert.ok(contents.includes(expected));
+ }
+ // Check that we're also using the display output.
+ Assert.ok(contents.includes("<html>"));
+
+ await sinkPromise;
+
+ let r = smimeHeaderSink._results;
+ Assert.equal(r.length, numExpected);
+
+ let sigIndex = 0;
+
+ if (msg.enc) {
+ Assert.equal(r[0].type, "encrypted");
+ Assert.equal(r[0].status, 0);
+ Assert.equal(r[0].certificate, null);
+ sigIndex = 1;
+ }
+ if (msg.sig) {
+ Assert.equal(r[sigIndex].type, "signed");
+ let cert = r[sigIndex].certificate;
+ if (msg.sig_good) {
+ Assert.notEqual(cert, null);
+ }
+ if (cert) {
+ if ("dave" in msg) {
+ Assert.equal(cert.emailAddress, "dave@example.com");
+ } else {
+ Assert.equal(cert.emailAddress, "alice@example.com");
+ }
+ }
+ if (msg.sig_good) {
+ Assert.equal(r[sigIndex].status, 0);
+ } else {
+ Assert.notEqual(r[sigIndex].status, 0);
+ }
+ }
+
+ hdrIndex++;
+ }
+});
diff --git a/comm/mailnews/mime/test/unit/test_smime_decrypt_allow_sha1.js b/comm/mailnews/mime/test/unit/test_smime_decrypt_allow_sha1.js
new file mode 100644
index 0000000000..3e2eff43ae
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_smime_decrypt_allow_sha1.js
@@ -0,0 +1,717 @@
+/* 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/. */
+
+/**
+ * This file is mostly a copy of test_smime_decrypt.js
+ * with the difference that pref
+ * mail.smime.accept_insecure_sha1_message_signatures is set to true,
+ * and tests using sha-1 are expected to pass.
+ *
+ * This file must not run in parallel with other s/mime tests.
+ */
+
+/**
+ * Tests to ensure signed and/or encrypted S/MIME messages are
+ * processed correctly, and the signature status is treated as good
+ * or bad as expected.
+ */
+
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { PromiseUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PromiseUtils.sys.mjs"
+);
+var { SmimeUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/smimeUtils.jsm"
+);
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(
+ "mail.smime.accept_insecure_sha1_message_signatures"
+ );
+});
+
+add_setup(function () {
+ Services.prefs.setBoolPref(
+ "mail.smime.accept_insecure_sha1_message_signatures",
+ true
+ );
+
+ let messageInjection = new MessageInjection({ mode: "local" });
+ gInbox = messageInjection.getInboxFolder();
+ SmimeUtils.ensureNSS();
+
+ SmimeUtils.loadPEMCertificate(
+ do_get_file(smimeDataDirectory + "TestCA.pem"),
+ Ci.nsIX509Cert.CA_CERT
+ );
+ SmimeUtils.loadCertificateAndKey(
+ do_get_file(smimeDataDirectory + "Alice.p12"),
+ "nss"
+ );
+ SmimeUtils.loadCertificateAndKey(
+ do_get_file(smimeDataDirectory + "Bob.p12"),
+ "nss"
+ );
+ SmimeUtils.loadCertificateAndKey(
+ do_get_file(smimeDataDirectory + "Dave.p12"),
+ "nss"
+ );
+});
+
+add_task(async function verifyTestCertsStillValid() {
+ // implementation of nsIDoneFindCertForEmailCallback
+ var doneFindCertForEmailCallback = {
+ findCertDone(email, cert) {
+ Assert.notEqual(cert, null);
+ if (!cert) {
+ Assert.ok(
+ false,
+ "The S/MIME test certificates are invalid today.\n" +
+ "Please look at the expiration date in file comm/mailnews/test/data/smime/expiration.txt\n" +
+ "If that date is in the past, new certificates need to be generated and committed.\n" +
+ "Follow the instructions in comm/mailnews/test/data/smime/README.md\n" +
+ "If that date is in the future, the test failure is unrelated to expiration and indicates " +
+ "an error in certificate validation."
+ );
+ }
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIDoneFindCertForEmailCallback"]),
+ };
+
+ let composeSecure = Cc[
+ "@mozilla.org/messengercompose/composesecure;1"
+ ].createInstance(Ci.nsIMsgComposeSecure);
+ composeSecure.asyncFindCertByEmailAddr(
+ "Alice@example.com",
+ doneFindCertForEmailCallback
+ );
+});
+
+var gInbox;
+
+var smimeDataDirectory = "../../../data/smime/";
+
+let smimeHeaderSink = {
+ expectResults(maxLen) {
+ // dump("Restarting for next test\n");
+ this._deferred = PromiseUtils.defer();
+ this._expectedEvents = maxLen;
+ this.countReceived = 0;
+ this._results = [];
+ this.haveSignedBad = false;
+ this.haveEncryptionBad = false;
+ this.resultSig = null;
+ this.resultEnc = null;
+ this.resultSigFirst = undefined;
+ return this._deferred.promise;
+ },
+ signedStatus(aNestingLevel, aSignedStatus, aSignerCert) {
+ console.log("signedStatus " + aSignedStatus + " level " + aNestingLevel);
+ // dump("Signed message\n");
+ Assert.equal(aNestingLevel, 1);
+ if (!this.haveSignedBad) {
+ // override with newer allowed
+ this.resultSig = {
+ type: "signed",
+ status: aSignedStatus,
+ certificate: aSignerCert,
+ };
+ if (aSignedStatus != 0) {
+ this.haveSignedBad = true;
+ }
+ if (this.resultSigFirst == undefined) {
+ this.resultSigFirst = true;
+ }
+ }
+ this.countReceived++;
+ this.checkFinished();
+ },
+ encryptionStatus(aNestingLevel, aEncryptedStatus, aRecipientCert) {
+ console.log(
+ "encryptionStatus " + aEncryptedStatus + " level " + aNestingLevel
+ );
+ // dump("Encrypted message\n");
+ Assert.equal(aNestingLevel, 1);
+ if (!this.haveEncryptionBad) {
+ // override with newer allowed
+ this.resultEnc = {
+ type: "encrypted",
+ status: aEncryptedStatus,
+ certificate: aRecipientCert,
+ };
+ if (aEncryptedStatus != 0) {
+ this.haveEncryptionBad = true;
+ }
+ if (this.resultSigFirst == undefined) {
+ this.resultSigFirst = false;
+ }
+ }
+ this.countReceived++;
+ this.checkFinished();
+ },
+ checkFinished() {
+ if (this.countReceived == this._expectedEvents) {
+ if (this.resultSigFirst) {
+ this._results.push(this.resultSig);
+ if (this.resultEnc != null) {
+ this._results.push(this.resultEnc);
+ }
+ } else {
+ this._results.push(this.resultEnc);
+ if (this.resultSig != null) {
+ this._results.push(this.resultSig);
+ }
+ }
+ this._deferred.resolve(this._results);
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIMsgSMIMEHeaderSink"]),
+};
+
+/**
+ * Note on FILENAMES taken from the NSS test suite:
+ * - env: CMS enveloped (encrypted)
+ * - dsig: CMS detached signature (with multipart MIME)
+ * - sig: CMS opaque signature (content embedded inside signature)
+ * - bad: message text does not match signature
+ * - mismatch: embedded content is different
+ *
+ * Control variables used for checking results:
+ * - env: If true, we expect a report to encryptionStatus() that message
+ * is encrypted.
+ * - sig: If true, we expect a report to signedStatus() that message
+ * is signed.
+ * - sig_good: If true, we expect that the reported signature has a
+ * good status.
+ * If false, we expect a report of bad status.
+ * Because of the sequential processing caused by nested
+ * messages, additional calls to signedStatus() might
+ * override an earlier decision.
+ * (An earlier bad status report cannot be overridden by a
+ * later report of a good status.)
+ * - extra: If set to a number > 0, we expect that nested processing of
+ * MIME parts will trigger the given number of additional
+ * status calls.
+ * (default is 0.)
+ * - dave: If true, we expect that the outermost message was done by
+ * Dave's certificate.
+ * (default is false, which means we expect Alice's cert.)
+ */
+
+var gMessages = [
+ {
+ filename: "alice.env.eml",
+ enc: true,
+ sig: false,
+ sig_good: false,
+ check_text: true,
+ },
+ {
+ filename: "alice.dsig.SHA1.multipart.bad.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.dsig.SHA1.multipart.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: true,
+ check_text: true,
+ },
+ {
+ filename: "alice.dsig.SHA1.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: true,
+ check_text: true,
+ },
+ {
+ filename: "alice.dsig.SHA1.multipart.mismatch-econtent.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.dsig.SHA256.multipart.bad.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.dsig.SHA256.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.dsig.SHA256.multipart.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.dsig.SHA256.multipart.mismatch-econtent.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.dsig.SHA384.multipart.bad.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.dsig.SHA384.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.dsig.SHA384.multipart.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.dsig.SHA384.multipart.mismatch-econtent.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.dsig.SHA512.multipart.bad.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.dsig.SHA512.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.dsig.SHA512.multipart.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.dsig.SHA512.multipart.mismatch-econtent.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ },
+ {
+ filename: "alice.sig.SHA1.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: true,
+ check_text: true,
+ },
+ {
+ filename: "alice.sig.SHA1.opaque.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: true,
+ check_text: true,
+ },
+ {
+ filename: "alice.sig.SHA256.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.sig.SHA256.opaque.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.sig.SHA384.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.sig.SHA384.opaque.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.sig.SHA512.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: true,
+ },
+ {
+ filename: "alice.sig.SHA512.opaque.env.eml",
+ enc: true,
+ sig: true,
+ sig_good: true,
+ },
+
+ // encrypt-then-sign
+ {
+ filename: "alice.env.sig.SHA1.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA1.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.sig.SHA256.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA256.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.sig.SHA384.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA384.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.sig.SHA512.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA512.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ extra: 1,
+ },
+
+ // encrypt-then-sign, then sign again
+ {
+ filename: "alice.env.sig.SHA1.opaque.dave.sig.SHA1.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA1.multipart.dave.sig.SHA1.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.sig.SHA256.opaque.dave.sig.SHA256.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA256.multipart.dave.sig.SHA256.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.sig.SHA384.opaque.dave.sig.SHA384.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA384.multipart.dave.sig.SHA384.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.sig.SHA512.opaque.dave.sig.SHA512.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.env.dsig.SHA512.multipart.dave.sig.SHA512.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+
+ // sign, then sign again
+ {
+ filename: "alice.plain.sig.SHA1.opaque.dave.sig.SHA1.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.dsig.SHA1.multipart.dave.sig.SHA1.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.sig.SHA256.opaque.dave.sig.SHA256.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.dsig.SHA256.multipart.dave.sig.SHA256.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.sig.SHA384.opaque.dave.sig.SHA384.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.dsig.SHA384.multipart.dave.sig.SHA384.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.sig.SHA512.opaque.dave.sig.SHA512.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.dsig.SHA512.multipart.dave.sig.SHA512.opaque.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+
+ {
+ filename: "alice.plain.sig.SHA1.opaque.dave.dsig.SHA1.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.dsig.SHA1.multipart.dave.dsig.SHA1.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.sig.SHA256.opaque.dave.dsig.SHA256.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename:
+ "alice.plain.dsig.SHA256.multipart.dave.dsig.SHA256.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.sig.SHA384.opaque.dave.dsig.SHA384.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename:
+ "alice.plain.dsig.SHA384.multipart.dave.dsig.SHA384.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename: "alice.plain.sig.SHA512.opaque.dave.dsig.SHA512.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+ {
+ filename:
+ "alice.plain.dsig.SHA512.multipart.dave.dsig.SHA512.multipart.eml",
+ enc: false,
+ sig: true,
+ sig_good: false,
+ dave: 1,
+ extra: 1,
+ },
+];
+
+let gCopyWaiter = PromiseUtils.defer();
+
+add_task(async function copy_messages() {
+ for (let msg of gMessages) {
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+
+ MailServices.copy.copyFileMessage(
+ do_get_file(smimeDataDirectory + msg.filename),
+ gInbox,
+ null,
+ true,
+ 0,
+ "",
+ promiseCopyListener,
+ null
+ );
+
+ await promiseCopyListener.promise;
+ promiseCopyListener = null;
+ }
+ gCopyWaiter.resolve();
+});
+
+add_task(async function check_smime_message() {
+ await gCopyWaiter.promise;
+
+ let hdrIndex = 0;
+
+ for (let msg of gMessages) {
+ console.log("checking " + msg.filename);
+
+ let numExpected = 1;
+ if (msg.enc && msg.sig) {
+ numExpected++;
+ }
+
+ let eventsExpected = numExpected;
+ if ("extra" in msg) {
+ eventsExpected += msg.extra;
+ }
+
+ let hdr = mailTestUtils.getMsgHdrN(gInbox, hdrIndex);
+ let uri = hdr.folder.getUriForMsg(hdr);
+ let sinkPromise = smimeHeaderSink.expectResults(eventsExpected);
+
+ let conversion = apply_mime_conversion(uri, smimeHeaderSink);
+ await conversion.promise;
+
+ let contents = conversion._data;
+ // dump("contents: " + contents + "\n");
+
+ if (!msg.sig || msg.sig_good || "check_text" in msg) {
+ let expected = "This is a test message from Alice to Bob.";
+ Assert.ok(contents.includes(expected));
+ }
+ // Check that we're also using the display output.
+ Assert.ok(contents.includes("<html>"));
+
+ await sinkPromise;
+
+ let r = smimeHeaderSink._results;
+ Assert.equal(r.length, numExpected);
+
+ let sigIndex = 0;
+
+ if (msg.enc) {
+ Assert.equal(r[0].type, "encrypted");
+ Assert.equal(r[0].status, 0);
+ Assert.equal(r[0].certificate, null);
+ sigIndex = 1;
+ }
+ if (msg.sig) {
+ Assert.equal(r[sigIndex].type, "signed");
+ let cert = r[sigIndex].certificate;
+ if (msg.sig_good) {
+ Assert.notEqual(cert, null);
+ }
+ if (cert) {
+ if ("dave" in msg) {
+ Assert.equal(cert.emailAddress, "dave@example.com");
+ } else {
+ Assert.equal(cert.emailAddress, "alice@example.com");
+ }
+ }
+ if (msg.sig_good) {
+ Assert.equal(r[sigIndex].status, 0);
+ } else {
+ Assert.notEqual(r[sigIndex].status, 0);
+ }
+ }
+
+ hdrIndex++;
+ }
+});
diff --git a/comm/mailnews/mime/test/unit/test_smime_perm_decrypt.js b/comm/mailnews/mime/test/unit/test_smime_perm_decrypt.js
new file mode 100644
index 0000000000..4ae1374f1a
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_smime_perm_decrypt.js
@@ -0,0 +1,274 @@
+/* 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/. */
+
+/**
+ * Tests to ensure signed and/or encrypted S/MIME messages are
+ * processed correctly, and the signature status is treated as good
+ * or bad as expected.
+ */
+
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { PromiseUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PromiseUtils.sys.mjs"
+);
+var { SmimeUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/smimeUtils.jsm"
+);
+const { EnigmailPersistentCrypto } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/persistentCrypto.jsm"
+);
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+let gCertValidityResult = 0;
+
+/**
+ * @implements nsICertVerificationCallback
+ */
+class CertVerificationResultCallback {
+ constructor(callback) {
+ this.callback = callback;
+ }
+ verifyCertFinished(prErrorCode, verifiedChain, hasEVPolicy) {
+ gCertValidityResult = prErrorCode;
+ this.callback();
+ }
+}
+
+function testCertValidity(cert, date) {
+ let prom = new Promise((resolve, reject) => {
+ const certificateUsageEmailRecipient = 0x0020;
+ let result = new CertVerificationResultCallback(resolve);
+ let flags = Ci.nsIX509CertDB.FLAG_LOCAL_ONLY;
+ const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ certdb.asyncVerifyCertAtTime(
+ cert,
+ certificateUsageEmailRecipient,
+ flags,
+ "Alice@example.com",
+ date,
+ result
+ );
+ });
+ return prom;
+}
+
+add_setup(async function () {
+ let messageInjection = new MessageInjection({ mode: "local" });
+ gInbox = messageInjection.getInboxFolder();
+ SmimeUtils.ensureNSS();
+
+ SmimeUtils.loadPEMCertificate(
+ do_get_file(smimeDataDirectory + "TestCA.pem"),
+ Ci.nsIX509Cert.CA_CERT
+ );
+ SmimeUtils.loadCertificateAndKey(
+ do_get_file(smimeDataDirectory + "Alice.p12"),
+ "nss"
+ );
+ SmimeUtils.loadCertificateAndKey(
+ do_get_file(smimeDataDirectory + "Bob.p12"),
+ "nss"
+ );
+});
+
+var gInbox;
+
+var smimeDataDirectory = "../../../data/smime/";
+
+let smimeHeaderSink = {
+ expectResults(maxLen) {
+ // dump("Restarting for next test\n");
+ this._deferred = PromiseUtils.defer();
+ this._expectedEvents = maxLen;
+ this.countReceived = 0;
+ this._results = [];
+ this.haveSignedBad = false;
+ this.haveEncryptionBad = false;
+ this.resultSig = null;
+ this.resultEnc = null;
+ this.resultSigFirst = undefined;
+ return this._deferred.promise;
+ },
+ signedStatus(aNestingLevel, aSignedStatus, aSignerCert) {
+ console.log("signedStatus " + aSignedStatus + " level " + aNestingLevel);
+ // dump("Signed message\n");
+ Assert.equal(aNestingLevel, 1);
+ if (!this.haveSignedBad) {
+ // override with newer allowed
+ this.resultSig = {
+ type: "signed",
+ status: aSignedStatus,
+ certificate: aSignerCert,
+ };
+ if (aSignedStatus != 0) {
+ this.haveSignedBad = true;
+ }
+ if (this.resultSigFirst == undefined) {
+ this.resultSigFirst = true;
+ }
+ }
+ this.countReceived++;
+ this.checkFinished();
+ },
+ encryptionStatus(aNestingLevel, aEncryptedStatus, aRecipientCert) {
+ console.log(
+ "encryptionStatus " + aEncryptedStatus + " level " + aNestingLevel
+ );
+ // dump("Encrypted message\n");
+ Assert.equal(aNestingLevel, 1);
+ if (!this.haveEncryptionBad) {
+ // override with newer allowed
+ this.resultEnc = {
+ type: "encrypted",
+ status: aEncryptedStatus,
+ certificate: aRecipientCert,
+ };
+ if (aEncryptedStatus != 0) {
+ this.haveEncryptionBad = true;
+ }
+ if (this.resultSigFirst == undefined) {
+ this.resultSigFirst = false;
+ }
+ }
+ this.countReceived++;
+ this.checkFinished();
+ },
+ checkFinished() {
+ if (this.countReceived == this._expectedEvents) {
+ if (this.resultSigFirst) {
+ if (this.resultSig) {
+ this._results.push(this.resultSig);
+ }
+ if (this.resultEnc) {
+ this._results.push(this.resultEnc);
+ }
+ } else {
+ if (this.resultEnc) {
+ this._results.push(this.resultEnc);
+ }
+ if (this.resultSig) {
+ this._results.push(this.resultSig);
+ }
+ }
+ this._deferred.resolve(this._results);
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIMsgSMIMEHeaderSink"]),
+};
+
+/**
+ * Note on FILENAMES taken from the NSS test suite:
+ * - env: CMS enveloped (encrypted)
+ * - dsig: CMS detached signature (with multipart MIME)
+ * - sig: CMS opaque signature (content embedded inside signature)
+ * - bad: message text does not match signature
+ * - mismatch: embedded content is different
+ *
+ * Control variables used for checking results:
+ * - env: If true, we expect a report to encryptionStatus() that message
+ * is encrypted.
+ */
+
+var gMessages = [{ filename: "alice.env.eml", enc: true }];
+
+var gDecFolder;
+
+add_task(async function copy_messages() {
+ for (let msg of gMessages) {
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+
+ MailServices.copy.copyFileMessage(
+ do_get_file(smimeDataDirectory + msg.filename),
+ gInbox,
+ null,
+ true,
+ 0,
+ "",
+ promiseCopyListener,
+ null
+ );
+
+ await promiseCopyListener.promise;
+ promiseCopyListener = null;
+ }
+ gInbox.server.rootFolder.createSubfolder("decrypted", null);
+ gDecFolder = gInbox.server.rootFolder.getChildNamed("decrypted");
+});
+
+add_task(async function check_smime_message() {
+ let hdrIndex = 0;
+
+ for (let msg of gMessages) {
+ console.log("checking " + msg.filename);
+
+ let numExpected = 1;
+
+ let eventsExpected = numExpected;
+
+ let hdr = mailTestUtils.getMsgHdrN(gInbox, hdrIndex);
+ let uri = hdr.folder.getUriForMsg(hdr);
+ let sinkPromise = smimeHeaderSink.expectResults(eventsExpected);
+
+ let conversion = apply_mime_conversion(uri, smimeHeaderSink);
+ await conversion.promise;
+
+ let contents = conversion._data;
+ // dump("contents: " + contents + "\n");
+
+ // Check that we're also using the display output.
+ Assert.ok(contents.includes("<html>"));
+
+ await sinkPromise;
+
+ let r = smimeHeaderSink._results;
+ Assert.equal(r.length, numExpected);
+
+ if (msg.enc) {
+ Assert.equal(r[0].type, "encrypted");
+ Assert.equal(r[0].status, 0);
+ Assert.equal(r[0].certificate, null);
+ }
+
+ await EnigmailPersistentCrypto.cryptMessage(
+ hdr,
+ gDecFolder.URI,
+ false,
+ null
+ );
+
+ eventsExpected = 0;
+
+ hdr = mailTestUtils.getMsgHdrN(gDecFolder, hdrIndex);
+ uri = hdr.folder.getUriForMsg(hdr);
+ sinkPromise = smimeHeaderSink.expectResults(eventsExpected);
+
+ conversion = apply_mime_conversion(uri, smimeHeaderSink);
+ await conversion.promise;
+
+ contents = conversion._data;
+ // dump("contents: " + contents + "\n");
+
+ // Check that we're also using the display output.
+ Assert.ok(contents.includes("<html>"));
+
+ // A message without S/MIME content didn't produce any events,
+ // so we must manually force this check.
+ smimeHeaderSink.checkFinished();
+ await sinkPromise;
+
+ // If the result length is 0, it wasn't decrypted.
+ Assert.equal(smimeHeaderSink._results.length, 0);
+
+ hdrIndex++;
+ }
+});
diff --git a/comm/mailnews/mime/test/unit/test_structured_headers.js b/comm/mailnews/mime/test/unit/test_structured_headers.js
new file mode 100644
index 0000000000..aedc70ac59
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_structured_headers.js
@@ -0,0 +1,249 @@
+/* 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/. */
+
+// This tests the msgIStructuredHeaders and msgIWritableStructuredHeaders
+// interfaces.
+
+// Verify that a specific XPCOM error code is thrown.
+function verifyError(block, errorCode) {
+ let caught = undefined;
+ try {
+ block();
+ } catch (actual) {
+ caught = actual.result;
+ }
+ Assert.equal(caught, errorCode);
+}
+
+var StructuredHeaders = CC(
+ "@mozilla.org/messenger/structuredheaders;1",
+ Ci.msgIWritableStructuredHeaders
+);
+
+add_task(async function check_addressing() {
+ let headers = new StructuredHeaders();
+ headers.setHeader("To", [{ name: "undisclosed-recipients", group: [] }]);
+ Assert.ok(Array.isArray(headers.getHeader("To")));
+ let flat = headers.getAddressingHeader("To", false);
+ Assert.ok(Array.isArray(flat));
+ Assert.equal(flat.length, 0);
+ let full = headers.getAddressingHeader("To", true);
+ Assert.ok(Array.isArray(full));
+ Assert.equal(full.length, 1);
+ Assert.equal(full[0].name, "undisclosed-recipients");
+ Assert.ok(Array.isArray(full[0].group));
+ Assert.equal(headers.getRawHeader("To"), "undisclosed-recipients: ;");
+
+ headers.setHeader("To", [{ name: "\u00D3", email: "test@foo.invalid" }]);
+ Assert.equal(
+ headers.getRawHeader("To"),
+ "=?UTF-8?B?w5M=?= <test@foo.invalid>"
+ );
+ headers.setAddressingHeader("To", [
+ { name: "Comma, Name", email: "test@foo.invalid" },
+ ]);
+ Assert.equal(headers.getRawHeader("To"), '"Comma, Name" <test@foo.invalid>');
+});
+
+add_task(async function check_custom_header() {
+ // Load an extension for our custom header.
+ let url = Services.io.newFileURI(do_get_file("custom_header.js")).spec;
+ let promise = new Promise((resolve, reject) => {
+ function observer(subject, topic, data) {
+ Assert.equal(topic, "xpcom-category-entry-added");
+ Assert.equal(data, "custom-mime-encoder");
+ resolve();
+ Services.obs.removeObserver(observer, "xpcom-category-entry-added");
+ }
+ Services.obs.addObserver(observer, "xpcom-category-entry-added");
+ });
+ Services.catMan.addCategoryEntry(
+ "custom-mime-encoder",
+ "X-Unusual",
+ url,
+ false,
+ true
+ );
+ // The category manager doesn't fire until a later timestep.
+ await promise;
+ let headers = new StructuredHeaders();
+ headers.setRawHeader("X-Unusual", "10");
+ Assert.equal(headers.getHeader("X-Unusual"), 16);
+ headers.setHeader("X-Unusual", 32);
+ Assert.equal(headers.getRawHeader("X-Unusual"), "20");
+});
+
+add_task(async function check_raw() {
+ let headers = new StructuredHeaders();
+ Assert.ok(!headers.hasHeader("Date"));
+ let day = new Date("2000-01-01T00:00:00Z");
+ headers.setHeader("Date", day);
+ Assert.ok(headers.hasHeader("Date"));
+ Assert.ok(headers.hasHeader("date"));
+ Assert.equal(headers.getHeader("Date"), day);
+ Assert.equal(headers.getHeader("date"), day);
+ verifyError(
+ () => headers.getUnstructuredHeader("Date"),
+ Cr.NS_ERROR_ILLEGAL_VALUE
+ );
+ verifyError(
+ () => headers.getAddressingHeader("Date"),
+ Cr.NS_ERROR_ILLEGAL_VALUE
+ );
+ // This is easier than trying to match the actual value for the Date header,
+ // since that depends on the current timezone.
+ Assert.equal(new Date(headers.getRawHeader("Date")).getTime(), day.getTime());
+
+ // Otherwise, the string values should work.
+ headers.setRawHeader("Custom-Date", "1 Jan 2000 00:00:00 +0000");
+ Assert.equal(
+ headers.getRawHeader("Custom-Date"),
+ "1 Jan 2000 00:00:00 +0000"
+ );
+ headers.deleteHeader("Custom-Date");
+
+ headers.setUnstructuredHeader("Content-Description", "A description!");
+ Assert.equal(headers.getHeader("Content-Description"), "A description!");
+ Assert.equal(
+ headers.getUnstructuredHeader("Content-Description"),
+ "A description!"
+ );
+ verifyError(
+ () => headers.getAddressingHeader("Content-Description"),
+ Cr.NS_ERROR_ILLEGAL_VALUE
+ );
+ Assert.equal(headers.getRawHeader("Content-Description"), "A description!");
+
+ Assert.ok(!headers.hasHeader("Subject"));
+ Assert.ok(headers.getUnstructuredHeader("Subject") === null);
+ headers.setRawHeader("Subject", "=?UTF-8?B?56eB44Gv5Lu25ZCN5Y2I5YmN?=");
+ Assert.equal(
+ headers.getHeader("Subject"),
+ "\u79c1\u306f\u4ef6\u540d\u5348\u524d"
+ );
+ Assert.equal(
+ headers.getRawHeader("Subject"),
+ "=?UTF-8?B?56eB44Gv5Lu25ZCN5Y2I5YmN?="
+ );
+
+ // Multiple found headers
+ Assert.equal(headers.getHeader("Not-Found-Anywhere"), undefined);
+ Assert.notEqual(headers.getHeader("Not-Found-Anywhere"), "");
+ Assert.equal(headers.getRawHeader("Not-Found-Anywhere"), undefined);
+ headers.setHeader("Not-Found-Anywhere", 515);
+ Assert.equal(headers.getHeader("Not-Found-Anywhere"), 515);
+ headers.deleteHeader("not-found-anywhere");
+ Assert.equal(headers.getHeader("Not-Found-Anywhere"), undefined);
+
+ // Check the enumeration of header values.
+ headers.setHeader("unabashed-random-header", false);
+ let headerList = [
+ "Date",
+ "Content-Description",
+ "Subject",
+ "Unabashed-Random-Header",
+ ];
+ for (let value of headers.headerNames) {
+ Assert.equal(value.toLowerCase(), headerList.shift().toLowerCase());
+ }
+
+ // Check that copying works
+ let moreHeaders = new StructuredHeaders();
+ moreHeaders.addAllHeaders(headers);
+ for (let value of headers.headerNames) {
+ Assert.equal(moreHeaders.getHeader(value), headers.getHeader(value));
+ }
+ headers.deleteHeader("Date");
+ Assert.ok(moreHeaders.hasHeader("Date"));
+});
+
+add_task(async function check_nsIMimeHeaders() {
+ let headers = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(
+ Ci.nsIMimeHeaders
+ );
+ Assert.ok(headers instanceof Ci.msgIStructuredHeaders);
+ Assert.equal(false, headers instanceof Ci.msgIWritableStructuredHeaders);
+ headers.initialize(
+ mailTestUtils.loadFileToString(do_get_file("../../../data/draft1"))
+ );
+ Assert.equal(headers.getHeader("To").length, 1);
+ Assert.equal(headers.getHeader("To")[0].email, "bugmail@example.org");
+ Assert.equal(headers.getAddressingHeader("To").length, 1);
+ Assert.equal(headers.getHeader("Content-Type").type, "text/html");
+
+ let headerList = [
+ "X-Mozilla-Status",
+ "X-Mozilla-Status2",
+ "X-Mozilla-Keys",
+ "FCC",
+ "BCC",
+ "X-Identity-Key",
+ "Message-ID",
+ "Date",
+ "From",
+ "X-Mozilla-Draft-Info",
+ "User-Agent",
+ "MIME-Version",
+ "To",
+ "Subject",
+ "Content-Type",
+ "Content-Transfer-Encoding",
+ ];
+ for (let value of headers.headerNames) {
+ Assert.equal(value.toLowerCase(), headerList.shift().toLowerCase());
+ }
+});
+
+add_task(async function checkBuildMimeText() {
+ let headers = new StructuredHeaders();
+ headers.setHeader("To", [
+ { name: "François Smith", email: "user@☃.invalid" },
+ ]);
+ headers.setHeader("From", [{ name: "John Doe", email: "jdoe@test.invalid" }]);
+ headers.setHeader(
+ "Subject",
+ "A subject that spans a distance quite in " +
+ "excess of 80 characters so as to force an intermediary CRLF"
+ );
+ headers.setHeader(
+ "User-Agent",
+ "Mozilla/5.0 (X11; Linux x86_64; rv:40.0) Gecko/20100101 Thunderbird/40.0a1"
+ );
+ let mimeText =
+ "To: =?UTF-8?Q?Fran=C3=A7ois_Smith?= <user@☃.invalid>\r\n" +
+ "From: John Doe <jdoe@test.invalid>\r\n" +
+ "Subject: A subject that spans a distance quite in excess of 80 characters so\r\n" +
+ " as to force an intermediary CRLF\r\n" +
+ "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:40.0) Gecko/20100101\r\n" +
+ " Thunderbird/40.0a1\r\n";
+ Assert.equal(headers.buildMimeText(), mimeText);
+
+ // Check the version used for the nsIMimeHeaders implementation. This requires
+ // initializing with a UTF-8 version.
+ let utf8Text = mimeText.replace("☃", "\xe2\x98\x83");
+ let mimeHeaders = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(
+ Ci.nsIMimeHeaders
+ );
+ mimeHeaders.initialize(utf8Text);
+ Assert.equal(mimeHeaders.getHeader("To")[0].email, "user@☃.invalid");
+ Assert.equal(mimeHeaders.buildMimeText(), mimeText);
+ Assert.equal(mimeHeaders.allHeaders, utf8Text);
+
+ // Check date header sanitization
+ headers = new StructuredHeaders();
+ headers.setHeader("Date", new Date("Fri, 6 Mar 2020 00:12:34 +0100"));
+ mimeText = "Date: Thu, 5 Mar 2020 23:12:00 +0000\r\n";
+ Assert.equal(headers.buildMimeText(true), mimeText);
+});
+
+/**
+ * Test that very long message id can be encoded without error.
+ */
+add_task(async function test_longMessageId() {
+ let msgId =
+ "<loqrvrxAUJXbUjUpqbrOJ8nHnJ49hmTREaUhehHZQv0AELQUM7ym6MUklPkt13aw4UD81bYIwO91pQL2OaeKMYVYD5hvZiRT2lSUmGtJkthgb3p5-y03p9bkxbnixgary7va1z0rv6hmd0yy69dm9exwga43h5k6266uwwchtjuxail7ipjhu6307yuft5bm186nu9vejf2joegwtq309cz9m-o3gwPZsvyB4qDpaAkxaj8iyh4OHc0kJsbQPQG8c5z6l3mmtwJuFHC4PxJnzAx9TyQzfnxhiXetQqFaNfvjNYetmNGMd4oq-sihw-d26z-bmdkvy47cloy2vwrnEYPKxtmjXtsmyFJGNxL7d1CeFIAOloSFAwccA6Onq6zPC9lfwWcAOFFje5XqkGVK2XNsUsFao5PR51WsOZStvoCzkqPuWB5PpJ791D9gzPXvGVa45ahuwgpmr1v8g1h5dalaytuxtpettthl506s7l4odqnkhufkvqkja56ulbd4ukgpbd88o3msjz3qk906pbfq6cahdecxoidplpbtsm-673718934717750999799265953521388769563044829819888815300763892678635939321303281062602679958225188.n050jeqcu1blxrm38i58q9dsws108c2m3xcc1tfmlgx8ya2wjyvzxyikgaaed3q6r@ZGCDPKIGJZGEPNVFFMXMTCMUFOPRMBFLIIPXSXECXKGNXBSDNPPHRBCXQRPCTUOCDDZVBEXYODLMFEQTUGBMHDJYUYus-575687677-2.673718934717750999799265953521388769563044829819888815300763892678635939321303281062602679958225188.invalid>";
+ let headers = new StructuredHeaders();
+ headers.setHeader("Message-ID", msgId);
+ Assert.equal(headers.getRawHeader("message-id"), msgId);
+});
diff --git a/comm/mailnews/mime/test/unit/test_text_attachment.js b/comm/mailnews/mime/test/unit/test_text_attachment.js
new file mode 100644
index 0000000000..bea9d5c31a
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/test_text_attachment.js
@@ -0,0 +1,89 @@
+/* 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/. */
+
+/**
+ * This test verifies that we don't display text attachments inline
+ * when mail.inline_attachments is false.
+ */
+
+var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+const TEXT_ATTACHMENT = "inline text attachment";
+
+var messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+var msgGen = new MessageGenerator();
+var inbox;
+var messageInjection = new MessageInjection({ mode: "local" });
+var msgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+);
+
+add_setup(function () {
+ inbox = messageInjection.getInboxFolder();
+});
+
+add_task(async function test_message_attachments_no_inline() {
+ Services.prefs.setBoolPref("mail.inline_attachments", false);
+ Services.prefs.setBoolPref("mail.inline_attachments.text", true);
+ await test_message_attachments({
+ // text attachment
+ attachments: [
+ {
+ body: TEXT_ATTACHMENT,
+ filename: "test.txt",
+ format: "",
+ },
+ ],
+ });
+});
+
+add_task(async function test_message_attachments_no_inline_text() {
+ Services.prefs.setBoolPref("mail.inline_attachments", true);
+ Services.prefs.setBoolPref("mail.inline_attachments.text", false);
+ await PromiseTestUtils.promiseDelay(100);
+ await test_message_attachments({
+ // text attachment
+ attachments: [
+ {
+ body: TEXT_ATTACHMENT,
+ filename: "test.txt",
+ format: "",
+ },
+ ],
+ });
+});
+
+async function test_message_attachments(info) {
+ let synMsg = msgGen.makeMessage(info);
+ let synSet = new SyntheticMessageSet([synMsg]);
+ await messageInjection.addSetsToFolders([inbox], [synSet]);
+
+ let msgURI = synSet.getMsgURI(0);
+ let msgService = MailServices.messageServiceFromURI(msgURI);
+
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+
+ msgService.streamMessage(
+ msgURI,
+ streamListener,
+ msgWindow,
+ null,
+ true, // have them create the converter
+ // additional uri payload, note that "header=" is prepended automatically
+ "filter",
+ false
+ );
+
+ let data = await streamListener.promise;
+ // check that text attachment contents didn't end up inline.
+ Assert.ok(!data.includes(TEXT_ATTACHMENT));
+}
diff --git a/comm/mailnews/mime/test/unit/xpcshell.ini b/comm/mailnews/mime/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..4e1e665f91
--- /dev/null
+++ b/comm/mailnews/mime/test/unit/xpcshell.ini
@@ -0,0 +1,35 @@
+[DEFAULT]
+head = head_mime.js
+tail =
+support-files =
+ custom_header.js
+ ../../../../mail/test/browser/openpgp/data/keys/*
+ ../../../../mail/test/browser/openpgp/data/eml/*
+
+[test_EncodeMimePartIIStr_UTF8.js]
+[test_alternate_p7m_handling.js]
+[test_attachment_size.js]
+[test_badContentType.js]
+[test_bug493544.js]
+[test_handlerRegistration.js]
+[test_hidden_attachments.js]
+[test_jsmime_charset.js]
+[test_message_attachment.js]
+[test_mimeContentType.js]
+[test_mimeStreaming.js]
+[test_nsIMsgHeaderParser1.js]
+[test_nsIMsgHeaderParser2.js]
+[test_nsIMsgHeaderParser3.js]
+[test_nsIMsgHeaderParser4.js]
+[test_nsIMsgHeaderParser5.js]
+[test_openpgp_decrypt.js]
+[test_parser.js]
+[test_rfc822_body.js]
+[test_smime_decrypt.js]
+
+[test_smime_decrypt_allow_sha1.js]
+run-sequentially = Because it set's a pref that must not be active for other tests.
+
+[test_smime_perm_decrypt.js]
+[test_structured_headers.js]
+[test_text_attachment.js]