• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/cookies/cookie_monster_change_dispatcher.h"
6 
7 #include <string_view>
8 #include <utility>
9 
10 #include "base/functional/bind.h"
11 #include "base/not_fatal_until.h"
12 #include "base/task/single_thread_task_runner.h"
13 #include "base/task/task_runner.h"
14 #include "net/base/features.h"
15 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
16 #include "net/cookies/canonical_cookie.h"
17 #include "net/cookies/cookie_access_delegate.h"
18 #include "net/cookies/cookie_change_dispatcher.h"
19 #include "net/cookies/cookie_constants.h"
20 #include "net/cookies/cookie_monster.h"
21 #include "net/cookies/cookie_util.h"
22 
23 namespace net {
24 
25 namespace {
26 
27 // Special key in GlobalDomainMap for global listeners.
28 constexpr std::string_view kGlobalDomainKey = std::string_view("\0", 1);
29 
30 //
31 constexpr std::string_view kGlobalNameKey = std::string_view("\0", 1);
32 
33 }  // anonymous namespace
34 
Subscription(base::WeakPtr<CookieMonsterChangeDispatcher> change_dispatcher,std::string domain_key,std::string name_key,GURL url,CookiePartitionKeyCollection cookie_partition_key_collection,net::CookieChangeCallback callback)35 CookieMonsterChangeDispatcher::Subscription::Subscription(
36     base::WeakPtr<CookieMonsterChangeDispatcher> change_dispatcher,
37     std::string domain_key,
38     std::string name_key,
39     GURL url,
40     CookiePartitionKeyCollection cookie_partition_key_collection,
41     net::CookieChangeCallback callback)
42     : change_dispatcher_(std::move(change_dispatcher)),
43       domain_key_(std::move(domain_key)),
44       name_key_(std::move(name_key)),
45       url_(std::move(url)),
46       cookie_partition_key_collection_(
47           std::move(cookie_partition_key_collection)),
48       callback_(std::move(callback)),
49       task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) {
50   DCHECK(url_.is_valid() || url_.is_empty());
51   DCHECK_EQ(url_.is_empty(), domain_key_ == kGlobalDomainKey);
52 }
53 
~Subscription()54 CookieMonsterChangeDispatcher::Subscription::~Subscription() {
55   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
56 
57   if (change_dispatcher_) {
58     change_dispatcher_->UnlinkSubscription(this);
59   }
60 }
61 
DispatchChange(const CookieChangeInfo & change,const CookieAccessDelegate * cookie_access_delegate)62 void CookieMonsterChangeDispatcher::Subscription::DispatchChange(
63     const CookieChangeInfo& change,
64     const CookieAccessDelegate* cookie_access_delegate) {
65   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
66 
67   const CanonicalCookie& cookie = change.cookie;
68 
69   // The net::CookieOptions are hard-coded for now, but future APIs may set
70   // different options. For example, JavaScript observers will not be allowed to
71   // see HTTP-only changes.
72   if (!url_.is_empty()) {
73     bool delegate_treats_url_as_trustworthy =
74         cookie_access_delegate &&
75         cookie_access_delegate->ShouldTreatUrlAsTrustworthy(url_);
76     CookieOptions options = CookieOptions::MakeAllInclusive();
77     if (!cookie
78              .IncludeForRequestURL(
79                  url_, options,
80                  CookieAccessParams{change.access_result.access_semantics,
81                                     delegate_treats_url_as_trustworthy})
82              .status.IsInclude()) {
83       return;
84     }
85   }
86 
87   if (!cookie_partition_key_collection_.ContainsAllKeys()) {
88     if (cookie_partition_key_collection_.PartitionKeys().empty()) {
89       if (cookie.IsPartitioned()) {
90         return;
91       }
92     } else {
93       DCHECK_EQ(1u, cookie_partition_key_collection_.PartitionKeys().size());
94       const CookiePartitionKey& key =
95           *cookie_partition_key_collection_.PartitionKeys().begin();
96       if (CookiePartitionKey::HasNonce(key) && !cookie.IsPartitioned()) {
97         return;
98       }
99       if (cookie.IsPartitioned() && key != *cookie.PartitionKey()) {
100         return;
101       }
102     }
103   }
104   Subscription::DoDispatchChange(change);
105 }
106 
DoDispatchChange(const CookieChangeInfo & change) const107 void CookieMonsterChangeDispatcher::Subscription::DoDispatchChange(
108     const CookieChangeInfo& change) const {
109   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
110 
111   callback_.Run(change);
112 }
113 
CookieMonsterChangeDispatcher(const CookieMonster * cookie_monster)114 CookieMonsterChangeDispatcher::CookieMonsterChangeDispatcher(
115     const CookieMonster* cookie_monster)
116     : cookie_monster_(cookie_monster) {}
117 
~CookieMonsterChangeDispatcher()118 CookieMonsterChangeDispatcher::~CookieMonsterChangeDispatcher() {
119   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
120 }
121 
122 // static
DomainKey(const std::string & domain)123 std::string CookieMonsterChangeDispatcher::DomainKey(
124     const std::string& domain) {
125   std::string domain_key =
126       net::registry_controlled_domains::GetDomainAndRegistry(
127           domain, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
128   DCHECK_NE(domain_key, kGlobalDomainKey);
129   return domain_key;
130 }
131 
132 // static
DomainKey(const GURL & url)133 std::string CookieMonsterChangeDispatcher::DomainKey(const GURL& url) {
134   std::string domain_key =
135       net::registry_controlled_domains::GetDomainAndRegistry(
136           url, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
137   DCHECK_NE(domain_key, kGlobalDomainKey);
138   return domain_key;
139 }
140 
141 // static
NameKey(std::string name)142 std::string CookieMonsterChangeDispatcher::NameKey(std::string name) {
143   DCHECK_NE(name, kGlobalNameKey);
144   return name;
145 }
146 
147 std::unique_ptr<CookieChangeSubscription>
AddCallbackForCookie(const GURL & url,const std::string & name,const std::optional<CookiePartitionKey> & cookie_partition_key,CookieChangeCallback callback)148 CookieMonsterChangeDispatcher::AddCallbackForCookie(
149     const GURL& url,
150     const std::string& name,
151     const std::optional<CookiePartitionKey>& cookie_partition_key,
152     CookieChangeCallback callback) {
153   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
154 
155   std::unique_ptr<Subscription> subscription = std::make_unique<Subscription>(
156       weak_ptr_factory_.GetWeakPtr(), DomainKey(url), NameKey(name), url,
157       CookiePartitionKeyCollection::FromOptional(cookie_partition_key),
158       std::move(callback));
159 
160   LinkSubscription(subscription.get());
161   return subscription;
162 }
163 
164 std::unique_ptr<CookieChangeSubscription>
AddCallbackForUrl(const GURL & url,const std::optional<CookiePartitionKey> & cookie_partition_key,CookieChangeCallback callback)165 CookieMonsterChangeDispatcher::AddCallbackForUrl(
166     const GURL& url,
167     const std::optional<CookiePartitionKey>& cookie_partition_key,
168     CookieChangeCallback callback) {
169   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
170 
171   std::unique_ptr<Subscription> subscription = std::make_unique<Subscription>(
172       weak_ptr_factory_.GetWeakPtr(), DomainKey(url),
173       std::string(kGlobalNameKey), url,
174       CookiePartitionKeyCollection::FromOptional(cookie_partition_key),
175       std::move(callback));
176 
177   LinkSubscription(subscription.get());
178   return subscription;
179 }
180 
181 std::unique_ptr<CookieChangeSubscription>
AddCallbackForAllChanges(CookieChangeCallback callback)182 CookieMonsterChangeDispatcher::AddCallbackForAllChanges(
183     CookieChangeCallback callback) {
184   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
185 
186   std::unique_ptr<Subscription> subscription = std::make_unique<Subscription>(
187       weak_ptr_factory_.GetWeakPtr(), std::string(kGlobalDomainKey),
188       std::string(kGlobalNameKey), GURL(""),
189       CookiePartitionKeyCollection::ContainsAll(), std::move(callback));
190 
191   LinkSubscription(subscription.get());
192   return subscription;
193 }
194 
DispatchChange(const CookieChangeInfo & change,bool notify_global_hooks)195 void CookieMonsterChangeDispatcher::DispatchChange(
196     const CookieChangeInfo& change,
197     bool notify_global_hooks) {
198   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
199 
200   DispatchChangeToDomainKey(change, DomainKey(change.cookie.Domain()));
201   if (notify_global_hooks)
202     DispatchChangeToDomainKey(change, std::string(kGlobalDomainKey));
203 }
204 
DispatchChangeToDomainKey(const CookieChangeInfo & change,const std::string & domain_key)205 void CookieMonsterChangeDispatcher::DispatchChangeToDomainKey(
206     const CookieChangeInfo& change,
207     const std::string& domain_key) {
208   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
209 
210   auto it = cookie_domain_map_.find(domain_key);
211   if (it == cookie_domain_map_.end())
212     return;
213 
214   DispatchChangeToNameKey(change, it->second, NameKey(change.cookie.Name()));
215   DispatchChangeToNameKey(change, it->second, std::string(kGlobalNameKey));
216 }
217 
DispatchChangeToNameKey(const CookieChangeInfo & change,CookieNameMap & cookie_name_map,const std::string & name_key)218 void CookieMonsterChangeDispatcher::DispatchChangeToNameKey(
219     const CookieChangeInfo& change,
220     CookieNameMap& cookie_name_map,
221     const std::string& name_key) {
222   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
223 
224   auto it = cookie_name_map.find(name_key);
225   if (it == cookie_name_map.end())
226     return;
227 
228   SubscriptionList& subscription_list = it->second;
229   for (base::LinkNode<Subscription>* node = subscription_list.head();
230        node != subscription_list.end(); node = node->next()) {
231     node->value()->DispatchChange(change,
232                                   cookie_monster_->cookie_access_delegate());
233   }
234 }
235 
LinkSubscription(Subscription * subscription)236 void CookieMonsterChangeDispatcher::LinkSubscription(
237     Subscription* subscription) {
238   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
239 
240   // The subscript operator creates empty maps if the lookups fail. This is
241   // exactly what this method needs.
242   CookieNameMap& cookie_name_map =
243       cookie_domain_map_[subscription->domain_key()];
244   SubscriptionList& subscription_list =
245       cookie_name_map[subscription->name_key()];
246   subscription_list.Append(subscription);
247 }
248 
UnlinkSubscription(Subscription * subscription)249 void CookieMonsterChangeDispatcher::UnlinkSubscription(
250     Subscription* subscription) {
251   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
252 
253   auto cookie_domain_map_iterator =
254       cookie_domain_map_.find(subscription->domain_key());
255   CHECK(cookie_domain_map_iterator != cookie_domain_map_.end(),
256         base::NotFatalUntil::M130);
257 
258   CookieNameMap& cookie_name_map = cookie_domain_map_iterator->second;
259   auto cookie_name_map_iterator =
260       cookie_name_map.find(subscription->name_key());
261   CHECK(cookie_name_map_iterator != cookie_name_map.end(),
262         base::NotFatalUntil::M130);
263 
264   SubscriptionList& subscription_list = cookie_name_map_iterator->second;
265   subscription->RemoveFromList();
266   if (!subscription_list.empty())
267     return;
268 
269   cookie_name_map.erase(cookie_name_map_iterator);
270   if (!cookie_name_map.empty())
271     return;
272 
273   cookie_domain_map_.erase(cookie_domain_map_iterator);
274 }
275 
276 }  // namespace net
277