[BuildBreak] Linux: Fix g++ 4.8 compilation for error: multi-line comment [-Werror...
[chromium-blink-merge.git] / extensions / common / features / simple_feature.cc
blob155e76429be855f76dcd833b16a9799c47239c7c
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"
7 #include <map>
8 #include <vector>
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/debug/alias.h"
13 #include "base/lazy_instance.h"
14 #include "base/sha1.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "extensions/common/extension_api.h"
19 #include "extensions/common/features/feature_provider.h"
20 #include "extensions/common/switches.h"
22 namespace extensions {
24 namespace {
26 Feature::Availability IsAvailableToManifestForBind(
27 const std::string& extension_id,
28 Manifest::Type type,
29 Manifest::Location location,
30 int manifest_version,
31 Feature::Platform platform,
32 const Feature* feature) {
33 return feature->IsAvailableToManifest(
34 extension_id, type, location, manifest_version, platform);
37 Feature::Availability IsAvailableToContextForBind(const Extension* extension,
38 Feature::Context context,
39 const GURL& url,
40 Feature::Platform platform,
41 const Feature* feature) {
42 return feature->IsAvailableToContext(extension, context, url, platform);
45 struct Mappings {
46 Mappings() {
47 extension_types["extension"] = Manifest::TYPE_EXTENSION;
48 extension_types["theme"] = Manifest::TYPE_THEME;
49 extension_types["legacy_packaged_app"] = Manifest::TYPE_LEGACY_PACKAGED_APP;
50 extension_types["hosted_app"] = Manifest::TYPE_HOSTED_APP;
51 extension_types["platform_app"] = Manifest::TYPE_PLATFORM_APP;
52 extension_types["shared_module"] = Manifest::TYPE_SHARED_MODULE;
54 contexts["blessed_extension"] = Feature::BLESSED_EXTENSION_CONTEXT;
55 contexts["unblessed_extension"] = Feature::UNBLESSED_EXTENSION_CONTEXT;
56 contexts["content_script"] = Feature::CONTENT_SCRIPT_CONTEXT;
57 contexts["web_page"] = Feature::WEB_PAGE_CONTEXT;
58 contexts["blessed_web_page"] = Feature::BLESSED_WEB_PAGE_CONTEXT;
60 locations["component"] = SimpleFeature::COMPONENT_LOCATION;
61 locations["policy"] = SimpleFeature::POLICY_LOCATION;
63 platforms["chromeos"] = Feature::CHROMEOS_PLATFORM;
64 platforms["linux"] = Feature::LINUX_PLATFORM;
65 platforms["mac"] = Feature::MACOSX_PLATFORM;
66 platforms["win"] = Feature::WIN_PLATFORM;
69 std::map<std::string, Manifest::Type> extension_types;
70 std::map<std::string, Feature::Context> contexts;
71 std::map<std::string, SimpleFeature::Location> locations;
72 std::map<std::string, Feature::Platform> platforms;
75 base::LazyInstance<Mappings> g_mappings = LAZY_INSTANCE_INITIALIZER;
77 // TODO(aa): Can we replace all this manual parsing with JSON schema stuff?
79 void ParseSet(const base::DictionaryValue* value,
80 const std::string& property,
81 std::set<std::string>* set) {
82 const base::ListValue* list_value = NULL;
83 if (!value->GetList(property, &list_value))
84 return;
86 set->clear();
87 for (size_t i = 0; i < list_value->GetSize(); ++i) {
88 std::string str_val;
89 CHECK(list_value->GetString(i, &str_val)) << property << " " << i;
90 set->insert(str_val);
94 template<typename T>
95 void ParseEnum(const std::string& string_value,
96 T* enum_value,
97 const std::map<std::string, T>& mapping) {
98 typename std::map<std::string, T>::const_iterator iter =
99 mapping.find(string_value);
100 if (iter == mapping.end()) {
101 // For http://crbug.com/365192.
102 char minidump[256];
103 base::debug::Alias(&minidump);
104 base::snprintf(minidump, arraysize(minidump),
105 "e::simple_feature.cc:%d:\"%s\"", __LINE__, string_value.c_str());
106 CHECK(false) << string_value;
108 *enum_value = iter->second;
111 template<typename T>
112 void ParseEnum(const base::DictionaryValue* value,
113 const std::string& property,
114 T* enum_value,
115 const std::map<std::string, T>& mapping) {
116 std::string string_value;
117 if (!value->GetString(property, &string_value))
118 return;
120 ParseEnum(string_value, enum_value, mapping);
123 template<typename T>
124 void ParseEnumSet(const base::DictionaryValue* value,
125 const std::string& property,
126 std::set<T>* enum_set,
127 const std::map<std::string, T>& mapping) {
128 if (!value->HasKey(property))
129 return;
131 enum_set->clear();
133 std::string property_string;
134 if (value->GetString(property, &property_string)) {
135 if (property_string == "all") {
136 for (typename std::map<std::string, T>::const_iterator j =
137 mapping.begin(); j != mapping.end(); ++j) {
138 enum_set->insert(j->second);
141 return;
144 std::set<std::string> string_set;
145 ParseSet(value, property, &string_set);
146 for (std::set<std::string>::iterator iter = string_set.begin();
147 iter != string_set.end(); ++iter) {
148 T enum_value = static_cast<T>(0);
149 ParseEnum(*iter, &enum_value, mapping);
150 enum_set->insert(enum_value);
154 void ParseURLPatterns(const base::DictionaryValue* value,
155 const std::string& key,
156 URLPatternSet* set) {
157 const base::ListValue* matches = NULL;
158 if (value->GetList(key, &matches)) {
159 set->ClearPatterns();
160 for (size_t i = 0; i < matches->GetSize(); ++i) {
161 std::string pattern;
162 CHECK(matches->GetString(i, &pattern));
163 set->AddPattern(URLPattern(URLPattern::SCHEME_ALL, pattern));
168 // Gets a human-readable name for the given extension type, suitable for giving
169 // to developers in an error message.
170 std::string GetDisplayName(Manifest::Type type) {
171 switch (type) {
172 case Manifest::TYPE_UNKNOWN:
173 return "unknown";
174 case Manifest::TYPE_EXTENSION:
175 return "extension";
176 case Manifest::TYPE_HOSTED_APP:
177 return "hosted app";
178 case Manifest::TYPE_LEGACY_PACKAGED_APP:
179 return "legacy packaged app";
180 case Manifest::TYPE_PLATFORM_APP:
181 return "packaged app";
182 case Manifest::TYPE_THEME:
183 return "theme";
184 case Manifest::TYPE_USER_SCRIPT:
185 return "user script";
186 case Manifest::TYPE_SHARED_MODULE:
187 return "shared module";
188 case Manifest::NUM_LOAD_TYPES:
189 NOTREACHED();
191 NOTREACHED();
192 return "";
195 // Gets a human-readable name for the given context type, suitable for giving
196 // to developers in an error message.
197 std::string GetDisplayName(Feature::Context context) {
198 switch (context) {
199 case Feature::UNSPECIFIED_CONTEXT:
200 return "unknown";
201 case Feature::BLESSED_EXTENSION_CONTEXT:
202 // "privileged" is vague but hopefully the developer will understand that
203 // means background or app window.
204 return "privileged page";
205 case Feature::UNBLESSED_EXTENSION_CONTEXT:
206 // "iframe" is a bit of a lie/oversimplification, but that's the most
207 // common unblessed context.
208 return "extension iframe";
209 case Feature::CONTENT_SCRIPT_CONTEXT:
210 return "content script";
211 case Feature::WEB_PAGE_CONTEXT:
212 return "web page";
213 case Feature::BLESSED_WEB_PAGE_CONTEXT:
214 return "hosted app";
216 NOTREACHED();
217 return "";
220 // Gets a human-readable list of the display names (pluralized, comma separated
221 // with the "and" in the correct place) for each of |enum_types|.
222 template <typename EnumType>
223 std::string ListDisplayNames(const std::vector<EnumType> enum_types) {
224 std::string display_name_list;
225 for (size_t i = 0; i < enum_types.size(); ++i) {
226 // Pluralize type name.
227 display_name_list += GetDisplayName(enum_types[i]) + "s";
228 // Comma-separate entries, with an Oxford comma if there is more than 2
229 // total entries.
230 if (enum_types.size() > 2) {
231 if (i < enum_types.size() - 2)
232 display_name_list += ", ";
233 else if (i == enum_types.size() - 2)
234 display_name_list += ", and ";
235 } else if (enum_types.size() == 2 && i == 0) {
236 display_name_list += " and ";
239 return display_name_list;
242 std::string HashExtensionId(const std::string& extension_id) {
243 const std::string id_hash = base::SHA1HashString(extension_id);
244 DCHECK(id_hash.length() == base::kSHA1Length);
245 return base::HexEncode(id_hash.c_str(), id_hash.length());
248 } // namespace
250 SimpleFeature::SimpleFeature()
251 : location_(UNSPECIFIED_LOCATION),
252 min_manifest_version_(0),
253 max_manifest_version_(0),
254 has_parent_(false),
255 component_extensions_auto_granted_(true) {}
257 SimpleFeature::~SimpleFeature() {}
259 bool SimpleFeature::HasDependencies() {
260 return !dependencies_.empty();
263 void SimpleFeature::AddFilter(scoped_ptr<SimpleFeatureFilter> filter) {
264 filters_.push_back(make_linked_ptr(filter.release()));
267 std::string SimpleFeature::Parse(const base::DictionaryValue* value) {
268 ParseURLPatterns(value, "matches", &matches_);
269 ParseSet(value, "blacklist", &blacklist_);
270 ParseSet(value, "whitelist", &whitelist_);
271 ParseSet(value, "dependencies", &dependencies_);
272 ParseEnumSet<Manifest::Type>(value, "extension_types", &extension_types_,
273 g_mappings.Get().extension_types);
274 ParseEnumSet<Context>(value, "contexts", &contexts_,
275 g_mappings.Get().contexts);
276 ParseEnum<Location>(value, "location", &location_,
277 g_mappings.Get().locations);
278 ParseEnumSet<Platform>(value, "platforms", &platforms_,
279 g_mappings.Get().platforms);
280 value->GetInteger("min_manifest_version", &min_manifest_version_);
281 value->GetInteger("max_manifest_version", &max_manifest_version_);
283 no_parent_ = false;
284 value->GetBoolean("noparent", &no_parent_);
286 component_extensions_auto_granted_ = true;
287 value->GetBoolean("component_extensions_auto_granted",
288 &component_extensions_auto_granted_);
290 // NOTE: ideally we'd sanity check that "matches" can be specified if and
291 // only if there's a "web_page" context, but without (Simple)Features being
292 // aware of their own heirarchy this is impossible.
294 // For example, we might have feature "foo" available to "web_page" context
295 // and "matches" google.com/*. Then a sub-feature "foo.bar" might override
296 // "matches" to be chromium.org/*. That sub-feature doesn't need to specify
297 // "web_page" context because it's inherited, but we don't know that here.
299 for (FilterList::iterator filter_iter = filters_.begin();
300 filter_iter != filters_.end();
301 ++filter_iter) {
302 std::string result = (*filter_iter)->Parse(value);
303 if (!result.empty()) {
304 return result;
308 return std::string();
311 Feature::Availability SimpleFeature::IsAvailableToManifest(
312 const std::string& extension_id,
313 Manifest::Type type,
314 Manifest::Location location,
315 int manifest_version,
316 Platform platform) const {
317 // Check extension type first to avoid granting platform app permissions
318 // to component extensions.
319 // HACK(kalman): user script -> extension. Solve this in a more generic way
320 // when we compile feature files.
321 Manifest::Type type_to_check = (type == Manifest::TYPE_USER_SCRIPT) ?
322 Manifest::TYPE_EXTENSION : type;
323 if (!extension_types_.empty() &&
324 extension_types_.find(type_to_check) == extension_types_.end()) {
325 return CreateAvailability(INVALID_TYPE, type);
328 if (IsIdInBlacklist(extension_id))
329 return CreateAvailability(FOUND_IN_BLACKLIST, type);
331 // TODO(benwells): don't grant all component extensions.
332 // See http://crbug.com/370375 for more details.
333 // Component extensions can access any feature.
334 // NOTE: Deliberately does not match EXTERNAL_COMPONENT.
335 if (component_extensions_auto_granted_ && location == Manifest::COMPONENT)
336 return CreateAvailability(IS_AVAILABLE, type);
338 if (!whitelist_.empty()) {
339 if (!IsIdInWhitelist(extension_id)) {
340 // TODO(aa): This is gross. There should be a better way to test the
341 // whitelist.
342 CommandLine* command_line = CommandLine::ForCurrentProcess();
343 if (!command_line->HasSwitch(switches::kWhitelistedExtensionID))
344 return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
346 std::string whitelist_switch_value =
347 CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
348 switches::kWhitelistedExtensionID);
349 if (extension_id != whitelist_switch_value)
350 return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
354 if (!MatchesManifestLocation(location))
355 return CreateAvailability(INVALID_LOCATION, type);
357 if (!platforms_.empty() &&
358 platforms_.find(platform) == platforms_.end())
359 return CreateAvailability(INVALID_PLATFORM, type);
361 if (min_manifest_version_ != 0 && manifest_version < min_manifest_version_)
362 return CreateAvailability(INVALID_MIN_MANIFEST_VERSION, type);
364 if (max_manifest_version_ != 0 && manifest_version > max_manifest_version_)
365 return CreateAvailability(INVALID_MAX_MANIFEST_VERSION, type);
367 for (FilterList::const_iterator filter_iter = filters_.begin();
368 filter_iter != filters_.end();
369 ++filter_iter) {
370 Availability availability = (*filter_iter)->IsAvailableToManifest(
371 extension_id, type, location, manifest_version, platform);
372 if (!availability.is_available())
373 return availability;
376 return CheckDependencies(base::Bind(&IsAvailableToManifestForBind,
377 extension_id,
378 type,
379 location,
380 manifest_version,
381 platform));
384 Feature::Availability SimpleFeature::IsAvailableToContext(
385 const Extension* extension,
386 SimpleFeature::Context context,
387 const GURL& url,
388 SimpleFeature::Platform platform) const {
389 if (extension) {
390 Availability result = IsAvailableToManifest(extension->id(),
391 extension->GetType(),
392 extension->location(),
393 extension->manifest_version(),
394 platform);
395 if (!result.is_available())
396 return result;
399 if (!contexts_.empty() && contexts_.find(context) == contexts_.end())
400 return CreateAvailability(INVALID_CONTEXT, context);
402 if (context == WEB_PAGE_CONTEXT && !matches_.MatchesURL(url))
403 return CreateAvailability(INVALID_URL, url);
405 for (FilterList::const_iterator filter_iter = filters_.begin();
406 filter_iter != filters_.end();
407 ++filter_iter) {
408 Availability availability =
409 (*filter_iter)->IsAvailableToContext(extension, context, url, platform);
410 if (!availability.is_available())
411 return availability;
414 return CheckDependencies(base::Bind(
415 &IsAvailableToContextForBind, extension, context, url, platform));
418 std::string SimpleFeature::GetAvailabilityMessage(
419 AvailabilityResult result,
420 Manifest::Type type,
421 const GURL& url,
422 Context context) const {
423 switch (result) {
424 case IS_AVAILABLE:
425 return std::string();
426 case NOT_FOUND_IN_WHITELIST:
427 case FOUND_IN_BLACKLIST:
428 return base::StringPrintf(
429 "'%s' is not allowed for specified extension ID.",
430 name().c_str());
431 case INVALID_URL:
432 return base::StringPrintf("'%s' is not allowed on %s.",
433 name().c_str(), url.spec().c_str());
434 case INVALID_TYPE:
435 return base::StringPrintf(
436 "'%s' is only allowed for %s, but this is a %s.",
437 name().c_str(),
438 ListDisplayNames(std::vector<Manifest::Type>(
439 extension_types_.begin(), extension_types_.end())).c_str(),
440 GetDisplayName(type).c_str());
441 case INVALID_CONTEXT:
442 return base::StringPrintf(
443 "'%s' is only allowed to run in %s, but this is a %s",
444 name().c_str(),
445 ListDisplayNames(std::vector<Context>(
446 contexts_.begin(), contexts_.end())).c_str(),
447 GetDisplayName(context).c_str());
448 case INVALID_LOCATION:
449 return base::StringPrintf(
450 "'%s' is not allowed for specified install location.",
451 name().c_str());
452 case INVALID_PLATFORM:
453 return base::StringPrintf(
454 "'%s' is not allowed for specified platform.",
455 name().c_str());
456 case INVALID_MIN_MANIFEST_VERSION:
457 return base::StringPrintf(
458 "'%s' requires manifest version of at least %d.",
459 name().c_str(),
460 min_manifest_version_);
461 case INVALID_MAX_MANIFEST_VERSION:
462 return base::StringPrintf(
463 "'%s' requires manifest version of %d or lower.",
464 name().c_str(),
465 max_manifest_version_);
466 case NOT_PRESENT:
467 return base::StringPrintf(
468 "'%s' requires a different Feature that is not present.",
469 name().c_str());
470 case UNSUPPORTED_CHANNEL:
471 return base::StringPrintf(
472 "'%s' is unsupported in this version of the platform.",
473 name().c_str());
476 NOTREACHED();
477 return std::string();
480 Feature::Availability SimpleFeature::CreateAvailability(
481 AvailabilityResult result) const {
482 return Availability(
483 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
484 UNSPECIFIED_CONTEXT));
487 Feature::Availability SimpleFeature::CreateAvailability(
488 AvailabilityResult result, Manifest::Type type) const {
489 return Availability(result, GetAvailabilityMessage(result, type, GURL(),
490 UNSPECIFIED_CONTEXT));
493 Feature::Availability SimpleFeature::CreateAvailability(
494 AvailabilityResult result,
495 const GURL& url) const {
496 return Availability(
497 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, url,
498 UNSPECIFIED_CONTEXT));
501 Feature::Availability SimpleFeature::CreateAvailability(
502 AvailabilityResult result,
503 Context context) const {
504 return Availability(
505 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
506 context));
509 bool SimpleFeature::IsInternal() const {
510 return false;
513 bool SimpleFeature::IsBlockedInServiceWorker() const { return false; }
515 bool SimpleFeature::IsIdInBlacklist(const std::string& extension_id) const {
516 return IsIdInList(extension_id, blacklist_);
519 bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id) const {
520 return IsIdInList(extension_id, whitelist_);
523 // static
524 bool SimpleFeature::IsIdInList(const std::string& extension_id,
525 const std::set<std::string>& list) {
526 // Belt-and-suspenders philosophy here. We should be pretty confident by this
527 // point that we've validated the extension ID format, but in case something
528 // slips through, we avoid a class of attack where creative ID manipulation
529 // leads to hash collisions.
530 if (extension_id.length() != 32) // 128 bits / 4 = 32 mpdecimal characters
531 return false;
533 if (list.find(extension_id) != list.end() ||
534 list.find(HashExtensionId(extension_id)) != list.end()) {
535 return true;
538 return false;
541 bool SimpleFeature::MatchesManifestLocation(
542 Manifest::Location manifest_location) const {
543 switch (location_) {
544 case SimpleFeature::UNSPECIFIED_LOCATION:
545 return true;
546 case SimpleFeature::COMPONENT_LOCATION:
547 // TODO(kalman/asargent): Should this include EXTERNAL_COMPONENT too?
548 return manifest_location == Manifest::COMPONENT;
549 case SimpleFeature::POLICY_LOCATION:
550 return manifest_location == Manifest::EXTERNAL_POLICY ||
551 manifest_location == Manifest::EXTERNAL_POLICY_DOWNLOAD;
553 NOTREACHED();
554 return false;
557 Feature::Availability SimpleFeature::CheckDependencies(
558 const base::Callback<Availability(const Feature*)>& checker) const {
559 for (std::set<std::string>::const_iterator it = dependencies_.begin();
560 it != dependencies_.end();
561 ++it) {
562 Feature* dependency =
563 ExtensionAPI::GetSharedInstance()->GetFeatureDependency(*it);
564 if (!dependency)
565 return CreateAvailability(NOT_PRESENT);
566 Availability dependency_availability = checker.Run(dependency);
567 if (!dependency_availability.is_available())
568 return dependency_availability;
570 return CreateAvailability(IS_AVAILABLE);
573 } // namespace extensions