Home | History | Annotate | Download | only in features
      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 #include "chrome/common/extensions/features/simple_feature.h"
      6 
      7 #include <map>
      8 #include <vector>
      9 
     10 #include "base/command_line.h"
     11 #include "base/lazy_instance.h"
     12 #include "base/sha1.h"
     13 #include "base/strings/string_number_conversions.h"
     14 #include "base/strings/string_util.h"
     15 #include "base/strings/stringprintf.h"
     16 #include "chrome/common/chrome_switches.h"
     17 #include "chrome/common/extensions/features/feature_channel.h"
     18 
     19 using chrome::VersionInfo;
     20 
     21 namespace extensions {
     22 
     23 namespace {
     24 
     25 struct Mappings {
     26   Mappings() {
     27     extension_types["extension"] = Manifest::TYPE_EXTENSION;
     28     extension_types["theme"] = Manifest::TYPE_THEME;
     29     extension_types["packaged_app"] = Manifest::TYPE_LEGACY_PACKAGED_APP;
     30     extension_types["hosted_app"] = Manifest::TYPE_HOSTED_APP;
     31     extension_types["platform_app"] = Manifest::TYPE_PLATFORM_APP;
     32     extension_types["shared_module"] = Manifest::TYPE_SHARED_MODULE;
     33 
     34     contexts["blessed_extension"] = Feature::BLESSED_EXTENSION_CONTEXT;
     35     contexts["unblessed_extension"] = Feature::UNBLESSED_EXTENSION_CONTEXT;
     36     contexts["content_script"] = Feature::CONTENT_SCRIPT_CONTEXT;
     37     contexts["web_page"] = Feature::WEB_PAGE_CONTEXT;
     38 
     39     locations["component"] = Feature::COMPONENT_LOCATION;
     40 
     41     platforms["chromeos"] = Feature::CHROMEOS_PLATFORM;
     42 
     43     channels["trunk"] = VersionInfo::CHANNEL_UNKNOWN;
     44     channels["canary"] = VersionInfo::CHANNEL_CANARY;
     45     channels["dev"] = VersionInfo::CHANNEL_DEV;
     46     channels["beta"] = VersionInfo::CHANNEL_BETA;
     47     channels["stable"] = VersionInfo::CHANNEL_STABLE;
     48   }
     49 
     50   std::map<std::string, Manifest::Type> extension_types;
     51   std::map<std::string, Feature::Context> contexts;
     52   std::map<std::string, Feature::Location> locations;
     53   std::map<std::string, Feature::Platform> platforms;
     54   std::map<std::string, VersionInfo::Channel> channels;
     55 };
     56 
     57 base::LazyInstance<Mappings> g_mappings = LAZY_INSTANCE_INITIALIZER;
     58 
     59 std::string GetChannelName(VersionInfo::Channel channel) {
     60   typedef std::map<std::string, VersionInfo::Channel> ChannelsMap;
     61   ChannelsMap channels = g_mappings.Get().channels;
     62   for (ChannelsMap::iterator i = channels.begin(); i != channels.end(); ++i) {
     63     if (i->second == channel)
     64       return i->first;
     65   }
     66   NOTREACHED();
     67   return "unknown";
     68 }
     69 
     70 // TODO(aa): Can we replace all this manual parsing with JSON schema stuff?
     71 
     72 void ParseSet(const base::DictionaryValue* value,
     73               const std::string& property,
     74               std::set<std::string>* set) {
     75   const base::ListValue* list_value = NULL;
     76   if (!value->GetList(property, &list_value))
     77     return;
     78 
     79   set->clear();
     80   for (size_t i = 0; i < list_value->GetSize(); ++i) {
     81     std::string str_val;
     82     CHECK(list_value->GetString(i, &str_val)) << property << " " << i;
     83     set->insert(str_val);
     84   }
     85 }
     86 
     87 template<typename T>
     88 void ParseEnum(const std::string& string_value,
     89                T* enum_value,
     90                const std::map<std::string, T>& mapping) {
     91   typename std::map<std::string, T>::const_iterator iter =
     92       mapping.find(string_value);
     93   CHECK(iter != mapping.end()) << string_value;
     94   *enum_value = iter->second;
     95 }
     96 
     97 template<typename T>
     98 void ParseEnum(const base::DictionaryValue* value,
     99                const std::string& property,
    100                T* enum_value,
    101                const std::map<std::string, T>& mapping) {
    102   std::string string_value;
    103   if (!value->GetString(property, &string_value))
    104     return;
    105 
    106   ParseEnum(string_value, enum_value, mapping);
    107 }
    108 
    109 template<typename T>
    110 void ParseEnumSet(const base::DictionaryValue* value,
    111                   const std::string& property,
    112                   std::set<T>* enum_set,
    113                   const std::map<std::string, T>& mapping) {
    114   if (!value->HasKey(property))
    115     return;
    116 
    117   enum_set->clear();
    118 
    119   std::string property_string;
    120   if (value->GetString(property, &property_string)) {
    121     if (property_string == "all") {
    122       for (typename std::map<std::string, T>::const_iterator j =
    123                mapping.begin(); j != mapping.end(); ++j) {
    124         enum_set->insert(j->second);
    125       }
    126     }
    127     return;
    128   }
    129 
    130   std::set<std::string> string_set;
    131   ParseSet(value, property, &string_set);
    132   for (std::set<std::string>::iterator iter = string_set.begin();
    133        iter != string_set.end(); ++iter) {
    134     T enum_value = static_cast<T>(0);
    135     ParseEnum(*iter, &enum_value, mapping);
    136     enum_set->insert(enum_value);
    137   }
    138 }
    139 
    140 void ParseURLPatterns(const base::DictionaryValue* value,
    141                       const std::string& key,
    142                       URLPatternSet* set) {
    143   const base::ListValue* matches = NULL;
    144   if (value->GetList(key, &matches)) {
    145     set->ClearPatterns();
    146     for (size_t i = 0; i < matches->GetSize(); ++i) {
    147       std::string pattern;
    148       CHECK(matches->GetString(i, &pattern));
    149       set->AddPattern(URLPattern(URLPattern::SCHEME_ALL, pattern));
    150     }
    151   }
    152 }
    153 
    154 // Gets a human-readable name for the given extension type.
    155 std::string GetDisplayTypeName(Manifest::Type type) {
    156   switch (type) {
    157     case Manifest::TYPE_UNKNOWN:
    158       return "unknown";
    159     case Manifest::TYPE_EXTENSION:
    160       return "extension";
    161     case Manifest::TYPE_HOSTED_APP:
    162       return "hosted app";
    163     case Manifest::TYPE_LEGACY_PACKAGED_APP:
    164       return "legacy packaged app";
    165     case Manifest::TYPE_PLATFORM_APP:
    166       return "packaged app";
    167     case Manifest::TYPE_THEME:
    168       return "theme";
    169     case Manifest::TYPE_USER_SCRIPT:
    170       return "user script";
    171     case Manifest::TYPE_SHARED_MODULE:
    172       return "shared module";
    173   }
    174 
    175   NOTREACHED();
    176   return std::string();
    177 }
    178 
    179 std::string HashExtensionId(const std::string& extension_id) {
    180   const std::string id_hash = base::SHA1HashString(extension_id);
    181   DCHECK(id_hash.length() == base::kSHA1Length);
    182   return base::HexEncode(id_hash.c_str(), id_hash.length());
    183 }
    184 
    185 }  // namespace
    186 
    187 SimpleFeature::SimpleFeature()
    188   : location_(UNSPECIFIED_LOCATION),
    189     platform_(UNSPECIFIED_PLATFORM),
    190     min_manifest_version_(0),
    191     max_manifest_version_(0),
    192     channel_(VersionInfo::CHANNEL_UNKNOWN),
    193     has_parent_(false),
    194     channel_has_been_set_(false) {
    195 }
    196 
    197 SimpleFeature::SimpleFeature(const SimpleFeature& other)
    198     : whitelist_(other.whitelist_),
    199       extension_types_(other.extension_types_),
    200       contexts_(other.contexts_),
    201       matches_(other.matches_),
    202       location_(other.location_),
    203       platform_(other.platform_),
    204       min_manifest_version_(other.min_manifest_version_),
    205       max_manifest_version_(other.max_manifest_version_),
    206       channel_(other.channel_),
    207       has_parent_(other.has_parent_),
    208       channel_has_been_set_(other.channel_has_been_set_) {
    209 }
    210 
    211 SimpleFeature::~SimpleFeature() {
    212 }
    213 
    214 bool SimpleFeature::Equals(const SimpleFeature& other) const {
    215   return whitelist_ == other.whitelist_ &&
    216       extension_types_ == other.extension_types_ &&
    217       contexts_ == other.contexts_ &&
    218       matches_ == other.matches_ &&
    219       location_ == other.location_ &&
    220       platform_ == other.platform_ &&
    221       min_manifest_version_ == other.min_manifest_version_ &&
    222       max_manifest_version_ == other.max_manifest_version_ &&
    223       channel_ == other.channel_ &&
    224       has_parent_ == other.has_parent_ &&
    225       channel_has_been_set_ == other.channel_has_been_set_;
    226 }
    227 
    228 std::string SimpleFeature::Parse(const base::DictionaryValue* value) {
    229   ParseURLPatterns(value, "matches", &matches_);
    230   ParseSet(value, "whitelist", &whitelist_);
    231   ParseSet(value, "dependencies", &dependencies_);
    232   ParseEnumSet<Manifest::Type>(value, "extension_types", &extension_types_,
    233                                 g_mappings.Get().extension_types);
    234   ParseEnumSet<Context>(value, "contexts", &contexts_,
    235                         g_mappings.Get().contexts);
    236   ParseEnum<Location>(value, "location", &location_,
    237                       g_mappings.Get().locations);
    238   ParseEnum<Platform>(value, "platform", &platform_,
    239                       g_mappings.Get().platforms);
    240   value->GetInteger("min_manifest_version", &min_manifest_version_);
    241   value->GetInteger("max_manifest_version", &max_manifest_version_);
    242   ParseEnum<VersionInfo::Channel>(
    243       value, "channel", &channel_,
    244       g_mappings.Get().channels);
    245 
    246   no_parent_ = false;
    247   value->GetBoolean("noparent", &no_parent_);
    248 
    249   // The "trunk" channel uses VersionInfo::CHANNEL_UNKNOWN, so we need to keep
    250   // track of whether the channel has been set or not separately.
    251   channel_has_been_set_ |= value->HasKey("channel");
    252   if (!channel_has_been_set_ && dependencies_.empty())
    253     return name() + ": Must supply a value for channel or dependencies.";
    254 
    255   if (matches_.is_empty() && contexts_.count(WEB_PAGE_CONTEXT) != 0) {
    256     return name() + ": Allowing web_page contexts requires supplying a value " +
    257         "for matches.";
    258   }
    259 
    260   return std::string();
    261 }
    262 
    263 Feature::Availability SimpleFeature::IsAvailableToManifest(
    264     const std::string& extension_id,
    265     Manifest::Type type,
    266     Location location,
    267     int manifest_version,
    268     Platform platform) const {
    269   // Component extensions can access any feature.
    270   if (location == COMPONENT_LOCATION)
    271     return CreateAvailability(IS_AVAILABLE, type);
    272 
    273   if (!whitelist_.empty()) {
    274     if (!IsIdInWhitelist(extension_id)) {
    275       // TODO(aa): This is gross. There should be a better way to test the
    276       // whitelist.
    277       CommandLine* command_line = CommandLine::ForCurrentProcess();
    278       if (!command_line->HasSwitch(switches::kWhitelistedExtensionID))
    279         return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
    280 
    281       std::string whitelist_switch_value =
    282           CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
    283               switches::kWhitelistedExtensionID);
    284       if (extension_id != whitelist_switch_value)
    285         return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
    286     }
    287   }
    288 
    289   // HACK(kalman): user script -> extension. Solve this in a more generic way
    290   // when we compile feature files.
    291   Manifest::Type type_to_check = (type == Manifest::TYPE_USER_SCRIPT) ?
    292       Manifest::TYPE_EXTENSION : type;
    293   if (!extension_types_.empty() &&
    294       extension_types_.find(type_to_check) == extension_types_.end()) {
    295     return CreateAvailability(INVALID_TYPE, type);
    296   }
    297 
    298   if (location_ != UNSPECIFIED_LOCATION && location_ != location)
    299     return CreateAvailability(INVALID_LOCATION, type);
    300 
    301   if (platform_ != UNSPECIFIED_PLATFORM && platform_ != platform)
    302     return CreateAvailability(INVALID_PLATFORM, type);
    303 
    304   if (min_manifest_version_ != 0 && manifest_version < min_manifest_version_)
    305     return CreateAvailability(INVALID_MIN_MANIFEST_VERSION, type);
    306 
    307   if (max_manifest_version_ != 0 && manifest_version > max_manifest_version_)
    308     return CreateAvailability(INVALID_MAX_MANIFEST_VERSION, type);
    309 
    310   if (channel_has_been_set_ && channel_ < GetCurrentChannel())
    311     return CreateAvailability(UNSUPPORTED_CHANNEL, type);
    312 
    313   return CreateAvailability(IS_AVAILABLE, type);
    314 }
    315 
    316 Feature::Availability SimpleFeature::IsAvailableToContext(
    317     const Extension* extension,
    318     SimpleFeature::Context context,
    319     const GURL& url,
    320     SimpleFeature::Platform platform) const {
    321   if (extension) {
    322     Availability result = IsAvailableToManifest(
    323         extension->id(),
    324         extension->GetType(),
    325         ConvertLocation(extension->location()),
    326         extension->manifest_version(),
    327         platform);
    328     if (!result.is_available())
    329       return result;
    330   }
    331 
    332   if (!contexts_.empty() && contexts_.find(context) == contexts_.end()) {
    333     return extension ?
    334         CreateAvailability(INVALID_CONTEXT, extension->GetType()) :
    335         CreateAvailability(INVALID_CONTEXT);
    336   }
    337 
    338   if (!matches_.is_empty() && !matches_.MatchesURL(url))
    339     return CreateAvailability(INVALID_URL, url);
    340 
    341   return CreateAvailability(IS_AVAILABLE);
    342 }
    343 
    344 std::string SimpleFeature::GetAvailabilityMessage(
    345     AvailabilityResult result, Manifest::Type type, const GURL& url) const {
    346   switch (result) {
    347     case IS_AVAILABLE:
    348       return std::string();
    349     case NOT_FOUND_IN_WHITELIST:
    350       return base::StringPrintf(
    351           "'%s' is not allowed for specified extension ID.",
    352           name().c_str());
    353     case INVALID_URL:
    354       return base::StringPrintf("'%s' is not allowed on %s.",
    355                                 name().c_str(), url.spec().c_str());
    356     case INVALID_TYPE: {
    357       std::string allowed_type_names;
    358       // Turn the set of allowed types into a vector so that it's easier to
    359       // inject the appropriate separator into the display string.
    360       std::vector<Manifest::Type> extension_types(
    361           extension_types_.begin(), extension_types_.end());
    362       for (size_t i = 0; i < extension_types.size(); i++) {
    363         // Pluralize type name.
    364         allowed_type_names += GetDisplayTypeName(extension_types[i]) + "s";
    365         if (i == extension_types_.size() - 2) {
    366           allowed_type_names += " and ";
    367         } else if (i != extension_types_.size() - 1) {
    368           allowed_type_names += ", ";
    369         }
    370       }
    371 
    372       return base::StringPrintf(
    373           "'%s' is only allowed for %s, and this is a %s.",
    374           name().c_str(),
    375           allowed_type_names.c_str(),
    376           GetDisplayTypeName(type).c_str());
    377     }
    378     case INVALID_CONTEXT:
    379       return base::StringPrintf(
    380           "'%s' is not allowed for specified context type content script, "
    381           " extension page, web page, etc.).",
    382           name().c_str());
    383     case INVALID_LOCATION:
    384       return base::StringPrintf(
    385           "'%s' is not allowed for specified install location.",
    386           name().c_str());
    387     case INVALID_PLATFORM:
    388       return base::StringPrintf(
    389           "'%s' is not allowed for specified platform.",
    390           name().c_str());
    391     case INVALID_MIN_MANIFEST_VERSION:
    392       return base::StringPrintf(
    393           "'%s' requires manifest version of at least %d.",
    394           name().c_str(),
    395           min_manifest_version_);
    396     case INVALID_MAX_MANIFEST_VERSION:
    397       return base::StringPrintf(
    398           "'%s' requires manifest version of %d or lower.",
    399           name().c_str(),
    400           max_manifest_version_);
    401     case NOT_PRESENT:
    402       return base::StringPrintf(
    403           "'%s' requires a different Feature that is not present.",
    404           name().c_str());
    405     case UNSUPPORTED_CHANNEL:
    406       return base::StringPrintf(
    407           "'%s' requires Google Chrome %s channel or newer, and this is the "
    408               "%s channel.",
    409           name().c_str(),
    410           GetChannelName(channel_).c_str(),
    411           GetChannelName(GetCurrentChannel()).c_str());
    412   }
    413 
    414   NOTREACHED();
    415   return std::string();
    416 }
    417 
    418 Feature::Availability SimpleFeature::CreateAvailability(
    419     AvailabilityResult result) const {
    420   return Availability(
    421       result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL()));
    422 }
    423 
    424 Feature::Availability SimpleFeature::CreateAvailability(
    425     AvailabilityResult result, Manifest::Type type) const {
    426   return Availability(result, GetAvailabilityMessage(result, type, GURL()));
    427 }
    428 
    429 Feature::Availability SimpleFeature::CreateAvailability(
    430     AvailabilityResult result,
    431     const GURL& url) const {
    432   return Availability(
    433       result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, url));
    434 }
    435 
    436 std::set<Feature::Context>* SimpleFeature::GetContexts() {
    437   return &contexts_;
    438 }
    439 
    440 bool SimpleFeature::IsInternal() const {
    441   NOTREACHED();
    442   return false;
    443 }
    444 
    445 bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id) const {
    446   // Belt-and-suspenders philosophy here. We should be pretty confident by this
    447   // point that we've validated the extension ID format, but in case something
    448   // slips through, we avoid a class of attack where creative ID manipulation
    449   // leads to hash collisions.
    450   if (extension_id.length() != 32)  // 128 bits / 4 = 32 mpdecimal characters
    451     return false;
    452 
    453   if (whitelist_.find(extension_id) != whitelist_.end() ||
    454       whitelist_.find(HashExtensionId(extension_id)) != whitelist_.end())
    455     return true;
    456 
    457   return false;
    458 }
    459 
    460 }  // namespace extensions
    461