blob: b711013f87a0b13db8da07226d4239e4d33ea753 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Implements the Chrome Extensions Cookies API.
#include "chrome/browser/extensions/api/cookies/cookies_api.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/time/time.h"
#include "chrome/browser/extensions/api/cookies/cookies_helpers.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/window_controller_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/api/cookies.h"
#include "components/safe_browsing/buildflags.h"
#include "components/safe_browsing/core/common/features.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_api_frame_id_map.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/stack_frame.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_constants.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#if !BUILDFLAG(IS_ANDROID) && BUILDFLAG(SAFE_BROWSING_AVAILABLE)
#include "chrome/browser/safe_browsing/extension_telemetry/cookies_get_all_signal.h"
#include "chrome/browser/safe_browsing/extension_telemetry/cookies_get_signal.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service_factory.h"
#endif
using content::BrowserThread;
namespace extensions {
namespace {
// Keys
constexpr char kCauseKey[] = "cause";
constexpr char kCookieKey[] = "cookie";
constexpr char kRemovedKey[] = "removed";
// Cause Constants
constexpr char kEvictedChangeCause[] = "evicted";
constexpr char kExpiredChangeCause[] = "expired";
constexpr char kExpiredOverwriteChangeCause[] = "expired_overwrite";
constexpr char kExplicitChangeCause[] = "explicit";
constexpr char kOverwriteChangeCause[] = "overwrite";
// Errors
constexpr char kCookieSetFailedError[] =
"Failed to parse or set cookie named \"*\".";
constexpr char kInvalidStoreIdError[] = "Invalid cookie store id: \"*\".";
constexpr char kInvalidUrlError[] = "Invalid url: \"*\".";
constexpr char kNoHostPermissionsError[] =
"No host permissions for cookies at url: \"*\".";
bool CheckHostPermissions(const Extension* extension,
const GURL& url,
std::string* error) {
if (!extension->permissions_data()->HasHostPermission(url)) {
*error =
ErrorUtils::FormatErrorMessage(kNoHostPermissionsError, url.spec());
return false;
}
return true;
}
bool ParseUrl(const Extension* extension,
const std::string& url_string,
GURL* url,
bool check_host_permissions,
std::string* error) {
*url = GURL(url_string);
if (!url->is_valid()) {
*error = ErrorUtils::FormatErrorMessage(kInvalidUrlError, url_string);
return false;
}
// Check against host permissions if needed.
if (check_host_permissions && !CheckHostPermissions(extension, *url, error)) {
return false;
}
return true;
}
network::mojom::CookieManager* ParseStoreCookieManager(
content::BrowserContext* function_context,
bool include_incognito,
std::string* store_id,
std::string* error) {
Profile* function_profile = Profile::FromBrowserContext(function_context);
Profile* store_profile = nullptr;
if (!store_id->empty()) {
store_profile = cookies_helpers::ChooseProfileFromStoreId(
*store_id, function_profile, include_incognito);
if (!store_profile) {
*error = ErrorUtils::FormatErrorMessage(kInvalidStoreIdError, *store_id);
return nullptr;
}
} else {
store_profile = function_profile;
*store_id = cookies_helpers::GetStoreIdFromProfile(store_profile);
}
return store_profile->GetDefaultStoragePartition()
->GetCookieManagerForBrowserProcess();
}
} // namespace
CookiesEventRouter::CookieChangeListener::CookieChangeListener(
CookiesEventRouter* router,
bool otr)
: router_(router), otr_(otr) {}
CookiesEventRouter::CookieChangeListener::~CookieChangeListener() = default;
void CookiesEventRouter::CookieChangeListener::OnCookieChange(
const net::CookieChangeInfo& change) {
router_->OnCookieChange(otr_, change);
}
CookiesEventRouter::CookiesEventRouter(content::BrowserContext* context)
: profile_(Profile::FromBrowserContext(context)),
profile_observation_(this),
otr_profile_observation_(this) {
MaybeStartListening();
profile_observation_.Observe(profile_);
}
CookiesEventRouter::~CookiesEventRouter() = default;
void CookiesEventRouter::OnCookieChange(bool otr,
const net::CookieChangeInfo& change) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// There is no way to represent non-serializable
// partition keys in JS so return to prevent a crash.
if (change.cookie.IsPartitioned() &&
!change.cookie.PartitionKey()->IsSerializeable()) {
return;
}
base::Value::List args;
base::Value::Dict dict;
dict.Set(
kRemovedKey,
change.cause != net::CookieChangeCause::INSERTED &&
change.cause != net::CookieChangeCause::INSERTED_NO_CHANGE_OVERWRITE);
Profile* profile =
otr ? profile_->GetPrimaryOTRProfile(/*create_if_needed=*/false)
: profile_->GetOriginalProfile();
// TODO(407373848): OTR profile must exist when the cookie change event
// arrived.
CHECK(profile);
api::cookies::Cookie cookie = cookies_helpers::CreateCookie(
change.cookie, cookies_helpers::GetStoreIdFromProfile(profile));
dict.Set(kCookieKey, cookie.ToValue());
// Map the internal cause to an external string.
std::string cause_dict_entry;
switch (change.cause) {
// Report an inserted cookie as an "explicit" change cause. All other causes
// only make sense for deletions.
case net::CookieChangeCause::INSERTED:
case net::CookieChangeCause::EXPLICIT:
case net::CookieChangeCause::INSERTED_NO_CHANGE_OVERWRITE:
cause_dict_entry = kExplicitChangeCause;
break;
case net::CookieChangeCause::OVERWRITE:
cause_dict_entry = kOverwriteChangeCause;
break;
case net::CookieChangeCause::EXPIRED:
cause_dict_entry = kExpiredChangeCause;
break;
case net::CookieChangeCause::EVICTED:
cause_dict_entry = kEvictedChangeCause;
break;
case net::CookieChangeCause::EXPIRED_OVERWRITE:
cause_dict_entry = kExpiredOverwriteChangeCause;
break;
case net::CookieChangeCause::UNKNOWN_DELETION:
NOTREACHED();
}
dict.Set(kCauseKey, cause_dict_entry);
args.Append(std::move(dict));
DispatchEvent(profile, events::COOKIES_ON_CHANGED,
api::cookies::OnChanged::kEventName, std::move(args),
cookies_helpers::GetURLFromCanonicalCookie(change.cookie));
}
void CookiesEventRouter::OnOffTheRecordProfileCreated(Profile* off_the_record) {
// When an off-the-record spinoff of |profile_| is created, start listening
// for cookie changes there. The OTR receiver should never be bound, since
// there wasn't previously an OTR profile.
// TODO(crbug.com/417228685): Clank allows for multiple OTR profiles, unlike
// desktop Chrome. Extensions APIs may have built-in assumptions that there
// will only be one OTR profile. We need to determine how this will be handled
// in Desktop Android.
if (!off_the_record->IsPrimaryOTRProfile()) {
return;
}
DCHECK(!otr_receiver_.is_bound());
otr_profile_observation_.Observe(off_the_record);
BindToCookieManager(&otr_receiver_, off_the_record);
}
void CookiesEventRouter::OnProfileWillBeDestroyed(Profile* profile) {
Profile* original_profile = profile_->GetOriginalProfile();
Profile* otr_profile =
original_profile->HasPrimaryOTRProfile()
? original_profile->GetPrimaryOTRProfile(/*create_if_needed=*/true)
: nullptr;
if (profile == otr_profile) {
otr_profile_observation_.Reset();
otr_receiver_.reset();
}
}
void CookiesEventRouter::MaybeStartListening() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(profile_);
Profile* original_profile = profile_->GetOriginalProfile();
Profile* otr_profile =
original_profile->HasPrimaryOTRProfile()
? original_profile->GetPrimaryOTRProfile(/*create_if_needed=*/true)
: nullptr;
if (!receiver_.is_bound()) {
BindToCookieManager(&receiver_, original_profile);
}
if (!otr_receiver_.is_bound() && otr_profile) {
otr_profile_observation_.Observe(otr_profile);
BindToCookieManager(&otr_receiver_, otr_profile);
}
}
void CookiesEventRouter::BindToCookieManager(
mojo::Receiver<network::mojom::CookieChangeListener>* receiver,
Profile* profile) {
network::mojom::CookieManager* cookie_manager =
profile->GetDefaultStoragePartition()
->GetCookieManagerForBrowserProcess();
if (!cookie_manager)
return;
cookie_manager->AddGlobalChangeListener(receiver->BindNewPipeAndPassRemote());
receiver->set_disconnect_handler(
base::BindOnce(&CookiesEventRouter::OnConnectionError,
base::Unretained(this), receiver));
}
void CookiesEventRouter::OnConnectionError(
mojo::Receiver<network::mojom::CookieChangeListener>* receiver) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
receiver->reset();
MaybeStartListening();
}
void CookiesEventRouter::DispatchEvent(content::BrowserContext* context,
events::HistogramValue histogram_value,
const std::string& event_name,
base::Value::List event_args,
const GURL& cookie_domain) {
EventRouter* router = context ? EventRouter::Get(context) : nullptr;
if (!router)
return;
auto event = std::make_unique<Event>(histogram_value, event_name,
std::move(event_args), context);
event->event_url = cookie_domain;
router->BroadcastEvent(std::move(event));
}
CookiesGetFunction::CookiesGetFunction() = default;
CookiesGetFunction::~CookiesGetFunction() = default;
ExtensionFunction::ResponseAction CookiesGetFunction::Run() {
parsed_args_ = api::cookies::Get::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parsed_args_);
// Read/validate input parameters.
std::string error;
if (!ParseUrl(extension(), parsed_args_->details.url, &url_, true, &error))
return RespondNow(Error(std::move(error)));
std::string store_id = parsed_args_->details.store_id.value_or(std::string());
network::mojom::CookieManager* cookie_manager = ParseStoreCookieManager(
browser_context(), include_incognito_information(), &store_id, &error);
if (!cookie_manager)
return RespondNow(Error(std::move(error)));
if (parsed_args_->details.partition_key.has_value() &&
!parsed_args_->details.partition_key->has_cross_site_ancestor
.has_value() &&
parsed_args_->details.partition_key->top_level_site.has_value()) {
base::expected<bool, std::string> cross_site_ancestor =
cookies_helpers::CalculateHasCrossSiteAncestor(
parsed_args_->details.url, parsed_args_->details.partition_key);
if (!cross_site_ancestor.has_value()) {
return RespondNow(Error(std::move(cross_site_ancestor.error())));
}
parsed_args_->details.partition_key->has_cross_site_ancestor =
cross_site_ancestor.value();
}
base::expected<std::optional<net::CookiePartitionKey>, std::string>
partition_key = cookies_helpers::ToNetCookiePartitionKey(
parsed_args_->details.partition_key);
if (!partition_key.has_value()) {
return RespondNow(Error(std::move(partition_key.error())));
}
if (!parsed_args_->details.store_id)
parsed_args_->details.store_id = store_id;
DCHECK(!url_.is_empty() && url_.is_valid());
cookies_helpers::GetCookieListFromManager(
cookie_manager, url_,
net::CookiePartitionKeyCollection(std::move(partition_key).value()),
base::BindOnce(&CookiesGetFunction::GetCookieListCallback, this));
// Extension telemetry signal intercept
NotifyExtensionTelemetry();
// Will finish asynchronously.
return RespondLater();
}
void CookiesGetFunction::GetCookieListCallback(
const net::CookieAccessResultList& cookie_list,
const net::CookieAccessResultList& excluded_cookies) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (const net::CookieWithAccessResult& cookie_with_access_result :
cookie_list) {
if (!cookies_helpers::
CanonicalCookiePartitionKeyMatchesApiCookiePartitionKey(
parsed_args_->details.partition_key,
cookie_with_access_result.cookie.PartitionKey())) {
continue;
}
// Return the first matching cookie. Relies on the fact that the
// CookieManager interface returns them in canonical order (longest path,
// then earliest creation time).
if (cookie_with_access_result.cookie.Name() == parsed_args_->details.name) {
api::cookies::Cookie api_cookie = cookies_helpers::CreateCookie(
cookie_with_access_result.cookie, *parsed_args_->details.store_id);
Respond(ArgumentList(api::cookies::Get::Results::Create(api_cookie)));
return;
}
}
// The cookie doesn't exist; return null.
Respond(WithArguments(base::Value()));
}
void CookiesGetFunction::NotifyExtensionTelemetry() {
// TODO(crbug.com/371423073): Support telemetry on Android.
#if !BUILDFLAG(IS_ANDROID) && BUILDFLAG(SAFE_BROWSING_AVAILABLE)
auto* telemetry_service =
safe_browsing::ExtensionTelemetryServiceFactory::GetForProfile(
Profile::FromBrowserContext(browser_context()));
if (!telemetry_service || !telemetry_service->enabled()) {
return;
}
auto cookies_get_signal = std::make_unique<safe_browsing::CookiesGetSignal>(
extension_id(), parsed_args_->details.name,
parsed_args_->details.store_id.value_or(std::string()),
parsed_args_->details.url, js_callstack().value_or(StackTrace()));
telemetry_service->AddSignal(std::move(cookies_get_signal));
#endif
}
CookiesGetAllFunction::CookiesGetAllFunction() = default;
CookiesGetAllFunction::~CookiesGetAllFunction() = default;
ExtensionFunction::ResponseAction CookiesGetAllFunction::Run() {
parsed_args_ = api::cookies::GetAll::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parsed_args_);
std::string error;
if (parsed_args_->details.url &&
!ParseUrl(extension(), *parsed_args_->details.url, &url_, false,
&error)) {
return RespondNow(Error(std::move(error)));
}
std::string store_id = parsed_args_->details.store_id.value_or(std::string());
network::mojom::CookieManager* cookie_manager = ParseStoreCookieManager(
browser_context(), include_incognito_information(), &store_id, &error);
if (!cookie_manager)
return RespondNow(Error(std::move(error)));
// make sure user input is valid
base::expected<std::optional<net::CookiePartitionKey>, std::string>
partition_key = cookies_helpers::ToNetCookiePartitionKey(
parsed_args_->details.partition_key);
if (!partition_key.has_value()) {
return RespondNow(Error(std::move(partition_key.error())));
}
if (!parsed_args_->details.store_id)
parsed_args_->details.store_id = store_id;
net::CookiePartitionKeyCollection cookie_partition_key_collection =
cookies_helpers::CookiePartitionKeyCollectionFromApiPartitionKey(
parsed_args_->details.partition_key);
DCHECK(url_.is_empty() || url_.is_valid());
if (url_.is_empty()) {
cookies_helpers::GetAllCookiesFromManager(
cookie_manager,
base::BindOnce(&CookiesGetAllFunction::GetAllCookiesCallback, this));
} else {
cookies_helpers::GetCookieListFromManager(
cookie_manager, url_, cookie_partition_key_collection,
base::BindOnce(&CookiesGetAllFunction::GetCookieListCallback, this));
}
// Extension telemetry signal intercept
NotifyExtensionTelemetry();
return RespondLater();
}
void CookiesGetAllFunction::GetAllCookiesCallback(
const net::CookieList& cookie_list) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (extension()) {
net::CookiePartitionKeyCollection cookie_partition_key_collection =
cookies_helpers::CookiePartitionKeyCollectionFromApiPartitionKey(
parsed_args_->details.partition_key);
std::vector<api::cookies::Cookie> match_vector;
cookies_helpers::AppendMatchingCookiesFromCookieListToVector(
cookie_list, &parsed_args_->details, extension(), &match_vector,
cookie_partition_key_collection);
Respond(ArgumentList(api::cookies::GetAll::Results::Create(match_vector)));
} else {
// TODO(devlin): When can |extension()| be null for this function?
Respond(NoArguments());
}
}
void CookiesGetAllFunction::GetCookieListCallback(
const net::CookieAccessResultList& cookie_list,
const net::CookieAccessResultList& excluded_cookies) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (extension()) {
std::vector<api::cookies::Cookie> match_vector;
cookies_helpers::AppendMatchingCookiesFromCookieAccessResultListToVector(
cookie_list, &parsed_args_->details, extension(), &match_vector);
Respond(ArgumentList(api::cookies::GetAll::Results::Create(match_vector)));
} else {
// TODO(devlin): When can |extension()| be null for this function?
Respond(NoArguments());
}
}
void CookiesGetAllFunction::NotifyExtensionTelemetry() {
// TODO(crbug.com/371423073): Support telemetry on Android.
#if !BUILDFLAG(IS_ANDROID) && BUILDFLAG(SAFE_BROWSING_AVAILABLE)
auto* telemetry_service =
safe_browsing::ExtensionTelemetryServiceFactory::GetForProfile(
Profile::FromBrowserContext(browser_context()));
if (!telemetry_service || !telemetry_service->enabled()) {
return;
}
auto cookies_get_all_signal =
std::make_unique<safe_browsing::CookiesGetAllSignal>(
extension_id(), parsed_args_->details.domain.value_or(std::string()),
parsed_args_->details.name.value_or(std::string()),
parsed_args_->details.path.value_or(std::string()),
parsed_args_->details.secure,
parsed_args_->details.store_id.value_or(std::string()),
parsed_args_->details.url.value_or(std::string()),
parsed_args_->details.session, js_callstack().value_or(StackTrace()));
telemetry_service->AddSignal(std::move(cookies_get_all_signal));
#endif
}
CookiesSetFunction::CookiesSetFunction()
: state_(NO_RESPONSE), success_(false) {}
CookiesSetFunction::~CookiesSetFunction() = default;
ExtensionFunction::ResponseAction CookiesSetFunction::Run() {
parsed_args_ = api::cookies::Set::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parsed_args_);
// Read/validate input parameters.
std::string error;
if (!ParseUrl(extension(), parsed_args_->details.url, &url_, true, &error))
return RespondNow(Error(std::move(error)));
std::string store_id = parsed_args_->details.store_id.value_or(std::string());
network::mojom::CookieManager* cookie_manager = ParseStoreCookieManager(
browser_context(), include_incognito_information(), &store_id, &error);
if (!cookie_manager)
return RespondNow(Error(std::move(error)));
// cookies.set api allows for an partitionKey with a `top_level_site` present
// but no value for `has_cross_site_ancestor`. If that is the case, the
// browser will calculate the value for `has_cross_site_ancestor`.
std::optional<extensions::api::cookies::CookiePartitionKey> api_partition_key;
if (parsed_args_->details.partition_key.has_value()) {
api_partition_key = parsed_args_->details.partition_key->Clone();
if (!api_partition_key->has_cross_site_ancestor.has_value() &&
api_partition_key->top_level_site.has_value()) {
base::expected<bool, std::string> cross_site_ancestor =
cookies_helpers::CalculateHasCrossSiteAncestor(
parsed_args_->details.url, api_partition_key);
if (!cross_site_ancestor.has_value()) {
return RespondNow(Error(std::move(cross_site_ancestor.error())));
}
api_partition_key->has_cross_site_ancestor = cross_site_ancestor.value();
}
}
if (!cookies_helpers::ValidateCrossSiteAncestor(parsed_args_->details.url,
api_partition_key, &error)) {
return RespondNow(Error(std::move(error)));
}
base::expected<std::optional<net::CookiePartitionKey>, std::string>
net_partition_key =
cookies_helpers::ToNetCookiePartitionKey(api_partition_key);
if (!net_partition_key.has_value()) {
return RespondNow(Error(std::move(net_partition_key.error())));
}
if (!parsed_args_->details.store_id)
parsed_args_->details.store_id = store_id;
base::Time expiration_time;
if (parsed_args_->details.expiration_date) {
// Time::FromSecondsSinceUnixEpoch converts double time 0 to empty Time
// object. So we need to do special handling here.
expiration_time = (*parsed_args_->details.expiration_date == 0)
? base::Time::UnixEpoch()
: base::Time::FromSecondsSinceUnixEpoch(
*parsed_args_->details.expiration_date);
}
net::CookieSameSite same_site = net::CookieSameSite::UNSPECIFIED;
switch (parsed_args_->details.same_site) {
case api::cookies::SameSiteStatus::kNoRestriction:
same_site = net::CookieSameSite::NO_RESTRICTION;
break;
case api::cookies::SameSiteStatus::kLax:
same_site = net::CookieSameSite::LAX_MODE;
break;
case api::cookies::SameSiteStatus::kStrict:
same_site = net::CookieSameSite::STRICT_MODE;
break;
// This is the case if the optional sameSite property is given as
// "unspecified":
case api::cookies::SameSiteStatus::kUnspecified:
// This is the case if the optional sameSite property is left out:
case api::cookies::SameSiteStatus::kNone:
same_site = net::CookieSameSite::UNSPECIFIED;
break;
}
std::unique_ptr<net::CanonicalCookie> cc(
net::CanonicalCookie::CreateSanitizedCookie(
url_, //
parsed_args_->details.name.value_or(std::string()), //
parsed_args_->details.value.value_or(std::string()), //
parsed_args_->details.domain.value_or(std::string()), //
parsed_args_->details.path.value_or(std::string()), //
/*creation_time=*/base::Time(), //
expiration_time, //
/*last_access_time=*/base::Time(), //
parsed_args_->details.secure.value_or(false), //
parsed_args_->details.http_only.value_or(false), //
same_site, //
net::COOKIE_PRIORITY_DEFAULT, //
net_partition_key.value(), //
/*status=*/nullptr));
if (!cc) {
// Return error through callbacks so that the proper error message
// is generated.
success_ = false;
state_ = SET_COMPLETED;
GetCookieListCallback(net::CookieAccessResultList(),
net::CookieAccessResultList());
return AlreadyResponded();
}
// Dispatch the setter, immediately followed by the getter. This
// plus FIFO ordering on the cookie_manager_ pipe means that no
// other extension function will affect the get result.
net::CookieOptions options;
options.set_include_httponly();
options.set_same_site_cookie_context(
net::CookieOptions::SameSiteCookieContext::MakeInclusive());
DCHECK(!url_.is_empty() && url_.is_valid());
cookie_manager->SetCanonicalCookie(
*cc, url_, options,
base::BindOnce(&CookiesSetFunction::SetCanonicalCookieCallback, this));
cookies_helpers::GetCookieListFromManager(
cookie_manager, url_,
net::CookiePartitionKeyCollection(std::move(net_partition_key).value()),
base::BindOnce(&CookiesSetFunction::GetCookieListCallback, this));
// Will finish asynchronously.
return RespondLater();
}
void CookiesSetFunction::SetCanonicalCookieCallback(
net::CookieAccessResult set_cookie_result) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(NO_RESPONSE, state_);
state_ = SET_COMPLETED;
success_ = set_cookie_result.status.IsInclude();
}
void CookiesSetFunction::GetCookieListCallback(
const net::CookieAccessResultList& cookie_list,
const net::CookieAccessResultList& excluded_cookies) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(SET_COMPLETED, state_);
state_ = GET_COMPLETED;
if (!success_) {
std::string name = parsed_args_->details.name.value_or(std::string());
Respond(Error(ErrorUtils::FormatErrorMessage(kCookieSetFailedError, name)));
return;
}
std::optional<ResponseValue> value;
for (const net::CookieWithAccessResult& cookie_with_access_result :
cookie_list) {
// Return the first matching cookie. Relies on the fact that the
// CookieMonster returns them in canonical order (longest path, then
// earliest creation time).
if (!extensions::cookies_helpers::
CanonicalCookiePartitionKeyMatchesApiCookiePartitionKey(
parsed_args_->details.partition_key,
cookie_with_access_result.cookie.PartitionKey())) {
continue;
}
std::string name = parsed_args_->details.name.value_or(std::string());
if (cookie_with_access_result.cookie.Name() == name) {
api::cookies::Cookie api_cookie = cookies_helpers::CreateCookie(
cookie_with_access_result.cookie, *parsed_args_->details.store_id);
value.emplace(
ArgumentList(api::cookies::Set::Results::Create(api_cookie)));
break;
}
}
Respond(value ? std::move(*value) : NoArguments());
}
CookiesRemoveFunction::CookiesRemoveFunction() = default;
CookiesRemoveFunction::~CookiesRemoveFunction() = default;
ExtensionFunction::ResponseAction CookiesRemoveFunction::Run() {
parsed_args_ = api::cookies::Remove::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parsed_args_);
// Read/validate input parameters.
std::string error;
if (!ParseUrl(extension(), parsed_args_->details.url, &url_, true, &error))
return RespondNow(Error(std::move(error)));
std::string store_id = parsed_args_->details.store_id.value_or(std::string());
network::mojom::CookieManager* cookie_manager = ParseStoreCookieManager(
browser_context(), include_incognito_information(), &store_id, &error);
if (!cookie_manager)
return RespondNow(Error(std::move(error)));
base::expected<std::optional<net::CookiePartitionKey>, std::string>
partition_key = cookies_helpers::ToNetCookiePartitionKey(
parsed_args_->details.partition_key);
if (!partition_key.has_value()) {
return RespondNow(Error(std::move(partition_key.error())));
}
if (!parsed_args_->details.store_id)
parsed_args_->details.store_id = store_id;
network::mojom::CookieDeletionFilterPtr filter(
network::mojom::CookieDeletionFilter::New());
filter->cookie_partition_key_collection =
net::CookiePartitionKeyCollection(std::move(partition_key).value());
filter->url = url_;
filter->cookie_name = parsed_args_->details.name;
cookie_manager->DeleteCookies(
std::move(filter),
base::BindOnce(&CookiesRemoveFunction::RemoveCookieCallback, this));
// Will return asynchronously.
return RespondLater();
}
void CookiesRemoveFunction::RemoveCookieCallback(uint32_t /* num_deleted */) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Build the callback result
api::cookies::Remove::Results::Details details;
details.name = parsed_args_->details.name;
details.url = url_.spec();
details.store_id = *parsed_args_->details.store_id;
if (parsed_args_->details.partition_key) {
details.partition_key = parsed_args_->details.partition_key->Clone();
}
Respond(ArgumentList(api::cookies::Remove::Results::Create(details)));
}
CookiesGetPartitionKeyFunction::CookiesGetPartitionKeyFunction() = default;
CookiesGetPartitionKeyFunction::~CookiesGetPartitionKeyFunction() = default;
ExtensionFunction::ResponseAction CookiesGetPartitionKeyFunction::Run() {
parsed_args_ = api::cookies::GetPartitionKey::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parsed_args_);
content::RenderFrameHost* render_frame_host = nullptr;
content::WebContents* web_contents = nullptr;
std::optional<int> frame_id = parsed_args_->details.frame_id;
std::optional<ExtensionApiFrameIdMap::DocumentId> document_id;
std::optional<int> tab_id = parsed_args_->details.tab_id;
if (parsed_args_->details.document_id.has_value()) {
document_id = ExtensionApiFrameIdMap::DocumentIdFromString(
*parsed_args_->details.document_id);
render_frame_host =
ExtensionApiFrameIdMap::Get()->GetRenderFrameHostByDocumentId(
document_id.value());
web_contents = content::WebContents::FromRenderFrameHost(render_frame_host);
} else if (tab_id.has_value()) {
if (!frame_id.has_value()) {
// Default to main frame if no frame is provided.
frame_id = 0;
}
if (!ExtensionTabUtil::GetTabById(tab_id.value(), browser_context(),
include_incognito_information(),
&web_contents) ||
!web_contents) {
return RespondNow(Error("Invalid `tabId`."));
}
render_frame_host = ExtensionApiFrameIdMap::GetRenderFrameHostById(
web_contents, frame_id.value());
} else if (frame_id.has_value()) {
if (frame_id.value() == 0) {
return RespondNow(
Error("`frameId` may not be 0 if no `tabId` is present."));
}
render_frame_host =
ExtensionApiFrameIdMap::Get()->GetRenderFrameHostByFrameId(
frame_id.value());
} else {
return RespondNow(
Error("Either `documentId` or `tabId` must be specified."));
}
if (!render_frame_host) {
return RespondNow(document_id.has_value() ? Error("Invalid `documentId`.")
: Error("Invalid `frameId`."));
}
// If both document_id and tab_id are provided, make sure they match.
if (document_id.has_value() && tab_id.has_value()) {
if (ExtensionTabUtil::GetTabId(web_contents) != tab_id.value() ||
ExtensionApiFrameIdMap::GetFrameId(render_frame_host) !=
frame_id.value()) {
return RespondNow(
Error("Provided `tabId` and `frameId` do not match the frame."));
}
}
base::expected<net::CookiePartitionKey::SerializedCookiePartitionKey,
std::string>
serialized_key = net::CookiePartitionKey::Serialize(
render_frame_host->GetStorageKey().ToCookiePartitionKey());
if (!serialized_key.has_value()) {
return RespondNow(Error("PartitionKey requested is not serializable."));
}
std::string error;
if (!CheckHostPermissions(extension(), GURL(serialized_key->TopLevelSite()),
&error) ||
!CheckHostPermissions(extension(),
render_frame_host->GetLastCommittedURL(), &error)) {
return RespondNow(Error(error));
}
api::cookies::CookiePartitionKey partition_key;
partition_key.has_cross_site_ancestor =
serialized_key->has_cross_site_ancestor();
partition_key.top_level_site = serialized_key->TopLevelSite();
api::cookies::GetPartitionKey::Results::Details details =
api::cookies::GetPartitionKey::Results::Details();
details.partition_key = partition_key.Clone();
return RespondNow(WithArguments(details.ToValue()));
}
ExtensionFunction::ResponseAction CookiesGetAllCookieStoresFunction::Run() {
Profile* original_profile = Profile::FromBrowserContext(browser_context());
DCHECK(original_profile);
base::Value::List original_tab_ids;
Profile* incognito_profile = nullptr;
base::Value::List incognito_tab_ids;
if (include_incognito_information() &&
original_profile->HasPrimaryOTRProfile()) {
incognito_profile =
original_profile->GetPrimaryOTRProfile(/*create_if_needed=*/true);
}
DCHECK(original_profile != incognito_profile);
// Iterate through all browser instances, and for each browser,
// add its tab IDs to either the regular or incognito tab ID list depending
// whether the browser is regular or incognito.
for (WindowController* window : *WindowControllerList::GetInstance()) {
if (window->profile() == original_profile) {
cookies_helpers::AppendToTabIdList(window, original_tab_ids);
} else if (window->profile() == incognito_profile) {
cookies_helpers::AppendToTabIdList(window, incognito_tab_ids);
}
}
// Return a list of all cookie stores with at least one open tab.
std::vector<api::cookies::CookieStore> cookie_stores;
if (!original_tab_ids.empty()) {
cookie_stores.push_back(cookies_helpers::CreateCookieStore(
original_profile, std::move(original_tab_ids)));
}
if (incognito_profile && !incognito_tab_ids.empty()) {
cookie_stores.push_back(cookies_helpers::CreateCookieStore(
incognito_profile, std::move(incognito_tab_ids)));
}
return RespondNow(ArgumentList(
api::cookies::GetAllCookieStores::Results::Create(cookie_stores)));
}
CookiesAPI::CookiesAPI(content::BrowserContext* context)
: browser_context_(context) {
EventRouter::Get(browser_context_)
->RegisterObserver(this, api::cookies::OnChanged::kEventName);
}
CookiesAPI::~CookiesAPI() = default;
void CookiesAPI::Shutdown() {
EventRouter::Get(browser_context_)->UnregisterObserver(this);
}
static base::LazyInstance<BrowserContextKeyedAPIFactory<CookiesAPI>>::
DestructorAtExit g_cookies_api_factory = LAZY_INSTANCE_INITIALIZER;
// static
BrowserContextKeyedAPIFactory<CookiesAPI>* CookiesAPI::GetFactoryInstance() {
return g_cookies_api_factory.Pointer();
}
void CookiesAPI::OnListenerAdded(const EventListenerInfo& details) {
DCHECK(!cookies_event_router_);
cookies_event_router_ =
std::make_unique<CookiesEventRouter>(browser_context_);
EventRouter::Get(browser_context_)->UnregisterObserver(this);
}
} // namespace extensions