blob: c03b03ebe1bc141d461d3fce9eb9b075c31597cd [file] [log] [blame]
// Copyright (c) 2012 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.
#include "chrome/common/extensions/features/simple_feature.h"
#include <map>
#include <vector>
#include "base/command_line.h"
#include "base/lazy_instance.h"
#include "base/sha1.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/features/feature_channel.h"
using chrome::VersionInfo;
namespace extensions {
namespace {
struct Mappings {
Mappings() {
extension_types["extension"] = Manifest::TYPE_EXTENSION;
extension_types["theme"] = Manifest::TYPE_THEME;
extension_types["legacy_packaged_app"] = Manifest::TYPE_LEGACY_PACKAGED_APP;
extension_types["hosted_app"] = Manifest::TYPE_HOSTED_APP;
extension_types["platform_app"] = Manifest::TYPE_PLATFORM_APP;
extension_types["shared_module"] = Manifest::TYPE_SHARED_MODULE;
contexts["blessed_extension"] = Feature::BLESSED_EXTENSION_CONTEXT;
contexts["unblessed_extension"] = Feature::UNBLESSED_EXTENSION_CONTEXT;
contexts["content_script"] = Feature::CONTENT_SCRIPT_CONTEXT;
contexts["web_page"] = Feature::WEB_PAGE_CONTEXT;
locations["component"] = Feature::COMPONENT_LOCATION;
platforms["chromeos"] = Feature::CHROMEOS_PLATFORM;
channels["trunk"] = VersionInfo::CHANNEL_UNKNOWN;
channels["canary"] = VersionInfo::CHANNEL_CANARY;
channels["dev"] = VersionInfo::CHANNEL_DEV;
channels["beta"] = VersionInfo::CHANNEL_BETA;
channels["stable"] = VersionInfo::CHANNEL_STABLE;
}
std::map<std::string, Manifest::Type> extension_types;
std::map<std::string, Feature::Context> contexts;
std::map<std::string, Feature::Location> locations;
std::map<std::string, Feature::Platform> platforms;
std::map<std::string, VersionInfo::Channel> channels;
};
base::LazyInstance<Mappings> g_mappings = LAZY_INSTANCE_INITIALIZER;
std::string GetChannelName(VersionInfo::Channel channel) {
typedef std::map<std::string, VersionInfo::Channel> ChannelsMap;
ChannelsMap channels = g_mappings.Get().channels;
for (ChannelsMap::iterator i = channels.begin(); i != channels.end(); ++i) {
if (i->second == channel)
return i->first;
}
NOTREACHED();
return "unknown";
}
// TODO(aa): Can we replace all this manual parsing with JSON schema stuff?
void ParseSet(const base::DictionaryValue* value,
const std::string& property,
std::set<std::string>* set) {
const base::ListValue* list_value = NULL;
if (!value->GetList(property, &list_value))
return;
set->clear();
for (size_t i = 0; i < list_value->GetSize(); ++i) {
std::string str_val;
CHECK(list_value->GetString(i, &str_val)) << property << " " << i;
set->insert(str_val);
}
}
template<typename T>
void ParseEnum(const std::string& string_value,
T* enum_value,
const std::map<std::string, T>& mapping) {
typename std::map<std::string, T>::const_iterator iter =
mapping.find(string_value);
CHECK(iter != mapping.end()) << string_value;
*enum_value = iter->second;
}
template<typename T>
void ParseEnum(const base::DictionaryValue* value,
const std::string& property,
T* enum_value,
const std::map<std::string, T>& mapping) {
std::string string_value;
if (!value->GetString(property, &string_value))
return;
ParseEnum(string_value, enum_value, mapping);
}
template<typename T>
void ParseEnumSet(const base::DictionaryValue* value,
const std::string& property,
std::set<T>* enum_set,
const std::map<std::string, T>& mapping) {
if (!value->HasKey(property))
return;
enum_set->clear();
std::string property_string;
if (value->GetString(property, &property_string)) {
if (property_string == "all") {
for (typename std::map<std::string, T>::const_iterator j =
mapping.begin(); j != mapping.end(); ++j) {
enum_set->insert(j->second);
}
}
return;
}
std::set<std::string> string_set;
ParseSet(value, property, &string_set);
for (std::set<std::string>::iterator iter = string_set.begin();
iter != string_set.end(); ++iter) {
T enum_value = static_cast<T>(0);
ParseEnum(*iter, &enum_value, mapping);
enum_set->insert(enum_value);
}
}
void ParseURLPatterns(const base::DictionaryValue* value,
const std::string& key,
URLPatternSet* set) {
const base::ListValue* matches = NULL;
if (value->GetList(key, &matches)) {
set->ClearPatterns();
for (size_t i = 0; i < matches->GetSize(); ++i) {
std::string pattern;
CHECK(matches->GetString(i, &pattern));
set->AddPattern(URLPattern(URLPattern::SCHEME_ALL, pattern));
}
}
}
// Gets a human-readable name for the given extension type.
std::string GetDisplayTypeName(Manifest::Type type) {
switch (type) {
case Manifest::TYPE_UNKNOWN:
return "unknown";
case Manifest::TYPE_EXTENSION:
return "extension";
case Manifest::TYPE_HOSTED_APP:
return "hosted app";
case Manifest::TYPE_LEGACY_PACKAGED_APP:
return "legacy packaged app";
case Manifest::TYPE_PLATFORM_APP:
return "packaged app";
case Manifest::TYPE_THEME:
return "theme";
case Manifest::TYPE_USER_SCRIPT:
return "user script";
case Manifest::TYPE_SHARED_MODULE:
return "shared module";
}
NOTREACHED();
return std::string();
}
std::string HashExtensionId(const std::string& extension_id) {
const std::string id_hash = base::SHA1HashString(extension_id);
DCHECK(id_hash.length() == base::kSHA1Length);
return base::HexEncode(id_hash.c_str(), id_hash.length());
}
} // namespace
SimpleFeature::SimpleFeature()
: location_(UNSPECIFIED_LOCATION),
platform_(UNSPECIFIED_PLATFORM),
min_manifest_version_(0),
max_manifest_version_(0),
channel_(VersionInfo::CHANNEL_UNKNOWN),
has_parent_(false),
channel_has_been_set_(false) {
}
SimpleFeature::SimpleFeature(const SimpleFeature& other)
: whitelist_(other.whitelist_),
extension_types_(other.extension_types_),
contexts_(other.contexts_),
matches_(other.matches_),
location_(other.location_),
platform_(other.platform_),
min_manifest_version_(other.min_manifest_version_),
max_manifest_version_(other.max_manifest_version_),
channel_(other.channel_),
has_parent_(other.has_parent_),
channel_has_been_set_(other.channel_has_been_set_) {
}
SimpleFeature::~SimpleFeature() {
}
bool SimpleFeature::Equals(const SimpleFeature& other) const {
return whitelist_ == other.whitelist_ &&
extension_types_ == other.extension_types_ &&
contexts_ == other.contexts_ &&
matches_ == other.matches_ &&
location_ == other.location_ &&
platform_ == other.platform_ &&
min_manifest_version_ == other.min_manifest_version_ &&
max_manifest_version_ == other.max_manifest_version_ &&
channel_ == other.channel_ &&
has_parent_ == other.has_parent_ &&
channel_has_been_set_ == other.channel_has_been_set_;
}
std::string SimpleFeature::Parse(const base::DictionaryValue* value) {
ParseURLPatterns(value, "matches", &matches_);
ParseSet(value, "whitelist", &whitelist_);
ParseSet(value, "dependencies", &dependencies_);
ParseEnumSet<Manifest::Type>(value, "extension_types", &extension_types_,
g_mappings.Get().extension_types);
ParseEnumSet<Context>(value, "contexts", &contexts_,
g_mappings.Get().contexts);
ParseEnum<Location>(value, "location", &location_,
g_mappings.Get().locations);
ParseEnum<Platform>(value, "platform", &platform_,
g_mappings.Get().platforms);
value->GetInteger("min_manifest_version", &min_manifest_version_);
value->GetInteger("max_manifest_version", &max_manifest_version_);
ParseEnum<VersionInfo::Channel>(
value, "channel", &channel_,
g_mappings.Get().channels);
no_parent_ = false;
value->GetBoolean("noparent", &no_parent_);
// The "trunk" channel uses VersionInfo::CHANNEL_UNKNOWN, so we need to keep
// track of whether the channel has been set or not separately.
channel_has_been_set_ |= value->HasKey("channel");
if (!channel_has_been_set_ && dependencies_.empty())
return name() + ": Must supply a value for channel or dependencies.";
if (matches_.is_empty() && contexts_.count(WEB_PAGE_CONTEXT) != 0) {
return name() + ": Allowing web_page contexts requires supplying a value " +
"for matches.";
}
return std::string();
}
Feature::Availability SimpleFeature::IsAvailableToManifest(
const std::string& extension_id,
Manifest::Type type,
Location location,
int manifest_version,
Platform platform) const {
// Component extensions can access any feature.
if (location == COMPONENT_LOCATION)
return CreateAvailability(IS_AVAILABLE, type);
if (!whitelist_.empty()) {
if (!IsIdInWhitelist(extension_id)) {
// TODO(aa): This is gross. There should be a better way to test the
// whitelist.
CommandLine* command_line = CommandLine::ForCurrentProcess();
if (!command_line->HasSwitch(switches::kWhitelistedExtensionID))
return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
std::string whitelist_switch_value =
CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kWhitelistedExtensionID);
if (extension_id != whitelist_switch_value)
return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
}
}
// HACK(kalman): user script -> extension. Solve this in a more generic way
// when we compile feature files.
Manifest::Type type_to_check = (type == Manifest::TYPE_USER_SCRIPT) ?
Manifest::TYPE_EXTENSION : type;
if (!extension_types_.empty() &&
extension_types_.find(type_to_check) == extension_types_.end()) {
return CreateAvailability(INVALID_TYPE, type);
}
if (location_ != UNSPECIFIED_LOCATION && location_ != location)
return CreateAvailability(INVALID_LOCATION, type);
if (platform_ != UNSPECIFIED_PLATFORM && platform_ != platform)
return CreateAvailability(INVALID_PLATFORM, type);
if (min_manifest_version_ != 0 && manifest_version < min_manifest_version_)
return CreateAvailability(INVALID_MIN_MANIFEST_VERSION, type);
if (max_manifest_version_ != 0 && manifest_version > max_manifest_version_)
return CreateAvailability(INVALID_MAX_MANIFEST_VERSION, type);
if (channel_has_been_set_ && channel_ < GetCurrentChannel())
return CreateAvailability(UNSUPPORTED_CHANNEL, type);
return CreateAvailability(IS_AVAILABLE, type);
}
Feature::Availability SimpleFeature::IsAvailableToContext(
const Extension* extension,
SimpleFeature::Context context,
const GURL& url,
SimpleFeature::Platform platform) const {
if (extension) {
Availability result = IsAvailableToManifest(
extension->id(),
extension->GetType(),
ConvertLocation(extension->location()),
extension->manifest_version(),
platform);
if (!result.is_available())
return result;
}
if (!contexts_.empty() && contexts_.find(context) == contexts_.end()) {
return extension ?
CreateAvailability(INVALID_CONTEXT, extension->GetType()) :
CreateAvailability(INVALID_CONTEXT);
}
if (!matches_.is_empty() && !matches_.MatchesURL(url))
return CreateAvailability(INVALID_URL, url);
return CreateAvailability(IS_AVAILABLE);
}
std::string SimpleFeature::GetAvailabilityMessage(
AvailabilityResult result, Manifest::Type type, const GURL& url) const {
switch (result) {
case IS_AVAILABLE:
return std::string();
case NOT_FOUND_IN_WHITELIST:
return base::StringPrintf(
"'%s' is not allowed for specified extension ID.",
name().c_str());
case INVALID_URL:
return base::StringPrintf("'%s' is not allowed on %s.",
name().c_str(), url.spec().c_str());
case INVALID_TYPE: {
std::string allowed_type_names;
// Turn the set of allowed types into a vector so that it's easier to
// inject the appropriate separator into the display string.
std::vector<Manifest::Type> extension_types(
extension_types_.begin(), extension_types_.end());
for (size_t i = 0; i < extension_types.size(); i++) {
// Pluralize type name.
allowed_type_names += GetDisplayTypeName(extension_types[i]) + "s";
if (i == extension_types_.size() - 2) {
allowed_type_names += " and ";
} else if (i != extension_types_.size() - 1) {
allowed_type_names += ", ";
}
}
return base::StringPrintf(
"'%s' is only allowed for %s, and this is a %s.",
name().c_str(),
allowed_type_names.c_str(),
GetDisplayTypeName(type).c_str());
}
case INVALID_CONTEXT:
return base::StringPrintf(
"'%s' is not allowed for specified context type content script, "
" extension page, web page, etc.).",
name().c_str());
case INVALID_LOCATION:
return base::StringPrintf(
"'%s' is not allowed for specified install location.",
name().c_str());
case INVALID_PLATFORM:
return base::StringPrintf(
"'%s' is not allowed for specified platform.",
name().c_str());
case INVALID_MIN_MANIFEST_VERSION:
return base::StringPrintf(
"'%s' requires manifest version of at least %d.",
name().c_str(),
min_manifest_version_);
case INVALID_MAX_MANIFEST_VERSION:
return base::StringPrintf(
"'%s' requires manifest version of %d or lower.",
name().c_str(),
max_manifest_version_);
case NOT_PRESENT:
return base::StringPrintf(
"'%s' requires a different Feature that is not present.",
name().c_str());
case UNSUPPORTED_CHANNEL:
return base::StringPrintf(
"'%s' requires Google Chrome %s channel or newer, and this is the "
"%s channel.",
name().c_str(),
GetChannelName(channel_).c_str(),
GetChannelName(GetCurrentChannel()).c_str());
}
NOTREACHED();
return std::string();
}
Feature::Availability SimpleFeature::CreateAvailability(
AvailabilityResult result) const {
return Availability(
result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL()));
}
Feature::Availability SimpleFeature::CreateAvailability(
AvailabilityResult result, Manifest::Type type) const {
return Availability(result, GetAvailabilityMessage(result, type, GURL()));
}
Feature::Availability SimpleFeature::CreateAvailability(
AvailabilityResult result,
const GURL& url) const {
return Availability(
result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, url));
}
std::set<Feature::Context>* SimpleFeature::GetContexts() {
return &contexts_;
}
bool SimpleFeature::IsInternal() const {
NOTREACHED();
return false;
}
bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id) const {
return IsIdInWhitelist(extension_id, whitelist_);
}
// static
bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id,
const std::set<std::string>& whitelist) {
// Belt-and-suspenders philosophy here. We should be pretty confident by this
// point that we've validated the extension ID format, but in case something
// slips through, we avoid a class of attack where creative ID manipulation
// leads to hash collisions.
if (extension_id.length() != 32) // 128 bits / 4 = 32 mpdecimal characters
return false;
if (whitelist.find(extension_id) != whitelist.end() ||
whitelist.find(HashExtensionId(extension_id)) != whitelist.end()) {
return true;
}
return false;
}
} // namespace extensions