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