Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(205)

Unified Diff: content/browser/frame_host/data_url_navigation_browsertest.cc

Issue 2702503002: Block renderer-initiated main frame navigations to data URLs (Closed)
Patch Set: Re-block data to data navigations, rebase, address nasko comments Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: content/browser/frame_host/data_url_navigation_browsertest.cc
diff --git a/content/browser/frame_host/data_url_navigation_browsertest.cc b/content/browser/frame_host/data_url_navigation_browsertest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..cf86b6ff02ce407c2eba8f825a1a44d0b2b7d1f4
--- /dev/null
+++ b/content/browser/frame_host/data_url_navigation_browsertest.cc
@@ -0,0 +1,906 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/strings/pattern.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "build/buildflag.h"
+#include "content/browser/site_per_process_browsertest.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/browser_side_navigation_policy.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/download_test_observer.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "content/shell/browser/shell.h"
+#include "content/shell/browser/shell_download_manager_delegate.h"
+#include "net/base/escape.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "ppapi/features/features.h"
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+#include "content/public/browser/plugin_service.h"
+#include "content/public/common/webplugininfo.h"
+#endif
+
+namespace content {
+
+namespace {
+
+// The pattern to catch messages printed by the browser when a data URL
+// navigation is blocked.
+const char kDataUrlBlockedPattern[] =
+ "Not allowed to top-level navigate to resource:*";
+
+// The message printed by the data URL when it successfully navigates.
+const char kDataUrlSuccessfulMessage[] = "NAVIGATION_SUCCESSFUL";
+
+// A "Hello World" PDF encoded as a data URL. Source of this PDF:
+// -------------------------
+// %PDF-1.7
+// 1 0 obj << /Type /Page /Parent 3 0 R /Resources 5 0 R /Contents 2 0 R >>
+// endobj
+// 2 0 obj << /Length 51 >>
+// stream BT
+// /F1 12 Tf
+// 1 0 0 1 100 20 Tm
+// (Hello World)Tj
+// ET
+// endstream
+// endobj
+// 3 0 obj << /Type /Pages /Kids [ 1 0 R ] /Count 1 /MediaBox [ 0 0 300 50] >>
+// endobj
+// 4 0 obj << /Type /Font /Subtype /Type1 /Name /F1 /BaseFont/Arial >>
+// endobj
+// 5 0 obj << /ProcSet[/PDF/Text] /Font <</F1 4 0 R >> >>
+// endobj
+// 6 0 obj << /Type /Catalog /Pages 3 0 R >>
+// endobj
+// trailer << /Root 6 0 R >>
+// -------------------------
+const char kPdfUrl[] =
+ "data:application/pdf;base64,JVBERi0xLjcKMSAwIG9iaiA8PCAvVHlwZSAvUGFnZSAvUG"
+ "FyZW50IDMgMCBSIC9SZXNvdXJjZXMgNSAwIFIgL0NvbnRlbnRzIDIgMCBSID4+CmVuZG9iagoy"
+ "IDAgb2JqIDw8IC9MZW5ndGggNTEgPj4KIHN0cmVhbSBCVAogL0YxIDEyIFRmCiAxIDAgMCAxID"
+ "EwMCAyMCBUbQogKEhlbGxvIFdvcmxkKVRqCiBFVAogZW5kc3RyZWFtCmVuZG9iagozIDAgb2Jq"
+ "IDw8IC9UeXBlIC9QYWdlcyAvS2lkcyBbIDEgMCBSIF0gL0NvdW50IDEgL01lZGlhQm94IFsgMC"
+ "AwIDMwMCA1MF0gPj4KZW5kb2JqCjQgMCBvYmogPDwgL1R5cGUgL0ZvbnQgL1N1YnR5cGUgL1R5"
+ "cGUxIC9OYW1lIC9GMSAvQmFzZUZvbnQvQXJpYWwgPj4KZW5kb2JqCjUgMCBvYmogPDwgL1Byb2"
+ "NTZXRbL1BERi9UZXh0XSAvRm9udCA8PC9GMSA0IDAgUiA+PiA+PgplbmRvYmoKNiAwIG9iaiA8"
+ "PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMyAwIFIgPj4KZW5kb2JqCnRyYWlsZXIgPDwgL1Jvb3"
+ "QgNiAwIFIgPj4K";
+
+enum ExpectedNavigationStatus { NAVIGATION_BLOCKED, NAVIGATION_ALLOWED };
+
+// This class is similar to ConsoleObserverDelegate in that it listens and waits
+// for specific console messages. The difference from ConsoleObserverDelegate is
+// that this class immediately stops waiting if it sees a message matching
+// fail_pattern, instead of waiting for a message matching success_pattern.
nasko 2017/04/05 23:55:18 nit: I'd rather build that functionality into the
meacer 2017/04/06 01:25:41 Filed https://crbug.com/708831
+class DataURLWarningConsoleObserverDelegate : public WebContentsDelegate {
+ public:
+ DataURLWarningConsoleObserverDelegate(
+ WebContents* web_contents,
+ ExpectedNavigationStatus expected_navigation_status)
+ : web_contents_(web_contents),
+ success_filter_(expected_navigation_status == NAVIGATION_ALLOWED
+ ? kDataUrlSuccessfulMessage
+ : kDataUrlBlockedPattern),
+ fail_filter_(expected_navigation_status == NAVIGATION_ALLOWED
+ ? kDataUrlBlockedPattern
+ : kDataUrlSuccessfulMessage),
+ message_loop_runner_(new MessageLoopRunner()),
nasko 2017/04/05 23:55:18 You need to pass IMMEDIATE for QuitMode, otherwise
meacer 2017/04/06 01:25:42 Done. I suppose we want the same for the other Con
nasko 2017/04/06 16:57:26 Probably, but I'd suggest doing it separately.
meacer 2017/04/06 18:14:19 Will do it as part of https://crbug.com/708831
+ saw_failure_message_(false) {}
+ ~DataURLWarningConsoleObserverDelegate() override {}
+
+ void Wait() { message_loop_runner_->Run(); }
+
+ // WebContentsDelegate method:
+ bool DidAddMessageToConsole(WebContents* source,
+ int32_t level,
+ const base::string16& message,
+ int32_t line_no,
+ const base::string16& source_id) override {
+ DCHECK(source == web_contents_);
+ const std::string ascii_message = base::UTF16ToASCII(message);
+ if (base::MatchPattern(ascii_message, fail_filter_)) {
+ saw_failure_message_ = true;
+ message_loop_runner_->Quit();
+ }
+ if (base::MatchPattern(ascii_message, success_filter_)) {
+ message_loop_runner_->Quit();
+ }
+ return false;
+ }
+
+ // Returns true if the observer encountered a message that matches
+ // |fail_filter_|.
+ bool saw_failure_message() const { return saw_failure_message_; }
+
+ private:
+ WebContents* web_contents_;
+ const std::string success_filter_;
+ const std::string fail_filter_;
+ scoped_refptr<MessageLoopRunner> message_loop_runner_;
+ bool saw_failure_message_;
+};
+
+enum TestNavigationType {
+ NO_PLZNAVIGATE,
+ PLZNAVIGATE,
+};
nasko 2017/04/05 23:55:18 Why does this enum differ from the one below in te
meacer 2017/04/06 01:25:42 Looks like the extra comma caused clang-format to
nasko 2017/04/06 16:57:26 Can we remove that and make it match the one below
meacer 2017/04/06 18:14:19 I removed the test parameters from the last patchs
+
+enum TestSitePerProcessType { NO_SITE_PER_PROCESS, SITE_PER_PROCESS };
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+// This class registers a fake PDF plugin handler so that data URL navigations
+// with a PDF mime type end up with a navigation and don't simply download the
+// file.
+class ScopedPluginRegister {
+ public:
+ ScopedPluginRegister(content::PluginService* plugin_service)
+ : plugin_service_(plugin_service) {
+ const char kPluginName[] = "PDF";
+ const char kPdfMimeType[] = "application/pdf";
+ const char kPdfFileType[] = "pdf";
+ WebPluginInfo plugin_info;
+ plugin_info.type = WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS;
+ plugin_info.name = base::ASCIIToUTF16(kPluginName);
+ plugin_info.mime_types.push_back(
+ WebPluginMimeType(kPdfMimeType, kPdfFileType, std::string()));
+ plugin_service_->RegisterInternalPlugin(plugin_info, false);
+ plugin_service_->RefreshPlugins();
+ }
+
+ ~ScopedPluginRegister() {
+ std::vector<WebPluginInfo> plugins;
+ plugin_service_->GetInternalPlugins(&plugins);
+ EXPECT_EQ(1u, plugins.size());
+ plugin_service_->UnregisterInternalPlugin(plugins[0].path);
+ plugin_service_->RefreshPlugins();
+
+ plugins.clear();
+ plugin_service_->GetInternalPlugins(&plugins);
+ EXPECT_TRUE(plugins.empty());
+ }
+
+ private:
+ content::PluginService* plugin_service_;
+};
+#endif // BUILDFLAG(ENABLE_PLUGINS)
+
+} // namespace
+
+class DataUrlNavigationBrowserTest
+ : public ContentBrowserTest,
+ public testing::WithParamInterface<
+ std::tuple<TestNavigationType, TestSitePerProcessType>> {
nasko 2017/04/05 23:55:18 I wonder if you need all this complexity in your t
meacer 2017/04/06 01:25:42 I noticed that trybots did these, but I also found
+ public:
+#if BUILDFLAG(ENABLE_PLUGINS)
+ DataUrlNavigationBrowserTest()
+ : scoped_plugin_register_(PluginService::GetInstance()) {}
+#else
+ DataUrlNavigationBrowserTest() {}
+#endif // BUILDFLAG(ENABLE_PLUGINS)
+
+ protected:
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ if (IsPlzNavigateTest()) {
+ command_line->AppendSwitch(switches::kEnableBrowserSideNavigation);
+ }
+ if (IsSitePerProcessTest()) {
+ IsolateAllSitesForTesting(command_line);
+ }
+ }
+
+ void SetUpOnMainThread() override {
+ host_resolver()->AddRule("*", "127.0.0.1");
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ base::FilePath path;
+ ASSERT_TRUE(PathService::Get(content::DIR_TEST_DATA, &path));
+ path = path.AppendASCII("data_url_navigations.html");
+ ASSERT_TRUE(base::PathExists(path));
+
+ std::string contents;
+ ASSERT_TRUE(base::ReadFileToString(path, &contents));
+ data_url_ = GURL(std::string("data:text/html,") + contents);
+
+ ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir());
+ ShellDownloadManagerDelegate* delegate =
+ static_cast<ShellDownloadManagerDelegate*>(
+ shell()
+ ->web_contents()
+ ->GetBrowserContext()
+ ->GetDownloadManagerDelegate());
+ delegate->SetDownloadBehaviorForTesting(downloads_directory_.GetPath());
+ }
+
+ // Adds an iframe to |rfh| pointing to |url|.
+ void AddIFrame(RenderFrameHost* rfh, const GURL& url) {
+ const std::string javascript = base::StringPrintf(
+ "f = document.createElement('iframe'); f.src = '%s';"
+ "document.body.appendChild(f);",
+ url.spec().c_str());
+ bool iframe_loaded = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(rfh, javascript, &iframe_loaded));
+ EXPECT_TRUE(iframe_loaded);
nasko 2017/04/05 23:55:18 Is the goal of this code to wait until the load ha
meacer 2017/04/06 01:25:42 Done.
+ }
+
+ // Runs |javascript| on the first child frame and checks for a navigation.
+ void TestNavigationFromFrame(
+ const std::string& javascript,
+ ExpectedNavigationStatus expected_navigation_status) {
+ RenderFrameHost* child =
+ ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+ ASSERT_TRUE(child);
+ if (IsSitePerProcessTest()) {
+ ASSERT_TRUE(child->IsCrossProcessSubframe());
+ }
+ ExecuteScriptAndCheckNavigation(child, javascript,
+ expected_navigation_status);
+ }
+
+ // Runs |javascript| on the first child frame and expects a download to occur.
+ void TestDownloadFromFrame(const std::string& javascript) {
+ RenderFrameHost* child =
+ ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+ ASSERT_TRUE(child);
+ if (IsSitePerProcessTest()) {
+ ASSERT_TRUE(child->IsCrossProcessSubframe());
+ }
+ ExecuteScriptAndCheckNavigationDownload(child, javascript);
+ }
+
+ // Runs |javascript| on the first child frame and checks for a navigation to
+ // the PDF file pointed by the test case.
+ void TestPDFNavigationFromFrame(
+ const std::string& javascript,
+ ExpectedNavigationStatus expected_navigation_status) {
+ RenderFrameHost* child =
+ ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+ ASSERT_TRUE(child);
+ if (IsSitePerProcessTest()) {
+ ASSERT_TRUE(child->IsCrossProcessSubframe());
+ }
+ ExecuteScriptAndCheckPDFNavigation(child, javascript,
+ expected_navigation_status);
+ }
+
+ // Same as TestNavigationFromFrame, but instead of navigating, the child frame
+ // tries to open a new window with a data URL.
+ void TestWindowOpenFromFrame(
+ const std::string& javascript,
+ ExpectedNavigationStatus expected_navigation_status) {
+ RenderFrameHost* child =
+ ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+ if (IsSitePerProcessTest()) {
+ ASSERT_TRUE(child->IsCrossProcessSubframe());
+ }
+ ExecuteScriptAndCheckWindowOpen(child, javascript,
+ expected_navigation_status);
+ }
+
+ // Executes |javascript| on |rfh| and waits for a console message based on
+ // |expected_navigation_status|.
+ // - Blocked navigations should print kDataUrlBlockedPattern.
+ // - Allowed navigations should print kDataUrlSuccessfulMessage.
+ void ExecuteScriptAndCheckNavigation(
+ RenderFrameHost* rfh,
+ const std::string& javascript,
+ ExpectedNavigationStatus expected_navigation_status) {
+ const GURL original_url(shell()->web_contents()->GetLastCommittedURL());
+ const std::string expected_message;
+
+ DataURLWarningConsoleObserverDelegate console_delegate(
+ shell()->web_contents(), expected_navigation_status);
+ shell()->web_contents()->SetDelegate(&console_delegate);
+
+ TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
nasko 2017/04/05 23:55:19 nit: I think the default constructor uses 1 naviga
meacer 2017/04/06 01:25:42 Done.
+ EXPECT_TRUE(ExecuteScript(rfh, javascript));
+ console_delegate.Wait();
+ EXPECT_FALSE(console_delegate.saw_failure_message());
+ shell()->web_contents()->SetDelegate(nullptr);
+
+ switch (expected_navigation_status) {
+ case NAVIGATION_ALLOWED:
+ navigation_observer.Wait();
+ // The new page should have a data URL.
+ EXPECT_TRUE(
+ shell()->web_contents()->GetURL().SchemeIs(url::kDataScheme));
+ EXPECT_TRUE(shell()
+ ->web_contents()
+ ->GetController()
+ .GetLastCommittedEntry()
+ ->GetURL()
nasko 2017/04/05 23:55:18 All that can be done with WebContents::GetLastComm
meacer 2017/04/06 01:25:42 Done.
+ .SchemeIs(url::kDataScheme));
+ EXPECT_TRUE(navigation_observer.last_navigation_url().SchemeIs(
+ url::kDataScheme));
+ EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
+ break;
+
+ case NAVIGATION_BLOCKED:
+ // Original page shouldn't navigate away.
+ EXPECT_EQ(original_url, shell()->web_contents()->GetURL());
+ EXPECT_EQ(original_url, shell()
+ ->web_contents()
+ ->GetController()
+ .GetLastCommittedEntry()
+ ->GetURL());
+ EXPECT_FALSE(navigation_observer.last_navigation_succeeded());
+ break;
+
+ default:
+ NOTREACHED();
+ }
+ }
+
+ // Similar to ExecuteScriptAndCheckNavigation(), but doesn't wait for a
+ // console message if the navigation is expected to be allowed (this is
+ // because PDF files can't print to the console).
+ void ExecuteScriptAndCheckPDFNavigation(
+ RenderFrameHost* rfh,
+ const std::string& javascript,
+ ExpectedNavigationStatus expected_navigation_status) {
+ const GURL original_url(shell()->web_contents()->GetLastCommittedURL());
+
+ const std::string expected_message =
+ (expected_navigation_status == NAVIGATION_ALLOWED)
+ ? std::string()
+ : kDataUrlBlockedPattern;
+
+ std::unique_ptr<ConsoleObserverDelegate> console_delegate;
+ if (!expected_message.empty()) {
+ console_delegate.reset(new ConsoleObserverDelegate(
+ shell()->web_contents(), expected_message));
+ shell()->web_contents()->SetDelegate(console_delegate.get());
+ }
+
+ TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
+ EXPECT_TRUE(ExecuteScript(rfh, javascript));
+
+ if (console_delegate) {
+ console_delegate->Wait();
+ shell()->web_contents()->SetDelegate(nullptr);
+ }
+
+ switch (expected_navigation_status) {
+ case NAVIGATION_ALLOWED:
+ navigation_observer.Wait();
+ // The new page should have a data URL.
+ EXPECT_TRUE(
+ shell()->web_contents()->GetURL().SchemeIs(url::kDataScheme));
+ EXPECT_TRUE(shell()
+ ->web_contents()
+ ->GetController()
+ .GetLastCommittedEntry()
+ ->GetURL()
+ .SchemeIs(url::kDataScheme));
+ EXPECT_TRUE(navigation_observer.last_navigation_url().SchemeIs(
+ url::kDataScheme));
+ EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
+ break;
+
+ case NAVIGATION_BLOCKED:
+ // Original page shouldn't navigate away.
+ EXPECT_EQ(original_url, shell()->web_contents()->GetURL());
+ EXPECT_EQ(original_url, shell()
+ ->web_contents()
+ ->GetController()
+ .GetLastCommittedEntry()
+ ->GetURL());
+ EXPECT_FALSE(navigation_observer.last_navigation_succeeded());
+ break;
+
+ default:
+ NOTREACHED();
+ }
+ }
+
+ // Executes |javascript| on |rfh| and waits for a new window to be opened.
+ // Does not check for console messages (it's currently not possible to
+ // concurrently wait for a new shell to be created and a console message to be
+ // printed on that new shell).
nasko 2017/04/05 23:55:18 Why not? Once you get the shell from the observer,
meacer 2017/04/06 01:25:42 Doesn't work unfortunately. The console message ar
nasko 2017/04/06 16:57:26 Hmm, maybe we can follow up here once I'm back, bu
meacer 2017/04/06 18:14:19 An alternative is to print the logging message to
+ void ExecuteScriptAndCheckWindowOpen(
+ RenderFrameHost* rfh,
+ const std::string& javascript,
+ ExpectedNavigationStatus expected_navigation_status) {
+ ShellAddedObserver new_shell_observer;
+ EXPECT_TRUE(ExecuteScript(rfh, javascript));
+
+ Shell* new_shell = new_shell_observer.GetShell();
+ WaitForLoadStop(new_shell->web_contents());
+
+ switch (expected_navigation_status) {
+ case NAVIGATION_ALLOWED:
+ EXPECT_TRUE(new_shell->web_contents()->GetLastCommittedURL().SchemeIs(
+ url::kDataScheme));
+ break;
+
+ case NAVIGATION_BLOCKED:
+ EXPECT_TRUE(
+ new_shell->web_contents()->GetLastCommittedURL().is_empty());
+ break;
+
+ default:
+ NOTREACHED();
+ }
+ }
+
+ // Executes |javascript| on |rfh| and waits for a download to be started by
+ // a window.open call.
+ void ExecuteScriptAndCheckWindowOpenDownload(RenderFrameHost* rfh,
+ const std::string& javascript) {
+ const GURL original_url(shell()->web_contents()->GetLastCommittedURL());
+ ShellAddedObserver new_shell_observer;
+ EXPECT_TRUE(ExecuteScript(rfh, javascript));
+ Shell* new_shell = new_shell_observer.GetShell();
+
+ DownloadManager* download_manager = BrowserContext::GetDownloadManager(
+ new_shell->web_contents()->GetBrowserContext());
nasko 2017/04/05 23:55:18 nit: The BrowserContext is the same across all win
meacer 2017/04/06 01:25:42 Doesn't change the test much, but moved above.
+ DownloadTestObserverTerminal download_observer(
+ download_manager, 1, DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
+
+ WaitForLoadStop(new_shell->web_contents());
+ // If no download happens, this will timeout.
+ download_observer.WaitForFinished();
+
+ EXPECT_TRUE(new_shell->web_contents()->GetURL().spec().empty());
nasko 2017/04/05 23:55:18 nit: GetLastCommittedURL, GetURL is deprecated.
meacer 2017/04/06 01:25:42 Removed GetURL from here and elsewhere.
+ // No navigation should commit.
+ EXPECT_FALSE(
+ new_shell->web_contents()->GetController().GetLastCommittedEntry());
+ // Original page shouldn't navigate away.
+ EXPECT_EQ(original_url, shell()->web_contents()->GetURL());
+ EXPECT_EQ(original_url, shell()
+ ->web_contents()
+ ->GetController()
+ .GetLastCommittedEntry()
+ ->GetURL());
+ }
+
+ // Executes |javascript| on |rfh| and waits for a download to be started.
+ void ExecuteScriptAndCheckNavigationDownload(RenderFrameHost* rfh,
+ const std::string& javascript) {
+ const GURL original_url(shell()->web_contents()->GetLastCommittedURL());
+ DownloadManager* download_manager = BrowserContext::GetDownloadManager(
+ shell()->web_contents()->GetBrowserContext());
+ DownloadTestObserverTerminal download_observer(
+ download_manager, 1, DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
+
+ EXPECT_TRUE(ExecuteScript(rfh, javascript));
+ // If no download happens, this will timeout.
+ download_observer.WaitForFinished();
+
+ // Original page shouldn't navigate away.
+ EXPECT_EQ(original_url, shell()->web_contents()->GetURL());
+ EXPECT_EQ(original_url, shell()
+ ->web_contents()
+ ->GetController()
+ .GetLastCommittedEntry()
+ ->GetURL());
+ }
+
+ // data URL form of the file at content/test/data/data_url_navigations.html
+ GURL data_url() const { return data_url_; }
+
+ base::ScopedTempDir downloads_directory_;
nasko 2017/04/05 23:55:18 Why does this need to be protected, can't it be pr
meacer 2017/04/06 01:25:41 Done.
+
+ private:
+ bool IsPlzNavigateTest() const {
+ return std::get<0>(GetParam()) == PLZNAVIGATE;
+ }
+
+ bool IsSitePerProcessTest() const {
+ return std::get<1>(GetParam()) == SITE_PER_PROCESS;
+ }
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+ ScopedPluginRegister scoped_plugin_register_;
+#endif // BUILDFLAG(ENABLE_PLUGINS)
+
+ GURL data_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataUrlNavigationBrowserTest);
+};
+
+INSTANTIATE_TEST_CASE_P(
+ ,
+ DataUrlNavigationBrowserTest,
+ ::testing::Combine(::testing::Values(NO_PLZNAVIGATE, PLZNAVIGATE),
+ ::testing::Values(NO_SITE_PER_PROCESS,
+ SITE_PER_PROCESS)));
+
+////////////////////////////////////////////////////////////////////////////////
+// data URLs with HTML mimetype
+//
+// Tests that a direct navigation to a data URL doesn't show a console warning
nasko 2017/04/05 23:55:18 nit: s/direct/browser-initiated/
meacer 2017/04/06 01:25:42 Done.
+// and is not blocked.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, BrowserInitiated_Allow) {
+ DataURLWarningConsoleObserverDelegate console_delegate(
+ shell()->web_contents(), NAVIGATION_ALLOWED);
+ shell()->web_contents()->SetDelegate(&console_delegate);
+
+ NavigateToURL(shell(), GURL("data:text/"
+ "html,<html><script>console.log('NAVIGATION_"
+ "SUCCESSFUL');</script>"));
+ console_delegate.Wait();
+ shell()->web_contents()->SetDelegate(nullptr);
+
+ EXPECT_TRUE(shell()->web_contents()->GetURL().SchemeIs(url::kDataScheme));
+ EXPECT_TRUE(shell()
+ ->web_contents()
+ ->GetController()
+ .GetLastCommittedEntry()
+ ->GetURL()
+ .SchemeIs(url::kDataScheme));
+}
+
+// Tests that a content initiated navigation to a data URL is blocked.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, HTML_Navigation_Block) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("/data_url_navigations.html"));
+ ExecuteScriptAndCheckNavigation(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('navigate-top-frame-to-html').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that a content initiated navigation to a data URL is allowed if
+// blocking is disabled with a feature flag.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ HTML_Navigation_Allow_FeatureFlag) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(features::kAllowInsecureDataUrlNavigations);
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("/data_url_navigations.html"));
+ ExecuteScriptAndCheckNavigation(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('navigate-top-frame-to-html').click()",
+ NAVIGATION_ALLOWED);
+}
+
+// Tests that a window.open to a data URL with HTML mime type is blocked.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, HTML_WindowOpen_Block) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("/data_url_navigations.html"));
+ ExecuteScriptAndCheckWindowOpen(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('window-open-html').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that a form post to a data URL with HTML mime type is blocked.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, HTML_FormPost_Block) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("/data_url_navigations.html"));
+ ExecuteScriptAndCheckNavigation(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('form-post-to-html').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that navigating the main frame to a data URL with HTML mimetype from a
+// subframe is blocked.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ HTML_NavigationFromFrame_Block) {
+ // This test fails and is disabled in site-per-process + no plznavigate mode.
+ // request->originDocument is null in FrameLoader::prepareForRequest,
+ // allowing the navigation by default. See https://crbug.com/647839
+ if (AreAllSitesIsolatedForTesting() && !IsBrowserSideNavigationEnabled()) {
+ return;
+ }
+
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("a.com", "/simple_page.html"));
+ AddIFrame(
+ shell()->web_contents()->GetMainFrame(),
+ embedded_test_server()->GetURL("b.com", "/data_url_navigations.html"));
+
+ TestNavigationFromFrame(
+ "document.getElementById('navigate-top-frame-to-html').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that opening a new data URL window from a subframe is blocked.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ HTML_WindowOpenFromFrame_Block) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("a.com", "/simple_page.html"));
+ AddIFrame(
+ shell()->web_contents()->GetMainFrame(),
+ embedded_test_server()->GetURL("b.com", "/data_url_navigations.html"));
+
+ TestWindowOpenFromFrame("document.getElementById('window-open-html').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that navigation to a data URL is blocked even if the top frame is
+// already a data URL.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ HTML_Navigation_DataToData_Block) {
+ NavigateToURL(shell(), data_url());
+ ExecuteScriptAndCheckNavigation(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('navigate-top-frame-to-html').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that a form post to a data URL with HTML mime type is blocked even if
+// the top frame is already a data URL.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ HTML_FormPost_DataToData_Block) {
+ NavigateToURL(shell(), data_url());
+ ExecuteScriptAndCheckNavigation(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('form-post-to-html').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that navigating the top frame to a data URL with HTML mimetype is
+// blocked even if the top frame is already a data URL.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ HTML_NavigationFromFrame_TopFrameIsDataURL_Block) {
+ // This test fails and is disabled in site-per-process + no plznavigate mode.
+ // request->originDocument is null in FrameLoader::prepareForRequest,
+ // allowing the navigation by default. See https://crbug.com/647839
+ if (AreAllSitesIsolatedForTesting() && !IsBrowserSideNavigationEnabled()) {
+ return;
+ }
+
+ const GURL top_url(
+ base::StringPrintf("data:text/html, <iframe src='%s'></iframe>",
+ embedded_test_server()
+ ->GetURL("/data_url_navigations.html")
+ .spec()
+ .c_str()));
+ NavigateToURL(shell(), top_url);
+
+ TestNavigationFromFrame(
+ "document.getElementById('navigate-top-frame-to-html').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that opening a new window with a data URL with HTML mimetype is blocked
+// even if the top frame is already a data URL.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ HTML_WindowOpenFromFrame_TopFrameIsDataURL_Block) {
+ const GURL top_url(
+ base::StringPrintf("data:text/html, <iframe src='%s'></iframe>",
+ embedded_test_server()
+ ->GetURL("/data_url_navigations.html")
+ .spec()
+ .c_str()));
+ NavigateToURL(shell(), top_url);
+
+ TestWindowOpenFromFrame("document.getElementById('window-open-html').click()",
+ NAVIGATION_BLOCKED);
+}
+////////////////////////////////////////////////////////////////////////////////
nasko 2017/04/05 23:55:19 nit: Empty line above this one.
meacer 2017/04/06 01:25:42 Done.
+// data URLs with octet-stream mimetype (binary)
+//
+// Test that window.open to a data URL results in a download if the URL has a
+// binary mime type.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ OctetStream_WindowOpen_Download) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("/data_url_navigations.html"));
+ ExecuteScriptAndCheckWindowOpenDownload(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('window-open-octetstream').click()");
+}
+
+// Test that a navigation to a data URL results in a download if the URL has a
+// binary mime type.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ OctetStream_Navigation_Download) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("/data_url_navigations.html"));
+ ExecuteScriptAndCheckNavigationDownload(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('navigate-top-frame-to-octetstream').click()");
+}
+
+// Test that a form post to a data URL results in a download if the URL has a
+// binary mime type.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ OctetStream_FormPost_Download) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("/data_url_navigations.html"));
+ ExecuteScriptAndCheckNavigationDownload(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('form-post-to-octetstream').click()");
+}
+
+// Tests that navigating the main frame from a subframe results in a download
+// if the URL has a binary mimetype.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ OctetStream_NavigationFromFrame_Download) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("a.com", "/simple_page.html"));
+ AddIFrame(
+ shell()->web_contents()->GetMainFrame(),
+ embedded_test_server()->GetURL("b.com", "/data_url_navigations.html"));
+
+ TestDownloadFromFrame(
+ "document.getElementById('navigate-top-frame-to-octetstream').click()");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// data URLs with unknown mimetype
+//
+// Test that window.open to a data URL results in a download if the URL has an
+// unknown mime type.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ UnknownMimeType_WindowOpen_Download) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("/data_url_navigations.html"));
+ ExecuteScriptAndCheckWindowOpenDownload(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('window-open-unknown-mimetype').click()");
+}
+
+// Test that a navigation to a data URL results in a download if the URL has an
+// unknown mime type.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ UnknownMimeType_Navigation_Download) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("/data_url_navigations.html"));
+ ExecuteScriptAndCheckNavigationDownload(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('navigate-top-"
+ "frame-to-unknown-mimetype').click()");
+}
+
+// Test that a form post to a data URL results in a download if the URL has an
+// unknown mime type.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ UnknownMimeType_FormPost_Download) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("/data_url_navigations.html"));
+ ExecuteScriptAndCheckNavigationDownload(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('form-post-to-unknown-mimetype').click()");
+}
+
+// Tests that navigating the main frame from a subframe results in a download
+// if the URL has an unknown mimetype.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ UnknownMimeType_NavigationFromFrame_Download) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("a.com", "/simple_page.html"));
+ AddIFrame(
+ shell()->web_contents()->GetMainFrame(),
+ embedded_test_server()->GetURL("b.com", "/data_url_navigations.html"));
+
+ TestDownloadFromFrame(
+ "document.getElementById('navigate-top-frame-to-unknown-mimetype').click("
+ ")");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// data URLs with PDF mimetype
+//
+// Tests that a direct navigation to a data URL with PDF mime type is allowed.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ PDF_DirectNavigation_Allow) {
+ TestNavigationObserver observer(shell()->web_contents());
+ NavigateToURL(shell(), GURL(kPdfUrl));
+ EXPECT_EQ(GURL(kPdfUrl), observer.last_navigation_url());
+ EXPECT_TRUE(observer.last_navigation_succeeded());
+ EXPECT_TRUE(shell()->web_contents()->GetURL().SchemeIs(url::kDataScheme));
+ EXPECT_TRUE(shell()
+ ->web_contents()
+ ->GetController()
+ .GetLastCommittedEntry()
+ ->GetURL()
+ .SchemeIs(url::kDataScheme));
+}
+
+// Tests that a window.open to a data URL is blocked if the data URL has a
+// mime type that will be handled by a plugin (PDF in this case).
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, PDF_WindowOpen_Block) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("/data_url_navigations.html"));
+ ExecuteScriptAndCheckWindowOpen(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('window-open-pdf').click()", NAVIGATION_BLOCKED);
+}
+
+// Test that a navigation to a data URL is blocked if the data URL has a mime
+// type that will be handled by a plugin (PDF in this case).
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, PDF_Navigation_Block) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("/data_url_navigations.html"));
+ ExecuteScriptAndCheckPDFNavigation(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('navigate-top-frame-to-pdf').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Test that a navigation to a data URL is blocked if the data URL has a mime
nasko 2017/04/05 23:55:19 nit: Did you mean s/navigation/form post/?
meacer 2017/04/06 01:25:42 Done.
+// type that will be handled by a plugin (PDF in this case).
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, PDF_FormPost_Block) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("/data_url_navigations.html"));
+ ExecuteScriptAndCheckPDFNavigation(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('form-post-to-pdf').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that navigating the main frame to a data URL with PDF mimetype from a
+// subframe is blocked.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ PDF_NavigationFromFrame_Block) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("a.com", "/simple_page.html"));
+ AddIFrame(
+ shell()->web_contents()->GetMainFrame(),
+ embedded_test_server()->GetURL("b.com", "/data_url_navigations.html"));
+
+ TestPDFNavigationFromFrame(
+ "document.getElementById('navigate-top-frame-to-pdf').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that opening a window with a data URL with PDF mimetype from a
+// subframe is blocked.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ PDF_WindowOpenFromFrame_Block) {
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("a.com", "/simple_page.html"));
+ AddIFrame(
+ shell()->web_contents()->GetMainFrame(),
+ embedded_test_server()->GetURL("b.com", "/data_url_navigations.html"));
+
+ TestWindowOpenFromFrame("document.getElementById('window-open-pdf').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that navigating the top frame to a data URL with PDF mimetype from a
+// subframe is blocked even if the top frame is already a data URL.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ PDF_NavigationFromFrame_TopFrameIsDataURL_Block) {
+ const GURL top_url(
+ base::StringPrintf("data:text/html, <iframe src='%s'></iframe>",
+ embedded_test_server()
+ ->GetURL("/data_url_navigations.html")
+ .spec()
+ .c_str()));
+ NavigateToURL(shell(), top_url);
+
+ TestPDFNavigationFromFrame(
+ "document.getElementById('navigate-top-frame-to-pdf').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that opening a window with a data URL with PDF mimetype from a
+// subframe is blocked even if the top frame is already a data URL.
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest,
+ PDF_WindowOpenFromFrame_TopFrameIsDataURL_Block) {
+ const GURL top_url(
+ base::StringPrintf("data:text/html, <iframe src='%s'></iframe>",
+ embedded_test_server()
+ ->GetURL("/data_url_navigations.html")
+ .spec()
+ .c_str()));
+ NavigateToURL(shell(), top_url);
+
+ TestWindowOpenFromFrame("document.getElementById('window-open-pdf').click()",
+ NAVIGATION_BLOCKED);
+}
+
+} // content

Powered by Google App Engine
This is Rietveld 408576698