extension_cookies_api.cc revision dc0f95d653279beabeb9817299e2902918ba123e
1// Copyright (c) 2011 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/extension_cookies_api.h" 8 9#include "base/json/json_writer.h" 10#include "base/task.h" 11#include "base/values.h" 12#include "chrome/browser/browser_list.h" 13#include "chrome/browser/extensions/extension_cookies_api_constants.h" 14#include "chrome/browser/extensions/extension_cookies_helpers.h" 15#include "chrome/browser/extensions/extension_event_router.h" 16#include "chrome/browser/profiles/profile.h" 17#include "chrome/common/extensions/extension.h" 18#include "chrome/common/extensions/extension_error_utils.h" 19#include "chrome/common/net/url_request_context_getter.h" 20#include "chrome/common/notification_type.h" 21#include "chrome/common/notification_service.h" 22#include "content/browser/browser_thread.h" 23#include "net/base/cookie_monster.h" 24 25namespace keys = extension_cookies_api_constants; 26 27// static 28ExtensionCookiesEventRouter* ExtensionCookiesEventRouter::GetInstance() { 29 return Singleton<ExtensionCookiesEventRouter>::get(); 30} 31 32void ExtensionCookiesEventRouter::Init() { 33 if (registrar_.IsEmpty()) { 34 registrar_.Add(this, 35 NotificationType::COOKIE_CHANGED, 36 NotificationService::AllSources()); 37 } 38} 39 40void ExtensionCookiesEventRouter::Observe(NotificationType type, 41 const NotificationSource& source, 42 const NotificationDetails& details) { 43 switch (type.value) { 44 case NotificationType::COOKIE_CHANGED: 45 CookieChanged( 46 Source<Profile>(source).ptr(), 47 Details<ChromeCookieDetails>(details).ptr()); 48 break; 49 50 default: 51 NOTREACHED(); 52 } 53} 54 55void ExtensionCookiesEventRouter::CookieChanged( 56 Profile* profile, 57 ChromeCookieDetails* details) { 58 ListValue args; 59 DictionaryValue* dict = new DictionaryValue(); 60 dict->SetBoolean(keys::kRemovedKey, details->removed); 61 dict->Set( 62 keys::kCookieKey, 63 extension_cookies_helpers::CreateCookieValue(*details->cookie, 64 extension_cookies_helpers::GetStoreIdFromProfile(profile))); 65 args.Append(dict); 66 67 std::string json_args; 68 base::JSONWriter::Write(&args, false, &json_args); 69 GURL cookie_domain = 70 extension_cookies_helpers::GetURLFromCanonicalCookie(*details->cookie); 71 DispatchEvent(profile, keys::kOnChanged, json_args, cookie_domain); 72} 73 74void ExtensionCookiesEventRouter::DispatchEvent(Profile* profile, 75 const char* event_name, 76 const std::string& json_args, 77 GURL& cookie_domain) { 78 if (profile && profile->GetExtensionEventRouter()) { 79 profile->GetExtensionEventRouter()->DispatchEventToRenderers( 80 event_name, json_args, profile, cookie_domain); 81 } 82} 83 84bool CookiesFunction::ParseUrl(const DictionaryValue* details, GURL* url, 85 bool check_host_permissions) { 86 DCHECK(details && url); 87 std::string url_string; 88 // Get the URL string or return false. 89 EXTENSION_FUNCTION_VALIDATE(details->GetString(keys::kUrlKey, &url_string)); 90 *url = GURL(url_string); 91 if (!url->is_valid()) { 92 error_ = ExtensionErrorUtils::FormatErrorMessage( 93 keys::kInvalidUrlError, url_string); 94 return false; 95 } 96 // Check against host permissions if needed. 97 if (check_host_permissions && 98 !GetExtension()->HasHostPermission(*url)) { 99 error_ = ExtensionErrorUtils::FormatErrorMessage( 100 keys::kNoHostPermissionsError, url->spec()); 101 return false; 102 } 103 return true; 104} 105 106bool CookiesFunction::ParseStoreContext(const DictionaryValue* details, 107 URLRequestContextGetter** context, 108 std::string* store_id) { 109 DCHECK(details && (context || store_id)); 110 Profile* store_profile = NULL; 111 if (details->HasKey(keys::kStoreIdKey)) { 112 // The store ID was explicitly specified in the details dictionary. 113 // Retrieve its corresponding cookie store. 114 std::string store_id_value; 115 // Get the store ID string or return false. 116 EXTENSION_FUNCTION_VALIDATE( 117 details->GetString(keys::kStoreIdKey, &store_id_value)); 118 store_profile = extension_cookies_helpers::ChooseProfileFromStoreId( 119 store_id_value, profile(), include_incognito()); 120 if (!store_profile) { 121 error_ = ExtensionErrorUtils::FormatErrorMessage( 122 keys::kInvalidStoreIdError, store_id_value); 123 return false; 124 } 125 } else { 126 // The store ID was not specified; use the current execution context's 127 // cookie store by default. 128 // GetCurrentBrowser() already takes into account incognito settings. 129 Browser* current_browser = GetCurrentBrowser(); 130 if (!current_browser) { 131 error_ = keys::kNoCookieStoreFoundError; 132 return false; 133 } 134 store_profile = current_browser->profile(); 135 } 136 DCHECK(store_profile); 137 138 if (context) 139 *context = store_profile->GetRequestContext(); 140 if (store_id) 141 *store_id = extension_cookies_helpers::GetStoreIdFromProfile(store_profile); 142 143 return true; 144} 145 146GetCookieFunction::GetCookieFunction() {} 147 148GetCookieFunction::~GetCookieFunction() {} 149 150bool GetCookieFunction::RunImpl() { 151 // Return false if the arguments are malformed. 152 DictionaryValue* details; 153 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &details)); 154 DCHECK(details); 155 156 // Read/validate input parameters. 157 if (!ParseUrl(details, &url_, true)) 158 return false; 159 160 // Get the cookie name string or return false. 161 EXTENSION_FUNCTION_VALIDATE(details->GetString(keys::kNameKey, &name_)); 162 163 URLRequestContextGetter* store_context = NULL; 164 if (!ParseStoreContext(details, &store_context, &store_id_)) 165 return false; 166 167 DCHECK(store_context && !store_id_.empty()); 168 store_context_ = store_context; 169 170 bool rv = BrowserThread::PostTask( 171 BrowserThread::IO, FROM_HERE, 172 NewRunnableMethod(this, &GetCookieFunction::GetCookieOnIOThread)); 173 DCHECK(rv); 174 175 // Will finish asynchronously. 176 return true; 177} 178 179void GetCookieFunction::GetCookieOnIOThread() { 180 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 181 net::CookieStore* cookie_store = store_context_->GetCookieStore(); 182 net::CookieList cookie_list = 183 extension_cookies_helpers::GetCookieListFromStore(cookie_store, url_); 184 net::CookieList::iterator it; 185 for (it = cookie_list.begin(); it != cookie_list.end(); ++it) { 186 // Return the first matching cookie. Relies on the fact that the 187 // CookieMonster returns them in canonical order (longest path, then 188 // earliest creation time). 189 if (it->Name() == name_) { 190 result_.reset( 191 extension_cookies_helpers::CreateCookieValue(*it, store_id_)); 192 break; 193 } 194 } 195 196 // The cookie doesn't exist; return null. 197 if (it == cookie_list.end()) 198 result_.reset(Value::CreateNullValue()); 199 200 bool rv = BrowserThread::PostTask( 201 BrowserThread::UI, FROM_HERE, 202 NewRunnableMethod(this, &GetCookieFunction::RespondOnUIThread)); 203 DCHECK(rv); 204} 205 206void GetCookieFunction::RespondOnUIThread() { 207 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 208 SendResponse(true); 209} 210 211GetAllCookiesFunction::GetAllCookiesFunction() : details_(NULL) {} 212 213GetAllCookiesFunction::~GetAllCookiesFunction() {} 214 215bool GetAllCookiesFunction::RunImpl() { 216 // Return false if the arguments are malformed. 217 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &details_)); 218 DCHECK(details_); 219 220 // Read/validate input parameters. 221 if (details_->HasKey(keys::kUrlKey) && !ParseUrl(details_, &url_, false)) 222 return false; 223 224 URLRequestContextGetter* store_context = NULL; 225 if (!ParseStoreContext(details_, &store_context, &store_id_)) 226 return false; 227 DCHECK(store_context); 228 store_context_ = store_context; 229 230 bool rv = BrowserThread::PostTask( 231 BrowserThread::IO, FROM_HERE, 232 NewRunnableMethod(this, &GetAllCookiesFunction::GetAllCookiesOnIOThread)); 233 DCHECK(rv); 234 235 // Will finish asynchronously. 236 return true; 237} 238 239void GetAllCookiesFunction::GetAllCookiesOnIOThread() { 240 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 241 net::CookieStore* cookie_store = store_context_->GetCookieStore(); 242 net::CookieList cookie_list = 243 extension_cookies_helpers::GetCookieListFromStore(cookie_store, url_); 244 245 const Extension* extension = GetExtension(); 246 if (extension) { 247 ListValue* matching_list = new ListValue(); 248 extension_cookies_helpers::AppendMatchingCookiesToList( 249 cookie_list, store_id_, url_, details_, 250 GetExtension(), matching_list); 251 result_.reset(matching_list); 252 } 253 bool rv = BrowserThread::PostTask( 254 BrowserThread::UI, FROM_HERE, 255 NewRunnableMethod(this, &GetAllCookiesFunction::RespondOnUIThread)); 256 DCHECK(rv); 257} 258 259void GetAllCookiesFunction::RespondOnUIThread() { 260 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 261 SendResponse(true); 262} 263 264SetCookieFunction::SetCookieFunction() 265 : secure_(false), 266 http_only_(false), 267 success_(false) { 268} 269 270SetCookieFunction::~SetCookieFunction() { 271} 272 273bool SetCookieFunction::RunImpl() { 274 // Return false if the arguments are malformed. 275 DictionaryValue* details; 276 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &details)); 277 DCHECK(details); 278 279 // Read/validate input parameters. 280 if (!ParseUrl(details, &url_, true)) 281 return false; 282 // The macros below return false if argument types are not as expected. 283 if (details->HasKey(keys::kNameKey)) 284 EXTENSION_FUNCTION_VALIDATE(details->GetString(keys::kNameKey, &name_)); 285 if (details->HasKey(keys::kValueKey)) 286 EXTENSION_FUNCTION_VALIDATE(details->GetString(keys::kValueKey, &value_)); 287 if (details->HasKey(keys::kDomainKey)) 288 EXTENSION_FUNCTION_VALIDATE(details->GetString(keys::kDomainKey, &domain_)); 289 if (details->HasKey(keys::kPathKey)) 290 EXTENSION_FUNCTION_VALIDATE(details->GetString(keys::kPathKey, &path_)); 291 292 if (details->HasKey(keys::kSecureKey)) { 293 EXTENSION_FUNCTION_VALIDATE( 294 details->GetBoolean(keys::kSecureKey, &secure_)); 295 } 296 if (details->HasKey(keys::kHttpOnlyKey)) { 297 EXTENSION_FUNCTION_VALIDATE( 298 details->GetBoolean(keys::kHttpOnlyKey, &http_only_)); 299 } 300 if (details->HasKey(keys::kExpirationDateKey)) { 301 Value* expiration_date_value; 302 EXTENSION_FUNCTION_VALIDATE(details->Get(keys::kExpirationDateKey, 303 &expiration_date_value)); 304 double expiration_date; 305 if (expiration_date_value->IsType(Value::TYPE_INTEGER)) { 306 int expiration_date_int; 307 EXTENSION_FUNCTION_VALIDATE( 308 expiration_date_value->GetAsInteger(&expiration_date_int)); 309 expiration_date = static_cast<double>(expiration_date_int); 310 } else { 311 EXTENSION_FUNCTION_VALIDATE( 312 expiration_date_value->GetAsDouble(&expiration_date)); 313 } 314 // Time::FromDoubleT converts double time 0 to empty Time object. So we need 315 // to do special handling here. 316 expiration_time_ = (expiration_date == 0) ? 317 base::Time::UnixEpoch() : base::Time::FromDoubleT(expiration_date); 318 } 319 320 URLRequestContextGetter* store_context = NULL; 321 if (!ParseStoreContext(details, &store_context, NULL)) 322 return false; 323 DCHECK(store_context); 324 store_context_ = store_context; 325 326 bool rv = BrowserThread::PostTask( 327 BrowserThread::IO, FROM_HERE, 328 NewRunnableMethod(this, &SetCookieFunction::SetCookieOnIOThread)); 329 DCHECK(rv); 330 331 // Will finish asynchronously. 332 return true; 333} 334 335void SetCookieFunction::SetCookieOnIOThread() { 336 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 337 net::CookieMonster* cookie_monster = 338 store_context_->GetCookieStore()->GetCookieMonster(); 339 success_ = cookie_monster->SetCookieWithDetails( 340 url_, name_, value_, domain_, path_, expiration_time_, 341 secure_, http_only_); 342 343 // Pull the newly set cookie. 344 net::CookieList cookie_list = 345 extension_cookies_helpers::GetCookieListFromStore(cookie_monster, url_); 346 net::CookieList::iterator it; 347 for (it = cookie_list.begin(); it != cookie_list.end(); ++it) { 348 // Return the first matching cookie. Relies on the fact that the 349 // CookieMonster returns them in canonical order (longest path, then 350 // earliest creation time). 351 if (it->Name() == name_) { 352 result_.reset( 353 extension_cookies_helpers::CreateCookieValue(*it, store_id_)); 354 break; 355 } 356 } 357 358 bool rv = BrowserThread::PostTask( 359 BrowserThread::UI, FROM_HERE, 360 NewRunnableMethod(this, &SetCookieFunction::RespondOnUIThread)); 361 DCHECK(rv); 362} 363 364void SetCookieFunction::RespondOnUIThread() { 365 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 366 if (!success_) { 367 error_ = ExtensionErrorUtils::FormatErrorMessage( 368 keys::kCookieSetFailedError, name_); 369 } 370 SendResponse(success_); 371} 372 373namespace { 374 375class RemoveCookieTask : public Task { 376 public: 377 RemoveCookieTask(const GURL& url, 378 const std::string& name, 379 const scoped_refptr<URLRequestContextGetter>& context_getter) 380 : url_(url), 381 name_(name), 382 context_getter_(context_getter) {} 383 384 virtual void Run() { 385 net::CookieStore* cookie_store = context_getter_->GetCookieStore(); 386 cookie_store->DeleteCookie(url_, name_); 387 } 388 389 private: 390 const GURL url_; 391 const std::string name_; 392 const scoped_refptr<URLRequestContextGetter> context_getter_; 393 394 DISALLOW_COPY_AND_ASSIGN(RemoveCookieTask); 395}; 396 397} // namespace 398 399bool RemoveCookieFunction::RunImpl() { 400 // Return false if the arguments are malformed. 401 DictionaryValue* details; 402 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &details)); 403 DCHECK(details); 404 405 // Read/validate input parameters. 406 GURL url; 407 if (!ParseUrl(details, &url, true)) 408 return false; 409 410 std::string name; 411 // Get the cookie name string or return false. 412 EXTENSION_FUNCTION_VALIDATE(details->GetString(keys::kNameKey, &name)); 413 414 URLRequestContextGetter* store_context = NULL; 415 std::string store_id; 416 if (!ParseStoreContext(details, &store_context, &store_id)) 417 return false; 418 DCHECK(store_context); 419 420 // We don't bother to synchronously wait for the result here, because 421 // CookieMonster is only ever accessed on the IO thread, so any other accesses 422 // should happen after this. 423 bool rv = BrowserThread::PostTask( 424 BrowserThread::IO, FROM_HERE, 425 new RemoveCookieTask(url, name, make_scoped_refptr(store_context))); 426 DCHECK(rv); 427 428 DictionaryValue* resultDictionary = new DictionaryValue(); 429 resultDictionary->SetString(keys::kNameKey, name); 430 resultDictionary->SetString(keys::kUrlKey, url.spec()); 431 resultDictionary->SetString(keys::kStoreIdKey, store_id); 432 result_.reset(resultDictionary); 433 434 return true; 435} 436 437void RemoveCookieFunction::Run() { 438 SendResponse(RunImpl()); 439} 440 441bool GetAllCookieStoresFunction::RunImpl() { 442 Profile* original_profile = profile(); 443 DCHECK(original_profile); 444 scoped_ptr<ListValue> original_tab_ids(new ListValue()); 445 Profile* incognito_profile = NULL; 446 scoped_ptr<ListValue> incognito_tab_ids; 447 if (include_incognito() && profile()->HasOffTheRecordProfile()) { 448 incognito_profile = profile()->GetOffTheRecordProfile(); 449 if (incognito_profile) 450 incognito_tab_ids.reset(new ListValue()); 451 } 452 DCHECK(original_profile != incognito_profile); 453 454 // Iterate through all browser instances, and for each browser, 455 // add its tab IDs to either the regular or incognito tab ID list depending 456 // whether the browser is regular or incognito. 457 for (BrowserList::const_iterator iter = BrowserList::begin(); 458 iter != BrowserList::end(); ++iter) { 459 Browser* browser = *iter; 460 if (browser->profile() == original_profile) { 461 extension_cookies_helpers::AppendToTabIdList(browser, 462 original_tab_ids.get()); 463 } else if (incognito_tab_ids.get() && 464 browser->profile() == incognito_profile) { 465 extension_cookies_helpers::AppendToTabIdList(browser, 466 incognito_tab_ids.get()); 467 } 468 } 469 // Return a list of all cookie stores with at least one open tab. 470 ListValue* cookie_store_list = new ListValue(); 471 if (original_tab_ids->GetSize() > 0) { 472 cookie_store_list->Append( 473 extension_cookies_helpers::CreateCookieStoreValue( 474 original_profile, original_tab_ids.release())); 475 } 476 if (incognito_tab_ids.get() && incognito_tab_ids->GetSize() > 0) { 477 cookie_store_list->Append( 478 extension_cookies_helpers::CreateCookieStoreValue( 479 incognito_profile, incognito_tab_ids.release())); 480 } 481 result_.reset(cookie_store_list); 482 return true; 483} 484 485void GetAllCookieStoresFunction::Run() { 486 SendResponse(RunImpl()); 487} 488