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,
const { MessageInjection } = ChromeUtils.importESModule(
);
const kDecodedMarker = "Hello World";
// A thousand layers is a tractable xpcshell fixture for proving that nesting is
// bounded while keeping the test safe if it runs against an unfixed build. The
// resource exhaustion risk comes from accepting arbitrary depth.
const kExcessiveDepth = 1000;
const kAcceptedDepth = 50;
const kMultipartTypes = [
"multipart/mixed",
"multipart/alternative",
"multipart/related",
"multipart/digest",
"multipart/parallel",
];
const messageInjection = new MessageInjection({ mode: "local" });
function makeNestedRfc822Message(depth) {
let message = [
"From: inner@example.invalid",
"To: recipient@example.invalid",
"Subject: inner",
"MIME-Version: 1.0",
"Content-Type: text/plain; charset=UTF-8",
"",
kDecodedMarker,
].join("\r\n");
for (let i = 0; i < depth; i++) {
message = [
`From: layer-${i}@example.invalid`,
"To: recipient@example.invalid",
`Subject: layer ${i}`,
"MIME-Version: 1.0",
"Content-Type: message/rfc822",
"",
message,
].join("\r\n");
}
return message;
}
function makeNestedMultipartMessage(depth) {
let part = [
"Content-Type: text/plain; charset=UTF-8",
"",
kDecodedMarker,
].join("\r\n");
for (let i = depth - 1; i >= 0; i--) {
const boundary = `nested-boundary-${i}`;
const contentType = kMultipartTypes[i % kMultipartTypes.length];
part = [
`Content-Type: ${contentType}; boundary="${boundary}"`,
"",
`--${boundary}`,
part,
"",
`--${boundary}--`,
"",
].join("\r\n");
}
return [
"From: outer@example.invalid",
"To: recipient@example.invalid",
"Subject: nested multipart",
"MIME-Version: 1.0",
part,
].join("\r\n");
}
async function streamMessage(message) {
const folder = await messageInjection.makeEmptyFolder();
folder.QueryInterface(Ci.nsIMsgLocalMailFolder);
folder.addMessage(message);
const msgHdr = [...folder.messages][0];
const msgURI = folder.getUriForMsg(msgHdr);
const msgService = MailServices.messageServiceFromURI(msgURI);
const streamListener = new PromiseTestUtils.PromiseStreamListener();
msgService.streamMessage(
msgURI,
streamListener,
null,
null,
true,
"filter",
false
);
return streamListener.promise;
}
async function assertMessageDecodes(message, description) {
const streamedData = await streamMessage(message);
Assert.ok(
streamedData.includes(kDecodedMarker),
`${description} should decode the inner payload`
);
}
async function assertDeepMessageDoesNotDecode(message, description) {
let streamedData = "";
try {
streamedData = await streamMessage(message);
} catch (ex) {
Assert.ok(true, `${description} should stop with a stream error`);
return;
}
Assert.ok(
!streamedData.includes(kDecodedMarker),
`${description} should not render ${kDecodedMarker}`
);
}
add_task(async function test_deeply_nested_rfc822_does_not_decode() {
await assertDeepMessageDoesNotDecode(
makeNestedRfc822Message(kExcessiveDepth),
"deep nested message/rfc822 fixture"
);
});
add_task(async function test_deeply_nested_multipart_does_not_decode() {
await assertDeepMessageDoesNotDecode(
makeNestedMultipartMessage(kExcessiveDepth),
"deep nested multipart fixture"
);
});
add_task(async function test_accepted_depth_rfc822_decodes() {
await assertMessageDecodes(
makeNestedRfc822Message(kAcceptedDepth),
"accepted-depth message/rfc822 fixture"
);
});
add_task(async function test_accepted_depth_multipart_decodes() {
await assertMessageDecodes(
makeNestedMultipartMessage(kAcceptedDepth),
"accepted-depth multipart fixture"
);
});