diff options
author | erg@chromium.org <erg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-11 07:25:40 +0000 |
---|---|---|
committer | erg@chromium.org <erg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-11 07:25:40 +0000 |
commit | f48b943fa405abdbff3278bd6d29bde6d1ef103b (patch) | |
tree | f3ad7676f0e484e5c783ef080cfdfea5cab32f0e /net/base/cookie_monster.cc | |
parent | e0392155775eb3dc066d51e78a320a10627a74ad (diff) | |
download | chromium_src-f48b943fa405abdbff3278bd6d29bde6d1ef103b.zip chromium_src-f48b943fa405abdbff3278bd6d29bde6d1ef103b.tar.gz chromium_src-f48b943fa405abdbff3278bd6d29bde6d1ef103b.tar.bz2 |
More reordering the methods in headers in net/.
BUG=68682
TEST=compiles
Review URL: http://codereview.chromium.org/6186005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@71017 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/base/cookie_monster.cc')
-rw-r--r-- | net/base/cookie_monster.cc | 1761 |
1 files changed, 881 insertions, 880 deletions
diff --git a/net/base/cookie_monster.cc b/net/base/cookie_monster.cc index 85f061d..cc96d44 100644 --- a/net/base/cookie_monster.cc +++ b/net/base/cookie_monster.cc @@ -98,178 +98,26 @@ const int kVlogGarbageCollection = 5; const int kVlogSetCookies = 7; const int kVlogGetCookies = 9; -} // namespace - -// static -bool CookieMonster::enable_file_scheme_ = false; - -// static -void CookieMonster::EnableFileScheme() { - enable_file_scheme_ = true; -} - -CookieMonster::CookieMonster(PersistentCookieStore* store, Delegate* delegate) - : initialized_(false), - expiry_and_key_scheme_(expiry_and_key_default_), - store_(store), - last_access_threshold_( - TimeDelta::FromSeconds(kDefaultAccessUpdateThresholdSeconds)), - delegate_(delegate), - last_statistic_record_time_(Time::Now()) { - InitializeHistograms(); - SetDefaultCookieableSchemes(); -} - -CookieMonster::~CookieMonster() { - DeleteAll(false); -} - -// Initialize all histogram counter variables used in this class. -// -// Normal histogram usage involves using the macros defined in -// histogram.h, which automatically takes care of declaring these -// variables (as statics), initializing them, and accumulating into -// them, all from a single entry point. Unfortunately, that solution -// doesn't work for the CookieMonster, as it's vulnerable to races between -// separate threads executing the same functions and hence initializing the -// same static variables. There isn't a race danger in the histogram -// accumulation calls; they are written to be resilient to simultaneous -// calls from multiple threads. -// -// The solution taken here is to have per-CookieMonster instance -// variables that are constructed during CookieMonster construction. -// Note that these variables refer to the same underlying histogram, -// so we still race (but safely) with other CookieMonster instances -// for accumulation. -// -// To do this we've expanded out the individual histogram macros calls, -// with declarations of the variables in the class decl, initialization here -// (done from the class constructor) and direct calls to the accumulation -// methods where needed. The specific histogram macro calls on which the -// initialization is based are included in comments below. -void CookieMonster::InitializeHistograms() { - // From UMA_HISTOGRAM_CUSTOM_COUNTS - histogram_expiration_duration_minutes_ = base::Histogram::FactoryGet( - "Cookie.ExpirationDurationMinutes", - 1, kMinutesInTenYears, 50, - base::Histogram::kUmaTargetedHistogramFlag); - histogram_between_access_interval_minutes_ = base::Histogram::FactoryGet( - "Cookie.BetweenAccessIntervalMinutes", - 1, kMinutesInTenYears, 50, - base::Histogram::kUmaTargetedHistogramFlag); - histogram_evicted_last_access_minutes_ = base::Histogram::FactoryGet( - "Cookie.EvictedLastAccessMinutes", - 1, kMinutesInTenYears, 50, - base::Histogram::kUmaTargetedHistogramFlag); - histogram_count_ = base::Histogram::FactoryGet( - "Cookie.Count", 1, 4000, 50, - base::Histogram::kUmaTargetedHistogramFlag); - histogram_domain_count_ = base::Histogram::FactoryGet( - "Cookie.DomainCount", 1, 4000, 50, - base::Histogram::kUmaTargetedHistogramFlag); - histogram_etldp1_count_ = base::Histogram::FactoryGet( - "Cookie.Etldp1Count", 1, 4000, 50, - base::Histogram::kUmaTargetedHistogramFlag); - histogram_domain_per_etldp1_count_ = base::Histogram::FactoryGet( - "Cookie.DomainPerEtldp1Count", 1, 4000, 50, - base::Histogram::kUmaTargetedHistogramFlag); - - // From UMA_HISTOGRAM_COUNTS_10000 & UMA_HISTOGRAM_CUSTOM_COUNTS - histogram_number_duplicate_db_cookies_ = base::Histogram::FactoryGet( - "Net.NumDuplicateCookiesInDb", 1, 10000, 50, - base::Histogram::kUmaTargetedHistogramFlag); - - // From UMA_HISTOGRAM_ENUMERATION - histogram_cookie_deletion_cause_ = base::LinearHistogram::FactoryGet( - "Cookie.DeletionCause", 1, - DELETE_COOKIE_LAST_ENTRY - 1, DELETE_COOKIE_LAST_ENTRY, - base::Histogram::kUmaTargetedHistogramFlag); - - // From UMA_HISTOGRAM_{CUSTOM_,}TIMES - histogram_time_get_ = base::Histogram::FactoryTimeGet("Cookie.TimeGet", - base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1), - 50, base::Histogram::kUmaTargetedHistogramFlag); - histogram_time_load_ = base::Histogram::FactoryTimeGet("Cookie.TimeLoad", - base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1), - 50, base::Histogram::kUmaTargetedHistogramFlag); -} - -void CookieMonster::InitStore() { - DCHECK(store_) << "Store must exist to initialize"; - - TimeTicks beginning_time(TimeTicks::Now()); - - // Initialize the store and sync in any saved persistent cookies. We don't - // care if it's expired, insert it so it can be garbage collected, removed, - // and sync'd. - std::vector<CanonicalCookie*> cookies; - // Reserve space for the maximum amount of cookies a database should have. - // This prevents multiple vector growth / copies as we append cookies. - cookies.reserve(kMaxCookies); - store_->Load(&cookies); - - // Avoid ever letting cookies with duplicate creation times into the store; - // that way we don't have to worry about what sections of code are safe - // to call while it's in that state. - std::set<int64> creation_times; - - // Presumably later than any access time in the store. - Time earliest_access_time; - - for (std::vector<CanonicalCookie*>::const_iterator it = cookies.begin(); - it != cookies.end(); ++it) { - int64 cookie_creation_time = (*it)->CreationDate().ToInternalValue(); - - if (creation_times.insert(cookie_creation_time).second) { - InternalInsertCookie(GetKey((*it)->Domain()), *it, false); - const Time cookie_access_time((*it)->LastAccessDate()); - if (earliest_access_time.is_null() || - cookie_access_time < earliest_access_time) - earliest_access_time = cookie_access_time; - } else { - LOG(ERROR) << base::StringPrintf("Found cookies with duplicate creation " - "times in backing store: " - "{name='%s', domain='%s', path='%s'}", - (*it)->Name().c_str(), - (*it)->Domain().c_str(), - (*it)->Path().c_str()); - // We've been given ownership of the cookie and are throwing it - // away; reclaim the space. - delete (*it); - } - } - earliest_access_time_= earliest_access_time; - - // After importing cookies from the PersistentCookieStore, verify that - // none of our other constraints are violated. - // - // In particular, the backing store might have given us duplicate cookies. - EnsureCookiesMapIsValid(); - - histogram_time_load_->AddTime(TimeTicks::Now() - beginning_time); +// Mozilla sorts on the path length (longest first), and then it +// sorts by creation time (oldest first). +// The RFC says the sort order for the domain attribute is undefined. +bool CookieSorter(CookieMonster::CanonicalCookie* cc1, + CookieMonster::CanonicalCookie* cc2) { + if (cc1->Path().length() == cc2->Path().length()) + return cc1->CreationDate() < cc2->CreationDate(); + return cc1->Path().length() > cc2->Path().length(); } -void CookieMonster::EnsureCookiesMapIsValid() { - lock_.AssertAcquired(); - - int num_duplicates_trimmed = 0; - - // Iterate through all the of the cookies, grouped by host. - CookieMap::iterator prev_range_end = cookies_.begin(); - while (prev_range_end != cookies_.end()) { - CookieMap::iterator cur_range_begin = prev_range_end; - const std::string key = cur_range_begin->first; // Keep a copy. - CookieMap::iterator cur_range_end = cookies_.upper_bound(key); - prev_range_end = cur_range_end; - - // Ensure no equivalent cookies for this host. - num_duplicates_trimmed += - TrimDuplicateCookiesForKey(key, cur_range_begin, cur_range_end); - } +bool LRUCookieSorter(const CookieMonster::CookieMap::iterator& it1, + const CookieMonster::CookieMap::iterator& it2) { + // Cookies accessed less recently should be deleted first. + if (it1->second->LastAccessDate() != it2->second->LastAccessDate()) + return it1->second->LastAccessDate() < it2->second->LastAccessDate(); - // Record how many duplicates were found in the database. - // See InitializeHistograms() for details. - histogram_cookie_deletion_cause_->Add(num_duplicates_trimmed); + // In rare cases we might have two cookies with identical last access times. + // To preserve the stability of the sort, in these cases prefer to delete + // older cookies over newer ones. CreationDate() is guaranteed to be unique. + return it1->second->CreationDate() < it2->second->CreationDate(); } // Our strategy to find duplicates is: @@ -277,7 +125,7 @@ void CookieMonster::EnsureCookiesMapIsValid() { // {list of cookies with this signature, sorted by creation time}. // (2) For each list with more than 1 entry, keep the cookie having the // most recent creation time, and delete the others. -namespace { +// // Two cookies are considered equivalent if they have the same domain, // name, and path. struct CookieSignature { @@ -309,252 +157,12 @@ struct CookieSignature { std::string domain; std::string path; }; -} - -int CookieMonster::TrimDuplicateCookiesForKey( - const std::string& key, - CookieMap::iterator begin, - CookieMap::iterator end) { - lock_.AssertAcquired(); - - // Set of cookies ordered by creation time. - typedef std::set<CookieMap::iterator, OrderByCreationTimeDesc> CookieSet; - - // Helper map we populate to find the duplicates. - typedef std::map<CookieSignature, CookieSet> EquivalenceMap; - EquivalenceMap equivalent_cookies; - - // The number of duplicate cookies that have been found. - int num_duplicates = 0; - - // Iterate through all of the cookies in our range, and insert them into - // the equivalence map. - for (CookieMap::iterator it = begin; it != end; ++it) { - DCHECK_EQ(key, it->first); - CanonicalCookie* cookie = it->second; - - CookieSignature signature(cookie->Name(), cookie->Domain(), - cookie->Path()); - CookieSet& set = equivalent_cookies[signature]; - - // We found a duplicate! - if (!set.empty()) - num_duplicates++; - - // We save the iterator into |cookies_| rather than the actual cookie - // pointer, since we may need to delete it later. - bool insert_success = set.insert(it).second; - DCHECK(insert_success) << - "Duplicate creation times found in duplicate cookie name scan."; - } - - // If there were no duplicates, we are done! - if (num_duplicates == 0) - return 0; - - // Make sure we find everything below that we did above. - int num_duplicates_found = 0; - - // Otherwise, delete all the duplicate cookies, both from our in-memory store - // and from the backing store. - for (EquivalenceMap::iterator it = equivalent_cookies.begin(); - it != equivalent_cookies.end(); - ++it) { - const CookieSignature& signature = it->first; - CookieSet& dupes = it->second; - - if (dupes.size() <= 1) - continue; // This cookiename/path has no duplicates. - num_duplicates_found += dupes.size() - 1; - - // Since |dups| is sorted by creation time (descending), the first cookie - // is the most recent one, so we will keep it. The rest are duplicates. - dupes.erase(dupes.begin()); - - LOG(ERROR) << base::StringPrintf( - "Found %d duplicate cookies for host='%s', " - "with {name='%s', domain='%s', path='%s'}", - static_cast<int>(dupes.size()), - key.c_str(), - signature.name.c_str(), - signature.domain.c_str(), - signature.path.c_str()); - - // Remove all the cookies identified by |dupes|. It is valid to delete our - // list of iterators one at a time, since |cookies_| is a multimap (they - // don't invalidate existing iterators following deletion). - for (CookieSet::iterator dupes_it = dupes.begin(); - dupes_it != dupes.end(); - ++dupes_it) { - InternalDeleteCookie(*dupes_it, true /*sync_to_store*/, - DELETE_COOKIE_DUPLICATE_IN_BACKING_STORE); - } - } - DCHECK_EQ(num_duplicates, num_duplicates_found); - - return num_duplicates; -} - -void CookieMonster::SetDefaultCookieableSchemes() { - // Note: file must be the last scheme. - static const char* kDefaultCookieableSchemes[] = { "http", "https", "file" }; - int num_schemes = enable_file_scheme_ ? 3 : 2; - SetCookieableSchemes(kDefaultCookieableSchemes, num_schemes); -} - -void CookieMonster::SetExpiryAndKeyScheme(ExpiryAndKeyScheme key_scheme) { - DCHECK(!initialized_); - expiry_and_key_scheme_ = key_scheme; -} - -void CookieMonster::SetClearPersistentStoreOnExit(bool clear_local_store) { - if(store_) - store_->SetClearLocalStateOnExit(clear_local_store); -} - -void CookieMonster::FlushStore(Task* completion_task) { - AutoLock autolock(lock_); - if (initialized_ && store_) - store_->Flush(completion_task); - else if (completion_task) - MessageLoop::current()->PostTask(FROM_HERE, completion_task); -} - -// The system resolution is not high enough, so we can have multiple -// set cookies that result in the same system time. When this happens, we -// increment by one Time unit. Let's hope computers don't get too fast. -Time CookieMonster::CurrentTime() { - return std::max(Time::Now(), - Time::FromInternalValue(last_time_seen_.ToInternalValue() + 1)); -} - -// Parse a cookie expiration time. We try to be lenient, but we need to -// assume some order to distinguish the fields. The basic rules: -// - The month name must be present and prefix the first 3 letters of the -// full month name (jan for January, jun for June). -// - If the year is <= 2 digits, it must occur after the day of month. -// - The time must be of the format hh:mm:ss. -// An average cookie expiration will look something like this: -// Sat, 15-Apr-17 21:01:22 GMT -Time CookieMonster::ParseCookieTime(const std::string& time_string) { - static const char* kMonths[] = { "jan", "feb", "mar", "apr", "may", "jun", - "jul", "aug", "sep", "oct", "nov", "dec" }; - static const int kMonthsLen = arraysize(kMonths); - // We want to be pretty liberal, and support most non-ascii and non-digit - // characters as a delimiter. We can't treat : as a delimiter, because it - // is the delimiter for hh:mm:ss, and we want to keep this field together. - // We make sure to include - and +, since they could prefix numbers. - // If the cookie attribute came in in quotes (ex expires="XXX"), the quotes - // will be preserved, and we will get them here. So we make sure to include - // quote characters, and also \ for anything that was internally escaped. - static const char* kDelimiters = "\t !\"#$%&'()*+,-./;<=>?@[\\]^_`{|}~"; - - Time::Exploded exploded = {0}; - - StringTokenizer tokenizer(time_string, kDelimiters); - - bool found_day_of_month = false; - bool found_month = false; - bool found_time = false; - bool found_year = false; - - while (tokenizer.GetNext()) { - const std::string token = tokenizer.token(); - DCHECK(!token.empty()); - bool numerical = IsAsciiDigit(token[0]); - - // String field - if (!numerical) { - if (!found_month) { - for (int i = 0; i < kMonthsLen; ++i) { - // Match prefix, so we could match January, etc - if (base::strncasecmp(token.c_str(), kMonths[i], 3) == 0) { - exploded.month = i + 1; - found_month = true; - break; - } - } - } else { - // If we've gotten here, it means we've already found and parsed our - // month, and we have another string, which we would expect to be the - // the time zone name. According to the RFC and my experiments with - // how sites format their expirations, we don't have much of a reason - // to support timezones. We don't want to ever barf on user input, - // but this DCHECK should pass for well-formed data. - // DCHECK(token == "GMT"); - } - // Numeric field w/ a colon - } else if (token.find(':') != std::string::npos) { - if (!found_time && -#ifdef COMPILER_MSVC - sscanf_s( -#else - sscanf( -#endif - token.c_str(), "%2u:%2u:%2u", &exploded.hour, - &exploded.minute, &exploded.second) == 3) { - found_time = true; - } else { - // We should only ever encounter one time-like thing. If we're here, - // it means we've found a second, which shouldn't happen. We keep - // the first. This check should be ok for well-formed input: - // NOTREACHED(); - } - // Numeric field - } else { - // Overflow with atoi() is unspecified, so we enforce a max length. - if (!found_day_of_month && token.length() <= 2) { - exploded.day_of_month = atoi(token.c_str()); - found_day_of_month = true; - } else if (!found_year && token.length() <= 5) { - exploded.year = atoi(token.c_str()); - found_year = true; - } else { - // If we're here, it means we've either found an extra numeric field, - // or a numeric field which was too long. For well-formed input, the - // following check would be reasonable: - // NOTREACHED(); - } - } - } - - if (!found_day_of_month || !found_month || !found_time || !found_year) { - // We didn't find all of the fields we need. For well-formed input, the - // following check would be reasonable: - // NOTREACHED() << "Cookie parse expiration failed: " << time_string; - return Time(); - } - - // Normalize the year to expand abbreviated years to the full year. - if (exploded.year >= 69 && exploded.year <= 99) - exploded.year += 1900; - if (exploded.year >= 0 && exploded.year <= 68) - exploded.year += 2000; - - // If our values are within their correct ranges, we got our time. - if (exploded.day_of_month >= 1 && exploded.day_of_month <= 31 && - exploded.month >= 1 && exploded.month <= 12 && - exploded.year >= 1601 && exploded.year <= 30827 && - exploded.hour <= 23 && exploded.minute <= 59 && exploded.second <= 59) { - return Time::FromUTCExploded(exploded); - } - - // One of our values was out of expected range. For well-formed input, - // the following check would be reasonable: - // NOTREACHED() << "Cookie exploded expiration failed: " << time_string; - - return Time(); -} - -bool CookieMonster::DomainIsHostOnly(const std::string& domain_string) { - return (domain_string.empty() || domain_string[0] != '.'); -} // Returns the effective TLD+1 for a given host. This only makes sense for http // and https schemes. For other schemes, the host will be returned unchanged // (minus any leading period). -static std::string GetEffectiveDomain(const std::string& scheme, - const std::string& host) { +std::string GetEffectiveDomain(const std::string& scheme, + const std::string& host) { if (scheme == "http" || scheme == "https") return RegistryControlledDomainService::GetDomainAndRegistry(host); @@ -563,51 +171,14 @@ static std::string GetEffectiveDomain(const std::string& scheme, return host; } -// A wrapper around RegistryControlledDomainService::GetDomainAndRegistry -// to make clear we're creating a key for our local map. Here and -// in FindCookiesForHostAndDomain() are the only two places where -// we need to conditionalize based on key type. -// -// Note that this key algorithm explicitly ignores the scheme. This is -// because when we're entering cookies into the map from the backing store, -// we in general won't have the scheme at that point. -// In practical terms, this means that file cookies will be stored -// in the map either by an empty string or by UNC name (and will be -// limited by kMaxCookiesPerHost), and extension cookies will be stored -// based on the single extension id, as the extension id won't have the -// form of a DNS host and hence GetKey() will return it unchanged. -// -// Arguably the right thing to do here is to make the key -// algorithm dependent on the scheme, and make sure that the scheme is -// available everywhere the key must be obtained (specfically at backing -// store load time). This would require either changing the backing store -// database schema to include the scheme (far more trouble than it's worth), or -// separating out file cookies into their own CookieMonster instance and -// thus restricting each scheme to a single cookie monster (which might -// be worth it, but is still too much trouble to solve what is currently a -// non-problem). -std::string CookieMonster::GetKey(const std::string& domain) const { - if (expiry_and_key_scheme_ == EKS_DISCARD_RECENT_AND_PURGE_DOMAIN) - return domain; - - std::string effective_domain( - RegistryControlledDomainService::GetDomainAndRegistry(domain)); - if (effective_domain.empty()) - effective_domain = domain; - - if (!effective_domain.empty() && effective_domain[0] == '.') - return effective_domain.substr(1); - return effective_domain; -} - // Determine the actual cookie domain based on the domain string passed // (if any) and the URL from which the cookie came. // On success returns true, and sets cookie_domain to either a // -host cookie domain (ex: "google.com") // -domain cookie domain (ex: ".google.com") -static bool GetCookieDomainWithString(const GURL& url, - const std::string& domain_string, - std::string* result) { +bool GetCookieDomainWithString(const GURL& url, + const std::string& domain_string, + std::string* result) { const std::string url_host(url.host()); // If no domain was specified in the domain string, default to a host cookie. @@ -659,17 +230,17 @@ static bool GetCookieDomainWithString(const GURL& url, } // Determine the cookie domain to use for setting the specified cookie. -static bool GetCookieDomain(const GURL& url, - const CookieMonster::ParsedCookie& pc, - std::string* result) { +bool GetCookieDomain(const GURL& url, + const CookieMonster::ParsedCookie& pc, + std::string* result) { std::string domain_string; if (pc.HasDomain()) domain_string = pc.Domain(); return GetCookieDomainWithString(url, domain_string, result); } -static std::string CanonPathWithString(const GURL& url, - const std::string& path_string) { +std::string CanonPathWithString(const GURL& url, + const std::string& path_string) { // The RFC says the path should be a prefix of the current URL path. // However, Mozilla allows you to set any path for compatibility with // broken websites. We unfortunately will mimic this behavior. We try @@ -698,16 +269,16 @@ static std::string CanonPathWithString(const GURL& url, return url_path.substr(0, idx); } -static std::string CanonPath(const GURL& url, - const CookieMonster::ParsedCookie& pc) { +std::string CanonPath(const GURL& url, + const CookieMonster::ParsedCookie& pc) { std::string path_string; if (pc.HasPath()) path_string = pc.Path(); return CanonPathWithString(url, path_string); } -static Time CanonExpirationInternal(const CookieMonster::ParsedCookie& pc, - const Time& current) { +Time CanonExpirationInternal(const CookieMonster::ParsedCookie& pc, + const Time& current) { // First, try the Max-Age attribute. uint64 max_age = 0; if (pc.HasMaxAge() && @@ -728,9 +299,9 @@ static Time CanonExpirationInternal(const CookieMonster::ParsedCookie& pc, return Time(); } -static Time CanonExpiration(const CookieMonster::ParsedCookie& pc, - const Time& current, - const CookieOptions& options) { +Time CanonExpiration(const CookieMonster::ParsedCookie& pc, + const Time& current, + const CookieOptions& options) { Time expiration_time = CanonExpirationInternal(pc, current); if (options.force_session()) { @@ -743,106 +314,195 @@ static Time CanonExpiration(const CookieMonster::ParsedCookie& pc, return expiration_time; } -bool CookieMonster::HasCookieableScheme(const GURL& url) { - lock_.AssertAcquired(); +// Helper for GarbageCollection. If |cookie_its->size() > num_max|, remove the +// |num_max - num_purge| most recently accessed cookies from cookie_its. +// (In other words, leave the entries that are candidates for +// eviction in cookie_its.) The cookies returned will be in order sorted by +// access time, least recently accessed first. The access time of the least +// recently accessed entry not returned will be placed in +// |*lra_removed| if that pointer is set. FindLeastRecentlyAccessed +// returns false if no manipulation is done (because the list size is less +// than num_max), true otherwise. +bool FindLeastRecentlyAccessed( + size_t num_max, + size_t num_purge, + Time* lra_removed, + std::vector<CookieMonster::CookieMap::iterator>* cookie_its) { + DCHECK_LE(num_purge, num_max); + if (cookie_its->size() > num_max) { + VLOG(kVlogGarbageCollection) + << "FindLeastRecentlyAccessed() Deep Garbage Collect."; + num_purge += cookie_its->size() - num_max; + DCHECK_GT(cookie_its->size(), num_purge); - // Make sure the request is on a cookie-able url scheme. - for (size_t i = 0; i < cookieable_schemes_.size(); ++i) { - // We matched a scheme. - if (url.SchemeIs(cookieable_schemes_[i].c_str())) { - // We've matched a supported scheme. - return true; - } + // Add 1 so that we can get the last time left in the store. + std::partial_sort(cookie_its->begin(), cookie_its->begin() + num_purge + 1, + cookie_its->end(), LRUCookieSorter); + *lra_removed = + (*(cookie_its->begin() + num_purge))->second->LastAccessDate(); + cookie_its->erase(cookie_its->begin() + num_purge, cookie_its->end()); + return true; } - - // The scheme didn't match any in our whitelist. - VLOG(kVlogPerCookieMonster) << "WARNING: Unsupported cookie scheme: " - << url.scheme(); return false; } -void CookieMonster::SetCookieableSchemes( - const char* schemes[], size_t num_schemes) { - AutoLock autolock(lock_); +} // namespace - // Cookieable Schemes must be set before first use of function. - DCHECK(!initialized_); +// static +bool CookieMonster::enable_file_scheme_ = false; - cookieable_schemes_.clear(); - cookieable_schemes_.insert(cookieable_schemes_.end(), - schemes, schemes + num_schemes); +CookieMonster::CookieMonster(PersistentCookieStore* store, Delegate* delegate) + : initialized_(false), + expiry_and_key_scheme_(expiry_and_key_default_), + store_(store), + last_access_threshold_( + TimeDelta::FromSeconds(kDefaultAccessUpdateThresholdSeconds)), + delegate_(delegate), + last_statistic_record_time_(Time::Now()) { + InitializeHistograms(); + SetDefaultCookieableSchemes(); } -bool CookieMonster::SetCookieWithCreationTimeAndOptions( - const GURL& url, - const std::string& cookie_line, - const Time& creation_time_or_null, - const CookieOptions& options) { - lock_.AssertAcquired(); +CookieMonster::CookieMonster(PersistentCookieStore* store, + Delegate* delegate, + int last_access_threshold_milliseconds) + : initialized_(false), + expiry_and_key_scheme_(expiry_and_key_default_), + store_(store), + last_access_threshold_(base::TimeDelta::FromMilliseconds( + last_access_threshold_milliseconds)), + delegate_(delegate), + last_statistic_record_time_(base::Time::Now()) { + InitializeHistograms(); + SetDefaultCookieableSchemes(); +} - VLOG(kVlogSetCookies) << "SetCookie() line: " << cookie_line; +// Parse a cookie expiration time. We try to be lenient, but we need to +// assume some order to distinguish the fields. The basic rules: +// - The month name must be present and prefix the first 3 letters of the +// full month name (jan for January, jun for June). +// - If the year is <= 2 digits, it must occur after the day of month. +// - The time must be of the format hh:mm:ss. +// An average cookie expiration will look something like this: +// Sat, 15-Apr-17 21:01:22 GMT +Time CookieMonster::ParseCookieTime(const std::string& time_string) { + static const char* kMonths[] = { "jan", "feb", "mar", "apr", "may", "jun", + "jul", "aug", "sep", "oct", "nov", "dec" }; + static const int kMonthsLen = arraysize(kMonths); + // We want to be pretty liberal, and support most non-ascii and non-digit + // characters as a delimiter. We can't treat : as a delimiter, because it + // is the delimiter for hh:mm:ss, and we want to keep this field together. + // We make sure to include - and +, since they could prefix numbers. + // If the cookie attribute came in in quotes (ex expires="XXX"), the quotes + // will be preserved, and we will get them here. So we make sure to include + // quote characters, and also \ for anything that was internally escaped. + static const char* kDelimiters = "\t !\"#$%&'()*+,-./;<=>?@[\\]^_`{|}~"; - Time creation_time = creation_time_or_null; - if (creation_time.is_null()) { - creation_time = CurrentTime(); - last_time_seen_ = creation_time; - } + Time::Exploded exploded = {0}; - // Parse the cookie. - ParsedCookie pc(cookie_line); + StringTokenizer tokenizer(time_string, kDelimiters); - if (!pc.IsValid()) { - VLOG(kVlogSetCookies) << "WARNING: Couldn't parse cookie"; - return false; - } + bool found_day_of_month = false; + bool found_month = false; + bool found_time = false; + bool found_year = false; - if (options.exclude_httponly() && pc.IsHttpOnly()) { - VLOG(kVlogSetCookies) << "SetCookie() not setting httponly cookie"; - return false; - } + while (tokenizer.GetNext()) { + const std::string token = tokenizer.token(); + DCHECK(!token.empty()); + bool numerical = IsAsciiDigit(token[0]); - std::string cookie_domain; - if (!GetCookieDomain(url, pc, &cookie_domain)) { - return false; + // String field + if (!numerical) { + if (!found_month) { + for (int i = 0; i < kMonthsLen; ++i) { + // Match prefix, so we could match January, etc + if (base::strncasecmp(token.c_str(), kMonths[i], 3) == 0) { + exploded.month = i + 1; + found_month = true; + break; + } + } + } else { + // If we've gotten here, it means we've already found and parsed our + // month, and we have another string, which we would expect to be the + // the time zone name. According to the RFC and my experiments with + // how sites format their expirations, we don't have much of a reason + // to support timezones. We don't want to ever barf on user input, + // but this DCHECK should pass for well-formed data. + // DCHECK(token == "GMT"); + } + // Numeric field w/ a colon + } else if (token.find(':') != std::string::npos) { + if (!found_time && +#ifdef COMPILER_MSVC + sscanf_s( +#else + sscanf( +#endif + token.c_str(), "%2u:%2u:%2u", &exploded.hour, + &exploded.minute, &exploded.second) == 3) { + found_time = true; + } else { + // We should only ever encounter one time-like thing. If we're here, + // it means we've found a second, which shouldn't happen. We keep + // the first. This check should be ok for well-formed input: + // NOTREACHED(); + } + // Numeric field + } else { + // Overflow with atoi() is unspecified, so we enforce a max length. + if (!found_day_of_month && token.length() <= 2) { + exploded.day_of_month = atoi(token.c_str()); + found_day_of_month = true; + } else if (!found_year && token.length() <= 5) { + exploded.year = atoi(token.c_str()); + found_year = true; + } else { + // If we're here, it means we've either found an extra numeric field, + // or a numeric field which was too long. For well-formed input, the + // following check would be reasonable: + // NOTREACHED(); + } + } } - std::string cookie_path = CanonPath(url, pc); - - scoped_ptr<CanonicalCookie> cc; - Time cookie_expires = CanonExpiration(pc, creation_time, options); + if (!found_day_of_month || !found_month || !found_time || !found_year) { + // We didn't find all of the fields we need. For well-formed input, the + // following check would be reasonable: + // NOTREACHED() << "Cookie parse expiration failed: " << time_string; + return Time(); + } - cc.reset(new CanonicalCookie(pc.Name(), pc.Value(), cookie_domain, - cookie_path, - pc.IsSecure(), pc.IsHttpOnly(), - creation_time, creation_time, - !cookie_expires.is_null(), cookie_expires)); + // Normalize the year to expand abbreviated years to the full year. + if (exploded.year >= 69 && exploded.year <= 99) + exploded.year += 1900; + if (exploded.year >= 0 && exploded.year <= 68) + exploded.year += 2000; - if (!cc.get()) { - VLOG(kVlogSetCookies) << "WARNING: Failed to allocate CanonicalCookie"; - return false; + // If our values are within their correct ranges, we got our time. + if (exploded.day_of_month >= 1 && exploded.day_of_month <= 31 && + exploded.month >= 1 && exploded.month <= 12 && + exploded.year >= 1601 && exploded.year <= 30827 && + exploded.hour <= 23 && exploded.minute <= 59 && exploded.second <= 59) { + return Time::FromUTCExploded(exploded); } - return SetCanonicalCookie(&cc, creation_time, options); -} -bool CookieMonster::SetCookieWithCreationTime(const GURL& url, - const std::string& cookie_line, - const base::Time& creation_time) { - AutoLock autolock(lock_); + // One of our values was out of expected range. For well-formed input, + // the following check would be reasonable: + // NOTREACHED() << "Cookie exploded expiration failed: " << time_string; - if (!HasCookieableScheme(url)) { - return false; - } + return Time(); +} - InitIfNecessary(); - return SetCookieWithCreationTimeAndOptions(url, cookie_line, creation_time, - CookieOptions()); +bool CookieMonster::DomainIsHostOnly(const std::string& domain_string) { + return (domain_string.empty() || domain_string[0] != '.'); } bool CookieMonster::SetCookieWithDetails( const GURL& url, const std::string& name, const std::string& value, const std::string& domain, const std::string& path, const base::Time& expiration_time, bool secure, bool http_only) { - AutoLock autolock(lock_); if (!HasCookieableScheme(url)) @@ -867,285 +527,63 @@ bool CookieMonster::SetCookieWithDetails( return SetCanonicalCookie(&cc, creation_time, options); } -bool CookieMonster::SetCanonicalCookie(scoped_ptr<CanonicalCookie>* cc, - const Time& creation_time, - const CookieOptions& options) { - const std::string key(GetKey((*cc)->Domain())); - if (DeleteAnyEquivalentCookie(key, **cc, options.exclude_httponly())) { - VLOG(kVlogSetCookies) << "SetCookie() not clobbering httponly cookie"; - return false; - } - - VLOG(kVlogSetCookies) << "SetCookie() key: " << key << " cc: " - << (*cc)->DebugString(); - - // Realize that we might be setting an expired cookie, and the only point - // was to delete the cookie which we've already done. - if (!(*cc)->IsExpired(creation_time)) { - // See InitializeHistograms() for details. - histogram_expiration_duration_minutes_->Add( - ((*cc)->ExpiryDate() - creation_time).InMinutes()); - InternalInsertCookie(key, cc->release(), true); - } - - // We assume that hopefully setting a cookie will be less common than - // querying a cookie. Since setting a cookie can put us over our limits, - // make sure that we garbage collect... We can also make the assumption that - // if a cookie was set, in the common case it will be used soon after, - // and we will purge the expired cookies in GetCookies(). - GarbageCollect(creation_time, key); - - return true; -} - -void CookieMonster::InternalInsertCookie(const std::string& key, - CanonicalCookie* cc, - bool sync_to_store) { - lock_.AssertAcquired(); - - if (cc->IsPersistent() && store_ && sync_to_store) - store_->AddCookie(*cc); - cookies_.insert(CookieMap::value_type(key, cc)); - if (delegate_.get()) - delegate_->OnCookieChanged(*cc, false); -} - -void CookieMonster::InternalUpdateCookieAccessTime(CanonicalCookie* cc, - const Time& current) { - lock_.AssertAcquired(); - - // Based off the Mozilla code. When a cookie has been accessed recently, - // don't bother updating its access time again. This reduces the number of - // updates we do during pageload, which in turn reduces the chance our storage - // backend will hit its batch thresholds and be forced to update. - if ((current - cc->LastAccessDate()) < last_access_threshold_) - return; - - // See InitializeHistograms() for details. - histogram_between_access_interval_minutes_->Add( - (current - cc->LastAccessDate()).InMinutes()); - - cc->SetLastAccessDate(current); - if (cc->IsPersistent() && store_) - store_->UpdateCookieAccessTime(*cc); -} - -void CookieMonster::InternalDeleteCookie(CookieMap::iterator it, - bool sync_to_store, - DeletionCause deletion_cause) { - lock_.AssertAcquired(); - - // See InitializeHistograms() for details. - if (deletion_cause != DELETE_COOKIE_DONT_RECORD) - histogram_cookie_deletion_cause_->Add(deletion_cause); - - CanonicalCookie* cc = it->second; - VLOG(kVlogSetCookies) << "InternalDeleteCookie() cc: " << cc->DebugString(); - - if (cc->IsPersistent() && store_ && sync_to_store) - store_->DeleteCookie(*cc); - if (delegate_.get()) - delegate_->OnCookieChanged(*cc, true); - cookies_.erase(it); - delete cc; -} - -bool CookieMonster::DeleteAnyEquivalentCookie(const std::string& key, - const CanonicalCookie& ecc, - bool skip_httponly) { - lock_.AssertAcquired(); - - bool found_equivalent_cookie = false; - bool skipped_httponly = false; - for (CookieMapItPair its = cookies_.equal_range(key); - its.first != its.second; ) { - CookieMap::iterator curit = its.first; - CanonicalCookie* cc = curit->second; - ++its.first; - if (ecc.IsEquivalent(*cc)) { - // We should never have more than one equivalent cookie, since they should - // overwrite each other. - CHECK(!found_equivalent_cookie) << - "Duplicate equivalent cookies found, cookie store is corrupted."; - if (skip_httponly && cc->IsHttpOnly()) { - skipped_httponly = true; - } else { - InternalDeleteCookie(curit, true, DELETE_COOKIE_OVERWRITE); - } - found_equivalent_cookie = true; - } - } - return skipped_httponly; -} - -static bool LRUCookieSorter(const CookieMonster::CookieMap::iterator& it1, - const CookieMonster::CookieMap::iterator& it2) { - // Cookies accessed less recently should be deleted first. - if (it1->second->LastAccessDate() != it2->second->LastAccessDate()) - return it1->second->LastAccessDate() < it2->second->LastAccessDate(); +CookieList CookieMonster::GetAllCookies() { + AutoLock autolock(lock_); + InitIfNecessary(); - // In rare cases we might have two cookies with identical last access times. - // To preserve the stability of the sort, in these cases prefer to delete - // older cookies over newer ones. CreationDate() is guaranteed to be unique. - return it1->second->CreationDate() < it2->second->CreationDate(); -} + // This function is being called to scrape the cookie list for management UI + // or similar. We shouldn't show expired cookies in this list since it will + // just be confusing to users, and this function is called rarely enough (and + // is already slow enough) that it's OK to take the time to garbage collect + // the expired cookies now. + // + // Note that this does not prune cookies to be below our limits (if we've + // exceeded them) the way that calling GarbageCollect() would. + GarbageCollectExpired(Time::Now(), + CookieMapItPair(cookies_.begin(), cookies_.end()), + NULL); -// Helper for GarbageCollection. If |cookie_its->size() > num_max|, remove the -// |num_max - num_purge| most recently accessed cookies from cookie_its. -// (In other words, leave the entries that are candidates for -// eviction in cookie_its.) The cookies returned will be in order sorted by -// access time, least recently accessed first. The access time of the least -// recently accessed entry not returned will be placed in -// |*lra_removed| if that pointer is set. FindLeastRecentlyAccessed -// returns false if no manipulation is done (because the list size is less -// than num_max), true otherwise. -static bool FindLeastRecentlyAccessed( - size_t num_max, - size_t num_purge, - Time* lra_removed, - std::vector<CookieMonster::CookieMap::iterator>* cookie_its) { - DCHECK_LE(num_purge, num_max); - if (cookie_its->size() > num_max) { - VLOG(kVlogGarbageCollection) - << "FindLeastRecentlyAccessed() Deep Garbage Collect."; - num_purge += cookie_its->size() - num_max; - DCHECK_GT(cookie_its->size(), num_purge); + // Copy the CanonicalCookie pointers from the map so that we can use the same + // sorter as elsewhere, then copy the result out. + std::vector<CanonicalCookie*> cookie_ptrs; + cookie_ptrs.reserve(cookies_.size()); + for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end(); ++it) + cookie_ptrs.push_back(it->second); + std::sort(cookie_ptrs.begin(), cookie_ptrs.end(), CookieSorter); - // Add 1 so that we can get the last time left in the store. - std::partial_sort(cookie_its->begin(), cookie_its->begin() + num_purge + 1, - cookie_its->end(), LRUCookieSorter); - *lra_removed = - (*(cookie_its->begin() + num_purge))->second->LastAccessDate(); - cookie_its->erase(cookie_its->begin() + num_purge, cookie_its->end()); - return true; - } - return false; -} + CookieList cookie_list; + cookie_list.reserve(cookie_ptrs.size()); + for (std::vector<CanonicalCookie*>::const_iterator it = cookie_ptrs.begin(); + it != cookie_ptrs.end(); ++it) + cookie_list.push_back(**it); -int CookieMonster::GarbageCollectDeleteList( - const Time& current, - const Time& keep_accessed_after, - DeletionCause cause, - std::vector<CookieMap::iterator>& cookie_its) { - int num_deleted = 0; - for (std::vector<CookieMap::iterator>::iterator it = cookie_its.begin(); - it != cookie_its.end(); it++) { - if (keep_accessed_after.is_null() || - (*it)->second->LastAccessDate() < keep_accessed_after) { - histogram_evicted_last_access_minutes_->Add( - (current - (*it)->second->LastAccessDate()).InMinutes()); - InternalDeleteCookie((*it), true, cause); - num_deleted++; - } - } - return num_deleted; + return cookie_list; } -// Domain expiry behavior is unchanged by key/expiry scheme (the -// meaning of the key is different, but that's not visible to this -// routine). Global garbage collection is dependent on key/expiry -// scheme in that recently touched cookies are not saved if -// expiry_and_key_scheme_ == EKS_DISCARD_RECENT_AND_PURGE_DOMAIN. -int CookieMonster::GarbageCollect(const Time& current, - const std::string& key) { - lock_.AssertAcquired(); - - int num_deleted = 0; - - // Collect garbage for this key. - if (cookies_.count(key) > kDomainMaxCookies) { - VLOG(kVlogGarbageCollection) << "GarbageCollect() key: " << key; - - std::vector<CookieMap::iterator> cookie_its; - num_deleted += GarbageCollectExpired( - current, cookies_.equal_range(key), &cookie_its); - base::Time oldest_removed; - if (FindLeastRecentlyAccessed(kDomainMaxCookies, kDomainPurgeCookies, - &oldest_removed, &cookie_its)) { - // Delete in two passes so we can figure out what we're nuking - // that would be kept at the global level. - int num_subject_to_global_purge = - GarbageCollectDeleteList( - current, - Time::Now() - TimeDelta::FromDays(kSafeFromGlobalPurgeDays), - DELETE_COOKIE_EVICTED_DOMAIN_PRE_SAFE, - cookie_its); - num_deleted += num_subject_to_global_purge; - // Correct because FindLeastRecentlyAccessed returns a sorted list. - cookie_its.erase(cookie_its.begin(), - cookie_its.begin() + num_subject_to_global_purge); - num_deleted += - GarbageCollectDeleteList( - current, - Time(), - DELETE_COOKIE_EVICTED_DOMAIN_POST_SAFE, - cookie_its); - } - } +CookieList CookieMonster::GetAllCookiesForURLWithOptions( + const GURL& url, + const CookieOptions& options) { + AutoLock autolock(lock_); + InitIfNecessary(); - // Collect garbage for everything. With firefox style we want to - // preserve cookies touched in kSafeFromGlobalPurgeDays, otherwise - // not. - if (cookies_.size() > kMaxCookies && - (expiry_and_key_scheme_ == EKS_DISCARD_RECENT_AND_PURGE_DOMAIN || - earliest_access_time_ < - Time::Now() - TimeDelta::FromDays(kSafeFromGlobalPurgeDays))) { - VLOG(kVlogGarbageCollection) << "GarbageCollect() everything"; - std::vector<CookieMap::iterator> cookie_its; - base::Time oldest_left; - num_deleted += GarbageCollectExpired( - current, CookieMapItPair(cookies_.begin(), cookies_.end()), - &cookie_its); - if (FindLeastRecentlyAccessed(kMaxCookies, kPurgeCookies, - &oldest_left, &cookie_its)) { - Time oldest_safe_cookie( - expiry_and_key_scheme_ == EKS_KEEP_RECENT_AND_PURGE_ETLDP1 ? - (Time::Now() - TimeDelta::FromDays(kSafeFromGlobalPurgeDays)) : - Time()); // Null time == ignore access time. - int num_evicted = GarbageCollectDeleteList( - current, - oldest_safe_cookie, - DELETE_COOKIE_EVICTED_GLOBAL, - cookie_its); + std::vector<CanonicalCookie*> cookie_ptrs; + FindCookiesForHostAndDomain(url, options, false, &cookie_ptrs); + std::sort(cookie_ptrs.begin(), cookie_ptrs.end(), CookieSorter); - // If no cookies were preserved by the time limit, the global last - // access is set to the value returned from FindLeastRecentlyAccessed. - // If the time limit preserved some cookies, we use the last access of - // the oldest preserved cookie. - if (num_evicted == static_cast<int>(cookie_its.size())) { - earliest_access_time_ = oldest_left; - } else { - earliest_access_time_ = - (*(cookie_its.begin() + num_evicted))->second->LastAccessDate(); - } - num_deleted += num_evicted; - } - } + CookieList cookies; + for (std::vector<CanonicalCookie*>::const_iterator it = cookie_ptrs.begin(); + it != cookie_ptrs.end(); it++) + cookies.push_back(**it); - return num_deleted; + return cookies; } -int CookieMonster::GarbageCollectExpired( - const Time& current, - const CookieMapItPair& itpair, - std::vector<CookieMap::iterator>* cookie_its) { - lock_.AssertAcquired(); - - int num_deleted = 0; - for (CookieMap::iterator it = itpair.first, end = itpair.second; it != end;) { - CookieMap::iterator curit = it; - ++it; - - if (curit->second->IsExpired(current)) { - InternalDeleteCookie(curit, true, DELETE_COOKIE_EXPIRED); - ++num_deleted; - } else if (cookie_its) { - cookie_its->push_back(curit); - } - } +CookieList CookieMonster::GetAllCookiesForURL(const GURL& url) { + CookieOptions options; + options.set_include_httponly(); - return num_deleted; + return GetAllCookiesForURLWithOptions(url, options); } int CookieMonster::DeleteAll(bool sync_to_store) { @@ -1239,14 +677,39 @@ bool CookieMonster::DeleteCanonicalCookie(const CanonicalCookie& cookie) { return false; } -// Mozilla sorts on the path length (longest first), and then it -// sorts by creation time (oldest first). -// The RFC says the sort order for the domain attribute is undefined. -static bool CookieSorter(CookieMonster::CanonicalCookie* cc1, - CookieMonster::CanonicalCookie* cc2) { - if (cc1->Path().length() == cc2->Path().length()) - return cc1->CreationDate() < cc2->CreationDate(); - return cc1->Path().length() > cc2->Path().length(); +void CookieMonster::SetCookieableSchemes( + const char* schemes[], size_t num_schemes) { + AutoLock autolock(lock_); + + // Cookieable Schemes must be set before first use of function. + DCHECK(!initialized_); + + cookieable_schemes_.clear(); + cookieable_schemes_.insert(cookieable_schemes_.end(), + schemes, schemes + num_schemes); +} + +void CookieMonster::SetExpiryAndKeyScheme(ExpiryAndKeyScheme key_scheme) { + DCHECK(!initialized_); + expiry_and_key_scheme_ = key_scheme; +} + +void CookieMonster::SetClearPersistentStoreOnExit(bool clear_local_store) { + if(store_) + store_->SetClearLocalStateOnExit(clear_local_store); +} + +// static +void CookieMonster::EnableFileScheme() { + enable_file_scheme_ = true; +} + +void CookieMonster::FlushStore(Task* completion_task) { + AutoLock autolock(lock_); + if (initialized_ && store_) + store_->Flush(completion_task); + else if (completion_task) + MessageLoop::current()->PostTask(FROM_HERE, completion_task); } bool CookieMonster::SetCookieWithOptions(const GURL& url, @@ -1336,64 +799,194 @@ CookieMonster* CookieMonster::GetCookieMonster() { return this; } -CookieList CookieMonster::GetAllCookies() { +CookieMonster::~CookieMonster() { + DeleteAll(false); +} + +bool CookieMonster::SetCookieWithCreationTime(const GURL& url, + const std::string& cookie_line, + const base::Time& creation_time) { AutoLock autolock(lock_); + + if (!HasCookieableScheme(url)) { + return false; + } + InitIfNecessary(); + return SetCookieWithCreationTimeAndOptions(url, cookie_line, creation_time, + CookieOptions()); +} - // This function is being called to scrape the cookie list for management UI - // or similar. We shouldn't show expired cookies in this list since it will - // just be confusing to users, and this function is called rarely enough (and - // is already slow enough) that it's OK to take the time to garbage collect - // the expired cookies now. - // - // Note that this does not prune cookies to be below our limits (if we've - // exceeded them) the way that calling GarbageCollect() would. - GarbageCollectExpired(Time::Now(), - CookieMapItPair(cookies_.begin(), cookies_.end()), - NULL); +void CookieMonster::InitStore() { + DCHECK(store_) << "Store must exist to initialize"; - // Copy the CanonicalCookie pointers from the map so that we can use the same - // sorter as elsewhere, then copy the result out. - std::vector<CanonicalCookie*> cookie_ptrs; - cookie_ptrs.reserve(cookies_.size()); - for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end(); ++it) - cookie_ptrs.push_back(it->second); - std::sort(cookie_ptrs.begin(), cookie_ptrs.end(), CookieSorter); + TimeTicks beginning_time(TimeTicks::Now()); - CookieList cookie_list; - cookie_list.reserve(cookie_ptrs.size()); - for (std::vector<CanonicalCookie*>::const_iterator it = cookie_ptrs.begin(); - it != cookie_ptrs.end(); ++it) - cookie_list.push_back(**it); + // Initialize the store and sync in any saved persistent cookies. We don't + // care if it's expired, insert it so it can be garbage collected, removed, + // and sync'd. + std::vector<CanonicalCookie*> cookies; + // Reserve space for the maximum amount of cookies a database should have. + // This prevents multiple vector growth / copies as we append cookies. + cookies.reserve(kMaxCookies); + store_->Load(&cookies); - return cookie_list; + // Avoid ever letting cookies with duplicate creation times into the store; + // that way we don't have to worry about what sections of code are safe + // to call while it's in that state. + std::set<int64> creation_times; + + // Presumably later than any access time in the store. + Time earliest_access_time; + + for (std::vector<CanonicalCookie*>::const_iterator it = cookies.begin(); + it != cookies.end(); ++it) { + int64 cookie_creation_time = (*it)->CreationDate().ToInternalValue(); + + if (creation_times.insert(cookie_creation_time).second) { + InternalInsertCookie(GetKey((*it)->Domain()), *it, false); + const Time cookie_access_time((*it)->LastAccessDate()); + if (earliest_access_time.is_null() || + cookie_access_time < earliest_access_time) + earliest_access_time = cookie_access_time; + } else { + LOG(ERROR) << base::StringPrintf("Found cookies with duplicate creation " + "times in backing store: " + "{name='%s', domain='%s', path='%s'}", + (*it)->Name().c_str(), + (*it)->Domain().c_str(), + (*it)->Path().c_str()); + // We've been given ownership of the cookie and are throwing it + // away; reclaim the space. + delete (*it); + } + } + earliest_access_time_= earliest_access_time; + + // After importing cookies from the PersistentCookieStore, verify that + // none of our other constraints are violated. + // + // In particular, the backing store might have given us duplicate cookies. + EnsureCookiesMapIsValid(); + + histogram_time_load_->AddTime(TimeTicks::Now() - beginning_time); } -CookieList CookieMonster::GetAllCookiesForURLWithOptions( - const GURL& url, - const CookieOptions& options) { - AutoLock autolock(lock_); - InitIfNecessary(); +void CookieMonster::EnsureCookiesMapIsValid() { + lock_.AssertAcquired(); - std::vector<CanonicalCookie*> cookie_ptrs; - FindCookiesForHostAndDomain(url, options, false, &cookie_ptrs); - std::sort(cookie_ptrs.begin(), cookie_ptrs.end(), CookieSorter); + int num_duplicates_trimmed = 0; - CookieList cookies; - for (std::vector<CanonicalCookie*>::const_iterator it = cookie_ptrs.begin(); - it != cookie_ptrs.end(); it++) - cookies.push_back(**it); + // Iterate through all the of the cookies, grouped by host. + CookieMap::iterator prev_range_end = cookies_.begin(); + while (prev_range_end != cookies_.end()) { + CookieMap::iterator cur_range_begin = prev_range_end; + const std::string key = cur_range_begin->first; // Keep a copy. + CookieMap::iterator cur_range_end = cookies_.upper_bound(key); + prev_range_end = cur_range_end; - return cookies; + // Ensure no equivalent cookies for this host. + num_duplicates_trimmed += + TrimDuplicateCookiesForKey(key, cur_range_begin, cur_range_end); + } + + // Record how many duplicates were found in the database. + // See InitializeHistograms() for details. + histogram_cookie_deletion_cause_->Add(num_duplicates_trimmed); } -CookieList CookieMonster::GetAllCookiesForURL(const GURL& url) { - CookieOptions options; - options.set_include_httponly(); +int CookieMonster::TrimDuplicateCookiesForKey( + const std::string& key, + CookieMap::iterator begin, + CookieMap::iterator end) { + lock_.AssertAcquired(); - return GetAllCookiesForURLWithOptions(url, options); + // Set of cookies ordered by creation time. + typedef std::set<CookieMap::iterator, OrderByCreationTimeDesc> CookieSet; + + // Helper map we populate to find the duplicates. + typedef std::map<CookieSignature, CookieSet> EquivalenceMap; + EquivalenceMap equivalent_cookies; + + // The number of duplicate cookies that have been found. + int num_duplicates = 0; + + // Iterate through all of the cookies in our range, and insert them into + // the equivalence map. + for (CookieMap::iterator it = begin; it != end; ++it) { + DCHECK_EQ(key, it->first); + CanonicalCookie* cookie = it->second; + + CookieSignature signature(cookie->Name(), cookie->Domain(), + cookie->Path()); + CookieSet& set = equivalent_cookies[signature]; + + // We found a duplicate! + if (!set.empty()) + num_duplicates++; + + // We save the iterator into |cookies_| rather than the actual cookie + // pointer, since we may need to delete it later. + bool insert_success = set.insert(it).second; + DCHECK(insert_success) << + "Duplicate creation times found in duplicate cookie name scan."; + } + + // If there were no duplicates, we are done! + if (num_duplicates == 0) + return 0; + + // Make sure we find everything below that we did above. + int num_duplicates_found = 0; + + // Otherwise, delete all the duplicate cookies, both from our in-memory store + // and from the backing store. + for (EquivalenceMap::iterator it = equivalent_cookies.begin(); + it != equivalent_cookies.end(); + ++it) { + const CookieSignature& signature = it->first; + CookieSet& dupes = it->second; + + if (dupes.size() <= 1) + continue; // This cookiename/path has no duplicates. + num_duplicates_found += dupes.size() - 1; + + // Since |dups| is sorted by creation time (descending), the first cookie + // is the most recent one, so we will keep it. The rest are duplicates. + dupes.erase(dupes.begin()); + + LOG(ERROR) << base::StringPrintf( + "Found %d duplicate cookies for host='%s', " + "with {name='%s', domain='%s', path='%s'}", + static_cast<int>(dupes.size()), + key.c_str(), + signature.name.c_str(), + signature.domain.c_str(), + signature.path.c_str()); + + // Remove all the cookies identified by |dupes|. It is valid to delete our + // list of iterators one at a time, since |cookies_| is a multimap (they + // don't invalidate existing iterators following deletion). + for (CookieSet::iterator dupes_it = dupes.begin(); + dupes_it != dupes.end(); + ++dupes_it) { + InternalDeleteCookie(*dupes_it, true /*sync_to_store*/, + DELETE_COOKIE_DUPLICATE_IN_BACKING_STORE); + } + } + DCHECK_EQ(num_duplicates, num_duplicates_found); + + return num_duplicates; } +void CookieMonster::SetDefaultCookieableSchemes() { + // Note: file must be the last scheme. + static const char* kDefaultCookieableSchemes[] = { "http", "https", "file" }; + int num_schemes = enable_file_scheme_ ? 3 : 2; + SetCookieableSchemes(kDefaultCookieableSchemes, num_schemes); +} + + void CookieMonster::FindCookiesForHostAndDomain( const GURL& url, const CookieOptions& options, @@ -1494,6 +1087,349 @@ void CookieMonster::FindCookiesForKey( } } +bool CookieMonster::DeleteAnyEquivalentCookie(const std::string& key, + const CanonicalCookie& ecc, + bool skip_httponly) { + lock_.AssertAcquired(); + + bool found_equivalent_cookie = false; + bool skipped_httponly = false; + for (CookieMapItPair its = cookies_.equal_range(key); + its.first != its.second; ) { + CookieMap::iterator curit = its.first; + CanonicalCookie* cc = curit->second; + ++its.first; + + if (ecc.IsEquivalent(*cc)) { + // We should never have more than one equivalent cookie, since they should + // overwrite each other. + CHECK(!found_equivalent_cookie) << + "Duplicate equivalent cookies found, cookie store is corrupted."; + if (skip_httponly && cc->IsHttpOnly()) { + skipped_httponly = true; + } else { + InternalDeleteCookie(curit, true, DELETE_COOKIE_OVERWRITE); + } + found_equivalent_cookie = true; + } + } + return skipped_httponly; +} + +void CookieMonster::InternalInsertCookie(const std::string& key, + CanonicalCookie* cc, + bool sync_to_store) { + lock_.AssertAcquired(); + + if (cc->IsPersistent() && store_ && sync_to_store) + store_->AddCookie(*cc); + cookies_.insert(CookieMap::value_type(key, cc)); + if (delegate_.get()) + delegate_->OnCookieChanged(*cc, false); +} + +bool CookieMonster::SetCookieWithCreationTimeAndOptions( + const GURL& url, + const std::string& cookie_line, + const Time& creation_time_or_null, + const CookieOptions& options) { + lock_.AssertAcquired(); + + VLOG(kVlogSetCookies) << "SetCookie() line: " << cookie_line; + + Time creation_time = creation_time_or_null; + if (creation_time.is_null()) { + creation_time = CurrentTime(); + last_time_seen_ = creation_time; + } + + // Parse the cookie. + ParsedCookie pc(cookie_line); + + if (!pc.IsValid()) { + VLOG(kVlogSetCookies) << "WARNING: Couldn't parse cookie"; + return false; + } + + if (options.exclude_httponly() && pc.IsHttpOnly()) { + VLOG(kVlogSetCookies) << "SetCookie() not setting httponly cookie"; + return false; + } + + std::string cookie_domain; + if (!GetCookieDomain(url, pc, &cookie_domain)) { + return false; + } + + std::string cookie_path = CanonPath(url, pc); + + scoped_ptr<CanonicalCookie> cc; + Time cookie_expires = CanonExpiration(pc, creation_time, options); + + cc.reset(new CanonicalCookie(pc.Name(), pc.Value(), cookie_domain, + cookie_path, + pc.IsSecure(), pc.IsHttpOnly(), + creation_time, creation_time, + !cookie_expires.is_null(), cookie_expires)); + + if (!cc.get()) { + VLOG(kVlogSetCookies) << "WARNING: Failed to allocate CanonicalCookie"; + return false; + } + return SetCanonicalCookie(&cc, creation_time, options); +} + +bool CookieMonster::SetCanonicalCookie(scoped_ptr<CanonicalCookie>* cc, + const Time& creation_time, + const CookieOptions& options) { + const std::string key(GetKey((*cc)->Domain())); + if (DeleteAnyEquivalentCookie(key, **cc, options.exclude_httponly())) { + VLOG(kVlogSetCookies) << "SetCookie() not clobbering httponly cookie"; + return false; + } + + VLOG(kVlogSetCookies) << "SetCookie() key: " << key << " cc: " + << (*cc)->DebugString(); + + // Realize that we might be setting an expired cookie, and the only point + // was to delete the cookie which we've already done. + if (!(*cc)->IsExpired(creation_time)) { + // See InitializeHistograms() for details. + histogram_expiration_duration_minutes_->Add( + ((*cc)->ExpiryDate() - creation_time).InMinutes()); + InternalInsertCookie(key, cc->release(), true); + } + + // We assume that hopefully setting a cookie will be less common than + // querying a cookie. Since setting a cookie can put us over our limits, + // make sure that we garbage collect... We can also make the assumption that + // if a cookie was set, in the common case it will be used soon after, + // and we will purge the expired cookies in GetCookies(). + GarbageCollect(creation_time, key); + + return true; +} + +void CookieMonster::InternalUpdateCookieAccessTime(CanonicalCookie* cc, + const Time& current) { + lock_.AssertAcquired(); + + // Based off the Mozilla code. When a cookie has been accessed recently, + // don't bother updating its access time again. This reduces the number of + // updates we do during pageload, which in turn reduces the chance our storage + // backend will hit its batch thresholds and be forced to update. + if ((current - cc->LastAccessDate()) < last_access_threshold_) + return; + + // See InitializeHistograms() for details. + histogram_between_access_interval_minutes_->Add( + (current - cc->LastAccessDate()).InMinutes()); + + cc->SetLastAccessDate(current); + if (cc->IsPersistent() && store_) + store_->UpdateCookieAccessTime(*cc); +} + +void CookieMonster::InternalDeleteCookie(CookieMap::iterator it, + bool sync_to_store, + DeletionCause deletion_cause) { + lock_.AssertAcquired(); + + // See InitializeHistograms() for details. + if (deletion_cause != DELETE_COOKIE_DONT_RECORD) + histogram_cookie_deletion_cause_->Add(deletion_cause); + + CanonicalCookie* cc = it->second; + VLOG(kVlogSetCookies) << "InternalDeleteCookie() cc: " << cc->DebugString(); + + if (cc->IsPersistent() && store_ && sync_to_store) + store_->DeleteCookie(*cc); + if (delegate_.get()) + delegate_->OnCookieChanged(*cc, true); + cookies_.erase(it); + delete cc; +} + +// Domain expiry behavior is unchanged by key/expiry scheme (the +// meaning of the key is different, but that's not visible to this +// routine). Global garbage collection is dependent on key/expiry +// scheme in that recently touched cookies are not saved if +// expiry_and_key_scheme_ == EKS_DISCARD_RECENT_AND_PURGE_DOMAIN. +int CookieMonster::GarbageCollect(const Time& current, + const std::string& key) { + lock_.AssertAcquired(); + + int num_deleted = 0; + + // Collect garbage for this key. + if (cookies_.count(key) > kDomainMaxCookies) { + VLOG(kVlogGarbageCollection) << "GarbageCollect() key: " << key; + + std::vector<CookieMap::iterator> cookie_its; + num_deleted += GarbageCollectExpired( + current, cookies_.equal_range(key), &cookie_its); + base::Time oldest_removed; + if (FindLeastRecentlyAccessed(kDomainMaxCookies, kDomainPurgeCookies, + &oldest_removed, &cookie_its)) { + // Delete in two passes so we can figure out what we're nuking + // that would be kept at the global level. + int num_subject_to_global_purge = + GarbageCollectDeleteList( + current, + Time::Now() - TimeDelta::FromDays(kSafeFromGlobalPurgeDays), + DELETE_COOKIE_EVICTED_DOMAIN_PRE_SAFE, + cookie_its); + num_deleted += num_subject_to_global_purge; + // Correct because FindLeastRecentlyAccessed returns a sorted list. + cookie_its.erase(cookie_its.begin(), + cookie_its.begin() + num_subject_to_global_purge); + num_deleted += + GarbageCollectDeleteList( + current, + Time(), + DELETE_COOKIE_EVICTED_DOMAIN_POST_SAFE, + cookie_its); + } + } + + // Collect garbage for everything. With firefox style we want to + // preserve cookies touched in kSafeFromGlobalPurgeDays, otherwise + // not. + if (cookies_.size() > kMaxCookies && + (expiry_and_key_scheme_ == EKS_DISCARD_RECENT_AND_PURGE_DOMAIN || + earliest_access_time_ < + Time::Now() - TimeDelta::FromDays(kSafeFromGlobalPurgeDays))) { + VLOG(kVlogGarbageCollection) << "GarbageCollect() everything"; + std::vector<CookieMap::iterator> cookie_its; + base::Time oldest_left; + num_deleted += GarbageCollectExpired( + current, CookieMapItPair(cookies_.begin(), cookies_.end()), + &cookie_its); + if (FindLeastRecentlyAccessed(kMaxCookies, kPurgeCookies, + &oldest_left, &cookie_its)) { + Time oldest_safe_cookie( + expiry_and_key_scheme_ == EKS_KEEP_RECENT_AND_PURGE_ETLDP1 ? + (Time::Now() - TimeDelta::FromDays(kSafeFromGlobalPurgeDays)) : + Time()); // Null time == ignore access time. + int num_evicted = GarbageCollectDeleteList( + current, + oldest_safe_cookie, + DELETE_COOKIE_EVICTED_GLOBAL, + cookie_its); + + // If no cookies were preserved by the time limit, the global last + // access is set to the value returned from FindLeastRecentlyAccessed. + // If the time limit preserved some cookies, we use the last access of + // the oldest preserved cookie. + if (num_evicted == static_cast<int>(cookie_its.size())) { + earliest_access_time_ = oldest_left; + } else { + earliest_access_time_ = + (*(cookie_its.begin() + num_evicted))->second->LastAccessDate(); + } + num_deleted += num_evicted; + } + } + + return num_deleted; +} + +int CookieMonster::GarbageCollectExpired( + const Time& current, + const CookieMapItPair& itpair, + std::vector<CookieMap::iterator>* cookie_its) { + lock_.AssertAcquired(); + + int num_deleted = 0; + for (CookieMap::iterator it = itpair.first, end = itpair.second; it != end;) { + CookieMap::iterator curit = it; + ++it; + + if (curit->second->IsExpired(current)) { + InternalDeleteCookie(curit, true, DELETE_COOKIE_EXPIRED); + ++num_deleted; + } else if (cookie_its) { + cookie_its->push_back(curit); + } + } + + return num_deleted; +} + +int CookieMonster::GarbageCollectDeleteList( + const Time& current, + const Time& keep_accessed_after, + DeletionCause cause, + std::vector<CookieMap::iterator>& cookie_its) { + int num_deleted = 0; + for (std::vector<CookieMap::iterator>::iterator it = cookie_its.begin(); + it != cookie_its.end(); it++) { + if (keep_accessed_after.is_null() || + (*it)->second->LastAccessDate() < keep_accessed_after) { + histogram_evicted_last_access_minutes_->Add( + (current - (*it)->second->LastAccessDate()).InMinutes()); + InternalDeleteCookie((*it), true, cause); + num_deleted++; + } + } + return num_deleted; +} + +// A wrapper around RegistryControlledDomainService::GetDomainAndRegistry +// to make clear we're creating a key for our local map. Here and +// in FindCookiesForHostAndDomain() are the only two places where +// we need to conditionalize based on key type. +// +// Note that this key algorithm explicitly ignores the scheme. This is +// because when we're entering cookies into the map from the backing store, +// we in general won't have the scheme at that point. +// In practical terms, this means that file cookies will be stored +// in the map either by an empty string or by UNC name (and will be +// limited by kMaxCookiesPerHost), and extension cookies will be stored +// based on the single extension id, as the extension id won't have the +// form of a DNS host and hence GetKey() will return it unchanged. +// +// Arguably the right thing to do here is to make the key +// algorithm dependent on the scheme, and make sure that the scheme is +// available everywhere the key must be obtained (specfically at backing +// store load time). This would require either changing the backing store +// database schema to include the scheme (far more trouble than it's worth), or +// separating out file cookies into their own CookieMonster instance and +// thus restricting each scheme to a single cookie monster (which might +// be worth it, but is still too much trouble to solve what is currently a +// non-problem). +std::string CookieMonster::GetKey(const std::string& domain) const { + if (expiry_and_key_scheme_ == EKS_DISCARD_RECENT_AND_PURGE_DOMAIN) + return domain; + + std::string effective_domain( + RegistryControlledDomainService::GetDomainAndRegistry(domain)); + if (effective_domain.empty()) + effective_domain = domain; + + if (!effective_domain.empty() && effective_domain[0] == '.') + return effective_domain.substr(1); + return effective_domain; +} + +bool CookieMonster::HasCookieableScheme(const GURL& url) { + lock_.AssertAcquired(); + + // Make sure the request is on a cookie-able url scheme. + for (size_t i = 0; i < cookieable_schemes_.size(); ++i) { + // We matched a scheme. + if (url.SchemeIs(cookieable_schemes_[i].c_str())) { + // We've matched a supported scheme. + return true; + } + } + + // The scheme didn't match any in our whitelist. + VLOG(kVlogPerCookieMonster) << "WARNING: Unsupported cookie scheme: " + << url.scheme(); + return false; +} + // Test to see if stats should be recorded, and record them if so. // The goal here is to get sampling for the average browser-hour of // activity. We won't take samples when the web isn't being surfed, @@ -1549,6 +1485,85 @@ void CookieMonster::RecordPeriodicStats(const base::Time& current_time) { last_statistic_record_time_ = current_time; } +// Initialize all histogram counter variables used in this class. +// +// Normal histogram usage involves using the macros defined in +// histogram.h, which automatically takes care of declaring these +// variables (as statics), initializing them, and accumulating into +// them, all from a single entry point. Unfortunately, that solution +// doesn't work for the CookieMonster, as it's vulnerable to races between +// separate threads executing the same functions and hence initializing the +// same static variables. There isn't a race danger in the histogram +// accumulation calls; they are written to be resilient to simultaneous +// calls from multiple threads. +// +// The solution taken here is to have per-CookieMonster instance +// variables that are constructed during CookieMonster construction. +// Note that these variables refer to the same underlying histogram, +// so we still race (but safely) with other CookieMonster instances +// for accumulation. +// +// To do this we've expanded out the individual histogram macros calls, +// with declarations of the variables in the class decl, initialization here +// (done from the class constructor) and direct calls to the accumulation +// methods where needed. The specific histogram macro calls on which the +// initialization is based are included in comments below. +void CookieMonster::InitializeHistograms() { + // From UMA_HISTOGRAM_CUSTOM_COUNTS + histogram_expiration_duration_minutes_ = base::Histogram::FactoryGet( + "Cookie.ExpirationDurationMinutes", + 1, kMinutesInTenYears, 50, + base::Histogram::kUmaTargetedHistogramFlag); + histogram_between_access_interval_minutes_ = base::Histogram::FactoryGet( + "Cookie.BetweenAccessIntervalMinutes", + 1, kMinutesInTenYears, 50, + base::Histogram::kUmaTargetedHistogramFlag); + histogram_evicted_last_access_minutes_ = base::Histogram::FactoryGet( + "Cookie.EvictedLastAccessMinutes", + 1, kMinutesInTenYears, 50, + base::Histogram::kUmaTargetedHistogramFlag); + histogram_count_ = base::Histogram::FactoryGet( + "Cookie.Count", 1, 4000, 50, + base::Histogram::kUmaTargetedHistogramFlag); + histogram_domain_count_ = base::Histogram::FactoryGet( + "Cookie.DomainCount", 1, 4000, 50, + base::Histogram::kUmaTargetedHistogramFlag); + histogram_etldp1_count_ = base::Histogram::FactoryGet( + "Cookie.Etldp1Count", 1, 4000, 50, + base::Histogram::kUmaTargetedHistogramFlag); + histogram_domain_per_etldp1_count_ = base::Histogram::FactoryGet( + "Cookie.DomainPerEtldp1Count", 1, 4000, 50, + base::Histogram::kUmaTargetedHistogramFlag); + + // From UMA_HISTOGRAM_COUNTS_10000 & UMA_HISTOGRAM_CUSTOM_COUNTS + histogram_number_duplicate_db_cookies_ = base::Histogram::FactoryGet( + "Net.NumDuplicateCookiesInDb", 1, 10000, 50, + base::Histogram::kUmaTargetedHistogramFlag); + + // From UMA_HISTOGRAM_ENUMERATION + histogram_cookie_deletion_cause_ = base::LinearHistogram::FactoryGet( + "Cookie.DeletionCause", 1, + DELETE_COOKIE_LAST_ENTRY - 1, DELETE_COOKIE_LAST_ENTRY, + base::Histogram::kUmaTargetedHistogramFlag); + + // From UMA_HISTOGRAM_{CUSTOM_,}TIMES + histogram_time_get_ = base::Histogram::FactoryTimeGet("Cookie.TimeGet", + base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1), + 50, base::Histogram::kUmaTargetedHistogramFlag); + histogram_time_load_ = base::Histogram::FactoryTimeGet("Cookie.TimeLoad", + base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1), + 50, base::Histogram::kUmaTargetedHistogramFlag); +} + + +// The system resolution is not high enough, so we can have multiple +// set cookies that result in the same system time. When this happens, we +// increment by one Time unit. Let's hope computers don't get too fast. +Time CookieMonster::CurrentTime() { + return std::max(Time::Now(), + Time::FromInternalValue(last_time_seen_.ToInternalValue() + 1)); +} + CookieMonster::ParsedCookie::ParsedCookie(const std::string& cookie_line) : is_valid_(false), path_index_(0), @@ -1608,6 +1623,21 @@ const char CookieMonster::ParsedCookie::kWhitespace[] = " \t"; const char CookieMonster::ParsedCookie::kValueSeparator[] = ";"; const char CookieMonster::ParsedCookie::kTokenSeparator[] = ";="; +// Create a cookie-line for the cookie. For debugging only! +// If we want to use this for something more than debugging, we +// should rewrite it better... +std::string CookieMonster::ParsedCookie::DebugString() const { + std::string out; + for (PairList::const_iterator it = pairs_.begin(); + it != pairs_.end(); ++it) { + out.append(it->first); + out.append("="); + out.append(it->second); + out.append("; "); + } + return out; +} + std::string::const_iterator CookieMonster::ParsedCookie::FindFirstTerminator( const std::string& s) { std::string::const_iterator end = s.end(); @@ -1812,21 +1842,6 @@ void CookieMonster::ParsedCookie::SetupAttributes() { } } -// Create a cookie-line for the cookie. For debugging only! -// If we want to use this for something more than debugging, we -// should rewrite it better... -std::string CookieMonster::ParsedCookie::DebugString() const { - std::string out; - for (PairList::const_iterator it = pairs_.begin(); - it != pairs_.end(); ++it) { - out.append(it->first); - out.append("="); - out.append(it->second); - out.append("; "); - } - return out; -} - CookieMonster::CanonicalCookie::CanonicalCookie() { } @@ -1879,20 +1894,6 @@ CookieMonster::CanonicalCookie::CanonicalCookie(const GURL& url, domain_ = cookie_domain; } -CookieMonster::CookieMonster(PersistentCookieStore* store, - Delegate* delegate, - int last_access_threshold_milliseconds) - : initialized_(false), - expiry_and_key_scheme_(expiry_and_key_default_), - store_(store), - last_access_threshold_(base::TimeDelta::FromMilliseconds( - last_access_threshold_milliseconds)), - delegate_(delegate), - last_statistic_record_time_(base::Time::Now()) { - InitializeHistograms(); - SetDefaultCookieableSchemes(); -} - CookieMonster::CanonicalCookie::~CanonicalCookie() { } |