// Copyright 2018 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 "net/cookies/cookie_monster_change_dispatcher.h" #include "base/bind.h" #include "base/strings/string_piece.h" #include "base/task_runner.h" #include "base/threading/thread_task_runner_handle.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "net/cookies/canonical_cookie.h" #include "net/cookies/cookie_change_dispatcher.h" namespace net { namespace { // Special key in GlobalDomainMap for global listeners. constexpr base::StringPiece kGlobalDomainKey = base::StringPiece("\0", 1); // constexpr base::StringPiece kGlobalNameKey = base::StringPiece("\0", 1); } // anonymous namespace CookieMonsterChangeDispatcher::Subscription::Subscription( base::WeakPtr change_dispatcher, std::string domain_key, std::string name_key, GURL url, net::CookieChangeCallback callback) : change_dispatcher_(std::move(change_dispatcher)), domain_key_(std::move(domain_key)), name_key_(std::move(name_key)), url_(std::move(url)), callback_(std::move(callback)), task_runner_(base::ThreadTaskRunnerHandle::Get()), weak_ptr_factory_(this) { DCHECK(url_.is_valid() || url_.is_empty()); DCHECK_EQ(url_.is_empty(), domain_key_ == kGlobalDomainKey); // The net::CookieOptions are hard-coded for now, but future APIs may set // different options. For example, JavaScript observers will not be allowed to // see HTTP-only changes. options_.set_include_httponly(); options_.set_same_site_cookie_mode( CookieOptions::SameSiteCookieMode::INCLUDE_STRICT_AND_LAX); } CookieMonsterChangeDispatcher::Subscription::~Subscription() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); change_dispatcher_->UnlinkSubscription(this); } void CookieMonsterChangeDispatcher::Subscription::DispatchChange( const net::CanonicalCookie& cookie, net::CookieChangeCause change_cause) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); if (!url_.is_empty() && !cookie.IncludeForRequestURL(url_, options_)) return; // TODO(mmenke, pwnall): Run callbacks synchronously? task_runner_->PostTask( FROM_HERE, base::BindOnce(&Subscription::DoDispatchChange, weak_ptr_factory_.GetWeakPtr(), cookie, change_cause)); } void CookieMonsterChangeDispatcher::Subscription::DoDispatchChange( const net::CanonicalCookie& cookie, net::CookieChangeCause change_cause) const { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); callback_.Run(cookie, change_cause); } CookieMonsterChangeDispatcher::CookieMonsterChangeDispatcher() : weak_ptr_factory_(this) {} CookieMonsterChangeDispatcher::~CookieMonsterChangeDispatcher() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); } // static std::string CookieMonsterChangeDispatcher::DomainKey( const std::string& domain) { std::string domain_key = net::registry_controlled_domains::GetDomainAndRegistry( domain, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); DCHECK_NE(domain_key, kGlobalDomainKey); return domain_key; } // static std::string CookieMonsterChangeDispatcher::DomainKey(const GURL& url) { std::string domain_key = net::registry_controlled_domains::GetDomainAndRegistry( url, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); DCHECK_NE(domain_key, kGlobalDomainKey); return domain_key; } // static std::string CookieMonsterChangeDispatcher::NameKey(std::string name) { DCHECK_NE(name, kGlobalNameKey); return name; } std::unique_ptr CookieMonsterChangeDispatcher::AddCallbackForCookie( const GURL& url, const std::string& name, CookieChangeCallback callback) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); std::unique_ptr subscription = std::make_unique( weak_ptr_factory_.GetWeakPtr(), DomainKey(url), NameKey(name), url, std::move(callback)); LinkSubscription(subscription.get()); return subscription; } std::unique_ptr CookieMonsterChangeDispatcher::AddCallbackForUrl( const GURL& url, CookieChangeCallback callback) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); std::unique_ptr subscription = std::make_unique( weak_ptr_factory_.GetWeakPtr(), DomainKey(url), std::string(kGlobalNameKey), url, std::move(callback)); LinkSubscription(subscription.get()); return subscription; } std::unique_ptr CookieMonsterChangeDispatcher::AddCallbackForAllChanges( CookieChangeCallback callback) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); std::unique_ptr subscription = std::make_unique( weak_ptr_factory_.GetWeakPtr(), std::string(kGlobalDomainKey), std::string(kGlobalNameKey), GURL(""), std::move(callback)); LinkSubscription(subscription.get()); return subscription; } void CookieMonsterChangeDispatcher::DispatchChange( const CanonicalCookie& cookie, CookieChangeCause cause, bool notify_global_hooks) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DispatchChangeToDomainKey(cookie, cause, DomainKey(cookie.Domain())); if (notify_global_hooks) DispatchChangeToDomainKey(cookie, cause, std::string(kGlobalDomainKey)); } void CookieMonsterChangeDispatcher::DispatchChangeToDomainKey( const CanonicalCookie& cookie, CookieChangeCause cause, const std::string& domain_key) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); CookieDomainMap::iterator it = cookie_domain_map_.find(domain_key); if (it == cookie_domain_map_.end()) return; DispatchChangeToNameKey(cookie, cause, it->second, NameKey(cookie.Name())); DispatchChangeToNameKey(cookie, cause, it->second, std::string(kGlobalNameKey)); } void CookieMonsterChangeDispatcher::DispatchChangeToNameKey( const CanonicalCookie& cookie, CookieChangeCause cause, CookieNameMap& cookie_name_map, const std::string& name_key) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); CookieNameMap::iterator it = cookie_name_map.find(name_key); if (it == cookie_name_map.end()) return; SubscriptionList& subscription_list = it->second; for (base::LinkNode* node = subscription_list.head(); node != subscription_list.end(); node = node->next()) { node->value()->DispatchChange(cookie, cause); } } void CookieMonsterChangeDispatcher::LinkSubscription( Subscription* subscription) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); // The subscript operator creates empty maps if the lookups fail. This is // exactly what this method needs. CookieNameMap& cookie_name_map = cookie_domain_map_[subscription->domain_key()]; SubscriptionList& subscription_list = cookie_name_map[subscription->name_key()]; subscription_list.Append(subscription); } void CookieMonsterChangeDispatcher::UnlinkSubscription( Subscription* subscription) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); CookieDomainMap::iterator cookie_domain_map_iterator = cookie_domain_map_.find(subscription->domain_key()); DCHECK(cookie_domain_map_iterator != cookie_domain_map_.end()); CookieNameMap& cookie_name_map = cookie_domain_map_iterator->second; CookieNameMap::iterator cookie_name_map_iterator = cookie_name_map.find(subscription->name_key()); DCHECK(cookie_name_map_iterator != cookie_name_map.end()); SubscriptionList& subscription_list = cookie_name_map_iterator->second; subscription->RemoveFromList(); if (!subscription_list.empty()) return; cookie_name_map.erase(cookie_name_map_iterator); if (!cookie_name_map.empty()) return; cookie_domain_map_.erase(cookie_domain_map_iterator); } } // namespace net