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