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->extension()->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 if (extension()) {
321 std::vector<linked_ptr<Cookie> > match_vector;
322 cookies_helpers::AppendMatchingCookiesToVector(
323 cookie_list, url_, &parsed_args_->details, extension(), &match_vector);
324
325 results_ = GetAll::Results::Create(match_vector);
326 }
327 bool rv = BrowserThread::PostTask(
328 BrowserThread::UI, FROM_HERE,
329 base::Bind(&CookiesGetAllFunction::RespondOnUIThread, this));
330 DCHECK(rv);
331 }
332
RespondOnUIThread()333 void CookiesGetAllFunction::RespondOnUIThread() {
334 DCHECK_CURRENTLY_ON(BrowserThread::UI);
335 SendResponse(true);
336 }
337
CookiesSetFunction()338 CookiesSetFunction::CookiesSetFunction() : success_(false) {
339 }
340
~CookiesSetFunction()341 CookiesSetFunction::~CookiesSetFunction() {
342 }
343
RunAsync()344 bool CookiesSetFunction::RunAsync() {
345 parsed_args_ = Set::Params::Create(*args_);
346 EXTENSION_FUNCTION_VALIDATE(parsed_args_.get());
347
348 // Read/validate input parameters.
349 if (!ParseUrl(this, parsed_args_->details.url, &url_, true))
350 return false;
351
352 std::string store_id =
353 parsed_args_->details.store_id.get() ? *parsed_args_->details.store_id
354 : std::string();
355 net::URLRequestContextGetter* store_context = NULL;
356 if (!ParseStoreContext(this, &store_id, &store_context))
357 return false;
358 store_browser_context_ = store_context;
359 if (!parsed_args_->details.store_id.get())
360 parsed_args_->details.store_id.reset(new std::string(store_id));
361
362 bool rv = BrowserThread::PostTask(
363 BrowserThread::IO, FROM_HERE,
364 base::Bind(&CookiesSetFunction::SetCookieOnIOThread, this));
365 DCHECK(rv);
366
367 // Will finish asynchronously.
368 return true;
369 }
370
SetCookieOnIOThread()371 void CookiesSetFunction::SetCookieOnIOThread() {
372 DCHECK_CURRENTLY_ON(BrowserThread::IO);
373 net::CookieMonster* cookie_monster =
374 store_browser_context_->GetURLRequestContext()
375 ->cookie_store()
376 ->GetCookieMonster();
377
378 base::Time expiration_time;
379 if (parsed_args_->details.expiration_date.get()) {
380 // Time::FromDoubleT converts double time 0 to empty Time object. So we need
381 // to do special handling here.
382 expiration_time = (*parsed_args_->details.expiration_date == 0) ?
383 base::Time::UnixEpoch() :
384 base::Time::FromDoubleT(*parsed_args_->details.expiration_date);
385 }
386
387 cookie_monster->SetCookieWithDetailsAsync(
388 url_,
389 parsed_args_->details.name.get() ? *parsed_args_->details.name
390 : std::string(),
391 parsed_args_->details.value.get() ? *parsed_args_->details.value
392 : std::string(),
393 parsed_args_->details.domain.get() ? *parsed_args_->details.domain
394 : std::string(),
395 parsed_args_->details.path.get() ? *parsed_args_->details.path
396 : std::string(),
397 expiration_time,
398 parsed_args_->details.secure.get() ? *parsed_args_->details.secure.get()
399 : false,
400 parsed_args_->details.http_only.get() ? *parsed_args_->details.http_only
401 : false,
402 net::COOKIE_PRIORITY_DEFAULT,
403 base::Bind(&CookiesSetFunction::PullCookie, this));
404 }
405
PullCookie(bool set_cookie_result)406 void CookiesSetFunction::PullCookie(bool set_cookie_result) {
407 // Pull the newly set cookie.
408 net::CookieMonster* cookie_monster =
409 store_browser_context_->GetURLRequestContext()
410 ->cookie_store()
411 ->GetCookieMonster();
412 success_ = set_cookie_result;
413 cookies_helpers::GetCookieListFromStore(
414 cookie_monster, url_,
415 base::Bind(&CookiesSetFunction::PullCookieCallback, this));
416 }
417
PullCookieCallback(const net::CookieList & cookie_list)418 void CookiesSetFunction::PullCookieCallback(
419 const net::CookieList& cookie_list) {
420 net::CookieList::const_iterator it;
421 for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
422 // Return the first matching cookie. Relies on the fact that the
423 // CookieMonster returns them in canonical order (longest path, then
424 // earliest creation time).
425 std::string name =
426 parsed_args_->details.name.get() ? *parsed_args_->details.name
427 : std::string();
428 if (it->Name() == name) {
429 scoped_ptr<Cookie> cookie(
430 cookies_helpers::CreateCookie(*it, *parsed_args_->details.store_id));
431 results_ = Set::Results::Create(*cookie);
432 break;
433 }
434 }
435
436 bool rv = BrowserThread::PostTask(
437 BrowserThread::UI, FROM_HERE,
438 base::Bind(&CookiesSetFunction::RespondOnUIThread, this));
439 DCHECK(rv);
440 }
441
RespondOnUIThread()442 void CookiesSetFunction::RespondOnUIThread() {
443 DCHECK_CURRENTLY_ON(BrowserThread::UI);
444 if (!success_) {
445 std::string name =
446 parsed_args_->details.name.get() ? *parsed_args_->details.name
447 : std::string();
448 error_ = ErrorUtils::FormatErrorMessage(keys::kCookieSetFailedError, name);
449 }
450 SendResponse(success_);
451 }
452
CookiesRemoveFunction()453 CookiesRemoveFunction::CookiesRemoveFunction() {
454 }
455
~CookiesRemoveFunction()456 CookiesRemoveFunction::~CookiesRemoveFunction() {
457 }
458
RunAsync()459 bool CookiesRemoveFunction::RunAsync() {
460 parsed_args_ = Remove::Params::Create(*args_);
461 EXTENSION_FUNCTION_VALIDATE(parsed_args_.get());
462
463 // Read/validate input parameters.
464 if (!ParseUrl(this, parsed_args_->details.url, &url_, true))
465 return false;
466
467 std::string store_id =
468 parsed_args_->details.store_id.get() ? *parsed_args_->details.store_id
469 : std::string();
470 net::URLRequestContextGetter* store_context = NULL;
471 if (!ParseStoreContext(this, &store_id, &store_context))
472 return false;
473 store_browser_context_ = store_context;
474 if (!parsed_args_->details.store_id.get())
475 parsed_args_->details.store_id.reset(new std::string(store_id));
476
477 // Pass the work off to the IO thread.
478 bool rv = BrowserThread::PostTask(
479 BrowserThread::IO, FROM_HERE,
480 base::Bind(&CookiesRemoveFunction::RemoveCookieOnIOThread, this));
481 DCHECK(rv);
482
483 // Will return asynchronously.
484 return true;
485 }
486
RemoveCookieOnIOThread()487 void CookiesRemoveFunction::RemoveCookieOnIOThread() {
488 DCHECK_CURRENTLY_ON(BrowserThread::IO);
489
490 // Remove the cookie
491 net::CookieStore* cookie_store =
492 store_browser_context_->GetURLRequestContext()->cookie_store();
493 cookie_store->DeleteCookieAsync(
494 url_, parsed_args_->details.name,
495 base::Bind(&CookiesRemoveFunction::RemoveCookieCallback, this));
496 }
497
RemoveCookieCallback()498 void CookiesRemoveFunction::RemoveCookieCallback() {
499 // Build the callback result
500 Remove::Results::Details details;
501 details.name = parsed_args_->details.name;
502 details.url = url_.spec();
503 details.store_id = *parsed_args_->details.store_id;
504 results_ = Remove::Results::Create(details);
505
506 // Return to UI thread
507 bool rv = BrowserThread::PostTask(
508 BrowserThread::UI, FROM_HERE,
509 base::Bind(&CookiesRemoveFunction::RespondOnUIThread, this));
510 DCHECK(rv);
511 }
512
RespondOnUIThread()513 void CookiesRemoveFunction::RespondOnUIThread() {
514 DCHECK_CURRENTLY_ON(BrowserThread::UI);
515 SendResponse(true);
516 }
517
RunSync()518 bool CookiesGetAllCookieStoresFunction::RunSync() {
519 Profile* original_profile = GetProfile();
520 DCHECK(original_profile);
521 scoped_ptr<base::ListValue> original_tab_ids(new base::ListValue());
522 Profile* incognito_profile = NULL;
523 scoped_ptr<base::ListValue> incognito_tab_ids;
524 if (include_incognito() && GetProfile()->HasOffTheRecordProfile()) {
525 incognito_profile = GetProfile()->GetOffTheRecordProfile();
526 if (incognito_profile)
527 incognito_tab_ids.reset(new base::ListValue());
528 }
529 DCHECK(original_profile != incognito_profile);
530
531 // Iterate through all browser instances, and for each browser,
532 // add its tab IDs to either the regular or incognito tab ID list depending
533 // whether the browser is regular or incognito.
534 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
535 Browser* browser = *it;
536 if (browser->profile() == original_profile) {
537 cookies_helpers::AppendToTabIdList(browser, original_tab_ids.get());
538 } else if (incognito_tab_ids.get() &&
539 browser->profile() == incognito_profile) {
540 cookies_helpers::AppendToTabIdList(browser, incognito_tab_ids.get());
541 }
542 }
543 // Return a list of all cookie stores with at least one open tab.
544 std::vector<linked_ptr<CookieStore> > cookie_stores;
545 if (original_tab_ids->GetSize() > 0) {
546 cookie_stores.push_back(make_linked_ptr(
547 cookies_helpers::CreateCookieStore(
548 original_profile, original_tab_ids.release()).release()));
549 }
550 if (incognito_tab_ids.get() && incognito_tab_ids->GetSize() > 0 &&
551 incognito_profile) {
552 cookie_stores.push_back(make_linked_ptr(
553 cookies_helpers::CreateCookieStore(
554 incognito_profile, incognito_tab_ids.release()).release()));
555 }
556 results_ = GetAllCookieStores::Results::Create(cookie_stores);
557 return true;
558 }
559
CookiesAPI(content::BrowserContext * context)560 CookiesAPI::CookiesAPI(content::BrowserContext* context)
561 : browser_context_(context) {
562 EventRouter::Get(browser_context_)
563 ->RegisterObserver(this, cookies::OnChanged::kEventName);
564 }
565
~CookiesAPI()566 CookiesAPI::~CookiesAPI() {
567 }
568
Shutdown()569 void CookiesAPI::Shutdown() {
570 EventRouter::Get(browser_context_)->UnregisterObserver(this);
571 }
572
573 static base::LazyInstance<BrowserContextKeyedAPIFactory<CookiesAPI> >
574 g_factory = LAZY_INSTANCE_INITIALIZER;
575
576 // static
GetFactoryInstance()577 BrowserContextKeyedAPIFactory<CookiesAPI>* CookiesAPI::GetFactoryInstance() {
578 return g_factory.Pointer();
579 }
580
OnListenerAdded(const extensions::EventListenerInfo & details)581 void CookiesAPI::OnListenerAdded(
582 const extensions::EventListenerInfo& details) {
583 cookies_event_router_.reset(new CookiesEventRouter(browser_context_));
584 EventRouter::Get(browser_context_)->UnregisterObserver(this);
585 }
586
587 } // namespace extensions
588