Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / extensions / common / features / simple_feature.cc
blob4ead4638978de6139e59144606a8e86305cba254
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/macros.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 "components/crx_file/id_util.h"
20 #include "extensions/common/extension_api.h"
21 #include "extensions/common/features/feature_provider.h"
22 #include "extensions/common/features/feature_util.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 CRASH_WITH_MINIDUMP("Enum value not found: " + string_value);
80 *enum_value = iter->second;
83 template<typename T>
84 void ParseEnum(const base::DictionaryValue* value,
85 const std::string& property,
86 T* enum_value,
87 const std::map<std::string, T>& mapping) {
88 std::string string_value;
89 if (!value->GetString(property, &string_value))
90 return;
92 ParseEnum(string_value, enum_value, mapping);
95 template<typename T>
96 void ParseEnumVector(const base::Value* value,
97 std::vector<T>* enum_vector,
98 const std::map<std::string, T>& mapping) {
99 enum_vector->clear();
100 std::string property_string;
101 if (value->GetAsString(&property_string)) {
102 if (property_string == "all") {
103 enum_vector->reserve(mapping.size());
104 for (const auto& it : mapping)
105 enum_vector->push_back(it.second);
107 std::sort(enum_vector->begin(), enum_vector->end());
108 return;
111 std::vector<std::string> string_vector;
112 ParseVector(value, &string_vector);
113 enum_vector->reserve(string_vector.size());
114 for (const auto& str : string_vector) {
115 T enum_value = static_cast<T>(0);
116 ParseEnum(str, &enum_value, mapping);
117 enum_vector->push_back(enum_value);
119 std::sort(enum_vector->begin(), enum_vector->end());
122 void ParseURLPatterns(const base::DictionaryValue* value,
123 const std::string& key,
124 URLPatternSet* set) {
125 const base::ListValue* matches = NULL;
126 if (value->GetList(key, &matches)) {
127 set->ClearPatterns();
128 for (size_t i = 0; i < matches->GetSize(); ++i) {
129 std::string pattern;
130 CHECK(matches->GetString(i, &pattern));
131 set->AddPattern(URLPattern(URLPattern::SCHEME_ALL, pattern));
136 // Gets a human-readable name for the given extension type, suitable for giving
137 // to developers in an error message.
138 std::string GetDisplayName(Manifest::Type type) {
139 switch (type) {
140 case Manifest::TYPE_UNKNOWN:
141 return "unknown";
142 case Manifest::TYPE_EXTENSION:
143 return "extension";
144 case Manifest::TYPE_HOSTED_APP:
145 return "hosted app";
146 case Manifest::TYPE_LEGACY_PACKAGED_APP:
147 return "legacy packaged app";
148 case Manifest::TYPE_PLATFORM_APP:
149 return "packaged app";
150 case Manifest::TYPE_THEME:
151 return "theme";
152 case Manifest::TYPE_USER_SCRIPT:
153 return "user script";
154 case Manifest::TYPE_SHARED_MODULE:
155 return "shared module";
156 case Manifest::NUM_LOAD_TYPES:
157 NOTREACHED();
159 NOTREACHED();
160 return "";
163 // Gets a human-readable name for the given context type, suitable for giving
164 // to developers in an error message.
165 std::string GetDisplayName(Feature::Context context) {
166 switch (context) {
167 case Feature::UNSPECIFIED_CONTEXT:
168 return "unknown";
169 case Feature::BLESSED_EXTENSION_CONTEXT:
170 // "privileged" is vague but hopefully the developer will understand that
171 // means background or app window.
172 return "privileged page";
173 case Feature::UNBLESSED_EXTENSION_CONTEXT:
174 // "iframe" is a bit of a lie/oversimplification, but that's the most
175 // common unblessed context.
176 return "extension iframe";
177 case Feature::CONTENT_SCRIPT_CONTEXT:
178 return "content script";
179 case Feature::WEB_PAGE_CONTEXT:
180 return "web page";
181 case Feature::BLESSED_WEB_PAGE_CONTEXT:
182 return "hosted app";
183 case Feature::WEBUI_CONTEXT:
184 return "webui";
185 case Feature::SERVICE_WORKER_CONTEXT:
186 return "service worker";
188 NOTREACHED();
189 return "";
192 // Gets a human-readable list of the display names (pluralized, comma separated
193 // with the "and" in the correct place) for each of |enum_types|.
194 template <typename EnumType>
195 std::string ListDisplayNames(const std::vector<EnumType>& enum_types) {
196 std::string display_name_list;
197 for (size_t i = 0; i < enum_types.size(); ++i) {
198 // Pluralize type name.
199 display_name_list += GetDisplayName(enum_types[i]) + "s";
200 // Comma-separate entries, with an Oxford comma if there is more than 2
201 // total entries.
202 if (enum_types.size() > 2) {
203 if (i < enum_types.size() - 2)
204 display_name_list += ", ";
205 else if (i == enum_types.size() - 2)
206 display_name_list += ", and ";
207 } else if (enum_types.size() == 2 && i == 0) {
208 display_name_list += " and ";
211 return display_name_list;
214 bool IsCommandLineSwitchEnabled(const std::string& switch_name) {
215 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
216 if (command_line->HasSwitch(switch_name + "=1"))
217 return true;
218 if (command_line->HasSwitch(std::string("enable-") + switch_name))
219 return true;
220 return false;
223 bool IsWhitelistedForTest(const std::string& extension_id) {
224 // TODO(jackhou): Delete the commandline whitelisting mechanism.
225 // Since it is only used it tests, ideally it should not be set via the
226 // commandline. At the moment the commandline is used as a mechanism to pass
227 // the id to the renderer process.
228 if (!g_whitelisted_extension_id) {
229 g_whitelisted_extension_id = new std::string(
230 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
231 switches::kWhitelistedExtensionID));
233 return !g_whitelisted_extension_id->empty() &&
234 *g_whitelisted_extension_id == extension_id;
237 } // namespace
239 SimpleFeature::ScopedWhitelistForTest::ScopedWhitelistForTest(
240 const std::string& id)
241 : previous_id_(g_whitelisted_extension_id) {
242 g_whitelisted_extension_id = new std::string(id);
245 SimpleFeature::ScopedWhitelistForTest::~ScopedWhitelistForTest() {
246 delete g_whitelisted_extension_id;
247 g_whitelisted_extension_id = previous_id_;
250 struct SimpleFeature::Mappings {
251 Mappings() {
252 extension_types["extension"] = Manifest::TYPE_EXTENSION;
253 extension_types["theme"] = Manifest::TYPE_THEME;
254 extension_types["legacy_packaged_app"] = Manifest::TYPE_LEGACY_PACKAGED_APP;
255 extension_types["hosted_app"] = Manifest::TYPE_HOSTED_APP;
256 extension_types["platform_app"] = Manifest::TYPE_PLATFORM_APP;
257 extension_types["shared_module"] = Manifest::TYPE_SHARED_MODULE;
259 contexts["blessed_extension"] = Feature::BLESSED_EXTENSION_CONTEXT;
260 contexts["unblessed_extension"] = Feature::UNBLESSED_EXTENSION_CONTEXT;
261 contexts["content_script"] = Feature::CONTENT_SCRIPT_CONTEXT;
262 contexts["web_page"] = Feature::WEB_PAGE_CONTEXT;
263 contexts["blessed_web_page"] = Feature::BLESSED_WEB_PAGE_CONTEXT;
264 contexts["webui"] = Feature::WEBUI_CONTEXT;
266 locations["component"] = SimpleFeature::COMPONENT_LOCATION;
267 locations["external_component"] =
268 SimpleFeature::EXTERNAL_COMPONENT_LOCATION;
269 locations["policy"] = SimpleFeature::POLICY_LOCATION;
271 platforms["chromeos"] = Feature::CHROMEOS_PLATFORM;
272 platforms["linux"] = Feature::LINUX_PLATFORM;
273 platforms["mac"] = Feature::MACOSX_PLATFORM;
274 platforms["win"] = Feature::WIN_PLATFORM;
277 std::map<std::string, Manifest::Type> extension_types;
278 std::map<std::string, Feature::Context> contexts;
279 std::map<std::string, SimpleFeature::Location> locations;
280 std::map<std::string, Feature::Platform> platforms;
283 SimpleFeature::SimpleFeature()
284 : location_(UNSPECIFIED_LOCATION),
285 min_manifest_version_(0),
286 max_manifest_version_(0),
287 component_extensions_auto_granted_(true) {}
289 SimpleFeature::~SimpleFeature() {}
291 bool SimpleFeature::HasDependencies() const {
292 return !dependencies_.empty();
295 void SimpleFeature::AddFilter(scoped_ptr<SimpleFeatureFilter> filter) {
296 filters_.push_back(filter.Pass());
299 std::string SimpleFeature::Parse(const base::DictionaryValue* dictionary) {
300 static base::LazyInstance<SimpleFeature::Mappings> mappings =
301 LAZY_INSTANCE_INITIALIZER;
303 no_parent_ = false;
304 for (base::DictionaryValue::Iterator it(*dictionary);
305 !it.IsAtEnd();
306 it.Advance()) {
307 std::string key = it.key();
308 const base::Value* value = &it.value();
309 if (key == "matches") {
310 ParseURLPatterns(dictionary, "matches", &matches_);
311 } else if (key == "blacklist") {
312 ParseVector(value, &blacklist_);
313 } else if (key == "whitelist") {
314 ParseVector(value, &whitelist_);
315 } else if (key == "dependencies") {
316 ParseVector(value, &dependencies_);
317 } else if (key == "extension_types") {
318 ParseEnumVector<Manifest::Type>(value, &extension_types_,
319 mappings.Get().extension_types);
320 } else if (key == "contexts") {
321 ParseEnumVector<Context>(value, &contexts_,
322 mappings.Get().contexts);
323 } else if (key == "location") {
324 ParseEnum<Location>(dictionary, "location", &location_,
325 mappings.Get().locations);
326 } else if (key == "platforms") {
327 ParseEnumVector<Platform>(value, &platforms_,
328 mappings.Get().platforms);
329 } else if (key == "min_manifest_version") {
330 dictionary->GetInteger("min_manifest_version", &min_manifest_version_);
331 } else if (key == "max_manifest_version") {
332 dictionary->GetInteger("max_manifest_version", &max_manifest_version_);
333 } else if (key == "noparent") {
334 dictionary->GetBoolean("noparent", &no_parent_);
335 } else if (key == "component_extensions_auto_granted") {
336 dictionary->GetBoolean("component_extensions_auto_granted",
337 &component_extensions_auto_granted_);
338 } else if (key == "command_line_switch") {
339 dictionary->GetString("command_line_switch", &command_line_switch_);
343 // NOTE: ideally we'd sanity check that "matches" can be specified if and
344 // only if there's a "web_page" or "webui" context, but without
345 // (Simple)Features being aware of their own heirarchy this is impossible.
347 // For example, we might have feature "foo" available to "web_page" context
348 // and "matches" google.com/*. Then a sub-feature "foo.bar" might override
349 // "matches" to be chromium.org/*. That sub-feature doesn't need to specify
350 // "web_page" context because it's inherited, but we don't know that here.
352 std::string result;
353 for (const auto& filter : filters_) {
354 result = filter->Parse(dictionary);
355 if (!result.empty())
356 break;
359 return result;
362 Feature::Availability SimpleFeature::IsAvailableToManifest(
363 const std::string& extension_id,
364 Manifest::Type type,
365 Manifest::Location location,
366 int manifest_version,
367 Platform platform) const {
368 // Check extension type first to avoid granting platform app permissions
369 // to component extensions.
370 // HACK(kalman): user script -> extension. Solve this in a more generic way
371 // when we compile feature files.
372 Manifest::Type type_to_check = (type == Manifest::TYPE_USER_SCRIPT) ?
373 Manifest::TYPE_EXTENSION : type;
374 if (!extension_types_.empty() &&
375 !ContainsValue(extension_types_, type_to_check)) {
376 return CreateAvailability(INVALID_TYPE, type);
379 if (IsIdInBlacklist(extension_id))
380 return CreateAvailability(FOUND_IN_BLACKLIST, type);
382 // TODO(benwells): don't grant all component extensions.
383 // See http://crbug.com/370375 for more details.
384 // Component extensions can access any feature.
385 // NOTE: Deliberately does not match EXTERNAL_COMPONENT.
386 if (component_extensions_auto_granted_ && location == Manifest::COMPONENT)
387 return CreateAvailability(IS_AVAILABLE, type);
389 if (!whitelist_.empty() && !IsIdInWhitelist(extension_id) &&
390 !IsWhitelistedForTest(extension_id)) {
391 return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
394 if (!MatchesManifestLocation(location))
395 return CreateAvailability(INVALID_LOCATION, type);
397 if (!platforms_.empty() && !ContainsValue(platforms_, platform))
398 return CreateAvailability(INVALID_PLATFORM, type);
400 if (min_manifest_version_ != 0 && manifest_version < min_manifest_version_)
401 return CreateAvailability(INVALID_MIN_MANIFEST_VERSION, type);
403 if (max_manifest_version_ != 0 && manifest_version > max_manifest_version_)
404 return CreateAvailability(INVALID_MAX_MANIFEST_VERSION, type);
406 if (!command_line_switch_.empty() &&
407 !IsCommandLineSwitchEnabled(command_line_switch_)) {
408 return CreateAvailability(MISSING_COMMAND_LINE_SWITCH, type);
411 for (const auto& filter : filters_) {
412 Availability availability = filter->IsAvailableToManifest(
413 extension_id, type, location, manifest_version, platform);
414 if (!availability.is_available())
415 return availability;
418 return CheckDependencies(base::Bind(&IsAvailableToManifestForBind,
419 extension_id,
420 type,
421 location,
422 manifest_version,
423 platform));
426 Feature::Availability SimpleFeature::IsAvailableToContext(
427 const Extension* extension,
428 SimpleFeature::Context context,
429 const GURL& url,
430 SimpleFeature::Platform platform) const {
431 if (extension) {
432 Availability result = IsAvailableToManifest(extension->id(),
433 extension->GetType(),
434 extension->location(),
435 extension->manifest_version(),
436 platform);
437 if (!result.is_available())
438 return result;
441 if (!contexts_.empty() && !ContainsValue(contexts_, context))
442 return CreateAvailability(INVALID_CONTEXT, context);
444 // TODO(kalman): Consider checking |matches_| regardless of context type.
445 // Fewer surprises, and if the feature configuration wants to isolate
446 // "matches" from say "blessed_extension" then they can use complex features.
447 if ((context == WEB_PAGE_CONTEXT || context == WEBUI_CONTEXT) &&
448 !matches_.MatchesURL(url)) {
449 return CreateAvailability(INVALID_URL, url);
452 for (const auto& filter : filters_) {
453 Availability availability =
454 filter->IsAvailableToContext(extension, context, url, platform);
455 if (!availability.is_available())
456 return availability;
459 // TODO(kalman): Assert that if the context was a webpage or WebUI context
460 // then at some point a "matches" restriction was checked.
461 return CheckDependencies(base::Bind(
462 &IsAvailableToContextForBind, extension, context, url, platform));
465 std::string SimpleFeature::GetAvailabilityMessage(
466 AvailabilityResult result,
467 Manifest::Type type,
468 const GURL& url,
469 Context context) const {
470 switch (result) {
471 case IS_AVAILABLE:
472 return std::string();
473 case NOT_FOUND_IN_WHITELIST:
474 case FOUND_IN_BLACKLIST:
475 return base::StringPrintf(
476 "'%s' is not allowed for specified extension ID.",
477 name().c_str());
478 case INVALID_URL:
479 return base::StringPrintf("'%s' is not allowed on %s.",
480 name().c_str(), url.spec().c_str());
481 case INVALID_TYPE:
482 return base::StringPrintf(
483 "'%s' is only allowed for %s, but this is a %s.",
484 name().c_str(),
485 ListDisplayNames(std::vector<Manifest::Type>(
486 extension_types_.begin(), extension_types_.end())).c_str(),
487 GetDisplayName(type).c_str());
488 case INVALID_CONTEXT:
489 return base::StringPrintf(
490 "'%s' is only allowed to run in %s, but this is a %s",
491 name().c_str(),
492 ListDisplayNames(std::vector<Context>(
493 contexts_.begin(), contexts_.end())).c_str(),
494 GetDisplayName(context).c_str());
495 case INVALID_LOCATION:
496 return base::StringPrintf(
497 "'%s' is not allowed for specified install location.",
498 name().c_str());
499 case INVALID_PLATFORM:
500 return base::StringPrintf(
501 "'%s' is not allowed for specified platform.",
502 name().c_str());
503 case INVALID_MIN_MANIFEST_VERSION:
504 return base::StringPrintf(
505 "'%s' requires manifest version of at least %d.",
506 name().c_str(),
507 min_manifest_version_);
508 case INVALID_MAX_MANIFEST_VERSION:
509 return base::StringPrintf(
510 "'%s' requires manifest version of %d or lower.",
511 name().c_str(),
512 max_manifest_version_);
513 case NOT_PRESENT:
514 return base::StringPrintf(
515 "'%s' requires a different Feature that is not present.",
516 name().c_str());
517 case UNSUPPORTED_CHANNEL:
518 return base::StringPrintf(
519 "'%s' is unsupported in this version of the platform.",
520 name().c_str());
521 case MISSING_COMMAND_LINE_SWITCH:
522 return base::StringPrintf(
523 "'%s' requires the '%s' command line switch to be enabled.",
524 name().c_str(), command_line_switch_.c_str());
527 NOTREACHED();
528 return std::string();
531 Feature::Availability SimpleFeature::CreateAvailability(
532 AvailabilityResult result) const {
533 return Availability(
534 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
535 UNSPECIFIED_CONTEXT));
538 Feature::Availability SimpleFeature::CreateAvailability(
539 AvailabilityResult result, Manifest::Type type) const {
540 return Availability(result, GetAvailabilityMessage(result, type, GURL(),
541 UNSPECIFIED_CONTEXT));
544 Feature::Availability SimpleFeature::CreateAvailability(
545 AvailabilityResult result,
546 const GURL& url) const {
547 return Availability(
548 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, url,
549 UNSPECIFIED_CONTEXT));
552 Feature::Availability SimpleFeature::CreateAvailability(
553 AvailabilityResult result,
554 Context context) const {
555 return Availability(
556 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
557 context));
560 bool SimpleFeature::IsInternal() const {
561 return false;
564 bool SimpleFeature::IsIdInBlacklist(const std::string& extension_id) const {
565 return IsIdInList(extension_id, blacklist_);
568 bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id) const {
569 return IsIdInList(extension_id, whitelist_);
572 // static
573 bool SimpleFeature::IsIdInArray(const std::string& extension_id,
574 const char* const array[],
575 size_t array_length) {
576 if (!IsValidExtensionId(extension_id))
577 return false;
579 const char* const* start = array;
580 const char* const* end = array + array_length;
582 return ((std::find(start, end, extension_id) != end) ||
583 (std::find(start, end, HashedIdInHex(extension_id)) != end));
586 // static
587 bool SimpleFeature::IsIdInList(const std::string& extension_id,
588 const std::vector<std::string>& list) {
589 if (!IsValidExtensionId(extension_id))
590 return false;
592 return (ContainsValue(list, extension_id) ||
593 ContainsValue(list, HashedIdInHex(extension_id)));
596 bool SimpleFeature::MatchesManifestLocation(
597 Manifest::Location manifest_location) const {
598 switch (location_) {
599 case SimpleFeature::UNSPECIFIED_LOCATION:
600 return true;
601 case SimpleFeature::COMPONENT_LOCATION:
602 return manifest_location == Manifest::COMPONENT;
603 case SimpleFeature::EXTERNAL_COMPONENT_LOCATION:
604 return manifest_location == Manifest::EXTERNAL_COMPONENT;
605 case SimpleFeature::POLICY_LOCATION:
606 return manifest_location == Manifest::EXTERNAL_POLICY ||
607 manifest_location == Manifest::EXTERNAL_POLICY_DOWNLOAD;
609 NOTREACHED();
610 return false;
613 Feature::Availability SimpleFeature::CheckDependencies(
614 const base::Callback<Availability(const Feature*)>& checker) const {
615 for (const auto& dep_name : dependencies_) {
616 Feature* dependency =
617 ExtensionAPI::GetSharedInstance()->GetFeatureDependency(dep_name);
618 if (!dependency)
619 return CreateAvailability(NOT_PRESENT);
620 Availability dependency_availability = checker.Run(dependency);
621 if (!dependency_availability.is_available())
622 return dependency_availability;
624 return CreateAvailability(IS_AVAILABLE);
627 // static
628 bool SimpleFeature::IsValidExtensionId(const std::string& extension_id) {
629 // Belt-and-suspenders philosophy here. We should be pretty confident by this
630 // point that we've validated the extension ID format, but in case something
631 // slips through, we avoid a class of attack where creative ID manipulation
632 // leads to hash collisions.
633 // 128 bits / 4 = 32 mpdecimal characters
634 return (extension_id.length() == 32);
637 } // namespace extensions