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 "components/google/core/browser/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 void EvictedDomainCookieCounter::OnLoaded() { 115 if (next_cookie_monster_delegate_.get()) 116 next_cookie_monster_delegate_->OnLoaded(); 117 } 118 119 // static 120 EvictedDomainCookieCounter::EvictedCookieKey 121 EvictedDomainCookieCounter::GetKey(const net::CanonicalCookie& cookie) { 122 return cookie.Domain() + ";" + cookie.Path() + ";" + cookie.Name(); 123 } 124 125 // static 126 bool EvictedDomainCookieCounter::CompareEvictedCookie( 127 const EvictedCookieMap::iterator evicted_cookie1, 128 const EvictedCookieMap::iterator evicted_cookie2) { 129 return evicted_cookie1->second->eviction_time 130 < evicted_cookie2->second->eviction_time; 131 } 132 133 void EvictedDomainCookieCounter::GarbageCollect(const Time& current_time) { 134 if (evicted_cookies_.size() <= max_size_) 135 return; 136 137 // From |evicted_cookies_|, removed all expired cookies, and remove cookies 138 // with the oldest |eviction_time| so that |size_goal| is attained. 139 size_t size_goal = max_size_ - purge_count_; 140 // Bound on number of non-expired cookies to remove. 141 size_t remove_quota = evicted_cookies_.size() - size_goal; 142 DCHECK_GT(remove_quota, 0u); 143 144 std::vector<EvictedCookieMap::iterator> remove_list; 145 remove_list.reserve(evicted_cookies_.size()); 146 147 EvictedCookieMap::iterator it = evicted_cookies_.begin(); 148 while (it != evicted_cookies_.end()) { 149 if (it->second->is_expired(current_time)) { 150 delete it->second; 151 evicted_cookies_.erase(it++); // Post-increment idiom for in-loop removal. 152 if (remove_quota) 153 --remove_quota; 154 } else { 155 if (remove_quota) // Don't bother storing if quota met. 156 remove_list.push_back(it); 157 ++it; 158 } 159 } 160 161 // Free the oldest |remove_quota| non-expired cookies. 162 std::partial_sort(remove_list.begin(), remove_list.begin() + remove_quota, 163 remove_list.end(), CompareEvictedCookie); 164 for (size_t i = 0; i < remove_quota; ++i) { 165 delete remove_list[i]->second; 166 evicted_cookies_.erase(remove_list[i]); 167 } 168 169 // Apply stricter check if non-expired cookies were deleted. 170 DCHECK(remove_quota ? evicted_cookies_.size() == size_goal : 171 evicted_cookies_.size() <= size_goal); 172 } 173 174 void EvictedDomainCookieCounter::StoreEvictedCookie( 175 const EvictedCookieKey& key, 176 const net::CanonicalCookie& cookie, 177 const Time& current_time) { 178 bool is_google = google_util::IsGoogleHostname( 179 cookie.Domain(), google_util::ALLOW_SUBDOMAIN); 180 EvictedCookie* evicted_cookie = 181 new EvictedCookie(current_time, cookie.ExpiryDate(), is_google); 182 std::pair<EvictedCookieMap::iterator, bool> prev_entry = 183 evicted_cookies_.insert( 184 EvictedCookieMap::value_type(key, evicted_cookie)); 185 if (!prev_entry.second) { 186 NOTREACHED(); 187 delete prev_entry.first->second; 188 prev_entry.first->second = evicted_cookie; 189 } 190 191 GarbageCollect(current_time); 192 } 193 194 void EvictedDomainCookieCounter::ProcessNewCookie( 195 const EvictedCookieKey& key, 196 const net::CanonicalCookie& cc, 197 const Time& current_time) { 198 EvictedCookieMap::iterator it = evicted_cookies_.find(key); 199 if (it != evicted_cookies_.end()) { 200 if (!it->second->is_expired(current_time)) // Reinstatement. 201 cookie_counter_delegate_->Report(*it->second, current_time); 202 delete it->second; 203 evicted_cookies_.erase(it); 204 } 205 } 206 207 } // namespace chrome_browser_net 208