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"
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
{
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
,
38 Manifest::Location location
,
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
,
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
))
63 size_t list_size
= list_value
->GetSize();
64 vector
->reserve(list_size
);
65 for (size_t i
= 0; i
< list_size
; ++i
) {
67 CHECK(list_value
->GetString(i
, &str_val
));
68 vector
->push_back(str_val
);
70 std::sort(vector
->begin(), vector
->end());
74 void ParseEnum(const std::string
& string_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.
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 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());
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
) {
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
) {
146 case Manifest::TYPE_UNKNOWN
:
148 case Manifest::TYPE_EXTENSION
:
150 case Manifest::TYPE_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
:
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
:
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
) {
173 case Feature::UNSPECIFIED_CONTEXT
:
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
:
187 case Feature::BLESSED_WEB_PAGE_CONTEXT
:
189 case Feature::WEBUI_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 bool IsCommandLineSwitchEnabled(const std::string
& switch_name
) {
219 base::CommandLine
* command_line
= base::CommandLine::ForCurrentProcess();
220 if (command_line
->HasSwitch(switch_name
+ "=1"))
222 if (command_line
->HasSwitch(std::string("enable-") + switch_name
))
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
;
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
{
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
;
308 for (base::DictionaryValue::Iterator
it(*dictionary
);
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.
357 for (const auto& filter
: filters_
) {
358 result
= filter
->Parse(dictionary
);
366 Feature::Availability
SimpleFeature::IsAvailableToManifest(
367 const std::string
& extension_id
,
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())
422 return CheckDependencies(base::Bind(&IsAvailableToManifestForBind
,
430 Feature::Availability
SimpleFeature::IsAvailableToContext(
431 const Extension
* extension
,
432 SimpleFeature::Context context
,
434 SimpleFeature::Platform platform
) const {
436 Availability result
= IsAvailableToManifest(extension
->id(),
437 extension
->GetType(),
438 extension
->location(),
439 extension
->manifest_version(),
441 if (!result
.is_available())
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())
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
,
473 Context context
) const {
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.",
483 return base::StringPrintf("'%s' is not allowed on %s.",
484 name().c_str(), url
.spec().c_str());
486 return base::StringPrintf(
487 "'%s' is only allowed for %s, but this is a %s.",
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",
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.",
503 case INVALID_PLATFORM
:
504 return base::StringPrintf(
505 "'%s' is not allowed for specified platform.",
507 case INVALID_MIN_MANIFEST_VERSION
:
508 return base::StringPrintf(
509 "'%s' requires manifest version of at least %d.",
511 min_manifest_version_
);
512 case INVALID_MAX_MANIFEST_VERSION
:
513 return base::StringPrintf(
514 "'%s' requires manifest version of %d or lower.",
516 max_manifest_version_
);
518 return base::StringPrintf(
519 "'%s' requires a different Feature that is not present.",
521 case UNSUPPORTED_CHANNEL
:
522 return base::StringPrintf(
523 "'%s' is unsupported in this version of the platform.",
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());
532 return std::string();
535 Feature::Availability
SimpleFeature::CreateAvailability(
536 AvailabilityResult result
) const {
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 {
552 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, url
,
553 UNSPECIFIED_CONTEXT
));
556 Feature::Availability
SimpleFeature::CreateAvailability(
557 AvailabilityResult result
,
558 Context context
) const {
560 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, GURL(),
564 bool SimpleFeature::IsInternal() const {
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_
);
577 bool SimpleFeature::IsIdInArray(const std::string
& extension_id
,
578 const char* const array
[],
579 size_t array_length
) {
580 if (!IsValidExtensionId(extension_id
))
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
));
591 bool SimpleFeature::IsIdInList(const std::string
& extension_id
,
592 const std::vector
<std::string
>& list
) {
593 if (!IsValidExtensionId(extension_id
))
596 return (ContainsValue(list
, extension_id
) ||
597 ContainsValue(list
, HashedIdInHex(extension_id
)));
600 bool SimpleFeature::MatchesManifestLocation(
601 Manifest::Location manifest_location
) const {
603 case SimpleFeature::UNSPECIFIED_LOCATION
:
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
;
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
);
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
);
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