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
/**
* 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
*
* 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 { OpenPGPTestUtils } = ChromeUtils.importESModule(
);
const { PromiseTestUtils } = ChromeUtils.importESModule(
);
const { MailServices } = ChromeUtils.importESModule(
);
const gEncryptedURIService = Cc[
"@mozilla.org/messenger-smime/smime-encrypted-uris-service;1"
].getService(Ci.nsIEncryptedSMIMEURIsService);
// The decrypted body of the test message.
const MSG_TEXT = "Sundays are nothing without callaloo.";
let aliceKeyId;
let survivingFolder;
const removableAccounts = [];
let accountCounter = 0;
async function addEncryptedMessage(folder) {
const copyListener = new PromiseTestUtils.PromiseCopyListener();
MailServices.copy.copyFileMessage(
new FileUtils.File(
getTestFilePath(
"data/eml/signed-by-0xfbfcc82a015e7330-encrypted-to-0xf231550c4f47e38e.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 encrypted message in it, and display it. Returns the
* account, the overlay's header view, and the URIs remembered while displaying.
*/
async function displayEncryptedMessageInRemovableAccount() {
accountCounter++;
const account = MailServices.accounts.createAccount();
account.incomingServer = MailServices.accounts.createIncomingServer(
"alice",
`openpgp-${accountCounter}.example`,
"pop3"
);
const identity = MailServices.accounts.createIdentity();
identity.email = "alice@openpgp.example";
identity.setUnicharAttribute("openpgp_key_id", aliceKeyId);
account.addIdentity(identity);
removableAccounts.push(account);
const rootFolder = account.incomingServer.rootFolder;
rootFolder.QueryInterface(Ci.nsIMsgLocalMailFolder);
const folder = rootFolder.createLocalSubfolder("Encrypted");
await addEncryptedMessage(folder);
await be_in_folder(folder);
await select_click_row(0);
const hdrView = get_about_message().Enigmail.hdrView;
await TestUtils.waitForCondition(
() => hdrView.lastEncryptedUri,
"the encrypted message should be processed"
);
const messageUri = hdrView.lastEncryptedUri;
const neckoUri = MailServices.neckoURLForMessageURI(messageUri);
await TestUtils.waitForCondition(
() => gEncryptedURIService.isEncrypted(neckoUri),
"the displayed encrypted message should be registered with the service"
);
return { account, hdrView, messageUri, neckoUri };
}
add_setup(async function () {
// Alice's (passphrase-less) private key, so the message decrypts without a
// prompt. The message is encrypted to Alice.
[aliceKeyId] = await OpenPGPTestUtils.importPrivateKey(
window,
new FileUtils.File(
getTestFilePath(
"data/keys/alice@openpgp.example-0xf231550c4f47e38e-secret.asc"
)
)
);
survivingFolder = await create_folder("OpenPGPEncryptedUriCleanupSurviving");
await addEncryptedMessage(survivingFolder);
registerCleanupFunction(async function () {
for (const account of removableAccounts) {
if (MailServices.accounts.accounts.includes(account)) {
MailServices.accounts.removeAccount(account, true);
}
}
await OpenPGPTestUtils.removeKeyById("0xf231550c4f47e38e", true);
});
});
/**
* 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, hdrView, 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 {
hdrView.forgetEncryptedMsgKey();
} 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_TEXT),
"the next message must still decrypt and display after the account was removed"
);
});