// 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 #include #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; platforms["linux"] = Feature::LINUX_PLATFORM; platforms["macosx"] = Feature::MACOSX_PLATFORM; platforms["windows"] = Feature::WIN_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 extension_types; std::map contexts; std::map locations; std::map platforms; std::map channels; }; base::LazyInstance g_mappings = LAZY_INSTANCE_INITIALIZER; std::string GetChannelName(VersionInfo::Channel channel) { typedef std::map 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* 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 void ParseEnum(const std::string& string_value, T* enum_value, const std::map& mapping) { typename std::map::const_iterator iter = mapping.find(string_value); CHECK(iter != mapping.end()) << string_value; *enum_value = iter->second; } template void ParseEnum(const base::DictionaryValue* value, const std::string& property, T* enum_value, const std::map& mapping) { std::string string_value; if (!value->GetString(property, &string_value)) return; ParseEnum(string_value, enum_value, mapping); } template void ParseEnumSet(const base::DictionaryValue* value, const std::string& property, std::set* enum_set, const std::map& 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::const_iterator j = mapping.begin(); j != mapping.end(); ++j) { enum_set->insert(j->second); } } return; } std::set string_set; ParseSet(value, property, &string_set); for (std::set::iterator iter = string_set.begin(); iter != string_set.end(); ++iter) { T enum_value = static_cast(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, suitable for giving // to developers in an error message. std::string GetDisplayName(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 ""; } // Gets a human-readable name for the given context type, suitable for giving // to developers in an error message. std::string GetDisplayName(Feature::Context context) { switch (context) { case Feature::UNSPECIFIED_CONTEXT: return "unknown"; case Feature::BLESSED_EXTENSION_CONTEXT: // "privileged" is vague but hopefully the developer will understand that // means background or app window. return "privileged page"; case Feature::UNBLESSED_EXTENSION_CONTEXT: // "iframe" is a bit of a lie/oversimplification, but that's the most // common unblessed context. return "extension iframe"; case Feature::CONTENT_SCRIPT_CONTEXT: return "content script"; case Feature::WEB_PAGE_CONTEXT: return "web page"; } NOTREACHED(); return ""; } // Gets a human-readable list of the display names (pluralized, comma separated // with the "and" in the correct place) for each of |enum_types|. template std::string ListDisplayNames(const std::vector enum_types) { std::string display_name_list; for (size_t i = 0; i < enum_types.size(); ++i) { // Pluralize type name. display_name_list += GetDisplayName(enum_types[i]) + "s"; // Comma-separate entries, with an Oxford comma if there is more than 2 // total entries. if (enum_types.size() > 2) { if (i < enum_types.size() - 2) display_name_list += ", "; else if (i == enum_types.size() - 2) display_name_list += ", and "; } else if (enum_types.size() == 2 && i == 0) { display_name_list += " and "; } } return display_name_list; } 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), 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_), platforms_(other.platforms_), 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_ && platforms_ == other.platforms_ && 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(value, "extension_types", &extension_types_, g_mappings.Get().extension_types); ParseEnumSet(value, "contexts", &contexts_, g_mappings.Get().contexts); ParseEnum(value, "location", &location_, g_mappings.Get().locations); ParseEnumSet(value, "platforms", &platforms_, g_mappings.Get().platforms); value->GetInteger("min_manifest_version", &min_manifest_version_); value->GetInteger("max_manifest_version", &max_manifest_version_); ParseEnum( 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 (!platforms_.empty() && platforms_.find(platform) == platforms_.end()) 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 CreateAvailability(INVALID_CONTEXT, 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, Context context) 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: return base::StringPrintf( "'%s' is only allowed for %s, but this is a %s.", name().c_str(), ListDisplayNames(std::vector( extension_types_.begin(), extension_types_.end())).c_str(), GetDisplayName(type).c_str()); case INVALID_CONTEXT: return base::StringPrintf( "'%s' is only allowed to run in %s, but this is a %s", name().c_str(), ListDisplayNames(std::vector( contexts_.begin(), contexts_.end())).c_str(), GetDisplayName(context).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, but 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(), UNSPECIFIED_CONTEXT)); } Feature::Availability SimpleFeature::CreateAvailability( AvailabilityResult result, Manifest::Type type) const { return Availability(result, GetAvailabilityMessage(result, type, GURL(), UNSPECIFIED_CONTEXT)); } Feature::Availability SimpleFeature::CreateAvailability( AvailabilityResult result, const GURL& url) const { return Availability( result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, url, UNSPECIFIED_CONTEXT)); } Feature::Availability SimpleFeature::CreateAvailability( AvailabilityResult result, Context context) const { return Availability( result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(), context)); } std::set* 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& 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