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