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