| // 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/bind.h" |
| #include "base/files/file_util.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/network_service_instance.h" |
| #include "content/public/browser/url_data_source.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/browser/web_ui_controller.h" |
| #include "content/public/browser/web_ui_controller_factory.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/network_service_util.h" |
| #include "content/public/common/url_utils.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/simple_url_loader_test_helper.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/test/content_browser_test_utils_internal.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/test/embedded_test_server/default_handlers.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/cpp/network_switches.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/cpp/simple_url_loader.h" |
| #include "services/network/public/mojom/network_service_test.mojom.h" |
| |
| #if defined(OS_ANDROID) |
| #include "base/android/application_status_listener.h" |
| #endif |
| |
| namespace content { |
| |
| namespace { |
| |
| class WebUITestWebUIControllerFactory : public WebUIControllerFactory { |
| public: |
| std::unique_ptr<WebUIController> CreateWebUIControllerForURL( |
| WebUI* web_ui, |
| const GURL& url) override { |
| std::string foo(url.path()); |
| if (url.path() == "/nobinding/") |
| web_ui->SetBindings(0); |
| return HasWebUIScheme(url) ? std::make_unique<WebUIController>(web_ui) |
| : nullptr; |
| } |
| WebUI::TypeID GetWebUIType(BrowserContext* browser_context, |
| const GURL& url) override { |
| return HasWebUIScheme(url) ? reinterpret_cast<WebUI::TypeID>(1) : nullptr; |
| } |
| bool UseWebUIForURL(BrowserContext* browser_context, |
| const GURL& url) override { |
| return HasWebUIScheme(url); |
| } |
| bool UseWebUIBindingsForURL(BrowserContext* browser_context, |
| const GURL& url) override { |
| return HasWebUIScheme(url); |
| } |
| }; |
| |
| class TestWebUIDataSource : public URLDataSource { |
| public: |
| TestWebUIDataSource() {} |
| ~TestWebUIDataSource() override {} |
| |
| std::string GetSource() override { return "webui"; } |
| |
| void StartDataRequest( |
| const std::string& path, |
| const WebContents::Getter& wc_getter, |
| const URLDataSource::GotDataCallback& callback) override { |
| std::string dummy_html = "<html><body>Foo</body></html>"; |
| scoped_refptr<base::RefCountedString> response = |
| base::RefCountedString::TakeString(&dummy_html); |
| callback.Run(response.get()); |
| } |
| |
| std::string GetMimeType(const std::string& path) override { |
| return "text/html"; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TestWebUIDataSource); |
| }; |
| |
| class NetworkServiceBrowserTest : public ContentBrowserTest { |
| public: |
| NetworkServiceBrowserTest() { |
| EXPECT_TRUE(embedded_test_server()->Start()); |
| EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| |
| WebUIControllerFactory::RegisterFactory(&factory_); |
| } |
| |
| bool ExecuteScript(const std::string& script) { |
| bool xhr_result = false; |
| // The JS call will fail if disallowed because the process will be killed. |
| bool execute_result = |
| ExecuteScriptAndExtractBool(shell(), script, &xhr_result); |
| return xhr_result && execute_result; |
| } |
| |
| bool FetchResource(const GURL& url, bool synchronous = false) { |
| if (!url.is_valid()) |
| return false; |
| std::string script = JsReplace( |
| "var xhr = new XMLHttpRequest();" |
| "xhr.open('GET', $1, $2);" |
| "xhr.onload = function (e) {" |
| " if (xhr.readyState === 4) {" |
| " window.domAutomationController.send(xhr.status === 200);" |
| " }" |
| "};" |
| "xhr.onerror = function () {" |
| " window.domAutomationController.send(false);" |
| "};" |
| "try {" |
| " xhr.send(null);" |
| "} catch (error) {" |
| " window.domAutomationController.send(false);" |
| "}", |
| url, !synchronous); |
| return ExecuteScript(script); |
| } |
| |
| bool CheckCanLoadHttp() { |
| return FetchResource(embedded_test_server()->GetURL("/echo")); |
| } |
| |
| void SetUpOnMainThread() override { |
| URLDataSource::Add(shell()->web_contents()->GetBrowserContext(), |
| std::make_unique<TestWebUIDataSource>()); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Since we assume exploited renderer process, it can bypass the same origin |
| // policy at will. Simulate that by passing the disable-web-security flag. |
| command_line->AppendSwitch(switches::kDisableWebSecurity); |
| IsolateAllSitesForTesting(command_line); |
| } |
| |
| base::FilePath GetCacheDirectory() { return temp_dir_.GetPath(); } |
| |
| base::FilePath GetCacheIndexDirectory() { |
| return GetCacheDirectory().AppendASCII("index-dir"); |
| } |
| |
| void LoadURL(const GURL& url, |
| network::mojom::URLLoaderFactory* loader_factory) { |
| std::unique_ptr<network::ResourceRequest> request = |
| std::make_unique<network::ResourceRequest>(); |
| request->url = url; |
| SimpleURLLoaderTestHelper simple_loader_helper; |
| std::unique_ptr<network::SimpleURLLoader> simple_loader = |
| network::SimpleURLLoader::Create(std::move(request), |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| |
| simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie( |
| loader_factory, simple_loader_helper.GetCallback()); |
| simple_loader_helper.WaitForCallback(); |
| ASSERT_TRUE(simple_loader_helper.response_body()); |
| } |
| |
| private: |
| WebUITestWebUIControllerFactory factory_; |
| base::ScopedTempDir temp_dir_; |
| |
| DISALLOW_COPY_AND_ASSIGN(NetworkServiceBrowserTest); |
| }; |
| |
| // Verifies that WebUI pages with WebUI bindings can't make network requests. |
| IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, WebUIBindingsNoHttp) { |
| GURL test_url(GetWebUIURL("webui/")); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| RenderProcessHostKillWaiter kill_waiter( |
| shell()->web_contents()->GetMainFrame()->GetProcess()); |
| ASSERT_FALSE(CheckCanLoadHttp()); |
| EXPECT_EQ(bad_message::WEBUI_BAD_SCHEME_ACCESS, kill_waiter.Wait()); |
| } |
| |
| // Verifies that WebUI pages without WebUI bindings can make network requests. |
| IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, NoWebUIBindingsHttp) { |
| GURL test_url(GetWebUIURL("webui/nobinding/")); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| ASSERT_TRUE(CheckCanLoadHttp()); |
| } |
| |
| // Verifies the filesystem URLLoaderFactory's check, using |
| // ChildProcessSecurityPolicyImpl::CanRequestURL is properly rejected. |
| IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, |
| FileSystemBindingsCorrectOrigin) { |
| GURL test_url(GetWebUIURL("webui/nobinding/")); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| |
| // Note: must be filesystem scheme (obviously). |
| // file: is not a safe web scheme (see IsWebSafeScheme), |
| // and /etc/passwd fails the CanCommitURL check. |
| GURL file_url("filesystem:file:///etc/passwd"); |
| EXPECT_FALSE(FetchResource(file_url)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, |
| SimpleUrlLoader_NoAuthWhenNoWebContents) { |
| auto request = std::make_unique<network::ResourceRequest>(); |
| request->url = embedded_test_server()->GetURL("/auth-basic?password="); |
| auto loader = network::SimpleURLLoader::Create(std::move(request), |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| auto loader_factory = BrowserContext::GetDefaultStoragePartition( |
| shell()->web_contents()->GetBrowserContext()) |
| ->GetURLLoaderFactoryForBrowserProcess(); |
| scoped_refptr<net::HttpResponseHeaders> headers; |
| base::RunLoop loop; |
| loader->DownloadHeadersOnly( |
| loader_factory.get(), |
| base::BindOnce( |
| [](base::OnceClosure quit_closure, |
| scoped_refptr<net::HttpResponseHeaders>* rh_out, |
| scoped_refptr<net::HttpResponseHeaders> rh_in) { |
| *rh_out = rh_in; |
| std::move(quit_closure).Run(); |
| }, |
| loop.QuitClosure(), &headers)); |
| loop.Run(); |
| ASSERT_TRUE(headers.get()); |
| ASSERT_EQ(headers->response_code(), 401); |
| } |
| |
| #if defined(OS_ANDROID) |
| IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, |
| HttpCacheWrittenToDiskOnApplicationStateChange) { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| |
| // Create network context with cache pointing to the temp cache dir. |
| mojo::Remote<network::mojom::NetworkContext> network_context; |
| network::mojom::NetworkContextParamsPtr context_params = |
| network::mojom::NetworkContextParams::New(); |
| context_params->http_cache_path = GetCacheDirectory(); |
| GetNetworkService()->CreateNetworkContext( |
| network_context.BindNewPipeAndPassReceiver(), std::move(context_params)); |
| |
| network::mojom::URLLoaderFactoryParamsPtr params = |
| network::mojom::URLLoaderFactoryParams::New(); |
| params->process_id = network::mojom::kBrowserProcessId; |
| params->is_corb_enabled = false; |
| network::mojom::URLLoaderFactoryPtr loader_factory; |
| network_context->CreateURLLoaderFactory(mojo::MakeRequest(&loader_factory), |
| std::move(params)); |
| |
| // Load a URL and check the cache index size. |
| LoadURL(embedded_test_server()->GetURL("/cachetime"), loader_factory.get()); |
| int64_t directory_size = base::ComputeDirectorySize(GetCacheIndexDirectory()); |
| |
| // Load another URL, cache index should not be written to disk yet. |
| LoadURL(embedded_test_server()->GetURL("/cachetime?foo"), |
| loader_factory.get()); |
| EXPECT_EQ(directory_size, |
| base::ComputeDirectorySize(GetCacheIndexDirectory())); |
| |
| // After application state changes, cache index should be written to disk. |
| base::android::ApplicationStatusListener::NotifyApplicationStateChange( |
| base::android::APPLICATION_STATE_HAS_STOPPED_ACTIVITIES); |
| base::RunLoop().RunUntilIdle(); |
| FlushNetworkServiceInstanceForTesting(); |
| disk_cache::FlushCacheThreadForTesting(); |
| |
| EXPECT_GT(base::ComputeDirectorySize(GetCacheIndexDirectory()), |
| directory_size); |
| } |
| |
| class NetworkConnectionObserver |
| : public network::NetworkConnectionTracker::NetworkConnectionObserver { |
| public: |
| NetworkConnectionObserver() { |
| content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this); |
| content::GetNetworkConnectionTracker()->GetConnectionType( |
| &last_connection_type_, |
| base::BindOnce(&NetworkConnectionObserver::OnConnectionChanged, |
| base::Unretained(this))); |
| } |
| |
| ~NetworkConnectionObserver() override { |
| content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver( |
| this); |
| } |
| |
| void WaitForConnectionType(network::mojom::ConnectionType type) { |
| type_to_wait_for_ = type; |
| if (last_connection_type_ == type_to_wait_for_) |
| return; |
| |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| run_loop_->Run(); |
| } |
| |
| // network::NetworkConnectionTracker::NetworkConnectionObserver: |
| void OnConnectionChanged(network::mojom::ConnectionType type) override { |
| last_connection_type_ = type; |
| if (run_loop_ && type_to_wait_for_ == type) |
| run_loop_->Quit(); |
| } |
| |
| private: |
| network::mojom::ConnectionType type_to_wait_for_ = |
| network::mojom::ConnectionType::CONNECTION_UNKNOWN; |
| network::mojom::ConnectionType last_connection_type_ = |
| network::mojom::ConnectionType::CONNECTION_UNKNOWN; |
| std::unique_ptr<base::RunLoop> run_loop_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, |
| ConnectionTypeChangeSyncedToNetworkProcess) { |
| NetworkConnectionObserver observer; |
| net::NetworkChangeNotifier::NotifyObserversOfConnectionTypeChangeForTests( |
| net::NetworkChangeNotifier::CONNECTION_WIFI); |
| observer.WaitForConnectionType( |
| network::mojom::ConnectionType::CONNECTION_WIFI); |
| |
| net::NetworkChangeNotifier::NotifyObserversOfConnectionTypeChangeForTests( |
| net::NetworkChangeNotifier::CONNECTION_ETHERNET); |
| observer.WaitForConnectionType( |
| network::mojom::ConnectionType::CONNECTION_ETHERNET); |
| } |
| #endif |
| |
| IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, |
| MemoryPressureSentToNetworkProcess) { |
| if (IsInProcessNetworkService()) |
| return; |
| |
| mojo::Remote<network::mojom::NetworkServiceTest> network_service_test; |
| GetNetworkService()->BindTestInterface( |
| network_service_test.BindNewPipeAndPassReceiver()); |
| // TODO(crbug.com/901026): Make sure the network process is started to avoid a |
| // deadlock on Android. |
| network_service_test.FlushForTesting(); |
| |
| mojo::ScopedAllowSyncCallForTesting allow_sync_call; |
| base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level = |
| base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE; |
| network_service_test->GetLatestMemoryPressureLevel(&memory_pressure_level); |
| EXPECT_EQ(memory_pressure_level, |
| base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE); |
| |
| base::MemoryPressureListener::NotifyMemoryPressure( |
| base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL); |
| base::RunLoop().RunUntilIdle(); |
| FlushNetworkServiceInstanceForTesting(); |
| |
| network_service_test->GetLatestMemoryPressureLevel(&memory_pressure_level); |
| EXPECT_EQ(memory_pressure_level, |
| base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL); |
| } |
| |
| // Verifies that sync XHRs don't hang if the network service crashes. |
| IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, SyncXHROnCrash) { |
| if (IsInProcessNetworkService()) |
| return; |
| |
| mojo::PendingRemote<network::mojom::NetworkServiceTest> |
| pending_network_service_test; |
| GetNetworkService()->BindTestInterface( |
| pending_network_service_test.InitWithNewPipeAndPassReceiver()); |
| |
| net::EmbeddedTestServer http_server; |
| http_server.AddDefaultHandlers(GetTestDataFilePath()); |
| http_server.RegisterRequestMonitor(base::BindLambdaForTesting( |
| [&](const net::test_server::HttpRequest& request) { |
| if (request.relative_url == "/hung") { |
| mojo::Remote<network::mojom::NetworkServiceTest> network_service_test( |
| std::move(pending_network_service_test)); |
| network_service_test->SimulateCrash(); |
| } |
| })); |
| EXPECT_TRUE(http_server.Start()); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), http_server.GetURL("/empty.html"))); |
| |
| FetchResource(http_server.GetURL("/hung"), true); |
| // If the renderer is hung the test will hang. |
| } |
| |
| // Verifies that sync cookie calls don't hang if the network service crashes. |
| IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, SyncCookieGetOnCrash) { |
| if (IsInProcessNetworkService()) |
| return; |
| |
| mojo::Remote<network::mojom::NetworkServiceTest> network_service_test; |
| GetNetworkService()->BindTestInterface( |
| network_service_test.BindNewPipeAndPassReceiver()); |
| network_service_test->CrashOnGetCookieList(); |
| |
| EXPECT_TRUE( |
| NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html"))); |
| |
| ASSERT_TRUE( |
| content::ExecuteScript(shell()->web_contents(), "document.cookie")); |
| // If the renderer is hung the test will hang. |
| } |
| |
| class NetworkServiceInProcessBrowserTest : public ContentBrowserTest { |
| public: |
| NetworkServiceInProcessBrowserTest() { |
| std::vector<base::Feature> features; |
| features.push_back(features::kNetworkServiceInProcess); |
| scoped_feature_list_.InitWithFeatures(features, |
| std::vector<base::Feature>()); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| EXPECT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| DISALLOW_COPY_AND_ASSIGN(NetworkServiceInProcessBrowserTest); |
| }; |
| |
| // Verifies that in-process network service works. |
| IN_PROC_BROWSER_TEST_F(NetworkServiceInProcessBrowserTest, Basic) { |
| GURL test_url = embedded_test_server()->GetURL("foo.com", "/echo"); |
| StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( |
| BrowserContext::GetDefaultStoragePartition( |
| shell()->web_contents()->GetBrowserContext())); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| ASSERT_EQ(net::OK, |
| LoadBasicRequest(partition->GetNetworkContext(), test_url)); |
| } |
| |
| class NetworkServiceInvalidLogBrowserTest : public ContentBrowserTest { |
| public: |
| NetworkServiceInvalidLogBrowserTest() {} |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitchASCII(network::switches::kLogNetLog, "/abc/def"); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| EXPECT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| private: |
| |
| DISALLOW_COPY_AND_ASSIGN(NetworkServiceInvalidLogBrowserTest); |
| }; |
| |
| // Verifies that an invalid --log-net-log flag won't crash the browser. |
| IN_PROC_BROWSER_TEST_F(NetworkServiceInvalidLogBrowserTest, Basic) { |
| GURL test_url = embedded_test_server()->GetURL("foo.com", "/echo"); |
| StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( |
| BrowserContext::GetDefaultStoragePartition( |
| shell()->web_contents()->GetBrowserContext())); |
| EXPECT_TRUE(NavigateToURL(shell(), test_url)); |
| ASSERT_EQ(net::OK, |
| LoadBasicRequest(partition->GetNetworkContext(), test_url)); |
| } |
| |
| } // namespace |
| |
| } // namespace content |