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