Home | History | Annotate | Download | only in features
      1 // Copyright 2014 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 "extensions/common/features/simple_feature.h"
      6 
      7 #include <map>
      8 #include <vector>
      9 
     10 #include "base/command_line.h"
     11 #include "base/debug/alias.h"
     12 #include "base/lazy_instance.h"
     13 #include "base/sha1.h"
     14 #include "base/strings/string_number_conversions.h"
     15 #include "base/strings/string_util.h"
     16 #include "base/strings/stringprintf.h"
     17 #include "extensions/common/switches.h"
     18 
     19 namespace extensions {
     20 
     21 namespace {
     22 
     23 struct Mappings {
     24   Mappings() {
     25     extension_types["extension"] = Manifest::TYPE_EXTENSION;
     26     extension_types["theme"] = Manifest::TYPE_THEME;
     27     extension_types["legacy_packaged_app"] = Manifest::TYPE_LEGACY_PACKAGED_APP;
     28     extension_types["hosted_app"] = Manifest::TYPE_HOSTED_APP;
     29     extension_types["platform_app"] = Manifest::TYPE_PLATFORM_APP;
     30     extension_types["shared_module"] = Manifest::TYPE_SHARED_MODULE;
     31 
     32     contexts["blessed_extension"] = Feature::BLESSED_EXTENSION_CONTEXT;
     33     contexts["unblessed_extension"] = Feature::UNBLESSED_EXTENSION_CONTEXT;
     34     contexts["content_script"] = Feature::CONTENT_SCRIPT_CONTEXT;
     35     contexts["web_page"] = Feature::WEB_PAGE_CONTEXT;
     36     contexts["blessed_web_page"] = Feature::BLESSED_WEB_PAGE_CONTEXT;
     37 
     38     locations["component"] = SimpleFeature::COMPONENT_LOCATION;
     39     locations["policy"] = SimpleFeature::POLICY_LOCATION;
     40 
     41     platforms["chromeos"] = Feature::CHROMEOS_PLATFORM;
     42     platforms["linux"] = Feature::LINUX_PLATFORM;
     43     platforms["mac"] = Feature::MACOSX_PLATFORM;
     44     platforms["win"] = Feature::WIN_PLATFORM;
     45   }
     46 
     47   std::map<std::string, Manifest::Type> extension_types;
     48   std::map<std::string, Feature::Context> contexts;
     49   std::map<std::string, SimpleFeature::Location> locations;
     50   std::map<std::string, Feature::Platform> platforms;
     51 };
     52 
     53 base::LazyInstance<Mappings> g_mappings = LAZY_INSTANCE_INITIALIZER;
     54 
     55 // TODO(aa): Can we replace all this manual parsing with JSON schema stuff?
     56 
     57 void ParseSet(const base::DictionaryValue* value,
     58               const std::string& property,
     59               std::set<std::string>* set) {
     60   const base::ListValue* list_value = NULL;
     61   if (!value->GetList(property, &list_value))
     62     return;
     63 
     64   set->clear();
     65   for (size_t i = 0; i < list_value->GetSize(); ++i) {
     66     std::string str_val;
     67     CHECK(list_value->GetString(i, &str_val)) << property << " " << i;
     68     set->insert(str_val);
     69   }
     70 }
     71 
     72 template<typename T>
     73 void ParseEnum(const std::string& string_value,
     74                T* enum_value,
     75                const std::map<std::string, T>& mapping) {
     76   typename std::map<std::string, T>::const_iterator iter =
     77       mapping.find(string_value);
     78   if (iter == mapping.end()) {
     79     // For http://crbug.com/365192.
     80     char minidump[256];
     81     base::debug::Alias(&minidump);
     82     base::snprintf(minidump, arraysize(minidump),
     83         "e::simple_feature.cc:%d:\"%s\"", __LINE__, string_value.c_str());
     84     CHECK(false) << string_value;
     85   }
     86   *enum_value = iter->second;
     87 }
     88 
     89 template<typename T>
     90 void ParseEnum(const base::DictionaryValue* value,
     91                const std::string& property,
     92                T* enum_value,
     93                const std::map<std::string, T>& mapping) {
     94   std::string string_value;
     95   if (!value->GetString(property, &string_value))
     96     return;
     97 
     98   ParseEnum(string_value, enum_value, mapping);
     99 }
    100 
    101 template<typename T>
    102 void ParseEnumSet(const base::DictionaryValue* value,
    103                   const std::string& property,
    104                   std::set<T>* enum_set,
    105                   const std::map<std::string, T>& mapping) {
    106   if (!value->HasKey(property))
    107     return;
    108 
    109   enum_set->clear();
    110 
    111   std::string property_string;
    112   if (value->GetString(property, &property_string)) {
    113     if (property_string == "all") {
    114       for (typename std::map<std::string, T>::const_iterator j =
    115                mapping.begin(); j != mapping.end(); ++j) {
    116         enum_set->insert(j->second);
    117       }
    118     }
    119     return;
    120   }
    121 
    122   std::set<std::string> string_set;
    123   ParseSet(value, property, &string_set);
    124   for (std::set<std::string>::iterator iter = string_set.begin();
    125        iter != string_set.end(); ++iter) {
    126     T enum_value = static_cast<T>(0);
    127     ParseEnum(*iter, &enum_value, mapping);
    128     enum_set->insert(enum_value);
    129   }
    130 }
    131 
    132 void ParseURLPatterns(const base::DictionaryValue* value,
    133                       const std::string& key,
    134                       URLPatternSet* set) {
    135   const base::ListValue* matches = NULL;
    136   if (value->GetList(key, &matches)) {
    137     set->ClearPatterns();
    138     for (size_t i = 0; i < matches->GetSize(); ++i) {
    139       std::string pattern;
    140       CHECK(matches->GetString(i, &pattern));
    141       set->AddPattern(URLPattern(URLPattern::SCHEME_ALL, pattern));
    142     }
    143   }
    144 }
    145 
    146 // Gets a human-readable name for the given extension type, suitable for giving
    147 // to developers in an error message.
    148 std::string GetDisplayName(Manifest::Type type) {
    149   switch (type) {
    150     case Manifest::TYPE_UNKNOWN:
    151       return "unknown";
    152     case Manifest::TYPE_EXTENSION:
    153       return "extension";
    154     case Manifest::TYPE_HOSTED_APP:
    155       return "hosted app";
    156     case Manifest::TYPE_LEGACY_PACKAGED_APP:
    157       return "legacy packaged app";
    158     case Manifest::TYPE_PLATFORM_APP:
    159       return "packaged app";
    160     case Manifest::TYPE_THEME:
    161       return "theme";
    162     case Manifest::TYPE_USER_SCRIPT:
    163       return "user script";
    164     case Manifest::TYPE_SHARED_MODULE:
    165       return "shared module";
    166     case Manifest::NUM_LOAD_TYPES:
    167       NOTREACHED();
    168   }
    169   NOTREACHED();
    170   return "";
    171 }
    172 
    173 // Gets a human-readable name for the given context type, suitable for giving
    174 // to developers in an error message.
    175 std::string GetDisplayName(Feature::Context context) {
    176   switch (context) {
    177     case Feature::UNSPECIFIED_CONTEXT:
    178       return "unknown";
    179     case Feature::BLESSED_EXTENSION_CONTEXT:
    180       // "privileged" is vague but hopefully the developer will understand that
    181       // means background or app window.
    182       return "privileged page";
    183     case Feature::UNBLESSED_EXTENSION_CONTEXT:
    184       // "iframe" is a bit of a lie/oversimplification, but that's the most
    185       // common unblessed context.
    186       return "extension iframe";
    187     case Feature::CONTENT_SCRIPT_CONTEXT:
    188       return "content script";
    189     case Feature::WEB_PAGE_CONTEXT:
    190       return "web page";
    191     case Feature::BLESSED_WEB_PAGE_CONTEXT:
    192       return "hosted app";
    193   }
    194   NOTREACHED();
    195   return "";
    196 }
    197 
    198 // Gets a human-readable list of the display names (pluralized, comma separated
    199 // with the "and" in the correct place) for each of |enum_types|.
    200 template <typename EnumType>
    201 std::string ListDisplayNames(const std::vector<EnumType> enum_types) {
    202   std::string display_name_list;
    203   for (size_t i = 0; i < enum_types.size(); ++i) {
    204     // Pluralize type name.
    205     display_name_list += GetDisplayName(enum_types[i]) + "s";
    206     // Comma-separate entries, with an Oxford comma if there is more than 2
    207     // total entries.
    208     if (enum_types.size() > 2) {
    209       if (i < enum_types.size() - 2)
    210         display_name_list += ", ";
    211       else if (i == enum_types.size() - 2)
    212         display_name_list += ", and ";
    213     } else if (enum_types.size() == 2 && i == 0) {
    214       display_name_list += " and ";
    215     }
    216   }
    217   return display_name_list;
    218 }
    219 
    220 std::string HashExtensionId(const std::string& extension_id) {
    221   const std::string id_hash = base::SHA1HashString(extension_id);
    222   DCHECK(id_hash.length() == base::kSHA1Length);
    223   return base::HexEncode(id_hash.c_str(), id_hash.length());
    224 }
    225 
    226 }  // namespace
    227 
    228 SimpleFeature::SimpleFeature()
    229     : location_(UNSPECIFIED_LOCATION),
    230       min_manifest_version_(0),
    231       max_manifest_version_(0),
    232       has_parent_(false),
    233       component_extensions_auto_granted_(true) {}
    234 
    235 SimpleFeature::~SimpleFeature() {}
    236 
    237 void SimpleFeature::AddFilter(scoped_ptr<SimpleFeatureFilter> filter) {
    238   filters_.push_back(make_linked_ptr(filter.release()));
    239 }
    240 
    241 std::string SimpleFeature::Parse(const base::DictionaryValue* value) {
    242   ParseURLPatterns(value, "matches", &matches_);
    243   ParseSet(value, "blacklist", &blacklist_);
    244   ParseSet(value, "whitelist", &whitelist_);
    245   ParseSet(value, "dependencies", &dependencies_);
    246   ParseEnumSet<Manifest::Type>(value, "extension_types", &extension_types_,
    247                                 g_mappings.Get().extension_types);
    248   ParseEnumSet<Context>(value, "contexts", &contexts_,
    249                         g_mappings.Get().contexts);
    250   ParseEnum<Location>(value, "location", &location_,
    251                       g_mappings.Get().locations);
    252   ParseEnumSet<Platform>(value, "platforms", &platforms_,
    253                          g_mappings.Get().platforms);
    254   value->GetInteger("min_manifest_version", &min_manifest_version_);
    255   value->GetInteger("max_manifest_version", &max_manifest_version_);
    256 
    257   no_parent_ = false;
    258   value->GetBoolean("noparent", &no_parent_);
    259 
    260   component_extensions_auto_granted_ = true;
    261   value->GetBoolean("component_extensions_auto_granted",
    262                     &component_extensions_auto_granted_);
    263 
    264   if (matches_.is_empty() && contexts_.count(WEB_PAGE_CONTEXT) != 0) {
    265     return name() + ": Allowing web_page contexts requires supplying a value " +
    266         "for matches.";
    267   }
    268 
    269   for (FilterList::iterator filter_iter = filters_.begin();
    270        filter_iter != filters_.end();
    271        ++filter_iter) {
    272     std::string result = (*filter_iter)->Parse(value);
    273     if (!result.empty()) {
    274       return result;
    275     }
    276   }
    277 
    278   return std::string();
    279 }
    280 
    281 Feature::Availability SimpleFeature::IsAvailableToManifest(
    282     const std::string& extension_id,
    283     Manifest::Type type,
    284     Manifest::Location location,
    285     int manifest_version,
    286     Platform platform) const {
    287   // Check extension type first to avoid granting platform app permissions
    288   // to component extensions.
    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 (IsIdInBlacklist(extension_id))
    299     return CreateAvailability(FOUND_IN_BLACKLIST, type);
    300 
    301   // TODO(benwells): don't grant all component extensions.
    302   // See http://crbug.com/370375 for more details.
    303   // Component extensions can access any feature.
    304   // NOTE: Deliberately does not match EXTERNAL_COMPONENT.
    305   if (component_extensions_auto_granted_ && location == Manifest::COMPONENT)
    306     return CreateAvailability(IS_AVAILABLE, type);
    307 
    308   if (!whitelist_.empty()) {
    309     if (!IsIdInWhitelist(extension_id)) {
    310       // TODO(aa): This is gross. There should be a better way to test the
    311       // whitelist.
    312       CommandLine* command_line = CommandLine::ForCurrentProcess();
    313       if (!command_line->HasSwitch(switches::kWhitelistedExtensionID))
    314         return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
    315 
    316       std::string whitelist_switch_value =
    317           CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
    318               switches::kWhitelistedExtensionID);
    319       if (extension_id != whitelist_switch_value)
    320         return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
    321     }
    322   }
    323 
    324   if (!MatchesManifestLocation(location))
    325     return CreateAvailability(INVALID_LOCATION, type);
    326 
    327   if (!platforms_.empty() &&
    328       platforms_.find(platform) == platforms_.end())
    329     return CreateAvailability(INVALID_PLATFORM, type);
    330 
    331   if (min_manifest_version_ != 0 && manifest_version < min_manifest_version_)
    332     return CreateAvailability(INVALID_MIN_MANIFEST_VERSION, type);
    333 
    334   if (max_manifest_version_ != 0 && manifest_version > max_manifest_version_)
    335     return CreateAvailability(INVALID_MAX_MANIFEST_VERSION, type);
    336 
    337   for (FilterList::const_iterator filter_iter = filters_.begin();
    338        filter_iter != filters_.end();
    339        ++filter_iter) {
    340     Availability availability = (*filter_iter)->IsAvailableToManifest(
    341         extension_id, type, location, manifest_version, platform);
    342     if (!availability.is_available())
    343       return availability;
    344   }
    345 
    346   return CreateAvailability(IS_AVAILABLE, type);
    347 }
    348 
    349 Feature::Availability SimpleFeature::IsAvailableToContext(
    350     const Extension* extension,
    351     SimpleFeature::Context context,
    352     const GURL& url,
    353     SimpleFeature::Platform platform) const {
    354   if (extension) {
    355     Availability result = IsAvailableToManifest(extension->id(),
    356                                                 extension->GetType(),
    357                                                 extension->location(),
    358                                                 extension->manifest_version(),
    359                                                 platform);
    360     if (!result.is_available())
    361       return result;
    362   }
    363 
    364   if (!contexts_.empty() && contexts_.find(context) == contexts_.end())
    365     return CreateAvailability(INVALID_CONTEXT, context);
    366 
    367   if (!matches_.is_empty() && !matches_.MatchesURL(url))
    368     return CreateAvailability(INVALID_URL, url);
    369 
    370   for (FilterList::const_iterator filter_iter = filters_.begin();
    371        filter_iter != filters_.end();
    372        ++filter_iter) {
    373     Availability availability =
    374         (*filter_iter)->IsAvailableToContext(extension, context, url, platform);
    375     if (!availability.is_available())
    376       return availability;
    377   }
    378 
    379   return CreateAvailability(IS_AVAILABLE);
    380 }
    381 
    382 std::string SimpleFeature::GetAvailabilityMessage(
    383     AvailabilityResult result,
    384     Manifest::Type type,
    385     const GURL& url,
    386     Context context) const {
    387   switch (result) {
    388     case IS_AVAILABLE:
    389       return std::string();
    390     case NOT_FOUND_IN_WHITELIST:
    391     case FOUND_IN_BLACKLIST:
    392       return base::StringPrintf(
    393           "'%s' is not allowed for specified extension ID.",
    394           name().c_str());
    395     case INVALID_URL:
    396       return base::StringPrintf("'%s' is not allowed on %s.",
    397                                 name().c_str(), url.spec().c_str());
    398     case INVALID_TYPE:
    399       return base::StringPrintf(
    400           "'%s' is only allowed for %s, but this is a %s.",
    401           name().c_str(),
    402           ListDisplayNames(std::vector<Manifest::Type>(
    403               extension_types_.begin(), extension_types_.end())).c_str(),
    404           GetDisplayName(type).c_str());
    405     case INVALID_CONTEXT:
    406       return base::StringPrintf(
    407           "'%s' is only allowed to run in %s, but this is a %s",
    408           name().c_str(),
    409           ListDisplayNames(std::vector<Context>(
    410               contexts_.begin(), contexts_.end())).c_str(),
    411           GetDisplayName(context).c_str());
    412     case INVALID_LOCATION:
    413       return base::StringPrintf(
    414           "'%s' is not allowed for specified install location.",
    415           name().c_str());
    416     case INVALID_PLATFORM:
    417       return base::StringPrintf(
    418           "'%s' is not allowed for specified platform.",
    419           name().c_str());
    420     case INVALID_MIN_MANIFEST_VERSION:
    421       return base::StringPrintf(
    422           "'%s' requires manifest version of at least %d.",
    423           name().c_str(),
    424           min_manifest_version_);
    425     case INVALID_MAX_MANIFEST_VERSION:
    426       return base::StringPrintf(
    427           "'%s' requires manifest version of %d or lower.",
    428           name().c_str(),
    429           max_manifest_version_);
    430     case NOT_PRESENT:
    431       return base::StringPrintf(
    432           "'%s' requires a different Feature that is not present.",
    433           name().c_str());
    434     case UNSUPPORTED_CHANNEL:
    435       return base::StringPrintf(
    436           "'%s' is unsupported in this version of the platform.",
    437           name().c_str());
    438   }
    439 
    440   NOTREACHED();
    441   return std::string();
    442 }
    443 
    444 Feature::Availability SimpleFeature::CreateAvailability(
    445     AvailabilityResult result) const {
    446   return Availability(
    447       result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
    448                                      UNSPECIFIED_CONTEXT));
    449 }
    450 
    451 Feature::Availability SimpleFeature::CreateAvailability(
    452     AvailabilityResult result, Manifest::Type type) const {
    453   return Availability(result, GetAvailabilityMessage(result, type, GURL(),
    454                                                      UNSPECIFIED_CONTEXT));
    455 }
    456 
    457 Feature::Availability SimpleFeature::CreateAvailability(
    458     AvailabilityResult result,
    459     const GURL& url) const {
    460   return Availability(
    461       result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, url,
    462                                      UNSPECIFIED_CONTEXT));
    463 }
    464 
    465 Feature::Availability SimpleFeature::CreateAvailability(
    466     AvailabilityResult result,
    467     Context context) const {
    468   return Availability(
    469       result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
    470                                      context));
    471 }
    472 
    473 std::set<Feature::Context>* SimpleFeature::GetContexts() {
    474   return &contexts_;
    475 }
    476 
    477 bool SimpleFeature::IsInternal() const {
    478   return false;
    479 }
    480 
    481 bool SimpleFeature::IsBlockedInServiceWorker() const { return false; }
    482 
    483 bool SimpleFeature::IsIdInBlacklist(const std::string& extension_id) const {
    484   return IsIdInList(extension_id, blacklist_);
    485 }
    486 
    487 bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id) const {
    488   return IsIdInList(extension_id, whitelist_);
    489 }
    490 
    491 // static
    492 bool SimpleFeature::IsIdInList(const std::string& extension_id,
    493                                const std::set<std::string>& list) {
    494   // Belt-and-suspenders philosophy here. We should be pretty confident by this
    495   // point that we've validated the extension ID format, but in case something
    496   // slips through, we avoid a class of attack where creative ID manipulation
    497   // leads to hash collisions.
    498   if (extension_id.length() != 32)  // 128 bits / 4 = 32 mpdecimal characters
    499     return false;
    500 
    501   if (list.find(extension_id) != list.end() ||
    502       list.find(HashExtensionId(extension_id)) != list.end()) {
    503     return true;
    504   }
    505 
    506   return false;
    507 }
    508 
    509 bool SimpleFeature::MatchesManifestLocation(
    510     Manifest::Location manifest_location) const {
    511   switch (location_) {
    512     case SimpleFeature::UNSPECIFIED_LOCATION:
    513       return true;
    514     case SimpleFeature::COMPONENT_LOCATION:
    515       // TODO(kalman/asargent): Should this include EXTERNAL_COMPONENT too?
    516       return manifest_location == Manifest::COMPONENT;
    517     case SimpleFeature::POLICY_LOCATION:
    518       return manifest_location == Manifest::EXTERNAL_POLICY ||
    519              manifest_location == Manifest::EXTERNAL_POLICY_DOWNLOAD;
    520   }
    521   NOTREACHED();
    522   return false;
    523 }
    524 
    525 }  // namespace extensions
    526