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 common functionality for the Chrome Extensions Cookies API.
6
7#include "chrome/browser/extensions/api/cookies/cookies_helpers.h"
8
9#include <vector>
10
11#include "base/logging.h"
12#include "base/memory/linked_ptr.h"
13#include "base/memory/scoped_ptr.h"
14#include "base/strings/string_util.h"
15#include "base/strings/utf_string_conversions.h"
16#include "base/values.h"
17#include "chrome/browser/extensions/api/cookies/cookies_api_constants.h"
18#include "chrome/browser/extensions/extension_tab_util.h"
19#include "chrome/browser/profiles/profile.h"
20#include "chrome/browser/ui/browser.h"
21#include "chrome/browser/ui/tabs/tab_strip_model.h"
22#include "chrome/common/extensions/api/cookies.h"
23#include "chrome/common/url_constants.h"
24#include "content/public/browser/web_contents.h"
25#include "extensions/common/extension.h"
26#include "extensions/common/permissions/permissions_data.h"
27#include "net/cookies/canonical_cookie.h"
28#include "net/cookies/cookie_util.h"
29#include "url/gurl.h"
30
31using extensions::api::cookies::Cookie;
32using extensions::api::cookies::CookieStore;
33
34namespace GetAll = extensions::api::cookies::GetAll;
35
36namespace extensions {
37
38namespace keys = cookies_api_constants;
39
40namespace cookies_helpers {
41
42static const char kOriginalProfileStoreId[] = "0";
43static const char kOffTheRecordProfileStoreId[] = "1";
44
45Profile* ChooseProfileFromStoreId(const std::string& store_id,
46                                  Profile* profile,
47                                  bool include_incognito) {
48  DCHECK(profile);
49  bool allow_original = !profile->IsOffTheRecord();
50  bool allow_incognito = profile->IsOffTheRecord() ||
51      (include_incognito && profile->HasOffTheRecordProfile());
52  if (store_id == kOriginalProfileStoreId && allow_original)
53    return profile->GetOriginalProfile();
54  if (store_id == kOffTheRecordProfileStoreId && allow_incognito)
55    return profile->GetOffTheRecordProfile();
56  return NULL;
57}
58
59const char* GetStoreIdFromProfile(Profile* profile) {
60  DCHECK(profile);
61  return profile->IsOffTheRecord() ?
62      kOffTheRecordProfileStoreId : kOriginalProfileStoreId;
63}
64
65scoped_ptr<Cookie> CreateCookie(
66    const net::CanonicalCookie& canonical_cookie,
67    const std::string& store_id) {
68  scoped_ptr<Cookie> cookie(new Cookie());
69
70  // A cookie is a raw byte sequence. By explicitly parsing it as UTF-8, we
71  // apply error correction, so the string can be safely passed to the renderer.
72  cookie->name = base::UTF16ToUTF8(base::UTF8ToUTF16(canonical_cookie.Name()));
73  cookie->value =
74      base::UTF16ToUTF8(base::UTF8ToUTF16(canonical_cookie.Value()));
75  cookie->domain = canonical_cookie.Domain();
76  cookie->host_only = net::cookie_util::DomainIsHostOnly(
77      canonical_cookie.Domain());
78  // A non-UTF8 path is invalid, so we just replace it with an empty string.
79  cookie->path = base::IsStringUTF8(canonical_cookie.Path()) ?
80      canonical_cookie.Path() : std::string();
81  cookie->secure = canonical_cookie.IsSecure();
82  cookie->http_only = canonical_cookie.IsHttpOnly();
83  cookie->session = !canonical_cookie.IsPersistent();
84  if (canonical_cookie.IsPersistent()) {
85    cookie->expiration_date.reset(
86        new double(canonical_cookie.ExpiryDate().ToDoubleT()));
87  }
88  cookie->store_id = store_id;
89
90  return cookie.Pass();
91}
92
93scoped_ptr<CookieStore> CreateCookieStore(Profile* profile,
94                                          base::ListValue* tab_ids) {
95  DCHECK(profile);
96  DCHECK(tab_ids);
97  base::DictionaryValue dict;
98  dict.SetString(keys::kIdKey, GetStoreIdFromProfile(profile));
99  dict.Set(keys::kTabIdsKey, tab_ids);
100
101  CookieStore* cookie_store = new CookieStore();
102  bool rv = CookieStore::Populate(dict, cookie_store);
103  CHECK(rv);
104  return scoped_ptr<CookieStore>(cookie_store);
105}
106
107void GetCookieListFromStore(
108    net::CookieStore* cookie_store, const GURL& url,
109    const net::CookieMonster::GetCookieListCallback& callback) {
110  DCHECK(cookie_store);
111  net::CookieMonster* monster = cookie_store->GetCookieMonster();
112  if (!url.is_empty()) {
113    DCHECK(url.is_valid());
114    monster->GetAllCookiesForURLAsync(url, callback);
115  } else {
116    monster->GetAllCookiesAsync(callback);
117  }
118}
119
120GURL GetURLFromCanonicalCookie(const net::CanonicalCookie& cookie) {
121  const std::string& domain_key = cookie.Domain();
122  const std::string scheme =
123      cookie.IsSecure() ? url::kHttpsScheme : url::kHttpScheme;
124  const std::string host =
125      domain_key.find('.') != 0 ? domain_key : domain_key.substr(1);
126  return GURL(scheme + url::kStandardSchemeSeparator + host + "/");
127}
128
129void AppendMatchingCookiesToVector(const net::CookieList& all_cookies,
130                                   const GURL& url,
131                                   const GetAll::Params::Details* details,
132                                   const Extension* extension,
133                                   LinkedCookieVec* match_vector) {
134  net::CookieList::const_iterator it;
135  for (it = all_cookies.begin(); it != all_cookies.end(); ++it) {
136    // Ignore any cookie whose domain doesn't match the extension's
137    // host permissions.
138    GURL cookie_domain_url = GetURLFromCanonicalCookie(*it);
139    if (!extension->permissions_data()->HasHostPermission(cookie_domain_url))
140      continue;
141    // Filter the cookie using the match filter.
142    cookies_helpers::MatchFilter filter(details);
143    if (filter.MatchesCookie(*it)) {
144      match_vector->push_back(make_linked_ptr(
145          CreateCookie(*it, *details->store_id).release()));
146    }
147  }
148}
149
150void AppendToTabIdList(Browser* browser, base::ListValue* tab_ids) {
151  DCHECK(browser);
152  DCHECK(tab_ids);
153  TabStripModel* tab_strip = browser->tab_strip_model();
154  for (int i = 0; i < tab_strip->count(); ++i) {
155    tab_ids->Append(new base::FundamentalValue(
156        ExtensionTabUtil::GetTabId(tab_strip->GetWebContentsAt(i))));
157  }
158}
159
160MatchFilter::MatchFilter(const GetAll::Params::Details* details)
161    : details_(details) {
162  DCHECK(details_);
163}
164
165bool MatchFilter::MatchesCookie(
166    const net::CanonicalCookie& cookie) {
167  if (details_->name.get() && *details_->name != cookie.Name())
168    return false;
169
170  if (!MatchesDomain(cookie.Domain()))
171    return false;
172
173  if (details_->path.get() && *details_->path != cookie.Path())
174    return false;
175
176  if (details_->secure.get() && *details_->secure != cookie.IsSecure())
177    return false;
178
179  if (details_->session.get() && *details_->session != !cookie.IsPersistent())
180    return false;
181
182  return true;
183}
184
185bool MatchFilter::MatchesDomain(const std::string& domain) {
186  if (!details_->domain.get())
187    return true;
188
189  // Add a leading '.' character to the filter domain if it doesn't exist.
190  if (net::cookie_util::DomainIsHostOnly(*details_->domain))
191    details_->domain->insert(0, ".");
192
193  std::string sub_domain(domain);
194  // Strip any leading '.' character from the input cookie domain.
195  if (!net::cookie_util::DomainIsHostOnly(sub_domain))
196    sub_domain = sub_domain.substr(1);
197
198  // Now check whether the domain argument is a subdomain of the filter domain.
199  for (sub_domain.insert(0, ".");
200       sub_domain.length() >= details_->domain->length();) {
201    if (sub_domain == *details_->domain)
202      return true;
203    const size_t next_dot = sub_domain.find('.', 1);  // Skip over leading dot.
204    sub_domain.erase(0, next_dot);
205  }
206  return false;
207}
208
209}  // namespace cookies_helpers
210}  // namespace extensions
211