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