Only grant permissions to new extensions from sync if they have the expected version
[chromium-blink-merge.git] / ios / net / cookies / cookie_store_ios.mm
blobbc5725141f2bb4fedbe20fe0a012d31d5f329480
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>
9 #include "base/bind.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"
32 #include "url/gurl.h"
34 namespace net {
36 namespace {
38 #if !defined(NDEBUG)
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
41 // cookie store.
42 CookieStoreIOS* g_current_synchronized_store = nullptr;
43 #endif
45 #pragma mark NotificationTrampoline
47 // NotificationTrampoline dispatches cookie notifications to all the existing
48 // CookieStoreIOS.
49 class NotificationTrampoline {
50  public:
51   static NotificationTrampoline* GetInstance();
53   void AddObserver(CookieNotificationObserver* obs);
54   void RemoveObserver(CookieNotificationObserver* obs);
56   // Notify the observers.
57   void NotifyCookiesChanged();
58   void NotifyCookiePolicyChanged();
60  private:
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 =
105     nullptr;
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()) {
125     return;
126   }
127   base::FilePath path = GetBinaryCookiesFilePath();
128   if (base::PathExists(path)) {
129     bool success = base::DeleteFile(path, false);
130     if (!success) {
131       DLOG(WARNING) << "Failed to remove binarycookies file.";
132     }
133   }
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
140 // URL.
141 NSHTTPCookie* GetNSHTTPCookieFromCookieLine(const std::string& cookie_line,
142                                             const GURL& url,
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;
148     return nil;
149   }
150   NSArray* cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:@{
151     @"Set-Cookie" : ns_cookie_line
152   } forURL:nsurl];
153   if ([cookies count] != 1)
154     return nil;
156   NSHTTPCookie* cookie = [cookies objectAtIndex:0];
157   if (![cookie expiresDate] || server_time.is_null())
158     return cookie;
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
174 // RFC6265.
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;
188   DCHECK(manager);
189   base::Time created_a = manager->GetCreationTime(cookie_a);
190   base::Time created_b = manager->GetCreationTime(cookie_b);
191 #if !defined(CRNET)
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";
199 #endif
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
215 // cookies.
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,
235                             base::Time time_end,
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,
246                                    base::Time time_end,
247                                    NSString* host,
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);
263   }
266 // Returns whether the specified cookie line has an explicit Domain attribute or
267 // not.
268 bool HasExplicitDomain(const std::string& cookie_line) {
269   ParsedCookie cookie(cookie_line);
270   return cookie.HasDomain();
273 }  // namespace
275 #pragma mark -
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();
291 // static
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)
299     return;
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());
314   return cookie_store;
317 // static
318 void CookieStoreIOS::SwitchSynchronizedStore(CookieStoreIOS* old_store,
319                                              CookieStoreIOS* new_store) {
320   DCHECK(new_store);
321   DCHECK_NE(new_store, old_store);
322   if (old_store)
323     old_store->SetSynchronizedWithSystemStore(false);
324   new_store->SetSynchronizedWithSystemStore(true);
327 // static
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]);
340   }
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;
357 #pragma mark -
358 #pragma mark CookieStore methods
360 void CookieStoreIOS::SetCookieWithOptionsAsync(
361     const GURL& url,
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));
371       break;
372     case SYNCHRONIZING:
373       tasks_pending_synchronization_.push_back(
374           base::Bind(&CookieStoreIOS::SetCookieWithOptionsAsync, this, url,
375                      cookie_line, options, WrapSetCallback(callback)));
376       break;
377     case SYNCHRONIZED:
378       // The exclude_httponly() option would only be used by a javascript
379       // engine.
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());
397       std::string dummy;
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
407       //          from the URL, or
408       //       2) The cookie had an explicit Domain for which the URL is allowed
409       //          to set cookies.
410       bool success = (cookie != nil) && !domain_string.empty() &&
411                      (!has_explicit_domain || has_valid_domain);
413       if (success) {
414         [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
415         creation_time_manager_->SetCreationTime(
416             cookie,
417             creation_time_manager_->MakeUniqueCreationTime(base::Time::Now()));
418       }
420       if (!callback.is_null())
421         callback.Run(success);
422       break;
423   }
426 void CookieStoreIOS::GetCookiesWithOptionsAsync(
427     const GURL& url,
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);
435       break;
436     case SYNCHRONIZING:
437       tasks_pending_synchronization_.push_back(
438           base::Bind(&CookieStoreIOS::GetCookiesWithOptionsAsync, this, url,
439                      options, callback));
440       break;
441     case SYNCHRONIZED:
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
446       // engine.
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));
452       break;
453   }
456 void CookieStoreIOS::GetAllCookiesForURLAsync(
457     const GURL& url,
458     const GetCookieListCallback& callback) {
459   DCHECK(thread_checker_.CalledOnValidThread());
461   switch (synchronization_state_) {
462     case NOT_SYNCHRONIZED:
463       cookie_monster_->GetAllCookiesForURLAsync(url, callback);
464       break;
465     case SYNCHRONIZING:
466       tasks_pending_synchronization_.push_back(base::Bind(
467           &CookieStoreIOS::GetAllCookiesForURLAsync, this, url, callback));
468       break;
469     case SYNCHRONIZED:
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);
474         return;
475       }
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));
483       }
484       if (!callback.is_null())
485         callback.Run(cookie_list);
486       break;
487   }
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));
499       break;
500     case SYNCHRONIZING:
501       tasks_pending_synchronization_.push_back(
502           base::Bind(&CookieStoreIOS::DeleteCookieAsync, this, url, cookie_name,
503                      WrapClosure(callback)));
504       break;
505     case SYNCHRONIZED:
506       NSArray* cookies = GetCookiesForURL(url, creation_time_manager_.get());
507       for (NSHTTPCookie* cookie in cookies) {
508         if ([[cookie name]
509                 isEqualToString:base::SysUTF8ToNSString(cookie_name)]) {
510           [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
511           creation_time_manager_->DeleteCreationTime(cookie);
512         }
513       }
514       if (!callback.is_null())
515         callback.Run();
516       break;
517   }
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());
525   return nullptr;
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));
541       break;
542     case SYNCHRONIZING:
543       tasks_pending_synchronization_.push_back(
544           base::Bind(&CookieStoreIOS::DeleteAllCreatedBetweenAsync, this,
545                      delete_begin, delete_end, WrapDeleteCallback(callback)));
546       break;
547     case SYNCHRONIZED:
548       CookieFilterFunction filter =
549           base::Bind(&IsCookieCreatedBetween, delete_begin, delete_end);
550       DeleteCookiesWithFilter(filter, callback);
551       break;
552   }
555 void CookieStoreIOS::DeleteAllCreatedBetweenForHostAsync(
556     const base::Time delete_begin,
557     const base::Time delete_end,
558     const GURL& url,
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));
569       break;
570     case SYNCHRONIZING:
571       tasks_pending_synchronization_.push_back(base::Bind(
572           &CookieStoreIOS::DeleteAllCreatedBetweenForHostAsync, this,
573           delete_begin, delete_end, url, WrapDeleteCallback(callback)));
574       break;
575     case SYNCHRONIZED:
576       NSString* host = base::SysUTF8ToNSString(url.host());
577       CookieFilterFunction filter = base::Bind(IsCookieCreatedBetweenForHost,
578                                                delete_begin, delete_end, host);
579       DeleteCookiesWithFilter(filter, callback);
580       break;
581   }
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));
593       break;
594     case SYNCHRONIZING:
595       tasks_pending_synchronization_.push_back(
596           base::Bind(&CookieStoreIOS::DeleteSessionCookiesAsync, this,
597                      WrapDeleteCallback(callback)));
598       break;
599     case SYNCHRONIZED:
600       CookieFilterFunction filter = base::Bind(&IsCookieSessionCookie);
601       DeleteCookiesWithFilter(filter, callback);
602       break;
603   }
606 #pragma mark -
607 #pragma mark Protected methods
609 CookieStoreIOS::~CookieStoreIOS() {
610   NotificationTrampoline::GetInstance()->RemoveObserver(this);
611   STLDeleteContainerPairSecondPointers(hook_map_.begin(), hook_map_.end());
614 #pragma mark -
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)
633     return;
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));
644   } else {
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());
651     ClearSystemStore();
652     if (synchronization_state_ == SYNCHRONIZING) {
653       // If synchronization was in progress, abort it and leave the cookie store
654       // empty.
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;
660     }
661   }
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.
670 #if !defined(NDEBUG)
671   if (!synchronized) {
672     DCHECK_EQ(this, g_current_synchronized_store)
673         << "This cookie store was not synchronized";
674     g_current_synchronized_store = nullptr;
675   } else {
676     DCHECK_EQ((CookieStoreIOS*)nullptr, g_current_synchronized_store)
677         << "Un-synchronize the current cookie store first.";
678     g_current_synchronized_store = this;
679   }
680 #endif
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) {
694     if (synchronized) {
695       synchronization_state_ = SYNCHRONIZING;
696       ClearSystemStore();
697       cookie_monster_->GetAllCookiesAsync(
698           base::Bind(&CookieStoreIOS::AddCookiesToSystemStore, this));
699       return;
700     } else {
701       // Copy the cookies from the global store to |cookie_monster_|.
702       Flush(base::Closure());
703     }
704   }
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();
711   }
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());
726     return;
727   }
729   // Report metrics.
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);
734   }
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.
742     if (!system_cookie)
743       continue;
744     [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:system_cookie];
745     creation_time_manager_->SetCreationTime(system_cookie,
746                                             net_cookie.CreationDate());
747   }
749   synchronization_state_ = SYNCHRONIZED;
750   RunAllPendingTasks();
753 void CookieStoreIOS::WriteToCookieMonster(NSArray* system_cookies) {
754   DCHECK(thread_checker_.CalledOnValidThread());
755   if (synchronization_state_ != SYNCHRONIZED)
756     return;
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)));
766   }
767   cookie_monster_->SetAllCookiesAsync(cookie_list, SetCookiesCallback());
769   // Update metrics.
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_) {
780     task.Run();
781   }
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];
798   }
800   // Delete them.
801   for (NSHTTPCookie* cookie in to_delete.get()) {
802     [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
803     creation_time_manager_->DeleteCreationTime(cookie);
804   }
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)
815     return;
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,
822                                        &added_cookies)) {
823       RunCallbacksForCookies(key.first, key.second, removed_cookies, true);
824       RunCallbacksForCookies(key.first, key.second, added_cookies, false);
825     }
826   }
828   // Do not schedule a flush if one is already scheduled.
829   if (!flush_closure_.IsCancelled())
830     return;
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;
850   }
852   DCHECK(hook_map_.find(key) != hook_map_.end());
853   return hook_map_[key]->Add(callback);
856 bool CookieStoreIOS::UpdateCacheForCookieFromSystem(
857     const GURL& gurl,
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,
865                                out_added_cookies);
868 void CookieStoreIOS::RunCallbacksForCookies(
869     const GURL& url,
870     const std::string& name,
871     const std::vector<net::CanonicalCookie>& cookies,
872     bool removed) {
873   DCHECK(thread_checker_.CalledOnValidThread());
874   if (cookies.empty())
875     return;
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);
882   }
885 bool CookieStoreIOS::GetSystemCookies(
886     const GURL& gurl,
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;
900     }
901   }
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,
914                             &added_cookies)) {
915     RunCallbacksForCookies(key.first, key.second, removed_cookies, true);
916     RunCallbacksForCookies(key.first, key.second, added_cookies, false);
917   }
920 void CookieStoreIOS::DidClearNSHTTPCookieStorageCookies(
921     const DeleteCallback& delete_callback,
922     int num_deleted) {
923   DCHECK(thread_checker_.CalledOnValidThread());
925   CookieStoreIOSClient* client = net::GetCookieStoreIOSClient();
926   DCHECK(client);
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();
941   DCHECK(client);
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);
953   }
956 void CookieStoreIOS::UpdateCachesAfterSet(const SetCookiesCallback& callback,
957                                           bool success) {
958   DCHECK(thread_checker_.CalledOnValidThread());
959   if (success)
960     UpdateCachesFromCookieMonster();
961   callback.Run(success);
964 void CookieStoreIOS::UpdateCachesAfterDelete(const DeleteCallback& callback,
965                                              int num_deleted) {
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();
974   callback.Run();
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);
994 }  // namespace net