blob: 98dcfba3959f25e536fbb2d722b407859b381fcf [file] [log] [blame]
// Copyright 2014 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 "chrome/browser/loader/chrome_resource_dispatcher_host_delegate.h"
#include <stddef.h>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/task/post_task.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_command_line.h"
#include "build/buildflag.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "chrome/browser/download/download_browsertest.h"
#include "chrome/browser/loader/chrome_navigation_data.h"
#include "chrome/browser/policy/cloud/policy_header_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/scoped_account_consistency.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
#include "components/google/core/common/google_util.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/policy/core/common/cloud/policy_header_service.h"
#include "components/policy/core/common/policy_switches.h"
#include "components/prefs/pref_service.h"
#include "components/signin/core/browser/signin_buildflags.h"
#include "components/signin/core/browser/signin_pref_names.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_data.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/resource_dispatcher_host.h"
#include "content/public/browser/resource_dispatcher_host_delegate.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/url_loader_throttle.h"
#include "content/public/test/browser_test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_request_headers.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/test/embedded_test_server/request_handler_util.h"
#include "net/test/url_request/url_request_mock_http_job.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_filter.h"
#include "services/network/public/cpp/features.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "third_party/blink/public/common/service_worker/service_worker_utils.h"
#if !BUILDFLAG(ENABLE_DICE_SUPPORT)
#include "components/signin/core/browser/signin_header_helper.h"
#endif
using content::ResourceType;
namespace {
std::unique_ptr<net::test_server::HttpResponse> HandleTestRequest(
const net::test_server::HttpRequest& request) {
if (request.relative_url == "/") {
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse);
http_response->set_code(net::HTTP_OK);
http_response->set_content("Success");
return std::move(http_response);
}
return nullptr;
}
class TestDispatcherHostDelegate : public ChromeResourceDispatcherHostDelegate {
public:
TestDispatcherHostDelegate() : should_add_data_reduction_proxy_data_(false) {}
~TestDispatcherHostDelegate() override {}
// ResourceDispatcherHostDelegate implementation:
content::NavigationData* GetNavigationData(
net::URLRequest* request) const override {
if (request && should_add_data_reduction_proxy_data_) {
data_reduction_proxy::DataReductionProxyData* data =
data_reduction_proxy::DataReductionProxyData::
GetDataAndCreateIfNecessary(request);
data->set_used_data_reduction_proxy(true);
}
return ChromeResourceDispatcherHostDelegate::GetNavigationData(request);
}
// ChromeResourceDispatcherHost implementation:
void AppendStandardResourceThrottles(
net::URLRequest* request,
content::ResourceContext* resource_context,
ResourceType resource_type,
std::vector<std::unique_ptr<content::ResourceThrottle>>* throttles)
override {
++times_stardard_throttles_added_for_url_[request->url()];
ChromeResourceDispatcherHostDelegate::AppendStandardResourceThrottles(
request, resource_context, resource_type, throttles);
}
void set_should_add_data_reduction_proxy_data(
bool should_add_data_reduction_proxy_data) {
should_add_data_reduction_proxy_data_ =
should_add_data_reduction_proxy_data;
}
// Writes the number of times the standard set of throttles have been added
// for requests for the speficied URL to |count|.
void GetTimesStandardThrottlesAddedForURL(const GURL& url, int* count) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
*count = times_stardard_throttles_added_for_url_[url];
}
private:
bool should_add_data_reduction_proxy_data_;
std::map<GURL, int> times_stardard_throttles_added_for_url_;
DISALLOW_COPY_AND_ASSIGN(TestDispatcherHostDelegate);
};
// Helper class to track DidFinishNavigation and verify that NavigationData is
// added to NavigationHandle and pause/resume execution of the test.
class DidFinishNavigationObserver : public content::WebContentsObserver {
public:
DidFinishNavigationObserver(content::WebContents* web_contents,
bool add_data_reduction_proxy_data)
: content::WebContentsObserver(web_contents),
add_data_reduction_proxy_data_(add_data_reduction_proxy_data) {}
~DidFinishNavigationObserver() override {}
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override {
ChromeNavigationData* data = static_cast<ChromeNavigationData*>(
navigation_handle->GetNavigationData());
if (add_data_reduction_proxy_data_) {
EXPECT_TRUE(data->GetDataReductionProxyData());
EXPECT_TRUE(
data->GetDataReductionProxyData()->used_data_reduction_proxy());
} else {
EXPECT_FALSE(data->GetDataReductionProxyData());
}
}
private:
bool add_data_reduction_proxy_data_;
DISALLOW_COPY_AND_ASSIGN(DidFinishNavigationObserver);
};
} // namespace
class ChromeResourceDispatcherHostDelegateBrowserTest :
public InProcessBrowserTest {
public:
ChromeResourceDispatcherHostDelegateBrowserTest() {}
void SetUpOnMainThread() override {
// Hook navigations with our delegate.
dispatcher_host_delegate_.reset(new TestDispatcherHostDelegate);
content::ResourceDispatcherHost::Get()->SetDelegate(
dispatcher_host_delegate_.get());
embedded_test_server()->RegisterRequestHandler(
base::Bind(&HandleTestRequest));
ASSERT_TRUE(embedded_test_server()->Start());
}
void TearDownOnMainThread() override {
content::ResourceDispatcherHost::Get()->SetDelegate(NULL);
dispatcher_host_delegate_.reset();
}
void SetShouldAddDataReductionProxyData(bool add_data) {
dispatcher_host_delegate_->set_should_add_data_reduction_proxy_data(
add_data);
}
int GetTimesStandardThrottlesAddedForURL(const GURL& url) {
int count;
base::RunLoop run_loop;
base::PostTaskWithTraitsAndReply(
FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(
&TestDispatcherHostDelegate::GetTimesStandardThrottlesAddedForURL,
base::Unretained(dispatcher_host_delegate_.get()), url, &count),
run_loop.QuitClosure());
run_loop.Run();
return count;
}
protected:
std::unique_ptr<TestDispatcherHostDelegate> dispatcher_host_delegate_;
private:
DISALLOW_COPY_AND_ASSIGN(ChromeResourceDispatcherHostDelegateBrowserTest);
};
IN_PROC_BROWSER_TEST_F(ChromeResourceDispatcherHostDelegateBrowserTest,
NavigationDataProcessed) {
// The network service code path doesn't go through ResourceDispatcherHost.
if (base::FeatureList::IsEnabled(network::features::kNetworkService))
return;
// Servicified service worker doesn't set NavigationData.
if (blink::ServiceWorkerUtils::IsServicificationEnabled())
return;
ui_test_utils::NavigateToURL(browser(), embedded_test_server()->base_url());
{
DidFinishNavigationObserver nav_observer(
browser()->tab_strip_model()->GetActiveWebContents(), false);
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/google/google.html"));
}
SetShouldAddDataReductionProxyData(true);
{
DidFinishNavigationObserver nav_observer(
browser()->tab_strip_model()->GetActiveWebContents(), true);
ui_test_utils::NavigateToURL(browser(), embedded_test_server()->base_url());
}
}
// Mirror is not supported on Dice platforms.
#if !BUILDFLAG(ENABLE_DICE_SUPPORT)
// A delegate to insert a user generated X-Chrome-Connected header
// to a specifict URL.
class HeaderModifyingThrottle : public content::URLLoaderThrottle {
public:
HeaderModifyingThrottle() = default;
~HeaderModifyingThrottle() override = default;
void WillStartRequest(network::ResourceRequest* request,
bool* defer) override {
request->headers.SetHeader(signin::kChromeConnectedHeader, "User Data");
}
private:
DISALLOW_COPY_AND_ASSIGN(HeaderModifyingThrottle);
};
class ThrottleContentBrowserClient : public ChromeContentBrowserClient {
public:
explicit ThrottleContentBrowserClient(const GURL& watch_url)
: watch_url_(watch_url) {}
~ThrottleContentBrowserClient() override = default;
// ContentBrowserClient overrides:
std::vector<std::unique_ptr<content::URLLoaderThrottle>>
CreateURLLoaderThrottles(
const network::ResourceRequest& request,
content::ResourceContext* resource_context,
const base::RepeatingCallback<content::WebContents*()>& wc_getter,
content::NavigationUIData* navigation_ui_data,
int frame_tree_node_id) override {
std::vector<std::unique_ptr<content::URLLoaderThrottle>> throttles;
if (request.url == watch_url_)
throttles.push_back(std::make_unique<HeaderModifyingThrottle>());
return throttles;
}
private:
const GURL watch_url_;
DISALLOW_COPY_AND_ASSIGN(ThrottleContentBrowserClient);
};
// Subclass of ChromeResourceDispatcherHostDelegateBrowserTest with Mirror
// enabled.
class ChromeResourceDispatcherHostDelegateMirrorBrowserTest
: public ChromeResourceDispatcherHostDelegateBrowserTest {
private:
void SetUpOnMainThread() override {
// The test makes requests to google.com and other domains which we want to
// redirect to the test server.
host_resolver()->AddRule("*", "127.0.0.1");
// The production code only allows known ports (80 for http and 443 for
// https), but the test server runs on a random port.
google_util::IgnorePortNumbersForGoogleURLChecksForTesting();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
// HTTPS server only serves a valid cert for localhost, so this is needed to
// load pages from "www.google.com" without an interstitial.
command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
}
ScopedAccountConsistencyMirror scoped_mirror_;
};
// Verify the following items:
// 1- X-Chrome-Connected is appended on Google domains if account
// consistency is enabled and access is secure.
// 2- The header is stripped in case a request is redirected from a Gooogle
// domain to non-google domain.
// 3- The header is NOT stripped in case it is added directly by the page
// and not because it was on a secure Google domain.
// This is a regression test for crbug.com/588492.
IN_PROC_BROWSER_TEST_F(ChromeResourceDispatcherHostDelegateMirrorBrowserTest,
MirrorRequestHeader) {
browser()->profile()->GetPrefs()->SetString(prefs::kGoogleServicesUsername,
"user@gmail.com");
browser()->profile()->GetPrefs()->SetString(
prefs::kGoogleServicesUserAccountId, "account_id");
base::Lock lock;
// Map from the path of the URLs that test server sees to the request header.
// This is the path, and not URL, because the requests use different domains
// which the mock HostResolver converts to 127.0.0.1.
std::map<std::string, net::test_server::HttpRequest::HeaderMap> header_map;
embedded_test_server()->RegisterRequestMonitor(base::BindLambdaForTesting(
[&](const net::test_server::HttpRequest& request) {
base::AutoLock auto_lock(lock);
header_map[request.GetURL().path()] = request.headers;
}));
ASSERT_TRUE(embedded_test_server()->Start());
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.AddDefaultHandlers(
base::FilePath(FILE_PATH_LITERAL("chrome/test/data")));
https_server.RegisterRequestMonitor(base::BindLambdaForTesting(
[&](const net::test_server::HttpRequest& request) {
base::AutoLock auto_lock(lock);
header_map[request.GetURL().path()] = request.headers;
}));
ASSERT_TRUE(https_server.Start());
base::FilePath root_http;
base::PathService::Get(chrome::DIR_TEST_DATA, &root_http);
root_http = root_http.AppendASCII("mirror_request_header");
struct TestCase {
GURL original_url; // The URL from which the request begins.
// The path to which navigation is redirected.
std::string redirected_to_path;
bool inject_header; // Should X-Chrome-Connected header be injected to the
// original request.
bool original_url_expects_header; // Expectation: The header should be
// visible in original URL.
bool redirected_to_url_expects_header; // Expectation: The header should be
// visible in redirected URL.
};
std::vector<TestCase> all_tests;
// Neither should have the header.
// Note we need to replace the port of the redirect's URL.
base::StringPairs replacement_text;
replacement_text.push_back(std::make_pair(
"{{PORT}}", base::NumberToString(embedded_test_server()->port())));
std::string replacement_path = net::test_server::GetFilePathWithReplacements(
"/mirror_request_header/http.www.google.com.html", replacement_text);
all_tests.push_back(
{embedded_test_server()->GetURL("www.google.com", replacement_path),
"/simple.html", false, false, false});
// First one adds the header and transfers it to the second.
replacement_path = net::test_server::GetFilePathWithReplacements(
"/mirror_request_header/http.www.header_adder.com.html",
replacement_text);
all_tests.push_back(
{embedded_test_server()->GetURL("www.header_adder.com", replacement_path),
"/simple.html", true, true, true});
// First one should have the header, but not transfered to second one.
replacement_text.clear();
replacement_text.push_back(
std::make_pair("{{PORT}}", base::NumberToString(https_server.port())));
replacement_path = net::test_server::GetFilePathWithReplacements(
"/mirror_request_header/https.www.google.com.html", replacement_text);
all_tests.push_back({https_server.GetURL("www.google.com", replacement_path),
"/simple.html", false, true, false});
for (const auto& test_case : all_tests) {
SCOPED_TRACE(test_case.original_url);
// If test case requires adding header for the first url add a throttle.
ThrottleContentBrowserClient browser_client(test_case.original_url);
content::ContentBrowserClient* old_browser_client = nullptr;
if (test_case.inject_header)
old_browser_client = content::SetBrowserClientForTesting(&browser_client);
// Navigate to first url.
ui_test_utils::NavigateToURL(browser(), test_case.original_url);
if (test_case.inject_header)
content::SetBrowserClientForTesting(old_browser_client);
base::AutoLock auto_lock(lock);
// Check if header exists and X-Chrome-Connected is correctly provided.
ASSERT_EQ(1u, header_map.count(test_case.original_url.path()));
if (test_case.original_url_expects_header) {
ASSERT_TRUE(!!header_map[test_case.original_url.path()].count(
signin::kChromeConnectedHeader));
} else {
ASSERT_FALSE(!!header_map[test_case.original_url.path()].count(
signin::kChromeConnectedHeader));
}
ASSERT_EQ(1u, header_map.count(test_case.redirected_to_path));
if (test_case.redirected_to_url_expects_header) {
ASSERT_TRUE(!!header_map[test_case.redirected_to_path].count(
signin::kChromeConnectedHeader));
} else {
ASSERT_FALSE(!!header_map[test_case.redirected_to_path].count(
signin::kChromeConnectedHeader));
}
header_map.clear();
}
}
#endif // !BUILDFLAG(ENABLE_DICE_SUPPORT)
// Check that exactly one set of throttles is added to smaller downloads, which
// have their mime type determined only after the response is completely
// received.
// See https://crbug.com/640545
IN_PROC_BROWSER_TEST_F(ChromeResourceDispatcherHostDelegateBrowserTest,
ThrottlesAddedExactlyOnceToTinySniffedDownloads) {
// This code path isn't used when the network service is enabled.
if (base::FeatureList::IsEnabled(network::features::kNetworkService))
return;
GURL url = embedded_test_server()->GetURL("/downloads/tiny_binary.bin");
DownloadTestObserverNotInProgress download_observer(
content::BrowserContext::GetDownloadManager(browser()->profile()), 1);
download_observer.StartObserving();
ui_test_utils::NavigateToURL(browser(), url);
download_observer.WaitForFinished();
EXPECT_EQ(1, GetTimesStandardThrottlesAddedForURL(url));
}
// Check that exactly one set of throttles is added to larger downloads, which
// have their mime type determined before the end of the response is reported.
IN_PROC_BROWSER_TEST_F(ChromeResourceDispatcherHostDelegateBrowserTest,
ThrottlesAddedExactlyOnceToLargeSniffedDownloads) {
// This code path isn't used when the network service is enabled.
if (base::FeatureList::IsEnabled(network::features::kNetworkService))
return;
GURL url = embedded_test_server()->GetURL("/downloads/thisdayinhistory.xls");
DownloadTestObserverNotInProgress download_observer(
content::BrowserContext::GetDownloadManager(browser()->profile()), 1);
download_observer.StartObserving();
ui_test_utils::NavigateToURL(browser(), url);
download_observer.WaitForFinished();
EXPECT_EQ(1, GetTimesStandardThrottlesAddedForURL(url));
}
// Check that exactly one set of throttles is added to downloads started by an
// <a download> click.
IN_PROC_BROWSER_TEST_F(ChromeResourceDispatcherHostDelegateBrowserTest,
ThrottlesAddedExactlyOnceToADownloads) {
// This code path isn't used when the network service is enabled.
if (base::FeatureList::IsEnabled(network::features::kNetworkService))
return;
DownloadTestObserverNotInProgress download_observer(
content::BrowserContext::GetDownloadManager(browser()->profile()), 1);
download_observer.StartObserving();
ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL(
"/download-anchor-attrib.html"));
download_observer.WaitForFinished();
EXPECT_EQ(1,
GetTimesStandardThrottlesAddedForURL(
embedded_test_server()->GetURL("/anchor_download_test.png")));
}