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