1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ios/net/cookies/cookie_store_ios.h"
7 #import <Foundation/Foundation.h>
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h"
12 #include "base/ios/ios_util.h"
13 #include "base/location.h"
14 #include "base/logging.h"
15 #include "base/mac/foundation_util.h"
16 #include "base/mac/scoped_nsobject.h"
17 #include "base/memory/weak_ptr.h"
18 #include "base/metrics/histogram.h"
19 #include "base/observer_list.h"
20 #include "base/sequenced_task_runner.h"
21 #include "base/stl_util.h"
22 #include "base/strings/sys_string_conversions.h"
23 #include "base/task_runner_util.h"
24 #include "base/thread_task_runner_handle.h"
25 #include "base/threading/thread_restrictions.h"
26 #include "ios/net/cookies/cookie_creation_time_manager.h"
27 #include "ios/net/cookies/cookie_store_ios_client.h"
28 #include "ios/net/cookies/system_cookie_util.h"
29 #import "net/base/mac/url_conversions.h"
30 #include "net/cookies/cookie_util.h"
31 #include "net/cookies/parsed_cookie.h"
39 // The current cookie store. This weak pointer must not be used to do actual
40 // work. Its only purpose is to check that there is only one synchronized
42 CookieStoreIOS* g_current_synchronized_store = nullptr;
45 #pragma mark NotificationTrampoline
47 // NotificationTrampoline dispatches cookie notifications to all the existing
49 class NotificationTrampoline {
51 static NotificationTrampoline* GetInstance();
53 void AddObserver(CookieNotificationObserver* obs);
54 void RemoveObserver(CookieNotificationObserver* obs);
56 // Notify the observers.
57 void NotifyCookiesChanged();
58 void NotifyCookiePolicyChanged();
61 NotificationTrampoline();
62 ~NotificationTrampoline();
64 base::ObserverList<CookieNotificationObserver> observer_list_;
66 DISALLOW_COPY_AND_ASSIGN(NotificationTrampoline);
68 static NotificationTrampoline* g_notification_trampoline;
71 #pragma mark NotificationTrampoline implementation
73 NotificationTrampoline* NotificationTrampoline::GetInstance() {
74 if (!g_notification_trampoline)
75 g_notification_trampoline = new NotificationTrampoline;
76 return g_notification_trampoline;
79 void NotificationTrampoline::AddObserver(CookieNotificationObserver* obs) {
80 observer_list_.AddObserver(obs);
83 void NotificationTrampoline::RemoveObserver(CookieNotificationObserver* obs) {
84 observer_list_.RemoveObserver(obs);
87 void NotificationTrampoline::NotifyCookiesChanged() {
88 FOR_EACH_OBSERVER(CookieNotificationObserver, observer_list_,
89 OnSystemCookiesChanged());
92 void NotificationTrampoline::NotifyCookiePolicyChanged() {
93 FOR_EACH_OBSERVER(CookieNotificationObserver, observer_list_,
94 OnSystemCookiePolicyChanged());
97 NotificationTrampoline::NotificationTrampoline() {
100 NotificationTrampoline::~NotificationTrampoline() {
103 // Global instance of NotificationTrampoline.
104 NotificationTrampoline* NotificationTrampoline::g_notification_trampoline =
107 #pragma mark Utility functions
109 // Returns the path to Cookie.binarycookies file on the file system where
110 // WKWebView flushes its cookies.
111 base::FilePath GetBinaryCookiesFilePath() {
112 base::FilePath path = base::mac::GetUserLibraryPath();
113 // The relative path of the file (from the user library folder) where
114 // WKWebView stores its cookies.
115 const std::string kCookiesFilePath = "Cookies/Cookies.binarycookies";
116 return path.Append(kCookiesFilePath);
119 // Clears all cookies from the .binarycookies file.
120 // Must be called from a thread where IO operations are allowed.
121 // Preconditions: There must be no active WKWebViews present in the app.
122 void ClearAllCookiesFromBinaryCookiesFile() {
123 // The .binarycookies file is present only on iOS8+.
124 if (!base::ios::IsRunningOnIOS8OrLater()) {
127 base::FilePath path = GetBinaryCookiesFilePath();
128 if (base::PathExists(path)) {
129 bool success = base::DeleteFile(path, false);
131 DLOG(WARNING) << "Failed to remove binarycookies file.";
134 // TODO(shreyasv): Should .binarycookies be parsed to find out how many
135 // more cookies are deleted? Investigate further if the accuracy of this
136 // actually matters to the callback.
139 // Builds a NSHTTPCookie from a header cookie line ("Set-Cookie: xxx") and a
141 NSHTTPCookie* GetNSHTTPCookieFromCookieLine(const std::string& cookie_line,
143 base::Time server_time) {
144 NSURL* nsurl = net::NSURLWithGURL(url);
145 NSString* ns_cookie_line = base::SysUTF8ToNSString(cookie_line);
146 if (!ns_cookie_line) {
147 DLOG(ERROR) << "Cookie line is not UTF8: " << cookie_line;
150 NSArray* cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:@{
151 @"Set-Cookie" : ns_cookie_line
153 if ([cookies count] != 1)
156 NSHTTPCookie* cookie = [cookies objectAtIndex:0];
157 if (![cookie expiresDate] || server_time.is_null())
160 // Perform clock skew correction.
161 base::TimeDelta clock_skew = base::Time::Now() - server_time;
162 NSDate* corrected_expire_date =
163 [[cookie expiresDate] dateByAddingTimeInterval:clock_skew.InSecondsF()];
164 NSMutableDictionary* properties =
165 [NSMutableDictionary dictionaryWithDictionary:[cookie properties]];
166 [properties setObject:corrected_expire_date forKey:NSHTTPCookieExpires];
167 NSHTTPCookie* corrected_cookie =
168 [NSHTTPCookie cookieWithProperties:properties];
169 DCHECK(corrected_cookie);
170 return corrected_cookie;
173 // Compares cookies based on the path lengths and the creation times, as per
175 NSInteger CompareCookies(id a, id b, void* context) {
176 NSHTTPCookie* cookie_a = (NSHTTPCookie*)a;
177 NSHTTPCookie* cookie_b = (NSHTTPCookie*)b;
178 // Compare path lengths first.
179 NSUInteger path_length_a = [[cookie_a path] length];
180 NSUInteger path_length_b = [[cookie_b path] length];
181 if (path_length_a < path_length_b)
182 return NSOrderedDescending;
183 if (path_length_b < path_length_a)
184 return NSOrderedAscending;
186 // Compare creation times.
187 CookieCreationTimeManager* manager = (CookieCreationTimeManager*)context;
189 base::Time created_a = manager->GetCreationTime(cookie_a);
190 base::Time created_b = manager->GetCreationTime(cookie_b);
192 // CookieCreationTimeManager is returning creation times that are null.
193 // Since in CrNet, the cookie store is recreated on startup, let's suppress
194 // this warning for now.
195 // TODO(huey): Instead of suppressing the warning, assign a creation time
196 // to cookies if one doesn't already exist.
197 DLOG_IF(ERROR, created_a.is_null() || created_b.is_null())
198 << "Cookie without creation date";
200 if (created_a < created_b)
201 return NSOrderedAscending;
202 return (created_a > created_b) ? NSOrderedDescending : NSOrderedSame;
205 // Gets the cookies for |url| from the system cookie store.
206 NSArray* GetCookiesForURL(const GURL& url, CookieCreationTimeManager* manager) {
207 NSArray* cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage]
208 cookiesForURL:net::NSURLWithGURL(url)];
210 // Sort cookies by decreasing path length, then creation time, as per RFC6265.
211 return [cookies sortedArrayUsingFunction:CompareCookies context:manager];
214 // Builds a cookie line (such as "key1=value1; key2=value2") from an array of
216 std::string BuildCookieLine(NSArray* cookies,
217 const net::CookieOptions& options) {
218 // The exclude_httponly() option would only be used by a javascript engine.
219 DCHECK(!options.exclude_httponly());
221 // This utility function returns all the cookies, including the httponly ones.
222 // This is fine because we don't support the exclude_httponly option.
223 NSDictionary* header = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
224 return base::SysNSStringToUTF8([header valueForKey:@"Cookie"]);
227 // Tests whether the |cookie| is a session cookie.
228 bool IsCookieSessionCookie(NSHTTPCookie* cookie, base::Time time) {
229 return [cookie isSessionOnly];
232 // Tests whether the |creation_time| of |cookie| is in the time range defined
233 // by |time_begin| and |time_end|. A null |time_end| means end-of-time.
234 bool IsCookieCreatedBetween(base::Time time_begin,
236 NSHTTPCookie* cookie,
237 base::Time creation_time) {
238 return time_begin <= creation_time &&
239 (time_end.is_null() || creation_time <= time_end);
242 // Tests whether the |creation_time| of |cookie| is in the time range defined
243 // by |time_begin| and |time_end| and the cookie host match |host|. A null
244 // |time_end| means end-of-time.
245 bool IsCookieCreatedBetweenForHost(base::Time time_begin,
248 NSHTTPCookie* cookie,
249 base::Time creation_time) {
250 NSString* domain = [cookie domain];
251 return [domain characterAtIndex:0] != '.' &&
252 [domain caseInsensitiveCompare:host] == NSOrderedSame &&
253 IsCookieCreatedBetween(time_begin, time_end, cookie, creation_time);
256 // Adds cookies in |cookies| with name |name| to |filtered|.
257 void OnlyCookiesWithName(const net::CookieList& cookies,
258 const std::string& name,
259 net::CookieList* filtered) {
260 for (const auto& cookie : cookies) {
261 if (cookie.Name() == name)
262 filtered->push_back(cookie);
266 // Returns whether the specified cookie line has an explicit Domain attribute or
268 bool HasExplicitDomain(const std::string& cookie_line) {
269 ParsedCookie cookie(cookie_line);
270 return cookie.HasDomain();
276 #pragma mark CookieStoreIOS
278 CookieStoreIOS::CookieStoreIOS(
279 net::CookieMonster::PersistentCookieStore* persistent_store)
280 : creation_time_manager_(new CookieCreationTimeManager),
281 metrics_enabled_(false),
282 flush_delay_(base::TimeDelta::FromSeconds(10)),
283 synchronization_state_(NOT_SYNCHRONIZED),
284 cookie_cache_(new CookieCache()) {
285 NotificationTrampoline::GetInstance()->AddObserver(this);
286 cookie_monster_ = new net::CookieMonster(persistent_store, nullptr);
287 cookie_monster_->SetPersistSessionCookies(true);
288 cookie_monster_->SetForceKeepSessionState();
292 void CookieStoreIOS::SetCookiePolicy(CookiePolicy setting) {
293 NSHTTPCookieAcceptPolicy policy = (setting == ALLOW)
294 ? NSHTTPCookieAcceptPolicyAlways
295 : NSHTTPCookieAcceptPolicyNever;
296 NSHTTPCookieStorage* store = [NSHTTPCookieStorage sharedHTTPCookieStorage];
297 NSHTTPCookieAcceptPolicy current_policy = [store cookieAcceptPolicy];
298 if (current_policy == policy)
300 [store setCookieAcceptPolicy:policy];
301 NotificationTrampoline::GetInstance()->NotifyCookiePolicyChanged();
304 CookieStoreIOS* CookieStoreIOS::CreateCookieStoreFromNSHTTPCookieStorage() {
305 // TODO(huey): Update this when CrNet supports multiple cookie jars.
306 [[NSHTTPCookieStorage sharedHTTPCookieStorage]
307 setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
309 // Create a cookie store with no persistent store backing. Then, populate
310 // it from the system's cookie jar.
311 CookieStoreIOS* cookie_store = new CookieStoreIOS(nullptr);
312 cookie_store->synchronization_state_ = SYNCHRONIZED;
313 cookie_store->Flush(base::Closure());
318 void CookieStoreIOS::SwitchSynchronizedStore(CookieStoreIOS* old_store,
319 CookieStoreIOS* new_store) {
321 DCHECK_NE(new_store, old_store);
323 old_store->SetSynchronizedWithSystemStore(false);
324 new_store->SetSynchronizedWithSystemStore(true);
328 void CookieStoreIOS::NotifySystemCookiesChanged() {
329 NotificationTrampoline::GetInstance()->NotifyCookiesChanged();
332 void CookieStoreIOS::Flush(const base::Closure& closure) {
333 DCHECK(thread_checker_.CalledOnValidThread());
335 if (SystemCookiesAllowed()) {
336 // If cookies are disabled, the system store is empty, and the cookies are
337 // stashed on disk. Do not delete the cookies on the disk in this case.
338 WriteToCookieMonster(
339 [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]);
341 cookie_monster_->FlushStore(closure);
342 flush_closure_.Cancel();
345 void CookieStoreIOS::UnSynchronize() {
346 SetSynchronizedWithSystemStore(false);
349 void CookieStoreIOS::SetMetricsEnabled() {
350 static CookieStoreIOS* g_cookie_store_with_metrics = nullptr;
351 DCHECK(!g_cookie_store_with_metrics || g_cookie_store_with_metrics == this)
352 << "Only one cookie store may use metrics.";
353 g_cookie_store_with_metrics = this;
354 metrics_enabled_ = true;
358 #pragma mark CookieStore methods
360 void CookieStoreIOS::SetCookieWithOptionsAsync(
362 const std::string& cookie_line,
363 const net::CookieOptions& options,
364 const SetCookiesCallback& callback) {
365 DCHECK(thread_checker_.CalledOnValidThread());
367 switch (synchronization_state_) {
368 case NOT_SYNCHRONIZED:
369 cookie_monster_->SetCookieWithOptionsAsync(url, cookie_line, options,
370 WrapSetCallback(callback));
373 tasks_pending_synchronization_.push_back(
374 base::Bind(&CookieStoreIOS::SetCookieWithOptionsAsync, this, url,
375 cookie_line, options, WrapSetCallback(callback)));
378 // The exclude_httponly() option would only be used by a javascript
380 DCHECK(!options.exclude_httponly());
381 // If cookies are not allowed, they are stashed in the CookieMonster, and
382 // should be written there instead.
383 DCHECK(SystemCookiesAllowed());
385 base::Time server_time =
386 options.has_server_time() ? options.server_time() : base::Time();
387 NSHTTPCookie* cookie =
388 GetNSHTTPCookieFromCookieLine(cookie_line, url, server_time);
389 DLOG_IF(WARNING, !cookie)
390 << "Could not create cookie for line: " << cookie_line;
392 // On iOS, [cookie domain] is not empty when the cookie domain is not
393 // specified: it is inferred from the URL instead. The only case when it
394 // is empty is when the domain attribute is incorrectly formatted.
395 std::string domain_string(base::SysNSStringToUTF8([cookie domain]));
396 const std::string url_host(url.host());
398 bool has_explicit_domain = HasExplicitDomain(cookie_line);
399 bool has_valid_domain =
400 net::cookie_util::GetCookieDomainWithString(
401 url, domain_string, &dummy);
402 // A cookie can be set if all of:
403 // a) The cookie line is well-formed
404 // b) The Domain attribute, if present, was not malformed
405 // c) At least one of:
406 // 1) The cookie had no explicit Domain, so the Domain was inferred
408 // 2) The cookie had an explicit Domain for which the URL is allowed
410 bool success = (cookie != nil) && !domain_string.empty() &&
411 (!has_explicit_domain || has_valid_domain);
414 [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
415 creation_time_manager_->SetCreationTime(
417 creation_time_manager_->MakeUniqueCreationTime(base::Time::Now()));
420 if (!callback.is_null())
421 callback.Run(success);
426 void CookieStoreIOS::GetCookiesWithOptionsAsync(
428 const net::CookieOptions& options,
429 const GetCookiesCallback& callback) {
430 DCHECK(thread_checker_.CalledOnValidThread());
432 switch (synchronization_state_) {
433 case NOT_SYNCHRONIZED:
434 cookie_monster_->GetCookiesWithOptionsAsync(url, options, callback);
437 tasks_pending_synchronization_.push_back(
438 base::Bind(&CookieStoreIOS::GetCookiesWithOptionsAsync, this, url,
442 // If cookies are not allowed, they are stashed in the CookieMonster, and
443 // should be read from there instead.
444 DCHECK(SystemCookiesAllowed());
445 // The exclude_httponly() option would only be used by a javascript
447 DCHECK(!options.exclude_httponly());
449 NSArray* cookies = GetCookiesForURL(url, creation_time_manager_.get());
450 if (!callback.is_null())
451 callback.Run(BuildCookieLine(cookies, options));
456 void CookieStoreIOS::GetAllCookiesForURLAsync(
458 const GetCookieListCallback& callback) {
459 DCHECK(thread_checker_.CalledOnValidThread());
461 switch (synchronization_state_) {
462 case NOT_SYNCHRONIZED:
463 cookie_monster_->GetAllCookiesForURLAsync(url, callback);
466 tasks_pending_synchronization_.push_back(base::Bind(
467 &CookieStoreIOS::GetAllCookiesForURLAsync, this, url, callback));
470 if (!SystemCookiesAllowed()) {
471 // If cookies are not allowed, the cookies are stashed in the
472 // CookieMonster, so get them from there.
473 cookie_monster_->GetAllCookiesForURLAsync(url, callback);
477 NSArray* cookies = GetCookiesForURL(url, creation_time_manager_.get());
478 net::CookieList cookie_list;
479 cookie_list.reserve([cookies count]);
480 for (NSHTTPCookie* cookie in cookies) {
481 base::Time created = creation_time_manager_->GetCreationTime(cookie);
482 cookie_list.push_back(CanonicalCookieFromSystemCookie(cookie, created));
484 if (!callback.is_null())
485 callback.Run(cookie_list);
490 void CookieStoreIOS::DeleteCookieAsync(const GURL& url,
491 const std::string& cookie_name,
492 const base::Closure& callback) {
493 DCHECK(thread_checker_.CalledOnValidThread());
495 switch (synchronization_state_) {
496 case NOT_SYNCHRONIZED:
497 cookie_monster_->DeleteCookieAsync(url, cookie_name,
498 WrapClosure(callback));
501 tasks_pending_synchronization_.push_back(
502 base::Bind(&CookieStoreIOS::DeleteCookieAsync, this, url, cookie_name,
503 WrapClosure(callback)));
506 NSArray* cookies = GetCookiesForURL(url, creation_time_manager_.get());
507 for (NSHTTPCookie* cookie in cookies) {
509 isEqualToString:base::SysUTF8ToNSString(cookie_name)]) {
510 [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
511 creation_time_manager_->DeleteCreationTime(cookie);
514 if (!callback.is_null())
520 // CookieStoreIOS is an implementation of CookieStore which is not a
521 // CookieMonster. As CookieStore is the main cookie API, a caller of
522 // GetCookieMonster must handle the case where this returns null.
523 net::CookieMonster* CookieStoreIOS::GetCookieMonster() {
524 DCHECK(thread_checker_.CalledOnValidThread());
528 void CookieStoreIOS::DeleteAllCreatedBetweenAsync(
529 const base::Time& delete_begin,
530 const base::Time& delete_end,
531 const DeleteCallback& callback) {
532 DCHECK(thread_checker_.CalledOnValidThread());
534 if (metrics_enabled_)
535 ResetCookieCountMetrics();
537 switch (synchronization_state_) {
538 case NOT_SYNCHRONIZED:
539 cookie_monster_->DeleteAllCreatedBetweenAsync(
540 delete_begin, delete_end, WrapDeleteCallback(callback));
543 tasks_pending_synchronization_.push_back(
544 base::Bind(&CookieStoreIOS::DeleteAllCreatedBetweenAsync, this,
545 delete_begin, delete_end, WrapDeleteCallback(callback)));
548 CookieFilterFunction filter =
549 base::Bind(&IsCookieCreatedBetween, delete_begin, delete_end);
550 DeleteCookiesWithFilter(filter, callback);
555 void CookieStoreIOS::DeleteAllCreatedBetweenForHostAsync(
556 const base::Time delete_begin,
557 const base::Time delete_end,
559 const DeleteCallback& callback) {
560 DCHECK(thread_checker_.CalledOnValidThread());
562 if (metrics_enabled_)
563 ResetCookieCountMetrics();
565 switch (synchronization_state_) {
566 case NOT_SYNCHRONIZED:
567 cookie_monster_->DeleteAllCreatedBetweenForHostAsync(
568 delete_begin, delete_end, url, WrapDeleteCallback(callback));
571 tasks_pending_synchronization_.push_back(base::Bind(
572 &CookieStoreIOS::DeleteAllCreatedBetweenForHostAsync, this,
573 delete_begin, delete_end, url, WrapDeleteCallback(callback)));
576 NSString* host = base::SysUTF8ToNSString(url.host());
577 CookieFilterFunction filter = base::Bind(IsCookieCreatedBetweenForHost,
578 delete_begin, delete_end, host);
579 DeleteCookiesWithFilter(filter, callback);
584 void CookieStoreIOS::DeleteSessionCookiesAsync(const DeleteCallback& callback) {
585 DCHECK(thread_checker_.CalledOnValidThread());
587 if (metrics_enabled_)
588 ResetCookieCountMetrics();
590 switch (synchronization_state_) {
591 case NOT_SYNCHRONIZED:
592 cookie_monster_->DeleteSessionCookiesAsync(WrapDeleteCallback(callback));
595 tasks_pending_synchronization_.push_back(
596 base::Bind(&CookieStoreIOS::DeleteSessionCookiesAsync, this,
597 WrapDeleteCallback(callback)));
600 CookieFilterFunction filter = base::Bind(&IsCookieSessionCookie);
601 DeleteCookiesWithFilter(filter, callback);
607 #pragma mark Protected methods
609 CookieStoreIOS::~CookieStoreIOS() {
610 NotificationTrampoline::GetInstance()->RemoveObserver(this);
611 STLDeleteContainerPairSecondPointers(hook_map_.begin(), hook_map_.end());
615 #pragma mark Private methods
617 void CookieStoreIOS::ClearSystemStore() {
618 DCHECK(thread_checker_.CalledOnValidThread());
619 NSHTTPCookieStorage* cookie_storage =
620 [NSHTTPCookieStorage sharedHTTPCookieStorage];
621 base::scoped_nsobject<NSArray> copy(
622 [[NSArray alloc] initWithArray:[cookie_storage cookies]]);
623 for (NSHTTPCookie* cookie in copy.get())
624 [cookie_storage deleteCookie:cookie];
625 DCHECK_EQ(0u, [[cookie_storage cookies] count]);
626 creation_time_manager_->Clear();
629 void CookieStoreIOS::OnSystemCookiePolicyChanged() {
630 DCHECK(thread_checker_.CalledOnValidThread());
632 if (synchronization_state_ == NOT_SYNCHRONIZED)
635 NSHTTPCookieAcceptPolicy policy =
636 [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookieAcceptPolicy];
637 if (policy == NSHTTPCookieAcceptPolicyAlways) {
638 // If cookies are disabled, the system cookie store should be empty.
639 DCHECK(![[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies] count]);
640 DCHECK(synchronization_state_ != SYNCHRONIZING);
641 synchronization_state_ = SYNCHRONIZING;
642 cookie_monster_->GetAllCookiesAsync(
643 base::Bind(&CookieStoreIOS::AddCookiesToSystemStore, this));
645 DCHECK_EQ(NSHTTPCookieAcceptPolicyNever, policy);
646 // Flush() does not write the cookies to disk when they are disabled.
647 // Explicitly copy them.
648 WriteToCookieMonster(
649 [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]);
650 Flush(base::Closure());
652 if (synchronization_state_ == SYNCHRONIZING) {
653 // If synchronization was in progress, abort it and leave the cookie store
655 // Temporarily toggle the synchronization state so that pending tasks are
656 // redirected to cookie_monster_ and can complete normally.
657 synchronization_state_ = NOT_SYNCHRONIZED;
658 RunAllPendingTasks();
659 synchronization_state_ = SYNCHRONIZED;
664 void CookieStoreIOS::SetSynchronizedWithSystemStore(bool synchronized) {
665 DCHECK(thread_checker_.CalledOnValidThread());
667 if (synchronized == (synchronization_state_ != NOT_SYNCHRONIZED))
668 return; // The cookie store is already in the desired state.
672 DCHECK_EQ(this, g_current_synchronized_store)
673 << "This cookie store was not synchronized";
674 g_current_synchronized_store = nullptr;
676 DCHECK_EQ((CookieStoreIOS*)nullptr, g_current_synchronized_store)
677 << "Un-synchronize the current cookie store first.";
678 g_current_synchronized_store = this;
682 NSHTTPCookieAcceptPolicy policy =
683 [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookieAcceptPolicy];
684 DCHECK(policy == NSHTTPCookieAcceptPolicyAlways ||
685 policy == NSHTTPCookieAcceptPolicyNever);
687 // If cookies are disabled, the system cookie store should be empty.
688 DCHECK(policy == NSHTTPCookieAcceptPolicyAlways ||
689 ![[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies] count]);
691 // If cookies are disabled, nothing is done now, the work will be done when
692 // cookies are re-enabled.
693 if (policy == NSHTTPCookieAcceptPolicyAlways) {
695 synchronization_state_ = SYNCHRONIZING;
697 cookie_monster_->GetAllCookiesAsync(
698 base::Bind(&CookieStoreIOS::AddCookiesToSystemStore, this));
701 // Copy the cookies from the global store to |cookie_monster_|.
702 Flush(base::Closure());
705 synchronization_state_ = synchronized ? SYNCHRONIZED : NOT_SYNCHRONIZED;
707 if (synchronization_state_ == NOT_SYNCHRONIZED) {
708 // If there are pending tasks, then it means that the synchronization is
709 // being canceled. All pending tasks can be sent to cookie_monster_.
710 RunAllPendingTasks();
714 bool CookieStoreIOS::SystemCookiesAllowed() {
715 DCHECK(thread_checker_.CalledOnValidThread());
716 return [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookieAcceptPolicy] ==
717 NSHTTPCookieAcceptPolicyAlways;
720 void CookieStoreIOS::AddCookiesToSystemStore(const net::CookieList& cookies) {
721 DCHECK(thread_checker_.CalledOnValidThread());
722 if (!SystemCookiesAllowed() || synchronization_state_ != SYNCHRONIZING) {
723 // If synchronization was aborted, the pending tasks have been processed at
724 // that time. Now is too late.
725 DCHECK(tasks_pending_synchronization_.empty());
730 if (metrics_enabled_) {
731 size_t cookie_count = cookies.size();
732 UMA_HISTOGRAM_COUNTS_10000("CookieIOS.CookieReadCount", cookie_count);
733 CheckForCookieLoss(cookie_count, COOKIES_READ);
736 net::CookieList::const_iterator it;
737 for (it = cookies.begin(); it != cookies.end(); ++it) {
738 const net::CanonicalCookie& net_cookie = *it;
739 NSHTTPCookie* system_cookie = SystemCookieFromCanonicalCookie(net_cookie);
740 // Canonical cookie may not be convertable into system cookie if it contains
741 // invalid characters.
744 [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:system_cookie];
745 creation_time_manager_->SetCreationTime(system_cookie,
746 net_cookie.CreationDate());
749 synchronization_state_ = SYNCHRONIZED;
750 RunAllPendingTasks();
753 void CookieStoreIOS::WriteToCookieMonster(NSArray* system_cookies) {
754 DCHECK(thread_checker_.CalledOnValidThread());
755 if (synchronization_state_ != SYNCHRONIZED)
758 // Copy the cookies from the global cookie store to |cookie_monster_|.
759 // Unlike the system store, CookieMonster requires unique creation times.
760 net::CookieList cookie_list;
761 NSUInteger cookie_count = [system_cookies count];
762 cookie_list.reserve(cookie_count);
763 for (NSHTTPCookie* cookie in system_cookies) {
764 cookie_list.push_back(CanonicalCookieFromSystemCookie(
765 cookie, creation_time_manager_->GetCreationTime(cookie)));
767 cookie_monster_->SetAllCookiesAsync(cookie_list, SetCookiesCallback());
770 if (metrics_enabled_)
771 UMA_HISTOGRAM_COUNTS_10000("CookieIOS.CookieWrittenCount", cookie_count);
774 void CookieStoreIOS::RunAllPendingTasks() {
775 // Executing the tasks while synchronizing would not run the tasks, but merely
776 // re-enqueue them. This function also does not support mutation of the queue
777 // during the iteration.
778 DCHECK(synchronization_state_ != SYNCHRONIZING);
779 for (const auto& task : tasks_pending_synchronization_) {
782 tasks_pending_synchronization_.clear();
785 void CookieStoreIOS::DeleteCookiesWithFilter(const CookieFilterFunction& filter,
786 const DeleteCallback& callback) {
787 DCHECK(thread_checker_.CalledOnValidThread());
788 DCHECK_EQ(SYNCHRONIZED, synchronization_state_);
789 NSArray* cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
791 // Collect the cookies to delete.
792 base::scoped_nsobject<NSMutableArray> to_delete(
793 [[NSMutableArray alloc] init]);
794 for (NSHTTPCookie* cookie in cookies) {
795 base::Time creation_time = creation_time_manager_->GetCreationTime(cookie);
796 if (filter.Run(cookie, creation_time))
797 [to_delete addObject:cookie];
801 for (NSHTTPCookie* cookie in to_delete.get()) {
802 [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
803 creation_time_manager_->DeleteCreationTime(cookie);
806 if (!callback.is_null())
807 callback.Run([to_delete count]);
810 void CookieStoreIOS::OnSystemCookiesChanged() {
811 DCHECK(thread_checker_.CalledOnValidThread());
813 // If the CookieStoreIOS is not synchronized, system cookies are irrelevant.
814 if (synchronization_state_ != SYNCHRONIZED)
817 for (const auto& hook_map_entry : hook_map_) {
818 std::pair<GURL, std::string> key = hook_map_entry.first;
819 std::vector<net::CanonicalCookie> removed_cookies;
820 std::vector<net::CanonicalCookie> added_cookies;
821 if (UpdateCacheForCookieFromSystem(key.first, key.second, &removed_cookies,
823 RunCallbacksForCookies(key.first, key.second, removed_cookies, true);
824 RunCallbacksForCookies(key.first, key.second, added_cookies, false);
828 // Do not schedule a flush if one is already scheduled.
829 if (!flush_closure_.IsCancelled())
832 flush_closure_.Reset(base::Bind(&CookieStoreIOS::Flush,
833 base::Unretained(this), base::Closure()));
834 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
835 FROM_HERE, flush_closure_.callback(), flush_delay_);
838 scoped_ptr<net::CookieStore::CookieChangedSubscription>
839 CookieStoreIOS::AddCallbackForCookie(const GURL& gurl,
840 const std::string& name,
841 const CookieChangedCallback& callback) {
842 DCHECK(thread_checker_.CalledOnValidThread());
844 // Prefill cookie cache with all pertinent cookies for |url| if needed.
845 std::pair<GURL, std::string> key(gurl, name);
846 if (hook_map_.count(key) == 0) {
847 UpdateCacheForCookieFromSystem(gurl, name, nullptr, nullptr);
848 if (hook_map_.count(key) == 0)
849 hook_map_[key] = new CookieChangedCallbackList;
852 DCHECK(hook_map_.find(key) != hook_map_.end());
853 return hook_map_[key]->Add(callback);
856 bool CookieStoreIOS::UpdateCacheForCookieFromSystem(
858 const std::string& name,
859 std::vector<net::CanonicalCookie>* out_removed_cookies,
860 std::vector<net::CanonicalCookie>* out_added_cookies) {
861 DCHECK(thread_checker_.CalledOnValidThread());
862 std::vector<net::CanonicalCookie> system_cookies;
863 GetSystemCookies(gurl, name, &system_cookies);
864 return cookie_cache_->Update(gurl, name, system_cookies, out_removed_cookies,
868 void CookieStoreIOS::RunCallbacksForCookies(
870 const std::string& name,
871 const std::vector<net::CanonicalCookie>& cookies,
873 DCHECK(thread_checker_.CalledOnValidThread());
877 std::pair<GURL, std::string> key(url, name);
878 CookieChangedCallbackList* callbacks = hook_map_[key];
879 for (const auto& cookie : cookies) {
880 DCHECK_EQ(name, cookie.Name());
881 callbacks->Notify(cookie, removed);
885 bool CookieStoreIOS::GetSystemCookies(
887 const std::string& name,
888 std::vector<net::CanonicalCookie>* cookies) {
889 DCHECK(thread_checker_.CalledOnValidThread());
890 NSURL* url = net::NSURLWithGURL(gurl);
891 NSHTTPCookieStorage* storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
892 NSArray* nscookies = [storage cookiesForURL:url];
893 bool found_cookies = false;
894 for (NSHTTPCookie* nscookie in nscookies) {
895 if (nscookie.name.UTF8String == name) {
896 net::CanonicalCookie canonical_cookie = CanonicalCookieFromSystemCookie(
897 nscookie, creation_time_manager_->GetCreationTime(nscookie));
898 cookies->push_back(canonical_cookie);
899 found_cookies = true;
902 return found_cookies;
905 void CookieStoreIOS::GotCookieListFor(const std::pair<GURL, std::string> key,
906 const net::CookieList& cookies) {
907 DCHECK(thread_checker_.CalledOnValidThread());
909 net::CookieList filtered;
910 OnlyCookiesWithName(cookies, key.second, &filtered);
911 std::vector<net::CanonicalCookie> removed_cookies;
912 std::vector<net::CanonicalCookie> added_cookies;
913 if (cookie_cache_->Update(key.first, key.second, filtered, &removed_cookies,
915 RunCallbacksForCookies(key.first, key.second, removed_cookies, true);
916 RunCallbacksForCookies(key.first, key.second, added_cookies, false);
920 void CookieStoreIOS::DidClearNSHTTPCookieStorageCookies(
921 const DeleteCallback& delete_callback,
923 DCHECK(thread_checker_.CalledOnValidThread());
925 CookieStoreIOSClient* client = net::GetCookieStoreIOSClient();
927 auto sequenced_task_runner = client->GetTaskRunner();
928 DCHECK(sequenced_task_runner);
929 auto callback = base::Bind(&CookieStoreIOS::DidClearBinaryCookiesFileCookies,
930 this, delete_callback, num_deleted);
931 sequenced_task_runner.get()->PostTaskAndReply(
932 FROM_HERE, base::Bind(&ClearAllCookiesFromBinaryCookiesFile), callback);
935 void CookieStoreIOS::DidClearBinaryCookiesFileCookies(
936 const DeleteCallback& callback,
937 int num_deleted_from_nshttp_cookie_storage) {
938 DCHECK(thread_checker_.CalledOnValidThread());
940 CookieStoreIOSClient* client = net::GetCookieStoreIOSClient();
942 client->DidChangeCookieStorage();
943 callback.Run(num_deleted_from_nshttp_cookie_storage);
946 void CookieStoreIOS::UpdateCachesFromCookieMonster() {
947 DCHECK(thread_checker_.CalledOnValidThread());
948 for (const auto& hook_map_entry : hook_map_) {
949 std::pair<GURL, std::string> key = hook_map_entry.first;
950 GetCookieListCallback callback =
951 base::Bind(&CookieStoreIOS::GotCookieListFor, this, key);
952 cookie_monster_->GetAllCookiesForURLAsync(key.first, callback);
956 void CookieStoreIOS::UpdateCachesAfterSet(const SetCookiesCallback& callback,
958 DCHECK(thread_checker_.CalledOnValidThread());
960 UpdateCachesFromCookieMonster();
961 callback.Run(success);
964 void CookieStoreIOS::UpdateCachesAfterDelete(const DeleteCallback& callback,
966 DCHECK(thread_checker_.CalledOnValidThread());
967 UpdateCachesFromCookieMonster();
968 callback.Run(num_deleted);
971 void CookieStoreIOS::UpdateCachesAfterClosure(const base::Closure& callback) {
972 DCHECK(thread_checker_.CalledOnValidThread());
973 UpdateCachesFromCookieMonster();
977 CookieStoreIOS::SetCookiesCallback CookieStoreIOS::WrapSetCallback(
978 const SetCookiesCallback& callback) {
979 DCHECK(thread_checker_.CalledOnValidThread());
980 return base::Bind(&CookieStoreIOS::UpdateCachesAfterSet, this, callback);
983 CookieStoreIOS::DeleteCallback CookieStoreIOS::WrapDeleteCallback(
984 const DeleteCallback& callback) {
985 DCHECK(thread_checker_.CalledOnValidThread());
986 return base::Bind(&CookieStoreIOS::UpdateCachesAfterDelete, this, callback);
989 base::Closure CookieStoreIOS::WrapClosure(const base::Closure& callback) {
990 DCHECK(thread_checker_.CalledOnValidThread());
991 return base::Bind(&CookieStoreIOS::UpdateCachesAfterClosure, this, callback);