diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /caps/tests/unit | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | caps/tests/unit/test_ipv6_host_literal.js | 38 | ||||
-rw-r--r-- | caps/tests/unit/test_oa_partitionKey_pattern.js | 159 | ||||
-rw-r--r-- | caps/tests/unit/test_origin.js | 323 | ||||
-rw-r--r-- | caps/tests/unit/test_precursor_principal.js | 259 | ||||
-rw-r--r-- | caps/tests/unit/test_site_origin.js | 184 | ||||
-rw-r--r-- | caps/tests/unit/test_uri_escaping.js | 28 | ||||
-rw-r--r-- | caps/tests/unit/xpcshell.ini | 11 |
7 files changed, 1002 insertions, 0 deletions
diff --git a/caps/tests/unit/test_ipv6_host_literal.js b/caps/tests/unit/test_ipv6_host_literal.js new file mode 100644 index 0000000000..cde7d82d7e --- /dev/null +++ b/caps/tests/unit/test_ipv6_host_literal.js @@ -0,0 +1,38 @@ +var ssm = Services.scriptSecurityManager; +function makeURI(uri) { + return Services.io.newURI(uri); +} + +function testIPV6Host(aHost, aExpected) { + var ipv6Host = ssm.createContentPrincipal(makeURI(aHost), {}); + Assert.equal(ipv6Host.origin, aExpected); +} + +function run_test() { + testIPV6Host("http://[::1]/", "http://[::1]"); + + testIPV6Host( + "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]/", + "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]" + ); + + testIPV6Host( + "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443/", + "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443" + ); + + testIPV6Host( + "http://[2001:db8:85a3::1319:8a2e:370:7348]/", + "http://[2001:db8:85a3:0:1319:8a2e:370:7348]" + ); + + testIPV6Host( + "http://[20D1:0000:3238:DFE1:63:0000:0000:FEFB]/", + "http://[20d1:0:3238:dfe1:63::fefb]" + ); + + testIPV6Host( + "http://[20D1:0:3238:DFE1:63::FEFB]/", + "http://[20d1:0:3238:dfe1:63::fefb]" + ); +} diff --git a/caps/tests/unit/test_oa_partitionKey_pattern.js b/caps/tests/unit/test_oa_partitionKey_pattern.js new file mode 100644 index 0000000000..5d0625018f --- /dev/null +++ b/caps/tests/unit/test_oa_partitionKey_pattern.js @@ -0,0 +1,159 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests origin attributes partitionKey pattern matching. + */ + +"use strict"; + +function testMatch(oa, pattern, shouldMatch = true) { + let msg = `Origin attributes should ${ + shouldMatch ? "match" : "not match" + } pattern.`; + msg += ` oa: ${JSON.stringify(oa)} - pattern: ${JSON.stringify(pattern)}`; + Assert.equal( + ChromeUtils.originAttributesMatchPattern(oa, pattern), + shouldMatch, + msg + ); +} + +function getPartitionKey(scheme, baseDomain, port) { + if (!scheme || !baseDomain) { + return ""; + } + return `(${scheme},${baseDomain}${port ? `,${port}` : ``})`; +} + +function getOAWithPartitionKey(scheme, baseDomain, port, oa = {}) { + oa.partitionKey = getPartitionKey(scheme, baseDomain, port); + return oa; +} + +/** + * Tests that an OriginAttributesPattern which is empty or only has an empty + * partitionKeyPattern matches any partitionKey. + */ +add_task(async function test_empty_pattern_matches_any() { + let list = [ + getOAWithPartitionKey("https", "example.com"), + getOAWithPartitionKey("http", "example.net", 8080), + getOAWithPartitionKey(), + ]; + + for (let oa of list) { + testMatch(oa, {}); + testMatch(oa, { partitionKeyPattern: {} }); + } +}); + +/** + * Tests that if a partitionKeyPattern is passed, but the partitionKey is + * invalid, the pattern match will always fail. + */ +add_task(async function test_invalid_pk() { + let list = [ + "()", + "(,,)", + "(https)", + "(https,,)", + "(example.com)", + "(http,example.com,invalid)", + "(http,example.com,8000,1000)", + ].map(partitionKey => ({ partitionKey })); + + for (let oa of list) { + testMatch(oa, {}); + testMatch(oa, { partitionKeyPattern: {} }); + testMatch( + oa, + { partitionKeyPattern: { baseDomain: "example.com" } }, + false + ); + testMatch(oa, { partitionKeyPattern: { scheme: "https" } }, false); + } +}); + +/** + * Tests that if a pattern sets "partitionKey" it takes precedence over "partitionKeyPattern". + */ +add_task(async function test_string_overwrites_pattern() { + let oa = getOAWithPartitionKey("https", "example.com", 8080, { + userContextId: 2, + }); + + testMatch(oa, { partitionKey: oa.partitionKey }); + testMatch(oa, { + partitionKey: oa.partitionKey, + partitionKeyPattern: { baseDomain: "example.com" }, + }); + testMatch(oa, { + partitionKey: oa.partitionKey, + partitionKeyPattern: { baseDomain: "example.net" }, + }); + testMatch( + oa, + { + partitionKey: getPartitionKey("https", "example.net"), + partitionKeyPattern: { scheme: "https", baseDomain: "example.com" }, + }, + false + ); +}); + +/** + * Tests that we can match parts of a partitionKey by setting + * partitionKeyPattern. + */ +add_task(async function test_pattern() { + let a = getOAWithPartitionKey("https", "example.com", 8080, { + userContextId: 2, + }); + let b = getOAWithPartitionKey("https", "example.com", undefined, { + privateBrowsingId: 1, + }); + + for (let oa of [a, b]) { + // Match + testMatch(oa, { partitionKeyPattern: { scheme: "https" } }); + testMatch(oa, { + partitionKeyPattern: { scheme: "https", baseDomain: "example.com" }, + }); + testMatch( + oa, + { + partitionKeyPattern: { + scheme: "https", + baseDomain: "example.com", + port: 8080, + }, + }, + oa == a + ); + testMatch(oa, { + partitionKeyPattern: { baseDomain: "example.com" }, + }); + testMatch( + oa, + { + partitionKeyPattern: { port: 8080 }, + }, + oa == a + ); + + // Mismatch + testMatch(oa, { partitionKeyPattern: { scheme: "http" } }, false); + testMatch( + oa, + { partitionKeyPattern: { baseDomain: "example.net" } }, + false + ); + testMatch(oa, { partitionKeyPattern: { port: 8443 } }, false); + testMatch( + oa, + { partitionKeyPattern: { scheme: "https", baseDomain: "example.net" } }, + false + ); + } +}); diff --git a/caps/tests/unit/test_origin.js b/caps/tests/unit/test_origin.js new file mode 100644 index 0000000000..c0cbc2996a --- /dev/null +++ b/caps/tests/unit/test_origin.js @@ -0,0 +1,323 @@ +var ssm = Services.scriptSecurityManager; +function makeURI(uri) { + return Services.io.newURI(uri); +} + +function checkThrows(f) { + var threw = false; + try { + f(); + } catch (e) { + threw = true; + } + Assert.ok(threw); +} + +function checkCrossOrigin(a, b) { + Assert.ok(!a.equals(b)); + Assert.ok(!a.equalsConsideringDomain(b)); + Assert.ok(!a.subsumes(b)); + Assert.ok(!a.subsumesConsideringDomain(b)); + Assert.ok(!b.subsumes(a)); + Assert.ok(!b.subsumesConsideringDomain(a)); +} + +function checkOriginAttributes(prin, attrs, suffix) { + attrs = attrs || {}; + Assert.equal( + prin.originAttributes.inIsolatedMozBrowser, + attrs.inIsolatedMozBrowser || false + ); + Assert.equal(prin.originSuffix, suffix || ""); + Assert.equal(ChromeUtils.originAttributesToSuffix(attrs), suffix || ""); + Assert.ok( + ChromeUtils.originAttributesMatchPattern(prin.originAttributes, attrs) + ); + if (!prin.isNullPrincipal && !prin.origin.startsWith("[")) { + Assert.ok(ssm.createContentPrincipalFromOrigin(prin.origin).equals(prin)); + } else { + checkThrows(() => ssm.createContentPrincipalFromOrigin(prin.origin)); + } +} + +function checkSandboxOriginAttributes(arr, attrs, options) { + options = options || {}; + var sandbox = Cu.Sandbox(arr, options); + checkOriginAttributes( + Cu.getObjectPrincipal(sandbox), + attrs, + ChromeUtils.originAttributesToSuffix(attrs) + ); +} + +// utility function useful for debugging +// eslint-disable-next-line no-unused-vars +function printAttrs(name, attrs) { + info( + name + + " {\n" + + "\tuserContextId: " + + attrs.userContextId + + ",\n" + + "\tinIsolatedMozBrowser: " + + attrs.inIsolatedMozBrowser + + ",\n" + + "\tprivateBrowsingId: '" + + attrs.privateBrowsingId + + "',\n" + + "\tfirstPartyDomain: '" + + attrs.firstPartyDomain + + "'\n}" + ); +} + +function checkValues(attrs, values) { + values = values || {}; + // printAttrs("attrs", attrs); + // printAttrs("values", values); + Assert.equal(attrs.userContextId, values.userContextId || 0); + Assert.equal( + attrs.inIsolatedMozBrowser, + values.inIsolatedMozBrowser || false + ); + Assert.equal(attrs.privateBrowsingId, values.privateBrowsingId || ""); + Assert.equal(attrs.firstPartyDomain, values.firstPartyDomain || ""); +} + +function run_test() { + // Attributeless origins. + Assert.equal(ssm.getSystemPrincipal().origin, "[System Principal]"); + checkOriginAttributes(ssm.getSystemPrincipal()); + var exampleOrg = ssm.createContentPrincipal( + makeURI("http://example.org"), + {} + ); + Assert.equal(exampleOrg.origin, "http://example.org"); + checkOriginAttributes(exampleOrg); + var exampleCom = ssm.createContentPrincipal( + makeURI("https://www.example.com:123"), + {} + ); + Assert.equal(exampleCom.origin, "https://www.example.com:123"); + checkOriginAttributes(exampleCom); + var nullPrin = Cu.getObjectPrincipal(new Cu.Sandbox(null)); + Assert.ok( + /^moz-nullprincipal:\{([0-9]|[a-z]|\-){36}\}$/.test(nullPrin.origin) + ); + checkOriginAttributes(nullPrin); + var ipv6Prin = ssm.createContentPrincipal( + makeURI("https://[2001:db8::ff00:42:8329]:123"), + {} + ); + Assert.equal(ipv6Prin.origin, "https://[2001:db8::ff00:42:8329]:123"); + checkOriginAttributes(ipv6Prin); + var ipv6NPPrin = ssm.createContentPrincipal( + makeURI("https://[2001:db8::ff00:42:8329]"), + {} + ); + Assert.equal(ipv6NPPrin.origin, "https://[2001:db8::ff00:42:8329]"); + checkOriginAttributes(ipv6NPPrin); + var ep = Cu.getObjectPrincipal( + Cu.Sandbox([exampleCom, nullPrin, exampleOrg]) + ); + checkOriginAttributes(ep); + checkCrossOrigin(exampleCom, exampleOrg); + checkCrossOrigin(exampleOrg, nullPrin); + + // nsEP origins should be in lexical order. + Assert.equal( + ep.origin, + `[Expanded Principal [${exampleCom.origin}, ${nullPrin.origin}, ${exampleOrg.origin}]]` + ); + + // Make sure createContentPrincipal does what the rest of gecko does. + Assert.ok( + exampleOrg.equals( + Cu.getObjectPrincipal(new Cu.Sandbox("http://example.org")) + ) + ); + + // + // Test origin attributes. + // + + // Just browser. + var exampleOrg_browser = ssm.createContentPrincipal( + makeURI("http://example.org"), + { inIsolatedMozBrowser: true } + ); + var nullPrin_browser = ssm.createNullPrincipal({ + inIsolatedMozBrowser: true, + }); + checkOriginAttributes( + exampleOrg_browser, + { inIsolatedMozBrowser: true }, + "^inBrowser=1" + ); + checkOriginAttributes( + nullPrin_browser, + { inIsolatedMozBrowser: true }, + "^inBrowser=1" + ); + Assert.equal(exampleOrg_browser.origin, "http://example.org^inBrowser=1"); + + // First party Uri + var exampleOrg_firstPartyDomain = ssm.createContentPrincipal( + makeURI("http://example.org"), + { firstPartyDomain: "example.org" } + ); + checkOriginAttributes( + exampleOrg_firstPartyDomain, + { firstPartyDomain: "example.org" }, + "^firstPartyDomain=example.org" + ); + Assert.equal( + exampleOrg_firstPartyDomain.origin, + "http://example.org^firstPartyDomain=example.org" + ); + + // Just userContext. + var exampleOrg_userContext = ssm.createContentPrincipal( + makeURI("http://example.org"), + { userContextId: 42 } + ); + checkOriginAttributes( + exampleOrg_userContext, + { userContextId: 42 }, + "^userContextId=42" + ); + Assert.equal( + exampleOrg_userContext.origin, + "http://example.org^userContextId=42" + ); + + checkSandboxOriginAttributes(null, {}); + checkSandboxOriginAttributes("http://example.org", {}); + checkSandboxOriginAttributes( + "http://example.org", + {}, + { originAttributes: {} } + ); + checkSandboxOriginAttributes(["http://example.org"], {}); + checkSandboxOriginAttributes( + ["http://example.org"], + {}, + { originAttributes: {} } + ); + + // Check that all of the above are cross-origin. + checkCrossOrigin(exampleOrg_browser, nullPrin_browser); + checkCrossOrigin(exampleOrg_firstPartyDomain, exampleOrg); + checkCrossOrigin(exampleOrg_userContext, exampleOrg); + + // Check Principal kinds. + function checkKind(prin, kind) { + Assert.equal(prin.isNullPrincipal, kind == "nullPrincipal"); + Assert.equal(prin.isContentPrincipal, kind == "contentPrincipal"); + Assert.equal(prin.isExpandedPrincipal, kind == "expandedPrincipal"); + Assert.equal(prin.isSystemPrincipal, kind == "systemPrincipal"); + } + checkKind(ssm.createNullPrincipal({}), "nullPrincipal"); + checkKind( + ssm.createContentPrincipal(makeURI("http://www.example.com"), {}), + "contentPrincipal" + ); + checkKind( + Cu.getObjectPrincipal( + Cu.Sandbox([ + ssm.createContentPrincipal(makeURI("http://www.example.com"), {}), + ]) + ), + "expandedPrincipal" + ); + checkKind(ssm.getSystemPrincipal(), "systemPrincipal"); + + // + // Test Origin Attribute Manipulation + // + + // check that we can create an empty origin attributes dict with default + // members and values. + var emptyAttrs = ChromeUtils.fillNonDefaultOriginAttributes({}); + checkValues(emptyAttrs); + + var uri = "http://example.org"; + var tests = [ + ["", {}], + ["^userContextId=3", { userContextId: 3 }], + ["^inBrowser=1", { inIsolatedMozBrowser: true }], + ["^firstPartyDomain=example.org", { firstPartyDomain: "example.org" }], + ]; + + // check that we can create an origin attributes from an origin properly + tests.forEach(t => { + let attrs = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]); + checkValues(attrs, t[1]); + Assert.equal(ChromeUtils.originAttributesToSuffix(attrs), t[0]); + }); + + // check that we can create an origin attributes from a dict properly + tests.forEach(t => { + let attrs = ChromeUtils.fillNonDefaultOriginAttributes(t[1]); + checkValues(attrs, t[1]); + Assert.equal(ChromeUtils.originAttributesToSuffix(attrs), t[0]); + }); + + // each row in the dflt_tests array has these values: + // [0] - the suffix used to create an origin attribute from + // [1] - the expected result of creating an origin attributes from [0] + // [2] - the expected result after setting userContextId to the default + // [3] - the expected result of creating a suffix from [2] + var dflt_tests = [ + ["", {}, {}, ""], + ["^userContextId=3", { userContextId: 3 }, {}, ""], + ]; + + // check that we can set the userContextId to default properly + dflt_tests.forEach(t => { + let orig = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]); + checkValues(orig, t[1]); + let mod = orig; + mod.userContextId = 0; + checkValues(mod, t[2]); + Assert.equal(ChromeUtils.originAttributesToSuffix(mod), t[3]); + }); + + // each row in the dflt2_tests array has these values: + // [0] - the suffix used to create an origin attribute from + // [1] - the expected result of creating an origin attributes from [0] + // [2] - the expected result after setting firstPartyUri to the default + // [3] - the expected result of creating a suffix from [2] + var dflt2_tests = [ + ["", {}, {}, ""], + ["^firstPartyDomain=foo.com", { firstPartyDomain: "foo.com" }, {}, ""], + ]; + + // check that we can set the userContextId to default properly + dflt2_tests.forEach(t => { + let orig = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]); + checkValues(orig, t[1]); + let mod = orig; + mod.firstPartyDomain = ""; + checkValues(mod, t[2]); + Assert.equal(ChromeUtils.originAttributesToSuffix(mod), t[3]); + }); + + var fileURI = makeURI("file:///foo/bar").QueryInterface(Ci.nsIFileURL); + var fileTests = [ + [true, fileURI.spec], + [false, "file://UNIVERSAL_FILE_URI_ORIGIN"], + ]; + fileTests.forEach(t => { + Services.prefs.setBoolPref("security.fileuri.strict_origin_policy", t[0]); + var filePrin = ssm.createContentPrincipal(fileURI, {}); + Assert.equal(filePrin.origin, t[1]); + }); + Services.prefs.clearUserPref("security.fileuri.strict_origin_policy"); + + var aboutBlankURI = makeURI("about:blank"); + var aboutBlankPrin = ssm.createContentPrincipal(aboutBlankURI, {}); + Assert.ok( + /^moz-nullprincipal:\{([0-9]|[a-z]|\-){36}\}$/.test(aboutBlankPrin.origin) + ); +} diff --git a/caps/tests/unit/test_precursor_principal.js b/caps/tests/unit/test_precursor_principal.js new file mode 100644 index 0000000000..37c1ae13bc --- /dev/null +++ b/caps/tests/unit/test_precursor_principal.js @@ -0,0 +1,259 @@ +"use strict"; +const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); +const { ExtensionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/ExtensionXPCShellUtils.sys.mjs" +); + +XPCShellContentUtils.init(this); +ExtensionTestUtils.init(this); + +const server = XPCShellContentUtils.createHttpServer({ + hosts: ["example.com", "example.org"], +}); + +server.registerPathHandler("/static_frames", (request, response) => { + response.setHeader("Content-Type", "text/html"); + response.write(` + <iframe name="same_origin" sandbox="allow-scripts allow-same-origin" src="http://example.com/frame"></iframe> + <iframe name="same_origin_sandbox" sandbox="allow-scripts" src="http://example.com/frame"></iframe> + <iframe name="cross_origin" sandbox="allow-scripts allow-same-origin" src="http://example.org/frame"></iframe> + <iframe name="cross_origin_sandbox" sandbox="allow-scripts" src="http://example.org/frame"></iframe> + <iframe name="data_uri" sandbox="allow-scripts allow-same-origin" src="data:text/html,<h1>Data Subframe</h1>"></iframe> + <iframe name="data_uri_sandbox" sandbox="allow-scripts" src="data:text/html,<h1>Data Subframe</h1>"></iframe> + <iframe name="srcdoc" sandbox="allow-scripts allow-same-origin" srcdoc="<h1>Srcdoc Subframe</h1>"></iframe> + <iframe name="srcdoc_sandbox" sandbox="allow-scripts" srcdoc="<h1>Srcdoc Subframe</h1>"></iframe> + <iframe name="blank" sandbox="allow-scripts allow-same-origin"></iframe> + <iframe name="blank_sandbox" sandbox="allow-scripts"></iframe> + <iframe name="redirect_com" sandbox="allow-scripts allow-same-origin" src="/redirect?com"></iframe> + <iframe name="redirect_com_sandbox" sandbox="allow-scripts" src="/redirect?com"></iframe> + <iframe name="redirect_org" sandbox="allow-scripts allow-same-origin" src="/redirect?org"></iframe> + <iframe name="redirect_org_sandbox" sandbox="allow-scripts" src="/redirect?org"></iframe> + <iframe name="ext_redirect_com_com" sandbox="allow-scripts allow-same-origin" src="/ext_redirect?com"></iframe> + <iframe name="ext_redirect_com_com_sandbox" sandbox="allow-scripts" src="/ext_redirect?com"></iframe> + <iframe name="ext_redirect_org_com" sandbox="allow-scripts allow-same-origin" src="http://example.org/ext_redirect?com"></iframe> + <iframe name="ext_redirect_org_com_sandbox" sandbox="allow-scripts" src="http://example.org/ext_redirect?com"></iframe> + <iframe name="ext_redirect_com_org" sandbox="allow-scripts allow-same-origin" src="/ext_redirect?org"></iframe> + <iframe name="ext_redirect_com_org_sandbox" sandbox="allow-scripts" src="/ext_redirect?org"></iframe> + <iframe name="ext_redirect_org_org" sandbox="allow-scripts allow-same-origin" src="http://example.org/ext_redirect?org"></iframe> + <iframe name="ext_redirect_org_org_sandbox" sandbox="allow-scripts" src="http://example.org/ext_redirect?org"></iframe> + <iframe name="ext_redirect_com_data" sandbox="allow-scripts allow-same-origin" src="/ext_redirect?data"></iframe> + <iframe name="ext_redirect_com_data_sandbox" sandbox="allow-scripts" src="/ext_redirect?data"></iframe> + <iframe name="ext_redirect_org_data" sandbox="allow-scripts allow-same-origin" src="http://example.org/ext_redirect?data"></iframe> + <iframe name="ext_redirect_org_data_sandbox" sandbox="allow-scripts" src="http://example.org/ext_redirect?data"></iframe> + + <!-- XXX(nika): These aren't static as they perform loads dynamically - perhaps consider testing them separately? --> + <iframe name="client_replace_org_blank" sandbox="allow-scripts allow-same-origin" src="http://example.org/client_replace?blank"></iframe> + <iframe name="client_replace_org_blank_sandbox" sandbox="allow-scripts" src="http://example.org/client_replace?blank"></iframe> + <iframe name="client_replace_org_data" sandbox="allow-scripts allow-same-origin" src="http://example.org/client_replace?data"></iframe> + <iframe name="client_replace_org_data_sandbox" sandbox="allow-scripts" src="http://example.org/client_replace?data"></iframe> + `); +}); + +server.registerPathHandler("/frame", (request, response) => { + response.setHeader("Content-Type", "text/html"); + response.write(`<h1>HTTP Subframe</h1>`); +}); + +server.registerPathHandler("/redirect", (request, response) => { + let redirect; + if (request.queryString == "com") { + redirect = "http://example.com/frame"; + } else if (request.queryString == "org") { + redirect = "http://example.org/frame"; + } else { + response.setStatusLine(request.httpVersion, 404, "Not found"); + return; + } + + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", redirect); +}); + +server.registerPathHandler("/client_replace", (request, response) => { + let redirect; + if (request.queryString == "blank") { + redirect = "about:blank"; + } else if (request.queryString == "data") { + redirect = "data:text/html,<h1>Data Subframe</h1>"; + } else { + response.setStatusLine(request.httpVersion, 404, "Not found"); + return; + } + + response.setHeader("Content-Type", "text/html"); + response.write(` + <script> + window.location.replace(${JSON.stringify(redirect)}); + </script> + `); +}); + +add_task(async function sandboxed_precursor() { + // Bug 1725345: Make XPCShellContentUtils.createHttpServer support https + Services.prefs.setBoolPref("dom.security.https_first", false); + + let extension = await ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["webRequest", "webRequestBlocking", "<all_urls>"], + }, + background() { + // eslint-disable-next-line no-undef + browser.webRequest.onBeforeRequest.addListener( + details => { + let url = new URL(details.url); + if (!url.pathname.includes("ext_redirect")) { + return {}; + } + + let redirectUrl; + if (url.search == "?com") { + redirectUrl = "http://example.com/frame"; + } else if (url.search == "?org") { + redirectUrl = "http://example.org/frame"; + } else if (url.search == "?data") { + redirectUrl = "data:text/html,<h1>Data Subframe</h1>"; + } + return { redirectUrl }; + }, + { urls: ["<all_urls>"] }, + ["blocking"] + ); + }, + }); + await extension.startup(); + + registerCleanupFunction(async function () { + await extension.unload(); + }); + + for (let userContextId of [undefined, 1]) { + let comURI = Services.io.newURI("http://example.com"); + let comPrin = Services.scriptSecurityManager.createContentPrincipal( + comURI, + { userContextId } + ); + let orgURI = Services.io.newURI("http://example.org"); + let orgPrin = Services.scriptSecurityManager.createContentPrincipal( + orgURI, + { userContextId } + ); + + let page = await XPCShellContentUtils.loadContentPage( + "http://example.com/static_frames", + { + remote: true, + remoteSubframes: true, + userContextId, + } + ); + let bc = page.browsingContext; + + ok( + bc.currentWindowGlobal.documentPrincipal.equals(comPrin), + "toplevel principal matches" + ); + + // XXX: This is sketchy as heck, but it's also the easiest way to wait for + // the `window.location.replace` loads to finish. + await TestUtils.waitForCondition( + () => + bc.children.every( + child => + !child.currentWindowGlobal.documentURI.spec.includes( + "/client_replace" + ) + ), + "wait for every client_replace global to be replaced" + ); + + let principals = {}; + for (let child of bc.children) { + notEqual(child.name, "", "child frames must have names"); + ok(!(child.name in principals), "duplicate child frame name"); + principals[child.name] = child.currentWindowGlobal.documentPrincipal; + } + + function principal_is(name, expected) { + let principal = principals[name]; + info(`${name} = ${principal.origin}`); + ok(principal.equals(expected), `${name} is correct`); + } + function precursor_is(name, precursor) { + let principal = principals[name]; + info(`${name} = ${principal.origin}`); + ok(principal.isNullPrincipal, `${name} is null`); + ok( + principal.precursorPrincipal.equals(precursor), + `${name} has the correct precursor` + ); + } + + // Basic loads should have the principals or precursor principals for the + // document being loaded. + principal_is("same_origin", comPrin); + precursor_is("same_origin_sandbox", comPrin); + + principal_is("cross_origin", orgPrin); + precursor_is("cross_origin_sandbox", orgPrin); + + // Loads of a data: URI should complete with a sandboxed principal based on + // the principal which tried to perform the load. + precursor_is("data_uri", comPrin); + precursor_is("data_uri_sandbox", comPrin); + + // Loads which inherit principals, such as srcdoc an about:blank loads, + // should also inherit sandboxed precursor principals. + principal_is("srcdoc", comPrin); + precursor_is("srcdoc_sandbox", comPrin); + + principal_is("blank", comPrin); + precursor_is("blank_sandbox", comPrin); + + // Redirects shouldn't interfere with the final principal, and it should be + // based only on the final URI. + principal_is("redirect_com", comPrin); + precursor_is("redirect_com_sandbox", comPrin); + + principal_is("redirect_org", orgPrin); + precursor_is("redirect_org_sandbox", orgPrin); + + // Extension redirects should act like normal redirects, and still resolve + // with the principal or sandboxed principal of the final URI. + principal_is("ext_redirect_com_com", comPrin); + precursor_is("ext_redirect_com_com_sandbox", comPrin); + + principal_is("ext_redirect_com_org", orgPrin); + precursor_is("ext_redirect_com_org_sandbox", orgPrin); + + principal_is("ext_redirect_org_com", comPrin); + precursor_is("ext_redirect_org_com_sandbox", comPrin); + + principal_is("ext_redirect_org_org", orgPrin); + precursor_is("ext_redirect_org_org_sandbox", orgPrin); + + // When an extension redirects to a data: URI, we use the last non-data: URI + // in the chain as the precursor principal. + // FIXME: This should perhaps use the extension's principal instead? + precursor_is("ext_redirect_com_data", comPrin); + precursor_is("ext_redirect_com_data_sandbox", comPrin); + + precursor_is("ext_redirect_org_data", orgPrin); + precursor_is("ext_redirect_org_data_sandbox", orgPrin); + + // Check that navigations triggred by script within the frames will have the + // correct behaviour when navigating to blank and data URIs. + principal_is("client_replace_org_blank", orgPrin); + precursor_is("client_replace_org_blank_sandbox", orgPrin); + + precursor_is("client_replace_org_data", orgPrin); + precursor_is("client_replace_org_data_sandbox", orgPrin); + + await page.close(); + } + Services.prefs.clearUserPref("dom.security.https_first"); +}); diff --git a/caps/tests/unit/test_site_origin.js b/caps/tests/unit/test_site_origin.js new file mode 100644 index 0000000000..bbde787f00 --- /dev/null +++ b/caps/tests/unit/test_site_origin.js @@ -0,0 +1,184 @@ +const scriptSecMan = Services.scriptSecurityManager; + +// SystemPrincipal checks +let systemPrincipal = scriptSecMan.getSystemPrincipal(); +Assert.ok(systemPrincipal.isSystemPrincipal); +Assert.equal(systemPrincipal.origin, "[System Principal]"); +Assert.equal(systemPrincipal.originNoSuffix, "[System Principal]"); +Assert.equal(systemPrincipal.siteOrigin, "[System Principal]"); +Assert.equal(systemPrincipal.siteOriginNoSuffix, "[System Principal]"); + +// ContentPrincipal checks +let uri1 = Services.io.newURI("http://example.com"); +let prinicpal1 = scriptSecMan.createContentPrincipal(uri1, { + userContextId: 11, +}); +Assert.ok(prinicpal1.isContentPrincipal); +Assert.equal(prinicpal1.origin, "http://example.com^userContextId=11"); +Assert.equal(prinicpal1.originNoSuffix, "http://example.com"); +Assert.equal(prinicpal1.siteOrigin, "http://example.com^userContextId=11"); +Assert.equal(prinicpal1.siteOriginNoSuffix, "http://example.com"); + +let uri2 = Services.io.newURI("http://test1.example.com"); +let prinicpal2 = scriptSecMan.createContentPrincipal(uri2, { + userContextId: 22, +}); +Assert.ok(prinicpal2.isContentPrincipal); +Assert.equal(prinicpal2.origin, "http://test1.example.com^userContextId=22"); +Assert.equal(prinicpal2.originNoSuffix, "http://test1.example.com"); +Assert.equal(prinicpal2.siteOrigin, "http://example.com^userContextId=22"); +Assert.equal(prinicpal2.siteOriginNoSuffix, "http://example.com"); + +let uri3 = Services.io.newURI("https://test1.test2.example.com:5555"); +let prinicpal3 = scriptSecMan.createContentPrincipal(uri3, { + userContextId: 5555, +}); +Assert.ok(prinicpal3.isContentPrincipal); +Assert.equal( + prinicpal3.origin, + "https://test1.test2.example.com:5555^userContextId=5555" +); +Assert.equal(prinicpal3.originNoSuffix, "https://test1.test2.example.com:5555"); +Assert.equal(prinicpal3.siteOrigin, "https://example.com^userContextId=5555"); +Assert.equal(prinicpal3.siteOriginNoSuffix, "https://example.com"); + +let uri4 = Services.io.newURI("https://.example.com:6666"); +let prinicpal4 = scriptSecMan.createContentPrincipal(uri4, { + userContextId: 6666, +}); +Assert.ok(prinicpal4.isContentPrincipal); +Assert.equal(prinicpal4.origin, "https://.example.com:6666^userContextId=6666"); +Assert.equal(prinicpal4.originNoSuffix, "https://.example.com:6666"); +Assert.equal(prinicpal4.siteOrigin, "https://.example.com^userContextId=6666"); +Assert.equal(prinicpal4.siteOriginNoSuffix, "https://.example.com"); + +let aboutURI = Services.io.newURI("about:preferences"); +let aboutPrincipal = scriptSecMan.createContentPrincipal(aboutURI, { + userContextId: 66, +}); +Assert.ok(aboutPrincipal.isContentPrincipal); +Assert.equal(aboutPrincipal.origin, "about:preferences^userContextId=66"); +Assert.equal(aboutPrincipal.originNoSuffix, "about:preferences"); +Assert.equal(aboutPrincipal.siteOrigin, "about:preferences^userContextId=66"); +Assert.equal(aboutPrincipal.siteOriginNoSuffix, "about:preferences"); + +let viewSourceURI = Services.io.newURI( + "view-source:https://test1.test2.example.com" +); +let viewSourcePrincipal = scriptSecMan.createContentPrincipal(viewSourceURI, { + userContextId: 101, +}); +Assert.ok(viewSourcePrincipal.isContentPrincipal); +Assert.ok(viewSourcePrincipal.schemeIs("view-source")); +Assert.equal( + viewSourcePrincipal.origin, + "https://test1.test2.example.com^userContextId=101" +); +Assert.equal( + viewSourcePrincipal.originNoSuffix, + "https://test1.test2.example.com" +); +Assert.equal( + viewSourcePrincipal.siteOrigin, + "https://example.com^userContextId=101" +); +Assert.equal(viewSourcePrincipal.siteOriginNoSuffix, "https://example.com"); + +let mozExtensionURI = Services.io.newURI( + "moz-extension://924f966d-c93b-4fbf-968a-16608461663c/meh.txt" +); +let mozExtensionPrincipal = scriptSecMan.createContentPrincipal( + mozExtensionURI, + { + userContextId: 102, + } +); +Assert.ok(mozExtensionPrincipal.isContentPrincipal); +Assert.ok(mozExtensionPrincipal.schemeIs("moz-extension")); +Assert.equal( + mozExtensionPrincipal.origin, + "moz-extension://924f966d-c93b-4fbf-968a-16608461663c^userContextId=102" +); +Assert.equal( + mozExtensionPrincipal.originNoSuffix, + "moz-extension://924f966d-c93b-4fbf-968a-16608461663c" +); +Assert.equal( + mozExtensionPrincipal.siteOrigin, + "moz-extension://924f966d-c93b-4fbf-968a-16608461663c^userContextId=102" +); +Assert.equal( + mozExtensionPrincipal.siteOriginNoSuffix, + "moz-extension://924f966d-c93b-4fbf-968a-16608461663c" +); + +let localhostURI = Services.io.newURI(" http://localhost:44203"); +let localhostPrincipal = scriptSecMan.createContentPrincipal(localhostURI, { + userContextId: 144, +}); +Assert.ok(localhostPrincipal.isContentPrincipal); +Assert.ok(localhostPrincipal.schemeIs("http")); +Assert.equal( + localhostPrincipal.origin, + "http://localhost:44203^userContextId=144" +); +Assert.equal(localhostPrincipal.originNoSuffix, "http://localhost:44203"); +Assert.equal( + localhostPrincipal.siteOrigin, + "http://localhost^userContextId=144" +); +Assert.equal(localhostPrincipal.siteOriginNoSuffix, "http://localhost"); + +// NullPrincipal checks +let nullPrincipal = scriptSecMan.createNullPrincipal({ userContextId: 33 }); +Assert.ok(nullPrincipal.isNullPrincipal); +Assert.ok(nullPrincipal.origin.includes("moz-nullprincipal")); +Assert.ok(nullPrincipal.origin.includes("^userContextId=33")); +Assert.ok(nullPrincipal.originNoSuffix.includes("moz-nullprincipal")); +Assert.ok(!nullPrincipal.originNoSuffix.includes("^userContextId=33")); +Assert.ok(nullPrincipal.siteOrigin.includes("moz-nullprincipal")); +Assert.ok(nullPrincipal.siteOrigin.includes("^userContextId=33")); +Assert.ok(nullPrincipal.siteOriginNoSuffix.includes("moz-nullprincipal")); +Assert.ok(!nullPrincipal.siteOriginNoSuffix.includes("^userContextId=33")); + +// ExpandedPrincipal checks +let expandedPrincipal = Cu.getObjectPrincipal(Cu.Sandbox([prinicpal2])); +Assert.ok(expandedPrincipal.isExpandedPrincipal); +Assert.equal( + expandedPrincipal.origin, + "[Expanded Principal [http://test1.example.com^userContextId=22]]^userContextId=22" +); +Assert.equal( + expandedPrincipal.originNoSuffix, + "[Expanded Principal [http://test1.example.com^userContextId=22]]" +); +Assert.equal( + expandedPrincipal.siteOrigin, + "[Expanded Principal [http://test1.example.com^userContextId=22]]^userContextId=22" +); +Assert.equal( + expandedPrincipal.siteOriginNoSuffix, + "[Expanded Principal [http://test1.example.com^userContextId=22]]" +); + +let ipv6URI = Services.io.newURI("https://[2001:db8::ff00:42:8329]:123"); +let ipv6Principal = scriptSecMan.createContentPrincipal(ipv6URI, { + userContextId: 6, +}); +Assert.ok(ipv6Principal.isContentPrincipal); +Assert.equal( + ipv6Principal.origin, + "https://[2001:db8::ff00:42:8329]:123^userContextId=6" +); +Assert.equal( + ipv6Principal.originNoSuffix, + "https://[2001:db8::ff00:42:8329]:123" +); +Assert.equal( + ipv6Principal.siteOrigin, + "https://[2001:db8::ff00:42:8329]^userContextId=6" +); +Assert.equal( + ipv6Principal.siteOriginNoSuffix, + "https://[2001:db8::ff00:42:8329]" +); diff --git a/caps/tests/unit/test_uri_escaping.js b/caps/tests/unit/test_uri_escaping.js new file mode 100644 index 0000000000..7feb581295 --- /dev/null +++ b/caps/tests/unit/test_uri_escaping.js @@ -0,0 +1,28 @@ +var ssm = Services.scriptSecurityManager; + +function makeURI(uri) { + return Services.io.newURI(uri); +} + +function createPrincipal(aURI) { + try { + var uri = makeURI(aURI); + var principal = ssm.createContentPrincipal(uri, {}); + return principal; + } catch (e) { + return null; + } +} + +function run_test() { + Assert.equal(createPrincipal("http://test^test/foo^bar#x^y"), null); + + Assert.equal(createPrincipal("http://test^test/foo\\bar"), null); + + Assert.equal(createPrincipal("http://test:2^3/foo\\bar"), null); + + Assert.equal( + createPrincipal("http://test/foo^bar").exposableSpec, + "http://test/foo%5Ebar" + ); +} diff --git a/caps/tests/unit/xpcshell.ini b/caps/tests/unit/xpcshell.ini new file mode 100644 index 0000000000..9ff2b22617 --- /dev/null +++ b/caps/tests/unit/xpcshell.ini @@ -0,0 +1,11 @@ +[DEFAULT] +head = + +[test_origin.js] +[test_uri_escaping.js] +[test_ipv6_host_literal.js] +[test_oa_partitionKey_pattern.js] +[test_site_origin.js] +[test_precursor_principal.js] +firefox-appdir = browser +skip-if = toolkit == 'android' |