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/. */
/**
* When an encrypted message is displayed, its URI is remembered by
* gEncryptedURIService (used by the remote-content policy). That entry is
* forgotten again when navigating away, which involves re-deriving the
* message's necko URL. If the message can no longer be resolved (e.g. its
* account has been removed), the cleanup must still remove the remembered entry
* and must not throw and break the display of the next message (bug 2052976).
*
* Two complementary checks: one on the cleanup itself (the entries are gone),
* and one on the user-visible symptom (the next message still displays).
*/
"use strict";
const { be_in_folder, create_folder, get_about_message, select_click_row } =
ChromeUtils.importESModule(
);
const { PromiseTestUtils } = ChromeUtils.importESModule(
);
const { SmimeUtils } = ChromeUtils.importESModule(
);
const { MailServices } = ChromeUtils.importESModule(
);
const gEncryptedURIService = Cc[
"@mozilla.org/messenger-smime/smime-encrypted-uris-service;1"
].getService(Ci.nsIEncryptedSMIMEURIsService);
const MSG_BODY = "This is a test message from Alice to Bob.";
let survivingFolder;
const removableAccounts = [];
let accountCounter = 0;
async function addEnvelopedMessage(folder) {
const copyListener = new PromiseTestUtils.PromiseCopyListener();
MailServices.copy.copyFileMessage(
new FileUtils.File(getTestFilePath("data/alice.env.eml")),
folder,
null,
false,
0,
"",
copyListener,
null
);
await copyListener.promise;
}
function getMsgBodyTxt() {
return get_about_message().getMessagePaneBrowser().contentDocument
.documentElement.textContent;
}
/**
* Create a dedicated account (so it, and thus the message's server, can be
* removed later), put an enveloped message in it, and display it. Returns the
* account plus the URIs that were remembered while displaying it.
*/
async function displayEncryptedMessageInRemovableAccount() {
accountCounter++;
const account = MailServices.accounts.createAccount();
account.incomingServer = MailServices.accounts.createIncomingServer(
"bob",
`example-${accountCounter}.test`,
"pop3"
);
const identity = MailServices.accounts.createIdentity();
identity.email = "bob@example.com";
account.addIdentity(identity);
removableAccounts.push(account);
const rootFolder = account.incomingServer.rootFolder;
rootFolder.QueryInterface(Ci.nsIMsgLocalMailFolder);
const folder = rootFolder.createLocalSubfolder("Encrypted");
await addEnvelopedMessage(folder);
await be_in_folder(folder);
await select_click_row(0);
const aboutMessage = get_about_message();
await TestUtils.waitForCondition(
() => aboutMessage.gMyLastEncryptedURI,
"the encrypted message should be processed"
);
const messageUri = aboutMessage.gMyLastEncryptedURI;
const neckoUri = MailServices.neckoURLForMessageURI(messageUri);
await TestUtils.waitForCondition(
() => gEncryptedURIService.isEncrypted(neckoUri),
"the displayed encrypted message should be registered with the service"
);
return { account, aboutMessage, messageUri, neckoUri };
}
add_setup(async function () {
SmimeUtils.ensureNSS();
SmimeUtils.loadPEMCertificate(
new FileUtils.File(getTestFilePath("data/TestCA.pem")),
Ci.nsIX509Cert.CA_CERT
);
// The message is enveloped to Bob; his key is needed to decrypt it.
SmimeUtils.loadCertificateAndKey(
new FileUtils.File(getTestFilePath("data/Bob.p12")),
"nss"
);
SmimeUtils.loadCertificateAndKey(
new FileUtils.File(getTestFilePath("data/Alice.p12")),
"nss"
);
survivingFolder = await create_folder("EncryptedUriCleanupSurviving");
await addEnvelopedMessage(survivingFolder);
registerCleanupFunction(function () {
for (const account of removableAccounts) {
if (MailServices.accounts.accounts.includes(account)) {
MailServices.accounts.removeAccount(account, true);
}
}
SmimeUtils.removeCertificates(["NSS Test CA (RSA)", "Bob", "Alice"]);
});
});
/**
* Cleanup check: after the account is removed, the remembered message URI and
* necko URL are both forgotten - even though the necko URL can no longer be
* derived from the (now unresolvable) message URI.
*/
add_task(async function test_entriesForgottenWhenAccountRemoved() {
const { account, aboutMessage, messageUri, neckoUri } =
await displayEncryptedMessageInRemovableAccount();
Assert.ok(
gEncryptedURIService.isEncrypted(messageUri),
"the message URI should be registered as encrypted"
);
MailServices.accounts.removeAccount(account, true);
let derivable = true;
try {
MailServices.neckoURLForMessageURI(messageUri);
} catch (ex) {
derivable = false;
}
Assert.ok(
!derivable,
"the necko URL can no longer be derived from the message URI"
);
try {
aboutMessage.forgetEncryptedURI();
} catch (ex) {}
Assert.ok(
!gEncryptedURIService.isEncrypted(neckoUri),
"the necko URL entry must be cleaned up even though the account is gone"
);
Assert.ok(
!gEncryptedURIService.isEncrypted(messageUri),
"the message URI entry must be cleaned up too"
);
});
/**
* Symptom check: navigating to another message after the account is removed must
* still work. Before the fix, the header sink threw while re-deriving the now
* unresolvable necko URL, which broke display of the next message.
*/
add_task(async function test_navigatingAwayWorksAfterAccountRemoved() {
const { account } = await displayEncryptedMessageInRemovableAccount();
MailServices.accounts.removeAccount(account, true);
await be_in_folder(survivingFolder);
await select_click_row(0);
await TestUtils.waitForCondition(
() => getMsgBodyTxt().includes(MSG_BODY),
"the next message must still decrypt and display after the account was removed"
);
});