// 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.
// Implements the Chrome Extensions Cookies API.
#include "chrome/browser/extensions/extension_cookies_api.h"
#include "base/json/json_writer.h"
#include "base/task.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_cookies_api_constants.h"
#include "chrome/browser/extensions/extension_cookies_helpers.h"
#include "chrome/browser/extensions/extension_event_router.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_error_utils.h"
#include "content/browser/browser_thread.h"
#include "content/common/notification_service.h"
#include "content/common/notification_type.h"
#include "net/base/cookie_monster.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
namespace keys = extension_cookies_api_constants;
// static
ExtensionCookiesEventRouter* ExtensionCookiesEventRouter::GetInstance() {
return Singleton<ExtensionCookiesEventRouter>::get();
}
void ExtensionCookiesEventRouter::Init() {
if (registrar_.IsEmpty()) {
registrar_.Add(this,
NotificationType::COOKIE_CHANGED,
NotificationService::AllSources());
}
}
void ExtensionCookiesEventRouter::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
switch (type.value) {
case NotificationType::COOKIE_CHANGED:
CookieChanged(
Source<Profile>(source).ptr(),
Details<ChromeCookieDetails>(details).ptr());
break;
default:
NOTREACHED();
}
}
void ExtensionCookiesEventRouter::CookieChanged(
Profile* profile,
ChromeCookieDetails* details) {
ListValue args;
DictionaryValue* dict = new DictionaryValue();
dict->SetBoolean(keys::kRemovedKey, details->removed);
dict->Set(
keys::kCookieKey,
extension_cookies_helpers::CreateCookieValue(*details->cookie,
extension_cookies_helpers::GetStoreIdFromProfile(profile)));
// Map the interal cause to an external string.
std::string cause;
switch (details->cause) {
case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPLICIT:
cause = keys::kExplicitChangeCause;
break;
case net::CookieMonster::Delegate::CHANGE_COOKIE_OVERWRITE:
cause = keys::kOverwriteChangeCause;
break;
case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPIRED:
cause = keys::kExpiredChangeCause;
break;
case net::CookieMonster::Delegate::CHANGE_COOKIE_EVICTED:
cause = keys::kEvictedChangeCause;
break;
case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPIRED_OVERWRITE:
cause = keys::kExpiredOverwriteChangeCause;
break;
default:
NOTREACHED();
}
dict->SetString(keys::kCauseKey, cause);
args.Append(dict);
std::string json_args;
base::JSONWriter::Write(&args, false, &json_args);
GURL cookie_domain =
extension_cookies_helpers::GetURLFromCanonicalCookie(*details->cookie);
DispatchEvent(profile, keys::kOnChanged, json_args, cookie_domain);
}
void ExtensionCookiesEventRouter::DispatchEvent(Profile* profile,
const char* event_name,
const std::string& json_args,
GURL& cookie_domain) {
if (profile && profile->GetExtensionEventRouter()) {
profile->GetExtensionEventRouter()->DispatchEventToRenderers(
event_name, json_args, profile, cookie_domain);
}
}
bool CookiesFunction::ParseUrl(const DictionaryValue* details, GURL* url,
bool check_host_permissions) {
DCHECK(details && url);
std::string url_string;
// Get the URL string or return false.
EXTENSION_FUNCTION_VALIDATE(details->GetString(keys::kUrlKey, &url_string));
*url = GURL(url_string);
if (!url->is_valid()) {
error_ = ExtensionErrorUtils::FormatErrorMessage(
keys::kInvalidUrlError, url_string);
return false;
}
// Check against host permissions if needed.
if (check_host_permissions &&
!GetExtension()->HasHostPermission(*url)) {
error_ = ExtensionErrorUtils::FormatErrorMessage(
keys::kNoHostPermissionsError, url->spec());
return false;
}
return true;
}
bool CookiesFunction::ParseStoreContext(const DictionaryValue* details,
net::URLRequestContextGetter** context,
std::string* store_id) {
DCHECK(details && (context || store_id));
Profile* store_profile = NULL;
if (details->HasKey(keys::kStoreIdKey)) {
// The store ID was explicitly specified in the details dictionary.
// Retrieve its corresponding cookie store.
std::string store_id_value;
// Get the store ID string or return false.
EXTENSION_FUNCTION_VALIDATE(
details->GetString(keys::kStoreIdKey, &store_id_value));
store_profile = extension_cookies_helpers::ChooseProfileFromStoreId(
store_id_value, profile(), include_incognito());
if (!store_profile) {
error_ = ExtensionErrorUtils::FormatErrorMessage(
keys::kInvalidStoreIdError, store_id_value);
return false;
}
} else {
// The store ID was not specified; use the current execution context's
// cookie store by default.
// GetCurrentBrowser() already takes into account incognito settings.
Browser* current_browser = GetCurrentBrowser();
if (!current_browser) {
error_ = keys::kNoCookieStoreFoundError;
return false;
}
store_profile = current_browser->profile();
}
DCHECK(store_profile);
if (context)
*context = store_profile->GetRequestContext();
if (store_id)
*store_id = extension_cookies_helpers::GetStoreIdFromProfile(store_profile);
return true;
}
GetCookieFunction::GetCookieFunction() {}
GetCookieFunction::~GetCookieFunction() {}
bool GetCookieFunction::RunImpl() {
// Return false if the arguments are malformed.
DictionaryValue* details;
EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &details));
DCHECK(details);
// Read/validate input parameters.
if (!ParseUrl(details, &url_, true))
return false;
// Get the cookie name string or return false.
EXTENSION_FUNCTION_VALIDATE(details->GetString(keys::kNameKey, &name_));
net::URLRequestContextGetter* store_context = NULL;
if (!ParseStoreContext(details, &store_context, &store_id_))
return false;
DCHECK(store_context && !store_id_.empty());
store_context_ = store_context;
bool rv = BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
NewRunnableMethod(this, &GetCookieFunction::GetCookieOnIOThread));
DCHECK(rv);
// Will finish asynchronously.
return true;
}
void GetCookieFunction::GetCookieOnIOThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
net::CookieStore* cookie_store =
store_context_->GetURLRequestContext()->cookie_store();
net::CookieList cookie_list =
extension_cookies_helpers::GetCookieListFromStore(cookie_store, url_);
net::CookieList::iterator it;
for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
// Return the first matching cookie. Relies on the fact that the
// CookieMonster returns them in canonical order (longest path, then
// earliest creation time).
if (it->Name() == name_) {
result_.reset(
extension_cookies_helpers::CreateCookieValue(*it, store_id_));
break;
}
}
// The cookie doesn't exist; return null.
if (it == cookie_list.end())
result_.reset(Value::CreateNullValue());
bool rv = BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
NewRunnableMethod(this, &GetCookieFunction::RespondOnUIThread));
DCHECK(rv);
}
void GetCookieFunction::RespondOnUIThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
SendResponse(true);
}
GetAllCookiesFunction::GetAllCookiesFunction() : details_(NULL) {}
GetAllCookiesFunction::~GetAllCookiesFunction() {}
bool GetAllCookiesFunction::RunImpl() {
// Return false if the arguments are malformed.
EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &details_));
DCHECK(details_);
// Read/validate input parameters.
if (details_->HasKey(keys::kUrlKey) && !ParseUrl(details_, &url_, false))
return false;
net::URLRequestContextGetter* store_context = NULL;
if (!ParseStoreContext(details_, &store_context, &store_id_))
return false;
DCHECK(store_context);
store_context_ = store_context;
bool rv = BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
NewRunnableMethod(this, &GetAllCookiesFunction::GetAllCookiesOnIOThread));
DCHECK(rv);
// Will finish asynchronously.
return true;
}
void GetAllCookiesFunction::GetAllCookiesOnIOThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
net::CookieStore* cookie_store =
store_context_->GetURLRequestContext()->cookie_store();
net::CookieList cookie_list =
extension_cookies_helpers::GetCookieListFromStore(cookie_store, url_);
const Extension* extension = GetExtension();
if (extension) {
ListValue* matching_list = new ListValue();
extension_cookies_helpers::AppendMatchingCookiesToList(
cookie_list, store_id_, url_, details_,
GetExtension(), matching_list);
result_.reset(matching_list);
}
bool rv = BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
NewRunnableMethod(this, &GetAllCookiesFunction::RespondOnUIThread));
DCHECK(rv);
}
void GetAllCookiesFunction::RespondOnUIThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
SendResponse(true);
}
SetCookieFunction::SetCookieFunction()
: secure_(false),
http_only_(false),
success_(false) {
}
SetCookieFunction::~SetCookieFunction() {
}
bool SetCookieFunction::RunImpl() {
// Return false if the arguments are malformed.
DictionaryValue* details;
EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &details));
DCHECK(details);
// Read/validate input parameters.
if (!ParseUrl(details, &url_, true))
return false;
// The macros below return false if argument types are not as expected.
if (details->HasKey(keys::kNameKey))
EXTENSION_FUNCTION_VALIDATE(details->GetString(keys::kNameKey, &name_));
if (details->HasKey(keys::kValueKey))
EXTENSION_FUNCTION_VALIDATE(details->GetString(keys::kValueKey, &value_));
if (details->HasKey(keys::kDomainKey))
EXTENSION_FUNCTION_VALIDATE(details->GetString(keys::kDomainKey, &domain_));
if (details->HasKey(keys::kPathKey))
EXTENSION_FUNCTION_VALIDATE(details->GetString(keys::kPathKey, &path_));
if (details->HasKey(keys::kSecureKey)) {
EXTENSION_FUNCTION_VALIDATE(
details->GetBoolean(keys::kSecureKey, &secure_));
}
if (details->HasKey(keys::kHttpOnlyKey)) {
EXTENSION_FUNCTION_VALIDATE(
details->GetBoolean(keys::kHttpOnlyKey, &http_only_));
}
if (details->HasKey(keys::kExpirationDateKey)) {
Value* expiration_date_value;
EXTENSION_FUNCTION_VALIDATE(details->Get(keys::kExpirationDateKey,
&expiration_date_value));
double expiration_date;
if (expiration_date_value->IsType(Value::TYPE_INTEGER)) {
int expiration_date_int;
EXTENSION_FUNCTION_VALIDATE(
expiration_date_value->GetAsInteger(&expiration_date_int));
expiration_date = static_cast<double>(expiration_date_int);
} else {
EXTENSION_FUNCTION_VALIDATE(
expiration_date_value->GetAsDouble(&expiration_date));
}
// Time::FromDoubleT converts double time 0 to empty Time object. So we need
// to do special handling here.
expiration_time_ = (expiration_date == 0) ?
base::Time::UnixEpoch() : base::Time::FromDoubleT(expiration_date);
}
net::URLRequestContextGetter* store_context = NULL;
if (!ParseStoreContext(details, &store_context, NULL))
return false;
DCHECK(store_context);
store_context_ = store_context;
bool rv = BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
NewRunnableMethod(this, &SetCookieFunction::SetCookieOnIOThread));
DCHECK(rv);
// Will finish asynchronously.
return true;
}
void SetCookieFunction::SetCookieOnIOThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
net::CookieMonster* cookie_monster =
store_context_->GetURLRequestContext()->cookie_store()->
GetCookieMonster();
success_ = cookie_monster->SetCookieWithDetails(
url_, name_, value_, domain_, path_, expiration_time_,
secure_, http_only_);
// Pull the newly set cookie.
net::CookieList cookie_list =
extension_cookies_helpers::GetCookieListFromStore(cookie_monster, url_);
net::CookieList::iterator it;
for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
// Return the first matching cookie. Relies on the fact that the
// CookieMonster returns them in canonical order (longest path, then
// earliest creation time).
if (it->Name() == name_) {
result_.reset(
extension_cookies_helpers::CreateCookieValue(*it, store_id_));
break;
}
}
bool rv = BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
NewRunnableMethod(this, &SetCookieFunction::RespondOnUIThread));
DCHECK(rv);
}
void SetCookieFunction::RespondOnUIThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!success_) {
error_ = ExtensionErrorUtils::FormatErrorMessage(
keys::kCookieSetFailedError, name_);
}
SendResponse(success_);
}
RemoveCookieFunction::RemoveCookieFunction() : success_(false) {
}
RemoveCookieFunction::~RemoveCookieFunction() {
}
bool RemoveCookieFunction::RunImpl() {
// Return false if the arguments are malformed.
DictionaryValue* details;
EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &details));
DCHECK(details);
// Read/validate input parameters.
if (!ParseUrl(details, &url_, true))
return false;
// Get the cookie name string or return false.
EXTENSION_FUNCTION_VALIDATE(details->GetString(keys::kNameKey, &name_));
net::URLRequestContextGetter* store_context = NULL;
if (!ParseStoreContext(details, &store_context, &store_id_))
return false;
DCHECK(store_context);
store_context_ = store_context;
// Pass the work off to the IO thread.
bool rv = BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
NewRunnableMethod(this, &RemoveCookieFunction::RemoveCookieOnIOThread));
DCHECK(rv);
// Will return asynchronously.
return true;
}
void RemoveCookieFunction::RemoveCookieOnIOThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// Remove the cookie
net::CookieStore* cookie_store =
store_context_->GetURLRequestContext()->cookie_store();
cookie_store->DeleteCookie(url_, name_);
// Build the callback result
DictionaryValue* resultDictionary = new DictionaryValue();
resultDictionary->SetString(keys::kNameKey, name_);
resultDictionary->SetString(keys::kUrlKey, url_.spec());
resultDictionary->SetString(keys::kStoreIdKey, store_id_);
result_.reset(resultDictionary);
// Return to UI thread
bool rv = BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
NewRunnableMethod(this, &RemoveCookieFunction::RespondOnUIThread));
DCHECK(rv);
}
void RemoveCookieFunction::RespondOnUIThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
SendResponse(true);
}
bool GetAllCookieStoresFunction::RunImpl() {
Profile* original_profile = profile();
DCHECK(original_profile);
scoped_ptr<ListValue> original_tab_ids(new ListValue());
Profile* incognito_profile = NULL;
scoped_ptr<ListValue> incognito_tab_ids;
if (include_incognito() && profile()->HasOffTheRecordProfile()) {
incognito_profile = profile()->GetOffTheRecordProfile();
if (incognito_profile)
incognito_tab_ids.reset(new ListValue());
}
DCHECK(original_profile != incognito_profile);
// Iterate through all browser instances, and for each browser,
// add its tab IDs to either the regular or incognito tab ID list depending
// whether the browser is regular or incognito.
for (BrowserList::const_iterator iter = BrowserList::begin();
iter != BrowserList::end(); ++iter) {
Browser* browser = *iter;
if (browser->profile() == original_profile) {
extension_cookies_helpers::AppendToTabIdList(browser,
original_tab_ids.get());
} else if (incognito_tab_ids.get() &&
browser->profile() == incognito_profile) {
extension_cookies_helpers::AppendToTabIdList(browser,
incognito_tab_ids.get());
}
}
// Return a list of all cookie stores with at least one open tab.
ListValue* cookie_store_list = new ListValue();
if (original_tab_ids->GetSize() > 0) {
cookie_store_list->Append(
extension_cookies_helpers::CreateCookieStoreValue(
original_profile, original_tab_ids.release()));
}
if (incognito_tab_ids.get() && incognito_tab_ids->GetSize() > 0) {
cookie_store_list->Append(
extension_cookies_helpers::CreateCookieStoreValue(
incognito_profile, incognito_tab_ids.release()));
}
result_.reset(cookie_store_list);
return true;
}
void GetAllCookieStoresFunction::Run() {
SendResponse(RunImpl());
}