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"
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
{
26 Feature::Availability
IsAvailableToManifestForBind(
27 const std::string
& extension_id
,
29 Manifest::Location location
,
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
,
40 Feature::Platform platform
,
41 const Feature
* feature
) {
42 return feature
->IsAvailableToContext(extension
, context
, url
, platform
);
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
;
59 contexts
["webui"] = Feature::WEBUI_CONTEXT
;
61 locations
["component"] = SimpleFeature::COMPONENT_LOCATION
;
62 locations
["policy"] = SimpleFeature::POLICY_LOCATION
;
64 platforms
["chromeos"] = Feature::CHROMEOS_PLATFORM
;
65 platforms
["linux"] = Feature::LINUX_PLATFORM
;
66 platforms
["mac"] = Feature::MACOSX_PLATFORM
;
67 platforms
["win"] = Feature::WIN_PLATFORM
;
70 std::map
<std::string
, Manifest::Type
> extension_types
;
71 std::map
<std::string
, Feature::Context
> contexts
;
72 std::map
<std::string
, SimpleFeature::Location
> locations
;
73 std::map
<std::string
, Feature::Platform
> platforms
;
76 base::LazyInstance
<Mappings
> g_mappings
= LAZY_INSTANCE_INITIALIZER
;
78 // TODO(aa): Can we replace all this manual parsing with JSON schema stuff?
80 void ParseSet(const base::DictionaryValue
* value
,
81 const std::string
& property
,
82 std::set
<std::string
>* set
) {
83 const base::ListValue
* list_value
= NULL
;
84 if (!value
->GetList(property
, &list_value
))
88 for (size_t i
= 0; i
< list_value
->GetSize(); ++i
) {
90 CHECK(list_value
->GetString(i
, &str_val
)) << property
<< " " << i
;
96 void ParseEnum(const std::string
& string_value
,
98 const std::map
<std::string
, T
>& mapping
) {
99 typename
std::map
<std::string
, T
>::const_iterator iter
=
100 mapping
.find(string_value
);
101 if (iter
== mapping
.end()) {
102 // For http://crbug.com/365192.
104 base::debug::Alias(&minidump
);
105 base::snprintf(minidump
, arraysize(minidump
),
106 "e::simple_feature.cc:%d:\"%s\"", __LINE__
, string_value
.c_str());
107 CHECK(false) << string_value
;
109 *enum_value
= iter
->second
;
113 void ParseEnum(const base::DictionaryValue
* value
,
114 const std::string
& property
,
116 const std::map
<std::string
, T
>& mapping
) {
117 std::string string_value
;
118 if (!value
->GetString(property
, &string_value
))
121 ParseEnum(string_value
, enum_value
, mapping
);
125 void ParseEnumSet(const base::DictionaryValue
* value
,
126 const std::string
& property
,
127 std::set
<T
>* enum_set
,
128 const std::map
<std::string
, T
>& mapping
) {
129 if (!value
->HasKey(property
))
134 std::string property_string
;
135 if (value
->GetString(property
, &property_string
)) {
136 if (property_string
== "all") {
137 for (typename
std::map
<std::string
, T
>::const_iterator j
=
138 mapping
.begin(); j
!= mapping
.end(); ++j
) {
139 enum_set
->insert(j
->second
);
145 std::set
<std::string
> string_set
;
146 ParseSet(value
, property
, &string_set
);
147 for (std::set
<std::string
>::iterator iter
= string_set
.begin();
148 iter
!= string_set
.end(); ++iter
) {
149 T enum_value
= static_cast<T
>(0);
150 ParseEnum(*iter
, &enum_value
, mapping
);
151 enum_set
->insert(enum_value
);
155 void ParseURLPatterns(const base::DictionaryValue
* value
,
156 const std::string
& key
,
157 URLPatternSet
* set
) {
158 const base::ListValue
* matches
= NULL
;
159 if (value
->GetList(key
, &matches
)) {
160 set
->ClearPatterns();
161 for (size_t i
= 0; i
< matches
->GetSize(); ++i
) {
163 CHECK(matches
->GetString(i
, &pattern
));
164 set
->AddPattern(URLPattern(URLPattern::SCHEME_ALL
, pattern
));
169 // Gets a human-readable name for the given extension type, suitable for giving
170 // to developers in an error message.
171 std::string
GetDisplayName(Manifest::Type type
) {
173 case Manifest::TYPE_UNKNOWN
:
175 case Manifest::TYPE_EXTENSION
:
177 case Manifest::TYPE_HOSTED_APP
:
179 case Manifest::TYPE_LEGACY_PACKAGED_APP
:
180 return "legacy packaged app";
181 case Manifest::TYPE_PLATFORM_APP
:
182 return "packaged app";
183 case Manifest::TYPE_THEME
:
185 case Manifest::TYPE_USER_SCRIPT
:
186 return "user script";
187 case Manifest::TYPE_SHARED_MODULE
:
188 return "shared module";
189 case Manifest::NUM_LOAD_TYPES
:
196 // Gets a human-readable name for the given context type, suitable for giving
197 // to developers in an error message.
198 std::string
GetDisplayName(Feature::Context context
) {
200 case Feature::UNSPECIFIED_CONTEXT
:
202 case Feature::BLESSED_EXTENSION_CONTEXT
:
203 // "privileged" is vague but hopefully the developer will understand that
204 // means background or app window.
205 return "privileged page";
206 case Feature::UNBLESSED_EXTENSION_CONTEXT
:
207 // "iframe" is a bit of a lie/oversimplification, but that's the most
208 // common unblessed context.
209 return "extension iframe";
210 case Feature::CONTENT_SCRIPT_CONTEXT
:
211 return "content script";
212 case Feature::WEB_PAGE_CONTEXT
:
214 case Feature::BLESSED_WEB_PAGE_CONTEXT
:
216 case Feature::WEBUI_CONTEXT
:
223 // Gets a human-readable list of the display names (pluralized, comma separated
224 // with the "and" in the correct place) for each of |enum_types|.
225 template <typename EnumType
>
226 std::string
ListDisplayNames(const std::vector
<EnumType
> enum_types
) {
227 std::string display_name_list
;
228 for (size_t i
= 0; i
< enum_types
.size(); ++i
) {
229 // Pluralize type name.
230 display_name_list
+= GetDisplayName(enum_types
[i
]) + "s";
231 // Comma-separate entries, with an Oxford comma if there is more than 2
233 if (enum_types
.size() > 2) {
234 if (i
< enum_types
.size() - 2)
235 display_name_list
+= ", ";
236 else if (i
== enum_types
.size() - 2)
237 display_name_list
+= ", and ";
238 } else if (enum_types
.size() == 2 && i
== 0) {
239 display_name_list
+= " and ";
242 return display_name_list
;
245 std::string
HashExtensionId(const std::string
& extension_id
) {
246 const std::string id_hash
= base::SHA1HashString(extension_id
);
247 DCHECK(id_hash
.length() == base::kSHA1Length
);
248 return base::HexEncode(id_hash
.c_str(), id_hash
.length());
253 SimpleFeature::SimpleFeature()
254 : location_(UNSPECIFIED_LOCATION
),
255 min_manifest_version_(0),
256 max_manifest_version_(0),
258 component_extensions_auto_granted_(true) {}
260 SimpleFeature::~SimpleFeature() {}
262 bool SimpleFeature::HasDependencies() {
263 return !dependencies_
.empty();
266 void SimpleFeature::AddFilter(scoped_ptr
<SimpleFeatureFilter
> filter
) {
267 filters_
.push_back(make_linked_ptr(filter
.release()));
270 std::string
SimpleFeature::Parse(const base::DictionaryValue
* value
) {
271 ParseURLPatterns(value
, "matches", &matches_
);
272 ParseSet(value
, "blacklist", &blacklist_
);
273 ParseSet(value
, "whitelist", &whitelist_
);
274 ParseSet(value
, "dependencies", &dependencies_
);
275 ParseEnumSet
<Manifest::Type
>(value
, "extension_types", &extension_types_
,
276 g_mappings
.Get().extension_types
);
277 ParseEnumSet
<Context
>(value
, "contexts", &contexts_
,
278 g_mappings
.Get().contexts
);
279 ParseEnum
<Location
>(value
, "location", &location_
,
280 g_mappings
.Get().locations
);
281 ParseEnumSet
<Platform
>(value
, "platforms", &platforms_
,
282 g_mappings
.Get().platforms
);
283 value
->GetInteger("min_manifest_version", &min_manifest_version_
);
284 value
->GetInteger("max_manifest_version", &max_manifest_version_
);
287 value
->GetBoolean("noparent", &no_parent_
);
289 component_extensions_auto_granted_
= true;
290 value
->GetBoolean("component_extensions_auto_granted",
291 &component_extensions_auto_granted_
);
293 // NOTE: ideally we'd sanity check that "matches" can be specified if and
294 // only if there's a "web_page" or "webui" context, but without
295 // (Simple)Features being aware of their own heirarchy this is impossible.
297 // For example, we might have feature "foo" available to "web_page" context
298 // and "matches" google.com/*. Then a sub-feature "foo.bar" might override
299 // "matches" to be chromium.org/*. That sub-feature doesn't need to specify
300 // "web_page" context because it's inherited, but we don't know that here.
302 for (FilterList::iterator filter_iter
= filters_
.begin();
303 filter_iter
!= filters_
.end();
305 std::string result
= (*filter_iter
)->Parse(value
);
306 if (!result
.empty()) {
311 return std::string();
314 Feature::Availability
SimpleFeature::IsAvailableToManifest(
315 const std::string
& extension_id
,
317 Manifest::Location location
,
318 int manifest_version
,
319 Platform platform
) const {
320 // Check extension type first to avoid granting platform app permissions
321 // to component extensions.
322 // HACK(kalman): user script -> extension. Solve this in a more generic way
323 // when we compile feature files.
324 Manifest::Type type_to_check
= (type
== Manifest::TYPE_USER_SCRIPT
) ?
325 Manifest::TYPE_EXTENSION
: type
;
326 if (!extension_types_
.empty() &&
327 extension_types_
.find(type_to_check
) == extension_types_
.end()) {
328 return CreateAvailability(INVALID_TYPE
, type
);
331 if (IsIdInBlacklist(extension_id
))
332 return CreateAvailability(FOUND_IN_BLACKLIST
, type
);
334 // TODO(benwells): don't grant all component extensions.
335 // See http://crbug.com/370375 for more details.
336 // Component extensions can access any feature.
337 // NOTE: Deliberately does not match EXTERNAL_COMPONENT.
338 if (component_extensions_auto_granted_
&& location
== Manifest::COMPONENT
)
339 return CreateAvailability(IS_AVAILABLE
, type
);
341 if (!whitelist_
.empty()) {
342 if (!IsIdInWhitelist(extension_id
)) {
343 // TODO(aa): This is gross. There should be a better way to test the
345 CommandLine
* command_line
= CommandLine::ForCurrentProcess();
346 if (!command_line
->HasSwitch(switches::kWhitelistedExtensionID
))
347 return CreateAvailability(NOT_FOUND_IN_WHITELIST
, type
);
349 std::string whitelist_switch_value
=
350 CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
351 switches::kWhitelistedExtensionID
);
352 if (extension_id
!= whitelist_switch_value
)
353 return CreateAvailability(NOT_FOUND_IN_WHITELIST
, type
);
357 if (!MatchesManifestLocation(location
))
358 return CreateAvailability(INVALID_LOCATION
, type
);
360 if (!platforms_
.empty() &&
361 platforms_
.find(platform
) == platforms_
.end())
362 return CreateAvailability(INVALID_PLATFORM
, type
);
364 if (min_manifest_version_
!= 0 && manifest_version
< min_manifest_version_
)
365 return CreateAvailability(INVALID_MIN_MANIFEST_VERSION
, type
);
367 if (max_manifest_version_
!= 0 && manifest_version
> max_manifest_version_
)
368 return CreateAvailability(INVALID_MAX_MANIFEST_VERSION
, type
);
370 for (FilterList::const_iterator filter_iter
= filters_
.begin();
371 filter_iter
!= filters_
.end();
373 Availability availability
= (*filter_iter
)->IsAvailableToManifest(
374 extension_id
, type
, location
, manifest_version
, platform
);
375 if (!availability
.is_available())
379 return CheckDependencies(base::Bind(&IsAvailableToManifestForBind
,
387 Feature::Availability
SimpleFeature::IsAvailableToContext(
388 const Extension
* extension
,
389 SimpleFeature::Context context
,
391 SimpleFeature::Platform platform
) const {
393 Availability result
= IsAvailableToManifest(extension
->id(),
394 extension
->GetType(),
395 extension
->location(),
396 extension
->manifest_version(),
398 if (!result
.is_available())
402 if (!contexts_
.empty() && contexts_
.find(context
) == contexts_
.end())
403 return CreateAvailability(INVALID_CONTEXT
, context
);
405 // TODO(kalman): Consider checking |matches_| regardless of context type.
406 // Fewer surprises, and if the feature configuration wants to isolate
407 // "matches" from say "blessed_extension" then they can use complex features.
408 if ((context
== WEB_PAGE_CONTEXT
|| context
== WEBUI_CONTEXT
) &&
409 !matches_
.MatchesURL(url
)) {
410 return CreateAvailability(INVALID_URL
, url
);
413 for (FilterList::const_iterator filter_iter
= filters_
.begin();
414 filter_iter
!= filters_
.end();
416 Availability availability
=
417 (*filter_iter
)->IsAvailableToContext(extension
, context
, url
, platform
);
418 if (!availability
.is_available())
422 // TODO(kalman): Assert that if the context was a webpage or WebUI context
423 // then at some point a "matches" restriction was checked.
424 return CheckDependencies(base::Bind(
425 &IsAvailableToContextForBind
, extension
, context
, url
, platform
));
428 std::string
SimpleFeature::GetAvailabilityMessage(
429 AvailabilityResult result
,
432 Context context
) const {
435 return std::string();
436 case NOT_FOUND_IN_WHITELIST
:
437 case FOUND_IN_BLACKLIST
:
438 return base::StringPrintf(
439 "'%s' is not allowed for specified extension ID.",
442 return base::StringPrintf("'%s' is not allowed on %s.",
443 name().c_str(), url
.spec().c_str());
445 return base::StringPrintf(
446 "'%s' is only allowed for %s, but this is a %s.",
448 ListDisplayNames(std::vector
<Manifest::Type
>(
449 extension_types_
.begin(), extension_types_
.end())).c_str(),
450 GetDisplayName(type
).c_str());
451 case INVALID_CONTEXT
:
452 return base::StringPrintf(
453 "'%s' is only allowed to run in %s, but this is a %s",
455 ListDisplayNames(std::vector
<Context
>(
456 contexts_
.begin(), contexts_
.end())).c_str(),
457 GetDisplayName(context
).c_str());
458 case INVALID_LOCATION
:
459 return base::StringPrintf(
460 "'%s' is not allowed for specified install location.",
462 case INVALID_PLATFORM
:
463 return base::StringPrintf(
464 "'%s' is not allowed for specified platform.",
466 case INVALID_MIN_MANIFEST_VERSION
:
467 return base::StringPrintf(
468 "'%s' requires manifest version of at least %d.",
470 min_manifest_version_
);
471 case INVALID_MAX_MANIFEST_VERSION
:
472 return base::StringPrintf(
473 "'%s' requires manifest version of %d or lower.",
475 max_manifest_version_
);
477 return base::StringPrintf(
478 "'%s' requires a different Feature that is not present.",
480 case UNSUPPORTED_CHANNEL
:
481 return base::StringPrintf(
482 "'%s' is unsupported in this version of the platform.",
487 return std::string();
490 Feature::Availability
SimpleFeature::CreateAvailability(
491 AvailabilityResult result
) const {
493 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, GURL(),
494 UNSPECIFIED_CONTEXT
));
497 Feature::Availability
SimpleFeature::CreateAvailability(
498 AvailabilityResult result
, Manifest::Type type
) const {
499 return Availability(result
, GetAvailabilityMessage(result
, type
, GURL(),
500 UNSPECIFIED_CONTEXT
));
503 Feature::Availability
SimpleFeature::CreateAvailability(
504 AvailabilityResult result
,
505 const GURL
& url
) const {
507 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, url
,
508 UNSPECIFIED_CONTEXT
));
511 Feature::Availability
SimpleFeature::CreateAvailability(
512 AvailabilityResult result
,
513 Context context
) const {
515 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, GURL(),
519 bool SimpleFeature::IsInternal() const {
523 bool SimpleFeature::IsIdInBlacklist(const std::string
& extension_id
) const {
524 return IsIdInList(extension_id
, blacklist_
);
527 bool SimpleFeature::IsIdInWhitelist(const std::string
& extension_id
) const {
528 return IsIdInList(extension_id
, whitelist_
);
532 bool SimpleFeature::IsIdInList(const std::string
& extension_id
,
533 const std::set
<std::string
>& list
) {
534 // Belt-and-suspenders philosophy here. We should be pretty confident by this
535 // point that we've validated the extension ID format, but in case something
536 // slips through, we avoid a class of attack where creative ID manipulation
537 // leads to hash collisions.
538 if (extension_id
.length() != 32) // 128 bits / 4 = 32 mpdecimal characters
541 if (list
.find(extension_id
) != list
.end() ||
542 list
.find(HashExtensionId(extension_id
)) != list
.end()) {
549 bool SimpleFeature::MatchesManifestLocation(
550 Manifest::Location manifest_location
) const {
552 case SimpleFeature::UNSPECIFIED_LOCATION
:
554 case SimpleFeature::COMPONENT_LOCATION
:
555 // TODO(kalman/asargent): Should this include EXTERNAL_COMPONENT too?
556 return manifest_location
== Manifest::COMPONENT
;
557 case SimpleFeature::POLICY_LOCATION
:
558 return manifest_location
== Manifest::EXTERNAL_POLICY
||
559 manifest_location
== Manifest::EXTERNAL_POLICY_DOWNLOAD
;
565 Feature::Availability
SimpleFeature::CheckDependencies(
566 const base::Callback
<Availability(const Feature
*)>& checker
) const {
567 for (std::set
<std::string
>::const_iterator it
= dependencies_
.begin();
568 it
!= dependencies_
.end();
570 Feature
* dependency
=
571 ExtensionAPI::GetSharedInstance()->GetFeatureDependency(*it
);
573 return CreateAvailability(NOT_PRESENT
);
574 Availability dependency_availability
= checker
.Run(dependency
);
575 if (!dependency_availability
.is_available())
576 return dependency_availability
;
578 return CreateAvailability(IS_AVAILABLE
);
581 } // namespace extensions