blob: d1b4ffcbfa8977707cb3b420c0cab27220b07daf [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <optional>
#include <string>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/bad_message.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/web_contents/web_contents_impl.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/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "ipc/ipc_security_test_util.h"
#include "net/base/features.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_access_result.h"
#include "net/cookies/cookie_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/alternative_service.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/mojom/restricted_cookie_manager.mojom-test-utils.h"
#include "services/network/public/mojom/restricted_cookie_manager.mojom.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace content {
namespace {
void SetCookieFromJS(RenderFrameHost* frame, std::string cookie) {
EvalJsResult result = EvalJs(frame, "document.cookie = '" + cookie + "'");
EXPECT_TRUE(result.error.empty()) << result.error;
}
std::string GetCookieFromJS(RenderFrameHost* frame) {
return EvalJs(frame, "document.cookie;").ExtractString();
}
void SetCookieDirect(WebContentsImpl* tab,
const GURL& url,
const std::string& cookie_line) {
net::CookieOptions options;
// Allow setting SameSite cookies.
options.set_same_site_cookie_context(
net::CookieOptions::SameSiteCookieContext::MakeInclusive());
auto cookie_obj = net::CanonicalCookie::CreateForTesting(url, cookie_line,
base::Time::Now());
base::RunLoop run_loop;
tab->GetBrowserContext()
->GetDefaultStoragePartition()
->GetCookieManagerForBrowserProcess()
->SetCanonicalCookie(
*cookie_obj, url, options,
base::BindLambdaForTesting(
[&](net::CookieAccessResult status) { run_loop.Quit(); }));
run_loop.Run();
}
std::string GetCookiesDirect(WebContentsImpl* tab, const GURL& url) {
net::CookieOptions options;
// Allow setting SameSite cookies.
options.set_same_site_cookie_context(
net::CookieOptions::SameSiteCookieContext::MakeInclusive());
net::CookieList result;
base::RunLoop run_loop;
tab->GetBrowserContext()
->GetDefaultStoragePartition()
->GetCookieManagerForBrowserProcess()
->GetCookieList(
url, options, net::CookiePartitionKeyCollection(),
base::BindLambdaForTesting(
[&](const net::CookieAccessResultList& cookie_list,
const net::CookieAccessResultList& excluded_cookies) {
result = net::cookie_util::StripAccessResults(cookie_list);
run_loop.Quit();
}));
run_loop.Run();
return net::CanonicalCookie::BuildCookieLine(result);
}
} // namespace
class CookieBrowserTest : public ContentBrowserTest {
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(
switches::kEnableExperimentalWebPlatformFeatures);
}
void SetUpOnMainThread() override {
// Support multiple sites on the test server.
host_resolver()->AddRule("*", "127.0.0.1");
}
};
// Exercises basic cookie operations via javascript, including an http page
// interacting with secure cookies.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, Cookies) {
SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.AddDefaultHandlers(GetTestDataFilePath());
https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
ASSERT_TRUE(https_server.Start());
// The server sends a HttpOnly cookie. The RestrictedCookieManager should
// never allow this to be sent to any renderer process.
GURL https_url =
https_server.GetURL("a.test", "/set-cookie?notforjs=1;HttpOnly");
GURL http_url =
embedded_test_server()->GetURL("a.test", "/frame_with_load_event.html");
Shell* shell2 = CreateBrowser();
EXPECT_TRUE(NavigateToURL(shell(), http_url));
EXPECT_TRUE(NavigateToURL(shell2, https_url));
WebContentsImpl* web_contents_https =
static_cast<WebContentsImpl*>(shell2->web_contents());
WebContentsImpl* web_contents_http =
static_cast<WebContentsImpl*>(shell()->web_contents());
if (AreDefaultSiteInstancesEnabled()) {
// Note: Both use the default SiteInstance because the URLs don't require
// a dedicated process, but these default SiteInstances are not the same
// object because they come from different BrowsingInstances.
EXPECT_TRUE(web_contents_http->GetSiteInstance()->IsDefaultSiteInstance());
EXPECT_TRUE(web_contents_https->GetSiteInstance()->IsDefaultSiteInstance());
EXPECT_NE(web_contents_http->GetSiteInstance(),
web_contents_https->GetSiteInstance());
EXPECT_FALSE(web_contents_http->GetSiteInstance()->IsRelatedSiteInstance(
web_contents_https->GetSiteInstance()));
} else {
EXPECT_EQ("http://a.test/",
web_contents_http->GetSiteInstance()->GetSiteURL().spec());
EXPECT_EQ("https://a.test/",
web_contents_https->GetSiteInstance()->GetSiteURL().spec());
}
EXPECT_NE(web_contents_http->GetSiteInstance()->GetProcess(),
web_contents_https->GetSiteInstance()->GetProcess());
EXPECT_EQ("", GetCookieFromJS(web_contents_https->GetPrimaryMainFrame()));
EXPECT_EQ("", GetCookieFromJS(web_contents_http->GetPrimaryMainFrame()));
// Non-TLS page writes secure cookie.
EXPECT_TRUE(ExecJs(web_contents_http->GetPrimaryMainFrame(),
"document.cookie = 'A=1; secure;';"));
EXPECT_EQ("", GetCookieFromJS(web_contents_https->GetPrimaryMainFrame()));
EXPECT_EQ("", GetCookieFromJS(web_contents_http->GetPrimaryMainFrame()));
// Non-TLS page writes not-secure cookie.
EXPECT_TRUE(ExecJs(web_contents_http->GetPrimaryMainFrame(),
"document.cookie = 'B=2';"));
EXPECT_EQ("B=2", GetCookieFromJS(web_contents_https->GetPrimaryMainFrame()));
EXPECT_EQ("B=2", GetCookieFromJS(web_contents_http->GetPrimaryMainFrame()));
// TLS page writes secure cookie.
EXPECT_TRUE(ExecJs(web_contents_https->GetPrimaryMainFrame(),
"document.cookie = 'C=3;secure;';"));
EXPECT_EQ("B=2; C=3",
GetCookieFromJS(web_contents_https->GetPrimaryMainFrame()));
EXPECT_EQ("B=2", GetCookieFromJS(web_contents_http->GetPrimaryMainFrame()));
// TLS page writes not-secure cookie.
EXPECT_TRUE(ExecJs(web_contents_https->GetPrimaryMainFrame(),
"document.cookie = 'D=4';"));
EXPECT_EQ("B=2; C=3; D=4",
GetCookieFromJS(web_contents_https->GetPrimaryMainFrame()));
EXPECT_EQ("B=2; D=4",
GetCookieFromJS(web_contents_http->GetPrimaryMainFrame()));
}
// Ensure "priority" cookie option is settable via document.cookie.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, CookiePriority) {
ASSERT_TRUE(embedded_test_server()->Start());
struct {
std::string param;
net::CookiePriority priority;
} cases[] = {{"name=value", net::COOKIE_PRIORITY_DEFAULT},
{"name=value;priority=Low", net::COOKIE_PRIORITY_LOW},
{"name=value;priority=Medium", net::COOKIE_PRIORITY_MEDIUM},
{"name=value;priority=High", net::COOKIE_PRIORITY_HIGH}};
for (auto test_case : cases) {
GURL url = embedded_test_server()->GetURL("/set_document_cookie.html?" +
test_case.param);
EXPECT_TRUE(NavigateToURL(shell(), url));
std::vector<net::CanonicalCookie> cookies =
GetCanonicalCookies(shell()->web_contents()->GetBrowserContext(), url);
EXPECT_EQ(1u, cookies.size());
EXPECT_EQ("name", cookies[0].Name());
EXPECT_EQ("value", cookies[0].Value());
EXPECT_EQ(test_case.priority, cookies[0].Priority());
}
}
// SameSite cookies (that aren't marked as http-only) should be available to
// JavaScript.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, SameSiteCookies) {
// Must use HTTPS because SameSite=None cookies must be Secure.
net::EmbeddedTestServer server(net::EmbeddedTestServer::TYPE_HTTPS);
server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
server.AddDefaultHandlers(GetTestDataFilePath());
SetupCrossSiteRedirector(&server);
ASSERT_TRUE(server.Start());
// The server sets eight cookies on 'a.test' and on 'b.test', then loads
// a page that frames both 'a.test' and 'b.test' under 'a.test'.
std::string cookies_to_set =
"/set-cookie?none=1;SameSite=None;Secure" // SameSite=None must be
// Secure.
"&none-insecure=1;SameSite=None"
"&strict=1;SameSite=Strict"
"&unspecified=1" // unspecified SameSite should be treated as Lax.
"&lax=1;SameSite=Lax"
"&none-http=1;SameSite=None;Secure;httponly"
"&strict-http=1;SameSite=Strict;httponly"
"&unspecified-http=1;httponly"
"&lax-http=1;SameSite=Lax;httponly";
GURL url = server.GetURL("a.test", cookies_to_set);
EXPECT_TRUE(NavigateToURL(shell(), url));
url = server.GetURL("b.test", cookies_to_set);
EXPECT_TRUE(NavigateToURL(shell(), url));
url = server.GetURL(
"a.test", "/cross_site_iframe_factory.html?a.test(a.test(),b.test())");
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
RenderFrameHost* a_iframe = web_contents->GetPrimaryFrameTree()
.root()
->child_at(0)
->current_frame_host();
RenderFrameHost* b_iframe = web_contents->GetPrimaryFrameTree()
.root()
->child_at(1)
->current_frame_host();
// The top-level frame should get all same-site cookies.
EXPECT_EQ("none=1; strict=1; unspecified=1; lax=1",
GetCookieFromJS(main_frame));
// Same-site cookies will be delievered to the 'a.com' frame, as it is same-
// site with its ancestors.
EXPECT_EQ("none=1; strict=1; unspecified=1; lax=1",
GetCookieFromJS(a_iframe));
// Same-site cookies should not be delievered to the 'b.com' frame, as it
// isn't same-site with its ancestors. The SameSite=None but insecure cookie
// is rejected.
EXPECT_EQ("none=1", GetCookieFromJS(b_iframe));
}
class TruncatedCookieBrowserTestP : public CookieBrowserTest,
public testing::WithParamInterface<bool> {
public:
TruncatedCookieBrowserTestP() {
truncated_cookies_blocked_ = GetParam();
if (TruncatedCookiesBlocked()) {
feature_list_.InitAndEnableFeature(net::features::kBlockTruncatedCookies);
} else {
feature_list_.InitAndDisableFeature(
net::features::kBlockTruncatedCookies);
}
}
bool TruncatedCookiesBlocked() { return truncated_cookies_blocked_; }
private:
base::test::ScopedFeatureList feature_list_;
bool truncated_cookies_blocked_;
};
INSTANTIATE_TEST_SUITE_P(TruncatedCookieBrowserTests,
TruncatedCookieBrowserTestP,
testing::Values(true, false));
IN_PROC_BROWSER_TEST_P(TruncatedCookieBrowserTestP,
CookieTruncatingCharFromJavascript) {
using std::string_literals::operator""s;
base::HistogramTester histogram;
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html")));
WebContentsImpl* tab = static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHost* frame = tab->GetPrimaryMainFrame();
// Test scenarios where a control char may appear at start, middle and end of
// a cookie line. Control char array with NULL (\x0), CR (\xD), and LF (xA).
const std::string kTestChars[] = {"\\x00", "\\x0D", "\\x0A"};
for (const std::string& ctl_string : kTestChars) {
// Control char at the start of the string.
// Note that when truncation of this cookie string occurs, no histogram
// entries get recorded because the code bails out early on the resulting
// empty cookie string.
std::string cookie_string = ctl_string + "foo1=bar"s;
SetCookieFromJS(frame, cookie_string);
// Control char in the middle of the string.
cookie_string = "foo2=bar;"s + ctl_string + "httponly"s;
SetCookieFromJS(frame, cookie_string);
cookie_string = "foo3=ba"s + ctl_string + "r; httponly"s;
SetCookieFromJS(frame, cookie_string);
// Control char at the end of the string.
cookie_string = "foo4=bar;" + ctl_string;
SetCookieFromJS(frame, cookie_string);
}
int expected_histogram_hit_count;
if (TruncatedCookiesBlocked()) {
EXPECT_EQ("", GetCookieFromJS(frame));
expected_histogram_hit_count = 0;
} else {
// Note: the last three test cases above are detectable as truncations
// (since the first case results in a failure that occurs before the
// histogram is recorded), so check for that below.
EXPECT_EQ("foo2=bar; foo3=ba; foo4=bar", GetCookieFromJS(frame));
expected_histogram_hit_count = 3;
}
FetchHistogramsFromChildProcesses();
histogram.ExpectBucketCount(
"Cookie.TruncatingCharacterInCookieString",
net::TruncatingCharacterInCookieStringType::kTruncatingCharNull,
expected_histogram_hit_count);
histogram.ExpectBucketCount(
"Cookie.TruncatingCharacterInCookieString",
net::TruncatingCharacterInCookieStringType::kTruncatingCharNewline,
expected_histogram_hit_count);
histogram.ExpectBucketCount(
"Cookie.TruncatingCharacterInCookieString",
net::TruncatingCharacterInCookieStringType::kTruncatingCharLineFeed,
expected_histogram_hit_count);
}
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, CookieTruncatingCharFromHeaders) {
using std::string_literals::operator""s;
std::string cookie_string;
embedded_test_server()->RegisterRequestHandler(base::BindLambdaForTesting(
[&](const net::test_server::HttpRequest& request)
-> std::unique_ptr<net::test_server::HttpResponse> {
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->AddCustomHeader("Set-Cookie", cookie_string);
return std::move(response);
}));
ASSERT_TRUE(embedded_test_server()->Start());
GURL http_url = embedded_test_server()->GetURL("/");
base::HistogramTester histogram;
// Test scenarios where a control char may appear at start, middle and end of
// a cookie line. Control char array with NULL (\x0), CR (\xD), and LF (xA)
char kTestChars[] = {'\x0', '\xD', '\xA'};
for (const auto& test : kTestChars) {
std::string ctl_string(1, test);
// ctrl char at start of string
cookie_string = ctl_string + "foo=bar"s;
EXPECT_TRUE(NavigateToURL(shell(), http_url));
// ctrl char at middle of string
cookie_string = "foo=bar;"s + ctl_string + "httponly"s;
EXPECT_TRUE(NavigateToURL(shell(), http_url));
cookie_string = "foo=ba"s + ctl_string + "r; httponly"s;
EXPECT_TRUE(NavigateToURL(shell(), http_url));
// ctrl char at end of string
cookie_string = "foo=bar;"s + "httponly;"s + ctl_string;
EXPECT_TRUE(NavigateToURL(shell(), http_url));
}
// Test if there are multiple control characters that terminate.
cookie_string = "foo=bar;\xA\xDhttponly"s;
EXPECT_TRUE(NavigateToURL(shell(), http_url));
FetchHistogramsFromChildProcesses();
histogram.ExpectBucketCount(
"Cookie.TruncatingCharacterInCookieString",
net::TruncatingCharacterInCookieStringType::kTruncatingCharNull, 0);
histogram.ExpectBucketCount(
"Cookie.TruncatingCharacterInCookieString",
net::TruncatingCharacterInCookieStringType::kTruncatingCharNewline, 0);
histogram.ExpectBucketCount(
"Cookie.TruncatingCharacterInCookieString",
net::TruncatingCharacterInCookieStringType::kTruncatingCharLineFeed, 0);
}
class RestrictedCookieManagerInterceptor
: public network::mojom::RestrictedCookieManagerInterceptorForTesting {
public:
RestrictedCookieManagerInterceptor(
mojo::PendingReceiver<network::mojom::RestrictedCookieManager> receiver,
mojo::PendingRemote<network::mojom::RestrictedCookieManager> real_rcm)
: receiver_(this, std::move(receiver)), real_rcm_(std::move(real_rcm)) {}
void set_override_url(std::optional<std::string> maybe_url) {
override_url_ = std::move(maybe_url);
}
void SetCookieFromString(const GURL& url,
const net::SiteForCookies& site_for_cookies,
const url::Origin& top_frame_origin,
bool has_storage_access,
const std::string& cookie,
SetCookieFromStringCallback callback) override {
GetForwardingInterface()->SetCookieFromString(
URLToUse(url), site_for_cookies, top_frame_origin, has_storage_access,
std::move(cookie), std::move(callback));
}
void GetCookiesString(const GURL& url,
const net::SiteForCookies& site_for_cookies,
const url::Origin& top_frame_origin,
bool has_storage_access,
bool get_version_shared_memory,
bool is_ad_tagged,
bool force_disable_third_party_cookies,
GetCookiesStringCallback callback) override {
GetForwardingInterface()->GetCookiesString(
URLToUse(url), site_for_cookies, top_frame_origin, has_storage_access,
get_version_shared_memory, is_ad_tagged,
force_disable_third_party_cookies, std::move(callback));
}
private:
network::mojom::RestrictedCookieManager* GetForwardingInterface() override {
return real_rcm_.get();
}
GURL URLToUse(const GURL& url_in) {
return override_url_ ? GURL(override_url_.value()) : url_in;
}
std::optional<std::string> override_url_;
mojo::Receiver<network::mojom::RestrictedCookieManager> receiver_;
mojo::Remote<network::mojom::RestrictedCookieManager> real_rcm_;
};
class CookieStoreContentBrowserClient
: public ContentBrowserTestContentBrowserClient {
public:
~CookieStoreContentBrowserClient() override = default;
bool WillCreateRestrictedCookieManager(
network::mojom::RestrictedCookieManagerRole role,
content::BrowserContext* browser_context,
const url::Origin& origin,
const net::IsolationInfo& isolation_info,
bool is_service_worker,
int process_id,
int routing_id,
mojo::PendingReceiver<network::mojom::RestrictedCookieManager>* receiver)
override {
mojo::PendingReceiver<network::mojom::RestrictedCookieManager>
orig_receiver = std::move(*receiver);
mojo::PendingRemote<network::mojom::RestrictedCookieManager> real_rcm;
*receiver = real_rcm.InitWithNewPipeAndPassReceiver();
rcm_interceptor_ = std::make_unique<RestrictedCookieManagerInterceptor>(
std::move(orig_receiver), std::move(real_rcm));
rcm_interceptor_->set_override_url(override_url_);
return false; // only made a proxy, still need the actual impl to be made.
}
void set_override_url(std::optional<std::string> maybe_url) {
override_url_ = maybe_url;
if (rcm_interceptor_)
rcm_interceptor_->set_override_url(override_url_);
}
private:
std::optional<std::string> override_url_;
std::unique_ptr<RestrictedCookieManagerInterceptor> rcm_interceptor_;
};
// Cookie access in loader is locked to a particular origin, so messages
// for wrong URLs are rejected.
// TODO(crbug.com/41453892): This should actually result in renderer
// kills.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, CrossSiteCookieSecurityEnforcement) {
// The code under test is only active under site isolation.
if (!AreAllSitesIsolatedForTesting()) {
return;
}
SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/frame_with_load_event.html")));
WebContentsImpl* tab = static_cast<WebContentsImpl*>(shell()->web_contents());
// The iframe on the http page should get its own process.
FrameTreeVisualizer v;
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://127.0.0.1/\n"
" B = http://baz.com/",
v.DepictFrameTree(tab->GetPrimaryFrameTree().root()));
RenderFrameHost* main_frame = tab->GetPrimaryMainFrame();
RenderFrameHost* iframe =
tab->GetPrimaryFrameTree().root()->child_at(0)->current_frame_host();
EXPECT_NE(iframe->GetProcess(), main_frame->GetProcess());
SetCookieDirect(tab, GURL("http://127.0.0.1/"), "A_cookie = parent");
SetCookieDirect(tab, GURL("http://baz.com/"), "B_cookie = child");
EXPECT_EQ("A_cookie=parent",
GetCookiesDirect(tab, GURL("http://127.0.0.1/")));
EXPECT_EQ("B_cookie=child", GetCookiesDirect(tab, GURL("http://baz.com/")));
// Try to get cross-site cookies from the subframe's process.
{
CookieStoreContentBrowserClient browser_client;
browser_client.set_override_url("http://127.0.0.1/");
EXPECT_EQ("", GetCookieFromJS(iframe));
}
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://127.0.0.1/\n"
" B = http://baz.com/",
v.DepictFrameTree(tab->GetPrimaryFrameTree().root()));
// Now set a cross-site cookie from the main frame's process.
{
CookieStoreContentBrowserClient browser_client;
browser_client.set_override_url("https://baz.com/");
SetCookieFromJS(iframe, "pwn=ed");
EXPECT_EQ("B_cookie=child", GetCookiesDirect(tab, GURL("http://baz.com/")));
}
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://127.0.0.1/\n"
" B = http://baz.com/",
v.DepictFrameTree(tab->GetPrimaryFrameTree().root()));
}
// Cookies for an eTLD should be stored (via JS) if they match the URL host,
// even if they begin with `.` or have non-canonical capitalization.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, ETldDomainCookies) {
ASSERT_TRUE(embedded_test_server()->Start());
// This test uses `gov.br` as an example of an eTLD.
GURL http_url = embedded_test_server()->GetURL("gov.br", "/empty.html");
EXPECT_TRUE(NavigateToURL(shell(), http_url));
WebContentsImpl* web_contents_http =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHost* frame = web_contents_http->GetPrimaryMainFrame();
const char* kCases[] = {
// A host cookie.
"c=1",
// A cookie for this domain.
"c=1; domain=gov.br",
// Same, but with a preceding dot. This dot should be ignored.
"c=1; domain=.gov.br",
// Same, but with non-canonical case. This should be canonicalized.
"c=1; domain=gOv.bR",
};
for (const char* set_cookie : kCases) {
SCOPED_TRACE(set_cookie);
SetCookieFromJS(frame, set_cookie);
EXPECT_EQ("c=1", GetCookieFromJS(frame));
SetCookieFromJS(frame, "c=;expires=Thu, 01 Jan 1970 00:00:00 GMT");
EXPECT_EQ("", GetCookieFromJS(frame));
}
}
// Cookies for an eTLD should be stored (via header) if they match the URL host,
// even if they begin with `.` or have non-canonical capitalization.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, ETldDomainCookiesHeader) {
std::string got_cookie_on_request;
std::string set_cookie_on_response;
embedded_test_server()->RegisterRequestHandler(base::BindLambdaForTesting(
[&](const net::test_server::HttpRequest& request)
-> std::unique_ptr<net::test_server::HttpResponse> {
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
if (request.headers.contains("Cookie")) {
got_cookie_on_request = request.headers.at("Cookie");
} else {
got_cookie_on_request = "";
}
if (set_cookie_on_response.size() != 0) {
response->AddCustomHeader("Set-Cookie", set_cookie_on_response);
set_cookie_on_response = "";
}
return std::move(response);
}));
ASSERT_TRUE(embedded_test_server()->Start());
// This test uses `gov.br` as an example of an eTLD.
GURL http_url = embedded_test_server()->GetURL("gov.br", "/empty.html");
const char* kCases[] = {
// A host cookie.
"c=1",
// A cookie for this domain.
"c=1; domain=gov.br",
// Same, but with a preceding dot. This dot should be ignored.
"c=1; domain=.gov.br",
// Same, but with non-canonical case. This should be canonicalized.
"c=1; domain=gOv.bR",
};
for (const char* set_cookie : kCases) {
SCOPED_TRACE(set_cookie);
set_cookie_on_response = set_cookie;
EXPECT_TRUE(NavigateToURL(shell(), http_url));
EXPECT_TRUE(NavigateToURL(shell(), http_url));
EXPECT_EQ("c=1", got_cookie_on_request);
set_cookie_on_response = "c=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
EXPECT_TRUE(NavigateToURL(shell(), http_url));
EXPECT_TRUE(NavigateToURL(shell(), http_url));
EXPECT_EQ("", got_cookie_on_request);
}
}
} // namespace content