diff options
Diffstat (limited to 'dom')
28 files changed, 688 insertions, 68 deletions
diff --git a/dom/base/Location.cpp b/dom/base/Location.cpp index e3b614931..b6b95aaa6 100644 --- a/dom/base/Location.cpp +++ b/dom/base/Location.cpp @@ -577,19 +577,17 @@ Location::GetPathname(nsAString& aPathname) aPathname.Truncate(); nsCOMPtr<nsIURI> uri; - nsresult result = NS_OK; + nsresult result = GetURI(getter_AddRefs(uri)); + if (NS_FAILED(result) || !uri) { + return result; + } - result = GetURI(getter_AddRefs(uri)); + nsAutoCString file; - nsCOMPtr<nsIURIWithQuery> url(do_QueryInterface(uri)); - if (url) { - nsAutoCString file; + result = uri->GetFilePath(file); - result = url->GetFilePath(file); - - if (NS_SUCCEEDED(result)) { - AppendUTF8toUTF16(file, aPathname); - } + if (NS_SUCCEEDED(result)) { + AppendUTF8toUTF16(file, aPathname); } return result; @@ -604,8 +602,7 @@ Location::SetPathname(const nsAString& aPathname) return rv; } - nsCOMPtr<nsIURIWithQuery> url(do_QueryInterface(uri)); - if (url && NS_SUCCEEDED(url->SetFilePath(NS_ConvertUTF16toUTF8(aPathname)))) { + if (NS_SUCCEEDED(uri->SetFilePath(NS_ConvertUTF16toUTF8(aPathname)))) { return SetURI(uri); } diff --git a/dom/base/nsContentPolicy.cpp b/dom/base/nsContentPolicy.cpp index 337debcea..5511b9086 100644 --- a/dom/base/nsContentPolicy.cpp +++ b/dom/base/nsContentPolicy.cpp @@ -20,6 +20,7 @@ #include "nsIDOMElement.h" #include "nsIDOMNode.h" #include "nsIDOMWindow.h" +#include "nsITabChild.h" #include "nsIContent.h" #include "nsILoadContext.h" #include "nsCOMArray.h" @@ -89,8 +90,9 @@ nsContentPolicy::CheckPolicy(CPMethod policyMethod, { nsCOMPtr<nsIDOMNode> node(do_QueryInterface(requestingContext)); nsCOMPtr<nsIDOMWindow> window(do_QueryInterface(requestingContext)); - NS_ASSERTION(!requestingContext || node || window, - "Context should be a DOM node or a DOM window!"); + nsCOMPtr<nsITabChild> tabChild(do_QueryInterface(requestingContext)); + NS_ASSERTION(!requestingContext || node || window || tabChild, + "Context should be a DOM node, DOM window or a tabChild!"); } #endif diff --git a/dom/locales/en-US/chrome/security/security.properties b/dom/locales/en-US/chrome/security/security.properties index 8b66cc265..c0b80996c 100644 --- a/dom/locales/en-US/chrome/security/security.properties +++ b/dom/locales/en-US/chrome/security/security.properties @@ -81,3 +81,6 @@ MimeTypeMismatch=The resource from “%1$S” was blocked due to MIME type misma XCTOHeaderValueMissing=X-Content-Type-Options header warning: value was “%1$S”; did you mean to send “nosniff”? BlockScriptWithWrongMimeType=Script from “%1$S” was blocked because of a disallowed MIME type. + +# LOCALIZATION NOTE: Do not translate "data: URI". +BlockTopLevelDataURINavigation=Navigation to toplevel data: URI not allowed (Blocked loading of: “%1$S”) diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp index a7517f65e..979bd915f 100644 --- a/dom/security/nsCSPContext.cpp +++ b/dom/security/nsCSPContext.cpp @@ -171,9 +171,10 @@ nsCSPContext::ShouldLoad(nsContentPolicyType aContentType, } } - // aExtra is only non-null if the channel got redirected. - bool wasRedirected = (aExtra != nullptr); + // aExtra holds the original URI of the channel if the + // channel got redirected (until we fix Bug 1332422). nsCOMPtr<nsIURI> originalURI = do_QueryInterface(aExtra); + bool wasRedirected = originalURI; bool permitted = permitsInternal(dir, aContentLocation, diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp index c4e1ed8e1..0cc4933fe 100644 --- a/dom/security/nsContentSecurityManager.cpp +++ b/dom/security/nsContentSecurityManager.cpp @@ -1,8 +1,10 @@ #include "nsContentSecurityManager.h" +#include "nsEscape.h" #include "nsIChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIStreamListener.h" #include "nsILoadInfo.h" +#include "nsIOService.h" #include "nsContentUtils.h" #include "nsCORSListenerProxy.h" #include "nsIStreamListener.h" @@ -10,11 +12,86 @@ #include "nsMixedContentBlocker.h" #include "mozilla/dom/Element.h" +#include "mozilla/dom/TabChild.h" NS_IMPL_ISUPPORTS(nsContentSecurityManager, nsIContentSecurityManager, nsIChannelEventSink) +/* static */ bool +nsContentSecurityManager::AllowTopLevelNavigationToDataURI(nsIChannel* aChannel) +{ + // Let's block all toplevel document navigations to a data: URI. + // In all cases where the toplevel document is navigated to a + // data: URI the triggeringPrincipal is a codeBasePrincipal, or + // a NullPrincipal. In other cases, e.g. typing a data: URL into + // the URL-Bar, the triggeringPrincipal is a SystemPrincipal; + // we don't want to block those loads. Only exception, loads coming + // from an external applicaton (e.g. Thunderbird) don't load + // using a codeBasePrincipal, but we want to block those loads. + if (!mozilla::net::nsIOService::BlockToplevelDataUriNavigations()) { + return true; + } + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); + if (!loadInfo) { + return true; + } + if (loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT) { + return true; + } + if (loadInfo->GetForceAllowDataURI()) { + // if the loadinfo explicitly allows the data URI navigation, let's allow it now + return true; + } + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, true); + bool isDataURI = + (NS_SUCCEEDED(uri->SchemeIs("data", &isDataURI)) && isDataURI); + if (!isDataURI) { + return true; + } + // Whitelist data: images as long as they are not SVGs + nsAutoCString filePath; + uri->GetFilePath(filePath); + if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/")) && + !StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/svg+xml"))) { + return true; + } + // Whitelist data: PDFs and JSON + if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("application/pdf")) || + StringBeginsWith(filePath, NS_LITERAL_CSTRING("application/json"))) { + return true; + } + // Redirecting to a toplevel data: URI is not allowed, hence we make + // sure the RedirectChain is empty. + if (!loadInfo->GetLoadTriggeredFromExternal() && + nsContentUtils::IsSystemPrincipal(loadInfo->TriggeringPrincipal()) && + loadInfo->RedirectChain().IsEmpty()) { + return true; + } + nsAutoCString dataSpec; + uri->GetSpec(dataSpec); + if (dataSpec.Length() > 50) { + dataSpec.Truncate(50); + dataSpec.AppendLiteral("..."); + } + nsCOMPtr<nsITabChild> tabChild = do_QueryInterface(loadInfo->ContextForTopLevelLoad()); + nsCOMPtr<nsIDocument> doc; + if (tabChild) { + doc = static_cast<mozilla::dom::TabChild*>(tabChild.get())->GetDocument(); + } + NS_ConvertUTF8toUTF16 specUTF16(NS_UnescapeURL(dataSpec)); + const char16_t* params[] = { specUTF16.get() }; + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("DATA_URI_BLOCKED"), + doc, + nsContentUtils::eSECURITY_PROPERTIES, + "BlockTopLevelDataURINavigation", + params, ArrayLength(params)); + return false; +} + static nsresult ValidateSecurityFlags(nsILoadInfo* aLoadInfo) { @@ -176,7 +253,7 @@ DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) nsContentPolicyType internalContentPolicyType = aLoadInfo->InternalContentPolicyType(); nsCString mimeTypeGuess; - nsCOMPtr<nsINode> requestingContext = nullptr; + nsCOMPtr<nsISupports> requestingContext = nullptr; #ifdef DEBUG // Don't enforce TYPE_DOCUMENT assertions for loads @@ -250,10 +327,13 @@ DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) case nsIContentPolicy::TYPE_XMLHTTPREQUEST: { // alias nsIContentPolicy::TYPE_DATAREQUEST: requestingContext = aLoadInfo->LoadingNode(); - MOZ_ASSERT(!requestingContext || - requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE, - "type_xml requires requestingContext of type Document"); - +#ifdef DEBUG + { + nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext); + MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::DOCUMENT_NODE, + "type_xml requires requestingContext of type Document"); + } +#endif // We're checking for the external TYPE_XMLHTTPREQUEST here in case // an addon creates a request with that type. if (internalContentPolicyType == @@ -274,18 +354,26 @@ DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST: { mimeTypeGuess = EmptyCString(); requestingContext = aLoadInfo->LoadingNode(); - MOZ_ASSERT(!requestingContext || - requestingContext->NodeType() == nsIDOMNode::ELEMENT_NODE, - "type_subrequest requires requestingContext of type Element"); +#ifdef DEBUG + { + nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext); + MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::ELEMENT_NODE, + "type_subrequest requires requestingContext of type Element"); + } +#endif break; } case nsIContentPolicy::TYPE_DTD: { mimeTypeGuess = EmptyCString(); requestingContext = aLoadInfo->LoadingNode(); - MOZ_ASSERT(!requestingContext || - requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE, - "type_dtd requires requestingContext of type Document"); +#ifdef DEBUG + { + nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext); + MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::DOCUMENT_NODE, + "type_dtd requires requestingContext of type Document"); + } +#endif break; } @@ -303,9 +391,13 @@ DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) mimeTypeGuess = EmptyCString(); } requestingContext = aLoadInfo->LoadingNode(); - MOZ_ASSERT(!requestingContext || - requestingContext->NodeType() == nsIDOMNode::ELEMENT_NODE, - "type_media requires requestingContext of type Element"); +#ifdef DEBUG + { + nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext); + MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::ELEMENT_NODE, + "type_media requires requestingContext of type Element"); + } +#endif break; } @@ -332,18 +424,26 @@ DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) case nsIContentPolicy::TYPE_XSLT: { mimeTypeGuess = NS_LITERAL_CSTRING("application/xml"); requestingContext = aLoadInfo->LoadingNode(); - MOZ_ASSERT(!requestingContext || - requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE, - "type_xslt requires requestingContext of type Document"); +#ifdef DEBUG + { + nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext); + MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::DOCUMENT_NODE, + "type_xslt requires requestingContext of type Document"); + } +#endif break; } case nsIContentPolicy::TYPE_BEACON: { mimeTypeGuess = EmptyCString(); requestingContext = aLoadInfo->LoadingNode(); - MOZ_ASSERT(!requestingContext || - requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE, - "type_beacon requires requestingContext of type Document"); +#ifdef DEBUG + { + nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext); + MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::DOCUMENT_NODE, + "type_beacon requires requestingContext of type Document"); + } +#endif break; } diff --git a/dom/security/nsContentSecurityManager.h b/dom/security/nsContentSecurityManager.h index 912c0e89f..bab847743 100644 --- a/dom/security/nsContentSecurityManager.h +++ b/dom/security/nsContentSecurityManager.h @@ -32,6 +32,8 @@ public: static nsresult doContentSecurityCheck(nsIChannel* aChannel, nsCOMPtr<nsIStreamListener>& aInAndOutListener); + static bool AllowTopLevelNavigationToDataURI(nsIChannel* aChannel); + private: static nsresult CheckChannel(nsIChannel* aChannel); diff --git a/dom/security/test/general/browser.ini b/dom/security/test/general/browser.ini new file mode 100644 index 000000000..b00baa95d --- /dev/null +++ b/dom/security/test/general/browser.ini @@ -0,0 +1,14 @@ +[DEFAULT] +[browser_test_toplevel_data_navigations.js] +support-files = + file_toplevel_data_navigations.sjs + file_toplevel_data_meta_redirect.html +[browser_test_data_download.js] +support-files = + file_data_download.html +[browser_test_data_text_csv.js] +support-files = + file_data_text_csv.html +[browser_test_view_image_data_navigation.js] +support-files = + file_view_image_data_navigation.html diff --git a/dom/security/test/general/browser_test_data_download.js b/dom/security/test/general/browser_test_data_download.js new file mode 100644 index 000000000..1ee8d5844 --- /dev/null +++ b/dom/security/test/general/browser_test_data_download.js @@ -0,0 +1,37 @@ +"use strict"; + +const kTestPath = getRootDirectory(gTestPath) + .replace("chrome://mochitests/content", "http://example.com") +const kTestURI = kTestPath + "file_data_download.html"; + +function addWindowListener(aURL, aCallback) { + Services.wm.addListener({ + onOpenWindow(aXULWindow) { + info("window opened, waiting for focus"); + Services.wm.removeListener(this); + var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + waitForFocus(function() { + is(domwindow.document.location.href, aURL, "should have seen the right window open"); + aCallback(domwindow); + }, domwindow); + }, + onCloseWindow(aXULWindow) { }, + onWindowTitleChange(aXULWindow, aNewTitle) { } + }); +} + +function test() { + waitForExplicitFinish(); + Services.prefs.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", true); + registerCleanupFunction(function() { + Services.prefs.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations"); + }); + addWindowListener("chrome://mozapps/content/downloads/unknownContentType.xul", function(win) { + is(win.document.getElementById("location").value, "data-foo.html", + "file name of download should match"); + win.close(); + finish(); + }); + gBrowser.loadURI(kTestURI); +} diff --git a/dom/security/test/general/browser_test_data_text_csv.js b/dom/security/test/general/browser_test_data_text_csv.js new file mode 100644 index 000000000..c45e40cc2 --- /dev/null +++ b/dom/security/test/general/browser_test_data_text_csv.js @@ -0,0 +1,37 @@ +"use strict"; + +const kTestPath = getRootDirectory(gTestPath) + .replace("chrome://mochitests/content", "http://example.com") +const kTestURI = kTestPath + "file_data_text_csv.html"; + +function addWindowListener(aURL, aCallback) { + Services.wm.addListener({ + onOpenWindow(aXULWindow) { + info("window opened, waiting for focus"); + Services.wm.removeListener(this); + var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + waitForFocus(function() { + is(domwindow.document.location.href, aURL, "should have seen the right window open"); + aCallback(domwindow); + }, domwindow); + }, + onCloseWindow(aXULWindow) { }, + onWindowTitleChange(aXULWindow, aNewTitle) { } + }); +} + +function test() { + waitForExplicitFinish(); + Services.prefs.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", true); + registerCleanupFunction(function() { + Services.prefs.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations"); + }); + addWindowListener("chrome://mozapps/content/downloads/unknownContentType.xul", function(win) { + is(win.document.getElementById("location").value, "text/csv;foo,bar,foobar", + "file name of download should match"); + win.close(); + finish(); + }); + gBrowser.loadURI(kTestURI); +} diff --git a/dom/security/test/general/browser_test_toplevel_data_navigations.js b/dom/security/test/general/browser_test_toplevel_data_navigations.js new file mode 100644 index 000000000..a13a6350e --- /dev/null +++ b/dom/security/test/general/browser_test_toplevel_data_navigations.js @@ -0,0 +1,54 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +"use strict"; + +const kDataBody = "toplevel navigation to data: URI allowed"; +const kDataURI = "data:text/html,<body>" + kDataBody + "</body>"; +const kTestPath = getRootDirectory(gTestPath) + .replace("chrome://mochitests/content", "http://example.com") +const kRedirectURI = kTestPath + "file_toplevel_data_navigations.sjs"; +const kMetaRedirectURI = kTestPath + "file_toplevel_data_meta_redirect.html"; + +add_task(async function test_nav_data_uri() { + await SpecialPowers.pushPrefEnv({ + "set": [["security.data_uri.block_toplevel_data_uri_navigations", true]], + }); + await BrowserTestUtils.withNewTab(kDataURI, async function(browser) { + await ContentTask.spawn(gBrowser.selectedBrowser, {kDataBody}, async function({kDataBody}) { // eslint-disable-line + is(content.document.body.innerHTML, kDataBody, + "data: URI navigation from system should be allowed"); + }); + }); +}); + +add_task(async function test_nav_data_uri_redirect() { + await SpecialPowers.pushPrefEnv({ + "set": [["security.data_uri.block_toplevel_data_uri_navigations", true]], + }); + let tab = BrowserTestUtils.addTab(gBrowser, kRedirectURI); + registerCleanupFunction(async function() { + await BrowserTestUtils.removeTab(tab); + }); + // wait to make sure data: URI did not load before checking that it got blocked + await new Promise(resolve => setTimeout(resolve, 500)); + await ContentTask.spawn(gBrowser.selectedBrowser, {}, async function() { + is(content.document.body.innerHTML, "", + "data: URI navigation after server redirect should be blocked"); + }); +}); + +add_task(async function test_nav_data_uri_meta_redirect() { + await SpecialPowers.pushPrefEnv({ + "set": [["security.data_uri.block_toplevel_data_uri_navigations", true]], + }); + let tab = BrowserTestUtils.addTab(gBrowser, kMetaRedirectURI); + registerCleanupFunction(async function() { + await BrowserTestUtils.removeTab(tab); + }); + // wait to make sure data: URI did not load before checking that it got blocked + await new Promise(resolve => setTimeout(resolve, 500)); + await ContentTask.spawn(gBrowser.selectedBrowser, {}, async function() { + is(content.document.body.innerHTML, "", + "data: URI navigation after meta redirect should be blocked"); + }); +}); diff --git a/dom/security/test/general/browser_test_view_image_data_navigation.js b/dom/security/test/general/browser_test_view_image_data_navigation.js new file mode 100644 index 000000000..22de35894 --- /dev/null +++ b/dom/security/test/general/browser_test_view_image_data_navigation.js @@ -0,0 +1,30 @@ +"use strict"; + +const TEST_PAGE = getRootDirectory(gTestPath) + "file_view_image_data_navigation.html"; + +add_task(async function test_principal_right_click_open_link_in_new_tab() { + await SpecialPowers.pushPrefEnv({ + "set": [["security.data_uri.block_toplevel_data_uri_navigations", true]], + }); + + await BrowserTestUtils.withNewTab(TEST_PAGE, async function(browser) { + let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, true); + + // simulate right-click->view-image + BrowserTestUtils.waitForEvent(document, "popupshown", false, event => { + // These are operations that must be executed synchronously with the event. + document.getElementById("context-viewimage").doCommand(); + event.target.hidePopup(); + return true; + }); + BrowserTestUtils.synthesizeMouseAtCenter("#testimage", + { type: "contextmenu", button: 2 }, + gBrowser.selectedBrowser); + await loadPromise; + + await ContentTask.spawn(gBrowser.selectedBrowser, {}, async function() { + ok(content.document.location.toString().startsWith("data:image/svg+xml;"), + "data:image/svg navigation allowed through right-click view-image") + }); + }); +}); diff --git a/dom/security/test/general/file_block_toplevel_data_navigation.html b/dom/security/test/general/file_block_toplevel_data_navigation.html new file mode 100644 index 000000000..5fbfdfdef --- /dev/null +++ b/dom/security/test/general/file_block_toplevel_data_navigation.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Toplevel data navigation</title> +</head> +<body> +test1: clicking data: URI tries to navigate window<br/> +<a id="testlink" href="data:text/html,<body>toplevel data: URI navigations should be blocked</body>">click me</a> +<script> + document.getElementById('testlink').click(); +</script> +</body> +</html> diff --git a/dom/security/test/general/file_block_toplevel_data_navigation2.html b/dom/security/test/general/file_block_toplevel_data_navigation2.html new file mode 100644 index 000000000..e0308e1ae --- /dev/null +++ b/dom/security/test/general/file_block_toplevel_data_navigation2.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Toplevel data navigation</title> +</head> +<body> +test2: data: URI in iframe tries to window.open(data:, _blank);<br/> +<iframe id="testFrame" src=""></iframe> +<script> + let DATA_URI = `data:text/html,<body><script> + var win = window.open("data:text/html,<body>toplevel data: URI navigations should be blocked</body>", "_blank"); + setTimeout(function () { + var result = win.document.body.innerHTML === "" ? "blocked" : "navigated"; + parent.postMessage(result, "*"); + win.close(); + }, 1000); + <\/script></body>`; + + window.addEventListener("message", receiveMessage); + function receiveMessage(event) { + window.removeEventListener("message", receiveMessage); + // propagate the information back to the caller + window.opener.postMessage(event.data, "*"); + } + document.getElementById('testFrame').src = DATA_URI; +</script> +</body> +</html> diff --git a/dom/security/test/general/file_block_toplevel_data_navigation3.html b/dom/security/test/general/file_block_toplevel_data_navigation3.html new file mode 100644 index 000000000..34aeddab3 --- /dev/null +++ b/dom/security/test/general/file_block_toplevel_data_navigation3.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Toplevel data navigation</title> +</head> +<body> +test3: performing data: URI navigation through win.loc.href<br/> +<script> + window.location.href = "data:text/html,<body>toplevel data: URI navigations should be blocked</body>"; +</script> +</body> +</html> diff --git a/dom/security/test/general/file_block_toplevel_data_redirect.sjs b/dom/security/test/general/file_block_toplevel_data_redirect.sjs new file mode 100644 index 000000000..64e294cab --- /dev/null +++ b/dom/security/test/general/file_block_toplevel_data_redirect.sjs @@ -0,0 +1,14 @@ +// Custom *.sjs file specifically for the needs of Bug: +// Bug 1394554 - Block toplevel data: URI navigations after redirect + +var DATA_URI = + "<body>toplevel data: URI navigations after redirect should be blocked</body>"; + +function handleRequest(request, response) +{ + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + + response.setStatusLine("1.1", 302, "Found"); + response.setHeader("Location", "data:text/html," + escape(DATA_URI), false); +} diff --git a/dom/security/test/general/file_data_download.html b/dom/security/test/general/file_data_download.html new file mode 100644 index 000000000..4cc92fe8f --- /dev/null +++ b/dom/security/test/general/file_data_download.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test download attribute for data: URI</title> +</head> +<body> + <a href="data:text/html,<body>data download</body>" download="data-foo.html" id="testlink">download data</a> + <script> + // click the link to have the downoad panel appear + let testlink = document.getElementById("testlink"); + testlink.click(); + </script> + </body> +</html> diff --git a/dom/security/test/general/file_data_text_csv.html b/dom/security/test/general/file_data_text_csv.html new file mode 100644 index 000000000..a9ac369d1 --- /dev/null +++ b/dom/security/test/general/file_data_text_csv.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test open data:text/csv</title> +</head> +<body> + <a href="data:text/csv;foo,bar,foobar" id="testlink">test text/csv</a> + <script> + // click the link to have the downoad panel appear + let testlink = document.getElementById("testlink"); + testlink.click(); + </script> + </body> +</html> diff --git a/dom/security/test/general/file_toplevel_data_meta_redirect.html b/dom/security/test/general/file_toplevel_data_meta_redirect.html new file mode 100644 index 000000000..f4f5deb52 --- /dev/null +++ b/dom/security/test/general/file_toplevel_data_meta_redirect.html @@ -0,0 +1,10 @@ +<html>
+<body>
+<head>
+ <meta http-equiv="refresh"
+ content="0; url='data:text/html,<body>toplevel meta redirect to data: URI should be blocked</body>'">
+</head>
+<body>
+Meta Redirect to data: URI
+</body>
+</html>
diff --git a/dom/security/test/general/file_toplevel_data_navigations.sjs b/dom/security/test/general/file_toplevel_data_navigations.sjs new file mode 100644 index 000000000..501b833e5 --- /dev/null +++ b/dom/security/test/general/file_toplevel_data_navigations.sjs @@ -0,0 +1,14 @@ +// Custom *.sjs file specifically for the needs of Bug: +// Bug 1394554 - Block toplevel data: URI navigations after redirect + +var DATA_URI = + "data:text/html,<body>toplevel data: URI navigations after redirect should be blocked</body>"; + +function handleRequest(request, response) +{ + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + + response.setStatusLine("1.1", 302, "Found"); + response.setHeader("Location", DATA_URI, false); +} diff --git a/dom/security/test/general/file_view_image_data_navigation.html b/dom/security/test/general/file_view_image_data_navigation.html new file mode 100644 index 000000000..a3f9acfb4 --- /dev/null +++ b/dom/security/test/general/file_view_image_data_navigation.html @@ -0,0 +1,12 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1407891: Test navigation for right-click view-image on data:image/svg</title> +</head> +<body> + +<img id="testimage" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNOCwxMkwzLDcsNCw2bDQsNCw0LTQsMSwxWiIgZmlsbD0iIzZBNkE2QSIgLz4KPC9zdmc+Cg=="></img> + +</body> +</html> diff --git a/dom/security/test/general/mochitest.ini b/dom/security/test/general/mochitest.ini index 70c0c9fb6..f3bcca072 100644 --- a/dom/security/test/general/mochitest.ini +++ b/dom/security/test/general/mochitest.ini @@ -3,7 +3,19 @@ support-files = file_contentpolicytype_targeted_link_iframe.sjs file_nosniff_testserver.sjs file_block_script_wrong_mime_server.sjs + file_block_toplevel_data_navigation.html + file_block_toplevel_data_navigation2.html + file_block_toplevel_data_navigation3.html + file_block_toplevel_data_redirect.sjs [test_contentpolicytype_targeted_link_iframe.html] [test_nosniff.html] [test_block_script_wrong_mime.html] +[test_block_toplevel_data_navigation.html] +skip-if = toolkit == 'android' # intermittent failure +[test_block_toplevel_data_img_navigation.html] +skip-if = toolkit == 'android' # intermittent failure +[test_allow_opening_data_pdf.html] +skip-if = toolkit == 'android' +[test_allow_opening_data_json.html] +skip-if = toolkit == 'android' diff --git a/dom/security/test/general/test_allow_opening_data_json.html b/dom/security/test/general/test_allow_opening_data_json.html new file mode 100644 index 000000000..1530a24e8 --- /dev/null +++ b/dom/security/test/general/test_allow_opening_data_json.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1403814: Allow toplevel data URI navigation data:application/json</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function test_toplevel_data_json() { + const DATA_JSON = "data:application/json,{'my_json_key':'my_json_value'}"; + + let win = window.open(DATA_JSON); + let wrappedWin = SpecialPowers.wrap(win); + + // Unfortunately we can't detect whether the JSON has loaded or not using some + // event, hence we are constantly polling location.href till we see that + // the data: URI appears. Test times out on failure. + var jsonLoaded = setInterval(function() { + if (wrappedWin.document.location.href.startsWith("data:application/json")) { + clearInterval(jsonLoaded); + ok(true, "navigating to data:application/json allowed"); + wrappedWin.close(); + SimpleTest.finish(); + } + }, 200); +} + +SpecialPowers.pushPrefEnv({ + set: [["security.data_uri.block_toplevel_data_uri_navigations", true]] +}, test_toplevel_data_json); + +</script> +</body> +</html> diff --git a/dom/security/test/general/test_allow_opening_data_pdf.html b/dom/security/test/general/test_allow_opening_data_pdf.html new file mode 100644 index 000000000..6b51fe57b --- /dev/null +++ b/dom/security/test/general/test_allow_opening_data_pdf.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1398692: Allow toplevel navigation to a data:application/pdf</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function test_toplevel_data_pdf() { + // The PDF contains one page and it is a 3/72" square, the minimum allowed by the spec + const DATA_PDF = + "data:application/pdf;base64,JVBERi0xLjANCjEgMCBvYmo8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PmVuZG9iaiAyIDAgb2JqPDwvVHlwZS9QYWdlcy9LaWRzWzMgMCBSXS9Db3VudCAxPj5lbmRvYmogMyAwIG9iajw8L1R5cGUvUGFnZS9NZWRpYUJveFswIDAgMyAzXT4+ZW5kb2JqDQp4cmVmDQowIDQNCjAwMDAwMDAwMDAgNjU1MzUgZg0KMDAwMDAwMDAxMCAwMDAwMCBuDQowMDAwMDAwMDUzIDAwMDAwIG4NCjAwMDAwMDAxMDIgMDAwMDAgbg0KdHJhaWxlcjw8L1NpemUgNC9Sb290IDEgMCBSPj4NCnN0YXJ0eHJlZg0KMTQ5DQolRU9G"; + + let win = window.open(DATA_PDF); + let wrappedWin = SpecialPowers.wrap(win); + + // Unfortunately we can't detect whether the PDF has loaded or not using some + // event, hence we are constantly polling location.href till we see that + // the data: URI appears. Test times out on failure. + var pdfLoaded = setInterval(function() { + if (wrappedWin.document.location.href.startsWith("data:application/pdf")) { + clearInterval(pdfLoaded); + ok(true, "navigating to data:application/pdf allowed"); + wrappedWin.close(); + SimpleTest.finish(); + } + }, 200); +} + +SpecialPowers.pushPrefEnv({ + set: [["security.data_uri.block_toplevel_data_uri_navigations", true]] +}, test_toplevel_data_pdf); + +</script> +</body> +</html> diff --git a/dom/security/test/general/test_block_toplevel_data_img_navigation.html b/dom/security/test/general/test_block_toplevel_data_img_navigation.html new file mode 100644 index 000000000..7f8dfc748 --- /dev/null +++ b/dom/security/test/general/test_block_toplevel_data_img_navigation.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1396798: Do not block toplevel data: navigation to image (except svgs)</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +SpecialPowers.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", true); +SimpleTest.registerCleanupFunction(() => { + SpecialPowers.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations"); +}); + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("have to test that top level data:image loading is blocked/allowed"); + +function test_toplevel_data_image() { + const DATA_PNG = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; + let win1 = window.open(DATA_PNG); + let wrappedWin1 = SpecialPowers.wrap(win1); + setTimeout(function () { + let images = wrappedWin1.document.getElementsByTagName('img'); + is(images.length, 1, "Loading data:image/png should be allowed"); + is(images[0].src, DATA_PNG, "Sanity: img src matches"); + wrappedWin1.close(); + test_toplevel_data_image_svg(); + }, 1000); +} + +function test_toplevel_data_image_svg() { + const DATA_SVG = + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNOCwxMkwzLDcsNCw2bDQsNCw0LTQsMSwxWiIgZmlsbD0iIzZBNkE2QSIgLz4KPC9zdmc+Cg=="; + let win2 = window.open(DATA_SVG); + // Unfortunately we can't detect whether the window was closed using some event, + // hence we are constantly polling till we see that win == null. + // Test times out on failure. + var win2Closed = setInterval(function() { + if (win2 == null || win2.closed) { + clearInterval(win2Closed); + ok(true, "Loading data:image/svg+xml should be blocked"); + SimpleTest.finish(); + } + }, 200); +} +// fire up the tests +test_toplevel_data_image(); + +</script> +</body> +</html> diff --git a/dom/security/test/general/test_block_toplevel_data_navigation.html b/dom/security/test/general/test_block_toplevel_data_navigation.html new file mode 100644 index 000000000..cef232b65 --- /dev/null +++ b/dom/security/test/general/test_block_toplevel_data_navigation.html @@ -0,0 +1,86 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1331351 - Block top level window data: URI navigations</title> + <!-- Including SimpleTest.js so we can use waitForExplicitFinish !--> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +SpecialPowers.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", true); +SimpleTest.registerCleanupFunction(() => { + SpecialPowers.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations"); +}); + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("have to test that top level data: URI navgiation is blocked"); + +function test1() { + // simple data: URI click navigation should be prevented + let TEST_FILE = "file_block_toplevel_data_navigation.html"; + let win1 = window.open(TEST_FILE); + setTimeout(function () { + ok(SpecialPowers.wrap(win1).document.body.innerHTML.indexOf("test1:") !== -1, + "toplevel data: URI navigation through click() should be blocked"); + win1.close(); + test2(); + }, 1000); +} + +function test2() { + // data: URI in iframe which opens data: URI in _blank should be blocked + let win2 = window.open("file_block_toplevel_data_navigation2.html"); + window.addEventListener("message", receiveMessage); + function receiveMessage(event) { + window.removeEventListener("message", receiveMessage); + is(event.data, "blocked", + "data: URI navigation using _blank from data: URI should be blocked"); + win2.close(); + test3(); + } +} + +function test3() { + // navigating to a data: URI using window.location.href should be blocked + let win3 = window.open("file_block_toplevel_data_navigation3.html"); + setTimeout(function () { + ok(win3.document.body.innerHTML.indexOf("test3:") !== -1, + "data: URI navigation through win.loc.href should be blocked"); + win3.close(); + test4(); + }, 1000); +} + +function test4() { + // navigating to a data: URI using window.open() should be blocked + let win4 = window.open("data:text/html,<body>toplevel data: URI navigations should be blocked</body>"); + setTimeout(function () { + // Please note that the data: URI will be displayed in the URL-Bar but not + // loaded, hence we rather rely on document.body than document.location + is(win4.document.body.innerHTML, "", + "navigating to a data: URI using window.open() should be blocked"); + test5(); + }, 1000); +} + +function test5() { + // navigating to a URI which redirects to a data: URI using window.open() should be blocked + let win5 = window.open("file_block_toplevel_data_redirect.sjs"); + setTimeout(function () { + // Please note that the data: URI will be displayed in the URL-Bar but not + // loaded, hence we rather rely on document.body than document.location + is(SpecialPowers.wrap(win5).document.body.innerHTML, "", + "navigating to URI which redirects to a data: URI using window.open() should be blocked"); + win5.close(); + SimpleTest.finish(); + }, 1000); +} + +// fire up the tests +test1(); + +</script> +</body> +</html> diff --git a/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html b/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html index 7b1ab72dc..3ef243824 100644 --- a/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html +++ b/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html @@ -61,6 +61,7 @@ var policy = { "content policy type should TYPESUBDOCUMENT"); categoryManager.deleteCategoryEntry("content-policy", POLICYNAME, false); SimpleTest.finish(); + return Ci.nsIContentPolicy.REJECT_REQUEST; } return Ci.nsIContentPolicy.ACCEPT; }, diff --git a/dom/security/test/moz.build b/dom/security/test/moz.build index ddb4e9b89..946959dee 100644 --- a/dom/security/test/moz.build +++ b/dom/security/test/moz.build @@ -27,5 +27,6 @@ MOCHITEST_CHROME_MANIFESTS += [ BROWSER_CHROME_MANIFESTS += [ 'contentverifier/browser.ini', 'csp/browser.ini', + 'general/browser.ini', 'hsts/browser.ini', ] diff --git a/dom/url/URL.cpp b/dom/url/URL.cpp index 1f15e1151..c8724c359 100644 --- a/dom/url/URL.cpp +++ b/dom/url/URL.cpp @@ -17,7 +17,6 @@ #include "nsEscape.h" #include "nsHostObjectProtocolHandler.h" #include "nsIIOService.h" -#include "nsIURIWithQuery.h" #include "nsIURL.h" #include "nsNetCID.h" #include "nsNetUtil.h" @@ -525,21 +524,10 @@ URLMainThread::GetPathname(nsAString& aPathname, ErrorResult& aRv) const // Do not throw! Not having a valid URI or URL should result in an empty // string. - nsCOMPtr<nsIURIWithQuery> url(do_QueryInterface(mURI)); - if (url) { - nsAutoCString file; - nsresult rv = url->GetFilePath(file); - if (NS_SUCCEEDED(rv)) { - CopyUTF8toUTF16(file, aPathname); - } - - return; - } - - nsAutoCString path; - nsresult rv = mURI->GetPath(path); + nsAutoCString file; + nsresult rv = mURI->GetFilePath(file); if (NS_SUCCEEDED(rv)) { - CopyUTF8toUTF16(path, aPathname); + CopyUTF8toUTF16(file, aPathname); } } @@ -548,11 +536,7 @@ URLMainThread::SetPathname(const nsAString& aPathname, ErrorResult& aRv) { // Do not throw! - nsCOMPtr<nsIURIWithQuery> url(do_QueryInterface(mURI)); - if (url) { - url->SetFilePath(NS_ConvertUTF16toUTF8(aPathname)); - return; - } + mURI->SetFilePath(NS_ConvertUTF16toUTF8(aPathname)); } void @@ -566,13 +550,9 @@ URLMainThread::GetSearch(nsAString& aSearch, ErrorResult& aRv) const nsAutoCString search; nsresult rv; - nsCOMPtr<nsIURIWithQuery> url(do_QueryInterface(mURI)); - if (url) { - rv = url->GetQuery(search); - if (NS_SUCCEEDED(rv) && !search.IsEmpty()) { - CopyUTF8toUTF16(NS_LITERAL_CSTRING("?") + search, aSearch); - } - return; + rv = mURI->GetQuery(search); + if (NS_SUCCEEDED(rv) && !search.IsEmpty()) { + CopyUTF8toUTF16(NS_LITERAL_CSTRING("?") + search, aSearch); } } @@ -603,11 +583,7 @@ URLMainThread::SetSearchInternal(const nsAString& aSearch, ErrorResult& aRv) { // Ignore failures to be compatible with NS4. - nsCOMPtr<nsIURIWithQuery> uriWithQuery(do_QueryInterface(mURI)); - if (uriWithQuery) { - uriWithQuery->SetQuery(NS_ConvertUTF16toUTF8(aSearch)); - return; - } + mURI->SetQuery(NS_ConvertUTF16toUTF8(aSearch)); } } // anonymous namespace |