// Copyright (c) 2011 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/browsing_data_cookie_helper.h"

#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_thread.h"
#include "googleurl/src/gurl.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"

using content::BrowserThread;

BrowsingDataCookieHelper::BrowsingDataCookieHelper(Profile* profile)
  : is_fetching_(false),
    profile_(profile),
    request_context_getter_(profile->GetRequestContext()) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}

BrowsingDataCookieHelper::~BrowsingDataCookieHelper() {
}

void BrowsingDataCookieHelper::StartFetching(
    const base::Callback<void(const net::CookieList& cookies)>& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!is_fetching_);
  DCHECK(!callback.is_null());
  DCHECK(completion_callback_.is_null());
  is_fetching_ = true;
  completion_callback_ = callback;
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      base::Bind(&BrowsingDataCookieHelper::FetchCookiesOnIOThread, this));
}

void BrowsingDataCookieHelper::CancelNotification() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  completion_callback_.Reset();
}

void BrowsingDataCookieHelper::DeleteCookie(
    const net::CookieMonster::CanonicalCookie& cookie) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      base::Bind(&BrowsingDataCookieHelper::DeleteCookieOnIOThread,
                 this, cookie));
}

void BrowsingDataCookieHelper::FetchCookiesOnIOThread() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  scoped_refptr<net::CookieMonster> cookie_monster =
      request_context_getter_->GetURLRequestContext()->
      cookie_store()->GetCookieMonster();
  if (cookie_monster) {
    cookie_monster->GetAllCookiesAsync(
        base::Bind(&BrowsingDataCookieHelper::OnFetchComplete, this));
  } else {
    OnFetchComplete(net::CookieList());
  }
}

void BrowsingDataCookieHelper::OnFetchComplete(const net::CookieList& cookies) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&BrowsingDataCookieHelper::NotifyInUIThread, this, cookies));
}

void BrowsingDataCookieHelper::NotifyInUIThread(
    const net::CookieList& cookies) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(is_fetching_);
  is_fetching_ = false;
  if (!completion_callback_.is_null()) {
    completion_callback_.Run(cookies);
    completion_callback_.Reset();
  }
}

void BrowsingDataCookieHelper::DeleteCookieOnIOThread(
    const net::CookieMonster::CanonicalCookie& cookie) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  scoped_refptr<net::CookieMonster> cookie_monster =
      request_context_getter_->GetURLRequestContext()->
      cookie_store()->GetCookieMonster();
  if (cookie_monster) {
    cookie_monster->DeleteCanonicalCookieAsync(
        cookie, net::CookieMonster::DeleteCookieCallback());
  }
}

CannedBrowsingDataCookieHelper::CannedBrowsingDataCookieHelper(
    Profile* profile)
    : BrowsingDataCookieHelper(profile),
    profile_(profile) {
}

CannedBrowsingDataCookieHelper::~CannedBrowsingDataCookieHelper() {}

CannedBrowsingDataCookieHelper* CannedBrowsingDataCookieHelper::Clone() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  CannedBrowsingDataCookieHelper* clone =
      new CannedBrowsingDataCookieHelper(profile_);

  clone->cookie_list_ = cookie_list_;
  return clone;
}

void CannedBrowsingDataCookieHelper::AddReadCookies(
    const GURL& url,
    const net::CookieList& cookie_list) {
  typedef net::CookieList::const_iterator cookie_iterator;
  for (cookie_iterator add_cookie = cookie_list.begin();
       add_cookie != cookie_list.end(); ++add_cookie) {
    DeleteMetchingCookie(*add_cookie);
    cookie_list_.push_back(*add_cookie);
  }
}

void CannedBrowsingDataCookieHelper::AddChangedCookie(
    const GURL& url,
    const std::string& cookie_line,
    const net::CookieOptions& options) {
  typedef net::CookieList::iterator cookie_iterator;

  net::CookieMonster::ParsedCookie pc(cookie_line);
  if (options.exclude_httponly() && pc.IsHttpOnly()) {
    // Return if a Javascript cookie illegally specified the HTTP only flag.
    return;
  }

  scoped_ptr<net::CookieMonster::CanonicalCookie> cc;
  // This fails to create a canonical cookie, if the normalized cookie domain
  // form cookie line and the url don't have the same domain+registry, or url
  // host isn't cookie domain or one of its subdomains.
  cc.reset(net::CookieMonster::CanonicalCookie::Create(url, pc));

  if (cc.get()) {
    DeleteMetchingCookie(*cc);
    cookie_list_.push_back(*cc);
  }
}

void CannedBrowsingDataCookieHelper::Reset() {
  cookie_list_.clear();
}

bool CannedBrowsingDataCookieHelper::empty() const {
  return cookie_list_.empty();
}

void CannedBrowsingDataCookieHelper::StartFetching(
    const net::CookieMonster::GetCookieListCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (!callback.is_null())
    callback.Run(cookie_list_);
}

void CannedBrowsingDataCookieHelper::CancelNotification() {}

bool CannedBrowsingDataCookieHelper::DeleteMetchingCookie(
    const net::CookieMonster::CanonicalCookie& add_cookie) {
  typedef net::CookieList::iterator cookie_iterator;
  for (cookie_iterator cookie = cookie_list_.begin();
      cookie != cookie_list_.end(); ++cookie) {
    if (cookie->Name() == add_cookie.Name() &&
        cookie->Domain() == add_cookie.Domain()&&
        cookie->Path() == add_cookie.Path()) {
      cookie_list_.erase(cookie);
      return true;
    }
  }
  return false;
}