cookies_api.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
1// Copyright (c) 2012 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/api/cookies/cookies_api.h"
8
9#include <vector>
10
11#include "base/bind.h"
12#include "base/json/json_writer.h"
13#include "base/lazy_instance.h"
14#include "base/memory/linked_ptr.h"
15#include "base/memory/scoped_ptr.h"
16#include "base/time/time.h"
17#include "base/values.h"
18#include "chrome/browser/chrome_notification_types.h"
19#include "chrome/browser/extensions/api/cookies/cookies_api_constants.h"
20#include "chrome/browser/extensions/api/cookies/cookies_helpers.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/browser/ui/browser.h"
23#include "chrome/browser/ui/browser_iterator.h"
24#include "chrome/common/extensions/api/cookies.h"
25#include "content/public/browser/browser_thread.h"
26#include "content/public/browser/notification_service.h"
27#include "extensions/browser/event_router.h"
28#include "extensions/browser/extension_system.h"
29#include "extensions/common/error_utils.h"
30#include "extensions/common/extension.h"
31#include "extensions/common/permissions/permissions_data.h"
32#include "net/cookies/canonical_cookie.h"
33#include "net/cookies/cookie_constants.h"
34#include "net/cookies/cookie_monster.h"
35#include "net/url_request/url_request_context.h"
36#include "net/url_request/url_request_context_getter.h"
37
38using content::BrowserThread;
39using extensions::api::cookies::Cookie;
40using extensions::api::cookies::CookieStore;
41
42namespace Get = extensions::api::cookies::Get;
43namespace GetAll = extensions::api::cookies::GetAll;
44namespace GetAllCookieStores = extensions::api::cookies::GetAllCookieStores;
45namespace Remove = extensions::api::cookies::Remove;
46namespace Set = extensions::api::cookies::Set;
47
48namespace extensions {
49namespace cookies = api::cookies;
50namespace keys = cookies_api_constants;
51
52CookiesEventRouter::CookiesEventRouter(content::BrowserContext* context)
53    : profile_(Profile::FromBrowserContext(context)) {
54  CHECK(registrar_.IsEmpty());
55  registrar_.Add(this,
56                 chrome::NOTIFICATION_COOKIE_CHANGED,
57                 content::NotificationService::AllBrowserContextsAndSources());
58}
59
60CookiesEventRouter::~CookiesEventRouter() {
61}
62
63void CookiesEventRouter::Observe(
64    int type,
65    const content::NotificationSource& source,
66    const content::NotificationDetails& details) {
67  Profile* profile = content::Source<Profile>(source).ptr();
68  if (!profile_->IsSameProfile(profile))
69    return;
70
71  switch (type) {
72    case chrome::NOTIFICATION_COOKIE_CHANGED:
73      CookieChanged(
74          profile,
75          content::Details<ChromeCookieDetails>(details).ptr());
76      break;
77
78    default:
79      NOTREACHED();
80  }
81}
82
83void CookiesEventRouter::CookieChanged(
84    Profile* profile,
85    ChromeCookieDetails* details) {
86  scoped_ptr<base::ListValue> args(new base::ListValue());
87  base::DictionaryValue* dict = new base::DictionaryValue();
88  dict->SetBoolean(keys::kRemovedKey, details->removed);
89
90  scoped_ptr<Cookie> cookie(
91      cookies_helpers::CreateCookie(*details->cookie,
92          cookies_helpers::GetStoreIdFromProfile(profile)));
93  dict->Set(keys::kCookieKey, cookie->ToValue().release());
94
95  // Map the internal cause to an external string.
96  std::string cause;
97  switch (details->cause) {
98    case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPLICIT:
99      cause = keys::kExplicitChangeCause;
100      break;
101
102    case net::CookieMonster::Delegate::CHANGE_COOKIE_OVERWRITE:
103      cause = keys::kOverwriteChangeCause;
104      break;
105
106    case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPIRED:
107      cause = keys::kExpiredChangeCause;
108      break;
109
110    case net::CookieMonster::Delegate::CHANGE_COOKIE_EVICTED:
111      cause = keys::kEvictedChangeCause;
112      break;
113
114    case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPIRED_OVERWRITE:
115      cause = keys::kExpiredOverwriteChangeCause;
116      break;
117
118    default:
119      NOTREACHED();
120  }
121  dict->SetString(keys::kCauseKey, cause);
122
123  args->Append(dict);
124
125  GURL cookie_domain =
126      cookies_helpers::GetURLFromCanonicalCookie(*details->cookie);
127  DispatchEvent(profile,
128                cookies::OnChanged::kEventName,
129                args.Pass(),
130                cookie_domain);
131}
132
133void CookiesEventRouter::DispatchEvent(content::BrowserContext* context,
134                                       const std::string& event_name,
135                                       scoped_ptr<base::ListValue> event_args,
136                                       GURL& cookie_domain) {
137  EventRouter* router =
138      context ? extensions::ExtensionSystem::Get(context)->event_router()
139              : NULL;
140  if (!router)
141    return;
142  scoped_ptr<Event> event(new Event(event_name, event_args.Pass()));
143  event->restrict_to_browser_context = context;
144  event->event_url = cookie_domain;
145  router->BroadcastEvent(event.Pass());
146}
147
148bool CookiesFunction::ParseUrl(const std::string& url_string, GURL* url,
149                               bool check_host_permissions) {
150  *url = GURL(url_string);
151  if (!url->is_valid()) {
152    error_ = ErrorUtils::FormatErrorMessage(
153        keys::kInvalidUrlError, url_string);
154    return false;
155  }
156  // Check against host permissions if needed.
157  if (check_host_permissions &&
158      !PermissionsData::HasHostPermission(GetExtension(), *url)) {
159    error_ = ErrorUtils::FormatErrorMessage(
160        keys::kNoHostPermissionsError, url->spec());
161    return false;
162  }
163  return true;
164}
165
166bool CookiesFunction::ParseStoreContext(
167    std::string* store_id,
168    net::URLRequestContextGetter** context) {
169  DCHECK((context || store_id->empty()));
170  Profile* store_profile = NULL;
171  if (!store_id->empty()) {
172    store_profile = cookies_helpers::ChooseProfileFromStoreId(
173        *store_id, GetProfile(), include_incognito());
174    if (!store_profile) {
175      error_ = ErrorUtils::FormatErrorMessage(
176          keys::kInvalidStoreIdError, *store_id);
177      return false;
178    }
179  } else {
180    // The store ID was not specified; use the current execution context's
181    // cookie store by default.
182    // GetCurrentBrowser() already takes into account incognito settings.
183    Browser* current_browser = GetCurrentBrowser();
184    if (!current_browser) {
185      error_ = keys::kNoCookieStoreFoundError;
186      return false;
187    }
188    store_profile = current_browser->profile();
189    *store_id = cookies_helpers::GetStoreIdFromProfile(store_profile);
190  }
191
192  if (context)
193    *context = store_profile->GetRequestContext();
194  DCHECK(context);
195
196  return true;
197}
198
199CookiesGetFunction::CookiesGetFunction() {
200}
201
202CookiesGetFunction::~CookiesGetFunction() {
203}
204
205bool CookiesGetFunction::RunImpl() {
206  parsed_args_ = Get::Params::Create(*args_);
207  EXTENSION_FUNCTION_VALIDATE(parsed_args_.get());
208
209  // Read/validate input parameters.
210  if (!ParseUrl(parsed_args_->details.url, &url_, true))
211    return false;
212
213  std::string store_id =
214      parsed_args_->details.store_id.get() ? *parsed_args_->details.store_id
215                                           : std::string();
216  net::URLRequestContextGetter* store_context = NULL;
217  if (!ParseStoreContext(&store_id, &store_context))
218    return false;
219  store_browser_context_ = store_context;
220  if (!parsed_args_->details.store_id.get())
221    parsed_args_->details.store_id.reset(new std::string(store_id));
222
223  store_browser_context_ = store_context;
224
225  bool rv = BrowserThread::PostTask(
226      BrowserThread::IO, FROM_HERE,
227      base::Bind(&CookiesGetFunction::GetCookieOnIOThread, this));
228  DCHECK(rv);
229
230  // Will finish asynchronously.
231  return true;
232}
233
234void CookiesGetFunction::GetCookieOnIOThread() {
235  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
236  net::CookieStore* cookie_store =
237      store_browser_context_->GetURLRequestContext()->cookie_store();
238  cookies_helpers::GetCookieListFromStore(
239      cookie_store, url_,
240      base::Bind(&CookiesGetFunction::GetCookieCallback, this));
241}
242
243void CookiesGetFunction::GetCookieCallback(const net::CookieList& cookie_list) {
244  net::CookieList::const_iterator it;
245  for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
246    // Return the first matching cookie. Relies on the fact that the
247    // CookieMonster returns them in canonical order (longest path, then
248    // earliest creation time).
249    if (it->Name() == parsed_args_->details.name) {
250      scoped_ptr<Cookie> cookie(
251          cookies_helpers::CreateCookie(*it, *parsed_args_->details.store_id));
252      results_ = Get::Results::Create(*cookie);
253      break;
254    }
255  }
256
257  // The cookie doesn't exist; return null.
258  if (it == cookie_list.end())
259    SetResult(base::Value::CreateNullValue());
260
261  bool rv = BrowserThread::PostTask(
262      BrowserThread::UI, FROM_HERE,
263      base::Bind(&CookiesGetFunction::RespondOnUIThread, this));
264  DCHECK(rv);
265}
266
267void CookiesGetFunction::RespondOnUIThread() {
268  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
269  SendResponse(true);
270}
271
272CookiesGetAllFunction::CookiesGetAllFunction() {
273}
274
275CookiesGetAllFunction::~CookiesGetAllFunction() {
276}
277
278bool CookiesGetAllFunction::RunImpl() {
279  parsed_args_ = GetAll::Params::Create(*args_);
280  EXTENSION_FUNCTION_VALIDATE(parsed_args_.get());
281
282  if (parsed_args_->details.url.get() &&
283      !ParseUrl(*parsed_args_->details.url, &url_, false)) {
284    return false;
285  }
286
287  std::string store_id =
288      parsed_args_->details.store_id.get() ? *parsed_args_->details.store_id
289                                           : std::string();
290  net::URLRequestContextGetter* store_context = NULL;
291  if (!ParseStoreContext(&store_id, &store_context))
292    return false;
293  store_browser_context_ = store_context;
294  if (!parsed_args_->details.store_id.get())
295    parsed_args_->details.store_id.reset(new std::string(store_id));
296
297  bool rv = BrowserThread::PostTask(
298      BrowserThread::IO, FROM_HERE,
299      base::Bind(&CookiesGetAllFunction::GetAllCookiesOnIOThread, this));
300  DCHECK(rv);
301
302  // Will finish asynchronously.
303  return true;
304}
305
306void CookiesGetAllFunction::GetAllCookiesOnIOThread() {
307  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
308  net::CookieStore* cookie_store =
309      store_browser_context_->GetURLRequestContext()->cookie_store();
310  cookies_helpers::GetCookieListFromStore(
311      cookie_store, url_,
312      base::Bind(&CookiesGetAllFunction::GetAllCookiesCallback, this));
313}
314
315void CookiesGetAllFunction::GetAllCookiesCallback(
316    const net::CookieList& cookie_list) {
317  const extensions::Extension* extension = GetExtension();
318  if (extension) {
319    std::vector<linked_ptr<Cookie> > match_vector;
320    cookies_helpers::AppendMatchingCookiesToVector(
321        cookie_list, url_, &parsed_args_->details,
322        GetExtension(), &match_vector);
323
324    results_ = GetAll::Results::Create(match_vector);
325  }
326  bool rv = BrowserThread::PostTask(
327      BrowserThread::UI, FROM_HERE,
328      base::Bind(&CookiesGetAllFunction::RespondOnUIThread, this));
329  DCHECK(rv);
330}
331
332void CookiesGetAllFunction::RespondOnUIThread() {
333  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
334  SendResponse(true);
335}
336
337CookiesSetFunction::CookiesSetFunction() : success_(false) {
338}
339
340CookiesSetFunction::~CookiesSetFunction() {
341}
342
343bool CookiesSetFunction::RunImpl() {
344  parsed_args_ = Set::Params::Create(*args_);
345  EXTENSION_FUNCTION_VALIDATE(parsed_args_.get());
346
347  // Read/validate input parameters.
348  if (!ParseUrl(parsed_args_->details.url, &url_, true))
349      return false;
350
351  std::string store_id =
352      parsed_args_->details.store_id.get() ? *parsed_args_->details.store_id
353                                           : std::string();
354  net::URLRequestContextGetter* store_context = NULL;
355  if (!ParseStoreContext(&store_id, &store_context))
356    return false;
357  store_browser_context_ = store_context;
358  if (!parsed_args_->details.store_id.get())
359    parsed_args_->details.store_id.reset(new std::string(store_id));
360
361  bool rv = BrowserThread::PostTask(
362      BrowserThread::IO, FROM_HERE,
363      base::Bind(&CookiesSetFunction::SetCookieOnIOThread, this));
364  DCHECK(rv);
365
366  // Will finish asynchronously.
367  return true;
368}
369
370void CookiesSetFunction::SetCookieOnIOThread() {
371  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
372  net::CookieMonster* cookie_monster =
373      store_browser_context_->GetURLRequestContext()
374          ->cookie_store()
375          ->GetCookieMonster();
376
377  base::Time expiration_time;
378  if (parsed_args_->details.expiration_date.get()) {
379    // Time::FromDoubleT converts double time 0 to empty Time object. So we need
380    // to do special handling here.
381    expiration_time = (*parsed_args_->details.expiration_date == 0) ?
382        base::Time::UnixEpoch() :
383        base::Time::FromDoubleT(*parsed_args_->details.expiration_date);
384  }
385
386  cookie_monster->SetCookieWithDetailsAsync(
387      url_,
388      parsed_args_->details.name.get() ? *parsed_args_->details.name
389                                       : std::string(),
390      parsed_args_->details.value.get() ? *parsed_args_->details.value
391                                        : std::string(),
392      parsed_args_->details.domain.get() ? *parsed_args_->details.domain
393                                         : std::string(),
394      parsed_args_->details.path.get() ? *parsed_args_->details.path
395                                       : std::string(),
396      expiration_time,
397      parsed_args_->details.secure.get() ? *parsed_args_->details.secure.get()
398                                         : false,
399      parsed_args_->details.http_only.get() ? *parsed_args_->details.http_only
400                                            : false,
401      net::COOKIE_PRIORITY_DEFAULT,
402      base::Bind(&CookiesSetFunction::PullCookie, this));
403}
404
405void CookiesSetFunction::PullCookie(bool set_cookie_result) {
406  // Pull the newly set cookie.
407  net::CookieMonster* cookie_monster =
408      store_browser_context_->GetURLRequestContext()
409          ->cookie_store()
410          ->GetCookieMonster();
411  success_ = set_cookie_result;
412  cookies_helpers::GetCookieListFromStore(
413      cookie_monster, url_,
414      base::Bind(&CookiesSetFunction::PullCookieCallback, this));
415}
416
417void CookiesSetFunction::PullCookieCallback(
418    const net::CookieList& cookie_list) {
419  net::CookieList::const_iterator it;
420  for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
421    // Return the first matching cookie. Relies on the fact that the
422    // CookieMonster returns them in canonical order (longest path, then
423    // earliest creation time).
424    std::string name =
425        parsed_args_->details.name.get() ? *parsed_args_->details.name
426                                         : std::string();
427    if (it->Name() == name) {
428      scoped_ptr<Cookie> cookie(
429          cookies_helpers::CreateCookie(*it, *parsed_args_->details.store_id));
430      results_ = Set::Results::Create(*cookie);
431      break;
432    }
433  }
434
435  bool rv = BrowserThread::PostTask(
436      BrowserThread::UI, FROM_HERE,
437      base::Bind(&CookiesSetFunction::RespondOnUIThread, this));
438  DCHECK(rv);
439}
440
441void CookiesSetFunction::RespondOnUIThread() {
442  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
443  if (!success_) {
444    std::string name =
445        parsed_args_->details.name.get() ? *parsed_args_->details.name
446                                         : std::string();
447    error_ = ErrorUtils::FormatErrorMessage(keys::kCookieSetFailedError, name);
448  }
449  SendResponse(success_);
450}
451
452CookiesRemoveFunction::CookiesRemoveFunction() {
453}
454
455CookiesRemoveFunction::~CookiesRemoveFunction() {
456}
457
458bool CookiesRemoveFunction::RunImpl() {
459  parsed_args_ = Remove::Params::Create(*args_);
460  EXTENSION_FUNCTION_VALIDATE(parsed_args_.get());
461
462  // Read/validate input parameters.
463  if (!ParseUrl(parsed_args_->details.url, &url_, true))
464    return false;
465
466  std::string store_id =
467      parsed_args_->details.store_id.get() ? *parsed_args_->details.store_id
468                                           : std::string();
469  net::URLRequestContextGetter* store_context = NULL;
470  if (!ParseStoreContext(&store_id, &store_context))
471    return false;
472  store_browser_context_ = store_context;
473  if (!parsed_args_->details.store_id.get())
474    parsed_args_->details.store_id.reset(new std::string(store_id));
475
476  // Pass the work off to the IO thread.
477  bool rv = BrowserThread::PostTask(
478      BrowserThread::IO, FROM_HERE,
479      base::Bind(&CookiesRemoveFunction::RemoveCookieOnIOThread, this));
480  DCHECK(rv);
481
482  // Will return asynchronously.
483  return true;
484}
485
486void CookiesRemoveFunction::RemoveCookieOnIOThread() {
487  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
488
489  // Remove the cookie
490  net::CookieStore* cookie_store =
491      store_browser_context_->GetURLRequestContext()->cookie_store();
492  cookie_store->DeleteCookieAsync(
493      url_, parsed_args_->details.name,
494      base::Bind(&CookiesRemoveFunction::RemoveCookieCallback, this));
495}
496
497void CookiesRemoveFunction::RemoveCookieCallback() {
498  // Build the callback result
499  Remove::Results::Details details;
500  details.name = parsed_args_->details.name;
501  details.url = url_.spec();
502  details.store_id = *parsed_args_->details.store_id;
503  results_ = Remove::Results::Create(details);
504
505  // Return to UI thread
506  bool rv = BrowserThread::PostTask(
507      BrowserThread::UI, FROM_HERE,
508      base::Bind(&CookiesRemoveFunction::RespondOnUIThread, this));
509  DCHECK(rv);
510}
511
512void CookiesRemoveFunction::RespondOnUIThread() {
513  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
514  SendResponse(true);
515}
516
517bool CookiesGetAllCookieStoresFunction::RunImpl() {
518  Profile* original_profile = GetProfile();
519  DCHECK(original_profile);
520  scoped_ptr<base::ListValue> original_tab_ids(new base::ListValue());
521  Profile* incognito_profile = NULL;
522  scoped_ptr<base::ListValue> incognito_tab_ids;
523  if (include_incognito() && GetProfile()->HasOffTheRecordProfile()) {
524    incognito_profile = GetProfile()->GetOffTheRecordProfile();
525    if (incognito_profile)
526      incognito_tab_ids.reset(new base::ListValue());
527  }
528  DCHECK(original_profile != incognito_profile);
529
530  // Iterate through all browser instances, and for each browser,
531  // add its tab IDs to either the regular or incognito tab ID list depending
532  // whether the browser is regular or incognito.
533  for (chrome::BrowserIterator it; !it.done(); it.Next()) {
534    Browser* browser = *it;
535    if (browser->profile() == original_profile) {
536      cookies_helpers::AppendToTabIdList(browser, original_tab_ids.get());
537    } else if (incognito_tab_ids.get() &&
538               browser->profile() == incognito_profile) {
539      cookies_helpers::AppendToTabIdList(browser, incognito_tab_ids.get());
540    }
541  }
542  // Return a list of all cookie stores with at least one open tab.
543  std::vector<linked_ptr<CookieStore> > cookie_stores;
544  if (original_tab_ids->GetSize() > 0) {
545    cookie_stores.push_back(make_linked_ptr(
546        cookies_helpers::CreateCookieStore(
547            original_profile, original_tab_ids.release()).release()));
548  }
549  if (incognito_tab_ids.get() && incognito_tab_ids->GetSize() > 0 &&
550      incognito_profile) {
551    cookie_stores.push_back(make_linked_ptr(
552        cookies_helpers::CreateCookieStore(
553            incognito_profile, incognito_tab_ids.release()).release()));
554  }
555  results_ = GetAllCookieStores::Results::Create(cookie_stores);
556  return true;
557}
558
559void CookiesGetAllCookieStoresFunction::Run() {
560  SendResponse(RunImpl());
561}
562
563CookiesAPI::CookiesAPI(content::BrowserContext* context)
564    : browser_context_(context) {
565  ExtensionSystem::Get(browser_context_)->event_router()->RegisterObserver(
566      this, cookies::OnChanged::kEventName);
567}
568
569CookiesAPI::~CookiesAPI() {
570}
571
572void CookiesAPI::Shutdown() {
573  ExtensionSystem::Get(browser_context_)->event_router()->UnregisterObserver(
574      this);
575}
576
577static base::LazyInstance<BrowserContextKeyedAPIFactory<CookiesAPI> >
578    g_factory = LAZY_INSTANCE_INITIALIZER;
579
580// static
581BrowserContextKeyedAPIFactory<CookiesAPI>* CookiesAPI::GetFactoryInstance() {
582  return g_factory.Pointer();
583}
584
585void CookiesAPI::OnListenerAdded(
586    const extensions::EventListenerInfo& details) {
587  cookies_event_router_.reset(new CookiesEventRouter(browser_context_));
588  ExtensionSystem::Get(browser_context_)->event_router()->UnregisterObserver(
589      this);
590}
591
592}  // namespace extensions
593