/* 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 that news: URLs embedded in messages can't be used to make the client
* contact arbitrary news servers (Bug 278176).
*/
"use strict";
var { be_in_folder, create_folder, get_about_message, select_click_row } =
ChromeUtils.importESModule(
"resource://testing-common/mail/FolderDisplayHelpers.sys.mjs"
);
var { MailServices } = ChromeUtils.importESModule(
"resource:///modules/MailServices.sys.mjs"
);
var { MessageGenerator } = ChromeUtils.importESModule(
"resource://testing-common/mailnews/MessageGenerator.sys.mjs"
);
var { NntpUtils } = ChromeUtils.importESModule(
"resource:///modules/NntpUtils.sys.mjs"
);
var { PromiseTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/mailnews/PromiseTestUtils.sys.mjs"
);
var { ServerTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/mailnews/ServerTestUtils.sys.mjs"
);
const GROUP = "test.contentpolicy";
const PNG_BASE64 =
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
const generator = new MessageGenerator();
let newsServer, trackerServer, newsAccount, mailFolder;
let targetMessageId;
/**
* @param {string} host - The news server to fetch the image from.
* @returns {string} A news: URL for the image attached to the target message.
*/
function imageURL(host) {
return `news://${host}:119/${encodeURIComponent(targetMessageId)}?part=1.2&type=image/png&filename=pixel.png`;
}
/**
* @param {string} src - The src of the image.
* @returns {SyntheticMessage}
*/
function makeImageMessage(src) {
return generator.makeMessage({
body: {
body: `
`,
contentType: "text/html",
},
});
}
/**
* @param {NNTPServer} server
* @returns {string[]} All commands the server has received.
*/
function receivedCommands(server) {
return [server.server.playTransaction()].flat().flatMap(t => t.them);
}
/**
* Displays the only message in `folder` and waits for its images to settle.
*
* @param {nsIMsgFolder} folder
* @returns {HTMLImageElement}
*/
async function displayMessage(folder) {
await be_in_folder(folder);
await select_click_row(0);
const doc = get_about_message().getMessagePaneBrowser().contentDocument;
const img = doc.getElementById("testelement");
await TestUtils.waitForCondition(() => img.complete, "image should settle");
return img;
}
add_setup(async function () {
[newsServer, trackerServer] = await ServerTestUtils.createServers([
ServerTestUtils.serverDefs.nntp.plain,
{ type: "nntp", hostname: "tracker.test", port: 119 },
]);
const targetMessage = generator.makeMessage({
attachments: [
{
filename: "pixel.png",
contentType: "image/png",
encoding: "base64",
charset: null,
body: PNG_BASE64 + "\n",
format: null,
},
],
});
targetMessageId = targetMessage.messageId;
newsServer.addGroup(GROUP);
newsServer.addMessages(GROUP, [targetMessage]);
trackerServer.addGroup(GROUP);
trackerServer.addMessages(GROUP, [targetMessage]);
newsAccount = MailServices.accounts.createAccount();
newsAccount.incomingServer = MailServices.accounts.createIncomingServer(
`${newsAccount.key}user`,
"test.test",
"nntp"
);
newsAccount.incomingServer.port = 119;
mailFolder = await create_folder("newsContentPolicy");
registerCleanupFunction(() => {
mailFolder.deleteSelf(null);
MailServices.accounts.removeAccount(newsAccount, false);
const stray = NntpUtils.findServer("tracker.test");
if (stray) {
MailServices.accounts.removeIncomingServer(stray, false);
}
});
});
/**
* Adds a newsgroup holding a single message and downloads its headers.
*
* @param {string} group
* @param {SyntheticMessage} message
* @returns {nsIMsgFolder}
*/
async function setupNewsFolder(group, message) {
newsServer.addGroup(group);
newsServer.addMessages(group, [message]);
const rootFolder = newsAccount.incomingServer.rootFolder;
rootFolder.createSubfolder(group, null);
const folder = rootFolder.getChildNamed(group);
const urlListener = new PromiseTestUtils.PromiseUrlListener();
newsAccount.incomingServer.getNewMessages(
folder,
window.msgWindow,
urlListener
);
await urlListener.promise;
return folder;
}
/**
* A news: image in a mail message must not contact the news server.
*/
add_task(async function testNewsImageInMailMessage() {
mailFolder
.QueryInterface(Ci.nsIMsgLocalMailFolder)
.addMessage(makeImageMessage(imageURL("tracker.test")).toMessageString());
const img = await displayMessage(mailFolder);
Assert.equal(img.naturalWidth, 0, "image should not load");
Assert.deepEqual(
receivedCommands(trackerServer),
[],
"tracker server should not be contacted from a mail message"
);
Assert.equal(
NntpUtils.findServer("tracker.test"),
null,
"no server should be created for tracker.test"
);
});
/**
* A news: image in a news message may load from the same news server.
*/
add_task(async function testSameServerNewsImageInNewsMessage() {
const folder = await setupNewsFolder(
`${GROUP}.same`,
makeImageMessage(imageURL("test.test"))
);
const img = await displayMessage(folder);
Assert.equal(img.naturalWidth, 1, "image should load");
Assert.ok(
receivedCommands(newsServer).includes(`ARTICLE <${targetMessageId}>`),
"same-server news image should be fetched"
);
});
/**
* A news: image in a news message must not contact a different news server.
*/
add_task(async function testOtherServerNewsImageInNewsMessage() {
const folder = await setupNewsFolder(
`${GROUP}.other`,
makeImageMessage(imageURL("tracker.test"))
);
const img = await displayMessage(folder);
Assert.equal(img.naturalWidth, 0, "image should not load");
Assert.deepEqual(
receivedCommands(trackerServer),
[],
"tracker server should not be contacted from a news message"
);
Assert.equal(
NntpUtils.findServer("tracker.test"),
null,
"no server should be created for tracker.test"
);
});