// Copyright (c) 2010 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 "chrome/browser/browser_list.h" #include "chrome/browser/chrome_thread.h" #include "chrome/browser/extensions/extension_cookies_api_constants.h" #include "chrome/browser/extensions/extension_cookies_helpers.h" #include "chrome/browser/extensions/extension_message_service.h" #include "chrome/browser/profile.h" #include "chrome/common/extensions/extension_error_utils.h" #include "chrome/common/net/url_request_context_getter.h" #include "chrome/common/notification_type.h" #include "chrome/common/notification_service.h" #include "net/base/cookie_monster.h" namespace keys = extension_cookies_api_constants; // static ExtensionCookiesEventRouter* ExtensionCookiesEventRouter::GetInstance() { return Singleton::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(source).ptr(), Details(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))); 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->GetExtensionMessageService()) { profile->GetExtensionMessageService()->DispatchEventToRenderers( event_name, json_args, profile->IsOffTheRecord(), 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, 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() {} 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_)); 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 = ChromeThread::PostTask( ChromeThread::IO, FROM_HERE, NewRunnableMethod(this, &GetCookieFunction::GetCookieOnIOThread)); DCHECK(rv); // Will finish asynchronously. return true; } void GetCookieFunction::GetCookieOnIOThread() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); net::CookieStore* cookie_store = store_context_->GetCookieStore(); cookie_list_ = extension_cookies_helpers::GetCookieListFromStore(cookie_store, url_); bool rv = ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod(this, &GetCookieFunction::RespondOnUIThread)); DCHECK(rv); } void GetCookieFunction::RespondOnUIThread() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); net::CookieMonster::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 retrieves them in reverse domain-length order. 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()); SendResponse(true); } GetAllCookiesFunction::GetAllCookiesFunction() : details_(NULL) {} 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; URLRequestContextGetter* store_context = NULL; if (!ParseStoreContext(details_, &store_context, &store_id_)) return false; DCHECK(store_context); store_context_ = store_context; bool rv = ChromeThread::PostTask( ChromeThread::IO, FROM_HERE, NewRunnableMethod(this, &GetAllCookiesFunction::GetAllCookiesOnIOThread)); DCHECK(rv); // Will finish asynchronously. return true; } void GetAllCookiesFunction::GetAllCookiesOnIOThread() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); net::CookieStore* cookie_store = store_context_->GetCookieStore(); cookie_list_ = extension_cookies_helpers::GetCookieListFromStore(cookie_store, url_); bool rv = ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod(this, &GetAllCookiesFunction::RespondOnUIThread)); DCHECK(rv); } void GetAllCookiesFunction::RespondOnUIThread() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); 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); } SendResponse(true); } SetCookieFunction::SetCookieFunction() : secure_(false), http_only_(false), success_(false) { } 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(expiration_date_int); } else { EXTENSION_FUNCTION_VALIDATE( expiration_date_value->GetAsReal(&expiration_date)); } expiration_time_ = base::Time::FromDoubleT(expiration_date); } URLRequestContextGetter* store_context = NULL; if (!ParseStoreContext(details, &store_context, NULL)) return false; DCHECK(store_context); store_context_ = store_context; bool rv = ChromeThread::PostTask( ChromeThread::IO, FROM_HERE, NewRunnableMethod(this, &SetCookieFunction::SetCookieOnIOThread)); DCHECK(rv); // Will finish asynchronously. return true; } void SetCookieFunction::SetCookieOnIOThread() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); net::CookieMonster* cookie_monster = store_context_->GetCookieStore()->GetCookieMonster(); success_ = cookie_monster->SetCookieWithDetails( url_, name_, value_, domain_, path_, expiration_time_, secure_, http_only_); bool rv = ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod(this, &SetCookieFunction::RespondOnUIThread)); DCHECK(rv); } void SetCookieFunction::RespondOnUIThread() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); if (!success_) { error_ = ExtensionErrorUtils::FormatErrorMessage( keys::kCookieSetFailedError, name_); } SendResponse(success_); } namespace { class RemoveCookieTask : public Task { public: RemoveCookieTask(const GURL& url, const std::string& name, const scoped_refptr& context_getter) : url_(url), name_(name), context_getter_(context_getter) {} virtual void Run() { net::CookieStore* cookie_store = context_getter_->GetCookieStore(); cookie_store->DeleteCookie(url_, name_); } private: const GURL url_; const std::string name_; const scoped_refptr context_getter_; DISALLOW_COPY_AND_ASSIGN(RemoveCookieTask); }; } // namespace 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. GURL url; if (!ParseUrl(details, &url, true)) return false; std::string name; // Get the cookie name string or return false. EXTENSION_FUNCTION_VALIDATE(details->GetString(keys::kNameKey, &name)); URLRequestContextGetter* store_context = NULL; if (!ParseStoreContext(details, &store_context, NULL)) return false; DCHECK(store_context); // We don't bother to synchronously wait for the result here, because // CookieMonster is only ever accessed on the IO thread, so any other accesses // should happen after this. bool rv = ChromeThread::PostTask( ChromeThread::IO, FROM_HERE, new RemoveCookieTask(url, name, store_context)); DCHECK(rv); return true; } bool GetAllCookieStoresFunction::RunImpl() { Profile* original_profile = profile()->GetOriginalProfile(); DCHECK(original_profile); scoped_ptr original_tab_ids(new ListValue()); Profile* incognito_profile = NULL; scoped_ptr incognito_tab_ids; if (include_incognito()) { incognito_profile = profile()->GetOffTheRecordProfile(); if (incognito_profile) incognito_tab_ids.reset(new ListValue()); } // 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; }