| // Copyright (c) 2012 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 <utility> |
| |
| #include "base/command_line.h" |
| #include "base/run_loop.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/download/download_prefs.h" |
| #include "chrome/browser/extensions/extension_apitest.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/common/extensions/api/streams_private.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/download_item.h" |
| #include "content/public/browser/download_manager.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/download_test_observer.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/common/manifest_handlers/mime_types_handler.h" |
| #include "extensions/test/result_catcher.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| |
| using content::BrowserContext; |
| using content::BrowserThread; |
| using content::DownloadItem; |
| using content::DownloadManager; |
| using content::DownloadUrlParameters; |
| using content::WebContents; |
| using extensions::Event; |
| using extensions::ExtensionSystem; |
| using extensions::ResultCatcher; |
| using net::test_server::BasicHttpResponse; |
| using net::test_server::HttpRequest; |
| using net::test_server::HttpResponse; |
| using testing::_; |
| |
| namespace streams_private = extensions::api::streams_private; |
| |
| namespace { |
| |
| // Test server's request handler. |
| // Returns response that should be sent by the test server. |
| std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request) { |
| std::unique_ptr<BasicHttpResponse> response(new BasicHttpResponse()); |
| |
| // For relative path "/doc_path.doc", return success response with MIME type |
| // "application/msword". |
| if (request.relative_url == "/doc_path.doc") { |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("application/msword"); |
| return std::move(response); |
| } |
| |
| // For relative path "/spreadsheet_path.xls", return success response with |
| // MIME type "application/xls". |
| if (request.relative_url == "/spreadsheet_path.xls") { |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("application/msexcel"); |
| // Test that multiple headers with the same name are merged. |
| response->AddCustomHeader("Test-Header", "part1"); |
| response->AddCustomHeader("Test-Header", "part2"); |
| return std::move(response); |
| } |
| |
| // For relative path "/text_path_attch.txt", return success response with |
| // MIME type "text/plain" and content "txt content". Also, set content |
| // disposition to be attachment. |
| if (request.relative_url == "/text_path_attch.txt") { |
| response->set_code(net::HTTP_OK); |
| response->set_content("txt content"); |
| response->set_content_type("text/plain"); |
| response->AddCustomHeader("Content-Disposition", |
| "attachment; filename=test_path.txt"); |
| return std::move(response); |
| } |
| |
| // For relative path "/test_path_attch.txt", return success response with |
| // MIME type "text/plain" and content "txt content". |
| if (request.relative_url == "/text_path.txt") { |
| response->set_code(net::HTTP_OK); |
| response->set_content("txt content"); |
| response->set_content_type("text/plain"); |
| return std::move(response); |
| } |
| |
| // A random HTML file to navigate to. |
| if (request.relative_url == "/index.html") { |
| response->set_code(net::HTTP_OK); |
| response->set_content("html content"); |
| response->set_content_type("text/html"); |
| return std::move(response); |
| } |
| |
| // RTF files for testing chrome.streamsPrivate.abort(). |
| if (request.relative_url == "/abort.rtf" || |
| request.relative_url == "/no_abort.rtf") { |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("application/rtf"); |
| return std::move(response); |
| } |
| |
| // Respond to /favicon.ico for navigating to the page. |
| if (request.relative_url == "/favicon.ico") { |
| response->set_code(net::HTTP_NOT_FOUND); |
| return std::move(response); |
| } |
| |
| // No other requests should be handled in the tests. |
| EXPECT_TRUE(false) << "NOTREACHED!"; |
| response->set_code(net::HTTP_NOT_FOUND); |
| return std::move(response); |
| } |
| |
| // Tests to verify that resources are correctly intercepted by |
| // StreamsResourceThrottle. |
| // The test extension expects the resources that should be handed to the |
| // extension to have MIME type 'application/msword' and the resources that |
| // should be downloaded by the browser to have MIME type 'text/plain'. |
| class StreamsPrivateApiTest : public ExtensionApiTest { |
| public: |
| StreamsPrivateApiTest() {} |
| |
| ~StreamsPrivateApiTest() override {} |
| |
| void SetUpOnMainThread() override { |
| // Init test server. |
| test_server_.reset(new net::EmbeddedTestServer); |
| test_server_->RegisterRequestHandler(base::Bind(&HandleRequest)); |
| ASSERT_TRUE(test_server_->Start()); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ExtensionApiTest::SetUpOnMainThread(); |
| } |
| |
| void TearDownOnMainThread() override { |
| // Tear down the test server. |
| EXPECT_TRUE(test_server_->ShutdownAndWaitUntilComplete()); |
| test_server_.reset(); |
| ExtensionApiTest::TearDownOnMainThread(); |
| } |
| |
| void InitializeDownloadSettings() { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| ASSERT_TRUE(browser()); |
| ASSERT_TRUE(downloads_dir_.CreateUniqueTempDir()); |
| |
| // Setup default downloads directory to the scoped tmp directory created for |
| // the test. |
| browser()->profile()->GetPrefs()->SetFilePath( |
| prefs::kDownloadDefaultDirectory, downloads_dir_.GetPath()); |
| // Ensure there are no prompts for download during the test. |
| browser()->profile()->GetPrefs()->SetBoolean( |
| prefs::kPromptForDownload, false); |
| |
| DownloadManager* manager = GetDownloadManager(); |
| DownloadPrefs::FromDownloadManager(manager)->ResetAutoOpen(); |
| } |
| |
| // Sends onExecuteContentHandler event with the MIME type "test/done" to the |
| // test extension. |
| // The test extension calls 'chrome.test.notifySuccess' when it receives the |
| // event with the "test/done" MIME type (unless the 'chrome.test.notifyFail' |
| // has already been called). |
| void SendDoneEvent() { |
| streams_private::StreamInfo info; |
| info.mime_type = "test/done"; |
| info.original_url = "http://foo"; |
| info.stream_url = "blob://bar"; |
| info.tab_id = 10; |
| info.expected_content_size = 20; |
| |
| std::unique_ptr<Event> event(new Event( |
| extensions::events::STREAMS_PRIVATE_ON_EXECUTE_MIME_TYPE_HANDLER, |
| streams_private::OnExecuteMimeTypeHandler::kEventName, |
| streams_private::OnExecuteMimeTypeHandler::Create(info))); |
| |
| extensions::EventRouter::Get(browser()->profile()) |
| ->DispatchEventToExtension(test_extension_id_, std::move(event)); |
| } |
| |
| // Loads the test extension and set's up its file_browser_handler to handle |
| // 'application/msword' and 'text/plain' MIME types. |
| // The extension will notify success when it detects an event with the MIME |
| // type 'application/msword' and notify fail when it detects an event with the |
| // MIME type 'text/plain'. |
| const extensions::Extension* LoadTestExtension() { |
| // The test extension id is set by the key value in the manifest. |
| test_extension_id_ = "oickdpebdnfbgkcaoklfcdhjniefkcji"; |
| |
| const extensions::Extension* extension = LoadExtension( |
| test_data_dir_.AppendASCII("streams_private/handle_mime_type")); |
| if (!extension) |
| return NULL; |
| |
| MimeTypesHandler* handler = MimeTypesHandler::GetHandler(extension); |
| if (!handler) { |
| message_ = "No mime type handlers defined."; |
| return NULL; |
| } |
| |
| DCHECK_EQ(test_extension_id_, extension->id()); |
| |
| return extension; |
| } |
| |
| // Returns the download manager for the current browser. |
| DownloadManager* GetDownloadManager() const { |
| DownloadManager* download_manager = |
| BrowserContext::GetDownloadManager(browser()->profile()); |
| EXPECT_TRUE(download_manager); |
| return download_manager; |
| } |
| |
| // Deletes the download and waits until it's flushed. |
| // The |manager| should have |download| in its list of downloads. |
| void DeleteDownloadAndWaitForFlush(DownloadItem* download, |
| DownloadManager* manager) { |
| scoped_refptr<content::DownloadTestFlushObserver> flush_observer( |
| new content::DownloadTestFlushObserver(manager)); |
| download->Remove(); |
| flush_observer->WaitForFlush(); |
| } |
| |
| protected: |
| std::string test_extension_id_; |
| // The HTTP server used in the tests. |
| std::unique_ptr<net::EmbeddedTestServer> test_server_; |
| base::ScopedTempDir downloads_dir_; |
| }; |
| |
| // Tests that navigating to a resource with a MIME type handleable by an |
| // installed, white-listed extension invokes the extension's |
| // onExecuteContentHandler event (and does not start a download). |
| IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, Navigate) { |
| ASSERT_TRUE(LoadTestExtension()) << message_; |
| |
| ResultCatcher catcher; |
| |
| ui_test_utils::NavigateToURL(browser(), |
| test_server_->GetURL("/doc_path.doc")); |
| |
| // Wait for the response from the test server. |
| base::RunLoop().RunUntilIdle(); |
| |
| // There should be no downloads started by the navigation. |
| DownloadManager* download_manager = GetDownloadManager(); |
| std::vector<DownloadItem*> downloads; |
| download_manager->GetAllDownloads(&downloads); |
| ASSERT_EQ(0u, downloads.size()); |
| |
| // The test extension should receive onExecuteContentHandler event with MIME |
| // type 'application/msword' (and call chrome.test.notifySuccess). |
| EXPECT_TRUE(catcher.GetNextResult()); |
| } |
| |
| // Tests that navigating to a file URL also intercepts despite there being no |
| // HTTP headers. This is a regression test for https://crbug.com/416433. |
| IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, FileURL) { |
| ASSERT_TRUE(LoadTestExtension()) << message_; |
| |
| ResultCatcher catcher; |
| |
| ui_test_utils::NavigateToURL(browser(), ui_test_utils::GetTestUrl( |
| base::FilePath(FILE_PATH_LITERAL("downloads")), |
| base::FilePath(FILE_PATH_LITERAL("Picture_1.doc")))); |
| |
| // There should be no downloads started by the navigation. |
| DownloadManager* download_manager = GetDownloadManager(); |
| std::vector<DownloadItem*> downloads; |
| download_manager->GetAllDownloads(&downloads); |
| ASSERT_EQ(0u, downloads.size()); |
| |
| // The test extension should receive onExecuteContentHandler event with MIME |
| // type 'application/msword' (and call chrome.test.notifySuccess). |
| EXPECT_TRUE(catcher.GetNextResult()); |
| } |
| |
| // Tests that navigating cross-site to a resource with a MIME type handleable by |
| // an installed, white-listed extension invokes the extension's |
| // onExecuteContentHandler event (and does not start a download). |
| // Regression test for http://crbug.com/342999. |
| IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, NavigateCrossSite) { |
| ASSERT_TRUE(LoadTestExtension()) << message_; |
| |
| ResultCatcher catcher; |
| |
| // Navigate to a URL on a different hostname. |
| static const char kInitialHost[] = "www.example.com"; |
| GURL::Replacements replacements; |
| replacements.SetHostStr(kInitialHost); |
| GURL initial_url = |
| test_server_->GetURL("/index.html").ReplaceComponents(replacements); |
| ui_test_utils::NavigateToURL(browser(), initial_url); |
| |
| // Now navigate to the doc file; the extension should pick it up normally. |
| ui_test_utils::NavigateToURL(browser(), |
| test_server_->GetURL("/doc_path.doc")); |
| |
| // Wait for the response from the test server. |
| base::RunLoop().RunUntilIdle(); |
| |
| // There should be no downloads started by the navigation. |
| DownloadManager* download_manager = GetDownloadManager(); |
| std::vector<DownloadItem*> downloads; |
| download_manager->GetAllDownloads(&downloads); |
| ASSERT_EQ(0u, downloads.size()); |
| |
| // The test extension should receive onExecuteContentHandler event with MIME |
| // type 'application/msword' (and call chrome.test.notifySuccess). |
| EXPECT_TRUE(catcher.GetNextResult()); |
| } |
| |
| // Flaky on Linux and ChromeOS: http://crbug.com/746526. |
| #if defined(OS_LINUX) |
| #define MAYBE_NavigateToAnAttachment DISABLED_NavigateToAnAttachment |
| #else |
| #define MAYBE_NavigateToAnAttachment NavigateToAnAttachment |
| #endif |
| |
| // Tests that navigation to an attachment starts a download, even if there is an |
| // extension with a file browser handler that can handle the attachment's MIME |
| // type. |
| IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, MAYBE_NavigateToAnAttachment) { |
| InitializeDownloadSettings(); |
| |
| ASSERT_TRUE(LoadTestExtension()) << message_; |
| |
| ResultCatcher catcher; |
| |
| // The test should start a download. |
| DownloadManager* download_manager = GetDownloadManager(); |
| std::unique_ptr<content::DownloadTestObserver> download_observer( |
| new content::DownloadTestObserverInProgress(download_manager, 1)); |
| |
| ui_test_utils::NavigateToURL(browser(), |
| test_server_->GetURL("/text_path_attch.txt")); |
| |
| // Wait for the download to start. |
| download_observer->WaitForFinished(); |
| |
| // There should be one download started by the navigation. |
| DownloadManager::DownloadVector downloads; |
| download_manager->GetAllDownloads(&downloads); |
| ASSERT_EQ(1u, downloads.size()); |
| |
| // Cancel and delete the download started in the test. |
| DeleteDownloadAndWaitForFlush(downloads[0], download_manager); |
| |
| // The test extension should not receive any events by now. Send it an event |
| // with MIME type "test/done", so it stops waiting for the events. (If there |
| // was an event with MIME type 'text/plain', |catcher.GetNextResult()| will |
| // fail regardless of the sent event; chrome.test.notifySuccess will not be |
| // called by the extension). |
| SendDoneEvent(); |
| EXPECT_TRUE(catcher.GetNextResult()); |
| } |
| |
| // Flaky on Linux and Win 10: http://crbug.com/746526. |
| #if defined(OS_LINUX) || defined(OS_WIN) |
| #define MAYBE_DirectDownload DISABLED_DirectDownload |
| #else |
| #define MAYBE_DirectDownload DirectDownload |
| #endif |
| |
| // Tests that direct download requests don't get intercepted by |
| // StreamsResourceThrottle, even if there is an extension with a file |
| // browser handler that can handle the download's MIME type. |
| IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, MAYBE_DirectDownload) { |
| InitializeDownloadSettings(); |
| |
| ASSERT_TRUE(LoadTestExtension()) << message_; |
| |
| ResultCatcher catcher; |
| |
| DownloadManager* download_manager = GetDownloadManager(); |
| std::unique_ptr<content::DownloadTestObserver> download_observer( |
| new content::DownloadTestObserverInProgress(download_manager, 1)); |
| |
| // The resource's URL on the test server. |
| GURL url = test_server_->GetURL("/text_path.txt"); |
| |
| // The download's target file path. |
| base::FilePath target_path = |
| downloads_dir_.GetPath().Append(FILE_PATH_LITERAL("download_target.txt")); |
| |
| // Set the downloads parameters. |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ASSERT_TRUE(web_contents); |
| std::unique_ptr<DownloadUrlParameters> params( |
| DownloadUrlParameters::CreateForWebContentsMainFrame( |
| web_contents, url, TRAFFIC_ANNOTATION_FOR_TESTS)); |
| params->set_file_path(target_path); |
| |
| // Start download of the URL with a path "/text_path.txt" on the test server. |
| download_manager->DownloadUrl(std::move(params)); |
| |
| // Wait for the download to start. |
| download_observer->WaitForFinished(); |
| |
| // There should have been one download. |
| std::vector<DownloadItem*> downloads; |
| download_manager->GetAllDownloads(&downloads); |
| ASSERT_EQ(1u, downloads.size()); |
| |
| // Cancel and delete the download statred in the test. |
| DeleteDownloadAndWaitForFlush(downloads[0], download_manager); |
| |
| // The test extension should not receive any events by now. Send it an event |
| // with MIME type "test/done", so it stops waiting for the events. (If there |
| // was an event with MIME type 'text/plain', |catcher.GetNextResult()| will |
| // fail regardless of the sent event; chrome.test.notifySuccess will not be |
| // called by the extension). |
| SendDoneEvent(); |
| EXPECT_TRUE(catcher.GetNextResult()); |
| } |
| |
| // Tests that response headers are correctly passed to the API and that multiple |
| // repsonse headers with the same name are merged correctly. |
| IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, Headers) { |
| ASSERT_TRUE(LoadTestExtension()) << message_; |
| |
| ResultCatcher catcher; |
| |
| ui_test_utils::NavigateToURL(browser(), |
| test_server_->GetURL("/spreadsheet_path.xls")); |
| |
| // Wait for the response from the test server. |
| base::RunLoop().RunUntilIdle(); |
| |
| // There should be no downloads started by the navigation. |
| DownloadManager* download_manager = GetDownloadManager(); |
| std::vector<DownloadItem*> downloads; |
| download_manager->GetAllDownloads(&downloads); |
| ASSERT_EQ(0u, downloads.size()); |
| |
| // The test extension should receive onExecuteContentHandler event with MIME |
| // type 'application/msexcel' (and call chrome.test.notifySuccess). |
| EXPECT_TRUE(catcher.GetNextResult()); |
| } |
| |
| // Tests that chrome.streamsPrivate.abort() works correctly. |
| IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, Abort) { |
| ASSERT_TRUE(LoadTestExtension()) << message_; |
| |
| ResultCatcher catcher; |
| ui_test_utils::NavigateToURL(browser(), |
| test_server_->GetURL("/no_abort.rtf")); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(catcher.GetNextResult()); |
| |
| ui_test_utils::NavigateToURL(browser(), |
| test_server_->GetURL("/abort.rtf")); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(catcher.GetNextResult()); |
| } |
| |
| } // namespace |