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