Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / extensions / common / features / simple_feature.cc
blobe7e68440b709832e799786f6198c036a64e0c46d
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 <algorithm>
8 #include <map>
9 #include <vector>
11 #include "base/bind.h"
12 #include "base/command_line.h"
13 #include "base/debug/alias.h"
14 #include "base/macros.h"
15 #include "base/sha1.h"
16 #include "base/stl_util.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "components/crx_file/id_util.h"
21 #include "extensions/common/extension_api.h"
22 #include "extensions/common/features/feature_provider.h"
23 #include "extensions/common/switches.h"
25 using crx_file::id_util::HashedIdInHex;
27 namespace extensions {
29 namespace {
31 // A singleton copy of the --whitelisted-extension-id so that we don't need to
32 // copy it from the CommandLine each time.
33 std::string* g_whitelisted_extension_id = NULL;
35 Feature::Availability IsAvailableToManifestForBind(
36 const std::string& extension_id,
37 Manifest::Type type,
38 Manifest::Location location,
39 int manifest_version,
40 Feature::Platform platform,
41 const Feature* feature) {
42 return feature->IsAvailableToManifest(
43 extension_id, type, location, manifest_version, platform);
46 Feature::Availability IsAvailableToContextForBind(const Extension* extension,
47 Feature::Context context,
48 const GURL& url,
49 Feature::Platform platform,
50 const Feature* feature) {
51 return feature->IsAvailableToContext(extension, context, url, platform);
54 // TODO(aa): Can we replace all this manual parsing with JSON schema stuff?
56 void ParseVector(const base::Value* value,
57 std::vector<std::string>* vector) {
58 const base::ListValue* list_value = NULL;
59 if (!value->GetAsList(&list_value))
60 return;
62 vector->clear();
63 size_t list_size = list_value->GetSize();
64 vector->reserve(list_size);
65 for (size_t i = 0; i < list_size; ++i) {
66 std::string str_val;
67 CHECK(list_value->GetString(i, &str_val));
68 vector->push_back(str_val);
70 std::sort(vector->begin(), vector->end());
73 template<typename T>
74 void ParseEnum(const std::string& string_value,
75 T* enum_value,
76 const std::map<std::string, T>& mapping) {
77 const auto& iter = 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;
86 *enum_value = iter->second;
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;
98 ParseEnum(string_value, enum_value, mapping);
101 template<typename T>
102 void ParseEnumVector(const base::Value* value,
103 std::vector<T>* enum_vector,
104 const std::map<std::string, T>& mapping) {
105 enum_vector->clear();
106 std::string property_string;
107 if (value->GetAsString(&property_string)) {
108 if (property_string == "all") {
109 enum_vector->reserve(mapping.size());
110 for (const auto& it : mapping)
111 enum_vector->push_back(it.second);
113 std::sort(enum_vector->begin(), enum_vector->end());
114 return;
117 std::vector<std::string> string_vector;
118 ParseVector(value, &string_vector);
119 enum_vector->reserve(string_vector.size());
120 for (const auto& str : string_vector) {
121 T enum_value = static_cast<T>(0);
122 ParseEnum(str, &enum_value, mapping);
123 enum_vector->push_back(enum_value);
125 std::sort(enum_vector->begin(), enum_vector->end());
128 void ParseURLPatterns(const base::DictionaryValue* value,
129 const std::string& key,
130 URLPatternSet* set) {
131 const base::ListValue* matches = NULL;
132 if (value->GetList(key, &matches)) {
133 set->ClearPatterns();
134 for (size_t i = 0; i < matches->GetSize(); ++i) {
135 std::string pattern;
136 CHECK(matches->GetString(i, &pattern));
137 set->AddPattern(URLPattern(URLPattern::SCHEME_ALL, pattern));
142 // Gets a human-readable name for the given extension type, suitable for giving
143 // to developers in an error message.
144 std::string GetDisplayName(Manifest::Type type) {
145 switch (type) {
146 case Manifest::TYPE_UNKNOWN:
147 return "unknown";
148 case Manifest::TYPE_EXTENSION:
149 return "extension";
150 case Manifest::TYPE_HOSTED_APP:
151 return "hosted app";
152 case Manifest::TYPE_LEGACY_PACKAGED_APP:
153 return "legacy packaged app";
154 case Manifest::TYPE_PLATFORM_APP:
155 return "packaged app";
156 case Manifest::TYPE_THEME:
157 return "theme";
158 case Manifest::TYPE_USER_SCRIPT:
159 return "user script";
160 case Manifest::TYPE_SHARED_MODULE:
161 return "shared module";
162 case Manifest::NUM_LOAD_TYPES:
163 NOTREACHED();
165 NOTREACHED();
166 return "";
169 // Gets a human-readable name for the given context type, suitable for giving
170 // to developers in an error message.
171 std::string GetDisplayName(Feature::Context context) {
172 switch (context) {
173 case Feature::UNSPECIFIED_CONTEXT:
174 return "unknown";
175 case Feature::BLESSED_EXTENSION_CONTEXT:
176 // "privileged" is vague but hopefully the developer will understand that
177 // means background or app window.
178 return "privileged page";
179 case Feature::UNBLESSED_EXTENSION_CONTEXT:
180 // "iframe" is a bit of a lie/oversimplification, but that's the most
181 // common unblessed context.
182 return "extension iframe";
183 case Feature::CONTENT_SCRIPT_CONTEXT:
184 return "content script";
185 case Feature::WEB_PAGE_CONTEXT:
186 return "web page";
187 case Feature::BLESSED_WEB_PAGE_CONTEXT:
188 return "hosted app";
189 case Feature::WEBUI_CONTEXT:
190 return "webui";
192 NOTREACHED();
193 return "";
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
205 // total entries.
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 bool IsCommandLineSwitchEnabled(const std::string& switch_name) {
219 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
220 if (command_line->HasSwitch(switch_name + "=1"))
221 return true;
222 if (command_line->HasSwitch(std::string("enable-") + switch_name))
223 return true;
224 return false;
227 bool IsWhitelistedForTest(const std::string& extension_id) {
228 // TODO(jackhou): Delete the commandline whitelisting mechanism.
229 // Since it is only used it tests, ideally it should not be set via the
230 // commandline. At the moment the commandline is used as a mechanism to pass
231 // the id to the renderer process.
232 if (!g_whitelisted_extension_id) {
233 g_whitelisted_extension_id = new std::string(
234 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
235 switches::kWhitelistedExtensionID));
237 return !g_whitelisted_extension_id->empty() &&
238 *g_whitelisted_extension_id == extension_id;
241 } // namespace
243 SimpleFeature::ScopedWhitelistForTest::ScopedWhitelistForTest(
244 const std::string& id)
245 : previous_id_(g_whitelisted_extension_id) {
246 g_whitelisted_extension_id = new std::string(id);
249 SimpleFeature::ScopedWhitelistForTest::~ScopedWhitelistForTest() {
250 delete g_whitelisted_extension_id;
251 g_whitelisted_extension_id = previous_id_;
254 struct SimpleFeature::Mappings {
255 Mappings() {
256 extension_types["extension"] = Manifest::TYPE_EXTENSION;
257 extension_types["theme"] = Manifest::TYPE_THEME;
258 extension_types["legacy_packaged_app"] = Manifest::TYPE_LEGACY_PACKAGED_APP;
259 extension_types["hosted_app"] = Manifest::TYPE_HOSTED_APP;
260 extension_types["platform_app"] = Manifest::TYPE_PLATFORM_APP;
261 extension_types["shared_module"] = Manifest::TYPE_SHARED_MODULE;
263 contexts["blessed_extension"] = Feature::BLESSED_EXTENSION_CONTEXT;
264 contexts["unblessed_extension"] = Feature::UNBLESSED_EXTENSION_CONTEXT;
265 contexts["content_script"] = Feature::CONTENT_SCRIPT_CONTEXT;
266 contexts["web_page"] = Feature::WEB_PAGE_CONTEXT;
267 contexts["blessed_web_page"] = Feature::BLESSED_WEB_PAGE_CONTEXT;
268 contexts["webui"] = Feature::WEBUI_CONTEXT;
270 locations["component"] = SimpleFeature::COMPONENT_LOCATION;
271 locations["external_component"] =
272 SimpleFeature::EXTERNAL_COMPONENT_LOCATION;
273 locations["policy"] = SimpleFeature::POLICY_LOCATION;
275 platforms["chromeos"] = Feature::CHROMEOS_PLATFORM;
276 platforms["linux"] = Feature::LINUX_PLATFORM;
277 platforms["mac"] = Feature::MACOSX_PLATFORM;
278 platforms["win"] = Feature::WIN_PLATFORM;
281 std::map<std::string, Manifest::Type> extension_types;
282 std::map<std::string, Feature::Context> contexts;
283 std::map<std::string, SimpleFeature::Location> locations;
284 std::map<std::string, Feature::Platform> platforms;
287 SimpleFeature::SimpleFeature()
288 : location_(UNSPECIFIED_LOCATION),
289 min_manifest_version_(0),
290 max_manifest_version_(0),
291 component_extensions_auto_granted_(true) {}
293 SimpleFeature::~SimpleFeature() {}
295 bool SimpleFeature::HasDependencies() const {
296 return !dependencies_.empty();
299 void SimpleFeature::AddFilter(scoped_ptr<SimpleFeatureFilter> filter) {
300 filters_.push_back(filter.Pass());
303 std::string SimpleFeature::Parse(const base::DictionaryValue* dictionary) {
304 static base::LazyInstance<SimpleFeature::Mappings> mappings =
305 LAZY_INSTANCE_INITIALIZER;
307 no_parent_ = false;
308 for (base::DictionaryValue::Iterator it(*dictionary);
309 !it.IsAtEnd();
310 it.Advance()) {
311 std::string key = it.key();
312 const base::Value* value = &it.value();
313 if (key == "matches") {
314 ParseURLPatterns(dictionary, "matches", &matches_);
315 } else if (key == "blacklist") {
316 ParseVector(value, &blacklist_);
317 } else if (key == "whitelist") {
318 ParseVector(value, &whitelist_);
319 } else if (key == "dependencies") {
320 ParseVector(value, &dependencies_);
321 } else if (key == "extension_types") {
322 ParseEnumVector<Manifest::Type>(value, &extension_types_,
323 mappings.Get().extension_types);
324 } else if (key == "contexts") {
325 ParseEnumVector<Context>(value, &contexts_,
326 mappings.Get().contexts);
327 } else if (key == "location") {
328 ParseEnum<Location>(dictionary, "location", &location_,
329 mappings.Get().locations);
330 } else if (key == "platforms") {
331 ParseEnumVector<Platform>(value, &platforms_,
332 mappings.Get().platforms);
333 } else if (key == "min_manifest_version") {
334 dictionary->GetInteger("min_manifest_version", &min_manifest_version_);
335 } else if (key == "max_manifest_version") {
336 dictionary->GetInteger("max_manifest_version", &max_manifest_version_);
337 } else if (key == "noparent") {
338 dictionary->GetBoolean("noparent", &no_parent_);
339 } else if (key == "component_extensions_auto_granted") {
340 dictionary->GetBoolean("component_extensions_auto_granted",
341 &component_extensions_auto_granted_);
342 } else if (key == "command_line_switch") {
343 dictionary->GetString("command_line_switch", &command_line_switch_);
347 // NOTE: ideally we'd sanity check that "matches" can be specified if and
348 // only if there's a "web_page" or "webui" context, but without
349 // (Simple)Features being aware of their own heirarchy this is impossible.
351 // For example, we might have feature "foo" available to "web_page" context
352 // and "matches" google.com/*. Then a sub-feature "foo.bar" might override
353 // "matches" to be chromium.org/*. That sub-feature doesn't need to specify
354 // "web_page" context because it's inherited, but we don't know that here.
356 std::string result;
357 for (const auto& filter : filters_) {
358 result = filter->Parse(dictionary);
359 if (!result.empty())
360 break;
363 return result;
366 Feature::Availability SimpleFeature::IsAvailableToManifest(
367 const std::string& extension_id,
368 Manifest::Type type,
369 Manifest::Location location,
370 int manifest_version,
371 Platform platform) const {
372 // Check extension type first to avoid granting platform app permissions
373 // to component extensions.
374 // HACK(kalman): user script -> extension. Solve this in a more generic way
375 // when we compile feature files.
376 Manifest::Type type_to_check = (type == Manifest::TYPE_USER_SCRIPT) ?
377 Manifest::TYPE_EXTENSION : type;
378 if (!extension_types_.empty() &&
379 !ContainsValue(extension_types_, type_to_check)) {
380 return CreateAvailability(INVALID_TYPE, type);
383 if (IsIdInBlacklist(extension_id))
384 return CreateAvailability(FOUND_IN_BLACKLIST, type);
386 // TODO(benwells): don't grant all component extensions.
387 // See http://crbug.com/370375 for more details.
388 // Component extensions can access any feature.
389 // NOTE: Deliberately does not match EXTERNAL_COMPONENT.
390 if (component_extensions_auto_granted_ && location == Manifest::COMPONENT)
391 return CreateAvailability(IS_AVAILABLE, type);
393 if (!whitelist_.empty() && !IsIdInWhitelist(extension_id) &&
394 !IsWhitelistedForTest(extension_id)) {
395 return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
398 if (!MatchesManifestLocation(location))
399 return CreateAvailability(INVALID_LOCATION, type);
401 if (!platforms_.empty() && !ContainsValue(platforms_, platform))
402 return CreateAvailability(INVALID_PLATFORM, type);
404 if (min_manifest_version_ != 0 && manifest_version < min_manifest_version_)
405 return CreateAvailability(INVALID_MIN_MANIFEST_VERSION, type);
407 if (max_manifest_version_ != 0 && manifest_version > max_manifest_version_)
408 return CreateAvailability(INVALID_MAX_MANIFEST_VERSION, type);
410 if (!command_line_switch_.empty() &&
411 !IsCommandLineSwitchEnabled(command_line_switch_)) {
412 return CreateAvailability(MISSING_COMMAND_LINE_SWITCH, type);
415 for (const auto& filter : filters_) {
416 Availability availability = filter->IsAvailableToManifest(
417 extension_id, type, location, manifest_version, platform);
418 if (!availability.is_available())
419 return availability;
422 return CheckDependencies(base::Bind(&IsAvailableToManifestForBind,
423 extension_id,
424 type,
425 location,
426 manifest_version,
427 platform));
430 Feature::Availability SimpleFeature::IsAvailableToContext(
431 const Extension* extension,
432 SimpleFeature::Context context,
433 const GURL& url,
434 SimpleFeature::Platform platform) const {
435 if (extension) {
436 Availability result = IsAvailableToManifest(extension->id(),
437 extension->GetType(),
438 extension->location(),
439 extension->manifest_version(),
440 platform);
441 if (!result.is_available())
442 return result;
445 if (!contexts_.empty() && !ContainsValue(contexts_, context))
446 return CreateAvailability(INVALID_CONTEXT, context);
448 // TODO(kalman): Consider checking |matches_| regardless of context type.
449 // Fewer surprises, and if the feature configuration wants to isolate
450 // "matches" from say "blessed_extension" then they can use complex features.
451 if ((context == WEB_PAGE_CONTEXT || context == WEBUI_CONTEXT) &&
452 !matches_.MatchesURL(url)) {
453 return CreateAvailability(INVALID_URL, url);
456 for (const auto& filter : filters_) {
457 Availability availability =
458 filter->IsAvailableToContext(extension, context, url, platform);
459 if (!availability.is_available())
460 return availability;
463 // TODO(kalman): Assert that if the context was a webpage or WebUI context
464 // then at some point a "matches" restriction was checked.
465 return CheckDependencies(base::Bind(
466 &IsAvailableToContextForBind, extension, context, url, platform));
469 std::string SimpleFeature::GetAvailabilityMessage(
470 AvailabilityResult result,
471 Manifest::Type type,
472 const GURL& url,
473 Context context) const {
474 switch (result) {
475 case IS_AVAILABLE:
476 return std::string();
477 case NOT_FOUND_IN_WHITELIST:
478 case FOUND_IN_BLACKLIST:
479 return base::StringPrintf(
480 "'%s' is not allowed for specified extension ID.",
481 name().c_str());
482 case INVALID_URL:
483 return base::StringPrintf("'%s' is not allowed on %s.",
484 name().c_str(), url.spec().c_str());
485 case INVALID_TYPE:
486 return base::StringPrintf(
487 "'%s' is only allowed for %s, but this is a %s.",
488 name().c_str(),
489 ListDisplayNames(std::vector<Manifest::Type>(
490 extension_types_.begin(), extension_types_.end())).c_str(),
491 GetDisplayName(type).c_str());
492 case INVALID_CONTEXT:
493 return base::StringPrintf(
494 "'%s' is only allowed to run in %s, but this is a %s",
495 name().c_str(),
496 ListDisplayNames(std::vector<Context>(
497 contexts_.begin(), contexts_.end())).c_str(),
498 GetDisplayName(context).c_str());
499 case INVALID_LOCATION:
500 return base::StringPrintf(
501 "'%s' is not allowed for specified install location.",
502 name().c_str());
503 case INVALID_PLATFORM:
504 return base::StringPrintf(
505 "'%s' is not allowed for specified platform.",
506 name().c_str());
507 case INVALID_MIN_MANIFEST_VERSION:
508 return base::StringPrintf(
509 "'%s' requires manifest version of at least %d.",
510 name().c_str(),
511 min_manifest_version_);
512 case INVALID_MAX_MANIFEST_VERSION:
513 return base::StringPrintf(
514 "'%s' requires manifest version of %d or lower.",
515 name().c_str(),
516 max_manifest_version_);
517 case NOT_PRESENT:
518 return base::StringPrintf(
519 "'%s' requires a different Feature that is not present.",
520 name().c_str());
521 case UNSUPPORTED_CHANNEL:
522 return base::StringPrintf(
523 "'%s' is unsupported in this version of the platform.",
524 name().c_str());
525 case MISSING_COMMAND_LINE_SWITCH:
526 return base::StringPrintf(
527 "'%s' requires the '%s' command line switch to be enabled.",
528 name().c_str(), command_line_switch_.c_str());
531 NOTREACHED();
532 return std::string();
535 Feature::Availability SimpleFeature::CreateAvailability(
536 AvailabilityResult result) const {
537 return Availability(
538 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
539 UNSPECIFIED_CONTEXT));
542 Feature::Availability SimpleFeature::CreateAvailability(
543 AvailabilityResult result, Manifest::Type type) const {
544 return Availability(result, GetAvailabilityMessage(result, type, GURL(),
545 UNSPECIFIED_CONTEXT));
548 Feature::Availability SimpleFeature::CreateAvailability(
549 AvailabilityResult result,
550 const GURL& url) const {
551 return Availability(
552 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, url,
553 UNSPECIFIED_CONTEXT));
556 Feature::Availability SimpleFeature::CreateAvailability(
557 AvailabilityResult result,
558 Context context) const {
559 return Availability(
560 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
561 context));
564 bool SimpleFeature::IsInternal() const {
565 return false;
568 bool SimpleFeature::IsIdInBlacklist(const std::string& extension_id) const {
569 return IsIdInList(extension_id, blacklist_);
572 bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id) const {
573 return IsIdInList(extension_id, whitelist_);
576 // static
577 bool SimpleFeature::IsIdInArray(const std::string& extension_id,
578 const char* const array[],
579 size_t array_length) {
580 if (!IsValidExtensionId(extension_id))
581 return false;
583 const char* const* start = array;
584 const char* const* end = array + array_length;
586 return ((std::find(start, end, extension_id) != end) ||
587 (std::find(start, end, HashedIdInHex(extension_id)) != end));
590 // static
591 bool SimpleFeature::IsIdInList(const std::string& extension_id,
592 const std::vector<std::string>& list) {
593 if (!IsValidExtensionId(extension_id))
594 return false;
596 return (ContainsValue(list, extension_id) ||
597 ContainsValue(list, HashedIdInHex(extension_id)));
600 bool SimpleFeature::MatchesManifestLocation(
601 Manifest::Location manifest_location) const {
602 switch (location_) {
603 case SimpleFeature::UNSPECIFIED_LOCATION:
604 return true;
605 case SimpleFeature::COMPONENT_LOCATION:
606 return manifest_location == Manifest::COMPONENT;
607 case SimpleFeature::EXTERNAL_COMPONENT_LOCATION:
608 return manifest_location == Manifest::EXTERNAL_COMPONENT;
609 case SimpleFeature::POLICY_LOCATION:
610 return manifest_location == Manifest::EXTERNAL_POLICY ||
611 manifest_location == Manifest::EXTERNAL_POLICY_DOWNLOAD;
613 NOTREACHED();
614 return false;
617 Feature::Availability SimpleFeature::CheckDependencies(
618 const base::Callback<Availability(const Feature*)>& checker) const {
619 for (const auto& dep_name : dependencies_) {
620 Feature* dependency =
621 ExtensionAPI::GetSharedInstance()->GetFeatureDependency(dep_name);
622 if (!dependency)
623 return CreateAvailability(NOT_PRESENT);
624 Availability dependency_availability = checker.Run(dependency);
625 if (!dependency_availability.is_available())
626 return dependency_availability;
628 return CreateAvailability(IS_AVAILABLE);
631 // static
632 bool SimpleFeature::IsValidExtensionId(const std::string& extension_id) {
633 // Belt-and-suspenders philosophy here. We should be pretty confident by this
634 // point that we've validated the extension ID format, but in case something
635 // slips through, we avoid a class of attack where creative ID manipulation
636 // leads to hash collisions.
637 // 128 bits / 4 = 32 mpdecimal characters
638 return (extension_id.length() == 32);
641 } // namespace extensions