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
26namespace keys = extension_cookies_api_constants;
27
28// static
29ExtensionCookiesEventRouter* ExtensionCookiesEventRouter::GetInstance() {
30  return Singleton<ExtensionCookiesEventRouter>::get();
31}
32
33void ExtensionCookiesEventRouter::Init() {
34  if (registrar_.IsEmpty()) {
35    registrar_.Add(this,
36                   NotificationType::COOKIE_CHANGED,
37                   NotificationService::AllSources());
38  }
39}
40
41void 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
56void 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
104void 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
114bool 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
136bool 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
176GetCookieFunction::GetCookieFunction() {}
177
178GetCookieFunction::~GetCookieFunction() {}
179
180bool 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
209void 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
237void GetCookieFunction::RespondOnUIThread() {
238  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
239  SendResponse(true);
240}
241
242GetAllCookiesFunction::GetAllCookiesFunction() : details_(NULL) {}
243
244GetAllCookiesFunction::~GetAllCookiesFunction() {}
245
246bool 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
270void 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
291void GetAllCookiesFunction::RespondOnUIThread() {
292  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
293  SendResponse(true);
294}
295
296SetCookieFunction::SetCookieFunction()
297    : secure_(false),
298      http_only_(false),
299      success_(false) {
300}
301
302SetCookieFunction::~SetCookieFunction() {
303}
304
305bool 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
367void 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
397void 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
406RemoveCookieFunction::RemoveCookieFunction() : success_(false) {
407}
408
409RemoveCookieFunction::~RemoveCookieFunction() {
410}
411
412bool 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
441void 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
463void RemoveCookieFunction::RespondOnUIThread() {
464  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
465  SendResponse(true);
466}
467
468bool 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
512void GetAllCookieStoresFunction::Run() {
513  SendResponse(RunImpl());
514}
515