Home | History | Annotate | Download | only in net
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/net/evicted_domain_cookie_counter.h"
      6 
      7 #include <algorithm>
      8 #include <vector>
      9 
     10 #include "base/metrics/histogram.h"
     11 #include "base/stl_util.h"
     12 #include "base/strings/string_util.h"
     13 #include "chrome/browser/google/google_util.h"
     14 #include "net/cookies/canonical_cookie.h"
     15 
     16 namespace chrome_browser_net {
     17 
     18 using base::Time;
     19 using base::TimeDelta;
     20 
     21 namespace {
     22 
     23 const size_t kMaxEvictedDomainCookies = 500;
     24 const size_t kPurgeEvictedDomainCookies = 100;
     25 
     26 class DelegateImpl : public EvictedDomainCookieCounter::Delegate {
     27  public:
     28   DelegateImpl();
     29 
     30   // EvictedDomainCookieCounter::Delegate implementation.
     31   virtual void Report(
     32       const EvictedDomainCookieCounter::EvictedCookie& evicted_cookie,
     33       const Time& reinstatement_time) OVERRIDE;
     34   virtual Time CurrentTime() const OVERRIDE;
     35 };
     36 
     37 DelegateImpl::DelegateImpl() {}
     38 
     39 void DelegateImpl::Report(
     40     const EvictedDomainCookieCounter::EvictedCookie& evicted_cookie,
     41     const Time& reinstatement_time) {
     42   TimeDelta reinstatement_delay(
     43       reinstatement_time - evicted_cookie.eviction_time);
     44   // Need to duplicate HISTOGRAM_CUSTOM_TIMES(), since it is a macro that
     45   // defines a static variable.
     46   if (evicted_cookie.is_google) {
     47     UMA_HISTOGRAM_CUSTOM_TIMES("Cookie.ReinstatedCookiesGoogle",
     48                                reinstatement_delay,
     49                                TimeDelta::FromSeconds(1),
     50                                TimeDelta::FromDays(7),
     51                                50);
     52   } else {
     53     UMA_HISTOGRAM_CUSTOM_TIMES("Cookie.ReinstatedCookiesOther",
     54                                reinstatement_delay,
     55                                TimeDelta::FromSeconds(1),
     56                                TimeDelta::FromDays(7),
     57                                50);
     58   }
     59 }
     60 
     61 Time DelegateImpl::CurrentTime() const {
     62   return Time::Now();
     63 }
     64 
     65 }  // namespace
     66 
     67 EvictedDomainCookieCounter::EvictedDomainCookieCounter(
     68     scoped_refptr<net::CookieMonster::Delegate> next_cookie_monster_delegate)
     69     : next_cookie_monster_delegate_(next_cookie_monster_delegate),
     70       cookie_counter_delegate_(new DelegateImpl),
     71       max_size_(kMaxEvictedDomainCookies),
     72       purge_count_(kPurgeEvictedDomainCookies) {
     73 }
     74 
     75 EvictedDomainCookieCounter::EvictedDomainCookieCounter(
     76     scoped_refptr<net::CookieMonster::Delegate> next_cookie_monster_delegate,
     77     scoped_ptr<Delegate> cookie_counter_delegate,
     78     size_t max_size,
     79     size_t purge_count)
     80     : next_cookie_monster_delegate_(next_cookie_monster_delegate),
     81       cookie_counter_delegate_(cookie_counter_delegate.Pass()),
     82       max_size_(max_size),
     83       purge_count_(purge_count) {
     84   DCHECK(cookie_counter_delegate_);
     85   DCHECK_LT(purge_count, max_size_);
     86 }
     87 
     88 EvictedDomainCookieCounter::~EvictedDomainCookieCounter() {
     89   STLDeleteContainerPairSecondPointers(evicted_cookies_.begin(),
     90                                        evicted_cookies_.end());
     91 }
     92 
     93 size_t EvictedDomainCookieCounter::GetStorageSize() const {
     94   return evicted_cookies_.size();
     95 }
     96 
     97 void EvictedDomainCookieCounter::OnCookieChanged(
     98     const net::CanonicalCookie& cookie,
     99     bool removed,
    100     ChangeCause cause) {
    101   EvictedDomainCookieCounter::EvictedCookieKey key(GetKey(cookie));
    102   Time current_time(cookie_counter_delegate_->CurrentTime());
    103   if (removed) {
    104     if (cause == net::CookieMonster::Delegate::CHANGE_COOKIE_EVICTED)
    105       StoreEvictedCookie(key, cookie, current_time);
    106   } else {  // Includes adds or updates.
    107     ProcessNewCookie(key, cookie, current_time);
    108   }
    109 
    110   if (next_cookie_monster_delegate_.get())
    111     next_cookie_monster_delegate_->OnCookieChanged(cookie, removed, cause);
    112 }
    113 
    114 // static
    115 EvictedDomainCookieCounter::EvictedCookieKey
    116     EvictedDomainCookieCounter::GetKey(const net::CanonicalCookie& cookie) {
    117   return cookie.Domain() + ";" + cookie.Path() + ";" + cookie.Name();
    118 }
    119 
    120 // static
    121 bool EvictedDomainCookieCounter::CompareEvictedCookie(
    122     const EvictedCookieMap::iterator evicted_cookie1,
    123     const EvictedCookieMap::iterator evicted_cookie2) {
    124   return evicted_cookie1->second->eviction_time
    125       < evicted_cookie2->second->eviction_time;
    126 }
    127 
    128 void EvictedDomainCookieCounter::GarbageCollect(const Time& current_time) {
    129   if (evicted_cookies_.size() <= max_size_)
    130     return;
    131 
    132   // From |evicted_cookies_|, removed all expired cookies, and remove cookies
    133   // with the oldest |eviction_time| so that |size_goal| is attained.
    134   size_t size_goal = max_size_ - purge_count_;
    135   // Bound on number of non-expired cookies to remove.
    136   size_t remove_quota = evicted_cookies_.size() - size_goal;
    137   DCHECK_GT(remove_quota, 0u);
    138 
    139   std::vector<EvictedCookieMap::iterator> remove_list;
    140   remove_list.reserve(evicted_cookies_.size());
    141 
    142   EvictedCookieMap::iterator it = evicted_cookies_.begin();
    143   while (it != evicted_cookies_.end()) {
    144     if (it->second->is_expired(current_time)) {
    145       delete it->second;
    146       evicted_cookies_.erase(it++); // Post-increment idiom for in-loop removal.
    147       if (remove_quota)
    148         --remove_quota;
    149     } else {
    150       if (remove_quota)  // Don't bother storing if quota met.
    151         remove_list.push_back(it);
    152       ++it;
    153     }
    154   }
    155 
    156   // Free the oldest |remove_quota| non-expired cookies.
    157   std::partial_sort(remove_list.begin(), remove_list.begin() + remove_quota,
    158                     remove_list.end(), CompareEvictedCookie);
    159   for (size_t i = 0; i < remove_quota; ++i) {
    160     delete remove_list[i]->second;
    161     evicted_cookies_.erase(remove_list[i]);
    162   }
    163 
    164   // Apply stricter check if non-expired cookies were deleted.
    165   DCHECK(remove_quota ? evicted_cookies_.size() == size_goal :
    166          evicted_cookies_.size() <= size_goal);
    167 }
    168 
    169 void EvictedDomainCookieCounter::StoreEvictedCookie(
    170     const EvictedCookieKey& key,
    171     const net::CanonicalCookie& cookie,
    172     const Time& current_time) {
    173   bool is_google = google_util::IsGoogleHostname(
    174       cookie.Domain(), google_util::ALLOW_SUBDOMAIN);
    175   EvictedCookie* evicted_cookie =
    176       new EvictedCookie(current_time, cookie.ExpiryDate(), is_google);
    177   std::pair<EvictedCookieMap::iterator, bool> prev_entry =
    178       evicted_cookies_.insert(
    179           EvictedCookieMap::value_type(key, evicted_cookie));
    180   if (!prev_entry.second) {
    181     NOTREACHED();
    182     delete prev_entry.first->second;
    183     prev_entry.first->second = evicted_cookie;
    184   }
    185 
    186   GarbageCollect(current_time);
    187 }
    188 
    189 void EvictedDomainCookieCounter::ProcessNewCookie(
    190     const EvictedCookieKey& key,
    191     const net::CanonicalCookie& cc,
    192     const Time& current_time) {
    193   EvictedCookieMap::iterator it = evicted_cookies_.find(key);
    194   if (it != evicted_cookies_.end()) {
    195     if (!it->second->is_expired(current_time))  // Reinstatement.
    196       cookie_counter_delegate_->Report(*it->second, current_time);
    197     delete it->second;
    198     evicted_cookies_.erase(it);
    199   }
    200 }
    201 
    202 }  // namespace chrome_browser_net
    203