| // Copyright (c) 2012 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/api/cookies/cookies_api.h" |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/json/json_writer.h" |
| #include "base/lazy_instance.h" |
| #include "base/memory/linked_ptr.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/extensions/api/cookies/cookies_api_constants.h" |
| #include "chrome/browser/extensions/api/cookies/cookies_helpers.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/common/extensions/api/cookies.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/notification_service.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/common/error_utils.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "net/cookies/canonical_cookie.h" |
| #include "net/cookies/cookie_constants.h" |
| #include "net/cookies/cookie_monster.h" |
| #include "net/cookies/cookie_store.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_context_getter.h" |
| |
| using content::BrowserThread; |
| |
| namespace extensions { |
| |
| namespace cookies = api::cookies; |
| namespace keys = cookies_api_constants; |
| namespace Get = cookies::Get; |
| namespace GetAll = cookies::GetAll; |
| namespace GetAllCookieStores = cookies::GetAllCookieStores; |
| namespace Remove = cookies::Remove; |
| namespace Set = cookies::Set; |
| |
| namespace { |
| |
| bool ParseUrl(ChromeAsyncExtensionFunction* function, |
| const std::string& url_string, |
| GURL* url, |
| bool check_host_permissions) { |
| *url = GURL(url_string); |
| if (!url->is_valid()) { |
| function->SetError( |
| ErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, url_string)); |
| return false; |
| } |
| // Check against host permissions if needed. |
| if (check_host_permissions && |
| !function->extension()->permissions_data()->HasHostPermission(*url)) { |
| function->SetError(ErrorUtils::FormatErrorMessage( |
| keys::kNoHostPermissionsError, url->spec())); |
| return false; |
| } |
| return true; |
| } |
| |
| bool ParseStoreContext(ChromeAsyncExtensionFunction* function, |
| std::string* store_id, |
| net::URLRequestContextGetter** context) { |
| DCHECK((context || store_id->empty())); |
| Profile* store_profile = NULL; |
| if (!store_id->empty()) { |
| store_profile = cookies_helpers::ChooseProfileFromStoreId( |
| *store_id, function->GetProfile(), function->include_incognito()); |
| if (!store_profile) { |
| function->SetError(ErrorUtils::FormatErrorMessage( |
| keys::kInvalidStoreIdError, *store_id)); |
| 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 = function->GetCurrentBrowser(); |
| if (!current_browser) { |
| function->SetError(keys::kNoCookieStoreFoundError); |
| return false; |
| } |
| store_profile = current_browser->profile(); |
| *store_id = cookies_helpers::GetStoreIdFromProfile(store_profile); |
| } |
| |
| if (context) |
| *context = store_profile->GetRequestContext(); |
| DCHECK(context); |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| CookiesEventRouter::CookiesEventRouter(content::BrowserContext* context) |
| : profile_(Profile::FromBrowserContext(context)) { |
| CHECK(registrar_.IsEmpty()); |
| registrar_.Add(this, |
| chrome::NOTIFICATION_COOKIE_CHANGED_FOR_EXTENSIONS, |
| content::NotificationService::AllBrowserContextsAndSources()); |
| } |
| |
| CookiesEventRouter::~CookiesEventRouter() { |
| } |
| |
| void CookiesEventRouter::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| Profile* profile = content::Source<Profile>(source).ptr(); |
| if (!profile_->IsSameProfile(profile)) |
| return; |
| |
| switch (type) { |
| case chrome::NOTIFICATION_COOKIE_CHANGED_FOR_EXTENSIONS: |
| CookieChanged( |
| profile, |
| content::Details<ChromeCookieDetails>(details).ptr()); |
| break; |
| |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| void CookiesEventRouter::CookieChanged( |
| Profile* profile, |
| ChromeCookieDetails* details) { |
| scoped_ptr<base::ListValue> args(new base::ListValue()); |
| base::DictionaryValue* dict = new base::DictionaryValue(); |
| dict->SetBoolean(keys::kRemovedKey, details->removed); |
| |
| scoped_ptr<cookies::Cookie> cookie(cookies_helpers::CreateCookie( |
| *details->cookie, cookies_helpers::GetStoreIdFromProfile(profile))); |
| dict->Set(keys::kCookieKey, cookie->ToValue().release()); |
| |
| // Map the internal cause to an external string. |
| std::string cause; |
| switch (details->cause) { |
| case net::CookieMonsterDelegate::CHANGE_COOKIE_EXPLICIT: |
| cause = keys::kExplicitChangeCause; |
| break; |
| |
| case net::CookieMonsterDelegate::CHANGE_COOKIE_OVERWRITE: |
| cause = keys::kOverwriteChangeCause; |
| break; |
| |
| case net::CookieMonsterDelegate::CHANGE_COOKIE_EXPIRED: |
| cause = keys::kExpiredChangeCause; |
| break; |
| |
| case net::CookieMonsterDelegate::CHANGE_COOKIE_EVICTED: |
| cause = keys::kEvictedChangeCause; |
| break; |
| |
| case net::CookieMonsterDelegate::CHANGE_COOKIE_EXPIRED_OVERWRITE: |
| cause = keys::kExpiredOverwriteChangeCause; |
| break; |
| |
| default: |
| NOTREACHED(); |
| } |
| dict->SetString(keys::kCauseKey, cause); |
| |
| args->Append(dict); |
| |
| GURL cookie_domain = |
| cookies_helpers::GetURLFromCanonicalCookie(*details->cookie); |
| DispatchEvent(profile, events::COOKIES_ON_CHANGED, |
| cookies::OnChanged::kEventName, std::move(args), cookie_domain); |
| } |
| |
| void CookiesEventRouter::DispatchEvent(content::BrowserContext* context, |
| events::HistogramValue histogram_value, |
| const std::string& event_name, |
| scoped_ptr<base::ListValue> event_args, |
| GURL& cookie_domain) { |
| EventRouter* router = context ? EventRouter::Get(context) : NULL; |
| if (!router) |
| return; |
| scoped_ptr<Event> event( |
| new Event(histogram_value, event_name, std::move(event_args))); |
| event->restrict_to_browser_context = context; |
| event->event_url = cookie_domain; |
| router->BroadcastEvent(std::move(event)); |
| } |
| |
| CookiesGetFunction::CookiesGetFunction() { |
| } |
| |
| CookiesGetFunction::~CookiesGetFunction() { |
| } |
| |
| bool CookiesGetFunction::RunAsync() { |
| parsed_args_ = Get::Params::Create(*args_); |
| EXTENSION_FUNCTION_VALIDATE(parsed_args_.get()); |
| |
| // Read/validate input parameters. |
| if (!ParseUrl(this, parsed_args_->details.url, &url_, true)) |
| return false; |
| |
| std::string store_id = |
| parsed_args_->details.store_id.get() ? *parsed_args_->details.store_id |
| : std::string(); |
| net::URLRequestContextGetter* store_context = NULL; |
| if (!ParseStoreContext(this, &store_id, &store_context)) |
| return false; |
| store_browser_context_ = store_context; |
| if (!parsed_args_->details.store_id.get()) |
| parsed_args_->details.store_id.reset(new std::string(store_id)); |
| |
| store_browser_context_ = store_context; |
| |
| bool rv = BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&CookiesGetFunction::GetCookieOnIOThread, this)); |
| DCHECK(rv); |
| |
| // Will finish asynchronously. |
| return true; |
| } |
| |
| void CookiesGetFunction::GetCookieOnIOThread() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| net::CookieStore* cookie_store = |
| store_browser_context_->GetURLRequestContext()->cookie_store(); |
| cookies_helpers::GetCookieListFromStore( |
| cookie_store, url_, |
| base::Bind(&CookiesGetFunction::GetCookieCallback, this)); |
| } |
| |
| void CookiesGetFunction::GetCookieCallback(const net::CookieList& cookie_list) { |
| net::CookieList::const_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() == parsed_args_->details.name) { |
| scoped_ptr<cookies::Cookie> cookie( |
| cookies_helpers::CreateCookie(*it, *parsed_args_->details.store_id)); |
| results_ = Get::Results::Create(*cookie); |
| break; |
| } |
| } |
| |
| // The cookie doesn't exist; return null. |
| if (it == cookie_list.end()) |
| SetResult(base::Value::CreateNullValue()); |
| |
| bool rv = BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&CookiesGetFunction::RespondOnUIThread, this)); |
| DCHECK(rv); |
| } |
| |
| void CookiesGetFunction::RespondOnUIThread() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| SendResponse(true); |
| } |
| |
| CookiesGetAllFunction::CookiesGetAllFunction() { |
| } |
| |
| CookiesGetAllFunction::~CookiesGetAllFunction() { |
| } |
| |
| bool CookiesGetAllFunction::RunAsync() { |
| parsed_args_ = GetAll::Params::Create(*args_); |
| EXTENSION_FUNCTION_VALIDATE(parsed_args_.get()); |
| |
| if (parsed_args_->details.url.get() && |
| !ParseUrl(this, *parsed_args_->details.url, &url_, false)) { |
| return false; |
| } |
| |
| std::string store_id = |
| parsed_args_->details.store_id.get() ? *parsed_args_->details.store_id |
| : std::string(); |
| net::URLRequestContextGetter* store_context = NULL; |
| if (!ParseStoreContext(this, &store_id, &store_context)) |
| return false; |
| store_browser_context_ = store_context; |
| if (!parsed_args_->details.store_id.get()) |
| parsed_args_->details.store_id.reset(new std::string(store_id)); |
| |
| bool rv = BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&CookiesGetAllFunction::GetAllCookiesOnIOThread, this)); |
| DCHECK(rv); |
| |
| // Will finish asynchronously. |
| return true; |
| } |
| |
| void CookiesGetAllFunction::GetAllCookiesOnIOThread() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| net::CookieStore* cookie_store = |
| store_browser_context_->GetURLRequestContext()->cookie_store(); |
| cookies_helpers::GetCookieListFromStore( |
| cookie_store, url_, |
| base::Bind(&CookiesGetAllFunction::GetAllCookiesCallback, this)); |
| } |
| |
| void CookiesGetAllFunction::GetAllCookiesCallback( |
| const net::CookieList& cookie_list) { |
| if (extension()) { |
| std::vector<linked_ptr<cookies::Cookie>> match_vector; |
| cookies_helpers::AppendMatchingCookiesToVector( |
| cookie_list, url_, &parsed_args_->details, extension(), &match_vector); |
| |
| results_ = GetAll::Results::Create(match_vector); |
| } |
| bool rv = BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&CookiesGetAllFunction::RespondOnUIThread, this)); |
| DCHECK(rv); |
| } |
| |
| void CookiesGetAllFunction::RespondOnUIThread() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| SendResponse(true); |
| } |
| |
| CookiesSetFunction::CookiesSetFunction() : success_(false) { |
| } |
| |
| CookiesSetFunction::~CookiesSetFunction() { |
| } |
| |
| bool CookiesSetFunction::RunAsync() { |
| parsed_args_ = Set::Params::Create(*args_); |
| EXTENSION_FUNCTION_VALIDATE(parsed_args_.get()); |
| |
| // Read/validate input parameters. |
| if (!ParseUrl(this, parsed_args_->details.url, &url_, true)) |
| return false; |
| |
| std::string store_id = |
| parsed_args_->details.store_id.get() ? *parsed_args_->details.store_id |
| : std::string(); |
| net::URLRequestContextGetter* store_context = NULL; |
| if (!ParseStoreContext(this, &store_id, &store_context)) |
| return false; |
| store_browser_context_ = store_context; |
| if (!parsed_args_->details.store_id.get()) |
| parsed_args_->details.store_id.reset(new std::string(store_id)); |
| |
| bool rv = BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&CookiesSetFunction::SetCookieOnIOThread, this)); |
| DCHECK(rv); |
| |
| // Will finish asynchronously. |
| return true; |
| } |
| |
| void CookiesSetFunction::SetCookieOnIOThread() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| net::CookieStore* cookie_store = |
| store_browser_context_->GetURLRequestContext()->cookie_store(); |
| |
| base::Time expiration_time; |
| if (parsed_args_->details.expiration_date.get()) { |
| // Time::FromDoubleT converts double time 0 to empty Time object. So we need |
| // to do special handling here. |
| expiration_time = (*parsed_args_->details.expiration_date == 0) ? |
| base::Time::UnixEpoch() : |
| base::Time::FromDoubleT(*parsed_args_->details.expiration_date); |
| } |
| |
| bool are_experimental_cookie_features_enabled = |
| store_browser_context_->GetURLRequestContext() |
| ->network_delegate() |
| ->AreExperimentalCookieFeaturesEnabled(); |
| |
| cookie_store->SetCookieWithDetailsAsync( |
| url_, parsed_args_->details.name.get() ? *parsed_args_->details.name |
| : std::string(), |
| parsed_args_->details.value.get() ? *parsed_args_->details.value |
| : std::string(), |
| parsed_args_->details.domain.get() ? *parsed_args_->details.domain |
| : std::string(), |
| parsed_args_->details.path.get() ? *parsed_args_->details.path |
| : std::string(), |
| base::Time(), expiration_time, base::Time(), |
| parsed_args_->details.secure.get() ? *parsed_args_->details.secure.get() |
| : false, |
| parsed_args_->details.http_only.get() ? *parsed_args_->details.http_only |
| : false, |
| // TODO(mkwst): If we decide to ship First-party-only cookies, we'll need |
| // to extend the extension API to support them. For the moment, we'll set |
| // all cookies as non-First-party-only. |
| net::CookieSameSite::DEFAULT_MODE, |
| are_experimental_cookie_features_enabled, net::COOKIE_PRIORITY_DEFAULT, |
| base::Bind(&CookiesSetFunction::PullCookie, this)); |
| } |
| |
| void CookiesSetFunction::PullCookie(bool set_cookie_result) { |
| // Pull the newly set cookie. |
| net::CookieStore* cookie_store = |
| store_browser_context_->GetURLRequestContext()->cookie_store(); |
| success_ = set_cookie_result; |
| cookies_helpers::GetCookieListFromStore( |
| cookie_store, url_, |
| base::Bind(&CookiesSetFunction::PullCookieCallback, this)); |
| } |
| |
| void CookiesSetFunction::PullCookieCallback( |
| const net::CookieList& cookie_list) { |
| net::CookieList::const_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). |
| std::string name = |
| parsed_args_->details.name.get() ? *parsed_args_->details.name |
| : std::string(); |
| if (it->Name() == name) { |
| scoped_ptr<cookies::Cookie> cookie( |
| cookies_helpers::CreateCookie(*it, *parsed_args_->details.store_id)); |
| results_ = Set::Results::Create(*cookie); |
| break; |
| } |
| } |
| |
| bool rv = BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&CookiesSetFunction::RespondOnUIThread, this)); |
| DCHECK(rv); |
| } |
| |
| void CookiesSetFunction::RespondOnUIThread() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!success_) { |
| std::string name = |
| parsed_args_->details.name.get() ? *parsed_args_->details.name |
| : std::string(); |
| error_ = ErrorUtils::FormatErrorMessage(keys::kCookieSetFailedError, name); |
| } |
| SendResponse(success_); |
| } |
| |
| CookiesRemoveFunction::CookiesRemoveFunction() { |
| } |
| |
| CookiesRemoveFunction::~CookiesRemoveFunction() { |
| } |
| |
| bool CookiesRemoveFunction::RunAsync() { |
| parsed_args_ = Remove::Params::Create(*args_); |
| EXTENSION_FUNCTION_VALIDATE(parsed_args_.get()); |
| |
| // Read/validate input parameters. |
| if (!ParseUrl(this, parsed_args_->details.url, &url_, true)) |
| return false; |
| |
| std::string store_id = |
| parsed_args_->details.store_id.get() ? *parsed_args_->details.store_id |
| : std::string(); |
| net::URLRequestContextGetter* store_context = NULL; |
| if (!ParseStoreContext(this, &store_id, &store_context)) |
| return false; |
| store_browser_context_ = store_context; |
| if (!parsed_args_->details.store_id.get()) |
| parsed_args_->details.store_id.reset(new std::string(store_id)); |
| |
| // Pass the work off to the IO thread. |
| bool rv = BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&CookiesRemoveFunction::RemoveCookieOnIOThread, this)); |
| DCHECK(rv); |
| |
| // Will return asynchronously. |
| return true; |
| } |
| |
| void CookiesRemoveFunction::RemoveCookieOnIOThread() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // Remove the cookie |
| net::CookieStore* cookie_store = |
| store_browser_context_->GetURLRequestContext()->cookie_store(); |
| cookie_store->DeleteCookieAsync( |
| url_, parsed_args_->details.name, |
| base::Bind(&CookiesRemoveFunction::RemoveCookieCallback, this)); |
| } |
| |
| void CookiesRemoveFunction::RemoveCookieCallback() { |
| // Build the callback result |
| Remove::Results::Details details; |
| details.name = parsed_args_->details.name; |
| details.url = url_.spec(); |
| details.store_id = *parsed_args_->details.store_id; |
| results_ = Remove::Results::Create(details); |
| |
| // Return to UI thread |
| bool rv = BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&CookiesRemoveFunction::RespondOnUIThread, this)); |
| DCHECK(rv); |
| } |
| |
| void CookiesRemoveFunction::RespondOnUIThread() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| SendResponse(true); |
| } |
| |
| bool CookiesGetAllCookieStoresFunction::RunSync() { |
| Profile* original_profile = GetProfile(); |
| DCHECK(original_profile); |
| scoped_ptr<base::ListValue> original_tab_ids(new base::ListValue()); |
| Profile* incognito_profile = NULL; |
| scoped_ptr<base::ListValue> incognito_tab_ids; |
| if (include_incognito() && GetProfile()->HasOffTheRecordProfile()) { |
| incognito_profile = GetProfile()->GetOffTheRecordProfile(); |
| if (incognito_profile) |
| incognito_tab_ids.reset(new base::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 (auto* browser : *BrowserList::GetInstance()) { |
| if (browser->profile() == original_profile) { |
| cookies_helpers::AppendToTabIdList(browser, original_tab_ids.get()); |
| } else if (incognito_tab_ids.get() && |
| browser->profile() == incognito_profile) { |
| cookies_helpers::AppendToTabIdList(browser, incognito_tab_ids.get()); |
| } |
| } |
| // Return a list of all cookie stores with at least one open tab. |
| std::vector<linked_ptr<cookies::CookieStore>> cookie_stores; |
| if (original_tab_ids->GetSize() > 0) { |
| cookie_stores.push_back(make_linked_ptr( |
| cookies_helpers::CreateCookieStore( |
| original_profile, original_tab_ids.release()).release())); |
| } |
| if (incognito_tab_ids.get() && incognito_tab_ids->GetSize() > 0 && |
| incognito_profile) { |
| cookie_stores.push_back(make_linked_ptr( |
| cookies_helpers::CreateCookieStore( |
| incognito_profile, incognito_tab_ids.release()).release())); |
| } |
| results_ = GetAllCookieStores::Results::Create(cookie_stores); |
| return true; |
| } |
| |
| CookiesAPI::CookiesAPI(content::BrowserContext* context) |
| : browser_context_(context) { |
| EventRouter::Get(browser_context_) |
| ->RegisterObserver(this, cookies::OnChanged::kEventName); |
| } |
| |
| CookiesAPI::~CookiesAPI() { |
| } |
| |
| void CookiesAPI::Shutdown() { |
| EventRouter::Get(browser_context_)->UnregisterObserver(this); |
| } |
| |
| static base::LazyInstance<BrowserContextKeyedAPIFactory<CookiesAPI> > |
| g_factory = LAZY_INSTANCE_INITIALIZER; |
| |
| // static |
| BrowserContextKeyedAPIFactory<CookiesAPI>* CookiesAPI::GetFactoryInstance() { |
| return g_factory.Pointer(); |
| } |
| |
| void CookiesAPI::OnListenerAdded(const EventListenerInfo& details) { |
| cookies_event_router_.reset(new CookiesEventRouter(browser_context_)); |
| EventRouter::Get(browser_context_)->UnregisterObserver(this); |
| } |
| |
| } // namespace extensions |