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