/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
const server = createHttpServer({
hosts: ["example.net", "example.com"],
});
server.registerDirectory("/data/", do_get_file("data"));
const pageContent = `
`;
server.registerPathHandler("/", (request, response) => {
response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Content-Type", "text/html");
if (request.queryString) {
response.setHeader(
"Content-Security-Policy",
decodeURIComponent(request.queryString)
);
}
response.write(pageContent);
});
let extensionData = {
manifest: {
permissions: ["webRequest", "webRequestBlocking", "*://example.net/*"],
},
background() {
let csp_value = undefined;
browser.test.onMessage.addListener(function(msg, expectedCount) {
csp_value = msg;
browser.test.sendMessage("csp-set");
});
browser.webRequest.onHeadersReceived.addListener(
e => {
browser.test.log(`onHeadersReceived ${e.requestId} ${e.url}`);
if (csp_value === undefined) {
browser.test.assertTrue(false, "extension called before CSP was set");
}
if (csp_value !== null) {
e.responseHeaders = e.responseHeaders.filter(
i => i.name.toLowerCase() != "content-security-policy"
);
if (csp_value !== "") {
e.responseHeaders.push({
name: "Content-Security-Policy",
value: csp_value,
});
}
}
return { responseHeaders: e.responseHeaders };
},
{ urls: ["*://example.net/*"] },
["blocking", "responseHeaders"]
);
},
};
/**
* Test a combination of Content Security Policies against first/third party images/scripts.
* @param {string} site_csp The CSP to be sent by the site, or null.
* @param {string} ext1_csp The CSP to be sent by the first extension,
* "" to remove the header, or null to not modify it.
* @param {string} ext2_csp The CSP to be sent by the first extension,
* "" to remove the header, or null to not modify it.
* @param {Object} expect Object containing information which resources are expected to be loaded.
* @param {Object} expect.img1_loaded image from a first party origin.
* @param {Object} expect.img3_loaded image from a third party origin.
* @param {Object} expect.script1_loaded script from a first party origin.
* @param {Object} expect.script3_loaded script from a third party origin.
*/
async function test_csp(site_csp, ext1_csp, ext2_csp, expect) {
let extension1 = await ExtensionTestUtils.loadExtension(extensionData);
let extension2 = await ExtensionTestUtils.loadExtension(extensionData);
await extension1.startup();
await extension2.startup();
extension1.sendMessage(ext1_csp);
extension2.sendMessage(ext2_csp);
await extension1.awaitMessage("csp-set");
await extension2.awaitMessage("csp-set");
let csp_value = encodeURIComponent(site_csp || "");
let contentPage = await ExtensionTestUtils.loadContentPage(
`http://example.net/?${csp_value}`
);
let results = await contentPage.spawn(null, async () => {
let img1 = this.content.document.getElementById("img1");
let img3 = this.content.document.getElementById("img3");
return {
img1_loaded: img1.complete && img1.naturalWidth > 0,
img3_loaded: img3.complete && img3.naturalWidth > 0,
// Note: "good" and "bad" are just placeholders; they don't mean anything.
script1_loaded: !!this.content.document.getElementById("good"),
script3_loaded: !!this.content.document.getElementById("bad"),
};
});
await contentPage.close();
await extension1.unload();
await extension2.unload();
let action = {
true: "loaded",
false: "blocked",
};
info(`test_csp: From "${site_csp}" to "${ext1_csp}" to "${ext2_csp}"`);
equal(
expect.img1_loaded,
results.img1_loaded,
`expected first party image to be ${action[expect.img1_loaded]}`
);
equal(
expect.img3_loaded,
results.img3_loaded,
`expected third party image to be ${action[expect.img3_loaded]}`
);
equal(
expect.script1_loaded,
results.script1_loaded,
`expected first party script to be ${action[expect.script1_loaded]}`
);
equal(
expect.script3_loaded,
results.script3_loaded,
`expected third party script to be ${action[expect.script3_loaded]}`
);
}
add_task(async function test_webRequest_mergecsp() {
await test_csp("default-src *", "script-src 'none'", null, {
img1_loaded: true,
img3_loaded: true,
script1_loaded: false,
script3_loaded: false,
});
await test_csp(null, "script-src 'none'", null, {
img1_loaded: true,
img3_loaded: true,
script1_loaded: false,
script3_loaded: false,
});
await test_csp("default-src *", "script-src 'none'", "img-src 'none'", {
img1_loaded: false,
img3_loaded: false,
script1_loaded: false,
script3_loaded: false,
});
await test_csp(null, "script-src 'none'", "img-src 'none'", {
img1_loaded: false,
img3_loaded: false,
script1_loaded: false,
script3_loaded: false,
});
await test_csp(
"default-src *",
"img-src example.com",
"img-src example.org",
{
img1_loaded: false,
img3_loaded: false,
script1_loaded: true,
script3_loaded: true,
}
);
});
add_task(async function test_remove_and_replace_csp() {
// CSP removed, CSP added.
await test_csp("img-src 'self'", "", "img-src example.com", {
img1_loaded: false,
img3_loaded: true,
script1_loaded: true,
script3_loaded: true,
});
// CSP removed, CSP added.
await test_csp("default-src 'none'", "", "img-src example.com", {
img1_loaded: false,
img3_loaded: true,
script1_loaded: true,
script3_loaded: true,
});
// CSP replaced - regression test for bug 1635781.
await test_csp("default-src 'none'", "img-src example.com", null, {
img1_loaded: false,
img3_loaded: true,
script1_loaded: true,
script3_loaded: true,
});
// CSP unchanged, CSP replaced - regression test for bug 1635781.
await test_csp("default-src 'none'", null, "img-src example.com", {
img1_loaded: false,
img3_loaded: true,
script1_loaded: true,
script3_loaded: true,
});
// CSP replaced, CSP removed.
await test_csp("default-src 'none'", "img-src example.com", "", {
img1_loaded: true,
img3_loaded: true,
script1_loaded: true,
script3_loaded: true,
});
});