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
16namespace chrome_browser_net {
17
18using base::Time;
19using base::TimeDelta;
20
21namespace {
22
23const size_t kMaxEvictedDomainCookies = 500;
24const size_t kPurgeEvictedDomainCookies = 100;
25
26class 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
37DelegateImpl::DelegateImpl() {}
38
39void 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 UMA_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
61Time DelegateImpl::CurrentTime() const {
62  return Time::Now();
63}
64
65}  // namespace
66
67EvictedDomainCookieCounter::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
75EvictedDomainCookieCounter::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
88EvictedDomainCookieCounter::~EvictedDomainCookieCounter() {
89  STLDeleteContainerPairSecondPointers(evicted_cookies_.begin(),
90                                       evicted_cookies_.end());
91}
92
93size_t EvictedDomainCookieCounter::GetStorageSize() const {
94  return evicted_cookies_.size();
95}
96
97void 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
114void EvictedDomainCookieCounter::OnLoaded() {
115  if (next_cookie_monster_delegate_.get())
116    next_cookie_monster_delegate_->OnLoaded();
117}
118
119// static
120EvictedDomainCookieCounter::EvictedCookieKey
121    EvictedDomainCookieCounter::GetKey(const net::CanonicalCookie& cookie) {
122  return cookie.Domain() + ";" + cookie.Path() + ";" + cookie.Name();
123}
124
125// static
126bool 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
133void 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
174void 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
194void 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