summaryrefslogtreecommitdiffstats
path: root/ios
diff options
context:
space:
mode:
authordroger <droger@chromium.org>2015-03-18 08:29:53 -0700
committerCommit bot <commit-bot@chromium.org>2015-03-18 15:30:49 +0000
commit3e8d98b6e21fa696a4cb85a59cb0023a83f1e991 (patch)
tree3130004069c1ff9f489a901397c26a69431a8f94 /ios
parent23445b31b84396a79d77c16fe700aedaf393d9f6 (diff)
downloadchromium_src-3e8d98b6e21fa696a4cb85a59cb0023a83f1e991.zip
chromium_src-3e8d98b6e21fa696a4cb85a59cb0023a83f1e991.tar.gz
chromium_src-3e8d98b6e21fa696a4cb85a59cb0023a83f1e991.tar.bz2
[iOS] Upstream CookieStoreIOS
Review URL: https://codereview.chromium.org/1021463002 Cr-Commit-Position: refs/heads/master@{#321138}
Diffstat (limited to 'ios')
-rw-r--r--ios/net/cookies/cookie_store_ios.h298
-rw-r--r--ios/net/cookies/cookie_store_ios.mm961
-rw-r--r--ios/net/cookies/cookie_store_ios_unittest.mm912
-rw-r--r--ios/net/ios_net.gyp2
-rw-r--r--ios/net/ios_net_unittests.gyp1
5 files changed, 2174 insertions, 0 deletions
diff --git a/ios/net/cookies/cookie_store_ios.h b/ios/net/cookies/cookie_store_ios.h
new file mode 100644
index 0000000..d44123d
--- /dev/null
+++ b/ios/net/cookies/cookie_store_ios.h
@@ -0,0 +1,298 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_NET_COOKIES_COOKIE_STORE_IOS_H_
+#define IOS_NET_COOKIES_COOKIE_STORE_IOS_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/cancelable_callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "ios/net/cookies/cookie_cache.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/cookies/cookie_store.h"
+#include "url/gurl.h"
+
+#if defined(__OBJC__)
+@class NSHTTPCookie;
+@class NSArray;
+#else
+class NSHTTPCookie;
+class NSArray;
+#endif
+
+namespace net {
+
+class CookieCreationTimeManager;
+
+// Observer for system cookie notifications.
+class CookieNotificationObserver {
+ public:
+ // Called when any cookie is added, deleted or changed in the system store.
+ virtual void OnSystemCookiesChanged() = 0;
+ // Called when the cookie policy changes.
+ virtual void OnSystemCookiePolicyChanged() = 0;
+};
+
+// The CookieStoreIOS is an implementation of CookieStore relying on
+// NSHTTPCookieStorage, ensuring that the cookies are consistent between the
+// network stack and the UIWebViews.
+// On iOS, the Chrome CookieMonster is not used in conjunction with UIWebView,
+// because UIWebView expects the cookies to be in the shared
+// NSHTTPCookieStorage. In particular, javascript may read and write cookies
+// there.
+// CookieStoreIOS is not thread safe.
+//
+// At any given time, a CookieStoreIOS can either be synchronized with the
+// system cookie store or not. If a CookieStoreIOS is not synchronized with the
+// system store, changes are written back to the backing CookieStore. If a
+// CookieStoreIOS is synchronized with the system store, changes are written
+// directly to the system cookie store, then propagated to the backing store by
+// OnSystemCookiesChanged, which is called by the system store once the change
+// to the system store is written back.
+//
+// To unsynchronize, CookieStoreIOS copies the system cookie store into its
+// backing CookieStore. To synchronize, CookieStoreIOS clears the system cookie
+// store, copies its backing CookieStore into the system cookie store.
+class CookieStoreIOS : public net::CookieStore,
+ public CookieNotificationObserver {
+ public:
+ explicit CookieStoreIOS(
+ net::CookieMonster::PersistentCookieStore* persistent_store);
+
+ enum CookiePolicy { ALLOW, BLOCK };
+
+ // Must be called on the thread where CookieStoreIOS instances live.
+ static void SetCookiePolicy(CookiePolicy setting);
+
+ // Create an instance of CookieStoreIOS that is generated from the cookies
+ // stored in the system NSHTTPCookieStorage. The caller is responsible for
+ // deleting the cookie store.
+ // Apple does not persist the cookies' creation dates in the system store,
+ // so callers should not expect these values to be populated.
+ static CookieStoreIOS* CreateCookieStoreFromNSHTTPCookieStorage();
+
+ // As there is only one system store, only one CookieStoreIOS at a time may
+ // be synchronized with it.
+ static void SwitchSynchronizedStore(CookieStoreIOS* old_store,
+ CookieStoreIOS* new_store);
+
+ // Must be called when the state of the system cookie store changes.
+ static void NotifySystemCookiesChanged();
+
+ // Saves the cookies to the cookie monster.
+ // Note: ignores the write cookie operation if |write_on_flush_| is false.
+ void Flush(const base::Closure& callback);
+
+ // Unsynchronizes the cookie store if it is currently synchronized.
+ void UnSynchronize();
+
+ // Only one cookie store may enable metrics.
+ void SetMetricsEnabled();
+
+ // Sets the delay between flushes. Only used in tests.
+ void set_flush_delay_for_testing(base::TimeDelta delay) {
+ flush_delay_ = delay;
+ }
+
+ // Inherited CookieStore methods.
+ void SetCookieWithOptionsAsync(const GURL& url,
+ const std::string& cookie_line,
+ const net::CookieOptions& options,
+ const SetCookiesCallback& callback) override;
+ void GetCookiesWithOptionsAsync(const GURL& url,
+ const net::CookieOptions& options,
+ const GetCookiesCallback& callback) override;
+ void GetAllCookiesForURLAsync(const GURL& url,
+ const GetCookieListCallback& callback) override;
+ void DeleteCookieAsync(const GURL& url,
+ const std::string& cookie_name,
+ const base::Closure& callback) override;
+ net::CookieMonster* GetCookieMonster() override;
+ void DeleteAllCreatedBetweenAsync(const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const DeleteCallback& callback) override;
+ void DeleteAllCreatedBetweenForHostAsync(
+ const base::Time delete_begin,
+ const base::Time delete_end,
+ const GURL& url,
+ const DeleteCallback& callback) override;
+ void DeleteSessionCookiesAsync(const DeleteCallback& callback) override;
+
+ scoped_ptr<CookieChangedSubscription> AddCallbackForCookie(
+ const GURL& url,
+ const std::string& name,
+ const CookieChangedCallback& callback) override;
+
+ protected:
+ ~CookieStoreIOS() override;
+
+ private:
+ // For tests.
+ friend struct CookieStoreIOSTestTraits;
+
+ enum SynchronizationState {
+ NOT_SYNCHRONIZED, // Uses CookieMonster as backend.
+ SYNCHRONIZING, // Moves from NSHTTPCookieStorage to CookieMonster.
+ SYNCHRONIZED // Uses NSHTTPCookieStorage as backend.
+ };
+
+ // Cookie fliter for DeleteCookiesWithFilter().
+ // Takes a cookie and a creation time and returns true if the cookie must be
+ // deleted.
+ typedef base::Callback<bool(NSHTTPCookie*, base::Time)> CookieFilterFunction;
+
+ // Clears the system cookie store.
+ void ClearSystemStore();
+ // Changes the synchronization of the store.
+ // If |synchronized| is true, then the system cookie store is used as a
+ // backend, else |cookie_monster_| is used. Cookies are moved from one to
+ // the other accordingly.
+ void SetSynchronizedWithSystemStore(bool synchronized);
+ // Returns true if the system cookie store policy is
+ // |NSHTTPCookieAcceptPolicyAlways|.
+ bool SystemCookiesAllowed();
+ // Converts |cookies| to NSHTTPCookie and add them to the system store.
+ void AddCookiesToSystemStore(const net::CookieList& cookies);
+ // Copies the cookies to the backing CookieMonster. If the cookie store is not
+ // synchronized with the system store, this is a no-op.
+ void WriteToCookieMonster(NSArray* system_cookies);
+
+ // Inherited CookieNotificationObserver methods.
+ void OnSystemCookiesChanged() override;
+ void OnSystemCookiePolicyChanged() override;
+
+ void DeleteCookiesWithFilter(const CookieFilterFunction& filter,
+ const DeleteCallback& callback);
+
+ scoped_refptr<net::CookieMonster> cookie_monster_;
+ scoped_ptr<CookieCreationTimeManager> creation_time_manager_;
+ bool metrics_enabled_;
+ base::TimeDelta flush_delay_;
+ base::CancelableClosure flush_closure_;
+
+ SynchronizationState synchronization_state_;
+ // Tasks received when SYNCHRONIZING are queued and run when SYNCHRONIZED.
+ std::vector<base::Closure> tasks_pending_synchronization_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Cookie notification methods.
+ // The cookie cache is updated from both the system store and the
+ // CookieStoreIOS' own mutators. Changes when the CookieStoreIOS is
+ // synchronized are signalled by the system store; changes when the
+ // CookieStoreIOS is not synchronized are signalled by the appropriate
+ // mutators on CookieStoreIOS. The cookie cache tracks the system store when
+ // the CookieStoreIOS is synchronized and the CookieStore when the
+ // CookieStoreIOS is not synchronized.
+
+ // Fetches any cookies named |name| that would be sent with a request for
+ // |url| from the system cookie store and pushes them onto the back of the
+ // vector pointed to by |cookies|. Returns true if any cookies were pushed
+ // onto the vector, and false otherwise.
+ bool GetSystemCookies(const GURL& url,
+ const std::string& name,
+ std::vector<net::CanonicalCookie>* cookies);
+
+ // Updates the cookie cache with the current set of system cookies named
+ // |name| that would be sent with a request for |url|. Returns whether the
+ // cache changed.
+ // |out_removed_cookies|, if not null, will be populated with the cookies that
+ // were removed.
+ // |out_changed_cookies|, if not null, will be populated with the cookies that
+ // were added.
+ bool UpdateCacheForCookieFromSystem(
+ const GURL& gurl,
+ const std::string& name,
+ std::vector<net::CanonicalCookie>* out_removed_cookies,
+ std::vector<net::CanonicalCookie>* out_added_cookies);
+
+ // Runs all callbacks registered for cookies named |name| that would be sent
+ // with a request for |url|.
+ // All cookies in |cookies| must have the name equal to |name|.
+ void RunCallbacksForCookies(const GURL& url,
+ const std::string& name,
+ const std::vector<net::CanonicalCookie>& cookies,
+ bool removed);
+
+ // Called by this CookieStoreIOS' internal CookieMonster instance when
+ // GetAllCookiesForURLAsync() completes. Updates the cookie cache and runs
+ // callbacks if the cache changed.
+ void GotCookieListFor(const std::pair<GURL, std::string> key,
+ const net::CookieList& cookies);
+
+ // Fetches new values for all (url, name) pairs that have hooks registered,
+ // asynchronously invoking callbacks if necessary.
+ void UpdateCachesFromCookieMonster();
+
+ // Called after cookies are cleared from NSHTTPCookieStorage so that cookies
+ // can be cleared from .binarycookies file. |callback| is called after all the
+ // cookies are deleted (with the total number of cookies deleted).
+ // |num_deleted| contains the number of cookies deleted from
+ // NSHTTPCookieStorage.
+ void DidClearNSHTTPCookieStorageCookies(const DeleteCallback& callback,
+ int num_deleted);
+ // Called after cookies are cleared from .binarycookies files. |callback| is
+ // called after all the cookies are deleted with the total number of cookies
+ // deleted.
+ // |num_deleted_from_nshttp_cookie_storage| contains the number of cookies
+ // deleted from NSHTTPCookieStorage.
+ void DidClearBinaryCookiesFileCookies(
+ const DeleteCallback& callback,
+ int num_deleted_from_nshttp_cookie_storage);
+
+ // Callback-wrapping:
+ // When this CookieStoreIOS object is synchronized with the system store,
+ // OnSystemCookiesChanged is responsible for updating the cookie cache (and
+ // hence running callbacks).
+ //
+ // When this CookieStoreIOS object is not synchronized (or is synchronizing),
+ // the various mutator methods (SetCookieWithOptionsAsync &c) instead store
+ // their state in a CookieMonster object to be written back when the system
+ // store synchronizes. To deliver notifications in a timely manner, the
+ // mutators have to ensure that hooks get run, but only after the changes have
+ // been written back to CookieMonster. To do this, the mutators wrap the
+ // user-supplied callback in a callback which schedules an asynchronous task
+ // to synchronize the cache and run callbacks, then calls through to the
+ // user-specified callback.
+ //
+ // These three UpdateCachesAfter functions are responsible for scheduling an
+ // asynchronous cache update (using UpdateCachesFromCookieMonster()) and
+ // calling the provided callback.
+
+ void UpdateCachesAfterSet(const SetCookiesCallback& callback, bool success);
+ void UpdateCachesAfterDelete(const DeleteCallback& callback, int num_deleted);
+ void UpdateCachesAfterClosure(const base::Closure& callback);
+
+ // These three functions are used for wrapping user-supplied callbacks given
+ // to CookieStoreIOS mutator methods. Given a callback, they return a new
+ // callback that invokes UpdateCachesFromCookieMonster() to schedule an
+ // asynchronous synchronization of the cookie cache and then calls the
+ // original callback.
+
+ SetCookiesCallback WrapSetCallback(const SetCookiesCallback& callback);
+ DeleteCallback WrapDeleteCallback(const DeleteCallback& callback);
+ base::Closure WrapClosure(const base::Closure& callback);
+
+ // Cached values of system cookies. Only cookies which have an observer added
+ // with AddCallbackForCookie are kept in this cache.
+ scoped_ptr<CookieCache> cookie_cache_;
+
+ // Callbacks for cookie changes installed by AddCallbackForCookie.
+ typedef std::map<std::pair<GURL, std::string>, CookieChangedCallbackList*>
+ CookieChangedHookMap;
+ CookieChangedHookMap hook_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(CookieStoreIOS);
+};
+
+} // namespace net
+
+#endif // IOS_NET_COOKIES_COOKIE_STORE_IOS_H_
diff --git a/ios/net/cookies/cookie_store_ios.mm b/ios/net/cookies/cookie_store_ios.mm
new file mode 100644
index 0000000..4ccf8b7
--- /dev/null
+++ b/ios/net/cookies/cookie_store_ios.mm
@@ -0,0 +1,961 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ios/net/cookies/cookie_store_ios.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/ios/ios_util.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "base/observer_list.h"
+#include "base/sequenced_task_runner.h"
+#include "base/stl_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/task_runner_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "ios/net/cookies/cookie_creation_time_manager.h"
+#include "ios/net/cookies/cookie_store_ios_client.h"
+#include "ios/net/cookies/system_cookie_util.h"
+#import "net/base/mac/url_conversions.h"
+#include "net/cookies/cookie_util.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+#if !defined(NDEBUG)
+// The current cookie store. This weak pointer must not be used to do actual
+// work. Its only purpose is to check that there is only one synchronized
+// cookie store.
+CookieStoreIOS* g_current_synchronized_store = nullptr;
+#endif
+
+#pragma mark NotificationTrampoline
+
+// NotificationTrampoline dispatches cookie notifications to all the existing
+// CookieStoreIOS.
+class NotificationTrampoline {
+ public:
+ static NotificationTrampoline* GetInstance();
+
+ void AddObserver(CookieNotificationObserver* obs);
+ void RemoveObserver(CookieNotificationObserver* obs);
+
+ // Notify the observers.
+ void NotifyCookiesChanged();
+ void NotifyCookiePolicyChanged();
+
+ private:
+ NotificationTrampoline();
+ ~NotificationTrampoline();
+
+ ObserverList<CookieNotificationObserver> observer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(NotificationTrampoline);
+
+ static NotificationTrampoline* g_notification_trampoline;
+};
+
+#pragma mark NotificationTrampoline implementation
+
+NotificationTrampoline* NotificationTrampoline::GetInstance() {
+ if (!g_notification_trampoline)
+ g_notification_trampoline = new NotificationTrampoline;
+ return g_notification_trampoline;
+}
+
+void NotificationTrampoline::AddObserver(CookieNotificationObserver* obs) {
+ observer_list_.AddObserver(obs);
+}
+
+void NotificationTrampoline::RemoveObserver(CookieNotificationObserver* obs) {
+ observer_list_.RemoveObserver(obs);
+}
+
+void NotificationTrampoline::NotifyCookiesChanged() {
+ FOR_EACH_OBSERVER(CookieNotificationObserver, observer_list_,
+ OnSystemCookiesChanged());
+}
+
+void NotificationTrampoline::NotifyCookiePolicyChanged() {
+ FOR_EACH_OBSERVER(CookieNotificationObserver, observer_list_,
+ OnSystemCookiePolicyChanged());
+}
+
+NotificationTrampoline::NotificationTrampoline() {
+}
+
+NotificationTrampoline::~NotificationTrampoline() {
+}
+
+// Global instance of NotificationTrampoline.
+NotificationTrampoline* NotificationTrampoline::g_notification_trampoline =
+ nullptr;
+
+#pragma mark Utility functions
+
+// Returns the path to Cookie.binarycookies file on the file system where
+// WKWebView flushes its cookies.
+base::FilePath GetBinaryCookiesFilePath() {
+ base::FilePath path = base::mac::GetUserLibraryPath();
+ // The relative path of the file (from the user library folder) where
+ // WKWebView stores its cookies.
+ const std::string kCookiesFilePath = "Cookies/Cookies.binarycookies";
+ return path.Append(kCookiesFilePath);
+}
+
+// Clears all cookies from the .binarycookies file.
+// Must be called from a thread where IO operations are allowed.
+// Preconditions: There must be no active WKWebViews present in the app.
+void ClearAllCookiesFromBinaryCookiesFile() {
+ // The .binarycookies file is present only on iOS8+.
+ if (!base::ios::IsRunningOnIOS8OrLater()) {
+ return;
+ }
+ base::FilePath path = GetBinaryCookiesFilePath();
+ if (base::PathExists(path)) {
+ bool success = base::DeleteFile(path, false);
+ if (!success) {
+ DLOG(WARNING) << "Failed to remove binarycookies file.";
+ }
+ }
+ // TODO(shreyasv): Should .binarycookies be parsed to find out how many
+ // more cookies are deleted? Investigate further if the accuracy of this
+ // actually matters to the callback.
+}
+
+// Builds a NSHTTPCookie from a header cookie line ("Set-Cookie: xxx") and a
+// URL.
+NSHTTPCookie* GetNSHTTPCookieFromCookieLine(const std::string& cookie_line,
+ const GURL& url,
+ base::Time server_time) {
+ NSURL* nsurl = net::NSURLWithGURL(url);
+ NSString* ns_cookie_line = base::SysUTF8ToNSString(cookie_line);
+ if (!ns_cookie_line) {
+ DLOG(ERROR) << "Cookie line is not UTF8: " << cookie_line;
+ return nil;
+ }
+ NSArray* cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:@{
+ @"Set-Cookie" : ns_cookie_line
+ } forURL:nsurl];
+ if ([cookies count] != 1)
+ return nil;
+
+ NSHTTPCookie* cookie = [cookies objectAtIndex:0];
+ if (![cookie expiresDate] || server_time.is_null())
+ return cookie;
+
+ // Perform clock skew correction.
+ base::TimeDelta clock_skew = base::Time::Now() - server_time;
+ NSDate* corrected_expire_date =
+ [[cookie expiresDate] dateByAddingTimeInterval:clock_skew.InSecondsF()];
+ NSMutableDictionary* properties =
+ [NSMutableDictionary dictionaryWithDictionary:[cookie properties]];
+ [properties setObject:corrected_expire_date forKey:NSHTTPCookieExpires];
+ NSHTTPCookie* corrected_cookie =
+ [NSHTTPCookie cookieWithProperties:properties];
+ DCHECK(corrected_cookie);
+ return corrected_cookie;
+}
+
+// Compares cookies based on the path lengths and the creation times, as per
+// RFC6265.
+NSInteger CompareCookies(id a, id b, void* context) {
+ NSHTTPCookie* cookie_a = (NSHTTPCookie*)a;
+ NSHTTPCookie* cookie_b = (NSHTTPCookie*)b;
+ // Compare path lengths first.
+ NSUInteger path_length_a = [[cookie_a path] length];
+ NSUInteger path_length_b = [[cookie_b path] length];
+ if (path_length_a < path_length_b)
+ return NSOrderedDescending;
+ if (path_length_b < path_length_a)
+ return NSOrderedAscending;
+
+ // Compare creation times.
+ CookieCreationTimeManager* manager = (CookieCreationTimeManager*)context;
+ DCHECK(manager);
+ base::Time created_a = manager->GetCreationTime(cookie_a);
+ base::Time created_b = manager->GetCreationTime(cookie_b);
+#if !defined(CRNET)
+ // CookieCreationTimeManager is returning creation times that are null.
+ // Since in CrNet, the cookie store is recreated on startup, let's suppress
+ // this warning for now.
+ // TODO(huey): Instead of suppressing the warning, assign a creation time
+ // to cookies if one doesn't already exist.
+ DLOG_IF(ERROR, created_a.is_null() || created_b.is_null())
+ << "Cookie without creation date";
+#endif
+ if (created_a < created_b)
+ return NSOrderedAscending;
+ return (created_a > created_b) ? NSOrderedDescending : NSOrderedSame;
+}
+
+// Gets the cookies for |url| from the system cookie store.
+NSArray* GetCookiesForURL(const GURL& url, CookieCreationTimeManager* manager) {
+ NSArray* cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage]
+ cookiesForURL:net::NSURLWithGURL(url)];
+
+ // Sort cookies by decreasing path length, then creation time, as per RFC6265.
+ return [cookies sortedArrayUsingFunction:CompareCookies context:manager];
+}
+
+// Builds a cookie line (such as "key1=value1; key2=value2") from an array of
+// cookies.
+std::string BuildCookieLine(NSArray* cookies,
+ const net::CookieOptions& options) {
+ // The exclude_httponly() option would only be used by a javascript engine.
+ DCHECK(!options.exclude_httponly());
+
+ // This utility function returns all the cookies, including the httponly ones.
+ // This is fine because we don't support the exclude_httponly option.
+ NSDictionary* header = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
+ return base::SysNSStringToUTF8([header valueForKey:@"Cookie"]);
+}
+
+// Tests whether the |cookie| is a session cookie.
+bool IsCookieSessionCookie(NSHTTPCookie* cookie, base::Time time) {
+ return [cookie isSessionOnly];
+}
+
+// Tests whether the |creation_time| of |cookie| is in the time range defined
+// by |time_begin| and |time_end|. A null |time_end| means end-of-time.
+bool IsCookieCreatedBetween(base::Time time_begin,
+ base::Time time_end,
+ NSHTTPCookie* cookie,
+ base::Time creation_time) {
+ return time_begin <= creation_time &&
+ (time_end.is_null() || creation_time <= time_end);
+}
+
+// Tests whether the |creation_time| of |cookie| is in the time range defined
+// by |time_begin| and |time_end| and the cookie host match |host|. A null
+// |time_end| means end-of-time.
+bool IsCookieCreatedBetweenForHost(base::Time time_begin,
+ base::Time time_end,
+ NSString* host,
+ NSHTTPCookie* cookie,
+ base::Time creation_time) {
+ NSString* domain = [cookie domain];
+ return [domain characterAtIndex:0] != '.' &&
+ [domain caseInsensitiveCompare:host] == NSOrderedSame &&
+ IsCookieCreatedBetween(time_begin, time_end, cookie, creation_time);
+}
+
+// Adds cookies in |cookies| with name |name| to |filtered|.
+void OnlyCookiesWithName(const net::CookieList& cookies,
+ const std::string& name,
+ net::CookieList* filtered) {
+ for (const auto& cookie : cookies) {
+ if (cookie.Name() == name)
+ filtered->push_back(cookie);
+ }
+}
+
+} // namespace
+
+#pragma mark -
+#pragma mark CookieStoreIOS
+
+CookieStoreIOS::CookieStoreIOS(
+ net::CookieMonster::PersistentCookieStore* persistent_store)
+ : creation_time_manager_(new CookieCreationTimeManager),
+ metrics_enabled_(false),
+ flush_delay_(base::TimeDelta::FromSeconds(10)),
+ synchronization_state_(NOT_SYNCHRONIZED),
+ cookie_cache_(new CookieCache()) {
+ NotificationTrampoline::GetInstance()->AddObserver(this);
+ cookie_monster_ = new net::CookieMonster(persistent_store, nullptr);
+ cookie_monster_->SetPersistSessionCookies(true);
+ cookie_monster_->SetForceKeepSessionState();
+}
+
+// static
+void CookieStoreIOS::SetCookiePolicy(CookiePolicy setting) {
+ NSHTTPCookieAcceptPolicy policy = (setting == ALLOW)
+ ? NSHTTPCookieAcceptPolicyAlways
+ : NSHTTPCookieAcceptPolicyNever;
+ NSHTTPCookieStorage* store = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ NSHTTPCookieAcceptPolicy current_policy = [store cookieAcceptPolicy];
+ if (current_policy == policy)
+ return;
+ [store setCookieAcceptPolicy:policy];
+ NotificationTrampoline::GetInstance()->NotifyCookiePolicyChanged();
+}
+
+CookieStoreIOS* CookieStoreIOS::CreateCookieStoreFromNSHTTPCookieStorage() {
+ // TODO(huey): Update this when CrNet supports multiple cookie jars.
+ [[NSHTTPCookieStorage sharedHTTPCookieStorage]
+ setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
+
+ // Create a cookie store with no persistent store backing. Then, populate
+ // it from the system's cookie jar.
+ CookieStoreIOS* cookie_store = new CookieStoreIOS(nullptr);
+ cookie_store->synchronization_state_ = SYNCHRONIZED;
+ cookie_store->Flush(base::Closure());
+ return cookie_store;
+}
+
+// static
+void CookieStoreIOS::SwitchSynchronizedStore(CookieStoreIOS* old_store,
+ CookieStoreIOS* new_store) {
+ DCHECK(new_store);
+ DCHECK_NE(new_store, old_store);
+ if (old_store)
+ old_store->SetSynchronizedWithSystemStore(false);
+ new_store->SetSynchronizedWithSystemStore(true);
+}
+
+// static
+void CookieStoreIOS::NotifySystemCookiesChanged() {
+ NotificationTrampoline::GetInstance()->NotifyCookiesChanged();
+}
+
+void CookieStoreIOS::Flush(const base::Closure& closure) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (SystemCookiesAllowed()) {
+ // If cookies are disabled, the system store is empty, and the cookies are
+ // stashed on disk. Do not delete the cookies on the disk in this case.
+ WriteToCookieMonster(
+ [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]);
+ }
+ cookie_monster_->FlushStore(closure);
+ flush_closure_.Cancel();
+}
+
+void CookieStoreIOS::UnSynchronize() {
+ SetSynchronizedWithSystemStore(false);
+}
+
+void CookieStoreIOS::SetMetricsEnabled() {
+ static CookieStoreIOS* g_cookie_store_with_metrics = nullptr;
+ DCHECK(!g_cookie_store_with_metrics || g_cookie_store_with_metrics == this)
+ << "Only one cookie store may use metrics.";
+ g_cookie_store_with_metrics = this;
+ metrics_enabled_ = true;
+}
+
+#pragma mark -
+#pragma mark CookieStore methods
+
+void CookieStoreIOS::SetCookieWithOptionsAsync(
+ const GURL& url,
+ const std::string& cookie_line,
+ const net::CookieOptions& options,
+ const SetCookiesCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ switch (synchronization_state_) {
+ case NOT_SYNCHRONIZED:
+ cookie_monster_->SetCookieWithOptionsAsync(url, cookie_line, options,
+ WrapSetCallback(callback));
+ break;
+ case SYNCHRONIZING:
+ tasks_pending_synchronization_.push_back(
+ base::Bind(&CookieStoreIOS::SetCookieWithOptionsAsync, this, url,
+ cookie_line, options, WrapSetCallback(callback)));
+ break;
+ case SYNCHRONIZED:
+ // The exclude_httponly() option would only be used by a javascript
+ // engine.
+ DCHECK(!options.exclude_httponly());
+ // If cookies are not allowed, they are stashed in the CookieMonster, and
+ // should be written there instead.
+ DCHECK(SystemCookiesAllowed());
+
+ base::Time server_time =
+ options.has_server_time() ? options.server_time() : base::Time();
+ NSHTTPCookie* cookie =
+ GetNSHTTPCookieFromCookieLine(cookie_line, url, server_time);
+ DLOG_IF(WARNING, !cookie)
+ << "Could not create cookie for line: " << cookie_line;
+
+ // On iOS, [cookie domain] is not empty when the cookie domain is not
+ // specified: it is inferred from the URL instead.
+ // That is why two specific cases are tested here:
+ // - url_host == domain_string, happens when the cookie has no domain
+ // - domain_string.empty(), happens when the domain is not formatted
+ // correctly
+ std::string domain_string(base::SysNSStringToUTF8([cookie domain]));
+ const std::string url_host(url.host());
+ std::string dummy;
+ bool success = (cookie != nil) && !domain_string.empty() &&
+ (url_host == domain_string ||
+ net::cookie_util::GetCookieDomainWithString(
+ url, domain_string, &dummy));
+
+ if (success) {
+ [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
+ creation_time_manager_->SetCreationTime(
+ cookie,
+ creation_time_manager_->MakeUniqueCreationTime(base::Time::Now()));
+ }
+
+ if (!callback.is_null())
+ callback.Run(success);
+ break;
+ }
+}
+
+void CookieStoreIOS::GetCookiesWithOptionsAsync(
+ const GURL& url,
+ const net::CookieOptions& options,
+ const GetCookiesCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ switch (synchronization_state_) {
+ case NOT_SYNCHRONIZED:
+ cookie_monster_->GetCookiesWithOptionsAsync(url, options, callback);
+ break;
+ case SYNCHRONIZING:
+ tasks_pending_synchronization_.push_back(
+ base::Bind(&CookieStoreIOS::GetCookiesWithOptionsAsync, this, url,
+ options, callback));
+ break;
+ case SYNCHRONIZED:
+ // If cookies are not allowed, they are stashed in the CookieMonster, and
+ // should be read from there instead.
+ DCHECK(SystemCookiesAllowed());
+ // The exclude_httponly() option would only be used by a javascript
+ // engine.
+ DCHECK(!options.exclude_httponly());
+
+ NSArray* cookies = GetCookiesForURL(url, creation_time_manager_.get());
+ if (!callback.is_null())
+ callback.Run(BuildCookieLine(cookies, options));
+ break;
+ }
+}
+
+void CookieStoreIOS::GetAllCookiesForURLAsync(
+ const GURL& url,
+ const GetCookieListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ switch (synchronization_state_) {
+ case NOT_SYNCHRONIZED:
+ cookie_monster_->GetAllCookiesForURLAsync(url, callback);
+ break;
+ case SYNCHRONIZING:
+ tasks_pending_synchronization_.push_back(base::Bind(
+ &CookieStoreIOS::GetAllCookiesForURLAsync, this, url, callback));
+ break;
+ case SYNCHRONIZED:
+ if (!SystemCookiesAllowed()) {
+ // If cookies are not allowed, the cookies are stashed in the
+ // CookieMonster, so get them from there.
+ cookie_monster_->GetAllCookiesForURLAsync(url, callback);
+ return;
+ }
+
+ NSArray* cookies = GetCookiesForURL(url, creation_time_manager_.get());
+ net::CookieList cookie_list;
+ cookie_list.reserve([cookies count]);
+ for (NSHTTPCookie* cookie in cookies) {
+ base::Time created = creation_time_manager_->GetCreationTime(cookie);
+ cookie_list.push_back(CanonicalCookieFromSystemCookie(cookie, created));
+ }
+ if (!callback.is_null())
+ callback.Run(cookie_list);
+ break;
+ }
+}
+
+void CookieStoreIOS::DeleteCookieAsync(const GURL& url,
+ const std::string& cookie_name,
+ const base::Closure& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ switch (synchronization_state_) {
+ case NOT_SYNCHRONIZED:
+ cookie_monster_->DeleteCookieAsync(url, cookie_name,
+ WrapClosure(callback));
+ break;
+ case SYNCHRONIZING:
+ tasks_pending_synchronization_.push_back(
+ base::Bind(&CookieStoreIOS::DeleteCookieAsync, this, url, cookie_name,
+ WrapClosure(callback)));
+ break;
+ case SYNCHRONIZED:
+ NSArray* cookies = GetCookiesForURL(url, creation_time_manager_.get());
+ for (NSHTTPCookie* cookie in cookies) {
+ if ([[cookie name]
+ isEqualToString:base::SysUTF8ToNSString(cookie_name)]) {
+ [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
+ creation_time_manager_->DeleteCreationTime(cookie);
+ }
+ }
+ if (!callback.is_null())
+ callback.Run();
+ break;
+ }
+}
+
+// CookieStoreIOS is an implementation of CookieStore which is not a
+// CookieMonster. As CookieStore is the main cookie API, a caller of
+// GetCookieMonster must handle the case where this returns null.
+net::CookieMonster* CookieStoreIOS::GetCookieMonster() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return nullptr;
+}
+
+void CookieStoreIOS::DeleteAllCreatedBetweenAsync(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const DeleteCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (metrics_enabled_)
+ ResetCookieCountMetrics();
+
+ switch (synchronization_state_) {
+ case NOT_SYNCHRONIZED:
+ cookie_monster_->DeleteAllCreatedBetweenAsync(
+ delete_begin, delete_end, WrapDeleteCallback(callback));
+ break;
+ case SYNCHRONIZING:
+ tasks_pending_synchronization_.push_back(
+ base::Bind(&CookieStoreIOS::DeleteAllCreatedBetweenAsync, this,
+ delete_begin, delete_end, WrapDeleteCallback(callback)));
+ break;
+ case SYNCHRONIZED:
+ CookieFilterFunction filter =
+ base::Bind(&IsCookieCreatedBetween, delete_begin, delete_end);
+ DeleteCookiesWithFilter(filter, callback);
+ break;
+ }
+}
+
+void CookieStoreIOS::DeleteAllCreatedBetweenForHostAsync(
+ const base::Time delete_begin,
+ const base::Time delete_end,
+ const GURL& url,
+ const DeleteCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (metrics_enabled_)
+ ResetCookieCountMetrics();
+
+ switch (synchronization_state_) {
+ case NOT_SYNCHRONIZED:
+ cookie_monster_->DeleteAllCreatedBetweenForHostAsync(
+ delete_begin, delete_end, url, WrapDeleteCallback(callback));
+ break;
+ case SYNCHRONIZING:
+ tasks_pending_synchronization_.push_back(base::Bind(
+ &CookieStoreIOS::DeleteAllCreatedBetweenForHostAsync, this,
+ delete_begin, delete_end, url, WrapDeleteCallback(callback)));
+ break;
+ case SYNCHRONIZED:
+ NSString* host = base::SysUTF8ToNSString(url.host());
+ CookieFilterFunction filter = base::Bind(IsCookieCreatedBetweenForHost,
+ delete_begin, delete_end, host);
+ DeleteCookiesWithFilter(filter, callback);
+ break;
+ }
+}
+
+void CookieStoreIOS::DeleteSessionCookiesAsync(const DeleteCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (metrics_enabled_)
+ ResetCookieCountMetrics();
+
+ switch (synchronization_state_) {
+ case NOT_SYNCHRONIZED:
+ cookie_monster_->DeleteSessionCookiesAsync(WrapDeleteCallback(callback));
+ break;
+ case SYNCHRONIZING:
+ tasks_pending_synchronization_.push_back(
+ base::Bind(&CookieStoreIOS::DeleteSessionCookiesAsync, this,
+ WrapDeleteCallback(callback)));
+ break;
+ case SYNCHRONIZED:
+ CookieFilterFunction filter = base::Bind(&IsCookieSessionCookie);
+ DeleteCookiesWithFilter(filter, callback);
+ break;
+ }
+}
+
+#pragma mark -
+#pragma mark Protected methods
+
+CookieStoreIOS::~CookieStoreIOS() {
+ NotificationTrampoline::GetInstance()->RemoveObserver(this);
+ STLDeleteContainerPairSecondPointers(hook_map_.begin(), hook_map_.end());
+}
+
+#pragma mark -
+#pragma mark Private methods
+
+void CookieStoreIOS::ClearSystemStore() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ NSHTTPCookieStorage* cookie_storage =
+ [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ base::scoped_nsobject<NSArray> copy(
+ [[NSArray alloc] initWithArray:[cookie_storage cookies]]);
+ for (NSHTTPCookie* cookie in copy.get())
+ [cookie_storage deleteCookie:cookie];
+ DCHECK_EQ(0u, [[cookie_storage cookies] count]);
+ creation_time_manager_->Clear();
+}
+
+void CookieStoreIOS::OnSystemCookiePolicyChanged() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (synchronization_state_ == NOT_SYNCHRONIZED)
+ return;
+
+ // If the state is SYNCHRONIZING, this function will be a no-op because
+ // AddCookiesToSystemStore() will return early.
+
+ NSHTTPCookieAcceptPolicy policy =
+ [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookieAcceptPolicy];
+ if (policy == NSHTTPCookieAcceptPolicyAlways) {
+ // If cookies are disabled, the system cookie store should be empty.
+ DCHECK(![[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies] count]);
+ synchronization_state_ = SYNCHRONIZING;
+ cookie_monster_->GetAllCookiesAsync(
+ base::Bind(&CookieStoreIOS::AddCookiesToSystemStore, this));
+ } else {
+ DCHECK_EQ(NSHTTPCookieAcceptPolicyNever, policy);
+ // Flush() does not write the cookies to disk when they are disabled.
+ // Explicitly copy them.
+ WriteToCookieMonster(
+ [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]);
+ Flush(base::Closure());
+ ClearSystemStore();
+ }
+}
+
+void CookieStoreIOS::SetSynchronizedWithSystemStore(bool synchronized) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (synchronized == (synchronization_state_ != NOT_SYNCHRONIZED))
+ return; // The cookie store is already in the desired state.
+
+#if !defined(NDEBUG)
+ if (!synchronized) {
+ DCHECK_EQ(this, g_current_synchronized_store)
+ << "This cookie store was not synchronized";
+ g_current_synchronized_store = nullptr;
+ } else {
+ DCHECK_EQ((CookieStoreIOS*)nullptr, g_current_synchronized_store)
+ << "Un-synchronize the current cookie store first.";
+ g_current_synchronized_store = this;
+ }
+#endif
+
+ NSHTTPCookieAcceptPolicy policy =
+ [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookieAcceptPolicy];
+ DCHECK(policy == NSHTTPCookieAcceptPolicyAlways ||
+ policy == NSHTTPCookieAcceptPolicyNever);
+
+ // If cookies are disabled, the system cookie store should be empty.
+ DCHECK(policy == NSHTTPCookieAcceptPolicyAlways ||
+ ![[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies] count]);
+
+ // If cookies are disabled, nothing is done now, the work will be done when
+ // cookies are re-enabled.
+ if (policy == NSHTTPCookieAcceptPolicyAlways) {
+ if (synchronized) {
+ synchronization_state_ = SYNCHRONIZING;
+ ClearSystemStore();
+ cookie_monster_->GetAllCookiesAsync(
+ base::Bind(&CookieStoreIOS::AddCookiesToSystemStore, this));
+ return;
+ } else {
+ // Copy the cookies from the global store to |cookie_monster_|.
+ Flush(base::Closure());
+ }
+ }
+ synchronization_state_ = synchronized ? SYNCHRONIZED : NOT_SYNCHRONIZED;
+}
+
+bool CookieStoreIOS::SystemCookiesAllowed() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookieAcceptPolicy] ==
+ NSHTTPCookieAcceptPolicyAlways;
+}
+
+void CookieStoreIOS::AddCookiesToSystemStore(const net::CookieList& cookies) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!SystemCookiesAllowed() || synchronization_state_ != SYNCHRONIZING) {
+ // The case where synchronization aborts while there are pending tasks is
+ // not supported.
+ // Note: it should be ok to drop the tasks here (at least if cookies are not
+ // allowed).
+ DCHECK(tasks_pending_synchronization_.empty());
+ return;
+ }
+
+ // Report metrics.
+ if (metrics_enabled_) {
+ size_t cookie_count = cookies.size();
+ UMA_HISTOGRAM_COUNTS_10000("CookieIOS.CookieReadCount", cookie_count);
+ CheckForCookieLoss(cookie_count, COOKIES_READ);
+ }
+
+ net::CookieList::const_iterator it;
+ for (it = cookies.begin(); it != cookies.end(); ++it) {
+ const net::CanonicalCookie& net_cookie = *it;
+ NSHTTPCookie* system_cookie = SystemCookieFromCanonicalCookie(net_cookie);
+ // Canonical cookie may not be convertable into system cookie if it contains
+ // invalid characters.
+ if (!system_cookie)
+ continue;
+ [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:system_cookie];
+ creation_time_manager_->SetCreationTime(system_cookie,
+ net_cookie.CreationDate());
+ }
+
+ synchronization_state_ = SYNCHRONIZED;
+ // Run all the pending tasks.
+ for (const auto& task : tasks_pending_synchronization_) {
+ task.Run();
+ }
+ tasks_pending_synchronization_.clear();
+}
+
+void CookieStoreIOS::WriteToCookieMonster(NSArray* system_cookies) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (synchronization_state_ != SYNCHRONIZED)
+ return;
+
+ // Copy the cookies from the global cookie store to |cookie_monster_|.
+ // Unlike the system store, CookieMonster requires unique creation times.
+ net::CookieList cookie_list;
+ NSUInteger cookie_count = [system_cookies count];
+ cookie_list.reserve(cookie_count);
+ for (NSHTTPCookie* cookie in system_cookies) {
+ cookie_list.push_back(CanonicalCookieFromSystemCookie(
+ cookie, creation_time_manager_->GetCreationTime(cookie)));
+ }
+ cookie_monster_->SetAllCookiesAsync(cookie_list, SetCookiesCallback());
+
+ // Update metrics.
+ if (metrics_enabled_)
+ UMA_HISTOGRAM_COUNTS_10000("CookieIOS.CookieWrittenCount", cookie_count);
+}
+
+void CookieStoreIOS::DeleteCookiesWithFilter(const CookieFilterFunction& filter,
+ const DeleteCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(SYNCHRONIZED, synchronization_state_);
+ NSArray* cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
+
+ // Collect the cookies to delete.
+ base::scoped_nsobject<NSMutableArray> to_delete(
+ [[NSMutableArray alloc] init]);
+ for (NSHTTPCookie* cookie in cookies) {
+ base::Time creation_time = creation_time_manager_->GetCreationTime(cookie);
+ if (filter.Run(cookie, creation_time))
+ [to_delete addObject:cookie];
+ }
+
+ // Delete them.
+ for (NSHTTPCookie* cookie in to_delete.get()) {
+ [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
+ creation_time_manager_->DeleteCreationTime(cookie);
+ }
+
+ if (!callback.is_null())
+ callback.Run([to_delete count]);
+}
+
+void CookieStoreIOS::OnSystemCookiesChanged() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // If the CookieStoreIOS is not synchronized, system cookies are irrelevant.
+ if (synchronization_state_ != SYNCHRONIZED)
+ return;
+
+ for (const auto& hook_map_entry : hook_map_) {
+ std::pair<GURL, std::string> key = hook_map_entry.first;
+ std::vector<net::CanonicalCookie> removed_cookies;
+ std::vector<net::CanonicalCookie> added_cookies;
+ if (UpdateCacheForCookieFromSystem(key.first, key.second, &removed_cookies,
+ &added_cookies)) {
+ RunCallbacksForCookies(key.first, key.second, removed_cookies, true);
+ RunCallbacksForCookies(key.first, key.second, added_cookies, false);
+ }
+ }
+
+ // Do not schedule a flush if one is already scheduled.
+ if (!flush_closure_.IsCancelled())
+ return;
+
+ flush_closure_.Reset(base::Bind(&CookieStoreIOS::Flush,
+ base::Unretained(this), base::Closure()));
+ base::MessageLoopProxy::current()->PostDelayedTask(
+ FROM_HERE, flush_closure_.callback(), flush_delay_);
+}
+
+scoped_ptr<net::CookieStore::CookieChangedSubscription>
+CookieStoreIOS::AddCallbackForCookie(const GURL& gurl,
+ const std::string& name,
+ const CookieChangedCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Prefill cookie cache with all pertinent cookies for |url| if needed.
+ std::pair<GURL, std::string> key(gurl, name);
+ if (hook_map_.count(key) == 0) {
+ UpdateCacheForCookieFromSystem(gurl, name, nullptr, nullptr);
+ if (hook_map_.count(key) == 0)
+ hook_map_[key] = new CookieChangedCallbackList;
+ }
+
+ DCHECK(hook_map_.find(key) != hook_map_.end());
+ return hook_map_[key]->Add(callback);
+}
+
+bool CookieStoreIOS::UpdateCacheForCookieFromSystem(
+ const GURL& gurl,
+ const std::string& name,
+ std::vector<net::CanonicalCookie>* out_removed_cookies,
+ std::vector<net::CanonicalCookie>* out_added_cookies) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ std::vector<net::CanonicalCookie> system_cookies;
+ GetSystemCookies(gurl, name, &system_cookies);
+ return cookie_cache_->Update(gurl, name, system_cookies, out_removed_cookies,
+ out_added_cookies);
+}
+
+void CookieStoreIOS::RunCallbacksForCookies(
+ const GURL& url,
+ const std::string& name,
+ const std::vector<net::CanonicalCookie>& cookies,
+ bool removed) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (cookies.empty())
+ return;
+
+ std::pair<GURL, std::string> key(url, name);
+ CookieChangedCallbackList* callbacks = hook_map_[key];
+ for (const auto& cookie : cookies) {
+ DCHECK_EQ(name, cookie.Name());
+ callbacks->Notify(cookie, removed);
+ }
+}
+
+bool CookieStoreIOS::GetSystemCookies(
+ const GURL& gurl,
+ const std::string& name,
+ std::vector<net::CanonicalCookie>* cookies) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ NSURL* url = net::NSURLWithGURL(gurl);
+ NSHTTPCookieStorage* storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ NSArray* nscookies = [storage cookiesForURL:url];
+ bool found_cookies = false;
+ for (NSHTTPCookie* nscookie in nscookies) {
+ if (nscookie.name.UTF8String == name) {
+ net::CanonicalCookie canonical_cookie = CanonicalCookieFromSystemCookie(
+ nscookie, creation_time_manager_->GetCreationTime(nscookie));
+ cookies->push_back(canonical_cookie);
+ found_cookies = true;
+ }
+ }
+ return found_cookies;
+}
+
+void CookieStoreIOS::GotCookieListFor(const std::pair<GURL, std::string> key,
+ const net::CookieList& cookies) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ net::CookieList filtered;
+ OnlyCookiesWithName(cookies, key.second, &filtered);
+ std::vector<net::CanonicalCookie> removed_cookies;
+ std::vector<net::CanonicalCookie> added_cookies;
+ if (cookie_cache_->Update(key.first, key.second, filtered, &removed_cookies,
+ &added_cookies)) {
+ RunCallbacksForCookies(key.first, key.second, removed_cookies, true);
+ RunCallbacksForCookies(key.first, key.second, added_cookies, false);
+ }
+}
+
+void CookieStoreIOS::DidClearNSHTTPCookieStorageCookies(
+ const DeleteCallback& delete_callback,
+ int num_deleted) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ CookieStoreIOSClient* client = net::GetCookieStoreIOSClient();
+ DCHECK(client);
+ auto sequenced_task_runner = client->GetTaskRunner();
+ DCHECK(sequenced_task_runner);
+ auto callback = base::Bind(&CookieStoreIOS::DidClearBinaryCookiesFileCookies,
+ this, delete_callback, num_deleted);
+ sequenced_task_runner.get()->PostTaskAndReply(
+ FROM_HERE, base::Bind(&ClearAllCookiesFromBinaryCookiesFile), callback);
+}
+
+void CookieStoreIOS::DidClearBinaryCookiesFileCookies(
+ const DeleteCallback& callback,
+ int num_deleted_from_nshttp_cookie_storage) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ CookieStoreIOSClient* client = net::GetCookieStoreIOSClient();
+ DCHECK(client);
+ client->DidChangeCookieStorage();
+ callback.Run(num_deleted_from_nshttp_cookie_storage);
+}
+
+void CookieStoreIOS::UpdateCachesFromCookieMonster() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ for (const auto& hook_map_entry : hook_map_) {
+ std::pair<GURL, std::string> key = hook_map_entry.first;
+ GetCookieListCallback callback =
+ base::Bind(&CookieStoreIOS::GotCookieListFor, this, key);
+ cookie_monster_->GetAllCookiesForURLAsync(key.first, callback);
+ }
+}
+
+void CookieStoreIOS::UpdateCachesAfterSet(const SetCookiesCallback& callback,
+ bool success) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (success)
+ UpdateCachesFromCookieMonster();
+ callback.Run(success);
+}
+
+void CookieStoreIOS::UpdateCachesAfterDelete(const DeleteCallback& callback,
+ int num_deleted) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ UpdateCachesFromCookieMonster();
+ callback.Run(num_deleted);
+}
+
+void CookieStoreIOS::UpdateCachesAfterClosure(const base::Closure& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ UpdateCachesFromCookieMonster();
+ callback.Run();
+}
+
+CookieStoreIOS::SetCookiesCallback CookieStoreIOS::WrapSetCallback(
+ const SetCookiesCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return base::Bind(&CookieStoreIOS::UpdateCachesAfterSet, this, callback);
+}
+
+CookieStoreIOS::DeleteCallback CookieStoreIOS::WrapDeleteCallback(
+ const DeleteCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return base::Bind(&CookieStoreIOS::UpdateCachesAfterDelete, this, callback);
+}
+
+base::Closure CookieStoreIOS::WrapClosure(const base::Closure& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return base::Bind(&CookieStoreIOS::UpdateCachesAfterClosure, this, callback);
+}
+
+} // namespace net
diff --git a/ios/net/cookies/cookie_store_ios_unittest.mm b/ios/net/cookies/cookie_store_ios_unittest.mm
new file mode 100644
index 0000000..311587a
--- /dev/null
+++ b/ios/net/cookies/cookie_store_ios_unittest.mm
@@ -0,0 +1,912 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ios/net/cookies/cookie_store_ios.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/bind_helpers.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/sys_string_conversions.h"
+#import "net/base/mac/url_conversions.h"
+#include "net/cookies/cookie_store_unittest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+// Clears the underlying NSHTTPCookieStorage.
+void ClearCookies() {
+ NSHTTPCookieStorage* store = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ [store setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
+ NSArray* cookies = [store cookies];
+ for (NSHTTPCookie* cookie in cookies)
+ [store deleteCookie:cookie];
+ EXPECT_EQ(0u, [[store cookies] count]);
+}
+} // namespace
+
+namespace net {
+
+struct CookieStoreIOSTestTraits {
+ static net::CookieStore* Create() {
+ ClearCookies();
+ CookieStoreIOS* store = new CookieStoreIOS(nullptr);
+ store->synchronization_state_ = CookieStoreIOS::SYNCHRONIZED;
+ return store;
+ }
+
+ static const bool is_cookie_monster = false;
+ static const bool supports_http_only = false;
+ static const bool supports_non_dotted_domains = false;
+ static const bool supports_trailing_dots = false;
+ static const bool filters_schemes = false;
+ static const bool has_path_prefix_bug = true;
+ static const int creation_time_granularity_in_ms = 1000;
+
+ base::MessageLoop loop_;
+};
+
+struct InactiveCookieStoreIOSTestTraits {
+ static scoped_refptr<net::CookieStore> Create() {
+ return new CookieStoreIOS(nullptr);
+ }
+
+ static const bool is_cookie_monster = false;
+ static const bool supports_http_only = false;
+ static const bool supports_non_dotted_domains = true;
+ static const bool supports_trailing_dots = true;
+ static const bool filters_schemes = false;
+ static const bool has_path_prefix_bug = false;
+ static const int creation_time_granularity_in_ms = 0;
+
+ base::MessageLoop loop_;
+};
+
+// RoundTripTestCookieStore is un-synchronized and re-synchronized after all
+// cookie operations. This means all system cookies are converted to Chrome
+// cookies and converted back.
+// The purpose of this class is to test that cookie conversions do not break the
+// cookie store.
+class RoundTripTestCookieStore : public net::CookieStore {
+ public:
+ RoundTripTestCookieStore()
+ : store_(new CookieStoreIOS(nullptr)),
+ dummy_store_(new CookieStoreIOS(nullptr)) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ }
+
+ // Inherited CookieStore methods.
+ void SetCookieWithOptionsAsync(const GURL& url,
+ const std::string& cookie_line,
+ const net::CookieOptions& options,
+ const SetCookiesCallback& callback) override {
+ RoundTrip();
+ store_->SetCookieWithOptionsAsync(url, cookie_line, options, callback);
+ }
+
+ void GetCookiesWithOptionsAsync(const GURL& url,
+ const net::CookieOptions& options,
+ const GetCookiesCallback& callback) override {
+ RoundTrip();
+ store_->GetCookiesWithOptionsAsync(url, options, callback);
+ }
+
+ void GetAllCookiesForURLAsync(
+ const GURL& url,
+ const GetCookieListCallback& callback) override {
+ RoundTrip();
+ store_->GetAllCookiesForURLAsync(url, callback);
+ }
+
+ void DeleteCookieAsync(const GURL& url,
+ const std::string& cookie_name,
+ const base::Closure& callback) override {
+ RoundTrip();
+ store_->DeleteCookieAsync(url, cookie_name, callback);
+ }
+
+ net::CookieMonster* GetCookieMonster() override { return nullptr; }
+
+ void DeleteAllCreatedBetweenAsync(const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const DeleteCallback& callback) override {
+ RoundTrip();
+ store_->DeleteAllCreatedBetweenAsync(delete_begin, delete_end, callback);
+ }
+
+ void DeleteAllCreatedBetweenForHostAsync(
+ const base::Time delete_begin,
+ const base::Time delete_end,
+ const GURL& url,
+ const DeleteCallback& callback) override {
+ RoundTrip();
+ store_->DeleteAllCreatedBetweenForHostAsync(delete_begin, delete_end, url,
+ callback);
+ }
+
+ void DeleteSessionCookiesAsync(const DeleteCallback& callback) override {
+ RoundTrip();
+ store_->DeleteSessionCookiesAsync(callback);
+ }
+
+ scoped_ptr<CookieStore::CookieChangedSubscription> AddCallbackForCookie(
+ const GURL& url,
+ const std::string& name,
+ const CookieChangedCallback& callback) override {
+ return scoped_ptr<CookieStore::CookieChangedSubscription>();
+ }
+
+ protected:
+ ~RoundTripTestCookieStore() override { store_->UnSynchronize(); }
+
+ private:
+ void RoundTrip() {
+ CookieStoreIOS::SwitchSynchronizedStore(store_.get(), dummy_store_.get());
+ // Check that the system store is empty, because it is synchronized with
+ // |dummy_store_| which is empty.
+ NSHTTPCookieStorage* store = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ EXPECT_EQ(0u, [[store cookies] count]);
+ CookieStoreIOS::SwitchSynchronizedStore(dummy_store_.get(), store_.get());
+ }
+
+ scoped_refptr<CookieStoreIOS> store_;
+ // |dummy_store_| is not directly used, but is needed to make |store_|
+ // inactive.
+ scoped_refptr<CookieStoreIOS> dummy_store_;
+};
+
+struct RoundTripTestCookieStoreTraits {
+ static scoped_refptr<net::CookieStore> Create() {
+ ClearCookies();
+ return new RoundTripTestCookieStore();
+ }
+
+ static const bool is_cookie_monster = false;
+ static const bool supports_http_only = false;
+ static const bool supports_non_dotted_domains = false;
+ static const bool supports_trailing_dots = false;
+ static const bool filters_schemes = false;
+ static const bool has_path_prefix_bug = true;
+ static const int creation_time_granularity_in_ms = 1000;
+};
+
+} // namespace net
+
+namespace net {
+
+INSTANTIATE_TYPED_TEST_CASE_P(CookieStoreIOS,
+ CookieStoreTest,
+ CookieStoreIOSTestTraits);
+
+INSTANTIATE_TYPED_TEST_CASE_P(InactiveCookieStoreIOS,
+ CookieStoreTest,
+ InactiveCookieStoreIOSTestTraits);
+
+INSTANTIATE_TYPED_TEST_CASE_P(RoundTripTestCookieStore,
+ CookieStoreTest,
+ RoundTripTestCookieStoreTraits);
+
+} // namespace net
+
+namespace {
+
+// Test net::CookieMonster::PersistentCookieStore allowing to control when the
+// initialization completes.
+class TestPersistentCookieStore
+ : public net::CookieMonster::PersistentCookieStore {
+ public:
+ TestPersistentCookieStore()
+ : kTestCookieURL("http://foo.google.com/bar"), flushed_(false) {}
+
+ // Runs the completion callback with a "a=b" cookie.
+ void RunLoadedCallback() {
+ std::vector<net::CanonicalCookie*> cookies;
+ net::CookieOptions options;
+ options.set_include_httponly();
+ cookies.push_back(net::CanonicalCookie::Create(kTestCookieURL, "a=b",
+ base::Time::Now(), options));
+ // Some canonical cookies cannot be converted into System cookies, for
+ // example if value is not valid utf8. Such cookies are ignored.
+ net::CanonicalCookie* bad_canonical_cookie = new net::CanonicalCookie(
+ kTestCookieURL, "name", "\x81r\xe4\xbd\xa0\xe5\xa5\xbd", "domain",
+ "path/",
+ base::Time(), // creation
+ base::Time(), // expires
+ base::Time(), // last_access
+ false, // secure
+ false, // httponly
+ false, // first_party_only
+ net::COOKIE_PRIORITY_DEFAULT);
+ cookies.push_back(bad_canonical_cookie);
+ loaded_callback_.Run(cookies);
+ }
+
+ bool flushed() { return flushed_; }
+
+ private:
+ // net::CookieMonster::PersistentCookieStore implementation:
+ void Load(const LoadedCallback& loaded_callback) override {
+ loaded_callback_ = loaded_callback;
+ }
+
+ void LoadCookiesForKey(const std::string& key,
+ const LoadedCallback& loaded_callback) override {
+ loaded_callback_ = loaded_callback;
+ }
+
+ void AddCookie(const net::CanonicalCookie& cc) override {}
+ void UpdateCookieAccessTime(const net::CanonicalCookie& cc) override {}
+ void DeleteCookie(const net::CanonicalCookie& cc) override {}
+ void SetForceKeepSessionState() override {}
+ void Flush(const base::Closure& callback) override { flushed_ = true; }
+
+ private:
+ ~TestPersistentCookieStore() override {}
+
+ const GURL kTestCookieURL;
+ LoadedCallback loaded_callback_;
+ bool flushed_;
+};
+
+// Helper callback to be passed to CookieStore::GetCookiesWithOptionsAsync().
+class GetCookieCallback {
+ public:
+ GetCookieCallback() : did_run_(false) {}
+
+ // Returns true if the callback has been run.
+ bool did_run() { return did_run_; }
+
+ // Returns the parameter of the callback.
+ const std::string& cookie_line() { return cookie_line_; }
+
+ void Run(const std::string& cookie_line) {
+ ASSERT_FALSE(did_run_);
+ did_run_ = true;
+ cookie_line_ = cookie_line;
+ }
+
+ private:
+ bool did_run_;
+ std::string cookie_line_;
+};
+
+// Helper callback to be passed to CookieStore::GetAllCookiesForURLAsync().
+class GetAllCookiesCallback {
+ public:
+ GetAllCookiesCallback() : did_run_(false) {}
+
+ // Returns true if the callback has been run.
+ bool did_run() { return did_run_; }
+
+ // Returns the parameter of the callback.
+ const net::CookieList& cookie_list() { return cookie_list_; }
+
+ void Run(const net::CookieList& cookie_list) {
+ ASSERT_FALSE(did_run_);
+ did_run_ = true;
+ cookie_list_ = cookie_list;
+ }
+
+ private:
+ bool did_run_;
+ net::CookieList cookie_list_;
+};
+
+namespace {
+
+void RecordCookieChanges(std::vector<net::CanonicalCookie>* out_cookies,
+ std::vector<bool>* out_removes,
+ const net::CanonicalCookie& cookie,
+ bool removed) {
+ DCHECK(out_cookies);
+ out_cookies->push_back(cookie);
+ if (out_removes)
+ out_removes->push_back(removed);
+}
+
+void IgnoreBoolean(bool ignored) {
+}
+
+void IgnoreString(const std::string& ignored) {
+}
+
+} // namespace
+
+class CookieStoreIOSWithBackend : public testing::Test {
+ public:
+ CookieStoreIOSWithBackend()
+ : kTestCookieURL("http://foo.google.com/bar"),
+ kTestCookieURL2("http://foo.google.com/baz"),
+ kTestCookieURL3("http://foo.google.com"),
+ kTestCookieURL4("http://bar.google.com/bar") {
+ backend_ = new TestPersistentCookieStore;
+ store_ = new net::CookieStoreIOS(backend_.get());
+ net::CookieStoreIOS::SetCookiePolicy(net::CookieStoreIOS::ALLOW);
+ cookie_changed_callback_ = store_->AddCallbackForCookie(
+ kTestCookieURL, "abc",
+ base::Bind(&RecordCookieChanges, &cookies_changed_, &cookies_removed_));
+ }
+
+ ~CookieStoreIOSWithBackend() override {}
+
+ // Gets the cookies. |callback| will be called on completion.
+ void GetCookies(const net::CookieStore::GetCookiesCallback& callback) {
+ net::CookieOptions options;
+ options.set_include_httponly();
+ store_->GetCookiesWithOptionsAsync(kTestCookieURL, options, callback);
+ }
+
+ // Sets a cookie.
+ void SetCookie(const std::string& cookie_line) {
+ net::CookieOptions options;
+ options.set_include_httponly();
+ store_->SetCookieWithOptionsAsync(kTestCookieURL, cookie_line, options,
+ base::Bind(&IgnoreBoolean));
+ net::CookieStoreIOS::NotifySystemCookiesChanged();
+ // Wait until the flush is posted.
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void SetSystemCookie(const GURL& url,
+ const std::string& name,
+ const std::string& value) {
+ NSHTTPCookieStorage* storage =
+ [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ [storage setCookie:[NSHTTPCookie cookieWithProperties:@{
+ NSHTTPCookiePath : base::SysUTF8ToNSString(url.path()),
+ NSHTTPCookieName : base::SysUTF8ToNSString(name),
+ NSHTTPCookieValue : base::SysUTF8ToNSString(value),
+ NSHTTPCookieDomain : base::SysUTF8ToNSString(url.host()),
+ }]];
+ net::CookieStoreIOS::NotifySystemCookiesChanged();
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void DeleteSystemCookie(const GURL& gurl, const std::string& name) {
+ NSHTTPCookieStorage* storage =
+ [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ NSURL* nsurl = net::NSURLWithGURL(gurl);
+ NSArray* cookies = [storage cookiesForURL:nsurl];
+ for (NSHTTPCookie* cookie in cookies) {
+ if (cookie.name.UTF8String == name) {
+ [storage deleteCookie:cookie];
+ break;
+ }
+ }
+ net::CookieStoreIOS::NotifySystemCookiesChanged();
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ protected:
+ const GURL kTestCookieURL;
+ const GURL kTestCookieURL2;
+ const GURL kTestCookieURL3;
+ const GURL kTestCookieURL4;
+
+ base::MessageLoop loop_;
+ scoped_refptr<TestPersistentCookieStore> backend_;
+ scoped_refptr<net::CookieStoreIOS> store_;
+ scoped_ptr<net::CookieStore::CookieChangedSubscription>
+ cookie_changed_callback_;
+ std::vector<net::CanonicalCookie> cookies_changed_;
+ std::vector<bool> cookies_removed_;
+};
+
+} // namespace
+
+namespace net {
+
+TEST_F(CookieStoreIOSWithBackend, SetCookieCallsHookWhenNotSynchronized) {
+ ClearCookies();
+ SetCookie("abc=def");
+ EXPECT_EQ(0U, cookies_changed_.size());
+ EXPECT_EQ(0U, cookies_removed_.size());
+ backend_->RunLoadedCallback();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1U, cookies_changed_.size());
+ EXPECT_EQ(1U, cookies_removed_.size());
+ EXPECT_EQ("abc", cookies_changed_[0].Name());
+ EXPECT_EQ("def", cookies_changed_[0].Value());
+ EXPECT_FALSE(cookies_removed_[0]);
+
+ // Replacing an existing cookie is actually a two-phase delete + set
+ // operation, so we get an extra notification.
+ SetCookie("abc=ghi");
+ EXPECT_EQ(3U, cookies_changed_.size());
+ EXPECT_EQ(3U, cookies_removed_.size());
+ EXPECT_EQ("abc", cookies_changed_[1].Name());
+ EXPECT_EQ("def", cookies_changed_[1].Value());
+ EXPECT_TRUE(cookies_removed_[1]);
+ EXPECT_EQ("abc", cookies_changed_[2].Name());
+ EXPECT_EQ("ghi", cookies_changed_[2].Value());
+ EXPECT_FALSE(cookies_removed_[2]);
+
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, SetCookieCallsHookWhenSynchronized) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ GetCookies(base::Bind(&IgnoreString));
+ backend_->RunLoadedCallback();
+ base::MessageLoop::current()->RunUntilIdle();
+ ClearCookies();
+ SetCookie("abc=def");
+ EXPECT_EQ(1U, cookies_changed_.size());
+ EXPECT_EQ(1U, cookies_removed_.size());
+ EXPECT_EQ("abc", cookies_changed_[0].Name());
+ EXPECT_EQ("def", cookies_changed_[0].Value());
+ EXPECT_FALSE(cookies_removed_[0]);
+
+ SetCookie("abc=ghi");
+ EXPECT_EQ(3U, cookies_changed_.size());
+ EXPECT_EQ(3U, cookies_removed_.size());
+ EXPECT_EQ("abc", cookies_changed_[1].Name());
+ EXPECT_EQ("def", cookies_changed_[1].Value());
+ EXPECT_TRUE(cookies_removed_[1]);
+ EXPECT_EQ("abc", cookies_changed_[2].Name());
+ EXPECT_EQ("ghi", cookies_changed_[2].Value());
+ EXPECT_FALSE(cookies_removed_[2]);
+ DeleteSystemCookie(kTestCookieURL, "abc");
+
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, DeleteCallsHook) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ GetCookies(base::Bind(&IgnoreString));
+ backend_->RunLoadedCallback();
+ base::MessageLoop::current()->RunUntilIdle();
+ ClearCookies();
+ SetCookie("abc=def");
+ EXPECT_EQ(1U, cookies_changed_.size());
+ EXPECT_EQ(1U, cookies_removed_.size());
+ store_->DeleteCookieAsync(kTestCookieURL, "abc",
+ base::Bind(&IgnoreBoolean, false));
+ CookieStoreIOS::NotifySystemCookiesChanged();
+ base::MessageLoop::current()->RunUntilIdle();
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, SameValueDoesNotCallHook) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ GetCookieCallback callback;
+ GetCookies(base::Bind(&IgnoreString));
+ backend_->RunLoadedCallback();
+ base::MessageLoop::current()->RunUntilIdle();
+ ClearCookies();
+ SetCookie("abc=def");
+ EXPECT_EQ(1U, cookies_changed_.size());
+ SetCookie("abc=def");
+ EXPECT_EQ(1U, cookies_changed_.size());
+ store_->UnSynchronize();
+}
+
+TEST(CookieStoreIOS, GetAllCookiesForURLAsync) {
+ base::MessageLoop loop;
+ const GURL kTestCookieURL("http://foo.google.com/bar");
+ ClearCookies();
+ scoped_refptr<CookieStoreIOS> cookie_store(new CookieStoreIOS(nullptr));
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, cookie_store.get());
+ // Add a cookie.
+ net::CookieOptions options;
+ options.set_include_httponly();
+ cookie_store->SetCookieWithOptionsAsync(
+ kTestCookieURL, "a=b", options, net::CookieStore::SetCookiesCallback());
+ // Disallow cookies.
+ CookieStoreIOS::SetCookiePolicy(CookieStoreIOS::BLOCK);
+ // No cookie in the system store.
+ NSHTTPCookieStorage* system_store =
+ [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ EXPECT_EQ(0u, [[system_store cookies] count]);
+ // Flushing should not have any effect.
+ cookie_store->Flush(base::Closure());
+ // Check we can get the cookie even though cookies are disabled.
+ GetAllCookiesCallback callback;
+ cookie_store->GetAllCookiesForURLAsync(
+ kTestCookieURL,
+ base::Bind(&GetAllCookiesCallback::Run, base::Unretained(&callback)));
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_EQ(1u, callback.cookie_list().size());
+ net::CanonicalCookie cookie = callback.cookie_list()[0];
+ EXPECT_EQ("a", cookie.Name());
+ EXPECT_EQ("b", cookie.Value());
+ // Re-enable cookies.
+ CookieStoreIOS::SetCookiePolicy(CookieStoreIOS::ALLOW);
+ // Cookie is back in the system store.
+ EXPECT_EQ(1u, [[system_store cookies] count]);
+ cookie_store->UnSynchronize();
+}
+
+// Tests that cookies can be read before the backend is loaded.
+TEST_F(CookieStoreIOSWithBackend, NotSynchronized) {
+ // Start fetching the cookie.
+ GetCookieCallback callback;
+ GetCookies(base::Bind(&GetCookieCallback::Run, base::Unretained(&callback)));
+ // Backend loading completes.
+ backend_->RunLoadedCallback();
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_EQ("a=b", callback.cookie_line());
+}
+
+// Tests that cookies can be read before synchronization is complete.
+TEST_F(CookieStoreIOSWithBackend, Synchronizing) {
+ // Start synchronization.
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ GetCookieCallback callback;
+ GetCookies(base::Bind(&GetCookieCallback::Run, base::Unretained(&callback)));
+ // Backend loading completes (end of synchronization).
+ backend_->RunLoadedCallback();
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_EQ("a=b", callback.cookie_line());
+ store_->UnSynchronize();
+}
+
+// Tests that cookies can be read before synchronization is complete, when
+// triggered by a change in cookie policy.
+TEST_F(CookieStoreIOSWithBackend, SynchronizingAfterPolicyChange) {
+ ClearCookies();
+ CookieStoreIOS::SetCookiePolicy(CookieStoreIOS::BLOCK);
+ // SwitchSynchronizedStore() does nothing when cookies are blocked.
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ // Start synchronization by allowing cookies.
+ CookieStoreIOS::SetCookiePolicy(CookieStoreIOS::ALLOW);
+ GetCookieCallback callback;
+ GetCookies(base::Bind(&GetCookieCallback::Run, base::Unretained(&callback)));
+ // Backend loading completes (end of synchronization).
+ backend_->RunLoadedCallback();
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_EQ("a=b", callback.cookie_line());
+ store_->UnSynchronize();
+}
+
+// Tests that Synchronization can be "aborted" (i.e. the cookie store is
+// unsynchronized while synchronization is in progress).
+TEST_F(CookieStoreIOSWithBackend, SyncThenUnsync) {
+ ClearCookies();
+ scoped_refptr<CookieStoreIOS> dummy_store = new CookieStoreIOS(nullptr);
+ // Switch back and forth before synchronization can complete.
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ CookieStoreIOS::SwitchSynchronizedStore(store_.get(), dummy_store.get());
+ backend_->RunLoadedCallback();
+ // No cookie leak in the system store.
+ NSHTTPCookieStorage* store = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ EXPECT_EQ(0u, [[store cookies] count]);
+ // No cookie lost.
+ GetCookieCallback callback;
+ GetCookies(base::Bind(&GetCookieCallback::Run, base::Unretained(&callback)));
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_EQ("a=b", callback.cookie_line());
+ dummy_store->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, ChangePolicyOnceDuringSynchronization) {
+ // Start synchronization.
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ // Toggle cookie policy to trigger another synchronization while the first one
+ // is still in progress.
+ CookieStoreIOS::SetCookiePolicy(CookieStoreIOS::BLOCK);
+ // Backend loading completes (end of synchronization).
+ backend_->RunLoadedCallback();
+ CookieStoreIOS::SetCookiePolicy(CookieStoreIOS::ALLOW);
+ GetCookieCallback callback;
+ GetCookies(base::Bind(&GetCookieCallback::Run, base::Unretained(&callback)));
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_EQ("a=b", callback.cookie_line());
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, ChangePolicyTwiceDuringSynchronization) {
+ // Start synchronization.
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ // Toggle cookie policy to trigger another synchronization while the first one
+ // is still in progress.
+ CookieStoreIOS::SetCookiePolicy(CookieStoreIOS::BLOCK);
+ CookieStoreIOS::SetCookiePolicy(CookieStoreIOS::ALLOW);
+ // Backend loading completes (end of synchronization).
+ backend_->RunLoadedCallback();
+ GetCookieCallback callback;
+ GetCookies(base::Bind(&GetCookieCallback::Run, base::Unretained(&callback)));
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_EQ("a=b", callback.cookie_line());
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, UnSynchronizeBeforeLoadComplete) {
+ ClearCookies();
+ // Switch back and forth before synchronization can complete.
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ store_->UnSynchronize();
+ backend_->RunLoadedCallback();
+ // No cookie lost.
+ GetCookieCallback callback;
+ GetCookies(base::Bind(&GetCookieCallback::Run, base::Unretained(&callback)));
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_EQ("a=b", callback.cookie_line());
+}
+
+TEST_F(CookieStoreIOSWithBackend, UnSynchronize) {
+ ClearCookies();
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ backend_->RunLoadedCallback();
+ store_->UnSynchronize();
+ // No cookie lost.
+ GetCookieCallback callback;
+ GetCookies(base::Bind(&GetCookieCallback::Run, base::Unretained(&callback)));
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_EQ("a=b", callback.cookie_line());
+}
+
+TEST_F(CookieStoreIOSWithBackend, FlushOnUnSynchronize) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ EXPECT_FALSE(backend_->flushed());
+ store_->UnSynchronize();
+ EXPECT_TRUE(backend_->flushed());
+}
+
+TEST_F(CookieStoreIOSWithBackend, FlushOnSwitch) {
+ scoped_refptr<CookieStoreIOS> dummy_store = new CookieStoreIOS(nullptr);
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ EXPECT_FALSE(backend_->flushed());
+ CookieStoreIOS::SwitchSynchronizedStore(store_.get(), dummy_store.get());
+ EXPECT_TRUE(backend_->flushed());
+ dummy_store->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, FlushOnCookieChanged) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ store_->set_flush_delay_for_testing(base::TimeDelta());
+ backend_->RunLoadedCallback();
+ EXPECT_FALSE(backend_->flushed());
+
+ // Set a cookie an check that it triggers a flush.
+ SetCookie("x=y");
+ EXPECT_TRUE(backend_->flushed());
+
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, ManualFlush) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ backend_->RunLoadedCallback();
+ EXPECT_FALSE(backend_->flushed());
+
+ // The store should be flushed even if it is not dirty.
+ store_->Flush(base::Closure());
+ EXPECT_TRUE(backend_->flushed());
+
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, FlushOnPolicyChange) {
+ // Start synchronization.
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ // Toggle cookie policy to trigger a flush.
+ EXPECT_FALSE(backend_->flushed());
+ CookieStoreIOS::SetCookiePolicy(CookieStoreIOS::BLOCK);
+ EXPECT_TRUE(backend_->flushed());
+ store_->UnSynchronize();
+ CookieStoreIOS::SetCookiePolicy(CookieStoreIOS::ALLOW);
+}
+
+TEST_F(CookieStoreIOSWithBackend, NoInitialNotifyWithNoCookie) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ std::vector<net::CanonicalCookie> cookies;
+ store_->AddCallbackForCookie(
+ kTestCookieURL, "abc",
+ base::Bind(&RecordCookieChanges, &cookies, nullptr));
+ EXPECT_EQ(0U, cookies.size());
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, NoInitialNotifyWithSystemCookie) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ SetSystemCookie(kTestCookieURL, "abc", "def");
+ std::vector<net::CanonicalCookie> cookies;
+ store_->AddCallbackForCookie(
+ kTestCookieURL, "abc",
+ base::Bind(&RecordCookieChanges, &cookies, nullptr));
+ EXPECT_EQ(0U, cookies.size());
+ DeleteSystemCookie(kTestCookieURL, "abc");
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, NotifyOnAdd) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ backend_->RunLoadedCallback();
+ std::vector<net::CanonicalCookie> cookies;
+ std::vector<bool> removes;
+ scoped_ptr<net::CookieStore::CookieChangedSubscription> handle =
+ store_->AddCallbackForCookie(
+ kTestCookieURL, "abc",
+ base::Bind(&RecordCookieChanges, &cookies, &removes));
+ EXPECT_EQ(0U, cookies.size());
+ EXPECT_EQ(0U, removes.size());
+ SetSystemCookie(kTestCookieURL, "abc", "def");
+ EXPECT_EQ(1U, cookies.size());
+ EXPECT_EQ(1U, removes.size());
+ EXPECT_EQ("abc", cookies[0].Name());
+ EXPECT_EQ("def", cookies[0].Value());
+ EXPECT_FALSE(removes[0]);
+
+ SetSystemCookie(kTestCookieURL, "ghi", "jkl");
+ EXPECT_EQ(1U, cookies.size());
+ EXPECT_EQ(1U, removes.size());
+
+ DeleteSystemCookie(kTestCookieURL, "abc");
+ DeleteSystemCookie(kTestCookieURL, "ghi");
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, NotifyOnChange) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ backend_->RunLoadedCallback();
+ std::vector<net::CanonicalCookie> cookies;
+ std::vector<bool> removes;
+ scoped_ptr<net::CookieStore::CookieChangedSubscription> handle =
+ store_->AddCallbackForCookie(
+ kTestCookieURL, "abc",
+ base::Bind(&RecordCookieChanges, &cookies, &removes));
+ EXPECT_EQ(0U, cookies.size());
+ SetSystemCookie(kTestCookieURL, "abc", "def");
+ EXPECT_EQ(1U, cookies.size());
+ SetSystemCookie(kTestCookieURL, "abc", "ghi");
+ EXPECT_EQ(3U, cookies.size());
+ EXPECT_EQ(3U, removes.size());
+ EXPECT_EQ("abc", cookies[1].Name());
+ EXPECT_EQ("def", cookies[1].Value());
+ EXPECT_TRUE(removes[1]);
+ EXPECT_EQ("abc", cookies[2].Name());
+ EXPECT_EQ("ghi", cookies[2].Value());
+ EXPECT_FALSE(removes[2]);
+
+ DeleteSystemCookie(kTestCookieURL, "abc");
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, NotifyOnDelete) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ backend_->RunLoadedCallback();
+ std::vector<net::CanonicalCookie> cookies;
+ std::vector<bool> removes;
+ SetSystemCookie(kTestCookieURL, "abc", "def");
+ scoped_ptr<net::CookieStore::CookieChangedSubscription> handle =
+ store_->AddCallbackForCookie(
+ kTestCookieURL, "abc",
+ base::Bind(&RecordCookieChanges, &cookies, &removes));
+ EXPECT_EQ(0U, cookies.size());
+ DeleteSystemCookie(kTestCookieURL, "abc");
+ EXPECT_EQ(1U, cookies.size());
+ EXPECT_EQ(1U, removes.size());
+ EXPECT_TRUE(removes[0]);
+ SetSystemCookie(kTestCookieURL, "abc", "def");
+ EXPECT_EQ(2U, cookies.size());
+ EXPECT_EQ(2U, removes.size());
+ EXPECT_FALSE(removes[1]);
+ DeleteSystemCookie(kTestCookieURL, "abc");
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, NoNotifyOnNoChange) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ backend_->RunLoadedCallback();
+ std::vector<net::CanonicalCookie> cookies;
+ scoped_ptr<net::CookieStore::CookieChangedSubscription> handle =
+ store_->AddCallbackForCookie(
+ kTestCookieURL, "abc",
+ base::Bind(&RecordCookieChanges, &cookies, nullptr));
+ EXPECT_EQ(0U, cookies.size());
+ SetSystemCookie(kTestCookieURL, "abc", "def");
+ EXPECT_EQ(1U, cookies.size());
+ SetSystemCookie(kTestCookieURL, "abc", "def");
+ EXPECT_EQ(1U, cookies.size());
+ DeleteSystemCookie(kTestCookieURL, "abc");
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, MultipleNotifies) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ backend_->RunLoadedCallback();
+ std::vector<net::CanonicalCookie> cookies;
+ std::vector<net::CanonicalCookie> cookies2;
+ std::vector<net::CanonicalCookie> cookies3;
+ std::vector<net::CanonicalCookie> cookies4;
+ scoped_ptr<net::CookieStore::CookieChangedSubscription> handle =
+ store_->AddCallbackForCookie(
+ kTestCookieURL, "abc",
+ base::Bind(&RecordCookieChanges, &cookies, nullptr));
+ scoped_ptr<net::CookieStore::CookieChangedSubscription> handle2 =
+ store_->AddCallbackForCookie(
+ kTestCookieURL2, "abc",
+ base::Bind(&RecordCookieChanges, &cookies2, nullptr));
+ scoped_ptr<net::CookieStore::CookieChangedSubscription> handle3 =
+ store_->AddCallbackForCookie(
+ kTestCookieURL3, "abc",
+ base::Bind(&RecordCookieChanges, &cookies3, nullptr));
+ scoped_ptr<net::CookieStore::CookieChangedSubscription> handle4 =
+ store_->AddCallbackForCookie(
+ kTestCookieURL4, "abc",
+ base::Bind(&RecordCookieChanges, &cookies4, nullptr));
+ SetSystemCookie(kTestCookieURL, "abc", "def");
+ SetSystemCookie(kTestCookieURL2, "abc", "def");
+ SetSystemCookie(kTestCookieURL3, "abc", "def");
+ SetSystemCookie(kTestCookieURL4, "abc", "def");
+ EXPECT_EQ(2U, cookies.size());
+ EXPECT_EQ(2U, cookies2.size());
+ EXPECT_EQ(1U, cookies3.size());
+ EXPECT_EQ(1U, cookies4.size());
+ DeleteSystemCookie(kTestCookieURL, "abc");
+ DeleteSystemCookie(kTestCookieURL2, "abc");
+ DeleteSystemCookie(kTestCookieURL3, "abc");
+ DeleteSystemCookie(kTestCookieURL4, "abc");
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, LessSpecificNestedCookie) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ backend_->RunLoadedCallback();
+ std::vector<net::CanonicalCookie> cookies;
+ SetSystemCookie(kTestCookieURL2, "abc", "def");
+ scoped_ptr<net::CookieStore::CookieChangedSubscription> handle =
+ store_->AddCallbackForCookie(
+ kTestCookieURL2, "abc",
+ base::Bind(&RecordCookieChanges, &cookies, nullptr));
+ EXPECT_EQ(0U, cookies.size());
+ SetSystemCookie(kTestCookieURL3, "abc", "ghi");
+ EXPECT_EQ(1U, cookies.size());
+ DeleteSystemCookie(kTestCookieURL, "abc");
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, MoreSpecificNestedCookie) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ backend_->RunLoadedCallback();
+ std::vector<net::CanonicalCookie> cookies;
+ SetSystemCookie(kTestCookieURL3, "abc", "def");
+ scoped_ptr<net::CookieStore::CookieChangedSubscription> handle =
+ store_->AddCallbackForCookie(
+ kTestCookieURL2, "abc",
+ base::Bind(&RecordCookieChanges, &cookies, nullptr));
+ EXPECT_EQ(0U, cookies.size());
+ SetSystemCookie(kTestCookieURL2, "abc", "ghi");
+ EXPECT_EQ(1U, cookies.size());
+ DeleteSystemCookie(kTestCookieURL, "abc");
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, MoreSpecificNestedCookieWithSameValue) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ backend_->RunLoadedCallback();
+ std::vector<net::CanonicalCookie> cookies;
+ SetSystemCookie(kTestCookieURL3, "abc", "def");
+ scoped_ptr<net::CookieStore::CookieChangedSubscription> handle =
+ store_->AddCallbackForCookie(
+ kTestCookieURL2, "abc",
+ base::Bind(&RecordCookieChanges, &cookies, nullptr));
+ EXPECT_EQ(0U, cookies.size());
+ SetSystemCookie(kTestCookieURL2, "abc", "def");
+ EXPECT_EQ(1U, cookies.size());
+ DeleteSystemCookie(kTestCookieURL, "abc");
+ store_->UnSynchronize();
+}
+
+TEST_F(CookieStoreIOSWithBackend, RemoveCallback) {
+ CookieStoreIOS::SwitchSynchronizedStore(nullptr, store_.get());
+ backend_->RunLoadedCallback();
+ std::vector<net::CanonicalCookie> cookies;
+ SetSystemCookie(kTestCookieURL, "abc", "def");
+ scoped_ptr<net::CookieStore::CookieChangedSubscription> handle =
+ store_->AddCallbackForCookie(
+ kTestCookieURL, "abc",
+ base::Bind(&RecordCookieChanges, &cookies, nullptr));
+ EXPECT_EQ(0U, cookies.size());
+ SetSystemCookie(kTestCookieURL, "abc", "ghi");
+ EXPECT_EQ(2U, cookies.size());
+ // this deletes the callback
+ handle.reset();
+ SetSystemCookie(kTestCookieURL, "abc", "jkl");
+ EXPECT_EQ(2U, cookies.size());
+ DeleteSystemCookie(kTestCookieURL, "abc");
+ store_->UnSynchronize();
+}
+
+} // namespace net
diff --git a/ios/net/ios_net.gyp b/ios/net/ios_net.gyp
index ed1a582..6208ce9 100644
--- a/ios/net/ios_net.gyp
+++ b/ios/net/ios_net.gyp
@@ -29,6 +29,8 @@
'cookies/cookie_cache.h',
'cookies/cookie_creation_time_manager.h',
'cookies/cookie_creation_time_manager.mm',
+ 'cookies/cookie_store_ios.h',
+ 'cookies/cookie_store_ios.mm',
'cookies/cookie_store_ios_client.h',
'cookies/cookie_store_ios_client.mm',
'cookies/system_cookie_util.h',
diff --git a/ios/net/ios_net_unittests.gyp b/ios/net/ios_net_unittests.gyp
index 42a19ec..8818b33 100644
--- a/ios/net/ios_net_unittests.gyp
+++ b/ios/net/ios_net_unittests.gyp
@@ -24,6 +24,7 @@
'clients/crn_forwarding_network_client_factory_unittest.mm',
'cookies/cookie_cache_unittest.cc',
'cookies/cookie_creation_time_manager_unittest.mm',
+ 'cookies/cookie_store_ios_unittest.mm',
'cookies/system_cookie_util_unittest.mm',
'nsurlrequest_util_unittest.mm',
'protocol_handler_util_unittest.mm',