ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / extensions / common / features / simple_feature.cc
blob7d1e06e35b11dffe0a7f4b469fc35bc61cb60e96
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/stl_util.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "extensions/common/extension_api.h"
20 #include "extensions/common/features/feature_provider.h"
21 #include "extensions/common/switches.h"
23 namespace extensions {
25 namespace {
27 Feature::Availability IsAvailableToManifestForBind(
28 const std::string& extension_id,
29 Manifest::Type type,
30 Manifest::Location location,
31 int manifest_version,
32 Feature::Platform platform,
33 const Feature* feature) {
34 return feature->IsAvailableToManifest(
35 extension_id, type, location, manifest_version, platform);
38 Feature::Availability IsAvailableToContextForBind(const Extension* extension,
39 Feature::Context context,
40 const GURL& url,
41 Feature::Platform platform,
42 const Feature* feature) {
43 return feature->IsAvailableToContext(extension, context, url, platform);
46 struct Mappings {
47 Mappings() {
48 extension_types["extension"] = Manifest::TYPE_EXTENSION;
49 extension_types["theme"] = Manifest::TYPE_THEME;
50 extension_types["legacy_packaged_app"] = Manifest::TYPE_LEGACY_PACKAGED_APP;
51 extension_types["hosted_app"] = Manifest::TYPE_HOSTED_APP;
52 extension_types["platform_app"] = Manifest::TYPE_PLATFORM_APP;
53 extension_types["shared_module"] = Manifest::TYPE_SHARED_MODULE;
55 contexts["blessed_extension"] = Feature::BLESSED_EXTENSION_CONTEXT;
56 contexts["unblessed_extension"] = Feature::UNBLESSED_EXTENSION_CONTEXT;
57 contexts["content_script"] = Feature::CONTENT_SCRIPT_CONTEXT;
58 contexts["web_page"] = Feature::WEB_PAGE_CONTEXT;
59 contexts["blessed_web_page"] = Feature::BLESSED_WEB_PAGE_CONTEXT;
60 contexts["webui"] = Feature::WEBUI_CONTEXT;
62 locations["component"] = SimpleFeature::COMPONENT_LOCATION;
63 locations["external_component"] =
64 SimpleFeature::EXTERNAL_COMPONENT_LOCATION;
65 locations["policy"] = SimpleFeature::POLICY_LOCATION;
67 platforms["chromeos"] = Feature::CHROMEOS_PLATFORM;
68 platforms["linux"] = Feature::LINUX_PLATFORM;
69 platforms["mac"] = Feature::MACOSX_PLATFORM;
70 platforms["win"] = Feature::WIN_PLATFORM;
73 std::map<std::string, Manifest::Type> extension_types;
74 std::map<std::string, Feature::Context> contexts;
75 std::map<std::string, SimpleFeature::Location> locations;
76 std::map<std::string, Feature::Platform> platforms;
79 base::LazyInstance<Mappings> g_mappings = LAZY_INSTANCE_INITIALIZER;
81 // TODO(aa): Can we replace all this manual parsing with JSON schema stuff?
83 void ParseSet(const base::DictionaryValue* value,
84 const std::string& property,
85 std::set<std::string>* set) {
86 const base::ListValue* list_value = NULL;
87 if (!value->GetList(property, &list_value))
88 return;
90 set->clear();
91 for (size_t i = 0; i < list_value->GetSize(); ++i) {
92 std::string str_val;
93 CHECK(list_value->GetString(i, &str_val)) << property << " " << i;
94 set->insert(str_val);
98 template<typename T>
99 void ParseEnum(const std::string& string_value,
100 T* enum_value,
101 const std::map<std::string, T>& mapping) {
102 const auto& iter = mapping.find(string_value);
103 if (iter == mapping.end()) {
104 // For http://crbug.com/365192.
105 char minidump[256];
106 base::debug::Alias(&minidump);
107 base::snprintf(minidump, arraysize(minidump),
108 "e::simple_feature.cc:%d:\"%s\"", __LINE__, string_value.c_str());
109 CHECK(false) << string_value;
111 *enum_value = iter->second;
114 template<typename T>
115 void ParseEnum(const base::DictionaryValue* value,
116 const std::string& property,
117 T* enum_value,
118 const std::map<std::string, T>& mapping) {
119 std::string string_value;
120 if (!value->GetString(property, &string_value))
121 return;
123 ParseEnum(string_value, enum_value, mapping);
126 template<typename T>
127 void ParseEnumSet(const base::DictionaryValue* value,
128 const std::string& property,
129 std::set<T>* enum_set,
130 const std::map<std::string, T>& mapping) {
131 if (!value->HasKey(property))
132 return;
134 enum_set->clear();
136 std::string property_string;
137 if (value->GetString(property, &property_string)) {
138 if (property_string == "all") {
139 for (const auto& it : mapping)
140 enum_set->insert(it.second);
142 return;
145 std::set<std::string> string_set;
146 ParseSet(value, property, &string_set);
147 for (const auto& str : string_set) {
148 T enum_value = static_cast<T>(0);
149 ParseEnum(str, &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";
215 case Feature::WEBUI_CONTEXT:
216 return "webui";
218 NOTREACHED();
219 return "";
222 // Gets a human-readable list of the display names (pluralized, comma separated
223 // with the "and" in the correct place) for each of |enum_types|.
224 template <typename EnumType>
225 std::string ListDisplayNames(const std::vector<EnumType>& enum_types) {
226 std::string display_name_list;
227 for (size_t i = 0; i < enum_types.size(); ++i) {
228 // Pluralize type name.
229 display_name_list += GetDisplayName(enum_types[i]) + "s";
230 // Comma-separate entries, with an Oxford comma if there is more than 2
231 // total entries.
232 if (enum_types.size() > 2) {
233 if (i < enum_types.size() - 2)
234 display_name_list += ", ";
235 else if (i == enum_types.size() - 2)
236 display_name_list += ", and ";
237 } else if (enum_types.size() == 2 && i == 0) {
238 display_name_list += " and ";
241 return display_name_list;
244 std::string HashExtensionId(const std::string& extension_id) {
245 const std::string id_hash = base::SHA1HashString(extension_id);
246 DCHECK_EQ(base::kSHA1Length, id_hash.length());
247 return base::HexEncode(id_hash.c_str(), id_hash.length());
250 bool IsCommandLineSwitchEnabled(const std::string& switch_name) {
251 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
252 if (command_line->HasSwitch(switch_name + "=1"))
253 return true;
254 if (command_line->HasSwitch(std::string("enable-") + switch_name))
255 return true;
256 return false;
259 } // namespace
261 SimpleFeature::SimpleFeature()
262 : location_(UNSPECIFIED_LOCATION),
263 min_manifest_version_(0),
264 max_manifest_version_(0),
265 component_extensions_auto_granted_(true) {}
267 SimpleFeature::~SimpleFeature() {}
269 bool SimpleFeature::HasDependencies() const {
270 return !dependencies_.empty();
273 void SimpleFeature::AddFilter(scoped_ptr<SimpleFeatureFilter> filter) {
274 filters_.push_back(filter.release());
277 std::string SimpleFeature::Parse(const base::DictionaryValue* value) {
278 ParseURLPatterns(value, "matches", &matches_);
279 ParseSet(value, "blacklist", &blacklist_);
280 ParseSet(value, "whitelist", &whitelist_);
281 ParseSet(value, "dependencies", &dependencies_);
282 ParseEnumSet<Manifest::Type>(value, "extension_types", &extension_types_,
283 g_mappings.Get().extension_types);
284 ParseEnumSet<Context>(value, "contexts", &contexts_,
285 g_mappings.Get().contexts);
286 ParseEnum<Location>(value, "location", &location_,
287 g_mappings.Get().locations);
288 ParseEnumSet<Platform>(value, "platforms", &platforms_,
289 g_mappings.Get().platforms);
290 value->GetInteger("min_manifest_version", &min_manifest_version_);
291 value->GetInteger("max_manifest_version", &max_manifest_version_);
293 no_parent_ = false;
294 value->GetBoolean("noparent", &no_parent_);
296 value->GetBoolean("component_extensions_auto_granted",
297 &component_extensions_auto_granted_);
299 value->GetString("command_line_switch", &command_line_switch_);
301 // NOTE: ideally we'd sanity check that "matches" can be specified if and
302 // only if there's a "web_page" or "webui" context, but without
303 // (Simple)Features being aware of their own heirarchy this is impossible.
305 // For example, we might have feature "foo" available to "web_page" context
306 // and "matches" google.com/*. Then a sub-feature "foo.bar" might override
307 // "matches" to be chromium.org/*. That sub-feature doesn't need to specify
308 // "web_page" context because it's inherited, but we don't know that here.
310 std::string result;
311 for (const auto& filter : filters_) {
312 result = filter->Parse(value);
313 if (!result.empty())
314 break;
317 return result;
320 Feature::Availability SimpleFeature::IsAvailableToManifest(
321 const std::string& extension_id,
322 Manifest::Type type,
323 Manifest::Location location,
324 int manifest_version,
325 Platform platform) const {
326 // Check extension type first to avoid granting platform app permissions
327 // to component extensions.
328 // HACK(kalman): user script -> extension. Solve this in a more generic way
329 // when we compile feature files.
330 Manifest::Type type_to_check = (type == Manifest::TYPE_USER_SCRIPT) ?
331 Manifest::TYPE_EXTENSION : type;
332 if (!extension_types_.empty() &&
333 !ContainsKey(extension_types_, type_to_check)) {
334 return CreateAvailability(INVALID_TYPE, type);
337 if (IsIdInBlacklist(extension_id))
338 return CreateAvailability(FOUND_IN_BLACKLIST, type);
340 // TODO(benwells): don't grant all component extensions.
341 // See http://crbug.com/370375 for more details.
342 // Component extensions can access any feature.
343 // NOTE: Deliberately does not match EXTERNAL_COMPONENT.
344 if (component_extensions_auto_granted_ && location == Manifest::COMPONENT)
345 return CreateAvailability(IS_AVAILABLE, type);
347 if (!whitelist_.empty()) {
348 if (!IsIdInWhitelist(extension_id)) {
349 // TODO(aa): This is gross. There should be a better way to test the
350 // whitelist.
351 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
352 if (!command_line->HasSwitch(switches::kWhitelistedExtensionID))
353 return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
355 std::string whitelist_switch_value =
356 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
357 switches::kWhitelistedExtensionID);
358 if (extension_id != whitelist_switch_value)
359 return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
363 if (!MatchesManifestLocation(location))
364 return CreateAvailability(INVALID_LOCATION, type);
366 if (!platforms_.empty() && !ContainsKey(platforms_, platform))
367 return CreateAvailability(INVALID_PLATFORM, type);
369 if (min_manifest_version_ != 0 && manifest_version < min_manifest_version_)
370 return CreateAvailability(INVALID_MIN_MANIFEST_VERSION, type);
372 if (max_manifest_version_ != 0 && manifest_version > max_manifest_version_)
373 return CreateAvailability(INVALID_MAX_MANIFEST_VERSION, type);
375 if (!command_line_switch_.empty() &&
376 !IsCommandLineSwitchEnabled(command_line_switch_)) {
377 return CreateAvailability(MISSING_COMMAND_LINE_SWITCH, type);
380 for (const auto& filter : filters_) {
381 Availability availability = filter->IsAvailableToManifest(
382 extension_id, type, location, manifest_version, platform);
383 if (!availability.is_available())
384 return availability;
387 return CheckDependencies(base::Bind(&IsAvailableToManifestForBind,
388 extension_id,
389 type,
390 location,
391 manifest_version,
392 platform));
395 Feature::Availability SimpleFeature::IsAvailableToContext(
396 const Extension* extension,
397 SimpleFeature::Context context,
398 const GURL& url,
399 SimpleFeature::Platform platform) const {
400 if (extension) {
401 Availability result = IsAvailableToManifest(extension->id(),
402 extension->GetType(),
403 extension->location(),
404 extension->manifest_version(),
405 platform);
406 if (!result.is_available())
407 return result;
410 if (!contexts_.empty() && !ContainsKey(contexts_, context))
411 return CreateAvailability(INVALID_CONTEXT, context);
413 // TODO(kalman): Consider checking |matches_| regardless of context type.
414 // Fewer surprises, and if the feature configuration wants to isolate
415 // "matches" from say "blessed_extension" then they can use complex features.
416 if ((context == WEB_PAGE_CONTEXT || context == WEBUI_CONTEXT) &&
417 !matches_.MatchesURL(url)) {
418 return CreateAvailability(INVALID_URL, url);
421 for (const auto& filter : filters_) {
422 Availability availability =
423 filter->IsAvailableToContext(extension, context, url, platform);
424 if (!availability.is_available())
425 return availability;
428 // TODO(kalman): Assert that if the context was a webpage or WebUI context
429 // then at some point a "matches" restriction was checked.
430 return CheckDependencies(base::Bind(
431 &IsAvailableToContextForBind, extension, context, url, platform));
434 std::string SimpleFeature::GetAvailabilityMessage(
435 AvailabilityResult result,
436 Manifest::Type type,
437 const GURL& url,
438 Context context) const {
439 switch (result) {
440 case IS_AVAILABLE:
441 return std::string();
442 case NOT_FOUND_IN_WHITELIST:
443 case FOUND_IN_BLACKLIST:
444 return base::StringPrintf(
445 "'%s' is not allowed for specified extension ID.",
446 name().c_str());
447 case INVALID_URL:
448 return base::StringPrintf("'%s' is not allowed on %s.",
449 name().c_str(), url.spec().c_str());
450 case INVALID_TYPE:
451 return base::StringPrintf(
452 "'%s' is only allowed for %s, but this is a %s.",
453 name().c_str(),
454 ListDisplayNames(std::vector<Manifest::Type>(
455 extension_types_.begin(), extension_types_.end())).c_str(),
456 GetDisplayName(type).c_str());
457 case INVALID_CONTEXT:
458 return base::StringPrintf(
459 "'%s' is only allowed to run in %s, but this is a %s",
460 name().c_str(),
461 ListDisplayNames(std::vector<Context>(
462 contexts_.begin(), contexts_.end())).c_str(),
463 GetDisplayName(context).c_str());
464 case INVALID_LOCATION:
465 return base::StringPrintf(
466 "'%s' is not allowed for specified install location.",
467 name().c_str());
468 case INVALID_PLATFORM:
469 return base::StringPrintf(
470 "'%s' is not allowed for specified platform.",
471 name().c_str());
472 case INVALID_MIN_MANIFEST_VERSION:
473 return base::StringPrintf(
474 "'%s' requires manifest version of at least %d.",
475 name().c_str(),
476 min_manifest_version_);
477 case INVALID_MAX_MANIFEST_VERSION:
478 return base::StringPrintf(
479 "'%s' requires manifest version of %d or lower.",
480 name().c_str(),
481 max_manifest_version_);
482 case NOT_PRESENT:
483 return base::StringPrintf(
484 "'%s' requires a different Feature that is not present.",
485 name().c_str());
486 case UNSUPPORTED_CHANNEL:
487 return base::StringPrintf(
488 "'%s' is unsupported in this version of the platform.",
489 name().c_str());
490 case MISSING_COMMAND_LINE_SWITCH:
491 return base::StringPrintf(
492 "'%s' requires the '%s' command line switch to be enabled.",
493 name().c_str(), command_line_switch_.c_str());
496 NOTREACHED();
497 return std::string();
500 Feature::Availability SimpleFeature::CreateAvailability(
501 AvailabilityResult result) const {
502 return Availability(
503 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
504 UNSPECIFIED_CONTEXT));
507 Feature::Availability SimpleFeature::CreateAvailability(
508 AvailabilityResult result, Manifest::Type type) const {
509 return Availability(result, GetAvailabilityMessage(result, type, GURL(),
510 UNSPECIFIED_CONTEXT));
513 Feature::Availability SimpleFeature::CreateAvailability(
514 AvailabilityResult result,
515 const GURL& url) const {
516 return Availability(
517 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, url,
518 UNSPECIFIED_CONTEXT));
521 Feature::Availability SimpleFeature::CreateAvailability(
522 AvailabilityResult result,
523 Context context) const {
524 return Availability(
525 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
526 context));
529 bool SimpleFeature::IsInternal() const {
530 return false;
533 bool SimpleFeature::IsIdInBlacklist(const std::string& extension_id) const {
534 return IsIdInList(extension_id, blacklist_);
537 bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id) const {
538 return IsIdInList(extension_id, whitelist_);
541 // static
542 bool SimpleFeature::IsIdInList(const std::string& extension_id,
543 const std::set<std::string>& list) {
544 // Belt-and-suspenders philosophy here. We should be pretty confident by this
545 // point that we've validated the extension ID format, but in case something
546 // slips through, we avoid a class of attack where creative ID manipulation
547 // leads to hash collisions.
548 if (extension_id.length() != 32) // 128 bits / 4 = 32 mpdecimal characters
549 return false;
551 return (ContainsKey(list, extension_id) ||
552 ContainsKey(list, HashExtensionId(extension_id)));
555 bool SimpleFeature::MatchesManifestLocation(
556 Manifest::Location manifest_location) const {
557 switch (location_) {
558 case SimpleFeature::UNSPECIFIED_LOCATION:
559 return true;
560 case SimpleFeature::COMPONENT_LOCATION:
561 return manifest_location == Manifest::COMPONENT;
562 case SimpleFeature::EXTERNAL_COMPONENT_LOCATION:
563 return manifest_location == Manifest::EXTERNAL_COMPONENT;
564 case SimpleFeature::POLICY_LOCATION:
565 return manifest_location == Manifest::EXTERNAL_POLICY ||
566 manifest_location == Manifest::EXTERNAL_POLICY_DOWNLOAD;
568 NOTREACHED();
569 return false;
572 Feature::Availability SimpleFeature::CheckDependencies(
573 const base::Callback<Availability(const Feature*)>& checker) const {
574 for (const auto& dep_name : dependencies_) {
575 Feature* dependency =
576 ExtensionAPI::GetSharedInstance()->GetFeatureDependency(dep_name);
577 if (!dependency)
578 return CreateAvailability(NOT_PRESENT);
579 Availability dependency_availability = checker.Run(dependency);
580 if (!dependency_availability.is_available())
581 return dependency_availability;
583 return CreateAvailability(IS_AVAILABLE);
586 } // namespace extensions