diff options
Diffstat (limited to 'chromium/content/browser/frame_host/blocked_scheme_navigation_browsertest.cc')
-rw-r--r-- | chromium/content/browser/frame_host/blocked_scheme_navigation_browsertest.cc | 1418 |
1 files changed, 1418 insertions, 0 deletions
diff --git a/chromium/content/browser/frame_host/blocked_scheme_navigation_browsertest.cc b/chromium/content/browser/frame_host/blocked_scheme_navigation_browsertest.cc new file mode 100644 index 00000000000..f25ed52f307 --- /dev/null +++ b/chromium/content/browser/frame_host/blocked_scheme_navigation_browsertest.cc @@ -0,0 +1,1418 @@ +// 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/base64.h" +#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/run_loop.h" +#include "base/strings/pattern.h" +#include "base/strings/string_util.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" + +#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 navigation to a +// URL is blocked. +const char kNavigationBlockedMessage[] = + "Not allowed to navigate top frame to %s URL:*"; + +// The message printed by the data or filesystem URL when it successfully +// navigates. +const char kNavigationSuccessfulMessage[] = "NAVIGATION_SUCCESSFUL"; + +// A "Hello World" pdf. +const char kPDF[] = + "%PDF-1.7\n" + "1 0 obj << /Type /Page /Parent 3 0 R /Resources 5 0 R /Contents 2 0 R >>\n" + "endobj\n" + "2 0 obj << /Length 51 >>\n" + " stream BT\n" + " /F1 12 Tf\n" + " 1 0 0 1 100 20 Tm\n" + " (Hello World)Tj\n" + " ET\n" + " endstream\n" + "endobj\n" + "3 0 obj << /Type /Pages /Kids [ 1 0 R ] /Count 1 /MediaBox [ 0 0 300 50] " + ">>\n" + "endobj\n" + "4 0 obj << /Type /Font /Subtype /Type1 /Name /F1 /BaseFont/Arial >>\n" + "endobj\n" + "5 0 obj << /ProcSet[/PDF/Text] /Font <</F1 4 0 R >> >>\n" + "endobj\n" + "6 0 obj << /Type /Catalog /Pages 3 0 R >>\n" + "endobj\n" + "trailer << /Root 6 0 R >>\n"; + +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. +class BlockedURLWarningConsoleObserverDelegate : public WebContentsDelegate { + public: + enum Status { + NO_MESSAGE, + SAW_SUCCESS_MESSAGE, + SAW_FAILURE_MESSAGE, + }; + BlockedURLWarningConsoleObserverDelegate(WebContents* web_contents, + const std::string& success_filter, + const std::string& fail_filter) + : web_contents_(web_contents), + success_filter_(success_filter), + fail_filter_(fail_filter), + status_(NO_MESSAGE) {} + + ~BlockedURLWarningConsoleObserverDelegate() override {} + + // 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_)) { + status_ = SAW_FAILURE_MESSAGE; + run_loop_.Quit(); + } + if (base::MatchPattern(ascii_message, success_filter_)) { + status_ = SAW_SUCCESS_MESSAGE; + run_loop_.Quit(); + } + return false; + } + + void Wait() { run_loop_.Run(); } + + Status status() const { return status_; } + + private: + WebContents* web_contents_; + const std::string success_filter_; + const std::string fail_filter_; + base::RunLoop run_loop_; + Status status_; +}; + +#if BUILDFLAG(ENABLE_PLUGINS) +// This class registers a fake PDF plugin handler so that 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 BlockedSchemeNavigationBrowserTest + : public ContentBrowserTest, + public testing::WithParamInterface<const char*> { + public: +#if BUILDFLAG(ENABLE_PLUGINS) + BlockedSchemeNavigationBrowserTest() + : scoped_plugin_register_(PluginService::GetInstance()) {} +#else + BlockedSchemeNavigationBrowserTest() {} +#endif // BUILDFLAG(ENABLE_PLUGINS) + + protected: + void SetUpOnMainThread() override { + host_resolver()->AddRule("*", "127.0.0.1"); + ASSERT_TRUE(embedded_test_server()->Start()); + + base::FilePath path; + ASSERT_TRUE(base::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()); + } + + void Navigate(const GURL& url) { + content::DOMMessageQueue message_queue; + EXPECT_TRUE(NavigateToURL(shell(), url)); + std::string message; + while (message_queue.WaitForMessage(&message)) { + if (message == "\"READY\"") + break; + } + } + + // Creates a filesystem: URL on the current origin. + GURL CreateFileSystemUrl(const std::string& filename, + const std::string& content, + const std::string& mime_type) { + const char kCreateFilesystemUrlScript[] = + "var contents = `%s`;" + "webkitRequestFileSystem(window.TEMPORARY, 1024, fs => {" + " fs.root.getFile('%s', {create: true}, entry => {" + " entry.createWriter(w => {" + " w.write(new Blob([contents], {type: '%s'}));" + " w.onwrite = function(evt) {" + " domAutomationController.send(entry.toURL());" + " }" + " });" + " });" + "});"; + std::string filesystem_url_string; + EXPECT_TRUE(ExecuteScriptAndExtractString( + shell()->web_contents()->GetMainFrame(), + base::StringPrintf(kCreateFilesystemUrlScript, content.c_str(), + filename.c_str(), mime_type.c_str()), + &filesystem_url_string)); + GURL filesystem_url(filesystem_url_string); + EXPECT_TRUE(filesystem_url.is_valid()); + EXPECT_TRUE(filesystem_url.SchemeIsFileSystem()); + return filesystem_url; + } + + bool IsDataURLTest() const { + return std::string(url::kDataScheme) == GetParam(); + } + + GURL CreateEmptyURLWithBlockedScheme() { + return CreateURLWithBlockedScheme("empty.html", "<html></html>", + "text/html"); + } + + GURL CreateURLWithBlockedScheme(const std::string& filename, + const std::string& content, + const std::string& mimetype) { + if (IsDataURLTest()) { + return GURL( + base::StringPrintf("data:%s,%s", mimetype.c_str(), content.c_str())); + } + // We need an origin to create a filesystem URL on, so navigate to one. + NavigateToURL(shell(), + embedded_test_server()->GetURL("a.com", "/simple_page.html")); + return CreateFileSystemUrl(filename, content, mimetype); + } + + GURL GetTestURL() { + return embedded_test_server()->GetURL( + base::StringPrintf("/%s_url_navigations.html", GetParam())); + } + + // Adds an iframe to |rfh| pointing to |url|. + void AddIFrame(RenderFrameHost* rfh, const GURL& url) { + content::DOMMessageQueue message_queue; + const std::string javascript = base::StringPrintf( + "f = document.createElement('iframe'); f.src = '%s';" + "document.body.appendChild(f);", + url.spec().c_str()); + TestNavigationObserver observer(shell()->web_contents()); + EXPECT_TRUE(ExecuteScript(rfh, javascript)); + observer.Wait(); + std::string message; + while (message_queue.WaitForMessage(&message)) { + if (message == "\"READY\"") + break; + } + } + + // Runs |javascript| on the first child frame and checks for a navigation. + void TestNavigationFromFrame( + const std::string& scheme, + const std::string& javascript, + ExpectedNavigationStatus expected_navigation_status) { + RenderFrameHost* child = + ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0); + ASSERT_TRUE(child); + if (AreAllSitesIsolatedForTesting()) { + ASSERT_TRUE(child->IsCrossProcessSubframe()); + } + ExecuteScriptAndCheckNavigation(shell(), child, scheme, 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 (AreAllSitesIsolatedForTesting()) { + ASSERT_TRUE(child->IsCrossProcessSubframe()); + } + ExecuteScriptAndCheckDownload(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& scheme, + const std::string& javascript, + ExpectedNavigationStatus expected_navigation_status) { + RenderFrameHost* child = + ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0); + ASSERT_TRUE(child); + if (AreAllSitesIsolatedForTesting()) { + ASSERT_TRUE(child->IsCrossProcessSubframe()); + } + ExecuteScriptAndCheckPDFNavigation(child, scheme, javascript, + expected_navigation_status); + } + + // Same as TestNavigationFromFrame, but instead of navigating, the child frame + // tries to open a new window with a blocked URL (data or filesystem) + void TestWindowOpenFromFrame( + const std::string& scheme, + const std::string& javascript, + ExpectedNavigationStatus expected_navigation_status) { + RenderFrameHost* child = + ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0); + if (AreAllSitesIsolatedForTesting()) { + ASSERT_TRUE(child->IsCrossProcessSubframe()); + } + ExecuteScriptAndCheckWindowOpen(child, scheme, 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 kNavigationSuccessfulMessage. + void ExecuteScriptAndCheckNavigation( + Shell* shell, + RenderFrameHost* rfh, + const std::string& scheme, + const std::string& javascript, + ExpectedNavigationStatus expected_navigation_status) { + if (expected_navigation_status == NAVIGATION_ALLOWED) + ExecuteScriptAndCheckNavigationAllowed(shell, rfh, javascript, scheme); + else + ExecuteScriptAndCheckNavigationBlocked(shell, rfh, javascript, scheme); + } + + protected: + // 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& scheme, + 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() + : base::StringPrintf(kNavigationBlockedMessage, scheme.c_str()); + + 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()); + 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 the expected scheme. + EXPECT_TRUE( + shell()->web_contents()->GetLastCommittedURL().SchemeIs(scheme)); + EXPECT_TRUE(navigation_observer.last_navigation_url().SchemeIs(scheme)); + EXPECT_TRUE(navigation_observer.last_navigation_succeeded()); + break; + + case NAVIGATION_BLOCKED: + // Original page shouldn't navigate away. + EXPECT_EQ(original_url, shell()->web_contents()->GetLastCommittedURL()); + 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). + void ExecuteScriptAndCheckWindowOpen( + RenderFrameHost* rfh, + const std::string& scheme, + 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(scheme)); + 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; + DownloadManager* download_manager = BrowserContext::GetDownloadManager( + shell()->web_contents()->GetBrowserContext()); + + EXPECT_TRUE(ExecuteScript(rfh, javascript)); + Shell* new_shell = new_shell_observer.GetShell(); + + 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()->GetLastCommittedURL().spec().empty()); + // 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()->GetLastCommittedURL()); + } + + // Executes |javascript| on |rfh| and waits for a download to be started. + void ExecuteScriptAndCheckDownload(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()->GetLastCommittedURL()); + } + + // Initiates a browser initiated navigation to |url| and waits for a download + // to be started. + void NavigateAndCheckDownload(const GURL& url) { + 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); + NavigateToURL(shell(), url); + + // If no download happens, this will timeout. + download_observer.WaitForFinished(); + + // Original page shouldn't navigate away. + EXPECT_EQ(original_url, shell()->web_contents()->GetLastCommittedURL()); + } + + // data URL form of the file at content/test/data/data_url_navigations.html + GURL data_url() const { return data_url_; } + + std::string GetNavigationBlockedMessage() const { + return base::StringPrintf(kNavigationBlockedMessage, GetParam()); + } + + private: + // Executes |javascript| on |rfh| and waits for a console message that + // indicates the navigation has completed. |scheme| is the scheme being + // tested. + static void ExecuteScriptAndCheckNavigationAllowed( + Shell* shell, + RenderFrameHost* rfh, + const std::string& javascript, + const std::string& scheme) { + // Should see success message, should never see blocked message. + const std::string blocked_message = + base::StringPrintf(kNavigationBlockedMessage, scheme.c_str()); + BlockedURLWarningConsoleObserverDelegate console_delegate( + shell->web_contents(), kNavigationSuccessfulMessage, blocked_message); + shell->web_contents()->SetDelegate(&console_delegate); + + TestNavigationObserver navigation_observer(shell->web_contents()); + EXPECT_TRUE(ExecuteScript(rfh, javascript)); + console_delegate.Wait(); + EXPECT_EQ(BlockedURLWarningConsoleObserverDelegate::SAW_SUCCESS_MESSAGE, + console_delegate.status()); + shell->web_contents()->SetDelegate(nullptr); + navigation_observer.Wait(); + + // The new page should have the expected scheme. + EXPECT_EQ(navigation_observer.last_navigation_url(), + shell->web_contents()->GetLastCommittedURL()); + EXPECT_TRUE(navigation_observer.last_navigation_succeeded()); + } + + // Similar to ExecuteScriptAndCheckNavigationAllowed. Executes |javascript| on + // |rfh| and waits for a console message that indicates the navigation has + // been blocked. |scheme| is the scheme being tested. + static void ExecuteScriptAndCheckNavigationBlocked( + Shell* shell, + RenderFrameHost* rfh, + const std::string& javascript, + const std::string& scheme) { + const GURL original_url(shell->web_contents()->GetLastCommittedURL()); + + // Should see blocked message, should never see success message. + const std::string blocked_message = + base::StringPrintf(kNavigationBlockedMessage, scheme.c_str()); + BlockedURLWarningConsoleObserverDelegate console_delegate( + shell->web_contents(), kNavigationSuccessfulMessage, blocked_message); + shell->web_contents()->SetDelegate(&console_delegate); + + TestNavigationObserver navigation_observer(shell->web_contents()); + EXPECT_TRUE(ExecuteScript(rfh, javascript)); + console_delegate.Wait(); + EXPECT_EQ(BlockedURLWarningConsoleObserverDelegate::SAW_FAILURE_MESSAGE, + console_delegate.status()); + shell->web_contents()->SetDelegate(nullptr); + + // Original page shouldn't navigate away. + EXPECT_EQ(original_url, shell->web_contents()->GetLastCommittedURL()); + EXPECT_FALSE(navigation_observer.last_navigation_succeeded()); + } + + base::ScopedTempDir downloads_directory_; + +#if BUILDFLAG(ENABLE_PLUGINS) + ScopedPluginRegister scoped_plugin_register_; +#endif // BUILDFLAG(ENABLE_PLUGINS) + + GURL data_url_; + + DISALLOW_COPY_AND_ASSIGN(BlockedSchemeNavigationBrowserTest); +}; + +INSTANTIATE_TEST_CASE_P(, + BlockedSchemeNavigationBrowserTest, + ::testing::Values(url::kDataScheme, + url::kFileSystemScheme)); + +//////////////////////////////////////////////////////////////////////////////// +// Blocked schemes with HTML mimetype +// +// Tests that a browser initiated navigation to a blocked scheme doesn't show a +// console warning and is not blocked. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + BrowserInitiated_Allow) { + const GURL kUrl(CreateURLWithBlockedScheme( + "test.html", + "<html><script>console.log('NAVIGATION_SUCCESSFUL');</script></html>", + "text/html")); + if (IsDataURLTest()) { + BlockedURLWarningConsoleObserverDelegate console_delegate( + shell()->web_contents(), kNavigationSuccessfulMessage, + GetNavigationBlockedMessage()); + + shell()->web_contents()->SetDelegate(&console_delegate); + EXPECT_TRUE(NavigateToURL(shell(), kUrl)); + console_delegate.Wait(); + shell()->web_contents()->SetDelegate(nullptr); + EXPECT_TRUE( + shell()->web_contents()->GetLastCommittedURL().SchemeIs(GetParam())); + + } else { + // Navigate to a.com and create a filesystem URL on it. + // For filesystem: tests we create a new shell and navigate that shell to + // the filesystem: URL created above. Navigating the a tab away from the + // original page may clear all filesystem: URLs associated with that origin, + // so we keep the origin around in the original shell. + ShellAddedObserver new_shell_observer; + EXPECT_TRUE( + ExecuteScript(shell()->web_contents(), "window.open('about:blank');")); + Shell* new_shell = new_shell_observer.GetShell(); + WaitForLoadStop(new_shell->web_contents()); + + BlockedURLWarningConsoleObserverDelegate console_delegate( + new_shell->web_contents(), kNavigationSuccessfulMessage, + GetNavigationBlockedMessage()); + new_shell->web_contents()->SetDelegate(&console_delegate); + EXPECT_TRUE(NavigateToURL(new_shell, kUrl)); + + console_delegate.Wait(); + new_shell->web_contents()->SetDelegate(nullptr); + EXPECT_TRUE( + new_shell->web_contents()->GetLastCommittedURL().SchemeIs(GetParam())); + } +} + +// Tests that a content initiated navigation to a blocked scheme is blocked. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + HTML_Navigation_Block) { + Navigate(GetTestURL()); + ExecuteScriptAndCheckNavigation( + shell(), shell()->web_contents()->GetMainFrame(), GetParam(), + "document.getElementById('navigate-top-frame-to-html').click()", + NAVIGATION_BLOCKED); +} + +class DataUrlNavigationBrowserTestWithFeatureFlag + : public BlockedSchemeNavigationBrowserTest { + public: + DataUrlNavigationBrowserTestWithFeatureFlag() { + scoped_feature_list_.InitAndEnableFeature( + features::kAllowContentInitiatedDataUrlNavigations); + } + ~DataUrlNavigationBrowserTestWithFeatureFlag() override {} + + private: + base::test::ScopedFeatureList scoped_feature_list_; + + DISALLOW_COPY_AND_ASSIGN(DataUrlNavigationBrowserTestWithFeatureFlag); +}; + +// Tests that a content initiated navigation to a data URL is allowed if +// blocking is disabled with a feature flag. +IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTestWithFeatureFlag, + HTML_Navigation_Allow_FeatureFlag) { + EXPECT_TRUE(NavigateToURL( + shell(), embedded_test_server()->GetURL("/data_url_navigations.html"))); + ExecuteScriptAndCheckNavigation( + shell(), shell()->web_contents()->GetMainFrame(), url::kDataScheme, + "document.getElementById('navigate-top-frame-to-html').click()", + NAVIGATION_ALLOWED); +} + +// Tests that a window.open to a blocked scheme with HTML mime type is blocked. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + HTML_WindowOpen_Block) { + Navigate(GetTestURL()); + ExecuteScriptAndCheckWindowOpen( + shell()->web_contents()->GetMainFrame(), GetParam(), + "document.getElementById('window-open-html').click()", + NAVIGATION_BLOCKED); +} + +// Tests that a form post to a blocked scheme with HTML mime type is blocked. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + HTML_FormPost_Block) { + Navigate(GetTestURL()); + ExecuteScriptAndCheckNavigation( + shell(), shell()->web_contents()->GetMainFrame(), GetParam(), + "document.getElementById('form-post-to-html').click()", + NAVIGATION_BLOCKED); +} + +// Tests that clicking <a download> link downloads the URL even with a blocked +// scheme. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, HTML_Download) { + Navigate(GetTestURL()); + ExecuteScriptAndCheckDownload( + shell()->web_contents()->GetMainFrame(), + "document.getElementById('download-link').click()"); +} + +// Tests that navigating the main frame to a blocked scheme with HTML mimetype +// from a subframe is blocked. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + HTML_NavigationFromFrame_Block) { + EXPECT_TRUE(NavigateToURL( + shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html"))); + AddIFrame( + shell()->web_contents()->GetMainFrame(), + embedded_test_server()->GetURL( + "b.com", base::StringPrintf("/%s_url_navigations.html", GetParam()))); + + TestNavigationFromFrame( + GetParam(), + "document.getElementById('navigate-top-frame-to-html').click()", + NAVIGATION_BLOCKED); +} + +// Tests that opening a new window with a blocked scheme from a subframe is +// blocked. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + HTML_WindowOpenFromFrame_Block) { + EXPECT_TRUE(NavigateToURL( + shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html"))); + AddIFrame( + shell()->web_contents()->GetMainFrame(), + embedded_test_server()->GetURL( + "b.com", base::StringPrintf("/%s_url_navigations.html", GetParam()))); + + TestWindowOpenFromFrame(GetParam(), + "document.getElementById('window-open-html').click()", + NAVIGATION_BLOCKED); +} + +// Tests that navigation to a blocked scheme is blocked even if the top frame is +// already has a blocked scheme. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + HTML_Navigation_SameScheme_Block) { + if (IsDataURLTest()) { + EXPECT_TRUE(NavigateToURL(shell(), data_url())); + ExecuteScriptAndCheckNavigation( + shell(), shell()->web_contents()->GetMainFrame(), url::kDataScheme, + "document.getElementById('navigate-top-frame-to-html').click()", + NAVIGATION_BLOCKED); + } else { + // We need an origin to create a filesystem URL on, so navigate to one. + EXPECT_TRUE(NavigateToURL( + shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html"))); + const GURL kFilesystemURL1( + CreateFileSystemUrl("empty1.html", "empty1", "text/html")); + const GURL kFilesystemURL2( + CreateFileSystemUrl("empty2.html", "empty2", "text/html")); + + // Create a new shell and navigate that shell to the filesystem: URL created + // above. Navigating the a tab away from the + // original page may clear all filesystem: URLs associated with that origin, + // so we keep the origin around in the original shell. + ShellAddedObserver new_shell_observer; + EXPECT_TRUE( + ExecuteScript(shell()->web_contents(), "window.open('about:blank');")); + Shell* new_shell = new_shell_observer.GetShell(); + WaitForLoadStop(new_shell->web_contents()); + + EXPECT_TRUE(NavigateToURL(new_shell, kFilesystemURL1)); + ExecuteScriptAndCheckNavigation( + new_shell, new_shell->web_contents()->GetMainFrame(), + url::kFileSystemScheme, + base::StringPrintf("window.location='%s';", + kFilesystemURL2.spec().c_str()), + NAVIGATION_BLOCKED); + } +} + +// Tests that a form post to a blocked scheme with HTML mime type is blocked +// even if the top frame is already a blocked scheme. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + HTML_FormPost_SameScheme_Block) { + if (IsDataURLTest()) { + EXPECT_TRUE(NavigateToURL(shell(), data_url())); + ExecuteScriptAndCheckNavigation( + shell(), shell()->web_contents()->GetMainFrame(), url::kDataScheme, + "document.getElementById('form-post-to-html').click()", + NAVIGATION_BLOCKED); + } else { + // We need an origin to create a filesystem URL on, so navigate to one. + EXPECT_TRUE(NavigateToURL( + shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html"))); + const GURL kFilesystemURL1( + CreateFileSystemUrl("target.html", "form target", "text/html")); + const GURL kFilesystemURL2(CreateFileSystemUrl( + "form.html", + base::StringPrintf("<html><form id=f method=post action='%s'><input " + "type=submit " + "onclick=document.getElementById('f').click() " + "id=btn-submit></form></html>", + kFilesystemURL1.spec().c_str()), + "text/html")); + + // Create a new shell and navigate that shell to the filesystem: URL created + // above. Navigating the a tab away from the + // original page may clear all filesystem: URLs associated with that origin, + // so we keep the origin around in the original shell. + ShellAddedObserver new_shell_observer; + // TODO(crbug/811558): about:blank might commit without needing to wait. + // Remove the wait. + EXPECT_TRUE( + ExecuteScript(shell()->web_contents(), "window.open('about:blank');")); + Shell* new_shell = new_shell_observer.GetShell(); + WaitForLoadStop(new_shell->web_contents()); + + EXPECT_TRUE(NavigateToURL(new_shell, kFilesystemURL2)); + ExecuteScriptAndCheckNavigation( + new_shell, new_shell->web_contents()->GetMainFrame(), + url::kFileSystemScheme, "document.getElementById('btn-submit').click()", + NAVIGATION_BLOCKED); + } +} + +// Tests that navigating the top frame to a blocked scheme with HTML mimetype is +// blocked even if the top frame already has a blocked scheme. +IN_PROC_BROWSER_TEST_P( + BlockedSchemeNavigationBrowserTest, + HTML_NavigationFromFrame_TopFrameHasBlockedScheme_Block) { + EXPECT_TRUE(NavigateToURL(shell(), CreateEmptyURLWithBlockedScheme())); + AddIFrame(shell()->web_contents()->GetMainFrame(), GetTestURL()); + + TestNavigationFromFrame( + GetParam(), + "document.getElementById('navigate-top-frame-to-html').click()", + NAVIGATION_BLOCKED); +} + +// Tests that opening a new window with a blocked scheme with HTML mimetype is +// blocked even if the top frame already has a blocked scheme. +IN_PROC_BROWSER_TEST_P( + BlockedSchemeNavigationBrowserTest, + HTML_WindowOpenFromFrame_TopFrameHasBlockedScheme_Block) { + // Create an empty URL with a blocked scheme, navigate to it, and add a frame. + EXPECT_TRUE(NavigateToURL(shell(), CreateEmptyURLWithBlockedScheme())); + AddIFrame(shell()->web_contents()->GetMainFrame(), GetTestURL()); + + TestWindowOpenFromFrame(GetParam(), + "document.getElementById('window-open-html').click()", + NAVIGATION_BLOCKED); +} + +//////////////////////////////////////////////////////////////////////////////// +// Blocked schemes with octet-stream mimetype (binary) +// +// Test direct navigations to a binary mime types. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + OctetStream_BrowserInitiated) { + const GURL kUrl(CreateURLWithBlockedScheme("test.html", "test", + "application/octet-stream")); + + if (IsDataURLTest()) { + // Navigations to data URLs with unknown mime types should end up as + // downloads. + NavigateAndCheckDownload(kUrl); + } else { + // Navigations to filesystem URLs never end up as downloads. + EXPECT_TRUE(NavigateToURL(shell(), kUrl)); + EXPECT_EQ(kUrl, shell()->web_contents()->GetLastCommittedURL()); + } +} + +#if defined(OS_ANDROID) +// Flaky on android: https://crbug.com/734563 +#define MAYBE_DataUrl_OctetStream_WindowOpen \ + DISABLED_DataUrl_OctetStream_WindowOpen +#else +#define MAYBE_DataUrl_OctetStream_WindowOpen DataUrl_OctetStream_WindowOpen +#endif + +// Test window.open to a data URL with binary mimetype. +IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest, + DataUrl_OctetStream_WindowOpen) { + Navigate(embedded_test_server()->GetURL( + base::StringPrintf("/data_url_navigations.html"))); + // Navigations to data URLs with unknown mime types should end up as + // downloads. + ExecuteScriptAndCheckWindowOpenDownload( + shell()->web_contents()->GetMainFrame(), + "document.getElementById('window-open-octetstream').click()"); +} + +// Test window.open to a filesystem URL with binary mimetype. +IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest, + FilesystemUrl_OctetStream_WindowOpen) { + Navigate(embedded_test_server()->GetURL( + base::StringPrintf("/filesystem_url_navigations.html"))); + // Navigations to filesystem URLs never end up as downloads. + ExecuteScriptAndCheckWindowOpen( + shell()->web_contents()->GetMainFrame(), url::kFileSystemScheme, + "document.getElementById('window-open-octetstream').click()", + NAVIGATION_BLOCKED); +} + +// Test navigation to a data URL with binary mimetype. +IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest, + DataUrl_OctetStream_Navigation) { + Navigate(embedded_test_server()->GetURL( + base::StringPrintf("/data_url_navigations.html"))); + // Navigations to data URLs with unknown mime types should end up as + // downloads. + ExecuteScriptAndCheckDownload( + shell()->web_contents()->GetMainFrame(), + "document.getElementById('navigate-top-frame-to-octetstream').click()"); +} + +// Test navigation to a filesystem URL with binary mimetype. +IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest, + FilesystemUrl_OctetStream_Navigation) { + Navigate(embedded_test_server()->GetURL( + base::StringPrintf("/filesystem_url_navigations.html"))); + // Navigations to filesystem URLs never end up as downloads. + ExecuteScriptAndCheckNavigation( + shell(), shell()->web_contents()->GetMainFrame(), url::kFileSystemScheme, + "document.getElementById('navigate-top-frame-to-octetstream').click()", + NAVIGATION_BLOCKED); +} + +// Test form post to a data URL with binary mimetype. +IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest, + DataUrl_OctetStream_FormPost) { + Navigate(embedded_test_server()->GetURL( + base::StringPrintf("/data_url_navigations.html"))); + // Form posts to data URLs with unknown mime types should end up as + // downloads. + ExecuteScriptAndCheckDownload( + shell()->web_contents()->GetMainFrame(), + "document.getElementById('form-post-to-octetstream').click()"); +} + +// Test form post to a filesystem URL with binary mimetype. +IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest, + FilesystemUrl_OctetStream_FormPost) { + Navigate(embedded_test_server()->GetURL( + base::StringPrintf("/filesystem_url_navigations.html"))); + // Navigations to filesystem URLs never end up as downloads. + ExecuteScriptAndCheckNavigation( + shell(), shell()->web_contents()->GetMainFrame(), url::kFileSystemScheme, + "document.getElementById('form-post-to-octetstream').click()", + NAVIGATION_BLOCKED); +} + +// Tests navigation of the main frame to a data URL with a binary mimetype +// from a subframe. These navigations should end up as downloads. +IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest, + DataUrl_OctetStream_NavigationFromFrame) { + EXPECT_TRUE(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()"); +} + +// Tests navigation of the main frame to a filesystem URL with a binary mimetype +// from a subframe. Navigations to filesystem URLs never end up as downloads. +IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest, + FilesystemUrl_OctetStream_NavigationFromFrame) { + EXPECT_TRUE(NavigateToURL( + shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html"))); + AddIFrame(shell()->web_contents()->GetMainFrame(), + embedded_test_server()->GetURL("b.com", + "/filesystem_url_navigations.html")); + + TestNavigationFromFrame( + url::kFileSystemScheme, + "document.getElementById('navigate-top-frame-to-octetstream').click()", + NAVIGATION_BLOCKED); +} + +//////////////////////////////////////////////////////////////////////////////// +// URLs with unknown mimetype +// +// Test direct navigation to an unknown mime type. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + UnknownMimeType_BrowserInitiated_Download) { + const GURL kUrl( + CreateURLWithBlockedScheme("test.html", "test", "unknown/mimetype")); + + if (IsDataURLTest()) { + // Navigations to data URLs with unknown mime types should end up as + // downloads. + NavigateAndCheckDownload(kUrl); + } else { + // Navigations to filesystem URLs never end up as downloads. + EXPECT_TRUE(NavigateToURL(shell(), kUrl)); + EXPECT_EQ(kUrl, shell()->web_contents()->GetLastCommittedURL()); + } +} + +#if defined(OS_ANDROID) +// Flaky on android: https://crbug.com/734563 +#define MAYBE_UnknownMimeType_WindowOpen DISABLED_UnknownMimeType_WindowOpen +#else +#define MAYBE_UnknownMimeType_WindowOpen UnknownMimeType_WindowOpen +#endif + +// Test window.open to a blocked scheme with an unknown mime type. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + MAYBE_UnknownMimeType_WindowOpen) { + Navigate(GetTestURL()); + if (IsDataURLTest()) { + // Navigations to data URLs with unknown mime types should end up as + // downloads. + ExecuteScriptAndCheckWindowOpenDownload( + shell()->web_contents()->GetMainFrame(), + "document.getElementById('window-open-unknown-mimetype').click()"); + } else { + // Navigations to filesystem URLs never end up as downloads. + ExecuteScriptAndCheckWindowOpen( + shell()->web_contents()->GetMainFrame(), GetParam(), + "document.getElementById('window-open-unknown-mimetype').click()", + NAVIGATION_BLOCKED); + } +} + +// Test navigation to a data URL with an unknown mime type. +IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest, + DataUrl_UnknownMimeType_Navigation) { + Navigate(embedded_test_server()->GetURL( + base::StringPrintf("/data_url_navigations.html"))); + // Navigations to data URLs with unknown mime types should end up as + // downloads. + ExecuteScriptAndCheckDownload(shell()->web_contents()->GetMainFrame(), + "document.getElementById('navigate-top-frame-" + "to-unknown-mimetype').click()"); +} + +// Test navigation to a filesystem URL with an unknown mime type. +IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest, + FilesystemUrl_UnknownMimeType_Navigation) { + Navigate(embedded_test_server()->GetURL( + base::StringPrintf("/filesystem_url_navigations.html"))); + // Navigations to filesystem URLs never end up as downloads. + ExecuteScriptAndCheckNavigation( + shell(), shell()->web_contents()->GetMainFrame(), url::kFileSystemScheme, + "document.getElementById('navigate-top-frame-to-unknown-mimetype')." + "click()", + NAVIGATION_BLOCKED); +} + +// Test form post to a data URL with an unknown mime type. +IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest, + DataUrl_UnknownMimeType_FormPost) { + Navigate(embedded_test_server()->GetURL( + base::StringPrintf("/data_url_navigations.html"))); + // Form posts to data URLs with unknown mime types should end up as + // downloads. + ExecuteScriptAndCheckDownload( + shell()->web_contents()->GetMainFrame(), + "document.getElementById('form-post-to-unknown-mimetype').click()"); +} + +// Test form post to a filesystem URL with an unknown mime type. +IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest, + FilesystemUrl_UnknownMimeType_FormPost) { + Navigate(embedded_test_server()->GetURL( + base::StringPrintf("/filesystem_url_navigations.html"))); + // Navigations to filesystem URLs never end up as downloads. + ExecuteScriptAndCheckNavigation( + shell(), shell()->web_contents()->GetMainFrame(), url::kFileSystemScheme, + "document.getElementById('form-post-to-unknown-mimetype').click()", + NAVIGATION_BLOCKED); +} + +// Test navigation of the main frame to a data URL with an unknown mimetype from +// a subframe. These navigations should end up as downloads. +IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest, + DataUrl_UnknownMimeType_NavigationFromFrame) { + EXPECT_TRUE(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()"); +} + +// Test navigation of the main frame to a filesystem URL with an unknown +// mimetype from a subframe. Navigations to filesystem URLs don't end up as +// downloads. +IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest, + FilesystemUrl_UnknownMimeType_NavigationFromFrame) { + EXPECT_TRUE(NavigateToURL( + shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html"))); + AddIFrame(shell()->web_contents()->GetMainFrame(), + embedded_test_server()->GetURL("b.com", + "/filesystem_url_navigations.html")); + + TestNavigationFromFrame(url::kFileSystemScheme, + "document.getElementById('navigate-top-frame-to-" + "unknown-mimetype').click()", + NAVIGATION_BLOCKED); +} + +//////////////////////////////////////////////////////////////////////////////// +// URLs with PDF mimetype +// +// Tests that a browser initiated navigation to a blocked scheme URL with PDF +// mime type is allowed, or initiates a download on Android. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + PDF_BrowserInitiatedNavigation_Allow) { + std::string pdf_base64; + base::Base64Encode(kPDF, &pdf_base64); + const GURL kPDFUrl(CreateURLWithBlockedScheme( + "test.pdf", IsDataURLTest() ? pdf_base64 : kPDF, "application/pdf")); + +#if !defined(OS_ANDROID) + TestNavigationObserver observer(shell()->web_contents()); + EXPECT_TRUE(NavigateToURL(shell(), kPDFUrl)); + EXPECT_EQ(kPDFUrl, observer.last_navigation_url()); + EXPECT_TRUE(observer.last_navigation_succeeded()); + EXPECT_TRUE( + shell()->web_contents()->GetLastCommittedURL().SchemeIs(GetParam())); +#else + NavigateAndCheckDownload(kPDFUrl); +#endif +} + +// Tests that a window.open to a blocked scheme is blocked if the URL has a +// mime type that will be handled by a plugin (PDF in this case). +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + PDF_WindowOpen_Block) { + Navigate(GetTestURL()); + +#if !defined(OS_ANDROID) + ExecuteScriptAndCheckWindowOpen( + shell()->web_contents()->GetMainFrame(), GetParam(), + "document.getElementById('window-open-pdf').click()", NAVIGATION_BLOCKED); +#else + if (IsDataURLTest()) { + // On Android, data URL PDFs are downloaded upon navigation. + ExecuteScriptAndCheckDownload( + shell()->web_contents()->GetMainFrame(), + "document.getElementById('window-open-pdf').click()"); + } else { + // On Android, filesystem PDF URLs are navigated and should be blocked. + ExecuteScriptAndCheckWindowOpen( + shell()->web_contents()->GetMainFrame(), GetParam(), + "document.getElementById('window-open-pdf').click()", + NAVIGATION_BLOCKED); + } +#endif +} + +// Test that a navigation to a blocked scheme URL is blocked if the URL has a +// mime type that will be handled by a plugin (PDF in this case). +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + PDF_Navigation_Block) { + Navigate(GetTestURL()); + +#if !defined(OS_ANDROID) + ExecuteScriptAndCheckPDFNavigation( + shell()->web_contents()->GetMainFrame(), GetParam(), + "document.getElementById('navigate-top-frame-to-pdf').click()", + NAVIGATION_BLOCKED); +#else + if (IsDataURLTest()) { + // On Android, data URL PDFs are downloaded upon navigation. + ExecuteScriptAndCheckDownload( + shell()->web_contents()->GetMainFrame(), + "document.getElementById('navigate-top-frame-to-pdf').click()"); + } else { + // On Android, filesystem PDF URLs are navigated and should be blocked. + ExecuteScriptAndCheckPDFNavigation( + shell()->web_contents()->GetMainFrame(), GetParam(), + "document.getElementById('navigate-top-frame-to-pdf').click()", + NAVIGATION_BLOCKED); + } +#endif +} + +// Test that a form post to a blocked scheme is blocked if the URL has a mime +// type that will be handled by a plugin (PDF in this case). +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, PDF_FormPost_Block) { + Navigate(GetTestURL()); + +#if !defined(OS_ANDROID) + ExecuteScriptAndCheckPDFNavigation( + shell()->web_contents()->GetMainFrame(), GetParam(), + "document.getElementById('form-post-to-pdf').click()", + NAVIGATION_BLOCKED); +#else + if (IsDataURLTest()) { + // On Android, data URL PDFs are downloaded upon navigation. + ExecuteScriptAndCheckDownload( + shell()->web_contents()->GetMainFrame(), + "document.getElementById('form-post-to-pdf').click()"); + } else { + // On Android, filesystem PDF URLs are navigated and should be blocked. + ExecuteScriptAndCheckPDFNavigation( + shell()->web_contents()->GetMainFrame(), GetParam(), + "document.getElementById('form-post-to-pdf').click()", + NAVIGATION_BLOCKED); + } +#endif +} + +// Tests that navigating the main frame to a blocked scheme with PDF mimetype +// from a subframe is blocked, or is downloaded on Android. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + PDF_NavigationFromFrame_Block) { + EXPECT_TRUE(NavigateToURL( + shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html"))); + AddIFrame( + shell()->web_contents()->GetMainFrame(), + embedded_test_server()->GetURL( + "b.com", base::StringPrintf("/%s_url_navigations.html", GetParam()))); + +#if !defined(OS_ANDROID) + TestPDFNavigationFromFrame( + GetParam(), + "document.getElementById('navigate-top-frame-to-pdf').click()", + NAVIGATION_BLOCKED); +#else + if (IsDataURLTest()) { + // On Android, data URL PDFs are downloaded upon navigation. + RenderFrameHost* child = + ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0); + ASSERT_TRUE(child); + if (AreAllSitesIsolatedForTesting()) { + ASSERT_TRUE(child->IsCrossProcessSubframe()); + } + ExecuteScriptAndCheckDownload( + child, "document.getElementById('navigate-top-frame-to-pdf').click()"); + } else { + // On Android, filesystem PDF URLs are navigated and should be blocked. + TestPDFNavigationFromFrame( + GetParam(), + "document.getElementById('navigate-top-frame-to-pdf').click()", + NAVIGATION_BLOCKED); + } +#endif +} + +// Tests that opening a window with a blocked scheme with PDF mimetype from a +// subframe is blocked, or is downloaded on Android. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + PDF_WindowOpenFromFrame_Block) { + EXPECT_TRUE(NavigateToURL( + shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html"))); + AddIFrame(shell()->web_contents()->GetMainFrame(), + embedded_test_server()->GetURL( + base::StringPrintf("/%s_url_navigations.html", GetParam()))); + +#if !defined(OS_ANDROID) + TestWindowOpenFromFrame(GetParam(), + "document.getElementById('window-open-pdf').click()", + NAVIGATION_BLOCKED); +#else + if (IsDataURLTest()) { + // On Android, data URL PDFs are downloaded upon navigation. + RenderFrameHost* child = + ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0); + ASSERT_TRUE(child); + if (AreAllSitesIsolatedForTesting()) { + ASSERT_TRUE(child->IsCrossProcessSubframe()); + } + ExecuteScriptAndCheckDownload( + child, "document.getElementById('window-open-pdf').click()"); + } else { + // On Android, filesystem PDF URLs are navigated and should be blocked. + TestWindowOpenFromFrame( + GetParam(), "document.getElementById('window-open-pdf').click()", + NAVIGATION_BLOCKED); + } +#endif +} + +// Tests that navigating the top frame to a blocked scheme with PDF mimetype +// from a subframe is blocked even if the top frame already has a blocked +// scheme. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + PDF_NavigationFromFrame_TopFrameHasBlockedScheme_Block) { + EXPECT_TRUE(NavigateToURL(shell(), CreateEmptyURLWithBlockedScheme())); + AddIFrame(shell()->web_contents()->GetMainFrame(), GetTestURL()); + +#if !defined(OS_ANDROID) + TestPDFNavigationFromFrame( + GetParam(), + "document.getElementById('navigate-top-frame-to-pdf').click()", + NAVIGATION_BLOCKED); +#else + if (IsDataURLTest()) { + // On Android, data URL PDFs are downloaded upon navigation. + RenderFrameHost* child = + ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0); + ASSERT_TRUE(child); + if (AreAllSitesIsolatedForTesting()) { + ASSERT_TRUE(child->IsCrossProcessSubframe()); + } + ExecuteScriptAndCheckDownload( + child, "document.getElementById('navigate-top-frame-to-pdf').click()"); + } else { + // On Android, filesystem PDF URLs are navigated and should be blocked. + TestPDFNavigationFromFrame( + GetParam(), + "document.getElementById('navigate-top-frame-to-pdf').click()", + NAVIGATION_BLOCKED); + } +#endif +} + +// Tests that opening a window with a blocked scheme with PDF mimetype from a +// subframe is blocked even if the top frame already has a blocked scheme. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + PDF_WindowOpenFromFrame_TopFrameHasBlockedScheme_Block) { + EXPECT_TRUE(NavigateToURL(shell(), CreateEmptyURLWithBlockedScheme())); + AddIFrame(shell()->web_contents()->GetMainFrame(), GetTestURL()); + +#if !defined(OS_ANDROID) + TestWindowOpenFromFrame(GetParam(), + "document.getElementById('window-open-pdf').click()", + NAVIGATION_BLOCKED); +#else + if (IsDataURLTest()) { + // On Android, data URL PDFs are downloaded upon navigation. + RenderFrameHost* child = + ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0); + ASSERT_TRUE(child); + if (AreAllSitesIsolatedForTesting()) { + ASSERT_TRUE(child->IsCrossProcessSubframe()); + } + ExecuteScriptAndCheckDownload( + child, "document.getElementById('window-open-pdf').click()"); + } else { + // On Android, filesystem PDF URLs are navigated to and should be blocked. + TestWindowOpenFromFrame( + GetParam(), "document.getElementById('window-open-pdf').click()", + NAVIGATION_BLOCKED); + } +#endif +} + +// Test case to verify that redirects to blocked schemes are properly +// disallowed, even when invoked through history navigations. See +// https://crbug.com/723796. +IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, + WindowOpenRedirectAndBack) { + Navigate(GetTestURL()); + + // This test will need to navigate the newly opened window. + ShellAddedObserver new_shell_observer; + EXPECT_TRUE( + ExecuteScript(shell()->web_contents(), + "document.getElementById('window-open-redirect').click()")); + Shell* new_shell = new_shell_observer.GetShell(); + NavigationController* controller = + &new_shell->web_contents()->GetController(); + WaitForLoadStop(new_shell->web_contents()); + + // The window.open() should have resulted in an error page. The blocked + // URL should be in both the actual and the virtual URL. + { + EXPECT_EQ(0, controller->GetLastCommittedEntryIndex()); + NavigationEntry* entry = controller->GetLastCommittedEntry(); + EXPECT_EQ(PAGE_TYPE_ERROR, entry->GetPageType()); + EXPECT_FALSE(entry->GetURL().SchemeIs(url::kDataScheme)); + EXPECT_FALSE(entry->GetURL().SchemeIs(url::kFileSystemScheme)); + EXPECT_TRUE(base::StartsWith( + entry->GetURL().spec(), + embedded_test_server()->GetURL("/server-redirect?").spec(), + base::CompareCase::SENSITIVE)); + EXPECT_EQ(entry->GetURL(), entry->GetVirtualURL()); + } + + // Navigate forward and then go back to ensure the navigation to data: or + // filesystem: URL is blocked. Use a browser-initiated back navigation, + // equivalent to user pressing the back button. + EXPECT_TRUE( + NavigateToURL(new_shell, embedded_test_server()->GetURL("/title1.html"))); + EXPECT_EQ(1, controller->GetLastCommittedEntryIndex()); + { + TestNavigationObserver observer(new_shell->web_contents()); + controller->GoBack(); + observer.Wait(); + + NavigationEntry* entry = controller->GetLastCommittedEntry(); + EXPECT_EQ(0, controller->GetLastCommittedEntryIndex()); + EXPECT_FALSE(entry->GetURL().SchemeIs(url::kDataScheme)); + EXPECT_FALSE(entry->GetURL().SchemeIs(url::kFileSystemScheme)); + EXPECT_TRUE(base::StartsWith( + entry->GetURL().spec(), + embedded_test_server()->GetURL("/server-redirect?").spec(), + base::CompareCase::SENSITIVE)); + EXPECT_EQ(entry->GetURL(), entry->GetVirtualURL()); + } + + // Do another new navigation, but then use JavaScript to navigate back, + // equivalent to document executing JS. + EXPECT_TRUE( + NavigateToURL(new_shell, embedded_test_server()->GetURL("/title1.html"))); + EXPECT_EQ(1, controller->GetLastCommittedEntryIndex()); + { + TestNavigationObserver observer(new_shell->web_contents()); + EXPECT_TRUE(ExecuteScript(new_shell, "history.go(-1)")); + observer.Wait(); + + NavigationEntry* entry = controller->GetLastCommittedEntry(); + EXPECT_EQ(0, controller->GetLastCommittedEntryIndex()); + EXPECT_FALSE(entry->GetURL().SchemeIs(url::kDataScheme)); + EXPECT_FALSE(entry->GetURL().SchemeIs(url::kFileSystemScheme)); + EXPECT_TRUE(base::StartsWith( + entry->GetURL().spec(), + embedded_test_server()->GetURL("/server-redirect?").spec(), + base::CompareCase::SENSITIVE)); + EXPECT_EQ(entry->GetURL(), entry->GetVirtualURL()); + } +} + +} // namespace content |