// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Implements common functionality for the Chrome Extensions Cookies API.
#include "chrome/browser/extensions/extension_cookies_helpers.h"
#include "base/logging.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_cookies_api_constants.h"
#include "chrome/browser/extensions/extension_tabs_module.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tabs/tab_strip_model.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/url_constants.h"
#include "googleurl/src/gurl.h"
namespace keys = extension_cookies_api_constants;
namespace extension_cookies_helpers {
static const char kOriginalProfileStoreId[] = "0";
static const char kOffTheRecordProfileStoreId[] = "1";
Profile* ChooseProfileFromStoreId(const std::string& store_id,
Profile* profile,
bool include_incognito) {
DCHECK(profile);
bool allow_original = !profile->IsOffTheRecord();
bool allow_incognito = profile->IsOffTheRecord() ||
(include_incognito && profile->HasOffTheRecordProfile());
if (store_id == kOriginalProfileStoreId && allow_original)
return profile->GetOriginalProfile();
if (store_id == kOffTheRecordProfileStoreId && allow_incognito)
return profile->GetOffTheRecordProfile();
return NULL;
}
const char* GetStoreIdFromProfile(Profile* profile) {
DCHECK(profile);
return profile->IsOffTheRecord() ?
kOffTheRecordProfileStoreId : kOriginalProfileStoreId;
}
DictionaryValue* CreateCookieValue(
const net::CookieMonster::CanonicalCookie& cookie,
const std::string& store_id) {
DictionaryValue* result = new DictionaryValue();
result->SetString(keys::kNameKey, cookie.Name());
result->SetString(keys::kValueKey, cookie.Value());
result->SetString(keys::kDomainKey, cookie.Domain());
result->SetBoolean(keys::kHostOnlyKey,
net::CookieMonster::DomainIsHostOnly(cookie.Domain()));
result->SetString(keys::kPathKey, cookie.Path());
result->SetBoolean(keys::kSecureKey, cookie.IsSecure());
result->SetBoolean(keys::kHttpOnlyKey, cookie.IsHttpOnly());
result->SetBoolean(keys::kSessionKey, !cookie.DoesExpire());
if (cookie.DoesExpire()) {
result->SetDouble(keys::kExpirationDateKey,
cookie.ExpiryDate().ToDoubleT());
}
result->SetString(keys::kStoreIdKey, store_id);
return result;
}
DictionaryValue* CreateCookieStoreValue(Profile* profile,
ListValue* tab_ids) {
DCHECK(profile);
DCHECK(tab_ids);
DictionaryValue* result = new DictionaryValue();
result->SetString(keys::kIdKey, GetStoreIdFromProfile(profile));
result->Set(keys::kTabIdsKey, tab_ids);
return result;
}
net::CookieList GetCookieListFromStore(
net::CookieStore* cookie_store, const GURL& url) {
DCHECK(cookie_store);
net::CookieMonster* monster = cookie_store->GetCookieMonster();
if (!url.is_empty()) {
DCHECK(url.is_valid());
return monster->GetAllCookiesForURL(url);
}
return monster->GetAllCookies();
}
GURL GetURLFromCanonicalCookie(
const net::CookieMonster::CanonicalCookie& cookie) {
const std::string& domain_key = cookie.Domain();
const std::string scheme =
cookie.IsSecure() ? chrome::kHttpsScheme : chrome::kHttpScheme;
const std::string host =
domain_key.find('.') != 0 ? domain_key : domain_key.substr(1);
return GURL(scheme + chrome::kStandardSchemeSeparator + host + "/");
}
void AppendMatchingCookiesToList(
const net::CookieList& all_cookies,
const std::string& store_id,
const GURL& url, const DictionaryValue* details,
const Extension* extension,
ListValue* match_list) {
net::CookieList::const_iterator it;
for (it = all_cookies.begin(); it != all_cookies.end(); ++it) {
// Ignore any cookie whose domain doesn't match the extension's
// host permissions.
GURL cookie_domain_url = GetURLFromCanonicalCookie(*it);
if (!extension->HasHostPermission(cookie_domain_url))
continue;
// Filter the cookie using the match filter.
extension_cookies_helpers::MatchFilter filter(details);
if (filter.MatchesCookie(*it))
match_list->Append(CreateCookieValue(*it, store_id));
}
}
void AppendToTabIdList(Browser* browser, ListValue* tab_ids) {
DCHECK(browser);
DCHECK(tab_ids);
TabStripModel* tab_strip = browser->tabstrip_model();
for (int i = 0; i < tab_strip->count(); ++i) {
tab_ids->Append(Value::CreateIntegerValue(
ExtensionTabUtil::GetTabId(
tab_strip->GetTabContentsAt(i)->tab_contents())));
}
}
MatchFilter::MatchFilter(const DictionaryValue* details)
: details_(details) {
DCHECK(details_);
}
bool MatchFilter::MatchesCookie(
const net::CookieMonster::CanonicalCookie& cookie) {
return MatchesString(keys::kNameKey, cookie.Name()) &&
MatchesDomain(cookie.Domain()) &&
MatchesString(keys::kPathKey, cookie.Path()) &&
MatchesBoolean(keys::kSecureKey, cookie.IsSecure()) &&
MatchesBoolean(keys::kSessionKey, !cookie.DoesExpire());
}
bool MatchFilter::MatchesString(const char* key, const std::string& value) {
if (!details_->HasKey(key))
return true;
std::string filter_value;
return (details_->GetString(key, &filter_value) &&
value == filter_value);
}
bool MatchFilter::MatchesBoolean(const char* key, bool value) {
if (!details_->HasKey(key))
return true;
bool filter_value = false;
return (details_->GetBoolean(key, &filter_value) &&
value == filter_value);
}
bool MatchFilter::MatchesDomain(const std::string& domain) {
if (!details_->HasKey(keys::kDomainKey))
return true;
std::string filter_value;
if (!details_->GetString(keys::kDomainKey, &filter_value))
return false;
// Add a leading '.' character to the filter domain if it doesn't exist.
if (net::CookieMonster::DomainIsHostOnly(filter_value))
filter_value.insert(0, ".");
std::string sub_domain(domain);
// Strip any leading '.' character from the input cookie domain.
if (!net::CookieMonster::DomainIsHostOnly(sub_domain))
sub_domain = sub_domain.substr(1);
// Now check whether the domain argument is a subdomain of the filter domain.
for (sub_domain.insert(0, ".");
sub_domain.length() >= filter_value.length();) {
if (sub_domain == filter_value)
return true;
const size_t next_dot = sub_domain.find('.', 1); // Skip over leading dot.
sub_domain.erase(0, next_dot);
}
return false;
}
} // namespace extension_cookies_helpers