diff options
Diffstat (limited to 'dom/security')
38 files changed, 527 insertions, 141 deletions
diff --git a/dom/security/ReferrerInfo.cpp b/dom/security/ReferrerInfo.cpp index 565d4d3284..856aa90f12 100644 --- a/dom/security/ReferrerInfo.cpp +++ b/dom/security/ReferrerInfo.cpp @@ -1078,11 +1078,9 @@ ReferrerInfo::Equals(nsIReferrerInfo* aOther, bool* aResult) { } NS_IMETHODIMP -ReferrerInfo::GetComputedReferrerSpec(nsAString& aComputedReferrerSpec) { +ReferrerInfo::GetComputedReferrerSpec(nsACString& aComputedReferrerSpec) { aComputedReferrerSpec.Assign( - mComputedReferrer.isSome() - ? NS_ConvertUTF8toUTF16(mComputedReferrer.value()) - : EmptyString()); + mComputedReferrer.isSome() ? mComputedReferrer.value() : EmptyCString()); return NS_OK; } diff --git a/dom/security/ReferrerInfo.h b/dom/security/ReferrerInfo.h index b62afbb934..bfe254c1a9 100644 --- a/dom/security/ReferrerInfo.h +++ b/dom/security/ReferrerInfo.h @@ -14,7 +14,7 @@ #include "mozilla/HashFunctions.h" #include "mozilla/dom/ReferrerPolicyBinding.h" -#define REFERRERINFOF_CONTRACTID "@mozilla.org/referrer-info;1" +#define REFERRERINFO_CONTRACTID "@mozilla.org/referrer-info;1" // 041a129f-10ce-4bda-a60d-e027a26d5ed0 #define REFERRERINFO_CID \ { \ diff --git a/dom/security/fuzztest/csp_fuzzer.cpp b/dom/security/fuzztest/csp_fuzzer.cpp index 24f938cb1f..14e2ca05ca 100644 --- a/dom/security/fuzztest/csp_fuzzer.cpp +++ b/dom/security/fuzztest/csp_fuzzer.cpp @@ -27,7 +27,7 @@ static int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { if (ret != NS_OK) return 0; ret = - csp->SetRequestContextWithPrincipal(selfURIPrincipal, selfURI, u""_ns, 0); + csp->SetRequestContextWithPrincipal(selfURIPrincipal, selfURI, ""_ns, 0); if (ret != NS_OK) return 0; NS_ConvertASCIItoUTF16 policy(reinterpret_cast<const char*>(data), size); diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp index ed44304484..de67e2bf1c 100644 --- a/dom/security/nsCSPContext.cpp +++ b/dom/security/nsCSPContext.cpp @@ -450,15 +450,14 @@ nsCSPContext::AppendPolicy(const nsAString& aPolicyString, bool aReportOnly, if (policy) { if (policy->hasDirective( nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) { - nsAutoCString selfURIspec, referrer; + nsAutoCString selfURIspec; if (mSelfURI) { mSelfURI->GetAsciiSpec(selfURIspec); } - CopyUTF16toUTF8(mReferrer, referrer); CSPCONTEXTLOG( ("nsCSPContext::AppendPolicy added UPGRADE_IF_INSECURE_DIRECTIVE " "self-uri=%s referrer=%s", - selfURIspec.get(), referrer.get())); + selfURIspec.get(), mReferrer.get())); } mPolicies.AppendElement(policy); @@ -787,7 +786,7 @@ nsCSPContext::SetRequestContextWithDocument(Document* aDocument) { NS_IMETHODIMP nsCSPContext::SetRequestContextWithPrincipal(nsIPrincipal* aRequestPrincipal, nsIURI* aSelfURI, - const nsAString& aReferrer, + const nsACString& aReferrer, uint64_t aInnerWindowId) { NS_ENSURE_ARG(aRequestPrincipal); @@ -812,9 +811,8 @@ nsIPrincipal* nsCSPContext::GetRequestPrincipal() { return mLoadingPrincipal; } nsIURI* nsCSPContext::GetSelfURI() { return mSelfURI; } NS_IMETHODIMP -nsCSPContext::GetReferrer(nsAString& outReferrer) { - outReferrer.Truncate(); - outReferrer.Append(mReferrer); +nsCSPContext::GetReferrer(nsACString& outReferrer) { + outReferrer.Assign(mReferrer); return NS_OK; } @@ -987,7 +985,7 @@ nsresult nsCSPContext::GatherSecurityPolicyViolationEventData( CopyUTF8toUTF16(reportDocumentURI, aViolationEventInit.mDocumentURI); // referrer - aViolationEventInit.mReferrer = mReferrer; + CopyUTF8toUTF16(mReferrer, aViolationEventInit.mReferrer); // blocked-uri if (aBlockedURI) { diff --git a/dom/security/nsCSPContext.h b/dom/security/nsCSPContext.h index ae47e34cb3..e4fe5af315 100644 --- a/dom/security/nsCSPContext.h +++ b/dom/security/nsCSPContext.h @@ -175,7 +175,7 @@ class nsCSPContext : public nsIContentSecurityPolicy { uint32_t aViolatedPolicyIndex, uint32_t aLineNumber, uint32_t aColumnNumber); - nsString mReferrer; + nsCString mReferrer; uint64_t mInnerWindowID; // used for web console logging bool mSkipAllowInlineStyleCheck; // used to allow Devtools to edit styles // When deserializing an nsCSPContext instance, we initially just keep the diff --git a/dom/security/nsCSPParser.cpp b/dom/security/nsCSPParser.cpp index 2559367831..07812470a3 100644 --- a/dom/security/nsCSPParser.cpp +++ b/dom/security/nsCSPParser.cpp @@ -936,14 +936,6 @@ nsCSPDirective* nsCSPParser::directiveName() { // directive = *WSP [ directive-name [ WSP directive-value ] ] void nsCSPParser::directive() { - // Set the directiveName to mCurToken - // Remember, the directive name is stored at index 0 - mCurToken = mCurDir[0]; - - CSPPARSERLOG(("nsCSPParser::directive, mCurToken: %s, mCurValue: %s", - NS_ConvertUTF16toUTF8(mCurToken).get(), - NS_ConvertUTF16toUTF8(mCurValue).get())); - // Make sure that the directive-srcs-array contains at least // one directive. if (mCurDir.Length() == 0) { @@ -953,6 +945,14 @@ void nsCSPParser::directive() { return; } + // Set the directiveName to mCurToken + // Remember, the directive name is stored at index 0 + mCurToken = mCurDir[0]; + + CSPPARSERLOG(("nsCSPParser::directive, mCurToken: %s, mCurValue: %s", + NS_ConvertUTF16toUTF8(mCurToken).get(), + NS_ConvertUTF16toUTF8(mCurValue).get())); + if (CSP_IsEmptyDirective(mCurValue, mCurToken)) { return; } @@ -1029,20 +1029,32 @@ void nsCSPParser::directive() { srcs.InsertElementAt(0, keyword); } + MaybeWarnAboutIgnoredSources(srcs); + MaybeWarnAboutUnsafeInline(*cspDir); + MaybeWarnAboutUnsafeEval(*cspDir); + + // Add the newly created srcs to the directive and add the directive to the + // policy + cspDir->addSrcs(srcs); + mPolicy->addDirective(cspDir); +} + +void nsCSPParser::MaybeWarnAboutIgnoredSources( + const nsTArray<nsCSPBaseSrc*>& aSrcs) { // If policy contains 'strict-dynamic' warn about ignored sources. if (mStrictDynamic && !CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE)) { - for (uint32_t i = 0; i < srcs.Length(); i++) { + for (uint32_t i = 0; i < aSrcs.Length(); i++) { nsAutoString srcStr; - srcs[i]->toString(srcStr); + aSrcs[i]->toString(srcStr); // Hashes and nonces continue to apply with 'strict-dynamic', as well as // 'unsafe-eval', 'wasm-unsafe-eval' and 'unsafe-hashes'. - if (!srcs[i]->isKeyword(CSP_STRICT_DYNAMIC) && - !srcs[i]->isKeyword(CSP_UNSAFE_EVAL) && - !srcs[i]->isKeyword(CSP_WASM_UNSAFE_EVAL) && - !srcs[i]->isKeyword(CSP_UNSAFE_HASHES) && !srcs[i]->isNonce() && - !srcs[i]->isHash()) { + if (!aSrcs[i]->isKeyword(CSP_STRICT_DYNAMIC) && + !aSrcs[i]->isKeyword(CSP_UNSAFE_EVAL) && + !aSrcs[i]->isKeyword(CSP_WASM_UNSAFE_EVAL) && + !aSrcs[i]->isKeyword(CSP_UNSAFE_HASHES) && !aSrcs[i]->isNonce() && + !aSrcs[i]->isHash()) { AutoTArray<nsString, 2> params = {srcStr, mCurDir[0]}; logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringScriptSrcForStrictDynamic", params); @@ -1057,37 +1069,37 @@ void nsCSPParser::directive() { "strictDynamicButNoHashOrNonce", params); } } +} +void nsCSPParser::MaybeWarnAboutUnsafeInline(const nsCSPDirective& aDirective) { // From https://w3c.github.io/webappsec-csp/#allow-all-inline // follows that when either a hash or nonce is specified, 'unsafe-inline' // should not apply. if (mHasHashOrNonce && mUnsafeInlineKeywordSrc && - (cspDir->isDefaultDirective() || - cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE) || - cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) || - cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) || - cspDir->equals(nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE) || - cspDir->equals(nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE) || - cspDir->equals(nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE))) { + (aDirective.isDefaultDirective() || + aDirective.equals(nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE) || + aDirective.equals(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) || + aDirective.equals(nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) || + aDirective.equals(nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE) || + aDirective.equals(nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE) || + aDirective.equals(nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE))) { // Log to the console that unsafe-inline will be ignored. AutoTArray<nsString, 2> params = {u"'unsafe-inline'"_ns, mCurDir[0]}; logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringSrcWithinNonceOrHashDirective", params); } +} +void nsCSPParser::MaybeWarnAboutUnsafeEval(const nsCSPDirective& aDirective) { if (mHasAnyUnsafeEval && - (cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) || - cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE))) { + (aDirective.equals(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) || + aDirective.equals( + nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE))) { // Log to the console that (wasm-)unsafe-eval will be ignored. AutoTArray<nsString, 1> params = {mCurDir[0]}; logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringUnsafeEval", params); } - - // Add the newly created srcs to the directive and add the directive to the - // policy - cspDir->addSrcs(srcs); - mPolicy->addDirective(cspDir); } // policy = [ directive *( ";" [ directive ] ) ] diff --git a/dom/security/nsCSPParser.h b/dom/security/nsCSPParser.h index 21679d86a0..28c24440d0 100644 --- a/dom/security/nsCSPParser.h +++ b/dom/security/nsCSPParser.h @@ -139,6 +139,10 @@ class nsCSPParser { void logWarningErrorToConsole(uint32_t aSeverityFlag, const char* aProperty, const nsTArray<nsString>& aParams); + void MaybeWarnAboutIgnoredSources(const nsTArray<nsCSPBaseSrc*>& aSrcs); + void MaybeWarnAboutUnsafeInline(const nsCSPDirective& aDirective); + void MaybeWarnAboutUnsafeEval(const nsCSPDirective& aDirective); + /** * When parsing the policy, the parser internally uses the following helper * variables/members which are used/reset during parsing. The following diff --git a/dom/security/nsContentSecurityUtils.cpp b/dom/security/nsContentSecurityUtils.cpp index d2c1b257bc..7bcbbdd002 100644 --- a/dom/security/nsContentSecurityUtils.cpp +++ b/dom/security/nsContentSecurityUtils.cpp @@ -1065,7 +1065,7 @@ nsresult CheckCSPFrameAncestorPolicy(nsIChannel* aChannel, csp->SuppressParserLogMessages(); nsCOMPtr<nsIURI> selfURI; - nsAutoString referrerSpec; + nsAutoCString referrerSpec; if (httpChannel) { aChannel->GetURI(getter_AddRefs(selfURI)); nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo(); @@ -1367,6 +1367,7 @@ void nsContentSecurityUtils::AssertAboutPageHasCSP(Document* aDocument) { StringBeginsWith(aboutSpec, "about:preferences"_ns) || StringBeginsWith(aboutSpec, "about:settings"_ns) || StringBeginsWith(aboutSpec, "about:downloads"_ns) || + StringBeginsWith(aboutSpec, "about:fingerprinting"_ns) || StringBeginsWith(aboutSpec, "about:asrouter"_ns) || StringBeginsWith(aboutSpec, "about:newtab"_ns) || StringBeginsWith(aboutSpec, "about:logins"_ns) || diff --git a/dom/security/nsHTTPSOnlyUtils.cpp b/dom/security/nsHTTPSOnlyUtils.cpp index 535efaba4e..31c7408a37 100644 --- a/dom/security/nsHTTPSOnlyUtils.cpp +++ b/dom/security/nsHTTPSOnlyUtils.cpp @@ -398,8 +398,7 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI, // 4. Don't upgrade if upgraded previously or exempt from upgrades uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus(); - if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST || - httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) { + if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) { return false; } @@ -619,6 +618,18 @@ nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest( duration); } } + + nsresult channelStatus; + channel->GetStatus(&channelStatus); + if (channelStatus == NS_ERROR_NET_TIMEOUT_EXTERNAL) { + if (loadInfo->GetWasSchemelessInput() && + !nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin)) { + mozilla::glean::httpsfirst::downgraded_on_timer_schemeless + .AddToNumerator(); + } else { + mozilla::glean::httpsfirst::downgraded_on_timer.AddToNumerator(); + } + } } } @@ -891,6 +902,13 @@ bool nsHTTPSOnlyUtils::IsEqualURIExceptSchemeAndRef(nsIURI* aHTTPSSchemeURI, return uriEquals; } + +/* static */ +uint32_t nsHTTPSOnlyUtils::GetStatusForSubresourceLoad( + uint32_t aHttpsOnlyStatus) { + return aHttpsOnlyStatus & ~nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST; +} + ///////////////////////////////////////////////////////////////////// // Implementation of TestHTTPAnswerRunnable @@ -992,19 +1010,6 @@ TestHTTPAnswerRunnable::OnStartRequest(nsIRequest* aRequest) { nsresult httpsOnlyChannelStatus; httpsOnlyChannel->GetStatus(&httpsOnlyChannelStatus); if (httpsOnlyChannelStatus == NS_OK) { - bool isPrivateWin = - loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0; - if (!nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin)) { - // Record HTTPS-First Telemetry - if (loadInfo->GetWasSchemelessInput() && - !nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin)) { - mozilla::glean::httpsfirst::downgraded_on_timer_schemeless - .AddToNumerator(); - } else { - mozilla::glean::httpsfirst::downgraded_on_timer.AddToNumerator(); - } - } - httpsOnlyChannel->Cancel(NS_ERROR_NET_TIMEOUT_EXTERNAL); } } diff --git a/dom/security/nsHTTPSOnlyUtils.h b/dom/security/nsHTTPSOnlyUtils.h index 7e36bfadbd..775fbf39e0 100644 --- a/dom/security/nsHTTPSOnlyUtils.h +++ b/dom/security/nsHTTPSOnlyUtils.h @@ -164,6 +164,18 @@ class nsHTTPSOnlyUtils { nsIURI* aOtherURI, nsILoadInfo* aLoadInfo); + /** + * Determines which HTTPS-Only status flags should get propagated to + * sub-resources or sub-documents. As sub-resources and sub-documents are + * exempt when the top-level document is exempt, we need to copy the "exempt" + * flag. The HTTPS-First "upgraded" flag should not be copied to prevent a + * unwanted downgrade (Bug 1885949). + * @param aHttpsOnlyStatus The HTTPS-Only status of the top-level document. + * @return The HTTPS-Only status that the sub-resource/document should + * receive. + */ + static uint32_t GetStatusForSubresourceLoad(uint32_t aHttpsOnlyStatus); + private: /** * Checks if it can be ruled out that the error has something diff --git a/dom/security/test/general/file_block_script_wrong_mime_sw.js b/dom/security/test/general/file_block_script_wrong_mime_sw.js new file mode 100644 index 0000000000..4d8d667af4 --- /dev/null +++ b/dom/security/test/general/file_block_script_wrong_mime_sw.js @@ -0,0 +1,51 @@ +/** + * Service Worker that runs in 2 modes: 1) direct pass-through via + * fetch(event.request) and 2) indirect pass-through via + * fetch(event.request.url). + * + * Because this is updating a pre-existing mochitest that didn't use a SW and + * used a single test document, we use a SW idiom where the SW claims the + * existing window client. And because we operate in two modes and we + * parameterize via URL, we also ensure that we skipWaiting. + **/ + +/* eslint-env serviceworker */ + +// We are parameterized by "mode". +const params = new URLSearchParams(location.search); +const fetchMode = params.get("fetchMode"); + +// When activating on initial install, claim the existing window client. +// For synchronziation, also message the controlled document to report our mode. +self.addEventListener("activate", event => { + event.waitUntil( + (async () => { + await clients.claim(); + const allClients = await clients.matchAll(); + for (const client of allClients) { + client.postMessage({ + fetchMode, + }); + } + })() + ); +}); + +// When updating the SW to change our mode of operation, skipWaiting so we +// advance directly to activating without waiting for the test window client +// to stop being controlled by our previous configuration. +self.addEventListener("install", () => { + self.skipWaiting(); +}); + +self.addEventListener("fetch", event => { + switch (fetchMode) { + case "direct": + event.respondWith(fetch(event.request)); + break; + + case "indirect": + event.respondWith(fetch(event.request.url)); + break; + } +}); diff --git a/dom/security/test/general/mochitest.toml b/dom/security/test/general/mochitest.toml index c46b5ecf57..22024fcc67 100644 --- a/dom/security/test/general/mochitest.toml +++ b/dom/security/test/general/mochitest.toml @@ -8,6 +8,7 @@ support-files = [ "file_block_toplevel_data_navigation2.html", "file_block_toplevel_data_navigation3.html", "file_block_toplevel_data_redirect.sjs", + "file_block_script_wrong_mime_sw.js", "file_block_subresource_redir_to_data.sjs", "file_same_site_cookies_subrequest.sjs", "file_same_site_cookies_toplevel_nav.sjs", diff --git a/dom/security/test/general/test_block_script_wrong_mime.html b/dom/security/test/general/test_block_script_wrong_mime.html index 7122363dfc..896823a417 100644 --- a/dom/security/test/general/test_block_script_wrong_mime.html +++ b/dom/security/test/general/test_block_script_wrong_mime.html @@ -29,7 +29,7 @@ function testScript([mime, shouldLoad]) { let script = document.createElement("script"); script.onload = () => { document.body.removeChild(script); - ok(shouldLoad, `script with mime '${mime}' should load`); + ok(shouldLoad, `script with mime '${mime}' should ${shouldLoad ? "" : "NOT "}load`); resolve(); }; script.onerror = () => { @@ -47,7 +47,7 @@ function testWorker([mime, shouldLoad]) { return new Promise((resolve) => { let worker = new Worker("file_block_script_wrong_mime_server.sjs?type=worker&mime="+mime); worker.onmessage = (event) => { - ok(shouldLoad, `worker with mime '${mime}' should load`) + ok(shouldLoad, `worker with mime '${mime}' should ${shouldLoad ? "" : "NOT "}load`); is(event.data, "worker-loaded", "worker should send correct message"); resolve(); }; @@ -65,7 +65,7 @@ function testWorkerImportScripts([mime, shouldLoad]) { return new Promise((resolve) => { let worker = new Worker("file_block_script_wrong_mime_server.sjs?type=worker-import&mime="+mime); worker.onmessage = (event) => { - ok(shouldLoad, `worker/importScripts with mime '${mime}' should load`) + ok(shouldLoad, `worker/importScripts with mime '${mime}' should ${shouldLoad ? "" : "NOT "}load`); is(event.data, "worker-loaded", "worker should send correct message"); resolve(); }; @@ -73,20 +73,103 @@ function testWorkerImportScripts([mime, shouldLoad]) { ok(!shouldLoad, `worker/importScripts with wrong mime '${mime}' should be blocked`); error.preventDefault(); resolve(); + // The worker doesn't self-terminate via close, so let's do it. + worker.terminate(); } worker.postMessage("dummy"); }); } -SimpleTest.waitForExplicitFinish(); -Promise.all(MIMETypes.map(testScript)).then(() => { - return Promise.all(MIMETypes.map(testWorker)); -}).then(() => { - return Promise.all(MIMETypes.map(testWorkerImportScripts)); -}).then(() => { - return SpecialPowers.popPrefEnv(); -}).then(SimpleTest.finish); +async function runMimeTypePermutations() { + info("### Running document script MIME checks."); + for (const mimeType of MIMETypes) { + await testScript(mimeType); + } + info("### Running worker top-level script MIME checks."); + for (const mimeType of MIMETypes) { + await testWorker(mimeType); + } + + info("### Running worker importScripts MIME checks."); + for (const mimeType of MIMETypes) { + await testWorkerImportScripts(mimeType); + } +} + +let gRegistration; + +/** + * Register and wait for the helper ServiceWorker to be active in the given + * mode. + */ +async function useServiceWorker({ fetchMode }) { + info(`### Registering ServiceWorker with mode '${fetchMode}'`); + const activePromise = new Promise((resolve, reject) => { + navigator.serviceWorker.addEventListener( + "message", + event => { + if (event.data.fetchMode === fetchMode) { + resolve(); + } else { + reject(`wrong fetchMode: ${fetchMode}`); + } + is(fetchMode, event.data.fetchMode, "right fetch mode"); + }, + { once: true }); + }); + + const reg = gRegistration = await navigator.serviceWorker.register( + `file_block_script_wrong_mime_sw.js?fetchMode=${fetchMode}`); + info("register resolved. " + + `installing: ${!!reg.installing} ` + + `waiting: ${!!reg.waiting} ` + + `active: ${!!reg.active}`); + + await activePromise; +} + +/** + * Unregister the ServiceWorker, with the caveat that the ServiceWorker will + * still be controlling us until this window goes away. + */ +async function cleanupServiceWorkerWithCaveat() { + await gRegistration.unregister(); +} + +/** + * Top-level test that runs the MIME type checks in different ServiceWorker/ + * network configurations. + * + * We use the ServiceWorker mechanism that allows ServiceWorkers to claim + * existing scope-matching clients in order to make this window controlled and + * then run the tests. When changing the SW behavior the SW also needs to + * skipWaiting in order to advance to active. + */ +async function runNetworkPermutations() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.testing.enabled", true], + ], + }); + + info("## Run tests without a ServiceWorker involved."); + await runMimeTypePermutations(); + + info("## Run tests with a pass-through fetch(event.request) handler."); + await useServiceWorker({ fetchMode: "direct" }); + await runMimeTypePermutations(); + + info("## Run tests with a naive URL propagating fetch(event.request.url) handler."); + await useServiceWorker({ fetchMode: "indirect" }); + await runMimeTypePermutations(); + + await cleanupServiceWorkerWithCaveat(); +} + +add_task(runNetworkPermutations); </script> </body> </html> diff --git a/dom/security/test/gtest/TestCSPParser.cpp b/dom/security/test/gtest/TestCSPParser.cpp index b8a4e986b6..19ba0548de 100644 --- a/dom/security/test/gtest/TestCSPParser.cpp +++ b/dom/security/test/gtest/TestCSPParser.cpp @@ -93,8 +93,7 @@ nsresult runTest( // for testing the parser we only need to set a principal which is needed // to translate the keyword 'self' into an actual URI. - rv = - csp->SetRequestContextWithPrincipal(selfURIPrincipal, selfURI, u""_ns, 0); + rv = csp->SetRequestContextWithPrincipal(selfURIPrincipal, selfURI, ""_ns, 0); NS_ENSURE_SUCCESS(rv, rv); // append a policy diff --git a/dom/security/test/https-first/browser.toml b/dom/security/test/https-first/browser.toml index 0c63b8317d..49e2d522f4 100644 --- a/dom/security/test/https-first/browser.toml +++ b/dom/security/test/https-first/browser.toml @@ -7,7 +7,7 @@ support-files = ["file_beforeunload_permit_http.html"] support-files = [ "file_mixed_content_auto_upgrade.html", "pass.png", - "test.ogv", + "test.webm", "test.wav", ] @@ -40,6 +40,12 @@ support-files = [ ["browser_navigation.js"] support-files = ["file_navigation.html"] +["browser_subdocument_downgrade.js"] +support-files = [ + "file_empty.html", + "file_subdocument_downgrade.sjs", +] + ["browser_schemeless.js"] ["browser_slow_download.js"] diff --git a/dom/security/test/https-first/browser_beforeunload_permit_http.js b/dom/security/test/https-first/browser_beforeunload_permit_http.js index 660c1a352d..281def37e9 100644 --- a/dom/security/test/https-first/browser_beforeunload_permit_http.js +++ b/dom/security/test/https-first/browser_beforeunload_permit_http.js @@ -162,7 +162,7 @@ async function loadPageAndReload(testCase) { } ); is(true, hasInteractedWith, "Simulated successfully user interaction"); - BrowserReloadWithFlags(testCase.reloadFlag); + BrowserCommands.reloadWithFlags(testCase.reloadFlag); await BrowserTestUtils.browserLoaded(browser); is(true, true, `reload with flag ${testCase.name} was successful`); } diff --git a/dom/security/test/https-first/browser_subdocument_downgrade.js b/dom/security/test/https-first/browser_subdocument_downgrade.js new file mode 100644 index 0000000000..4cb5b4ed2e --- /dev/null +++ b/dom/security/test/https-first/browser_subdocument_downgrade.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const EMPTY_URL = + "http://example.com/browser/dom/security/test/https-first/file_empty.html"; +const SUBDOCUMENT_URL = + "https://example.com/browser/dom/security/test/https-first/file_subdocument_downgrade.sjs"; + +add_task(async function test_subdocument_downgrade() { + await SpecialPowers.pushPrefEnv({ + set: [ + // We want to test HTTPS-First + ["dom.security.https_first", true], + // Makes it easier to detect the error + ["security.mixed_content.block_active_content", false], + ], + }); + + // Open a empty document with origin http://example.com, which gets upgraded + // to https://example.com by HTTPS-First and thus is marked as + // HTTPS_ONLY_UPGRADED_HTTPS_FIRST. + await BrowserTestUtils.withNewTab(EMPTY_URL, async browser => { + await SpecialPowers.spawn( + browser, + [SUBDOCUMENT_URL], + async SUBDOCUMENT_URL => { + function isCrossOriginIframe(iframe) { + try { + return !iframe.contentDocument; + } catch (e) { + return true; + } + } + const subdocument = content.document.createElement("iframe"); + // We open https://example.com/.../file_subdocument_downgrade.sjs in a + // iframe, which sends a invalid response if the scheme is https. Thus + // we should get an error. But if we accidentally copy the + // HTTPS_ONLY_UPGRADED_HTTPS_FIRST flag from the parent into the iframe + // loadinfo, HTTPS-First will try to downgrade the iframe. We test that + // this doesn't happen. + subdocument.src = SUBDOCUMENT_URL; + const loadPromise = new Promise(resolve => { + subdocument.addEventListener("load", () => { + ok( + // If the iframe got downgraded, it should now have the origin + // http://example.com, which we can detect as being cross-origin. + !isCrossOriginIframe(subdocument), + "Subdocument should not be downgraded" + ); + resolve(); + }); + }); + content.document.body.appendChild(subdocument); + await loadPromise; + } + ); + }); +}); diff --git a/dom/security/test/https-first/file_empty.html b/dom/security/test/https-first/file_empty.html new file mode 100644 index 0000000000..39d495653e --- /dev/null +++ b/dom/security/test/https-first/file_empty.html @@ -0,0 +1 @@ +<!doctype html><html><body></body></html> diff --git a/dom/security/test/https-first/file_mixed_content_auto_upgrade.html b/dom/security/test/https-first/file_mixed_content_auto_upgrade.html index 7dda8909a5..5a8bef6bb0 100644 --- a/dom/security/test/https-first/file_mixed_content_auto_upgrade.html +++ b/dom/security/test/https-first/file_mixed_content_auto_upgrade.html @@ -6,7 +6,7 @@ <body> <!--upgradeable resources---> <img src="http://example.com/browser/dom/security/test/https-first/pass.png"> - <video src="http://example.com/browser/dom/security/test/https-first/test.ogv"> + <video src="http://example.com/browser/dom/security/test/https-first/test.webm"> <audio src="http://example.com/browser/dom/security/test/https-first/test.wav"> </body> </html> diff --git a/dom/security/test/https-first/file_multiple_redirection.sjs b/dom/security/test/https-first/file_multiple_redirection.sjs index 49098ccdb7..e34a360fa6 100644 --- a/dom/security/test/https-first/file_multiple_redirection.sjs +++ b/dom/security/test/https-first/file_multiple_redirection.sjs @@ -5,6 +5,8 @@ const REDIRECT_URI = "https://example.com/tests/dom/security/test/https-first/file_multiple_redirection.sjs?redirect"; const REDIRECT_URI_HTTP = "http://example.com/tests/dom/security/test/https-first/file_multiple_redirection.sjs?verify"; +const OTHERHOST_REDIRECT_URI_HTTP = + "http://example.org/tests/dom/security/test/https-first/file_multiple_redirection.sjs?verify"; const REDIRECT_URI_HTTPS = "https://example.com/tests/dom/security/test/https-first/file_multiple_redirection.sjs?verify"; @@ -44,6 +46,11 @@ function sendRedirection(query, response) { if (query.includes("test3")) { response.setHeader("Strict-Transport-Security", "max-age=60"); response.setHeader("Location", REDIRECT_URI_HTTP, false); + return; + } + // send a redirection to a different http uri + if (query.includes("test4")) { + response.setHeader("Location", OTHERHOST_REDIRECT_URI_HTTP, false); } } @@ -53,6 +60,11 @@ function handleRequest(request, response) { // if the query contains a test query start first test if (query.startsWith("test")) { + // all of these should be upgraded + if (request.scheme !== "https") { + response.setStatusLine(request.httpVersion, 500, "OK"); + response.write("Request should have been HTTPS."); + } // send a 302 redirection response.setStatusLine(request.httpVersion, 302, "Found"); response.setHeader("Location", REDIRECT_URI + query, false); @@ -60,6 +72,10 @@ function handleRequest(request, response) { } // Send a redirection if (query.includes("redirect")) { + if (request.scheme !== "https") { + response.setStatusLine(request.httpVersion, 500, "OK"); + response.write("Request should have been HTTPS."); + } response.setStatusLine(request.httpVersion, 302, "Found"); sendRedirection(query, response); return; @@ -83,5 +99,5 @@ function handleRequest(request, response) { // We should never get here, but just in case ... response.setStatusLine(request.httpVersion, 500, "OK"); - response.write("unexepcted query"); + response.write("unexpected query"); } diff --git a/dom/security/test/https-first/file_subdocument_downgrade.sjs b/dom/security/test/https-first/file_subdocument_downgrade.sjs new file mode 100644 index 0000000000..53ced94ba8 --- /dev/null +++ b/dom/security/test/https-first/file_subdocument_downgrade.sjs @@ -0,0 +1,8 @@ +function handleRequest(request, response) { + if (request.scheme === "https") { + response.setStatusLine("1.1", 429, "Too Many Requests"); + } else { + response.setHeader("Content-Type", "text/html", false); + response.write("<!doctype html><html><body></body></html>"); + } +} diff --git a/dom/security/test/https-first/test.ogv b/dom/security/test/https-first/test.ogv Binary files differdeleted file mode 100644 index 0f83996e5d..0000000000 --- a/dom/security/test/https-first/test.ogv +++ /dev/null diff --git a/dom/security/test/https-first/test.webm b/dom/security/test/https-first/test.webm Binary files differnew file mode 100644 index 0000000000..221877e303 --- /dev/null +++ b/dom/security/test/https-first/test.webm diff --git a/dom/security/test/https-first/test_multiple_redirection.html b/dom/security/test/https-first/test_multiple_redirection.html index d631f140e6..678a8133a8 100644 --- a/dom/security/test/https-first/test_multiple_redirection.html +++ b/dom/security/test/https-first/test_multiple_redirection.html @@ -37,6 +37,12 @@ Test multiple redirects using https-first and ensure the entire redirect chain i {name: "test last redirect HSTS", result: "scheme-https", query: "test3"}, // reset: reset hsts header for example.com {name: "reset HSTS header", result: "scheme-https", query: "reset"}, + // test 4: http://example.com/...test4 -upgrade-> httpS://example.com/...test4 + // https://example.com/...test4 -redir-> https://example.com/.../REDIRECT + // https://example.com/.../redirect -redir-> http://example.ORG/.../verify + // http://example.org/.../verify -upgrade-> httpS://example.ORG/.../verify + // Everything should be upgraded and accessed only via HTTPS! + {name: "test last redirect other HTTP origin gets upgraded", result: "scheme-https", query: "test4" }, ] let currentTest = 0; let testWin; @@ -48,7 +54,7 @@ Test multiple redirects using https-first and ensure the entire redirect chain i let test = testCase[currentTest]; is(event.data.result, test.result, - "same-origin redirect results in " + test.name + "redirect results in " + test.name ); testWin.close(); if (++currentTest < testCase.length) { diff --git a/dom/security/test/mixedcontentblocker/browser.toml b/dom/security/test/mixedcontentblocker/browser.toml index 5b0b85cb0b..402e8b91b1 100644 --- a/dom/security/test/mixedcontentblocker/browser.toml +++ b/dom/security/test/mixedcontentblocker/browser.toml @@ -15,7 +15,7 @@ support-files = [ support-files = [ "file_csp_block_all_mixedcontent_and_mixed_content_display_upgrade.html", "pass.png", - "test.ogv", + "test.webm", "test.wav", ] diff --git a/dom/security/test/mixedcontentblocker/browser_mixed_content_auth_download.js b/dom/security/test/mixedcontentblocker/browser_mixed_content_auth_download.js index 25fee8de3c..57842eb623 100644 --- a/dom/security/test/mixedcontentblocker/browser_mixed_content_auth_download.js +++ b/dom/security/test/mixedcontentblocker/browser_mixed_content_auth_download.js @@ -12,10 +12,6 @@ const { PromptTestUtils } = ChromeUtils.importESModule( "resource://testing-common/PromptTestUtils.sys.mjs" ); -let authPromptModalType = Services.prefs.getIntPref( - "prompts.modalType.httpAuth" -); - const downloadMonitoringView = { _listeners: [], onDownloadAdded(download) { @@ -107,7 +103,7 @@ async function runTest(url, link, checkFunction, description) { // Wait for the auth prompt, enter the login details and close the prompt await PromptTestUtils.handleNextPrompt( gBrowser.selectedBrowser, - { modalType: authPromptModalType, promptType: "promptUserAndPass" }, + { modalType: Ci.nsIPrompt.MODAL_TYPE_TAB, promptType: "promptUserAndPass" }, { buttonNumClick: 0, loginInput: "user", passwordInput: "pass" } ); await checkPromise; diff --git a/dom/security/test/mixedcontentblocker/file_csp_block_all_mixedcontent_and_mixed_content_display_upgrade.html b/dom/security/test/mixedcontentblocker/file_csp_block_all_mixedcontent_and_mixed_content_display_upgrade.html index 80e97443ed..62e705227f 100644 --- a/dom/security/test/mixedcontentblocker/file_csp_block_all_mixedcontent_and_mixed_content_display_upgrade.html +++ b/dom/security/test/mixedcontentblocker/file_csp_block_all_mixedcontent_and_mixed_content_display_upgrade.html @@ -8,7 +8,7 @@ <body> <!--upgradeable resources---> <img id="some-img" src="http://test1.example.com/browser/dom/security/test/mixedcontentblocker/pass.png" width="100px"> - <video id="some-video" src="http://test1.example.com/browser/dom/security/test/mixedcontentblocker/test.ogv" width="100px"> + <video id="some-video" src="http://test1.example.com/browser/dom/security/test/mixedcontentblocker/test.webm" width="100px"> <audio id="some-audio" src="http://test1.example.com/browser/dom/security/test/mixedcontentblocker/test.wav" width="100px"> </body> </html> diff --git a/dom/security/test/mixedcontentblocker/file_server.sjs b/dom/security/test/mixedcontentblocker/file_server.sjs index 4f86c282ee..90034dad3f 100644 --- a/dom/security/test/mixedcontentblocker/file_server.sjs +++ b/dom/security/test/mixedcontentblocker/file_server.sjs @@ -96,8 +96,8 @@ function handleRequest(request, response) { break; case "media": - response.setHeader("Content-Type", "video/ogg", false); - response.write(loadContentFromFile("tests/dom/media/test/320x240.ogv")); + response.setHeader("Content-Type", "video/webm", false); + response.write(loadContentFromFile("tests/dom/media/test/vp9.webm")); break; case "iframe": diff --git a/dom/security/test/mixedcontentblocker/mochitest.toml b/dom/security/test/mixedcontentblocker/mochitest.toml index 17d8cb4608..cf1d4827a0 100644 --- a/dom/security/test/mixedcontentblocker/mochitest.toml +++ b/dom/security/test/mixedcontentblocker/mochitest.toml @@ -16,7 +16,9 @@ support-files = [ "file_main_bug803225.html", "file_main_bug803225_websocket_wsh.py", "file_server.sjs", - "!/dom/media/test/320x240.ogv", + "!/dom/media/test/vp9.webm", + "test.webm", + "test.wav", "!/image/test/mochitest/blue.png", "file_redirect.html", "file_redirect_handler.sjs", diff --git a/dom/security/test/mixedcontentblocker/test.ogv b/dom/security/test/mixedcontentblocker/test.ogv Binary files differdeleted file mode 100644 index 0f83996e5d..0000000000 --- a/dom/security/test/mixedcontentblocker/test.ogv +++ /dev/null diff --git a/dom/security/test/mixedcontentblocker/test.webm b/dom/security/test/mixedcontentblocker/test.webm Binary files differnew file mode 100644 index 0000000000..221877e303 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/test.webm diff --git a/dom/security/test/referrer-policy/browser.toml b/dom/security/test/referrer-policy/browser.toml index a77046c85b..ba571fec81 100644 --- a/dom/security/test/referrer-policy/browser.toml +++ b/dom/security/test/referrer-policy/browser.toml @@ -1,9 +1,10 @@ [DEFAULT] support-files = ["referrer_page.sjs"] -["browser_session_history.js"] -support-files = ["file_session_history.sjs"] - ["browser_referrer_disallow_cross_site_relaxing.js"] +skip-if = ["asan"] # too slow ["browser_referrer_telemetry.js"] + +["browser_session_history.js"] +support-files = ["file_session_history.sjs"] diff --git a/dom/security/test/referrer-policy/browser_referrer_disallow_cross_site_relaxing.js b/dom/security/test/referrer-policy/browser_referrer_disallow_cross_site_relaxing.js index 7f8df7b34b..84e79af3ef 100644 --- a/dom/security/test/referrer-policy/browser_referrer_disallow_cross_site_relaxing.js +++ b/dom/security/test/referrer-policy/browser_referrer_disallow_cross_site_relaxing.js @@ -191,6 +191,8 @@ add_setup(async function () { set: [ // Disable mixed content blocking to be able to test downgrade scenario. ["security.mixed_content.block_active_content", false], + // Disable https-first since we are testing http and https referrers + ["dom.security.https_first", false], ], }); }); diff --git a/dom/security/trusted-types/TrustedTypePolicy.cpp b/dom/security/trusted-types/TrustedTypePolicy.cpp index 3c4e758ed0..62c58ae1f6 100644 --- a/dom/security/trusted-types/TrustedTypePolicy.cpp +++ b/dom/security/trusted-types/TrustedTypePolicy.cpp @@ -6,38 +6,98 @@ #include "mozilla/dom/TrustedTypePolicy.h" -#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/dom/DOMString.h" #include "mozilla/dom/TrustedTypePolicyFactory.h" #include "mozilla/dom/TrustedTypesBinding.h" +#include <utility> + namespace mozilla::dom { -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TrustedTypePolicy, mParentObject) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TrustedTypePolicy, mParentObject, + mOptions.mCreateHTMLCallback, + mOptions.mCreateScriptCallback, + mOptions.mCreateScriptURLCallback) + +TrustedTypePolicy::TrustedTypePolicy(TrustedTypePolicyFactory* aParentObject, + const nsAString& aName, Options&& aOptions) + : mParentObject{aParentObject}, + mName{aName}, + mOptions{std::move(aOptions)} {} JSObject* TrustedTypePolicy::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { return TrustedTypePolicy_Binding::Wrap(aCx, this, aGivenProto); } -UniquePtr<TrustedHTML> TrustedTypePolicy::CreateHTML( - JSContext* aJSContext, const nsAString& aInput, - const Sequence<JS::Value>& aArguments) const { - // TODO: implement the spec. - return MakeUnique<TrustedHTML>(); +void TrustedTypePolicy::GetName(DOMString& aResult) const { + aResult.SetKnownLiveString(mName); } -UniquePtr<TrustedScript> TrustedTypePolicy::CreateScript( - JSContext* aJSContext, const nsAString& aInput, - const Sequence<JS::Value>& aArguments) const { - // TODO: implement the spec. - return MakeUnique<TrustedScript>(); +#define IMPL_CREATE_TRUSTED_TYPE(_trustedTypeSuffix) \ + UniquePtr<Trusted##_trustedTypeSuffix> \ + TrustedTypePolicy::Create##_trustedTypeSuffix( \ + JSContext* aJSContext, const nsAString& aInput, \ + const Sequence<JS::Value>& aArguments, ErrorResult& aErrorResult) \ + const { \ + /* Invoking the callback could delete the policy and hence the callback. \ + * Hence keep a strong reference to it on the stack. \ + */ \ + RefPtr<Create##_trustedTypeSuffix##Callback> callbackObject = \ + mOptions.mCreate##_trustedTypeSuffix##Callback; \ + \ + return CreateTrustedType<Trusted##_trustedTypeSuffix>( \ + callbackObject, aInput, aArguments, aErrorResult); \ + } + +IMPL_CREATE_TRUSTED_TYPE(HTML) +IMPL_CREATE_TRUSTED_TYPE(Script) +IMPL_CREATE_TRUSTED_TYPE(ScriptURL) + +template <typename T, typename CallbackObject> +UniquePtr<T> TrustedTypePolicy::CreateTrustedType( + const RefPtr<CallbackObject>& aCallbackObject, const nsAString& aValue, + const Sequence<JS::Value>& aArguments, ErrorResult& aErrorResult) const { + nsString policyValue; + DetermineTrustedPolicyValue(aCallbackObject, aValue, aArguments, true, + aErrorResult, policyValue); + + if (aErrorResult.Failed()) { + return nullptr; + } + + UniquePtr<T> trustedObject = MakeUnique<T>(std::move(policyValue)); + + // TODO: add special handling for `TrustedScript` when default policy support + // is added. + + return trustedObject; } -UniquePtr<TrustedScriptURL> TrustedTypePolicy::CreateScriptURL( - JSContext* aJSContext, const nsAString& aInput, - const Sequence<JS::Value>& aArguments) const { - // TODO: implement the spec. - return MakeUnique<TrustedScriptURL>(); +template <typename CallbackObject> +void TrustedTypePolicy::DetermineTrustedPolicyValue( + const RefPtr<CallbackObject>& aCallbackObject, const nsAString& aValue, + const Sequence<JS::Value>& aArguments, bool aThrowIfMissing, + ErrorResult& aErrorResult, nsAString& aResult) const { + if (!aCallbackObject) { + // The spec lacks a definition for stringifying null, see + // <https://github.com/w3c/trusted-types/issues/469>. + aResult = EmptyString(); + + if (aThrowIfMissing) { + aErrorResult.ThrowTypeError("Function missing."); + } + + return; + } + + nsString callbackResult; + aCallbackObject->Call(aValue, aArguments, callbackResult, aErrorResult, + nullptr, CallbackObject::eRethrowExceptions); + + if (!aErrorResult.Failed()) { + aResult = std::move(callbackResult); + } } } // namespace mozilla::dom diff --git a/dom/security/trusted-types/TrustedTypePolicy.h b/dom/security/trusted-types/TrustedTypePolicy.h index 22d99947b3..d677088285 100644 --- a/dom/security/trusted-types/TrustedTypePolicy.h +++ b/dom/security/trusted-types/TrustedTypePolicy.h @@ -9,19 +9,20 @@ #include "js/TypeDecls.h" #include "js/Value.h" +#include "mozilla/Attributes.h" #include "mozilla/RefPtr.h" #include "mozilla/UniquePtr.h" #include "mozilla/dom/BindingDeclarations.h" -#include "mozilla/dom/DOMString.h" #include "mozilla/dom/TrustedHTML.h" #include "mozilla/dom/TrustedScript.h" #include "mozilla/dom/TrustedScriptURL.h" #include "nsISupportsImpl.h" -#include "nsStringFwd.h" +#include "nsString.h" #include "nsWrapperCache.h" namespace mozilla::dom { +class DOMString; class TrustedTypePolicyFactory; // https://w3c.github.io/trusted-types/dist/spec/#trusted-type-policy @@ -30,8 +31,14 @@ class TrustedTypePolicy : public nsWrapperCache { NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(TrustedTypePolicy) NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(TrustedTypePolicy) - explicit TrustedTypePolicy(TrustedTypePolicyFactory* aParentObject) - : mParentObject{aParentObject} {} + struct Options { + RefPtr<CreateHTMLCallback> mCreateHTMLCallback; + RefPtr<CreateScriptCallback> mCreateScriptCallback; + RefPtr<CreateScriptURLCallback> mCreateScriptURLCallback; + }; + + TrustedTypePolicy(TrustedTypePolicyFactory* aParentObject, + const nsAString& aName, Options&& aOptions); // Required for Web IDL binding. TrustedTypePolicyFactory* GetParentObject() const { return mParentObject; } @@ -41,30 +48,46 @@ class TrustedTypePolicy : public nsWrapperCache { JS::Handle<JSObject*> aGivenProto) override; // https://w3c.github.io/trusted-types/dist/spec/#trustedtypepolicy-name - void GetName(DOMString& aResult) const { - // TODO: impl. - } + void GetName(DOMString& aResult) const; // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicy-createhtml - UniquePtr<TrustedHTML> CreateHTML( + MOZ_CAN_RUN_SCRIPT UniquePtr<TrustedHTML> CreateHTML( JSContext* aJSContext, const nsAString& aInput, - const Sequence<JS::Value>& aArguments) const; + const Sequence<JS::Value>& aArguments, ErrorResult& aErrorResult) const; // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicy-createscript - UniquePtr<TrustedScript> CreateScript( + MOZ_CAN_RUN_SCRIPT UniquePtr<TrustedScript> CreateScript( JSContext* aJSContext, const nsAString& aInput, - const Sequence<JS::Value>& aArguments) const; + const Sequence<JS::Value>& aArguments, ErrorResult& aErrorResult) const; // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicy-createscripturl - UniquePtr<TrustedScriptURL> CreateScriptURL( + MOZ_CAN_RUN_SCRIPT UniquePtr<TrustedScriptURL> CreateScriptURL( JSContext* aJSContext, const nsAString& aInput, - const Sequence<JS::Value>& aArguments) const; + const Sequence<JS::Value>& aArguments, ErrorResult& aErrorResult) const; private: // Required because this class is ref-counted. virtual ~TrustedTypePolicy() = default; + // https://w3c.github.io/trusted-types/dist/spec/#abstract-opdef-create-a-trusted-type + template <typename T, typename CallbackObject> + MOZ_CAN_RUN_SCRIPT UniquePtr<T> CreateTrustedType( + const RefPtr<CallbackObject>& aCallbackObject, const nsAString& aValue, + const Sequence<JS::Value>& aArguments, ErrorResult& aErrorResult) const; + + // https://w3c.github.io/trusted-types/dist/spec/#abstract-opdef-get-trusted-type-policy-value + // + // @param aResult may become void. + template <typename CallbackObject> + MOZ_CAN_RUN_SCRIPT void DetermineTrustedPolicyValue( + const RefPtr<CallbackObject>& aCallbackObject, const nsAString& aValue, + const Sequence<JS::Value>& aArguments, bool aThrowIfMissing, + ErrorResult& aErrorResult, nsAString& aResult) const; RefPtr<TrustedTypePolicyFactory> mParentObject; + + const nsString mName; + + Options mOptions; }; } // namespace mozilla::dom diff --git a/dom/security/trusted-types/TrustedTypePolicyFactory.cpp b/dom/security/trusted-types/TrustedTypePolicyFactory.cpp index 448c51eb3b..c2544124e3 100644 --- a/dom/security/trusted-types/TrustedTypePolicyFactory.cpp +++ b/dom/security/trusted-types/TrustedTypePolicyFactory.cpp @@ -22,10 +22,50 @@ JSObject* TrustedTypePolicyFactory::WrapObject( already_AddRefed<TrustedTypePolicy> TrustedTypePolicyFactory::CreatePolicy( const nsAString& aPolicyName, const TrustedTypePolicyOptions& aPolicyOptions) { - // TODO: implement the spec. - return MakeRefPtr<TrustedTypePolicy>(this).forget(); + // TODO: add CSP support. + + // TODO: add default policy support; this requires accessing the default + // policy on the C++ side, hence already now ref-counting policy + // objects. + + TrustedTypePolicy::Options options; + + if (aPolicyOptions.mCreateHTML.WasPassed()) { + options.mCreateHTMLCallback = &aPolicyOptions.mCreateHTML.Value(); + } + + if (aPolicyOptions.mCreateScript.WasPassed()) { + options.mCreateScriptCallback = &aPolicyOptions.mCreateScript.Value(); + } + + if (aPolicyOptions.mCreateScriptURL.WasPassed()) { + options.mCreateScriptURLCallback = &aPolicyOptions.mCreateScriptURL.Value(); + } + + RefPtr<TrustedTypePolicy> policy = + MakeRefPtr<TrustedTypePolicy>(this, aPolicyName, std::move(options)); + + mCreatedPolicyNames.AppendElement(aPolicyName); + + return policy.forget(); } +#define IS_TRUSTED_TYPE_IMPL(_trustedTypeSuffix) \ + bool TrustedTypePolicyFactory::Is##_trustedTypeSuffix( \ + JSContext*, const JS::Handle<JS::Value>& aValue) const { \ + /** \ + * No need to check the internal slot. \ + * Ensured by the corresponding test: \ + * <https://searchfox.org/mozilla-central/rev/b60cb73160843adb5a5a3ec8058e75a69b46acf7/testing/web-platform/tests/trusted-types/TrustedTypePolicyFactory-isXXX.html> \ + */ \ + return aValue.isObject() && \ + IS_INSTANCE_OF(Trusted##_trustedTypeSuffix, &aValue.toObject()); \ + } + +IS_TRUSTED_TYPE_IMPL(HTML); +IS_TRUSTED_TYPE_IMPL(Script); +IS_TRUSTED_TYPE_IMPL(ScriptURL); + UniquePtr<TrustedHTML> TrustedTypePolicyFactory::EmptyHTML() { // Preserving the wrapper ensures: // ``` @@ -37,14 +77,14 @@ UniquePtr<TrustedHTML> TrustedTypePolicyFactory::EmptyHTML() { // multiple emptyHML objects. Both, the JS- and the C++-objects. dom::PreserveWrapper(this); - return MakeUnique<TrustedHTML>(); + return MakeUnique<TrustedHTML>(EmptyString()); } UniquePtr<TrustedScript> TrustedTypePolicyFactory::EmptyScript() { // See the explanation in `EmptyHTML()`. dom::PreserveWrapper(this); - return MakeUnique<TrustedScript>(); + return MakeUnique<TrustedScript>(EmptyString()); } } // namespace mozilla::dom diff --git a/dom/security/trusted-types/TrustedTypePolicyFactory.h b/dom/security/trusted-types/TrustedTypePolicyFactory.h index fea5312cf8..61dae94ed9 100644 --- a/dom/security/trusted-types/TrustedTypePolicyFactory.h +++ b/dom/security/trusted-types/TrustedTypePolicyFactory.h @@ -8,13 +8,16 @@ #define DOM_SECURITY_TRUSTED_TYPES_TRUSTEDTYPEPOLICYFACTORY_H_ #include "js/TypeDecls.h" +#include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/TrustedHTML.h" #include "mozilla/dom/TrustedScript.h" +#include "mozilla/dom/TrustedScriptURL.h" #include "mozilla/RefPtr.h" #include "mozilla/UniquePtr.h" #include "nsIGlobalObject.h" #include "nsISupportsImpl.h" #include "nsStringFwd.h" +#include "nsTArray.h" #include "nsWrapperCache.h" template <typename T> @@ -48,25 +51,15 @@ class TrustedTypePolicyFactory : public nsWrapperCache { const TrustedTypePolicyOptions& aPolicyOptions); // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicyfactory-ishtml - bool IsHTML(JSContext* aJSContext, - const JS::Handle<JS::Value>& aValue) const { - // TODO: impl. - return false; - } + bool IsHTML(JSContext* aJSContext, const JS::Handle<JS::Value>& aValue) const; // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicyfactory-isscript bool IsScript(JSContext* aJSContext, - const JS::Handle<JS::Value>& aValue) const { - // TODO: impl. - return false; - } + const JS::Handle<JS::Value>& aValue) const; // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicyfactory-isscripturl bool IsScriptURL(JSContext* aJSContext, - const JS::Handle<JS::Value>& aValue) const { - // TODO: impl. - return false; - } + const JS::Handle<JS::Value>& aValue) const; // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicyfactory-emptyhtml UniquePtr<TrustedHTML> EmptyHTML(); @@ -98,6 +91,8 @@ class TrustedTypePolicyFactory : public nsWrapperCache { virtual ~TrustedTypePolicyFactory() = default; RefPtr<nsIGlobalObject> mGlobalObject; + + nsTArray<nsString> mCreatedPolicyNames; }; } // namespace mozilla::dom diff --git a/dom/security/trusted-types/TrustedTypeUtils.h b/dom/security/trusted-types/TrustedTypeUtils.h index 90ffc50c38..508c26c3c2 100644 --- a/dom/security/trusted-types/TrustedTypeUtils.h +++ b/dom/security/trusted-types/TrustedTypeUtils.h @@ -10,20 +10,26 @@ #include "mozilla/dom/DOMString.h" #include "mozilla/dom/NonRefcountedDOMObject.h" #include "mozilla/dom/TrustedTypesBinding.h" -#include "nsStringFwd.h" +#include "nsString.h" #define DECL_TRUSTED_TYPE_CLASS(_class) \ class _class : public mozilla::dom::NonRefcountedDOMObject { \ public: \ + explicit _class(const nsAString& aData) : mData{aData} {} \ + \ /* Required for Web IDL binding. */ \ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, \ JS::MutableHandle<JSObject*> aObject); \ \ - void Stringify(nsAString& aResult) const { /* TODO: impl. */ \ + void Stringify(DOMString& aResult) const { \ + aResult.SetKnownLiveString(mData); \ } \ \ - void ToJSON(DOMString& aResult) const { /* TODO: impl. */ \ + void ToJSON(DOMString& aResult) const { \ + aResult.SetKnownLiveString(mData); \ } \ + \ + const nsString mData; \ }; #define IMPL_TRUSTED_TYPE_CLASS(_class) \ |