blob: 2ea0715a4cbaaa30036ee3c132e3e287d928b7eb [file] [log] [blame]
// 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 <stddef.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/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/observer_list.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/sys_string_conversions.h"
#include "base/task_runner_util.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.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"
#include "ios/net/ios_net_features.h"
#import "net/base/mac/url_conversions.h"
#include "net/cookies/cookie_util.h"
#include "net/cookies/parsed_cookie.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace net {
namespace {
#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();
private:
NotificationTrampoline();
~NotificationTrampoline();
base::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 (auto& observer : observer_list_)
observer.OnSystemCookiesChanged();
}
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.
// Note that the .binarycookies file is present only on iOS8+.
void ClearAllCookiesFromBinaryCookiesFile() {
base::FilePath path = GetBinaryCookiesFilePath();
if (base::PathExists(path)) {
bool success = base::DeleteFile(path, false);
if (!success) {
DLOG(WARNING) << "Failed to remove binarycookies file.";
}
}
}
// 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 !BUILDFLAG(CRONET_BUILD)
// CookieCreationTimeManager is returning creation times that are null.
// Since in Cronet, the cookie store is recreated on startup, let's suppress
// this warning for now.
// TODO(mef): 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(NSHTTPCookieStorage* system_store,
const GURL& url, CookieCreationTimeManager* manager) {
NSArray* cookies = [system_store cookiesForURL:net::NSURLWithGURL(url)];
// Sort cookies by decreasing path length, then creation time, as per RFC6265.
return [cookies sortedArrayUsingFunction:CompareCookies context:manager];
}
// Gets all cookies from the system cookie store.
NSArray* GetAllCookies(NSHTTPCookieStorage* system_store,
CookieCreationTimeManager* manager) {
NSArray* cookies = [system_store cookies];
// 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 BuildCookieLineWithOptions(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 IsCookieCreatedBetweenWithPredicate(
base::Time time_begin,
base::Time time_end,
const net::CookieStore::CookiePredicate& predicate,
NSHTTPCookie* cookie,
base::Time creation_time) {
if (predicate.is_null())
return false;
CanonicalCookie canonical_cookie =
CanonicalCookieFromSystemCookie(cookie, creation_time);
return IsCookieCreatedBetween(time_begin, time_end, cookie, creation_time) &&
predicate.Run(canonical_cookie);
}
// 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);
}
}
// Returns whether the specified cookie line has an explicit Domain attribute or
// not.
bool HasExplicitDomain(const std::string& cookie_line) {
ParsedCookie cookie(cookie_line);
return cookie.HasDomain();
}
} // namespace
#pragma mark -
#pragma mark CookieStoreIOS
CookieStoreIOS::CookieStoreIOS(NSHTTPCookieStorage* cookie_storage)
: CookieStoreIOS(nullptr, cookie_storage) {}
CookieStoreIOS::~CookieStoreIOS() {
NotificationTrampoline::GetInstance()->RemoveObserver(this);
}
// static
void CookieStoreIOS::NotifySystemCookiesChanged() {
NotificationTrampoline::GetInstance()->NotifyCookiesChanged();
}
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());
// 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. The only case when it
// is empty is when the domain attribute is incorrectly formatted.
std::string domain_string(base::SysNSStringToUTF8([cookie domain]));
std::string dummy;
bool has_explicit_domain = HasExplicitDomain(cookie_line);
bool has_valid_domain =
net::cookie_util::GetCookieDomainWithString(url, domain_string, &dummy);
// A cookie can be set if all of:
// a) The cookie line is well-formed
// b) The Domain attribute, if present, was not malformed
// c) At least one of:
// 1) The cookie had no explicit Domain, so the Domain was inferred
// from the URL, or
// 2) The cookie had an explicit Domain for which the URL is allowed
// to set cookies.
bool success = (cookie != nil) && !domain_string.empty() &&
(!has_explicit_domain || has_valid_domain);
if (success) {
[system_store_ setCookie:cookie];
creation_time_manager_->SetCreationTime(
cookie,
creation_time_manager_->MakeUniqueCreationTime(base::Time::Now()));
}
if (!callback.is_null())
callback.Run(success);
}
void CookieStoreIOS::SetCookieWithDetailsAsync(
const GURL& url,
const std::string& name,
const std::string& value,
const std::string& domain,
const std::string& path,
base::Time creation_time,
base::Time expiration_time,
base::Time last_access_time,
bool secure,
bool http_only,
CookieSameSite same_site,
CookiePriority priority,
const SetCookiesCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
// If cookies are not allowed, they are stashed in the CookieMonster, and
// should be written there instead.
DCHECK(SystemCookiesAllowed());
bool success = false;
if (creation_time.is_null())
creation_time = base::Time::Now();
// Validate consistency of passed arguments.
if (ParsedCookie::ParseTokenString(name) != name ||
ParsedCookie::ParseValueString(value) != value ||
ParsedCookie::ParseValueString(domain) != domain ||
ParsedCookie::ParseValueString(path) != path) {
if (!callback.is_null())
callback.Run(false);
return;
}
// Validate passed arguments against URL.
std::string cookie_domain;
std::string cookie_path = CanonicalCookie::CanonPathWithString(url, path);
if ((secure && !url.SchemeIsCryptographic()) ||
!cookie_util::GetCookieDomainWithString(url, domain, &cookie_domain) ||
(!path.empty() && cookie_path != path)) {
if (!callback.is_null())
callback.Run(false);
return;
}
// Canonicalize path again to make sure it escapes characters as needed.
url::Component path_component(0, cookie_path.length());
url::RawCanonOutputT<char> canon_path;
url::Component canon_path_component;
url::CanonicalizePath(cookie_path.data(), path_component, &canon_path,
&canon_path_component);
cookie_path = std::string(canon_path.data() + canon_path_component.begin,
canon_path_component.len);
// First create a CanonicalCookie, to normalize the arguments,
// particularly domain and path, and perform validation.
std::unique_ptr<net::CanonicalCookie> canonical_cookie =
base::MakeUnique<net::CanonicalCookie>(
name, value, cookie_domain, cookie_path, creation_time,
expiration_time, creation_time, secure, http_only, same_site,
priority);
if (canonical_cookie) {
NSHTTPCookie* cookie = SystemCookieFromCanonicalCookie(*canonical_cookie);
if (cookie != nil) {
[system_store_ setCookie:cookie];
creation_time_manager_->SetCreationTime(
cookie, creation_time_manager_->MakeUniqueCreationTime(
canonical_cookie->CreationDate()));
success = true;
}
}
if (!callback.is_null())
callback.Run(success);
}
void CookieStoreIOS::GetCookiesWithOptionsAsync(
const GURL& url,
const net::CookieOptions& options,
const GetCookiesCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
// 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());
// TODO(mkwst): If/when iOS supports Same-Site cookies, we'll need to pass
// options in here as well. https://crbug.com/459154
NSArray* cookies =
GetCookiesForURL(system_store_, url, creation_time_manager_.get());
if (!callback.is_null())
callback.Run(BuildCookieLineWithOptions(cookies, options));
}
void CookieStoreIOS::GetCookieListWithOptionsAsync(
const GURL& url,
const net::CookieOptions& options,
const GetCookieListCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!SystemCookiesAllowed()) {
// If cookies are not allowed, the cookies are stashed in the
// CookieMonster, so get them from there.
cookie_monster_->GetCookieListWithOptionsAsync(url, options, callback);
return;
}
// TODO(mkwst): If/when iOS supports Same-Site cookies, we'll need to pass
// options in here as well. https://crbug.com/459154
NSArray* cookies =
GetCookiesForURL(system_store_, url, creation_time_manager_.get());
net::CookieList cookie_list = CanonicalCookieListFromSystemCookies(cookies);
if (!callback.is_null())
callback.Run(cookie_list);
}
void CookieStoreIOS::GetAllCookiesAsync(const GetCookieListCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!SystemCookiesAllowed()) {
// If cookies are not allowed, the cookies are stashed in the
// CookieMonster, so get them from there.
cookie_monster_->GetAllCookiesAsync(callback);
return;
}
NSArray* cookies = GetAllCookies(system_store_, creation_time_manager_.get());
net::CookieList cookie_list = CanonicalCookieListFromSystemCookies(cookies);
if (!callback.is_null()) {
callback.Run(cookie_list);
}
}
void CookieStoreIOS::DeleteCookieAsync(const GURL& url,
const std::string& cookie_name,
const base::Closure& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
NSArray* cookies =
GetCookiesForURL(system_store_, url, creation_time_manager_.get());
for (NSHTTPCookie* cookie in cookies) {
if ([[cookie name] isEqualToString:base::SysUTF8ToNSString(cookie_name)]) {
[system_store_ deleteCookie:cookie];
creation_time_manager_->DeleteCreationTime(cookie);
}
}
if (!callback.is_null())
callback.Run();
}
void CookieStoreIOS::DeleteCanonicalCookieAsync(
const CanonicalCookie& cookie,
const DeleteCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
// This relies on the fact cookies are given unique creation dates.
CookieFilterFunction filter = base::Bind(
IsCookieCreatedBetween, cookie.CreationDate(), cookie.CreationDate());
DeleteCookiesWithFilter(filter, callback);
}
void CookieStoreIOS::DeleteAllCreatedBetweenAsync(
const base::Time& delete_begin,
const base::Time& delete_end,
const DeleteCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
if (metrics_enabled_)
ResetCookieCountMetrics();
CookieFilterFunction filter = base::Bind(
&IsCookieCreatedBetween, delete_begin, delete_end);
DeleteCookiesWithFilter(filter, callback);
}
void CookieStoreIOS::DeleteAllCreatedBetweenWithPredicateAsync(
const base::Time& delete_begin,
const base::Time& delete_end,
const CookiePredicate& predicate,
const DeleteCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
if (metrics_enabled_)
ResetCookieCountMetrics();
CookieFilterFunction filter = base::Bind(
IsCookieCreatedBetweenWithPredicate, delete_begin, delete_end, predicate);
DeleteCookiesWithFilter(filter, callback);
}
void CookieStoreIOS::DeleteSessionCookiesAsync(const DeleteCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
if (metrics_enabled_)
ResetCookieCountMetrics();
CookieFilterFunction filter = base::Bind(&IsCookieSessionCookie);
DeleteCookiesWithFilter(filter, callback);
}
void CookieStoreIOS::FlushStore(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([system_store_ cookies]);
}
cookie_monster_->FlushStore(closure);
flush_closure_.Cancel();
}
#pragma mark -
#pragma mark Protected methods
CookieStoreIOS::CookieStoreIOS(
net::CookieMonster::PersistentCookieStore* persistent_store,
NSHTTPCookieStorage* system_store)
: cookie_monster_(new net::CookieMonster(persistent_store, nullptr)),
system_store_(system_store),
creation_time_manager_(new CookieCreationTimeManager),
metrics_enabled_(false),
cookie_cache_(new CookieCache()),
weak_factory_(this) {
DCHECK(system_store);
NotificationTrampoline::GetInstance()->AddObserver(this);
cookie_monster_->SetPersistSessionCookies(true);
cookie_monster_->SetForceKeepSessionState();
}
CookieStoreIOS::SetCookiesCallback CookieStoreIOS::WrapSetCallback(
const SetCookiesCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
return base::Bind(&CookieStoreIOS::UpdateCachesAfterSet,
weak_factory_.GetWeakPtr(), callback);
}
CookieStoreIOS::DeleteCallback CookieStoreIOS::WrapDeleteCallback(
const DeleteCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
return base::Bind(&CookieStoreIOS::UpdateCachesAfterDelete,
weak_factory_.GetWeakPtr(), callback);
}
base::Closure CookieStoreIOS::WrapClosure(const base::Closure& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
return base::Bind(&CookieStoreIOS::UpdateCachesAfterClosure,
weak_factory_.GetWeakPtr(), callback);
}
#pragma mark -
#pragma mark Private methods
void CookieStoreIOS::ClearSystemStore() {
DCHECK(thread_checker_.CalledOnValidThread());
base::scoped_nsobject<NSArray> copy(
[[NSArray alloc] initWithArray:[system_store_ cookies]]);
for (NSHTTPCookie* cookie in copy.get())
[system_store_ deleteCookie:cookie];
DCHECK_EQ(0u, [[system_store_ cookies] count]);
creation_time_manager_->Clear();
}
bool CookieStoreIOS::SystemCookiesAllowed() {
DCHECK(thread_checker_.CalledOnValidThread());
return [system_store_ cookieAcceptPolicy] != NSHTTPCookieAcceptPolicyNever;
}
void CookieStoreIOS::WriteToCookieMonster(NSArray* system_cookies) {
DCHECK(thread_checker_.CalledOnValidThread());
// 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());
NSArray* cookies = [system_store_ 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()) {
[system_store_ deleteCookie:cookie];
creation_time_manager_->DeleteCreationTime(cookie);
}
if (!callback.is_null())
callback.Run([to_delete count]);
}
void CookieStoreIOS::OnSystemCookiesChanged() {
DCHECK(thread_checker_.CalledOnValidThread());
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,
net::CookieStore::ChangeCause::UNKNOWN_DELETION);
RunCallbacksForCookies(key.first, key.second, added_cookies,
net::CookieStore::ChangeCause::INSERTED);
}
}
// Do not schedule a flush if one is already scheduled.
if (!flush_closure_.IsCancelled())
return;
flush_closure_.Reset(base::Bind(&CookieStoreIOS::FlushStore,
weak_factory_.GetWeakPtr(), base::Closure()));
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, flush_closure_.callback(), base::TimeDelta::FromSeconds(10));
}
std::unique_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] = base::MakeUnique<CookieChangedCallbackList>();
}
DCHECK(hook_map_.find(key) != hook_map_.end());
return hook_map_[key]->Add(callback);
}
bool CookieStoreIOS::IsEphemeral() {
return cookie_monster_->IsEphemeral();
}
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,
net::CookieStore::ChangeCause cause) {
DCHECK(thread_checker_.CalledOnValidThread());
if (cookies.empty())
return;
std::pair<GURL, std::string> key(url, name);
CookieChangedCallbackList* callbacks = hook_map_[key].get();
for (const auto& cookie : cookies) {
DCHECK_EQ(name, cookie.Name());
callbacks->Notify(cookie, cause);
}
}
bool CookieStoreIOS::GetSystemCookies(
const GURL& gurl,
const std::string& name,
std::vector<net::CanonicalCookie>* cookies) {
DCHECK(thread_checker_.CalledOnValidThread());
NSURL* url = net::NSURLWithGURL(gurl);
NSArray* nscookies = [system_store_ 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,
net::CookieStore::ChangeCause::UNKNOWN_DELETION);
RunCallbacksForCookies(key.first, key.second, added_cookies,
net::CookieStore::ChangeCause::INSERTED);
}
}
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,
weak_factory_.GetWeakPtr(), 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();
if (!callback.is_null())
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, weak_factory_.GetWeakPtr(), key);
cookie_monster_->GetAllCookiesForURLAsync(key.first, callback);
}
}
void CookieStoreIOS::UpdateCachesAfterSet(const SetCookiesCallback& callback,
bool success) {
DCHECK(thread_checker_.CalledOnValidThread());
if (success)
UpdateCachesFromCookieMonster();
if (!callback.is_null())
callback.Run(success);
}
void CookieStoreIOS::UpdateCachesAfterDelete(const DeleteCallback& callback,
int num_deleted) {
DCHECK(thread_checker_.CalledOnValidThread());
UpdateCachesFromCookieMonster();
if (!callback.is_null())
callback.Run(num_deleted);
}
void CookieStoreIOS::UpdateCachesAfterClosure(const base::Closure& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
UpdateCachesFromCookieMonster();
if (!callback.is_null())
callback.Run();
}
net::CookieList
CookieStoreIOS::CanonicalCookieListFromSystemCookies(NSArray* cookies) {
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));
}
return cookie_list;
}
} // namespace net