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.
4
5 // Implements the Chrome Extensions Cookies API.
6
7 #include "chrome/browser/extensions/api/cookies/cookies_api.h"
8
9 #include <vector>
10
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"
36
37 using content::BrowserThread;
38 using extensions::api::cookies::Cookie;
39 using extensions::api::cookies::CookieStore;
40
41 namespace Get = extensions::api::cookies::Get;
42 namespace GetAll = extensions::api::cookies::GetAll;
43 namespace GetAllCookieStores = extensions::api::cookies::GetAllCookieStores;
44 namespace Remove = extensions::api::cookies::Remove;
45 namespace Set = extensions::api::cookies::Set;
46
47 namespace extensions {
48 namespace cookies = api::cookies;
49 namespace keys = cookies_api_constants;
50
51 namespace {
52
ParseUrl(ChromeAsyncExtensionFunction * function,const std::string & url_string,GURL * url,bool check_host_permissions)53 bool ParseUrl(ChromeAsyncExtensionFunction* function,
54 const std::string& url_string,
55 GURL* url,
56 bool check_host_permissions) {
57 *url = GURL(url_string);
58 if (!url->is_valid()) {
59 function->SetError(
60 ErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, url_string));
61 return false;
62 }
63 // Check against host permissions if needed.
64 if (check_host_permissions &&
65 !function->GetExtension()->permissions_data()->HasHostPermission(*url)) {
66 function->SetError(ErrorUtils::FormatErrorMessage(
67 keys::kNoHostPermissionsError, url->spec()));
68 return false;
69 }
70 return true;
71 }
72
ParseStoreContext(ChromeAsyncExtensionFunction * function,std::string * store_id,net::URLRequestContextGetter ** context)73 bool ParseStoreContext(ChromeAsyncExtensionFunction* function,
74 std::string* store_id,
75 net::URLRequestContextGetter** context) {
76 DCHECK((context || store_id->empty()));
77 Profile* store_profile = NULL;
78 if (!store_id->empty()) {
79 store_profile = cookies_helpers::ChooseProfileFromStoreId(
80 *store_id, function->GetProfile(), function->include_incognito());
81 if (!store_profile) {
82 function->SetError(ErrorUtils::FormatErrorMessage(
83 keys::kInvalidStoreIdError, *store_id));
84 return false;
85 }
86 } else {
87 // The store ID was not specified; use the current execution context's
88 // cookie store by default.
89 // GetCurrentBrowser() already takes into account incognito settings.
90 Browser* current_browser = function->GetCurrentBrowser();
91 if (!current_browser) {
92 function->SetError(keys::kNoCookieStoreFoundError);
93 return false;
94 }
95 store_profile = current_browser->profile();
96 *store_id = cookies_helpers::GetStoreIdFromProfile(store_profile);
97 }
98
99 if (context)
100 *context = store_profile->GetRequestContext();
101 DCHECK(context);
102
103 return true;
104 }
105
106 } // namespace
107
CookiesEventRouter(content::BrowserContext * context)108 CookiesEventRouter::CookiesEventRouter(content::BrowserContext* context)
109 : profile_(Profile::FromBrowserContext(context)) {
110 CHECK(registrar_.IsEmpty());
111 registrar_.Add(this,
112 chrome::NOTIFICATION_COOKIE_CHANGED,
113 content::NotificationService::AllBrowserContextsAndSources());
114 }
115
~CookiesEventRouter()116 CookiesEventRouter::~CookiesEventRouter() {
117 }
118
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)119 void CookiesEventRouter::Observe(
120 int type,
121 const content::NotificationSource& source,
122 const content::NotificationDetails& details) {
123 Profile* profile = content::Source<Profile>(source).ptr();
124 if (!profile_->IsSameProfile(profile))
125 return;
126
127 switch (type) {
128 case chrome::NOTIFICATION_COOKIE_CHANGED:
129 CookieChanged(
130 profile,
131 content::Details<ChromeCookieDetails>(details).ptr());
132 break;
133
134 default:
135 NOTREACHED();
136 }
137 }
138
CookieChanged(Profile * profile,ChromeCookieDetails * details)139 void CookiesEventRouter::CookieChanged(
140 Profile* profile,
141 ChromeCookieDetails* details) {
142 scoped_ptr<base::ListValue> args(new base::ListValue());
143 base::DictionaryValue* dict = new base::DictionaryValue();
144 dict->SetBoolean(keys::kRemovedKey, details->removed);
145
146 scoped_ptr<Cookie> cookie(
147 cookies_helpers::CreateCookie(*details->cookie,
148 cookies_helpers::GetStoreIdFromProfile(profile)));
149 dict->Set(keys::kCookieKey, cookie->ToValue().release());
150
151 // Map the internal cause to an external string.
152 std::string cause;
153 switch (details->cause) {
154 case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPLICIT:
155 cause = keys::kExplicitChangeCause;
156 break;
157
158 case net::CookieMonster::Delegate::CHANGE_COOKIE_OVERWRITE:
159 cause = keys::kOverwriteChangeCause;
160 break;
161
162 case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPIRED:
163 cause = keys::kExpiredChangeCause;
164 break;
165
166 case net::CookieMonster::Delegate::CHANGE_COOKIE_EVICTED:
167 cause = keys::kEvictedChangeCause;
168 break;
169
170 case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPIRED_OVERWRITE:
171 cause = keys::kExpiredOverwriteChangeCause;
172 break;
173
174 default:
175 NOTREACHED();
176 }
177 dict->SetString(keys::kCauseKey, cause);
178
179 args->Append(dict);
180
181 GURL cookie_domain =
182 cookies_helpers::GetURLFromCanonicalCookie(*details->cookie);
183 DispatchEvent(profile,
184 cookies::OnChanged::kEventName,
185 args.Pass(),
186 cookie_domain);
187 }
188
DispatchEvent(content::BrowserContext * context,const std::string & event_name,scoped_ptr<base::ListValue> event_args,GURL & cookie_domain)189 void CookiesEventRouter::DispatchEvent(content::BrowserContext* context,
190 const std::string& event_name,
191 scoped_ptr<base::ListValue> event_args,
192 GURL& cookie_domain) {
193 EventRouter* router = context ? extensions::EventRouter::Get(context) : NULL;
194 if (!router)
195 return;
196 scoped_ptr<Event> event(new Event(event_name, event_args.Pass()));
197 event->restrict_to_browser_context = context;
198 event->event_url = cookie_domain;
199 router->BroadcastEvent(event.Pass());
200 }
201
CookiesGetFunction()202 CookiesGetFunction::CookiesGetFunction() {
203 }
204
~CookiesGetFunction()205 CookiesGetFunction::~CookiesGetFunction() {
206 }
207
RunAsync()208 bool CookiesGetFunction::RunAsync() {
209 parsed_args_ = Get::Params::Create(*args_);
210 EXTENSION_FUNCTION_VALIDATE(parsed_args_.get());
211
212 // Read/validate input parameters.
213 if (!ParseUrl(this, parsed_args_->details.url, &url_, true))
214 return false;
215
216 std::string store_id =
217 parsed_args_->details.store_id.get() ? *parsed_args_->details.store_id
218 : std::string();
219 net::URLRequestContextGetter* store_context = NULL;
220 if (!ParseStoreContext(this, &store_id, &store_context))
221 return false;
222 store_browser_context_ = store_context;
223 if (!parsed_args_->details.store_id.get())
224 parsed_args_->details.store_id.reset(new std::string(store_id));
225
226 store_browser_context_ = store_context;
227
228 bool rv = BrowserThread::PostTask(
229 BrowserThread::IO, FROM_HERE,
230 base::Bind(&CookiesGetFunction::GetCookieOnIOThread, this));
231 DCHECK(rv);
232
233 // Will finish asynchronously.
234 return true;
235 }
236
GetCookieOnIOThread()237 void CookiesGetFunction::GetCookieOnIOThread() {
238 DCHECK_CURRENTLY_ON(BrowserThread::IO);
239 net::CookieStore* cookie_store =
240 store_browser_context_->GetURLRequestContext()->cookie_store();
241 cookies_helpers::GetCookieListFromStore(
242 cookie_store, url_,
243 base::Bind(&CookiesGetFunction::GetCookieCallback, this));
244 }
245
GetCookieCallback(const net::CookieList & cookie_list)246 void CookiesGetFunction::GetCookieCallback(const net::CookieList& cookie_list) {
247 net::CookieList::const_iterator it;
248 for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
249 // Return the first matching cookie. Relies on the fact that the
250 // CookieMonster returns them in canonical order (longest path, then
251 // earliest creation time).
252 if (it->Name() == parsed_args_->details.name) {
253 scoped_ptr<Cookie> cookie(
254 cookies_helpers::CreateCookie(*it, *parsed_args_->details.store_id));
255 results_ = Get::Results::Create(*cookie);
256 break;
257 }
258 }
259
260 // The cookie doesn't exist; return null.
261 if (it == cookie_list.end())
262 SetResult(base::Value::CreateNullValue());
263
264 bool rv = BrowserThread::PostTask(
265 BrowserThread::UI, FROM_HERE,
266 base::Bind(&CookiesGetFunction::RespondOnUIThread, this));
267 DCHECK(rv);
268 }
269
RespondOnUIThread()270 void CookiesGetFunction::RespondOnUIThread() {
271 DCHECK_CURRENTLY_ON(BrowserThread::UI);
272 SendResponse(true);
273 }
274
CookiesGetAllFunction()275 CookiesGetAllFunction::CookiesGetAllFunction() {
276 }
277
~CookiesGetAllFunction()278 CookiesGetAllFunction::~CookiesGetAllFunction() {
279 }
280
RunAsync()281 bool CookiesGetAllFunction::RunAsync() {
282 parsed_args_ = GetAll::Params::Create(*args_);
283 EXTENSION_FUNCTION_VALIDATE(parsed_args_.get());
284
285 if (parsed_args_->details.url.get() &&
286 !ParseUrl(this, *parsed_args_->details.url, &url_, false)) {
287 return false;
288 }
289
290 std::string store_id =
291 parsed_args_->details.store_id.get() ? *parsed_args_->details.store_id
292 : std::string();
293 net::URLRequestContextGetter* store_context = NULL;
294 if (!ParseStoreContext(this, &store_id, &store_context))
295 return false;
296 store_browser_context_ = store_context;
297 if (!parsed_args_->details.store_id.get())
298 parsed_args_->details.store_id.reset(new std::string(store_id));
299
300 bool rv = BrowserThread::PostTask(
301 BrowserThread::IO, FROM_HERE,
302 base::Bind(&CookiesGetAllFunction::GetAllCookiesOnIOThread, this));
303 DCHECK(rv);
304
305 // Will finish asynchronously.
306 return true;
307 }
308
GetAllCookiesOnIOThread()309 void CookiesGetAllFunction::GetAllCookiesOnIOThread() {
310 DCHECK_CURRENTLY_ON(BrowserThread::IO);
311 net::CookieStore* cookie_store =
312 store_browser_context_->GetURLRequestContext()->cookie_store();
313 cookies_helpers::GetCookieListFromStore(
314 cookie_store, url_,
315 base::Bind(&CookiesGetAllFunction::GetAllCookiesCallback, this));
316 }
317
GetAllCookiesCallback(const net::CookieList & cookie_list)318 void CookiesGetAllFunction::GetAllCookiesCallback(
319 const net::CookieList& cookie_list) {
320 const extensions::Extension* extension = GetExtension();
321 if (extension) {
322 std::vector<linked_ptr<Cookie> > match_vector;
323 cookies_helpers::AppendMatchingCookiesToVector(
324 cookie_list, url_, &parsed_args_->details,
325 GetExtension(), &match_vector);
326
327 results_ = GetAll::Results::Create(match_vector);
328 }
329 bool rv = BrowserThread::PostTask(
330 BrowserThread::UI, FROM_HERE,
331 base::Bind(&CookiesGetAllFunction::RespondOnUIThread, this));
332 DCHECK(rv);
333 }
334
RespondOnUIThread()335 void CookiesGetAllFunction::RespondOnUIThread() {
336 DCHECK_CURRENTLY_ON(BrowserThread::UI);
337 SendResponse(true);
338 }
339
CookiesSetFunction()340 CookiesSetFunction::CookiesSetFunction() : success_(false) {
341 }
342
~CookiesSetFunction()343 CookiesSetFunction::~CookiesSetFunction() {
344 }
345
RunAsync()346 bool CookiesSetFunction::RunAsync() {
347 parsed_args_ = Set::Params::Create(*args_);
348 EXTENSION_FUNCTION_VALIDATE(parsed_args_.get());
349
350 // Read/validate input parameters.
351 if (!ParseUrl(this, parsed_args_->details.url, &url_, true))
352 return false;
353
354 std::string store_id =
355 parsed_args_->details.store_id.get() ? *parsed_args_->details.store_id
356 : std::string();
357 net::URLRequestContextGetter* store_context = NULL;
358 if (!ParseStoreContext(this, &store_id, &store_context))
359 return false;
360 store_browser_context_ = store_context;
361 if (!parsed_args_->details.store_id.get())
362 parsed_args_->details.store_id.reset(new std::string(store_id));
363
364 bool rv = BrowserThread::PostTask(
365 BrowserThread::IO, FROM_HERE,
366 base::Bind(&CookiesSetFunction::SetCookieOnIOThread, this));
367 DCHECK(rv);
368
369 // Will finish asynchronously.
370 return true;
371 }
372
SetCookieOnIOThread()373 void CookiesSetFunction::SetCookieOnIOThread() {
374 DCHECK_CURRENTLY_ON(BrowserThread::IO);
375 net::CookieMonster* cookie_monster =
376 store_browser_context_->GetURLRequestContext()
377 ->cookie_store()
378 ->GetCookieMonster();
379
380 base::Time expiration_time;
381 if (parsed_args_->details.expiration_date.get()) {
382 // Time::FromDoubleT converts double time 0 to empty Time object. So we need
383 // to do special handling here.
384 expiration_time = (*parsed_args_->details.expiration_date == 0) ?
385 base::Time::UnixEpoch() :
386 base::Time::FromDoubleT(*parsed_args_->details.expiration_date);
387 }
388
389 cookie_monster->SetCookieWithDetailsAsync(
390 url_,
391 parsed_args_->details.name.get() ? *parsed_args_->details.name
392 : std::string(),
393 parsed_args_->details.value.get() ? *parsed_args_->details.value
394 : std::string(),
395 parsed_args_->details.domain.get() ? *parsed_args_->details.domain
396 : std::string(),
397 parsed_args_->details.path.get() ? *parsed_args_->details.path
398 : std::string(),
399 expiration_time,
400 parsed_args_->details.secure.get() ? *parsed_args_->details.secure.get()
401 : false,
402 parsed_args_->details.http_only.get() ? *parsed_args_->details.http_only
403 : false,
404 net::COOKIE_PRIORITY_DEFAULT,
405 base::Bind(&CookiesSetFunction::PullCookie, this));
406 }
407
PullCookie(bool set_cookie_result)408 void CookiesSetFunction::PullCookie(bool set_cookie_result) {
409 // Pull the newly set cookie.
410 net::CookieMonster* cookie_monster =
411 store_browser_context_->GetURLRequestContext()
412 ->cookie_store()
413 ->GetCookieMonster();
414 success_ = set_cookie_result;
415 cookies_helpers::GetCookieListFromStore(
416 cookie_monster, url_,
417 base::Bind(&CookiesSetFunction::PullCookieCallback, this));
418 }
419
PullCookieCallback(const net::CookieList & cookie_list)420 void CookiesSetFunction::PullCookieCallback(
421 const net::CookieList& cookie_list) {
422 net::CookieList::const_iterator it;
423 for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
424 // Return the first matching cookie. Relies on the fact that the
425 // CookieMonster returns them in canonical order (longest path, then
426 // earliest creation time).
427 std::string name =
428 parsed_args_->details.name.get() ? *parsed_args_->details.name
429 : std::string();
430 if (it->Name() == name) {
431 scoped_ptr<Cookie> cookie(
432 cookies_helpers::CreateCookie(*it, *parsed_args_->details.store_id));
433 results_ = Set::Results::Create(*cookie);
434 break;
435 }
436 }
437
438 bool rv = BrowserThread::PostTask(
439 BrowserThread::UI, FROM_HERE,
440 base::Bind(&CookiesSetFunction::RespondOnUIThread, this));
441 DCHECK(rv);
442 }
443
RespondOnUIThread()444 void CookiesSetFunction::RespondOnUIThread() {
445 DCHECK_CURRENTLY_ON(BrowserThread::UI);
446 if (!success_) {
447 std::string name =
448 parsed_args_->details.name.get() ? *parsed_args_->details.name
449 : std::string();
450 error_ = ErrorUtils::FormatErrorMessage(keys::kCookieSetFailedError, name);
451 }
452 SendResponse(success_);
453 }
454
CookiesRemoveFunction()455 CookiesRemoveFunction::CookiesRemoveFunction() {
456 }
457
~CookiesRemoveFunction()458 CookiesRemoveFunction::~CookiesRemoveFunction() {
459 }
460
RunAsync()461 bool CookiesRemoveFunction::RunAsync() {
462 parsed_args_ = Remove::Params::Create(*args_);
463 EXTENSION_FUNCTION_VALIDATE(parsed_args_.get());
464
465 // Read/validate input parameters.
466 if (!ParseUrl(this, parsed_args_->details.url, &url_, true))
467 return false;
468
469 std::string store_id =
470 parsed_args_->details.store_id.get() ? *parsed_args_->details.store_id
471 : std::string();
472 net::URLRequestContextGetter* store_context = NULL;
473 if (!ParseStoreContext(this, &store_id, &store_context))
474 return false;
475 store_browser_context_ = store_context;
476 if (!parsed_args_->details.store_id.get())
477 parsed_args_->details.store_id.reset(new std::string(store_id));
478
479 // Pass the work off to the IO thread.
480 bool rv = BrowserThread::PostTask(
481 BrowserThread::IO, FROM_HERE,
482 base::Bind(&CookiesRemoveFunction::RemoveCookieOnIOThread, this));
483 DCHECK(rv);
484
485 // Will return asynchronously.
486 return true;
487 }
488
RemoveCookieOnIOThread()489 void CookiesRemoveFunction::RemoveCookieOnIOThread() {
490 DCHECK_CURRENTLY_ON(BrowserThread::IO);
491
492 // Remove the cookie
493 net::CookieStore* cookie_store =
494 store_browser_context_->GetURLRequestContext()->cookie_store();
495 cookie_store->DeleteCookieAsync(
496 url_, parsed_args_->details.name,
497 base::Bind(&CookiesRemoveFunction::RemoveCookieCallback, this));
498 }
499
RemoveCookieCallback()500 void CookiesRemoveFunction::RemoveCookieCallback() {
501 // Build the callback result
502 Remove::Results::Details details;
503 details.name = parsed_args_->details.name;
504 details.url = url_.spec();
505 details.store_id = *parsed_args_->details.store_id;
506 results_ = Remove::Results::Create(details);
507
508 // Return to UI thread
509 bool rv = BrowserThread::PostTask(
510 BrowserThread::UI, FROM_HERE,
511 base::Bind(&CookiesRemoveFunction::RespondOnUIThread, this));
512 DCHECK(rv);
513 }
514
RespondOnUIThread()515 void CookiesRemoveFunction::RespondOnUIThread() {
516 DCHECK_CURRENTLY_ON(BrowserThread::UI);
517 SendResponse(true);
518 }
519
RunSync()520 bool CookiesGetAllCookieStoresFunction::RunSync() {
521 Profile* original_profile = GetProfile();
522 DCHECK(original_profile);
523 scoped_ptr<base::ListValue> original_tab_ids(new base::ListValue());
524 Profile* incognito_profile = NULL;
525 scoped_ptr<base::ListValue> incognito_tab_ids;
526 if (include_incognito() && GetProfile()->HasOffTheRecordProfile()) {
527 incognito_profile = GetProfile()->GetOffTheRecordProfile();
528 if (incognito_profile)
529 incognito_tab_ids.reset(new base::ListValue());
530 }
531 DCHECK(original_profile != incognito_profile);
532
533 // Iterate through all browser instances, and for each browser,
534 // add its tab IDs to either the regular or incognito tab ID list depending
535 // whether the browser is regular or incognito.
536 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
537 Browser* browser = *it;
538 if (browser->profile() == original_profile) {
539 cookies_helpers::AppendToTabIdList(browser, original_tab_ids.get());
540 } else if (incognito_tab_ids.get() &&
541 browser->profile() == incognito_profile) {
542 cookies_helpers::AppendToTabIdList(browser, incognito_tab_ids.get());
543 }
544 }
545 // Return a list of all cookie stores with at least one open tab.
546 std::vector<linked_ptr<CookieStore> > cookie_stores;
547 if (original_tab_ids->GetSize() > 0) {
548 cookie_stores.push_back(make_linked_ptr(
549 cookies_helpers::CreateCookieStore(
550 original_profile, original_tab_ids.release()).release()));
551 }
552 if (incognito_tab_ids.get() && incognito_tab_ids->GetSize() > 0 &&
553 incognito_profile) {
554 cookie_stores.push_back(make_linked_ptr(
555 cookies_helpers::CreateCookieStore(
556 incognito_profile, incognito_tab_ids.release()).release()));
557 }
558 results_ = GetAllCookieStores::Results::Create(cookie_stores);
559 return true;
560 }
561
CookiesAPI(content::BrowserContext * context)562 CookiesAPI::CookiesAPI(content::BrowserContext* context)
563 : browser_context_(context) {
564 EventRouter::Get(browser_context_)
565 ->RegisterObserver(this, cookies::OnChanged::kEventName);
566 }
567
~CookiesAPI()568 CookiesAPI::~CookiesAPI() {
569 }
570
Shutdown()571 void CookiesAPI::Shutdown() {
572 EventRouter::Get(browser_context_)->UnregisterObserver(this);
573 }
574
575 static base::LazyInstance<BrowserContextKeyedAPIFactory<CookiesAPI> >
576 g_factory = LAZY_INSTANCE_INITIALIZER;
577
578 // static
GetFactoryInstance()579 BrowserContextKeyedAPIFactory<CookiesAPI>* CookiesAPI::GetFactoryInstance() {
580 return g_factory.Pointer();
581 }
582
OnListenerAdded(const extensions::EventListenerInfo & details)583 void CookiesAPI::OnListenerAdded(
584 const extensions::EventListenerInfo& details) {
585 cookies_event_router_.reset(new CookiesEventRouter(browser_context_));
586 EventRouter::Get(browser_context_)->UnregisterObserver(this);
587 }
588
589 } // namespace extensions
590