blob: bb29f5cad3127519669116377128ca40e41454e2 [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 "content/network/cookie_manager_impl.h"
#include <algorithm>
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/cookie_monster.h"
#include "net/cookies/cookie_store.h"
#include "net/cookies/cookie_store_test_callbacks.h"
#include "services/network/public/interfaces/cookie_manager.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
// Test infrastructure outline:
// # Classes
// * SynchronousMojoCookieWrapper: Takes a network::mojom::CookieManager at
// construction; exposes synchronous interfaces that wrap the
// network::mojom::CookieManager async interfaces to make testing easier.
// * CookieChangeNotificationImpl: Test class implementing
// the CookieChangeNotification interface and recording
// incoming messages on it.
// * CookieManagerImplTest: Test base class. Automatically sets up
// a cookie store, a cookie service wrapping it, a mojo pipe
// connected to the server, and the cookie service implemented
// by the other end of the pipe.
//
// # Functions
// * CompareCanonicalCookies: Comparison function to make it easy to
// sort cookie list responses from the network::mojom::CookieManager.
namespace content {
// Wraps a network::mojom::CookieManager in synchronous, blocking calls to make
// it easier to test.
class SynchronousCookieManager {
public:
// Caller must guarantee that |*cookie_service| outlives the
// SynchronousCookieManager.
explicit SynchronousCookieManager(
network::mojom::CookieManager* cookie_service)
: cookie_service_(cookie_service) {}
~SynchronousCookieManager() {}
std::vector<net::CanonicalCookie> GetAllCookies() {
base::RunLoop run_loop;
std::vector<net::CanonicalCookie> cookies;
cookie_service_->GetAllCookies(base::BindOnce(
&SynchronousCookieManager::GetCookiesCallback, &run_loop, &cookies));
run_loop.Run();
return cookies;
}
std::vector<net::CanonicalCookie> GetCookieList(const GURL& url,
net::CookieOptions options) {
base::RunLoop run_loop;
std::vector<net::CanonicalCookie> cookies;
cookie_service_->GetCookieList(
url, options,
base::BindOnce(&SynchronousCookieManager::GetCookiesCallback, &run_loop,
&cookies));
run_loop.Run();
return cookies;
}
bool SetCanonicalCookie(const net::CanonicalCookie& cookie,
bool secure_source,
bool modify_http_only) {
base::RunLoop run_loop;
bool result = false;
cookie_service_->SetCanonicalCookie(
cookie, secure_source, modify_http_only,
base::BindOnce(&SynchronousCookieManager::SetCookieCallback, &run_loop,
&result));
run_loop.Run();
return result;
}
uint32_t DeleteCookies(network::mojom::CookieDeletionFilter filter) {
base::RunLoop run_loop;
uint32_t num_deleted = 0u;
network::mojom::CookieDeletionFilterPtr filter_ptr =
network::mojom::CookieDeletionFilter::New(filter);
cookie_service_->DeleteCookies(
std::move(filter_ptr),
base::BindOnce(&SynchronousCookieManager::DeleteCookiesCallback,
&run_loop, &num_deleted));
run_loop.Run();
return num_deleted;
}
// No need to wrap RequestNotification and CloneInterface, since their use
// is pure async.
private:
static void GetCookiesCallback(
base::RunLoop* run_loop,
std::vector<net::CanonicalCookie>* cookies_out,
const std::vector<net::CanonicalCookie>& cookies) {
*cookies_out = cookies;
run_loop->Quit();
}
static void SetCookieCallback(base::RunLoop* run_loop,
bool* result_out,
bool result) {
*result_out = result;
run_loop->Quit();
}
static void DeleteCookiesCallback(base::RunLoop* run_loop,
uint32_t* num_deleted_out,
uint32_t num_deleted) {
*num_deleted_out = num_deleted;
run_loop->Quit();
}
static void RequestNotificationCallback(
base::RunLoop* run_loop,
network::mojom::CookieChangeNotificationRequest* request_out,
network::mojom::CookieChangeNotificationRequest request) {
*request_out = std::move(request);
run_loop->Quit();
}
network::mojom::CookieManager* cookie_service_;
DISALLOW_COPY_AND_ASSIGN(SynchronousCookieManager);
};
class CookieManagerImplTest : public testing::Test {
public:
CookieManagerImplTest()
: connection_error_seen_(false),
cookie_monster_(nullptr, nullptr),
cookie_service_(base::MakeUnique<CookieManagerImpl>(&cookie_monster_)) {
cookie_service_->AddRequest(mojo::MakeRequest(&cookie_service_ptr_));
service_wrapper_ =
base::MakeUnique<SynchronousCookieManager>(cookie_service_ptr_.get());
cookie_service_ptr_.set_connection_error_handler(base::BindOnce(
&CookieManagerImplTest::OnConnectionError, base::Unretained(this)));
}
~CookieManagerImplTest() override {}
void SetUp() override {
setup_time_ = base::Time::Now();
// Set a couple of cookies for tests to play with.
bool result;
result = SetCanonicalCookie(
net::CanonicalCookie(
"A", "B", "foo_host", "/", base::Time(), base::Time(), base::Time(),
/*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true);
DCHECK(result);
result = SetCanonicalCookie(
net::CanonicalCookie(
"C", "D", "foo_host2", "/with/path", base::Time(), base::Time(),
base::Time(), /*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true);
DCHECK(result);
result = SetCanonicalCookie(
net::CanonicalCookie(
"Secure", "E", "foo_host", "/with/path", base::Time(), base::Time(),
base::Time(), /*secure=*/true,
/*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_MEDIUM),
true, true);
DCHECK(result);
result = SetCanonicalCookie(
net::CanonicalCookie(
"HttpOnly", "F", "foo_host", "/with/path", base::Time(),
base::Time(), base::Time(), /*secure=*/false,
/*httponly=*/true, net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_MEDIUM),
true, true);
DCHECK(result);
}
// Tear down the remote service.
void NukeService() { cookie_service_.reset(); }
// Set a canonical cookie directly into the store.
bool SetCanonicalCookie(const net::CanonicalCookie& cookie,
bool secure_source,
bool can_modify_httponly) {
net::ResultSavingCookieCallback<bool> callback;
cookie_monster_.SetCanonicalCookieAsync(
base::MakeUnique<net::CanonicalCookie>(cookie), secure_source,
can_modify_httponly,
base::BindOnce(&net::ResultSavingCookieCallback<bool>::Run,
base::Unretained(&callback)));
callback.WaitUntilDone();
return callback.result();
}
net::CookieStore* cookie_store() { return &cookie_monster_; }
CookieManagerImpl* service_impl() const { return cookie_service_.get(); }
// Return the cookie service at the client end of the mojo pipe.
network::mojom::CookieManager* cookie_service_client() {
return cookie_service_ptr_.get();
}
// Synchronous wrapper
SynchronousCookieManager* service_wrapper() { return service_wrapper_.get(); }
base::Time setup_time() const { return setup_time_; }
bool connection_error_seen() const { return connection_error_seen_; }
private:
void OnConnectionError() { connection_error_seen_ = true; }
bool connection_error_seen_;
base::MessageLoopForIO message_loop_;
net::CookieMonster cookie_monster_;
std::unique_ptr<content::CookieManagerImpl> cookie_service_;
network::mojom::CookieManagerPtr cookie_service_ptr_;
std::unique_ptr<SynchronousCookieManager> service_wrapper_;
base::Time setup_time_;
DISALLOW_COPY_AND_ASSIGN(CookieManagerImplTest);
};
namespace {
bool CompareCanonicalCookies(const net::CanonicalCookie& c1,
const net::CanonicalCookie& c2) {
return c1.FullCompare(c2);
}
} // anonymous namespace
// Test the GetAllCookies accessor. Also tests that canonical
// cookies come out of the store unchanged.
TEST_F(CookieManagerImplTest, GetAllCookies) {
base::Time now(base::Time::Now());
std::vector<net::CanonicalCookie> cookies =
service_wrapper()->GetAllCookies();
ASSERT_EQ(4u, cookies.size());
std::sort(cookies.begin(), cookies.end(), &CompareCanonicalCookies);
EXPECT_EQ("A", cookies[0].Name());
EXPECT_EQ("B", cookies[0].Value());
EXPECT_EQ("foo_host", cookies[0].Domain());
EXPECT_EQ("/", cookies[0].Path());
EXPECT_LT(setup_time(), cookies[0].CreationDate());
EXPECT_LT(cookies[0].CreationDate(), now);
EXPECT_EQ(cookies[0].LastAccessDate(), base::Time());
EXPECT_EQ(cookies[0].ExpiryDate(), base::Time());
EXPECT_FALSE(cookies[0].IsPersistent());
EXPECT_FALSE(cookies[0].IsSecure());
EXPECT_FALSE(cookies[0].IsHttpOnly());
EXPECT_EQ(net::CookieSameSite::NO_RESTRICTION, cookies[0].SameSite());
EXPECT_EQ(net::COOKIE_PRIORITY_MEDIUM, cookies[0].Priority());
EXPECT_EQ("C", cookies[1].Name());
EXPECT_EQ("D", cookies[1].Value());
EXPECT_EQ("foo_host2", cookies[1].Domain());
EXPECT_EQ("/with/path", cookies[1].Path());
EXPECT_LT(setup_time(), cookies[1].CreationDate());
EXPECT_LT(cookies[1].CreationDate(), now);
EXPECT_EQ(cookies[1].LastAccessDate(), base::Time());
EXPECT_EQ(cookies[1].ExpiryDate(), base::Time());
EXPECT_FALSE(cookies[1].IsPersistent());
EXPECT_FALSE(cookies[1].IsSecure());
EXPECT_FALSE(cookies[1].IsHttpOnly());
EXPECT_EQ(net::CookieSameSite::NO_RESTRICTION, cookies[1].SameSite());
EXPECT_EQ(net::COOKIE_PRIORITY_MEDIUM, cookies[1].Priority());
EXPECT_EQ("HttpOnly", cookies[2].Name());
EXPECT_EQ("F", cookies[2].Value());
EXPECT_EQ("foo_host", cookies[2].Domain());
EXPECT_EQ("/with/path", cookies[2].Path());
EXPECT_LT(setup_time(), cookies[2].CreationDate());
EXPECT_LT(cookies[2].CreationDate(), now);
EXPECT_EQ(cookies[2].LastAccessDate(), base::Time());
EXPECT_EQ(cookies[2].ExpiryDate(), base::Time());
EXPECT_FALSE(cookies[2].IsPersistent());
EXPECT_FALSE(cookies[2].IsSecure());
EXPECT_TRUE(cookies[2].IsHttpOnly());
EXPECT_EQ(net::CookieSameSite::NO_RESTRICTION, cookies[2].SameSite());
EXPECT_EQ(net::COOKIE_PRIORITY_MEDIUM, cookies[2].Priority());
EXPECT_EQ("Secure", cookies[3].Name());
EXPECT_EQ("E", cookies[3].Value());
EXPECT_EQ("foo_host", cookies[3].Domain());
EXPECT_EQ("/with/path", cookies[3].Path());
EXPECT_LT(setup_time(), cookies[3].CreationDate());
EXPECT_LT(cookies[3].CreationDate(), now);
EXPECT_EQ(cookies[3].LastAccessDate(), base::Time());
EXPECT_EQ(cookies[3].ExpiryDate(), base::Time());
EXPECT_FALSE(cookies[3].IsPersistent());
EXPECT_TRUE(cookies[3].IsSecure());
EXPECT_FALSE(cookies[3].IsHttpOnly());
EXPECT_EQ(net::CookieSameSite::NO_RESTRICTION, cookies[3].SameSite());
EXPECT_EQ(net::COOKIE_PRIORITY_MEDIUM, cookies[3].Priority());
}
TEST_F(CookieManagerImplTest, GetCookieList) {
std::vector<net::CanonicalCookie> cookies = service_wrapper()->GetCookieList(
GURL("https://foo_host/with/path"), net::CookieOptions());
EXPECT_EQ(2u, cookies.size());
std::sort(cookies.begin(), cookies.end(), &CompareCanonicalCookies);
EXPECT_EQ("A", cookies[0].Name());
EXPECT_EQ("B", cookies[0].Value());
EXPECT_EQ("Secure", cookies[1].Name());
EXPECT_EQ("E", cookies[1].Value());
}
TEST_F(CookieManagerImplTest, GetCookieListHttpOnly) {
// Clean out the cookies.
network::mojom::CookieDeletionFilter filter;
EXPECT_EQ(4u, service_wrapper()->DeleteCookies(filter));
// Create an httponly and a non-httponly cookie.
bool result;
result = SetCanonicalCookie(
net::CanonicalCookie(
"A", "B", "foo_host", "/", base::Time(), base::Time(), base::Time(),
/*secure=*/false, /*httponly=*/true,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true);
ASSERT_TRUE(result);
result = SetCanonicalCookie(
net::CanonicalCookie(
"C", "D", "foo_host", "/", base::Time(), base::Time(), base::Time(),
/*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true);
ASSERT_TRUE(result);
// Retrieve without httponly cookies (default)
net::CookieOptions options;
EXPECT_TRUE(options.exclude_httponly());
std::vector<net::CanonicalCookie> cookies = service_wrapper()->GetCookieList(
GURL("https://foo_host/with/path"), options);
EXPECT_EQ(1u, cookies.size());
EXPECT_EQ("C", cookies[0].Name());
// Retrieve with httponly cookies.
options.set_include_httponly();
cookies = service_wrapper()->GetCookieList(GURL("https://foo_host/with/path"),
options);
EXPECT_EQ(2u, cookies.size());
std::sort(cookies.begin(), cookies.end(), &CompareCanonicalCookies);
EXPECT_EQ("A", cookies[0].Name());
EXPECT_EQ("C", cookies[1].Name());
}
TEST_F(CookieManagerImplTest, GetCookieListSameSite) {
// Clean out the cookies.
network::mojom::CookieDeletionFilter filter;
EXPECT_EQ(4u, service_wrapper()->DeleteCookies(filter));
// Create an unrestricted, a lax, and a strict cookie.
bool result;
result = SetCanonicalCookie(
net::CanonicalCookie(
"A", "B", "foo_host", "/", base::Time(), base::Time(), base::Time(),
/*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true);
ASSERT_TRUE(result);
result = SetCanonicalCookie(
net::CanonicalCookie("C", "D", "foo_host", "/", base::Time(),
base::Time(), base::Time(), /*secure=*/false,
/*httponly=*/false, net::CookieSameSite::LAX_MODE,
net::COOKIE_PRIORITY_MEDIUM),
true, true);
ASSERT_TRUE(result);
result = SetCanonicalCookie(
net::CanonicalCookie("E", "F", "foo_host", "/", base::Time(),
base::Time(), base::Time(), /*secure=*/false,
/*httponly=*/false, net::CookieSameSite::STRICT_MODE,
net::COOKIE_PRIORITY_MEDIUM),
true, true);
ASSERT_TRUE(result);
// Retrieve only unrestricted cookies.
net::CookieOptions options;
EXPECT_EQ(net::CookieOptions::SameSiteCookieMode::DO_NOT_INCLUDE,
options.same_site_cookie_mode());
std::vector<net::CanonicalCookie> cookies = service_wrapper()->GetCookieList(
GURL("https://foo_host/with/path"), options);
EXPECT_EQ(1u, cookies.size());
EXPECT_EQ("A", cookies[0].Name());
// Retrieve unrestricted and lax cookies.
options.set_same_site_cookie_mode(
net::CookieOptions::SameSiteCookieMode::INCLUDE_LAX);
cookies = service_wrapper()->GetCookieList(GURL("https://foo_host/with/path"),
options);
EXPECT_EQ(2u, cookies.size());
std::sort(cookies.begin(), cookies.end(), &CompareCanonicalCookies);
EXPECT_EQ("A", cookies[0].Name());
EXPECT_EQ("C", cookies[1].Name());
// Retrieve everything.
options.set_same_site_cookie_mode(
net::CookieOptions::SameSiteCookieMode::INCLUDE_STRICT_AND_LAX);
cookies = service_wrapper()->GetCookieList(GURL("https://foo_host/with/path"),
options);
EXPECT_EQ(3u, cookies.size());
std::sort(cookies.begin(), cookies.end(), &CompareCanonicalCookies);
EXPECT_EQ("A", cookies[0].Name());
EXPECT_EQ("C", cookies[1].Name());
EXPECT_EQ("E", cookies[2].Name());
}
TEST_F(CookieManagerImplTest, GetCookieListAccessTime) {
// Clean out the cookies and set a new, clean cookie.
network::mojom::CookieDeletionFilter filter;
EXPECT_EQ(4u, service_wrapper()->DeleteCookies(filter));
bool result;
result = SetCanonicalCookie(
net::CanonicalCookie(
"A", "B", "foo_host", "/", base::Time(), base::Time(), base::Time(),
/*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true);
ASSERT_TRUE(result);
// Get the cookie without updating the access time and check
// the access time is null.
net::CookieOptions options;
options.set_do_not_update_access_time();
std::vector<net::CanonicalCookie> cookies = service_wrapper()->GetCookieList(
GURL("https://foo_host/with/path"), options);
ASSERT_EQ(1u, cookies.size());
EXPECT_EQ("A", cookies[0].Name());
EXPECT_TRUE(cookies[0].LastAccessDate().is_null());
// Get the cookie updating the access time and check
// that it's a valid value.
base::Time start(base::Time::Now());
options.set_update_access_time();
cookies = service_wrapper()->GetCookieList(GURL("https://foo_host/with/path"),
options);
ASSERT_EQ(1u, cookies.size());
EXPECT_EQ("A", cookies[0].Name());
EXPECT_FALSE(cookies[0].LastAccessDate().is_null());
EXPECT_GE(cookies[0].LastAccessDate(), start);
EXPECT_LE(cookies[0].LastAccessDate(), base::Time::Now());
}
TEST_F(CookieManagerImplTest, SetExtraCookie) {
EXPECT_TRUE(service_wrapper()->SetCanonicalCookie(
net::CanonicalCookie(
"X", "Y", "new_host", "/", base::Time(), base::Time(), base::Time(),
/*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
false, false));
std::vector<net::CanonicalCookie> cookies =
service_wrapper()->GetAllCookies();
ASSERT_EQ(5u, cookies.size());
std::sort(cookies.begin(), cookies.end(), &CompareCanonicalCookies);
EXPECT_EQ("A", cookies[0].Name());
EXPECT_EQ("B", cookies[0].Value());
EXPECT_EQ("C", cookies[1].Name());
EXPECT_EQ("D", cookies[1].Value());
EXPECT_EQ("HttpOnly", cookies[2].Name());
EXPECT_EQ("F", cookies[2].Value());
EXPECT_EQ("Secure", cookies[3].Name());
EXPECT_EQ("E", cookies[3].Value());
EXPECT_EQ("X", cookies[4].Name());
EXPECT_EQ("Y", cookies[4].Value());
}
TEST_F(CookieManagerImplTest, DeleteThroughSet) {
base::Time yesterday = base::Time::Now() - base::TimeDelta::FromDays(1);
EXPECT_TRUE(service_wrapper()->SetCanonicalCookie(
net::CanonicalCookie("A", "E", "foo_host", "/", base::Time(), yesterday,
base::Time(), /*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_MEDIUM),
false, false));
std::vector<net::CanonicalCookie> cookies =
service_wrapper()->GetAllCookies();
ASSERT_EQ(3u, cookies.size());
std::sort(cookies.begin(), cookies.end(), &CompareCanonicalCookies);
EXPECT_EQ("C", cookies[0].Name());
EXPECT_EQ("D", cookies[0].Value());
EXPECT_EQ("HttpOnly", cookies[1].Name());
EXPECT_EQ("F", cookies[1].Value());
EXPECT_EQ("Secure", cookies[2].Name());
EXPECT_EQ("E", cookies[2].Value());
}
TEST_F(CookieManagerImplTest, ConfirmSecureSetFails) {
EXPECT_FALSE(service_wrapper()->SetCanonicalCookie(
net::CanonicalCookie(
"N", "O", "foo_host", "/", base::Time(), base::Time(), base::Time(),
/*secure=*/true, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
false, false));
std::vector<net::CanonicalCookie> cookies =
service_wrapper()->GetAllCookies();
ASSERT_EQ(4u, cookies.size());
}
TEST_F(CookieManagerImplTest, ConfirmHttpOnlySetFails) {
EXPECT_FALSE(service_wrapper()->SetCanonicalCookie(
net::CanonicalCookie(
"N", "O", "foo_host", "/", base::Time(), base::Time(), base::Time(),
/*secure=*/false, /*httponly=*/true,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
false, false));
std::vector<net::CanonicalCookie> cookies =
service_wrapper()->GetAllCookies();
ASSERT_EQ(4u, cookies.size());
}
TEST_F(CookieManagerImplTest, ConfirmHttpOnlyOverwriteFails) {
EXPECT_FALSE(service_wrapper()->SetCanonicalCookie(
net::CanonicalCookie(
"HttpOnly", "Nope", "foo_host", "/with/path", base::Time(),
base::Time(), base::Time(), /*secure=*/false,
/*httponly=*/true, net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_MEDIUM),
false, false));
std::vector<net::CanonicalCookie> cookies =
service_wrapper()->GetAllCookies();
ASSERT_EQ(4u, cookies.size());
std::sort(cookies.begin(), cookies.end(), &CompareCanonicalCookies);
EXPECT_EQ("HttpOnly", cookies[2].Name());
EXPECT_EQ("F", cookies[2].Value());
}
TEST_F(CookieManagerImplTest, DeleteEverything) {
network::mojom::CookieDeletionFilter filter;
EXPECT_EQ(4u, service_wrapper()->DeleteCookies(filter));
std::vector<net::CanonicalCookie> cookies =
service_wrapper()->GetAllCookies();
ASSERT_EQ(0u, cookies.size());
}
TEST_F(CookieManagerImplTest, DeleteByTime) {
network::mojom::CookieDeletionFilter filter;
EXPECT_EQ(4u, service_wrapper()->DeleteCookies(filter));
base::Time now(base::Time::Now());
// Create three cookies and delete the middle one.
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A1", "val", "foo_host", "/", now - base::TimeDelta::FromMinutes(60),
base::Time(), base::Time(), /*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true));
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A2", "val", "foo_host", "/", now - base::TimeDelta::FromMinutes(120),
base::Time(), base::Time(), /*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true));
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A3", "val", "foo_host", "/", now - base::TimeDelta::FromMinutes(180),
base::Time(), base::Time(), /*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true));
filter.created_after_time = now - base::TimeDelta::FromMinutes(150);
filter.created_before_time = now - base::TimeDelta::FromMinutes(90);
EXPECT_EQ(1u, service_wrapper()->DeleteCookies(filter));
std::vector<net::CanonicalCookie> cookies =
service_wrapper()->GetAllCookies();
ASSERT_EQ(2u, cookies.size());
std::sort(cookies.begin(), cookies.end(), &CompareCanonicalCookies);
EXPECT_EQ("A1", cookies[0].Name());
EXPECT_EQ("A3", cookies[1].Name());
}
TEST_F(CookieManagerImplTest, DeleteByExcludingDomains) {
network::mojom::CookieDeletionFilter filter;
EXPECT_EQ(4u, service_wrapper()->DeleteCookies(filter));
// Create three cookies and delete the middle one.
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A1", "val", "foo_host1", "/", base::Time(), base::Time(),
base::Time(), /*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true));
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A2", "val", "foo_host2", "/", base::Time(), base::Time(),
base::Time(), /*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true));
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A3", "val", "foo_host3", "/", base::Time(), base::Time(),
base::Time(), /*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true));
filter.excluding_domains = std::vector<std::string>();
filter.excluding_domains->push_back("foo_host2");
EXPECT_EQ(2u, service_wrapper()->DeleteCookies(filter));
std::vector<net::CanonicalCookie> cookies =
service_wrapper()->GetAllCookies();
ASSERT_EQ(1u, cookies.size());
EXPECT_EQ("A2", cookies[0].Name());
}
TEST_F(CookieManagerImplTest, DeleteByIncludingDomains) {
network::mojom::CookieDeletionFilter filter;
EXPECT_EQ(4u, service_wrapper()->DeleteCookies(filter));
// Create three cookies and delete the middle one.
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A1", "val", "foo_host1", "/", base::Time(), base::Time(),
base::Time(), /*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true));
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A2", "val", "foo_host2", "/", base::Time(), base::Time(),
base::Time(), /*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true));
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A3", "val", "foo_host3", "/", base::Time(), base::Time(),
base::Time(), /*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true));
filter.including_domains = std::vector<std::string>();
filter.including_domains->push_back("foo_host1");
filter.including_domains->push_back("foo_host3");
EXPECT_EQ(2u, service_wrapper()->DeleteCookies(filter));
std::vector<net::CanonicalCookie> cookies =
service_wrapper()->GetAllCookies();
ASSERT_EQ(1u, cookies.size());
EXPECT_EQ("A2", cookies[0].Name());
}
TEST_F(CookieManagerImplTest, DeleteBySessionStatus) {
network::mojom::CookieDeletionFilter filter;
EXPECT_EQ(4u, service_wrapper()->DeleteCookies(filter));
base::Time now(base::Time::Now());
// Create three cookies and delete the middle one.
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A1", "val", "foo_host", "/", base::Time(), base::Time(),
base::Time(), /*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true));
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie("A2", "val", "foo_host", "/", base::Time(),
now + base::TimeDelta::FromDays(1), base::Time(),
/*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_MEDIUM),
true, true));
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A3", "val", "foo_host", "/", base::Time(), base::Time(),
base::Time(), /*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true));
filter.session_control =
network::mojom::CookieDeletionSessionControl::PERSISTENT_COOKIES;
EXPECT_EQ(1u, service_wrapper()->DeleteCookies(filter));
std::vector<net::CanonicalCookie> cookies =
service_wrapper()->GetAllCookies();
ASSERT_EQ(2u, cookies.size());
std::sort(cookies.begin(), cookies.end(), &CompareCanonicalCookies);
EXPECT_EQ("A1", cookies[0].Name());
EXPECT_EQ("A3", cookies[1].Name());
}
TEST_F(CookieManagerImplTest, DeleteByAll) {
network::mojom::CookieDeletionFilter filter;
EXPECT_EQ(4u, service_wrapper()->DeleteCookies(filter));
base::Time now(base::Time::Now());
// Add a lot of cookies, only one of which will be deleted by the filter.
// Filter will be:
// * Between two and four days ago.
// * Including domains: no.com and nope.com
// * Excluding domains: no.com and yes.com (excluding wins on no.com
// because of ANDing)
// * Persistent cookies.
// The archetypal cookie (which will be deleted) will satisfy all of
// these filters (2 days old, nope.com, persistent).
// Each of the other four cookies will vary in a way that will take it out
// of being deleted by one of the filter.
filter.created_after_time = now - base::TimeDelta::FromDays(4);
filter.created_before_time = now - base::TimeDelta::FromDays(2);
filter.including_domains = std::vector<std::string>();
filter.including_domains->push_back("no.com");
filter.including_domains->push_back("nope.com");
filter.excluding_domains = std::vector<std::string>();
filter.excluding_domains->push_back("no.com");
filter.excluding_domains->push_back("yes.com");
filter.session_control =
network::mojom::CookieDeletionSessionControl::PERSISTENT_COOKIES;
// Architectypal cookie:
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A1", "val", "nope.com", "/", now - base::TimeDelta::FromDays(3),
now + base::TimeDelta::FromDays(3), base::Time(), /*secure=*/false,
/*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_MEDIUM),
true, true));
// Too old cookie.
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A2", "val", "nope.com", "/", now - base::TimeDelta::FromDays(5),
now + base::TimeDelta::FromDays(3), base::Time(), /*secure=*/false,
/*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_MEDIUM),
true, true));
// Too young cookie.
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A3", "val", "nope.com", "/", now - base::TimeDelta::FromDays(1),
now + base::TimeDelta::FromDays(3), base::Time(), /*secure=*/false,
/*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_MEDIUM),
true, true));
// Not in including_domains.
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A4", "val", "other.com", "/", now - base::TimeDelta::FromDays(3),
now + base::TimeDelta::FromDays(3), base::Time(), /*secure=*/false,
/*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_MEDIUM),
true, true));
// In excluding_domains.
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A5", "val", "no.com", "/", now - base::TimeDelta::FromDays(3),
now + base::TimeDelta::FromDays(3), base::Time(), /*secure=*/false,
/*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_MEDIUM),
true, true));
// Session
EXPECT_TRUE(SetCanonicalCookie(
net::CanonicalCookie(
"A6", "val", "nope.com", "/", now - base::TimeDelta::FromDays(3),
base::Time(), base::Time(), /*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true));
EXPECT_EQ(1u, service_wrapper()->DeleteCookies(filter));
std::vector<net::CanonicalCookie> cookies =
service_wrapper()->GetAllCookies();
ASSERT_EQ(5u, cookies.size());
std::sort(cookies.begin(), cookies.end(), &CompareCanonicalCookies);
EXPECT_EQ("A2", cookies[0].Name());
EXPECT_EQ("A3", cookies[1].Name());
EXPECT_EQ("A4", cookies[2].Name());
EXPECT_EQ("A5", cookies[3].Name());
EXPECT_EQ("A6", cookies[4].Name());
}
// Receives and records notifications from the network::mojom::CookieManager.
class CookieChangeNotificationImpl
: public network::mojom::CookieChangeNotification {
public:
struct Notification {
Notification(const net::CanonicalCookie& cookie_in,
network::mojom::CookieChangeCause cause_in) {
cookie = cookie_in;
cause = cause_in;
}
net::CanonicalCookie cookie;
network::mojom::CookieChangeCause cause;
};
CookieChangeNotificationImpl(
network::mojom::CookieChangeNotificationRequest request)
: run_loop_(nullptr), binding_(this, std::move(request)) {}
void WaitForSomeNotification() {
if (!notifications_.empty())
return;
base::RunLoop loop;
run_loop_ = &loop;
loop.Run();
run_loop_ = nullptr;
}
// Adds existing notifications to passed in vector.
void GetCurrentNotifications(std::vector<Notification>* notifications) {
notifications->insert(notifications->end(), notifications_.begin(),
notifications_.end());
notifications_.clear();
}
// network::mojom::CookieChangesNotification
void OnCookieChanged(const net::CanonicalCookie& cookie,
network::mojom::CookieChangeCause cause) override {
notifications_.push_back(Notification(cookie, cause));
if (run_loop_)
run_loop_->Quit();
}
private:
std::vector<Notification> notifications_;
// Loop to signal on receiving a notification if not null.
base::RunLoop* run_loop_;
mojo::Binding<network::mojom::CookieChangeNotification> binding_;
};
TEST_F(CookieManagerImplTest, Notification) {
GURL notification_url("http://www.testing.com/pathele");
std::string notification_name("Cookie_Name");
network::mojom::CookieChangeNotificationPtr ptr;
network::mojom::CookieChangeNotificationRequest request(
mojo::MakeRequest(&ptr));
CookieChangeNotificationImpl notification_impl(std::move(request));
cookie_service_client()->RequestNotification(
notification_url, notification_name, std::move(ptr));
std::vector<CookieChangeNotificationImpl::Notification> notifications;
notification_impl.GetCurrentNotifications(&notifications);
EXPECT_EQ(0u, notifications.size());
notifications.clear();
// Set a cookie that doesn't match the above notification request in name
// and confirm it doesn't produce a notification.
service_wrapper()->SetCanonicalCookie(
net::CanonicalCookie(
"DifferentName", "val", notification_url.host(), "/", base::Time(),
base::Time(), base::Time(), /*secure=*/false,
/*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_MEDIUM),
true, true);
base::RunLoop().RunUntilIdle();
notification_impl.GetCurrentNotifications(&notifications);
EXPECT_EQ(0u, notifications.size());
notifications.clear();
// Set a cookie that doesn't match the above notification request in url
// and confirm it doesn't produce a notification.
service_wrapper()->SetCanonicalCookie(
net::CanonicalCookie(
notification_name, "val", "www.other.host", "/", base::Time(),
base::Time(), base::Time(), /*secure=*/false,
/*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_MEDIUM),
true, true);
base::RunLoop().RunUntilIdle();
notification_impl.GetCurrentNotifications(&notifications);
EXPECT_EQ(0u, notifications.size());
notifications.clear();
// Insert a cookie that does match.
service_wrapper()->SetCanonicalCookie(
net::CanonicalCookie(
notification_name, "val", notification_url.host(), "/", base::Time(),
base::Time(), base::Time(), /*secure=*/false,
/*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_MEDIUM),
true, true);
// Expect asynchrony
notification_impl.GetCurrentNotifications(&notifications);
EXPECT_EQ(0u, notifications.size());
notifications.clear();
// Expect notification
notification_impl.WaitForSomeNotification();
notification_impl.GetCurrentNotifications(&notifications);
EXPECT_EQ(1u, notifications.size());
EXPECT_EQ(notification_name, notifications[0].cookie.Name());
EXPECT_EQ(notification_url.host(), notifications[0].cookie.Domain());
EXPECT_EQ(network::mojom::CookieChangeCause::INSERTED,
notifications[0].cause);
notifications.clear();
// Delete all cookies matching the domain. This includes one for which
// a notification will be generated, and one for which a notification
// will not be generated.
network::mojom::CookieDeletionFilter filter;
filter.including_domains = std::vector<std::string>();
filter.including_domains->push_back(notification_url.host());
EXPECT_EQ(2u, service_wrapper()->DeleteCookies(filter));
// The notification may already have arrived, or it may arrive in the future.
notification_impl.WaitForSomeNotification();
notification_impl.GetCurrentNotifications(&notifications);
ASSERT_EQ(1u, notifications.size());
EXPECT_EQ(notification_name, notifications[0].cookie.Name());
EXPECT_EQ(notification_url.host(), notifications[0].cookie.Domain());
EXPECT_EQ(network::mojom::CookieChangeCause::EXPLICIT,
notifications[0].cause);
}
// Confirm the service operates properly if a returned notification interface
// is destroyed.
TEST_F(CookieManagerImplTest, NotificationRequestDestroyed) {
// Create two identical notification interfaces.
GURL notification_url("http://www.testing.com/pathele");
std::string notification_name("Cookie_Name");
network::mojom::CookieChangeNotificationPtr ptr1;
network::mojom::CookieChangeNotificationRequest request1(
mojo::MakeRequest(&ptr1));
std::unique_ptr<CookieChangeNotificationImpl> notification_impl1(
base::MakeUnique<CookieChangeNotificationImpl>(std::move(request1)));
cookie_service_client()->RequestNotification(
notification_url, notification_name, std::move(ptr1));
network::mojom::CookieChangeNotificationPtr ptr2;
network::mojom::CookieChangeNotificationRequest request2(
mojo::MakeRequest(&ptr2));
std::unique_ptr<CookieChangeNotificationImpl> notification_impl2(
base::MakeUnique<CookieChangeNotificationImpl>(std::move(request2)));
cookie_service_client()->RequestNotification(
notification_url, notification_name, std::move(ptr2));
// Add a cookie and receive a notification on both interfaces.
service_wrapper()->SetCanonicalCookie(
net::CanonicalCookie(
notification_name, "val", notification_url.host(), "/", base::Time(),
base::Time(), base::Time(), /*secure=*/false,
/*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_MEDIUM),
true, true);
std::vector<CookieChangeNotificationImpl::Notification> notifications;
notification_impl1->GetCurrentNotifications(&notifications);
EXPECT_EQ(0u, notifications.size());
notifications.clear();
notification_impl2->GetCurrentNotifications(&notifications);
EXPECT_EQ(0u, notifications.size());
notifications.clear();
notification_impl1->WaitForSomeNotification();
notification_impl1->GetCurrentNotifications(&notifications);
EXPECT_EQ(1u, notifications.size());
notifications.clear();
notification_impl2->WaitForSomeNotification();
notification_impl2->GetCurrentNotifications(&notifications);
EXPECT_EQ(1u, notifications.size());
notifications.clear();
EXPECT_EQ(2u, service_impl()->GetNotificationsBoundForTesting());
// Destroy the first interface
notification_impl1.reset();
// Confirm the second interface can still receive notifications.
network::mojom::CookieDeletionFilter filter;
EXPECT_EQ(5u, service_wrapper()->DeleteCookies(filter));
notification_impl2->WaitForSomeNotification();
notification_impl2->GetCurrentNotifications(&notifications);
EXPECT_EQ(1u, notifications.size());
notifications.clear();
EXPECT_EQ(1u, service_impl()->GetNotificationsBoundForTesting());
}
// Confirm we get a connection error notification if the service dies.
TEST_F(CookieManagerImplTest, ServiceDestructVisible) {
EXPECT_FALSE(connection_error_seen());
NukeService();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(connection_error_seen());
}
// Test service cloning. Also confirm that the service notices if a client
// dies.
TEST_F(CookieManagerImplTest, CloningAndClientDestructVisible) {
EXPECT_EQ(1u, service_impl()->GetClientsBoundForTesting());
// Clone the interface.
network::mojom::CookieManagerPtr new_ptr;
cookie_service_client()->CloneInterface(mojo::MakeRequest(&new_ptr));
SynchronousCookieManager new_wrapper(new_ptr.get());
// Set a cookie on the new interface and make sure it's visible on the
// old one.
EXPECT_TRUE(new_wrapper.SetCanonicalCookie(
net::CanonicalCookie(
"X", "Y", "www.other.host", "/", base::Time(), base::Time(),
base::Time(), /*secure=*/false, /*httponly=*/false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_MEDIUM),
true, true));
std::vector<net::CanonicalCookie> cookies = service_wrapper()->GetCookieList(
GURL("http://www.other.host/"), net::CookieOptions());
ASSERT_EQ(1u, cookies.size());
EXPECT_EQ("X", cookies[0].Name());
EXPECT_EQ("Y", cookies[0].Value());
// After a synchronous round trip through the new client pointer, it
// should be reflected in the bindings seen on the server.
EXPECT_EQ(2u, service_impl()->GetClientsBoundForTesting());
new_ptr.reset();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1u, service_impl()->GetClientsBoundForTesting());
}
} // namespace content