1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // Implements the Chrome Extensions Cookies API.
7 #include "chrome/browser/extensions/api/cookies/cookies_api.h"
11 #include "base/bind.h"
12 #include "base/json/json_writer.h"
13 #include "base/lazy_instance.h"
14 #include "base/memory/linked_ptr.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/time/time.h"
17 #include "base/values.h"
18 #include "chrome/browser/chrome_notification_types.h"
19 #include "chrome/browser/extensions/api/cookies/cookies_api_constants.h"
20 #include "chrome/browser/extensions/api/cookies/cookies_helpers.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/browser_iterator.h"
24 #include "chrome/common/extensions/api/cookies.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "content/public/browser/notification_service.h"
27 #include "extensions/browser/event_router.h"
28 #include "extensions/common/error_utils.h"
29 #include "extensions/common/extension.h"
30 #include "extensions/common/permissions/permissions_data.h"
31 #include "net/cookies/canonical_cookie.h"
32 #include "net/cookies/cookie_constants.h"
33 #include "net/cookies/cookie_monster.h"
34 #include "net/url_request/url_request_context.h"
35 #include "net/url_request/url_request_context_getter.h"
37 using content::BrowserThread
;
39 namespace extensions
{
41 namespace cookies
= api::cookies
;
42 namespace keys
= cookies_api_constants
;
43 namespace Get
= cookies::Get
;
44 namespace GetAll
= cookies::GetAll
;
45 namespace GetAllCookieStores
= cookies::GetAllCookieStores
;
46 namespace Remove
= cookies::Remove
;
47 namespace Set
= cookies::Set
;
51 bool ParseUrl(ChromeAsyncExtensionFunction
* function
,
52 const std::string
& url_string
,
54 bool check_host_permissions
) {
55 *url
= GURL(url_string
);
56 if (!url
->is_valid()) {
58 ErrorUtils::FormatErrorMessage(keys::kInvalidUrlError
, url_string
));
61 // Check against host permissions if needed.
62 if (check_host_permissions
&&
63 !function
->extension()->permissions_data()->HasHostPermission(*url
)) {
64 function
->SetError(ErrorUtils::FormatErrorMessage(
65 keys::kNoHostPermissionsError
, url
->spec()));
71 bool ParseStoreContext(ChromeAsyncExtensionFunction
* function
,
72 std::string
* store_id
,
73 net::URLRequestContextGetter
** context
) {
74 DCHECK((context
|| store_id
->empty()));
75 Profile
* store_profile
= NULL
;
76 if (!store_id
->empty()) {
77 store_profile
= cookies_helpers::ChooseProfileFromStoreId(
78 *store_id
, function
->GetProfile(), function
->include_incognito());
80 function
->SetError(ErrorUtils::FormatErrorMessage(
81 keys::kInvalidStoreIdError
, *store_id
));
85 // The store ID was not specified; use the current execution context's
86 // cookie store by default.
87 // GetCurrentBrowser() already takes into account incognito settings.
88 Browser
* current_browser
= function
->GetCurrentBrowser();
89 if (!current_browser
) {
90 function
->SetError(keys::kNoCookieStoreFoundError
);
93 store_profile
= current_browser
->profile();
94 *store_id
= cookies_helpers::GetStoreIdFromProfile(store_profile
);
98 *context
= store_profile
->GetRequestContext();
106 CookiesEventRouter::CookiesEventRouter(content::BrowserContext
* context
)
107 : profile_(Profile::FromBrowserContext(context
)) {
108 CHECK(registrar_
.IsEmpty());
110 chrome::NOTIFICATION_COOKIE_CHANGED_FOR_EXTENSIONS
,
111 content::NotificationService::AllBrowserContextsAndSources());
114 CookiesEventRouter::~CookiesEventRouter() {
117 void CookiesEventRouter::Observe(
119 const content::NotificationSource
& source
,
120 const content::NotificationDetails
& details
) {
121 Profile
* profile
= content::Source
<Profile
>(source
).ptr();
122 if (!profile_
->IsSameProfile(profile
))
126 case chrome::NOTIFICATION_COOKIE_CHANGED_FOR_EXTENSIONS
:
129 content::Details
<ChromeCookieDetails
>(details
).ptr());
137 void CookiesEventRouter::CookieChanged(
139 ChromeCookieDetails
* details
) {
140 scoped_ptr
<base::ListValue
> args(new base::ListValue());
141 base::DictionaryValue
* dict
= new base::DictionaryValue();
142 dict
->SetBoolean(keys::kRemovedKey
, details
->removed
);
144 scoped_ptr
<cookies::Cookie
> cookie(cookies_helpers::CreateCookie(
145 *details
->cookie
, cookies_helpers::GetStoreIdFromProfile(profile
)));
146 dict
->Set(keys::kCookieKey
, cookie
->ToValue().release());
148 // Map the internal cause to an external string.
150 switch (details
->cause
) {
151 case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPLICIT
:
152 cause
= keys::kExplicitChangeCause
;
155 case net::CookieMonster::Delegate::CHANGE_COOKIE_OVERWRITE
:
156 cause
= keys::kOverwriteChangeCause
;
159 case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPIRED
:
160 cause
= keys::kExpiredChangeCause
;
163 case net::CookieMonster::Delegate::CHANGE_COOKIE_EVICTED
:
164 cause
= keys::kEvictedChangeCause
;
167 case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPIRED_OVERWRITE
:
168 cause
= keys::kExpiredOverwriteChangeCause
;
174 dict
->SetString(keys::kCauseKey
, cause
);
179 cookies_helpers::GetURLFromCanonicalCookie(*details
->cookie
);
180 DispatchEvent(profile
, events::COOKIES_ON_CHANGED
,
181 cookies::OnChanged::kEventName
, args
.Pass(), cookie_domain
);
184 void CookiesEventRouter::DispatchEvent(content::BrowserContext
* context
,
185 events::HistogramValue histogram_value
,
186 const std::string
& event_name
,
187 scoped_ptr
<base::ListValue
> event_args
,
188 GURL
& cookie_domain
) {
189 EventRouter
* router
= context
? EventRouter::Get(context
) : NULL
;
192 scoped_ptr
<Event
> event(
193 new Event(histogram_value
, event_name
, event_args
.Pass()));
194 event
->restrict_to_browser_context
= context
;
195 event
->event_url
= cookie_domain
;
196 router
->BroadcastEvent(event
.Pass());
199 CookiesGetFunction::CookiesGetFunction() {
202 CookiesGetFunction::~CookiesGetFunction() {
205 bool CookiesGetFunction::RunAsync() {
206 parsed_args_
= Get::Params::Create(*args_
);
207 EXTENSION_FUNCTION_VALIDATE(parsed_args_
.get());
209 // Read/validate input parameters.
210 if (!ParseUrl(this, parsed_args_
->details
.url
, &url_
, true))
213 std::string store_id
=
214 parsed_args_
->details
.store_id
.get() ? *parsed_args_
->details
.store_id
216 net::URLRequestContextGetter
* store_context
= NULL
;
217 if (!ParseStoreContext(this, &store_id
, &store_context
))
219 store_browser_context_
= store_context
;
220 if (!parsed_args_
->details
.store_id
.get())
221 parsed_args_
->details
.store_id
.reset(new std::string(store_id
));
223 store_browser_context_
= store_context
;
225 bool rv
= BrowserThread::PostTask(
226 BrowserThread::IO
, FROM_HERE
,
227 base::Bind(&CookiesGetFunction::GetCookieOnIOThread
, this));
230 // Will finish asynchronously.
234 void CookiesGetFunction::GetCookieOnIOThread() {
235 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
236 net::CookieStore
* cookie_store
=
237 store_browser_context_
->GetURLRequestContext()->cookie_store();
238 cookies_helpers::GetCookieListFromStore(
240 base::Bind(&CookiesGetFunction::GetCookieCallback
, this));
243 void CookiesGetFunction::GetCookieCallback(const net::CookieList
& cookie_list
) {
244 net::CookieList::const_iterator it
;
245 for (it
= cookie_list
.begin(); it
!= cookie_list
.end(); ++it
) {
246 // Return the first matching cookie. Relies on the fact that the
247 // CookieMonster returns them in canonical order (longest path, then
248 // earliest creation time).
249 if (it
->Name() == parsed_args_
->details
.name
) {
250 scoped_ptr
<cookies::Cookie
> cookie(
251 cookies_helpers::CreateCookie(*it
, *parsed_args_
->details
.store_id
));
252 results_
= Get::Results::Create(*cookie
);
257 // The cookie doesn't exist; return null.
258 if (it
== cookie_list
.end())
259 SetResult(base::Value::CreateNullValue());
261 bool rv
= BrowserThread::PostTask(
262 BrowserThread::UI
, FROM_HERE
,
263 base::Bind(&CookiesGetFunction::RespondOnUIThread
, this));
267 void CookiesGetFunction::RespondOnUIThread() {
268 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
272 CookiesGetAllFunction::CookiesGetAllFunction() {
275 CookiesGetAllFunction::~CookiesGetAllFunction() {
278 bool CookiesGetAllFunction::RunAsync() {
279 parsed_args_
= GetAll::Params::Create(*args_
);
280 EXTENSION_FUNCTION_VALIDATE(parsed_args_
.get());
282 if (parsed_args_
->details
.url
.get() &&
283 !ParseUrl(this, *parsed_args_
->details
.url
, &url_
, false)) {
287 std::string store_id
=
288 parsed_args_
->details
.store_id
.get() ? *parsed_args_
->details
.store_id
290 net::URLRequestContextGetter
* store_context
= NULL
;
291 if (!ParseStoreContext(this, &store_id
, &store_context
))
293 store_browser_context_
= store_context
;
294 if (!parsed_args_
->details
.store_id
.get())
295 parsed_args_
->details
.store_id
.reset(new std::string(store_id
));
297 bool rv
= BrowserThread::PostTask(
298 BrowserThread::IO
, FROM_HERE
,
299 base::Bind(&CookiesGetAllFunction::GetAllCookiesOnIOThread
, this));
302 // Will finish asynchronously.
306 void CookiesGetAllFunction::GetAllCookiesOnIOThread() {
307 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
308 net::CookieStore
* cookie_store
=
309 store_browser_context_
->GetURLRequestContext()->cookie_store();
310 cookies_helpers::GetCookieListFromStore(
312 base::Bind(&CookiesGetAllFunction::GetAllCookiesCallback
, this));
315 void CookiesGetAllFunction::GetAllCookiesCallback(
316 const net::CookieList
& cookie_list
) {
318 std::vector
<linked_ptr
<cookies::Cookie
>> match_vector
;
319 cookies_helpers::AppendMatchingCookiesToVector(
320 cookie_list
, url_
, &parsed_args_
->details
, extension(), &match_vector
);
322 results_
= GetAll::Results::Create(match_vector
);
324 bool rv
= BrowserThread::PostTask(
325 BrowserThread::UI
, FROM_HERE
,
326 base::Bind(&CookiesGetAllFunction::RespondOnUIThread
, this));
330 void CookiesGetAllFunction::RespondOnUIThread() {
331 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
335 CookiesSetFunction::CookiesSetFunction() : success_(false) {
338 CookiesSetFunction::~CookiesSetFunction() {
341 bool CookiesSetFunction::RunAsync() {
342 parsed_args_
= Set::Params::Create(*args_
);
343 EXTENSION_FUNCTION_VALIDATE(parsed_args_
.get());
345 // Read/validate input parameters.
346 if (!ParseUrl(this, parsed_args_
->details
.url
, &url_
, true))
349 std::string store_id
=
350 parsed_args_
->details
.store_id
.get() ? *parsed_args_
->details
.store_id
352 net::URLRequestContextGetter
* store_context
= NULL
;
353 if (!ParseStoreContext(this, &store_id
, &store_context
))
355 store_browser_context_
= store_context
;
356 if (!parsed_args_
->details
.store_id
.get())
357 parsed_args_
->details
.store_id
.reset(new std::string(store_id
));
359 bool rv
= BrowserThread::PostTask(
360 BrowserThread::IO
, FROM_HERE
,
361 base::Bind(&CookiesSetFunction::SetCookieOnIOThread
, this));
364 // Will finish asynchronously.
368 void CookiesSetFunction::SetCookieOnIOThread() {
369 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
370 net::CookieMonster
* cookie_monster
=
371 store_browser_context_
->GetURLRequestContext()
373 ->GetCookieMonster();
375 base::Time expiration_time
;
376 if (parsed_args_
->details
.expiration_date
.get()) {
377 // Time::FromDoubleT converts double time 0 to empty Time object. So we need
378 // to do special handling here.
379 expiration_time
= (*parsed_args_
->details
.expiration_date
== 0) ?
380 base::Time::UnixEpoch() :
381 base::Time::FromDoubleT(*parsed_args_
->details
.expiration_date
);
384 cookie_monster
->SetCookieWithDetailsAsync(
386 parsed_args_
->details
.name
.get() ? *parsed_args_
->details
.name
388 parsed_args_
->details
.value
.get() ? *parsed_args_
->details
.value
390 parsed_args_
->details
.domain
.get() ? *parsed_args_
->details
.domain
392 parsed_args_
->details
.path
.get() ? *parsed_args_
->details
.path
395 parsed_args_
->details
.secure
.get() ? *parsed_args_
->details
.secure
.get()
397 parsed_args_
->details
.http_only
.get() ? *parsed_args_
->details
.http_only
399 // TODO(mkwst): If we decide to ship First-party-only cookies, we'll need
400 // to extend the extension API to support them. For the moment, we'll set
401 // all cookies as non-First-party-only.
403 net::COOKIE_PRIORITY_DEFAULT
,
404 base::Bind(&CookiesSetFunction::PullCookie
, this));
407 void CookiesSetFunction::PullCookie(bool set_cookie_result
) {
408 // Pull the newly set cookie.
409 net::CookieMonster
* cookie_monster
=
410 store_browser_context_
->GetURLRequestContext()
412 ->GetCookieMonster();
413 success_
= set_cookie_result
;
414 cookies_helpers::GetCookieListFromStore(
415 cookie_monster
, url_
,
416 base::Bind(&CookiesSetFunction::PullCookieCallback
, this));
419 void CookiesSetFunction::PullCookieCallback(
420 const net::CookieList
& cookie_list
) {
421 net::CookieList::const_iterator it
;
422 for (it
= cookie_list
.begin(); it
!= cookie_list
.end(); ++it
) {
423 // Return the first matching cookie. Relies on the fact that the
424 // CookieMonster returns them in canonical order (longest path, then
425 // earliest creation time).
427 parsed_args_
->details
.name
.get() ? *parsed_args_
->details
.name
429 if (it
->Name() == name
) {
430 scoped_ptr
<cookies::Cookie
> cookie(
431 cookies_helpers::CreateCookie(*it
, *parsed_args_
->details
.store_id
));
432 results_
= Set::Results::Create(*cookie
);
437 bool rv
= BrowserThread::PostTask(
438 BrowserThread::UI
, FROM_HERE
,
439 base::Bind(&CookiesSetFunction::RespondOnUIThread
, this));
443 void CookiesSetFunction::RespondOnUIThread() {
444 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
447 parsed_args_
->details
.name
.get() ? *parsed_args_
->details
.name
449 error_
= ErrorUtils::FormatErrorMessage(keys::kCookieSetFailedError
, name
);
451 SendResponse(success_
);
454 CookiesRemoveFunction::CookiesRemoveFunction() {
457 CookiesRemoveFunction::~CookiesRemoveFunction() {
460 bool CookiesRemoveFunction::RunAsync() {
461 parsed_args_
= Remove::Params::Create(*args_
);
462 EXTENSION_FUNCTION_VALIDATE(parsed_args_
.get());
464 // Read/validate input parameters.
465 if (!ParseUrl(this, parsed_args_
->details
.url
, &url_
, true))
468 std::string store_id
=
469 parsed_args_
->details
.store_id
.get() ? *parsed_args_
->details
.store_id
471 net::URLRequestContextGetter
* store_context
= NULL
;
472 if (!ParseStoreContext(this, &store_id
, &store_context
))
474 store_browser_context_
= store_context
;
475 if (!parsed_args_
->details
.store_id
.get())
476 parsed_args_
->details
.store_id
.reset(new std::string(store_id
));
478 // Pass the work off to the IO thread.
479 bool rv
= BrowserThread::PostTask(
480 BrowserThread::IO
, FROM_HERE
,
481 base::Bind(&CookiesRemoveFunction::RemoveCookieOnIOThread
, this));
484 // Will return asynchronously.
488 void CookiesRemoveFunction::RemoveCookieOnIOThread() {
489 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
492 net::CookieStore
* cookie_store
=
493 store_browser_context_
->GetURLRequestContext()->cookie_store();
494 cookie_store
->DeleteCookieAsync(
495 url_
, parsed_args_
->details
.name
,
496 base::Bind(&CookiesRemoveFunction::RemoveCookieCallback
, this));
499 void CookiesRemoveFunction::RemoveCookieCallback() {
500 // Build the callback result
501 Remove::Results::Details details
;
502 details
.name
= parsed_args_
->details
.name
;
503 details
.url
= url_
.spec();
504 details
.store_id
= *parsed_args_
->details
.store_id
;
505 results_
= Remove::Results::Create(details
);
507 // Return to UI thread
508 bool rv
= BrowserThread::PostTask(
509 BrowserThread::UI
, FROM_HERE
,
510 base::Bind(&CookiesRemoveFunction::RespondOnUIThread
, this));
514 void CookiesRemoveFunction::RespondOnUIThread() {
515 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
519 bool CookiesGetAllCookieStoresFunction::RunSync() {
520 Profile
* original_profile
= GetProfile();
521 DCHECK(original_profile
);
522 scoped_ptr
<base::ListValue
> original_tab_ids(new base::ListValue());
523 Profile
* incognito_profile
= NULL
;
524 scoped_ptr
<base::ListValue
> incognito_tab_ids
;
525 if (include_incognito() && GetProfile()->HasOffTheRecordProfile()) {
526 incognito_profile
= GetProfile()->GetOffTheRecordProfile();
527 if (incognito_profile
)
528 incognito_tab_ids
.reset(new base::ListValue());
530 DCHECK(original_profile
!= incognito_profile
);
532 // Iterate through all browser instances, and for each browser,
533 // add its tab IDs to either the regular or incognito tab ID list depending
534 // whether the browser is regular or incognito.
535 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
536 Browser
* browser
= *it
;
537 if (browser
->profile() == original_profile
) {
538 cookies_helpers::AppendToTabIdList(browser
, original_tab_ids
.get());
539 } else if (incognito_tab_ids
.get() &&
540 browser
->profile() == incognito_profile
) {
541 cookies_helpers::AppendToTabIdList(browser
, incognito_tab_ids
.get());
544 // Return a list of all cookie stores with at least one open tab.
545 std::vector
<linked_ptr
<cookies::CookieStore
>> cookie_stores
;
546 if (original_tab_ids
->GetSize() > 0) {
547 cookie_stores
.push_back(make_linked_ptr(
548 cookies_helpers::CreateCookieStore(
549 original_profile
, original_tab_ids
.release()).release()));
551 if (incognito_tab_ids
.get() && incognito_tab_ids
->GetSize() > 0 &&
553 cookie_stores
.push_back(make_linked_ptr(
554 cookies_helpers::CreateCookieStore(
555 incognito_profile
, incognito_tab_ids
.release()).release()));
557 results_
= GetAllCookieStores::Results::Create(cookie_stores
);
561 CookiesAPI::CookiesAPI(content::BrowserContext
* context
)
562 : browser_context_(context
) {
563 EventRouter::Get(browser_context_
)
564 ->RegisterObserver(this, cookies::OnChanged::kEventName
);
567 CookiesAPI::~CookiesAPI() {
570 void CookiesAPI::Shutdown() {
571 EventRouter::Get(browser_context_
)->UnregisterObserver(this);
574 static base::LazyInstance
<BrowserContextKeyedAPIFactory
<CookiesAPI
> >
575 g_factory
= LAZY_INSTANCE_INITIALIZER
;
578 BrowserContextKeyedAPIFactory
<CookiesAPI
>* CookiesAPI::GetFactoryInstance() {
579 return g_factory
.Pointer();
582 void CookiesAPI::OnListenerAdded(const EventListenerInfo
& details
) {
583 cookies_event_router_
.reset(new CookiesEventRouter(browser_context_
));
584 EventRouter::Get(browser_context_
)->UnregisterObserver(this);
587 } // namespace extensions