blob: 08ed7e73e2a04ec3f8ce673d7364893f714e004d [file] [log] [blame]
// 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 "components/variations/net/variations_http_headers.h"
#include <map>
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/variations/net/variations_http_headers.h"
#include "components/variations/variations_http_header_provider.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/simple_url_loader_test_helper.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 "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace {
class VariationsHttpHeadersBrowserTest : public InProcessBrowserTest {
public:
VariationsHttpHeadersBrowserTest()
: https_server_(net::test_server::EmbeddedTestServer::TYPE_HTTPS) {}
~VariationsHttpHeadersBrowserTest() override = default;
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
server()->RegisterRequestHandler(
base::BindRepeating(&VariationsHttpHeadersBrowserTest::RequestHandler,
base::Unretained(this)));
ASSERT_TRUE(server()->Start());
// Set up some fake variations.
auto* variations_provider =
variations::VariationsHttpHeaderProvider::GetInstance();
variations_provider->ForceVariationIds({"12", "456", "t789"}, "");
}
void SetUpCommandLine(base::CommandLine* command_line) override {
InProcessBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
}
net::EmbeddedTestServer* server() { return &https_server_; }
GURL GetGoogleRedirectUrl1() const {
return GURL(base::StringPrintf("https://www.google.com:%d/redirect",
https_server_.port()));
}
GURL GetGoogleRedirectUrl2() const {
return GURL(base::StringPrintf("https://www.google.com:%d/redirect2",
https_server_.port()));
}
GURL GetExampleUrl() const {
return GURL(base::StringPrintf("https://www.example.com:%d/landing.html",
https_server_.port()));
}
// Returns whether a given |header| has been received for a |url|. Note that
// false is returned if the |url| has not been observed.
bool HasReceivedHeader(const GURL& url, const std::string& header) const {
auto it = received_headers_.find(url);
if (it == received_headers_.end())
return false;
return it->second.find(header) != it->second.end();
}
bool FetchResource(const GURL& url) {
if (!url.is_valid())
return false;
std::string script(
"var xhr = new XMLHttpRequest();"
"xhr.open('GET', '");
script += url.spec() +
"', true);"
"xhr.onload = function (e) {"
" if (xhr.readyState === 4) {"
" window.domAutomationController.send(xhr.status === 200);"
" }"
"};"
"xhr.onerror = function () {"
" window.domAutomationController.send(false);"
"};"
"xhr.send(null)";
return ExecuteScript(script);
}
private:
content::WebContents* GetWebContents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
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(GetWebContents(), script, &xhr_result);
return xhr_result && execute_result;
}
// Custom request handler that record request headers and simulates a redirect
// from google.com to example.com.
std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
const net::test_server::HttpRequest& request);
net::EmbeddedTestServer https_server_;
// Stores the observed HTTP Request headers.
std::map<GURL, net::test_server::HttpRequest::HeaderMap> received_headers_;
DISALLOW_COPY_AND_ASSIGN(VariationsHttpHeadersBrowserTest);
};
class BlockingURLFetcherDelegate : public net::URLFetcherDelegate {
public:
BlockingURLFetcherDelegate() = default;
~BlockingURLFetcherDelegate() override = default;
void OnURLFetchComplete(const net::URLFetcher* source) override {
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
run_loop_.QuitClosure());
}
void AwaitResponse() { run_loop_.Run(); }
private:
base::RunLoop run_loop_;
DISALLOW_COPY_AND_ASSIGN(BlockingURLFetcherDelegate);
};
std::unique_ptr<net::test_server::HttpResponse>
VariationsHttpHeadersBrowserTest::RequestHandler(
const net::test_server::HttpRequest& request) {
// Retrieve the host name (without port) from the request headers.
std::string host = "";
if (request.headers.find("Host") != request.headers.end())
host = request.headers.find("Host")->second;
if (host.find(':') != std::string::npos)
host = host.substr(0, host.find(':'));
// Recover the original URL of the request by replacing the host name in
// request.GetURL() (which is 127.0.0.1) with the host name from the request
// headers.
url::Replacements<char> replacements;
replacements.SetHost(host.c_str(), url::Component(0, host.length()));
GURL original_url = request.GetURL().ReplaceComponents(replacements);
// Memorize the request headers for this URL for later verification.
received_headers_[original_url] = request.headers;
// Set up a test server that redirects according to the
// following redirect chain:
// https://www.google.com:<port>/redirect
// --> https://www.google.com:<port>/redirect2
// --> https://www.example.com:<port>/
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->AddCustomHeader("Access-Control-Allow-Origin", "*");
if (request.relative_url == GetGoogleRedirectUrl1().path()) {
http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
http_response->AddCustomHeader("Location", GetGoogleRedirectUrl2().spec());
} else if (request.relative_url == GetGoogleRedirectUrl2().path()) {
http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
http_response->AddCustomHeader("Location", GetExampleUrl().spec());
} else if (request.relative_url == GetExampleUrl().path()) {
http_response->set_code(net::HTTP_OK);
http_response->set_content("hello");
http_response->set_content_type("text/plain");
} else {
http_response->set_code(net::HTTP_NO_CONTENT);
}
return http_response;
}
} // namespace
// Verify in an integration test that the variations header (X-Client-Data) is
// attached to network requests to Google but stripped on redirects.
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
TestStrippingHeadersFromResourceRequest) {
ui_test_utils::NavigateToURL(browser(), GetGoogleRedirectUrl1());
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl2(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetExampleUrl(), "Host"));
EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
}
// Verify in an integration that that the variations header (X-Client-Data) is
// correctly attached and stripped from network requests that are triggered via
// a URLFetcher.
//
// TODO(juncai): Remove this test when there are no more clients left that use
// URLFetcher.
// https://crbug.com/773295
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
TestStrippingHeadersFromInternalRequest) {
BlockingURLFetcherDelegate delegate;
GURL url = GetGoogleRedirectUrl1();
std::unique_ptr<net::URLFetcher> fetcher =
net::URLFetcher::Create(url, net::URLFetcher::GET, &delegate);
net::HttpRequestHeaders headers;
variations::AppendVariationHeaders(url, variations::InIncognito::kNo,
variations::SignedIn::kNo, &headers);
fetcher->SetRequestContext(browser()->profile()->GetRequestContext());
fetcher->SetExtraRequestHeaders(headers.ToString());
fetcher->Start();
delegate.AwaitResponse();
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl2(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetExampleUrl(), "Host"));
EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
}
// Verify in an integration that that the variations header (X-Client-Data) is
// correctly attached and stripped from network requests that are triggered via
// the network service.
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
TestStrippingHeadersFromNetworkService) {
content::StoragePartition* partition =
content::BrowserContext::GetDefaultStoragePartition(browser()->profile());
network::mojom::NetworkContext* network_context =
partition->GetNetworkContext();
EXPECT_EQ(net::OK, content::LoadBasicRequest(network_context,
GetGoogleRedirectUrl1()));
// TODO(crbug.com/794644) Once the network service stack starts injecting
// X-Client-Data headers, the following expectations should be used.
EXPECT_FALSE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
/*
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl2(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetExampleUrl(), "Host"));
EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
*/
}
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
TestStrippingHeadersFromSubresourceRequest) {
GURL url = server()->GetURL("/simple_page.html");
ui_test_utils::NavigateToURL(browser(), url);
EXPECT_TRUE(FetchResource(GetGoogleRedirectUrl1()));
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl2(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetExampleUrl(), "Host"));
EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
}
IN_PROC_BROWSER_TEST_F(
VariationsHttpHeadersBrowserTest,
TestStrippingHeadersFromRequestUsingSimpleURLLoaderWithProfileNetworkContext) {
GURL url = GetGoogleRedirectUrl1();
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = url;
variations::AppendVariationHeaders(url, variations::InIncognito::kNo,
variations::SignedIn::kNo,
&resource_request->headers);
std::unique_ptr<network::SimpleURLLoader> loader =
network::SimpleURLLoader::Create(std::move(resource_request),
TRAFFIC_ANNOTATION_FOR_TESTS);
content::StoragePartition* partition =
content::BrowserContext::GetDefaultStoragePartition(browser()->profile());
network::SharedURLLoaderFactory* loader_factory =
partition->GetURLLoaderFactoryForBrowserProcess().get();
content::SimpleURLLoaderTestHelper loader_helper;
loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
loader_factory, loader_helper.GetCallback());
// Wait for the response to complete.
loader_helper.WaitForCallback();
EXPECT_TRUE(loader_helper.response_body());
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl2(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetExampleUrl(), "Host"));
EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
}
IN_PROC_BROWSER_TEST_F(
VariationsHttpHeadersBrowserTest,
TestStrippingHeadersFromRequestUsingSimpleURLLoaderWithGlobalSystemNetworkContext) {
GURL url = GetGoogleRedirectUrl1();
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = url;
variations::AppendVariationHeaders(url, variations::InIncognito::kNo,
variations::SignedIn::kNo,
&resource_request->headers);
std::unique_ptr<network::SimpleURLLoader> loader =
network::SimpleURLLoader::Create(std::move(resource_request),
TRAFFIC_ANNOTATION_FOR_TESTS);
network::SharedURLLoaderFactory* loader_factory =
g_browser_process->system_network_context_manager()
->GetSharedURLLoaderFactory()
.get();
content::SimpleURLLoaderTestHelper loader_helper;
loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
loader_factory, loader_helper.GetCallback());
// Wait for the response to complete.
loader_helper.WaitForCallback();
EXPECT_TRUE(loader_helper.response_body());
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl2(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetExampleUrl(), "Host"));
EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
}