/* * Description of the tests: * These tests check for conformance to the CSP spec as they relate to Web Manifests. * * In particular, the tests check that default-src and manifest-src directives are * are respected by the ManifestObtainer. */ /*globals Cu, is, ok*/ "use strict"; const { ManifestObtainer } = ChromeUtils.importESModule( "resource://gre/modules/ManifestObtainer.sys.mjs" ); const path = "/tests/dom/security/test/csp/"; const testFile = `${path}file_web_manifest.html`; const remoteFile = `${path}file_web_manifest_remote.html`; const httpsManifest = `${path}file_web_manifest_https.html`; const server = `${path}file_testserver.sjs`; const defaultURL = new URL(`http://example.org${server}`); const secureURL = new URL(`https://example.com:443${server}`); // Enable web manifest processing. Services.prefs.setBoolPref("dom.manifest.enabled", true); const tests = [ // CSP block everything, so trying to load a manifest // will result in a policy violation. { expected: "default-src 'none' blocks fetching manifest.", get tabURL() { const url = new URL(defaultURL); url.searchParams.append("file", testFile); url.searchParams.append("csp", "default-src 'none'"); return url.href; }, run(topic) { is(topic, "csp-on-violate-policy", this.expected); }, }, // CSP allows fetching only from mochi.test:8888, // so trying to load a manifest from same origin // triggers a CSP violation. { expected: "default-src mochi.test:8888 blocks manifest fetching.", get tabURL() { const url = new URL(defaultURL); url.searchParams.append("file", testFile); url.searchParams.append("csp", "default-src mochi.test:8888"); return url.href; }, run(topic) { is(topic, "csp-on-violate-policy", this.expected); }, }, // CSP restricts fetching to 'self', so allowing the manifest // to load. The name of the manifest is then checked. { expected: "CSP default-src 'self' allows fetch of manifest.", get tabURL() { const url = new URL(defaultURL); url.searchParams.append("file", testFile); url.searchParams.append("csp", "default-src 'self'"); return url.href; }, run(manifest) { is(manifest.name, "loaded", this.expected); }, }, // CSP only allows fetching from mochi.test:8888 and remoteFile // requests a manifest from that origin, so manifest should load. { expected: "CSP default-src mochi.test:8888 allows fetching manifest.", get tabURL() { const url = new URL(defaultURL); url.searchParams.append("file", remoteFile); url.searchParams.append("csp", "default-src http://mochi.test:8888"); return url.href; }, run(manifest) { is(manifest.name, "loaded", this.expected); }, }, // default-src blocks everything, so any attempt to // fetch a manifest from another origin will trigger a // policy violation. { expected: "default-src 'none' blocks mochi.test:8888", get tabURL() { const url = new URL(defaultURL); url.searchParams.append("file", remoteFile); url.searchParams.append("csp", "default-src 'none'"); return url.href; }, run(topic) { is(topic, "csp-on-violate-policy", this.expected); }, }, // CSP allows fetching from self, so manifest should load. { expected: "CSP manifest-src allows self", get tabURL() { const url = new URL(defaultURL); url.searchParams.append("file", testFile); url.searchParams.append("csp", "manifest-src 'self'"); return url.href; }, run(manifest) { is(manifest.name, "loaded", this.expected); }, }, // CSP allows fetching from example.org, so manifest should load. { expected: "CSP manifest-src allows http://example.org", get tabURL() { const url = new URL(defaultURL); url.searchParams.append("file", testFile); url.searchParams.append("csp", "manifest-src http://example.org"); return url.href; }, run(manifest) { is(manifest.name, "loaded", this.expected); }, }, { expected: "CSP manifest-src allows mochi.test:8888", get tabURL() { const url = new URL(defaultURL); url.searchParams.append("file", remoteFile); url.searchParams.append("cors", "*"); url.searchParams.append( "csp", "default-src *; manifest-src http://mochi.test:8888" ); return url.href; }, run(manifest) { is(manifest.name, "loaded", this.expected); }, }, // CSP restricts fetching to mochi.test:8888, but the test // file is at example.org. Hence, a policy violation is // triggered. { expected: "CSP blocks manifest fetching from example.org.", get tabURL() { const url = new URL(defaultURL); url.searchParams.append("file", testFile); url.searchParams.append("csp", "manifest-src mochi.test:8888"); return url.href; }, run(topic) { is(topic, "csp-on-violate-policy", this.expected); }, }, // CSP is set to only allow manifest to be loaded from same origin, // but the remote file attempts to load from a different origin. Thus // this causes a CSP violation. { expected: "CSP manifest-src 'self' blocks cross-origin fetch.", get tabURL() { const url = new URL(defaultURL); url.searchParams.append("file", remoteFile); url.searchParams.append("csp", "manifest-src 'self'"); return url.href; }, run(topic) { is(topic, "csp-on-violate-policy", this.expected); }, }, // CSP allows fetching over TLS from example.org, so manifest should load. { expected: "CSP manifest-src allows example.com over TLS", get tabURL() { // secureURL loads https://example.com:443 // and gets manifest from https://example.org:443 const url = new URL(secureURL); url.searchParams.append("file", httpsManifest); url.searchParams.append("cors", "*"); url.searchParams.append("csp", "manifest-src https://example.com:443"); return url.href; }, run(manifest) { is(manifest.name, "loaded", this.expected); }, }, ]; //jscs:disable add_task(async function () { //jscs:enable const testPromises = tests.map(test => { const tabOptions = { gBrowser, url: test.tabURL, skipAnimation: true, }; return BrowserTestUtils.withNewTab(tabOptions, browser => testObtainingManifest(browser, test) ); }); await Promise.all(testPromises); }); async function testObtainingManifest(aBrowser, aTest) { const waitForObserver = waitForNetObserver(aBrowser, aTest); // Expect an exception (from promise rejection) if there a content policy // that is violated. try { const manifest = await ManifestObtainer.browserObtainManifest(aBrowser); aTest.run(manifest); } catch (e) { const wasBlocked = e.message.includes( "NetworkError when attempting to fetch resource" ); ok( wasBlocked, `Expected promise rejection obtaining ${aTest.tabURL}: ${e.message}` ); } finally { await waitForObserver; } } // Helper object used to observe policy violations when blocking is expected. function waitForNetObserver(aBrowser, aTest) { // We don't need to wait for violation, so just resolve if (!aTest.expected.includes("block")) { return Promise.resolve(); } return ContentTask.spawn(aBrowser, [], () => { return new Promise(resolve => { function observe(subject, topic) { Services.obs.removeObserver(observe, "csp-on-violate-policy"); resolve(); } Services.obs.addObserver(observe, "csp-on-violate-policy"); }); }).then(() => aTest.run("csp-on-violate-policy")); }