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.
5 #include "extensions/common/features/simple_feature.h"
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"
19 namespace extensions {
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;
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;
38 locations["component"] = SimpleFeature::COMPONENT_LOCATION;
39 locations["policy"] = SimpleFeature::POLICY_LOCATION;
41 platforms["chromeos"] = Feature::CHROMEOS_PLATFORM;
42 platforms["linux"] = Feature::LINUX_PLATFORM;
43 platforms["mac"] = Feature::MACOSX_PLATFORM;
44 platforms["win"] = Feature::WIN_PLATFORM;
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;
53 base::LazyInstance<Mappings> g_mappings = LAZY_INSTANCE_INITIALIZER;
55 // TODO(aa): Can we replace all this manual parsing with JSON schema stuff?
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))
65 for (size_t i = 0; i < list_value->GetSize(); ++i) {
67 CHECK(list_value->GetString(i, &str_val)) << property << " " << i;
73 void ParseEnum(const std::string& string_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.
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;
86 *enum_value = iter->second;
90 void ParseEnum(const base::DictionaryValue* value,
91 const std::string& property,
93 const std::map<std::string, T>& mapping) {
94 std::string string_value;
95 if (!value->GetString(property, &string_value))
98 ParseEnum(string_value, enum_value, mapping);
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))
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);
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);
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) {
140 CHECK(matches->GetString(i, &pattern));
141 set->AddPattern(URLPattern(URLPattern::SCHEME_ALL, pattern));
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) {
150 case Manifest::TYPE_UNKNOWN:
152 case Manifest::TYPE_EXTENSION:
154 case Manifest::TYPE_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:
162 case Manifest::TYPE_USER_SCRIPT:
163 return "user script";
164 case Manifest::TYPE_SHARED_MODULE:
165 return "shared module";
171 // Gets a human-readable name for the given context type, suitable for giving
172 // to developers in an error message.
173 std::string GetDisplayName(Feature::Context context) {
175 case Feature::UNSPECIFIED_CONTEXT:
177 case Feature::BLESSED_EXTENSION_CONTEXT:
178 // "privileged" is vague but hopefully the developer will understand that
179 // means background or app window.
180 return "privileged page";
181 case Feature::UNBLESSED_EXTENSION_CONTEXT:
182 // "iframe" is a bit of a lie/oversimplification, but that's the most
183 // common unblessed context.
184 return "extension iframe";
185 case Feature::CONTENT_SCRIPT_CONTEXT:
186 return "content script";
187 case Feature::WEB_PAGE_CONTEXT:
189 case Feature::BLESSED_WEB_PAGE_CONTEXT:
196 // Gets a human-readable list of the display names (pluralized, comma separated
197 // with the "and" in the correct place) for each of |enum_types|.
198 template <typename EnumType>
199 std::string ListDisplayNames(const std::vector<EnumType> enum_types) {
200 std::string display_name_list;
201 for (size_t i = 0; i < enum_types.size(); ++i) {
202 // Pluralize type name.
203 display_name_list += GetDisplayName(enum_types[i]) + "s";
204 // Comma-separate entries, with an Oxford comma if there is more than 2
206 if (enum_types.size() > 2) {
207 if (i < enum_types.size() - 2)
208 display_name_list += ", ";
209 else if (i == enum_types.size() - 2)
210 display_name_list += ", and ";
211 } else if (enum_types.size() == 2 && i == 0) {
212 display_name_list += " and ";
215 return display_name_list;
218 std::string HashExtensionId(const std::string& extension_id) {
219 const std::string id_hash = base::SHA1HashString(extension_id);
220 DCHECK(id_hash.length() == base::kSHA1Length);
221 return base::HexEncode(id_hash.c_str(), id_hash.length());
226 SimpleFeature::SimpleFeature()
227 : location_(UNSPECIFIED_LOCATION),
228 min_manifest_version_(0),
229 max_manifest_version_(0),
231 component_extensions_auto_granted_(true) {}
233 SimpleFeature::~SimpleFeature() {}
235 void SimpleFeature::AddFilter(scoped_ptr<SimpleFeatureFilter> filter) {
236 filters_.push_back(make_linked_ptr(filter.release()));
239 std::string SimpleFeature::Parse(const base::DictionaryValue* value) {
240 ParseURLPatterns(value, "matches", &matches_);
241 ParseSet(value, "blacklist", &blacklist_);
242 ParseSet(value, "whitelist", &whitelist_);
243 ParseSet(value, "dependencies", &dependencies_);
244 ParseEnumSet<Manifest::Type>(value, "extension_types", &extension_types_,
245 g_mappings.Get().extension_types);
246 ParseEnumSet<Context>(value, "contexts", &contexts_,
247 g_mappings.Get().contexts);
248 ParseEnum<Location>(value, "location", &location_,
249 g_mappings.Get().locations);
250 ParseEnumSet<Platform>(value, "platforms", &platforms_,
251 g_mappings.Get().platforms);
252 value->GetInteger("min_manifest_version", &min_manifest_version_);
253 value->GetInteger("max_manifest_version", &max_manifest_version_);
256 value->GetBoolean("noparent", &no_parent_);
258 component_extensions_auto_granted_ = true;
259 value->GetBoolean("component_extensions_auto_granted",
260 &component_extensions_auto_granted_);
262 if (matches_.is_empty() && contexts_.count(WEB_PAGE_CONTEXT) != 0) {
263 return name() + ": Allowing web_page contexts requires supplying a value " +
267 for (FilterList::iterator filter_iter = filters_.begin();
268 filter_iter != filters_.end();
270 std::string result = (*filter_iter)->Parse(value);
271 if (!result.empty()) {
276 return std::string();
279 Feature::Availability SimpleFeature::IsAvailableToManifest(
280 const std::string& extension_id,
282 Manifest::Location location,
283 int manifest_version,
284 Platform platform) const {
285 // Check extension type first to avoid granting platform app permissions
286 // to component extensions.
287 // HACK(kalman): user script -> extension. Solve this in a more generic way
288 // when we compile feature files.
289 Manifest::Type type_to_check = (type == Manifest::TYPE_USER_SCRIPT) ?
290 Manifest::TYPE_EXTENSION : type;
291 if (!extension_types_.empty() &&
292 extension_types_.find(type_to_check) == extension_types_.end()) {
293 return CreateAvailability(INVALID_TYPE, type);
296 if (IsIdInBlacklist(extension_id))
297 return CreateAvailability(FOUND_IN_BLACKLIST, type);
299 // TODO(benwells): don't grant all component extensions.
300 // See http://crbug.com/370375 for more details.
301 // Component extensions can access any feature.
302 // NOTE: Deliberately does not match EXTERNAL_COMPONENT.
303 if (component_extensions_auto_granted_ && location == Manifest::COMPONENT)
304 return CreateAvailability(IS_AVAILABLE, type);
306 if (!whitelist_.empty()) {
307 if (!IsIdInWhitelist(extension_id)) {
308 // TODO(aa): This is gross. There should be a better way to test the
310 CommandLine* command_line = CommandLine::ForCurrentProcess();
311 if (!command_line->HasSwitch(switches::kWhitelistedExtensionID))
312 return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
314 std::string whitelist_switch_value =
315 CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
316 switches::kWhitelistedExtensionID);
317 if (extension_id != whitelist_switch_value)
318 return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
322 if (!MatchesManifestLocation(location))
323 return CreateAvailability(INVALID_LOCATION, type);
325 if (!platforms_.empty() &&
326 platforms_.find(platform) == platforms_.end())
327 return CreateAvailability(INVALID_PLATFORM, type);
329 if (min_manifest_version_ != 0 && manifest_version < min_manifest_version_)
330 return CreateAvailability(INVALID_MIN_MANIFEST_VERSION, type);
332 if (max_manifest_version_ != 0 && manifest_version > max_manifest_version_)
333 return CreateAvailability(INVALID_MAX_MANIFEST_VERSION, type);
335 for (FilterList::const_iterator filter_iter = filters_.begin();
336 filter_iter != filters_.end();
338 Availability availability = (*filter_iter)->IsAvailableToManifest(
339 extension_id, type, location, manifest_version, platform);
340 if (!availability.is_available())
344 return CreateAvailability(IS_AVAILABLE, type);
347 Feature::Availability SimpleFeature::IsAvailableToContext(
348 const Extension* extension,
349 SimpleFeature::Context context,
351 SimpleFeature::Platform platform) const {
353 Availability result = IsAvailableToManifest(extension->id(),
354 extension->GetType(),
355 extension->location(),
356 extension->manifest_version(),
358 if (!result.is_available())
362 if (!contexts_.empty() && contexts_.find(context) == contexts_.end())
363 return CreateAvailability(INVALID_CONTEXT, context);
365 if (!matches_.is_empty() && !matches_.MatchesURL(url))
366 return CreateAvailability(INVALID_URL, url);
368 for (FilterList::const_iterator filter_iter = filters_.begin();
369 filter_iter != filters_.end();
371 Availability availability =
372 (*filter_iter)->IsAvailableToContext(extension, context, url, platform);
373 if (!availability.is_available())
377 return CreateAvailability(IS_AVAILABLE);
380 std::string SimpleFeature::GetAvailabilityMessage(
381 AvailabilityResult result,
384 Context context) const {
387 return std::string();
388 case NOT_FOUND_IN_WHITELIST:
389 case FOUND_IN_BLACKLIST:
390 return base::StringPrintf(
391 "'%s' is not allowed for specified extension ID.",
394 return base::StringPrintf("'%s' is not allowed on %s.",
395 name().c_str(), url.spec().c_str());
397 return base::StringPrintf(
398 "'%s' is only allowed for %s, but this is a %s.",
400 ListDisplayNames(std::vector<Manifest::Type>(
401 extension_types_.begin(), extension_types_.end())).c_str(),
402 GetDisplayName(type).c_str());
403 case INVALID_CONTEXT:
404 return base::StringPrintf(
405 "'%s' is only allowed to run in %s, but this is a %s",
407 ListDisplayNames(std::vector<Context>(
408 contexts_.begin(), contexts_.end())).c_str(),
409 GetDisplayName(context).c_str());
410 case INVALID_LOCATION:
411 return base::StringPrintf(
412 "'%s' is not allowed for specified install location.",
414 case INVALID_PLATFORM:
415 return base::StringPrintf(
416 "'%s' is not allowed for specified platform.",
418 case INVALID_MIN_MANIFEST_VERSION:
419 return base::StringPrintf(
420 "'%s' requires manifest version of at least %d.",
422 min_manifest_version_);
423 case INVALID_MAX_MANIFEST_VERSION:
424 return base::StringPrintf(
425 "'%s' requires manifest version of %d or lower.",
427 max_manifest_version_);
429 return base::StringPrintf(
430 "'%s' requires a different Feature that is not present.",
432 case UNSUPPORTED_CHANNEL:
433 return base::StringPrintf(
434 "'%s' is unsupported in this version of the platform.",
439 return std::string();
442 Feature::Availability SimpleFeature::CreateAvailability(
443 AvailabilityResult result) const {
445 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
446 UNSPECIFIED_CONTEXT));
449 Feature::Availability SimpleFeature::CreateAvailability(
450 AvailabilityResult result, Manifest::Type type) const {
451 return Availability(result, GetAvailabilityMessage(result, type, GURL(),
452 UNSPECIFIED_CONTEXT));
455 Feature::Availability SimpleFeature::CreateAvailability(
456 AvailabilityResult result,
457 const GURL& url) const {
459 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, url,
460 UNSPECIFIED_CONTEXT));
463 Feature::Availability SimpleFeature::CreateAvailability(
464 AvailabilityResult result,
465 Context context) const {
467 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
471 std::set<Feature::Context>* SimpleFeature::GetContexts() {
475 bool SimpleFeature::IsInternal() const {
479 bool SimpleFeature::IsBlockedInServiceWorker() const { return false; }
481 bool SimpleFeature::IsIdInBlacklist(const std::string& extension_id) const {
482 return IsIdInList(extension_id, blacklist_);
485 bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id) const {
486 return IsIdInList(extension_id, whitelist_);
490 bool SimpleFeature::IsIdInList(const std::string& extension_id,
491 const std::set<std::string>& list) {
492 // Belt-and-suspenders philosophy here. We should be pretty confident by this
493 // point that we've validated the extension ID format, but in case something
494 // slips through, we avoid a class of attack where creative ID manipulation
495 // leads to hash collisions.
496 if (extension_id.length() != 32) // 128 bits / 4 = 32 mpdecimal characters
499 if (list.find(extension_id) != list.end() ||
500 list.find(HashExtensionId(extension_id)) != list.end()) {
507 bool SimpleFeature::MatchesManifestLocation(
508 Manifest::Location manifest_location) const {
510 case SimpleFeature::UNSPECIFIED_LOCATION:
512 case SimpleFeature::COMPONENT_LOCATION:
513 // TODO(kalman/asargent): Should this include EXTERNAL_COMPONENT too?
514 return manifest_location == Manifest::COMPONENT;
515 case SimpleFeature::POLICY_LOCATION:
516 return manifest_location == Manifest::EXTERNAL_POLICY ||
517 manifest_location == Manifest::EXTERNAL_POLICY_DOWNLOAD;
523 } // namespace extensions