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