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
GetInstance()29 ExtensionCookiesEventRouter* ExtensionCookiesEventRouter::GetInstance() {
30 return Singleton<ExtensionCookiesEventRouter>::get();
31 }
32
Init()33 void ExtensionCookiesEventRouter::Init() {
34 if (registrar_.IsEmpty()) {
35 registrar_.Add(this,
36 NotificationType::COOKIE_CHANGED,
37 NotificationService::AllSources());
38 }
39 }
40
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
CookieChanged(Profile * profile,ChromeCookieDetails * details)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
DispatchEvent(Profile * profile,const char * event_name,const std::string & json_args,GURL & cookie_domain)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
ParseUrl(const DictionaryValue * details,GURL * url,bool check_host_permissions)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
ParseStoreContext(const DictionaryValue * details,net::URLRequestContextGetter ** context,std::string * store_id)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
GetCookieFunction()176 GetCookieFunction::GetCookieFunction() {}
177
~GetCookieFunction()178 GetCookieFunction::~GetCookieFunction() {}
179
RunImpl()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
GetCookieOnIOThread()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
RespondOnUIThread()237 void GetCookieFunction::RespondOnUIThread() {
238 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
239 SendResponse(true);
240 }
241
GetAllCookiesFunction()242 GetAllCookiesFunction::GetAllCookiesFunction() : details_(NULL) {}
243
~GetAllCookiesFunction()244 GetAllCookiesFunction::~GetAllCookiesFunction() {}
245
RunImpl()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
GetAllCookiesOnIOThread()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
RespondOnUIThread()291 void GetAllCookiesFunction::RespondOnUIThread() {
292 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
293 SendResponse(true);
294 }
295
SetCookieFunction()296 SetCookieFunction::SetCookieFunction()
297 : secure_(false),
298 http_only_(false),
299 success_(false) {
300 }
301
~SetCookieFunction()302 SetCookieFunction::~SetCookieFunction() {
303 }
304
RunImpl()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
SetCookieOnIOThread()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
RespondOnUIThread()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
RemoveCookieFunction()406 RemoveCookieFunction::RemoveCookieFunction() : success_(false) {
407 }
408
~RemoveCookieFunction()409 RemoveCookieFunction::~RemoveCookieFunction() {
410 }
411
RunImpl()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
RemoveCookieOnIOThread()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
RespondOnUIThread()463 void RemoveCookieFunction::RespondOnUIThread() {
464 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
465 SendResponse(true);
466 }
467
RunImpl()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
Run()512 void GetAllCookieStoresFunction::Run() {
513 SendResponse(RunImpl());
514 }
515