Revision control
Copy as Markdown
Other Tools
/* 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 for OpenPGP unobtrusive signed message composition.
*
* Derived from browser_sendFormat.js and browser_composeSigned.js
*/
"use strict";
const { getMimeTree } = ChromeUtils.importESModule(
);
const { MimeParser } = ChromeUtils.importESModule(
);
const {
assert_selected_and_displayed,
be_in_folder,
empty_folder,
get_about_message,
get_special_folder,
select_click_row,
} = ChromeUtils.importESModule(
);
var { FormatHelper, get_msg_source, open_compose_new_mail, send_later } =
ChromeUtils.importESModule(
);
const { OpenPGPTestUtils } = ChromeUtils.importESModule(
);
const { MailServices } = ChromeUtils.importESModule(
);
let bobAcct;
let bobIdentity;
let gOutbox;
const aboutMessage = get_about_message();
const unobSigPrefName = "mail.openpgp.clear_signature_format";
async function waitCheckEncryptionStateDone(win) {
return BrowserTestUtils.waitForEvent(
win.document,
"encryption-state-checked"
);
}
var sendFormatPreference;
var htmlAsPreference;
var draftsFolder;
var outboxFolder;
/**
* Setup a mail account with a private key and import the public key for the
* receiver.
*/
add_setup(async function () {
Services.prefs.setStringPref(unobSigPrefName, "unobtrusive");
bobAcct = MailServices.accounts.createAccount();
bobAcct.incomingServer = MailServices.accounts.createIncomingServer(
"bob",
"openpgp.example",
"imap"
);
bobIdentity = MailServices.accounts.createIdentity();
bobIdentity.email = "bob@openpgp.example";
bobAcct.addIdentity(bobIdentity);
const [id] = await OpenPGPTestUtils.importPrivateKey(
window,
new FileUtils.File(
getTestFilePath(
"../data/keys/bob@openpgp.example-0xfbfcc82a015e7330-secret.asc"
)
)
);
Assert.ok(id, "private key id received");
bobIdentity.setUnicharAttribute("openpgp_key_id", id.split("0x").join(""));
await OpenPGPTestUtils.importPublicKey(
window,
new FileUtils.File(
getTestFilePath(
"../data/keys/alice@openpgp.example-0xf231550c4f47e38e-pub.asc"
)
)
);
await OpenPGPTestUtils.importPublicKey(
window,
new FileUtils.File(
getTestFilePath(
"../data/keys/carol@example.com-0x3099ff1238852b9f-pub.asc"
)
)
);
gOutbox = await get_special_folder(Ci.nsMsgFolderFlags.Queue);
sendFormatPreference = Services.prefs.getIntPref("mail.default_send_format");
htmlAsPreference = Services.prefs.getIntPref("mailnews.display.html_as");
// Show all parts to a message in the message display.
// This allows us to see if a message contains both a plain text and a HTML
// part.
Services.prefs.setIntPref("mailnews.display.html_as", 4);
draftsFolder = await get_special_folder(Ci.nsMsgFolderFlags.Drafts, true);
outboxFolder = await get_special_folder(Ci.nsMsgFolderFlags.Queue, true);
});
const PLAIN_MESSAGE_BODY = "Plain message body";
const BOLD_MESSAGE_BODY = "Bold message body";
const BOLD_MESSAGE_BODY_AS_PLAIN = `*${BOLD_MESSAGE_BODY}*`;
/**
* Set the default send format and create a new message in the compose window.
*
* @param {nsIMsgCompSendFormat} preference - The default send format to set via
* a preference before opening the window.
* @param {boolean} useBold - Whether to use bold text in the message's body.
*
* @returns {Window} - The opened compose window, pre-filled with a message.
*/
async function newMessage(preference, useBold) {
Services.prefs.setIntPref("mail.default_send_format", preference);
const composeWindow = await open_compose_new_mail();
// Focus should be on "To" field.
EventUtils.sendString("recipient@server.net", composeWindow);
await TestUtils.waitForTick();
EventUtils.synthesizeKey("KEY_Enter", {}, composeWindow);
await TestUtils.waitForTick();
EventUtils.synthesizeKey("KEY_Enter", {}, composeWindow);
await TestUtils.waitForTick();
// Focus should be in the "Subject" field.
EventUtils.sendString(
`${useBold ? "rich" : "plain"} message with preference ${preference}`,
composeWindow
);
await TestUtils.waitForTick();
EventUtils.synthesizeKey("KEY_Enter", {}, composeWindow);
await TestUtils.waitForTick();
// Focus should be in the body.
const formatHelper = new FormatHelper(composeWindow);
if (useBold) {
EventUtils.synthesizeMouseAtCenter(
formatHelper.boldButton,
{},
composeWindow
);
await TestUtils.waitForTick();
await formatHelper.typeInMessage(BOLD_MESSAGE_BODY);
} else {
await formatHelper.typeInMessage(PLAIN_MESSAGE_BODY);
}
return composeWindow;
}
/**
* Verify the actual sent message of a composed message.
*
* @param {Window} composeWindow - The compose window that contains the message
* we want to send.
* @param {object} expectMessage - The expected sent message.
* @param {boolean} expectMessage.isBold - Whether the message uses a bold
* message, rather than the plain message.
* @param {boolean} expectMessage.plain - Whether the message has a plain part.
* @param {boolean} expectMessage.html - Whether the message has a html part.
* @param {string} msg - A message to use in assertions.
*/
async function assertSentMessage(composeWindow, expectMessage, msg) {
const { isBold, plain, html } = expectMessage;
await send_later(composeWindow);
// Open the "sent" message.
await be_in_folder(outboxFolder);
// Should be the last message in the tree.
const clickedMessage = await select_click_row(0);
// The following delay was taken from origin browser_sendFormat.js
// Without it, the code below selects the wrong message.
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(resolve => setTimeout(resolve, 500));
await assert_selected_and_displayed(0);
// signed unobtrusive wraps the payload message in multipart/mixed,
// we must fetch the part that's one level below.
const src = await get_msg_source(clickedMessage);
const tree = getMimeTree(src, false);
const signedPayloadCT = tree.subParts[0].fullContentType;
// Ensure there is RFC 9788 parameter hp=clear
const ctParsed = MimeParser.parseHeaderField(
signedPayloadCT,
MimeParser.HEADER_PARAMETER
);
const hpParameter = ctParsed.get("hp");
Assert.equal(hpParameter, "clear");
// Test that the sent content type is either text/plain, text/html or
// multipart/alternative.
if (plain && html) {
Assert.ok(
signedPayloadCT.startsWith("multipart/alternative"),
`Sent nested contentType "${signedPayloadCT}" should be multipart: ${msg}`
);
} else if (plain) {
Assert.ok(
signedPayloadCT.startsWith("text/plain"),
`Sent nested contentType "${signedPayloadCT}" should be plain text only: ${msg}`
);
} else if (html) {
Assert.ok(
signedPayloadCT.startsWith("text/html"),
`Sent nested contentType "${signedPayloadCT}" should be html only: ${msg}`
);
} else {
throw new Error("Expected message is missing either plain or html parts");
}
// Assert the html and plain text parts are either hidden or shown.
// NOTE: We have set the mailnews.display.html_as preference to show all parts
// of the message, which means it will show both the plain text and html parts
// if both were sent.
const messageBody =
get_about_message().document.getElementById("messagepane").contentDocument
.body;
const plainBody = messageBody.querySelector(".moz-text-flowed");
const htmlBody = messageBody.querySelector(".moz-text-html");
Assert.equal(
!!plain,
!!plainBody,
`Message should ${plain ? "" : "not "}have a Plain part: ${msg}`
);
Assert.equal(
!!html,
!!htmlBody,
`Message should ${html ? "" : "not "}have a HTML part: ${msg}`
);
if (plain) {
Assert.ok(
BrowserTestUtils.isVisible(plainBody),
`Plain part should be visible: ${msg}`
);
Assert.equal(
plainBody.textContent.trim(),
isBold ? BOLD_MESSAGE_BODY_AS_PLAIN : PLAIN_MESSAGE_BODY,
`Plain text content should match: ${msg}`
);
}
if (html) {
Assert.ok(
BrowserTestUtils.isVisible(htmlBody),
`HTML part should be visible: ${msg}`
);
Assert.equal(
htmlBody.textContent.trim(),
isBold ? BOLD_MESSAGE_BODY : PLAIN_MESSAGE_BODY,
`HTML text content should match: ${msg}`
);
}
}
add_task(async function test_preference_send_format() {
// Sending a plain message.
for (const { preference, sendsPlain, sendsHtml } of [
{
preference: Ci.nsIMsgCompSendFormat.Auto,
sendsPlain: true,
sendsHtml: false,
},
{
preference: Ci.nsIMsgCompSendFormat.PlainText,
sendsPlain: true,
sendsHtml: false,
},
{
preference: Ci.nsIMsgCompSendFormat.HTML,
sendsPlain: false,
sendsHtml: true,
},
{
preference: Ci.nsIMsgCompSendFormat.Both,
sendsPlain: true,
sendsHtml: true,
},
]) {
info(`Testing preference ${preference} with a plain message`);
const composeWindow = await newMessage(preference, false);
await OpenPGPTestUtils.toggleMessageSigning(composeWindow);
await OpenPGPTestUtils.toggleMessageKeyAttachment(composeWindow);
await assertSentMessage(
composeWindow,
{ plain: sendsPlain, html: sendsHtml, isBold: false },
`Plain message with preference ${preference}`
);
}
// Sending a bold message.
for (const { preference, sendsPlain, sendsHtml } of [
{
preference: Ci.nsIMsgCompSendFormat.Auto,
sendsPlain: true,
sendsHtml: true,
},
{
preference: Ci.nsIMsgCompSendFormat.PlainText,
sendsPlain: true,
sendsHtml: false,
},
{
preference: Ci.nsIMsgCompSendFormat.HTML,
sendsPlain: false,
sendsHtml: true,
},
{
preference: Ci.nsIMsgCompSendFormat.Both,
sendsPlain: true,
sendsHtml: true,
},
]) {
info(`Testing preference ${preference} with a bold message`);
const composeWindow = await newMessage(preference, true);
await OpenPGPTestUtils.toggleMessageSigning(composeWindow);
await OpenPGPTestUtils.toggleMessageKeyAttachment(composeWindow);
await assertSentMessage(
composeWindow,
{ plain: sendsPlain, html: sendsHtml, isBold: true },
`Bold message with preference ${preference}`
);
}
});
registerCleanupFunction(async function tearDown() {
Services.prefs.clearUserPref("openpgp_key_id");
await OpenPGPTestUtils.removeKeyById("0xfbfcc82a015e7330", true);
MailServices.accounts.removeIncomingServer(bobAcct.incomingServer, true);
MailServices.accounts.removeAccount(bobAcct, true);
Services.prefs.clearUserPref(unobSigPrefName);
Services.prefs.setIntPref("mail.default_send_format", sendFormatPreference);
Services.prefs.setIntPref("mailnews.display.html_as", htmlAsPreference);
await empty_folder(draftsFolder);
await empty_folder(outboxFolder);
});