text: // Copyright 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/net/evicted_domain_cookie_counter.h"
  
  #include <algorithm>
  #include <vector>
  
  #include "base/metrics/histogram.h"
  #include "base/stl_util.h"
  #include "base/strings/string_util.h"
  #include "chrome/browser/google/google_util.h"
  #include "net/cookies/canonical_cookie.h"
  
  namespace chrome_browser_net {
  
  using base::Time;
  using base::TimeDelta;
  
  namespace {
  
  const size_t kMaxEvictedDomainCookies = 500;
  const size_t kPurgeEvictedDomainCookies = 100;
  
  class DelegateImpl : public EvictedDomainCookieCounter::Delegate {
   public:
    DelegateImpl();
  
    // EvictedDomainCookieCounter::Delegate implementation.
    virtual void Report(
        const EvictedDomainCookieCounter::EvictedCookie& evicted_cookie,
        const Time& reinstatement_time) OVERRIDE;
    virtual Time CurrentTime() const OVERRIDE;
  };
  
  DelegateImpl::DelegateImpl() {}
  
  void DelegateImpl::Report(
      const EvictedDomainCookieCounter::EvictedCookie& evicted_cookie,
      const Time& reinstatement_time) {
    TimeDelta reinstatement_delay(
        reinstatement_time - evicted_cookie.eviction_time);
    // Need to duplicate HISTOGRAM_CUSTOM_TIMES(), since it is a macro that
    // defines a static variable.
    if (evicted_cookie.is_google) {
      UMA_HISTOGRAM_CUSTOM_TIMES("Cookie.ReinstatedCookiesGoogle",
                                 reinstatement_delay,
                                 TimeDelta::FromSeconds(1),
                                 TimeDelta::FromDays(7),
                                 50);
    } else {
      UMA_HISTOGRAM_CUSTOM_TIMES("Cookie.ReinstatedCookiesOther",
                                 reinstatement_delay,
                                 TimeDelta::FromSeconds(1),
                                 TimeDelta::FromDays(7),
                                 50);
    }
  }
  
  Time DelegateImpl::CurrentTime() const {
    return Time::Now();
  }
  
  }  // namespace
  
  EvictedDomainCookieCounter::EvictedDomainCookieCounter(
      scoped_refptr<net::CookieMonster::Delegate> next_cookie_monster_delegate)
      : next_cookie_monster_delegate_(next_cookie_monster_delegate),
        cookie_counter_delegate_(new DelegateImpl),
        max_size_(kMaxEvictedDomainCookies),
        purge_count_(kPurgeEvictedDomainCookies) {
  }
  
  EvictedDomainCookieCounter::EvictedDomainCookieCounter(
      scoped_refptr<net::CookieMonster::Delegate> next_cookie_monster_delegate,
      scoped_ptr<Delegate> cookie_counter_delegate,
      size_t max_size,
      size_t purge_count)
      : next_cookie_monster_delegate_(next_cookie_monster_delegate),
        cookie_counter_delegate_(cookie_counter_delegate.Pass()),
        max_size_(max_size),
        purge_count_(purge_count) {
    DCHECK(cookie_counter_delegate_);
    DCHECK_LT(purge_count, max_size_);
  }
  
  EvictedDomainCookieCounter::~EvictedDomainCookieCounter() {
    STLDeleteContainerPairSecondPointers(evicted_cookies_.begin(),
                                         evicted_cookies_.end());
  }
  
  size_t EvictedDomainCookieCounter::GetStorageSize() const {
    return evicted_cookies_.size();
  }
  
  void EvictedDomainCookieCounter::OnCookieChanged(
      const net::CanonicalCookie& cookie,
      bool removed,
      ChangeCause cause) {
    EvictedDomainCookieCounter::EvictedCookieKey key(GetKey(cookie));
    Time current_time(cookie_counter_delegate_->CurrentTime());
    if (removed) {
      if (cause == net::CookieMonster::Delegate::CHANGE_COOKIE_EVICTED)
        StoreEvictedCookie(key, cookie, current_time);
    } else {  // Includes adds or updates.
      ProcessNewCookie(key, cookie, current_time);
    }
  
    if (next_cookie_monster_delegate_.get())
      next_cookie_monster_delegate_->OnCookieChanged(cookie, removed, cause);
  }
  
  // static
  EvictedDomainCookieCounter::EvictedCookieKey
      EvictedDomainCookieCounter::GetKey(const net::CanonicalCookie& cookie) {
    return cookie.Domain() + ";" + cookie.Path() + ";" + cookie.Name();
  }
  
  // static
  bool EvictedDomainCookieCounter::CompareEvictedCookie(
      const EvictedCookieMap::iterator evicted_cookie1,
      const EvictedCookieMap::iterator evicted_cookie2) {
    return evicted_cookie1->second->eviction_time
        < evicted_cookie2->second->eviction_time;
  }
  
  void EvictedDomainCookieCounter::GarbageCollect(const Time& current_time) {
    if (evicted_cookies_.size() <= max_size_)
      return;
  
    // From |evicted_cookies_|, removed all expired cookies, and remove cookies
    // with the oldest |eviction_time| so that |size_goal| is attained.
    size_t size_goal = max_size_ - purge_count_;
    // Bound on number of non-expired cookies to remove.
    size_t remove_quota = evicted_cookies_.size() - size_goal;
    DCHECK_GT(remove_quota, 0u);
  
    std::vector<EvictedCookieMap::iterator> remove_list;
    remove_list.reserve(evicted_cookies_.size());
  
    EvictedCookieMap::iterator it = evicted_cookies_.begin();
    while (it != evicted_cookies_.end()) {
      if (it->second->is_expired(current_time)) {
        delete it->second;
        evicted_cookies_.erase(it++); // Post-increment idiom for in-loop removal.
        if (remove_quota)
          --remove_quota;
      } else {
        if (remove_quota)  // Don't bother storing if quota met.
          remove_list.push_back(it);
        ++it;
      }
    }
  
    // Free the oldest |remove_quota| non-expired cookies.
    std::partial_sort(remove_list.begin(), remove_list.begin() + remove_quota,
                      remove_list.end(), CompareEvictedCookie);
    for (size_t i = 0; i < remove_quota; ++i) {
      delete remove_list[i]->second;
      evicted_cookies_.erase(remove_list[i]);
    }
  
    // Apply stricter check if non-expired cookies were deleted.
    DCHECK(remove_quota ? evicted_cookies_.size() == size_goal :
           evicted_cookies_.size() <= size_goal);
  }
  
  void EvictedDomainCookieCounter::StoreEvictedCookie(
      const EvictedCookieKey& key,
      const net::CanonicalCookie& cookie,
      const Time& current_time) {
    bool is_google = google_util::IsGoogleHostname(
        cookie.Domain(), google_util::ALLOW_SUBDOMAIN);
    EvictedCookie* evicted_cookie =
        new EvictedCookie(current_time, cookie.ExpiryDate(), is_google);
    std::pair<EvictedCookieMap::iterator, bool> prev_entry =
        evicted_cookies_.insert(
            EvictedCookieMap::value_type(key, evicted_cookie));
    if (!prev_entry.second) {
      NOTREACHED();
      delete prev_entry.first->second;
      prev_entry.first->second = evicted_cookie;
    }
  
    GarbageCollect(current_time);
  }
  
  void EvictedDomainCookieCounter::ProcessNewCookie(
      const EvictedCookieKey& key,
      const net::CanonicalCookie& cc,
      const Time& current_time) {
    EvictedCookieMap::iterator it = evicted_cookies_.find(key);
    if (it != evicted_cookies_.end()) {
      if (!it->second->is_expired(current_time))  // Reinstatement.
        cookie_counter_delegate_->Report(*it->second, current_time);
      delete it->second;
      evicted_cookies_.erase(it);
    }
  }
  
  }  // namespace chrome_browser_net
  