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
;
60 locations
["component"] = SimpleFeature::COMPONENT_LOCATION
;
61 locations
["policy"] = SimpleFeature::POLICY_LOCATION
;
63 platforms
["chromeos"] = Feature::CHROMEOS_PLATFORM
;
64 platforms
["linux"] = Feature::LINUX_PLATFORM
;
65 platforms
["mac"] = Feature::MACOSX_PLATFORM
;
66 platforms
["win"] = Feature::WIN_PLATFORM
;
69 std::map
<std::string
, Manifest::Type
> extension_types
;
70 std::map
<std::string
, Feature::Context
> contexts
;
71 std::map
<std::string
, SimpleFeature::Location
> locations
;
72 std::map
<std::string
, Feature::Platform
> platforms
;
75 base::LazyInstance
<Mappings
> g_mappings
= LAZY_INSTANCE_INITIALIZER
;
77 // TODO(aa): Can we replace all this manual parsing with JSON schema stuff?
79 void ParseSet(const base::DictionaryValue
* value
,
80 const std::string
& property
,
81 std::set
<std::string
>* set
) {
82 const base::ListValue
* list_value
= NULL
;
83 if (!value
->GetList(property
, &list_value
))
87 for (size_t i
= 0; i
< list_value
->GetSize(); ++i
) {
89 CHECK(list_value
->GetString(i
, &str_val
)) << property
<< " " << i
;
95 void ParseEnum(const std::string
& string_value
,
97 const std::map
<std::string
, T
>& mapping
) {
98 typename
std::map
<std::string
, T
>::const_iterator iter
=
99 mapping
.find(string_value
);
100 if (iter
== mapping
.end()) {
101 // For http://crbug.com/365192.
103 base::debug::Alias(&minidump
);
104 base::snprintf(minidump
, arraysize(minidump
),
105 "e::simple_feature.cc:%d:\"%s\"", __LINE__
, string_value
.c_str());
106 CHECK(false) << string_value
;
108 *enum_value
= iter
->second
;
112 void ParseEnum(const base::DictionaryValue
* value
,
113 const std::string
& property
,
115 const std::map
<std::string
, T
>& mapping
) {
116 std::string string_value
;
117 if (!value
->GetString(property
, &string_value
))
120 ParseEnum(string_value
, enum_value
, mapping
);
124 void ParseEnumSet(const base::DictionaryValue
* value
,
125 const std::string
& property
,
126 std::set
<T
>* enum_set
,
127 const std::map
<std::string
, T
>& mapping
) {
128 if (!value
->HasKey(property
))
133 std::string property_string
;
134 if (value
->GetString(property
, &property_string
)) {
135 if (property_string
== "all") {
136 for (typename
std::map
<std::string
, T
>::const_iterator j
=
137 mapping
.begin(); j
!= mapping
.end(); ++j
) {
138 enum_set
->insert(j
->second
);
144 std::set
<std::string
> string_set
;
145 ParseSet(value
, property
, &string_set
);
146 for (std::set
<std::string
>::iterator iter
= string_set
.begin();
147 iter
!= string_set
.end(); ++iter
) {
148 T enum_value
= static_cast<T
>(0);
149 ParseEnum(*iter
, &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
) {
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
) {
172 case Manifest::TYPE_UNKNOWN
:
174 case Manifest::TYPE_EXTENSION
:
176 case Manifest::TYPE_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
:
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
:
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
) {
199 case Feature::UNSPECIFIED_CONTEXT
:
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
:
213 case Feature::BLESSED_WEB_PAGE_CONTEXT
:
220 // Gets a human-readable list of the display names (pluralized, comma separated
221 // with the "and" in the correct place) for each of |enum_types|.
222 template <typename EnumType
>
223 std::string
ListDisplayNames(const std::vector
<EnumType
> enum_types
) {
224 std::string display_name_list
;
225 for (size_t i
= 0; i
< enum_types
.size(); ++i
) {
226 // Pluralize type name.
227 display_name_list
+= GetDisplayName(enum_types
[i
]) + "s";
228 // Comma-separate entries, with an Oxford comma if there is more than 2
230 if (enum_types
.size() > 2) {
231 if (i
< enum_types
.size() - 2)
232 display_name_list
+= ", ";
233 else if (i
== enum_types
.size() - 2)
234 display_name_list
+= ", and ";
235 } else if (enum_types
.size() == 2 && i
== 0) {
236 display_name_list
+= " and ";
239 return display_name_list
;
242 std::string
HashExtensionId(const std::string
& extension_id
) {
243 const std::string id_hash
= base::SHA1HashString(extension_id
);
244 DCHECK(id_hash
.length() == base::kSHA1Length
);
245 return base::HexEncode(id_hash
.c_str(), id_hash
.length());
250 SimpleFeature::SimpleFeature()
251 : location_(UNSPECIFIED_LOCATION
),
252 min_manifest_version_(0),
253 max_manifest_version_(0),
255 component_extensions_auto_granted_(true) {}
257 SimpleFeature::~SimpleFeature() {}
259 bool SimpleFeature::HasDependencies() {
260 return !dependencies_
.empty();
263 void SimpleFeature::AddFilter(scoped_ptr
<SimpleFeatureFilter
> filter
) {
264 filters_
.push_back(make_linked_ptr(filter
.release()));
267 std::string
SimpleFeature::Parse(const base::DictionaryValue
* value
) {
268 ParseURLPatterns(value
, "matches", &matches_
);
269 ParseSet(value
, "blacklist", &blacklist_
);
270 ParseSet(value
, "whitelist", &whitelist_
);
271 ParseSet(value
, "dependencies", &dependencies_
);
272 ParseEnumSet
<Manifest::Type
>(value
, "extension_types", &extension_types_
,
273 g_mappings
.Get().extension_types
);
274 ParseEnumSet
<Context
>(value
, "contexts", &contexts_
,
275 g_mappings
.Get().contexts
);
276 ParseEnum
<Location
>(value
, "location", &location_
,
277 g_mappings
.Get().locations
);
278 ParseEnumSet
<Platform
>(value
, "platforms", &platforms_
,
279 g_mappings
.Get().platforms
);
280 value
->GetInteger("min_manifest_version", &min_manifest_version_
);
281 value
->GetInteger("max_manifest_version", &max_manifest_version_
);
284 value
->GetBoolean("noparent", &no_parent_
);
286 component_extensions_auto_granted_
= true;
287 value
->GetBoolean("component_extensions_auto_granted",
288 &component_extensions_auto_granted_
);
290 // NOTE: ideally we'd sanity check that "matches" can be specified if and
291 // only if there's a "web_page" context, but without (Simple)Features being
292 // aware of their own heirarchy this is impossible.
294 // For example, we might have feature "foo" available to "web_page" context
295 // and "matches" google.com/*. Then a sub-feature "foo.bar" might override
296 // "matches" to be chromium.org/*. That sub-feature doesn't need to specify
297 // "web_page" context because it's inherited, but we don't know that here.
299 for (FilterList::iterator filter_iter
= filters_
.begin();
300 filter_iter
!= filters_
.end();
302 std::string result
= (*filter_iter
)->Parse(value
);
303 if (!result
.empty()) {
308 return std::string();
311 Feature::Availability
SimpleFeature::IsAvailableToManifest(
312 const std::string
& extension_id
,
314 Manifest::Location location
,
315 int manifest_version
,
316 Platform platform
) const {
317 // Check extension type first to avoid granting platform app permissions
318 // to component extensions.
319 // HACK(kalman): user script -> extension. Solve this in a more generic way
320 // when we compile feature files.
321 Manifest::Type type_to_check
= (type
== Manifest::TYPE_USER_SCRIPT
) ?
322 Manifest::TYPE_EXTENSION
: type
;
323 if (!extension_types_
.empty() &&
324 extension_types_
.find(type_to_check
) == extension_types_
.end()) {
325 return CreateAvailability(INVALID_TYPE
, type
);
328 if (IsIdInBlacklist(extension_id
))
329 return CreateAvailability(FOUND_IN_BLACKLIST
, type
);
331 // TODO(benwells): don't grant all component extensions.
332 // See http://crbug.com/370375 for more details.
333 // Component extensions can access any feature.
334 // NOTE: Deliberately does not match EXTERNAL_COMPONENT.
335 if (component_extensions_auto_granted_
&& location
== Manifest::COMPONENT
)
336 return CreateAvailability(IS_AVAILABLE
, type
);
338 if (!whitelist_
.empty()) {
339 if (!IsIdInWhitelist(extension_id
)) {
340 // TODO(aa): This is gross. There should be a better way to test the
342 CommandLine
* command_line
= CommandLine::ForCurrentProcess();
343 if (!command_line
->HasSwitch(switches::kWhitelistedExtensionID
))
344 return CreateAvailability(NOT_FOUND_IN_WHITELIST
, type
);
346 std::string whitelist_switch_value
=
347 CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
348 switches::kWhitelistedExtensionID
);
349 if (extension_id
!= whitelist_switch_value
)
350 return CreateAvailability(NOT_FOUND_IN_WHITELIST
, type
);
354 if (!MatchesManifestLocation(location
))
355 return CreateAvailability(INVALID_LOCATION
, type
);
357 if (!platforms_
.empty() &&
358 platforms_
.find(platform
) == platforms_
.end())
359 return CreateAvailability(INVALID_PLATFORM
, type
);
361 if (min_manifest_version_
!= 0 && manifest_version
< min_manifest_version_
)
362 return CreateAvailability(INVALID_MIN_MANIFEST_VERSION
, type
);
364 if (max_manifest_version_
!= 0 && manifest_version
> max_manifest_version_
)
365 return CreateAvailability(INVALID_MAX_MANIFEST_VERSION
, type
);
367 for (FilterList::const_iterator filter_iter
= filters_
.begin();
368 filter_iter
!= filters_
.end();
370 Availability availability
= (*filter_iter
)->IsAvailableToManifest(
371 extension_id
, type
, location
, manifest_version
, platform
);
372 if (!availability
.is_available())
376 return CheckDependencies(base::Bind(&IsAvailableToManifestForBind
,
384 Feature::Availability
SimpleFeature::IsAvailableToContext(
385 const Extension
* extension
,
386 SimpleFeature::Context context
,
388 SimpleFeature::Platform platform
) const {
390 Availability result
= IsAvailableToManifest(extension
->id(),
391 extension
->GetType(),
392 extension
->location(),
393 extension
->manifest_version(),
395 if (!result
.is_available())
399 if (!contexts_
.empty() && contexts_
.find(context
) == contexts_
.end())
400 return CreateAvailability(INVALID_CONTEXT
, context
);
402 if (context
== WEB_PAGE_CONTEXT
&& !matches_
.MatchesURL(url
))
403 return CreateAvailability(INVALID_URL
, url
);
405 for (FilterList::const_iterator filter_iter
= filters_
.begin();
406 filter_iter
!= filters_
.end();
408 Availability availability
=
409 (*filter_iter
)->IsAvailableToContext(extension
, context
, url
, platform
);
410 if (!availability
.is_available())
414 return CheckDependencies(base::Bind(
415 &IsAvailableToContextForBind
, extension
, context
, url
, platform
));
418 std::string
SimpleFeature::GetAvailabilityMessage(
419 AvailabilityResult result
,
422 Context context
) const {
425 return std::string();
426 case NOT_FOUND_IN_WHITELIST
:
427 case FOUND_IN_BLACKLIST
:
428 return base::StringPrintf(
429 "'%s' is not allowed for specified extension ID.",
432 return base::StringPrintf("'%s' is not allowed on %s.",
433 name().c_str(), url
.spec().c_str());
435 return base::StringPrintf(
436 "'%s' is only allowed for %s, but this is a %s.",
438 ListDisplayNames(std::vector
<Manifest::Type
>(
439 extension_types_
.begin(), extension_types_
.end())).c_str(),
440 GetDisplayName(type
).c_str());
441 case INVALID_CONTEXT
:
442 return base::StringPrintf(
443 "'%s' is only allowed to run in %s, but this is a %s",
445 ListDisplayNames(std::vector
<Context
>(
446 contexts_
.begin(), contexts_
.end())).c_str(),
447 GetDisplayName(context
).c_str());
448 case INVALID_LOCATION
:
449 return base::StringPrintf(
450 "'%s' is not allowed for specified install location.",
452 case INVALID_PLATFORM
:
453 return base::StringPrintf(
454 "'%s' is not allowed for specified platform.",
456 case INVALID_MIN_MANIFEST_VERSION
:
457 return base::StringPrintf(
458 "'%s' requires manifest version of at least %d.",
460 min_manifest_version_
);
461 case INVALID_MAX_MANIFEST_VERSION
:
462 return base::StringPrintf(
463 "'%s' requires manifest version of %d or lower.",
465 max_manifest_version_
);
467 return base::StringPrintf(
468 "'%s' requires a different Feature that is not present.",
470 case UNSUPPORTED_CHANNEL
:
471 return base::StringPrintf(
472 "'%s' is unsupported in this version of the platform.",
477 return std::string();
480 Feature::Availability
SimpleFeature::CreateAvailability(
481 AvailabilityResult result
) const {
483 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, GURL(),
484 UNSPECIFIED_CONTEXT
));
487 Feature::Availability
SimpleFeature::CreateAvailability(
488 AvailabilityResult result
, Manifest::Type type
) const {
489 return Availability(result
, GetAvailabilityMessage(result
, type
, GURL(),
490 UNSPECIFIED_CONTEXT
));
493 Feature::Availability
SimpleFeature::CreateAvailability(
494 AvailabilityResult result
,
495 const GURL
& url
) const {
497 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, url
,
498 UNSPECIFIED_CONTEXT
));
501 Feature::Availability
SimpleFeature::CreateAvailability(
502 AvailabilityResult result
,
503 Context context
) const {
505 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, GURL(),
509 bool SimpleFeature::IsInternal() const {
513 bool SimpleFeature::IsBlockedInServiceWorker() const { return false; }
515 bool SimpleFeature::IsIdInBlacklist(const std::string
& extension_id
) const {
516 return IsIdInList(extension_id
, blacklist_
);
519 bool SimpleFeature::IsIdInWhitelist(const std::string
& extension_id
) const {
520 return IsIdInList(extension_id
, whitelist_
);
524 bool SimpleFeature::IsIdInList(const std::string
& extension_id
,
525 const std::set
<std::string
>& list
) {
526 // Belt-and-suspenders philosophy here. We should be pretty confident by this
527 // point that we've validated the extension ID format, but in case something
528 // slips through, we avoid a class of attack where creative ID manipulation
529 // leads to hash collisions.
530 if (extension_id
.length() != 32) // 128 bits / 4 = 32 mpdecimal characters
533 if (list
.find(extension_id
) != list
.end() ||
534 list
.find(HashExtensionId(extension_id
)) != list
.end()) {
541 bool SimpleFeature::MatchesManifestLocation(
542 Manifest::Location manifest_location
) const {
544 case SimpleFeature::UNSPECIFIED_LOCATION
:
546 case SimpleFeature::COMPONENT_LOCATION
:
547 // TODO(kalman/asargent): Should this include EXTERNAL_COMPONENT too?
548 return manifest_location
== Manifest::COMPONENT
;
549 case SimpleFeature::POLICY_LOCATION
:
550 return manifest_location
== Manifest::EXTERNAL_POLICY
||
551 manifest_location
== Manifest::EXTERNAL_POLICY_DOWNLOAD
;
557 Feature::Availability
SimpleFeature::CheckDependencies(
558 const base::Callback
<Availability(const Feature
*)>& checker
) const {
559 for (std::set
<std::string
>::const_iterator it
= dependencies_
.begin();
560 it
!= dependencies_
.end();
562 Feature
* dependency
=
563 ExtensionAPI::GetSharedInstance()->GetFeatureDependency(*it
);
565 return CreateAvailability(NOT_PRESENT
);
566 Availability dependency_availability
= checker
.Run(dependency
);
567 if (!dependency_availability
.is_available())
568 return dependency_availability
;
570 return CreateAvailability(IS_AVAILABLE
);
573 } // namespace extensions