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/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 "extensions/common/extension_api.h"
20 #include "extensions/common/features/feature_provider.h"
21 #include "extensions/common/switches.h"
23 namespace extensions
{
27 Feature::Availability
IsAvailableToManifestForBind(
28 const std::string
& extension_id
,
30 Manifest::Location location
,
32 Feature::Platform platform
,
33 const Feature
* feature
) {
34 return feature
->IsAvailableToManifest(
35 extension_id
, type
, location
, manifest_version
, platform
);
38 Feature::Availability
IsAvailableToContextForBind(const Extension
* extension
,
39 Feature::Context context
,
41 Feature::Platform platform
,
42 const Feature
* feature
) {
43 return feature
->IsAvailableToContext(extension
, context
, url
, platform
);
48 extension_types
["extension"] = Manifest::TYPE_EXTENSION
;
49 extension_types
["theme"] = Manifest::TYPE_THEME
;
50 extension_types
["legacy_packaged_app"] = Manifest::TYPE_LEGACY_PACKAGED_APP
;
51 extension_types
["hosted_app"] = Manifest::TYPE_HOSTED_APP
;
52 extension_types
["platform_app"] = Manifest::TYPE_PLATFORM_APP
;
53 extension_types
["shared_module"] = Manifest::TYPE_SHARED_MODULE
;
55 contexts
["blessed_extension"] = Feature::BLESSED_EXTENSION_CONTEXT
;
56 contexts
["unblessed_extension"] = Feature::UNBLESSED_EXTENSION_CONTEXT
;
57 contexts
["content_script"] = Feature::CONTENT_SCRIPT_CONTEXT
;
58 contexts
["web_page"] = Feature::WEB_PAGE_CONTEXT
;
59 contexts
["blessed_web_page"] = Feature::BLESSED_WEB_PAGE_CONTEXT
;
60 contexts
["webui"] = Feature::WEBUI_CONTEXT
;
62 locations
["component"] = SimpleFeature::COMPONENT_LOCATION
;
63 locations
["external_component"] =
64 SimpleFeature::EXTERNAL_COMPONENT_LOCATION
;
65 locations
["policy"] = SimpleFeature::POLICY_LOCATION
;
67 platforms
["chromeos"] = Feature::CHROMEOS_PLATFORM
;
68 platforms
["linux"] = Feature::LINUX_PLATFORM
;
69 platforms
["mac"] = Feature::MACOSX_PLATFORM
;
70 platforms
["win"] = Feature::WIN_PLATFORM
;
73 std::map
<std::string
, Manifest::Type
> extension_types
;
74 std::map
<std::string
, Feature::Context
> contexts
;
75 std::map
<std::string
, SimpleFeature::Location
> locations
;
76 std::map
<std::string
, Feature::Platform
> platforms
;
79 base::LazyInstance
<Mappings
> g_mappings
= LAZY_INSTANCE_INITIALIZER
;
81 // TODO(aa): Can we replace all this manual parsing with JSON schema stuff?
83 void ParseSet(const base::DictionaryValue
* value
,
84 const std::string
& property
,
85 std::set
<std::string
>* set
) {
86 const base::ListValue
* list_value
= NULL
;
87 if (!value
->GetList(property
, &list_value
))
91 for (size_t i
= 0; i
< list_value
->GetSize(); ++i
) {
93 CHECK(list_value
->GetString(i
, &str_val
)) << property
<< " " << i
;
99 void ParseEnum(const std::string
& string_value
,
101 const std::map
<std::string
, T
>& mapping
) {
102 const auto& iter
= mapping
.find(string_value
);
103 if (iter
== mapping
.end()) {
104 // For http://crbug.com/365192.
106 base::debug::Alias(&minidump
);
107 base::snprintf(minidump
, arraysize(minidump
),
108 "e::simple_feature.cc:%d:\"%s\"", __LINE__
, string_value
.c_str());
109 CHECK(false) << string_value
;
111 *enum_value
= iter
->second
;
115 void ParseEnum(const base::DictionaryValue
* value
,
116 const std::string
& property
,
118 const std::map
<std::string
, T
>& mapping
) {
119 std::string string_value
;
120 if (!value
->GetString(property
, &string_value
))
123 ParseEnum(string_value
, enum_value
, mapping
);
127 void ParseEnumSet(const base::DictionaryValue
* value
,
128 const std::string
& property
,
129 std::set
<T
>* enum_set
,
130 const std::map
<std::string
, T
>& mapping
) {
131 if (!value
->HasKey(property
))
136 std::string property_string
;
137 if (value
->GetString(property
, &property_string
)) {
138 if (property_string
== "all") {
139 for (const auto& it
: mapping
)
140 enum_set
->insert(it
.second
);
145 std::set
<std::string
> string_set
;
146 ParseSet(value
, property
, &string_set
);
147 for (const auto& str
: string_set
) {
148 T enum_value
= static_cast<T
>(0);
149 ParseEnum(str
, &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
:
215 case Feature::WEBUI_CONTEXT
:
222 // Gets a human-readable list of the display names (pluralized, comma separated
223 // with the "and" in the correct place) for each of |enum_types|.
224 template <typename EnumType
>
225 std::string
ListDisplayNames(const std::vector
<EnumType
>& enum_types
) {
226 std::string display_name_list
;
227 for (size_t i
= 0; i
< enum_types
.size(); ++i
) {
228 // Pluralize type name.
229 display_name_list
+= GetDisplayName(enum_types
[i
]) + "s";
230 // Comma-separate entries, with an Oxford comma if there is more than 2
232 if (enum_types
.size() > 2) {
233 if (i
< enum_types
.size() - 2)
234 display_name_list
+= ", ";
235 else if (i
== enum_types
.size() - 2)
236 display_name_list
+= ", and ";
237 } else if (enum_types
.size() == 2 && i
== 0) {
238 display_name_list
+= " and ";
241 return display_name_list
;
244 std::string
HashExtensionId(const std::string
& extension_id
) {
245 const std::string id_hash
= base::SHA1HashString(extension_id
);
246 DCHECK_EQ(base::kSHA1Length
, id_hash
.length());
247 return base::HexEncode(id_hash
.c_str(), id_hash
.length());
250 bool IsCommandLineSwitchEnabled(const std::string
& switch_name
) {
251 base::CommandLine
* command_line
= base::CommandLine::ForCurrentProcess();
252 if (command_line
->HasSwitch(switch_name
+ "=1"))
254 if (command_line
->HasSwitch(std::string("enable-") + switch_name
))
261 SimpleFeature::SimpleFeature()
262 : location_(UNSPECIFIED_LOCATION
),
263 min_manifest_version_(0),
264 max_manifest_version_(0),
265 component_extensions_auto_granted_(true) {}
267 SimpleFeature::~SimpleFeature() {}
269 bool SimpleFeature::HasDependencies() const {
270 return !dependencies_
.empty();
273 void SimpleFeature::AddFilter(scoped_ptr
<SimpleFeatureFilter
> filter
) {
274 filters_
.push_back(filter
.release());
277 std::string
SimpleFeature::Parse(const base::DictionaryValue
* value
) {
278 ParseURLPatterns(value
, "matches", &matches_
);
279 ParseSet(value
, "blacklist", &blacklist_
);
280 ParseSet(value
, "whitelist", &whitelist_
);
281 ParseSet(value
, "dependencies", &dependencies_
);
282 ParseEnumSet
<Manifest::Type
>(value
, "extension_types", &extension_types_
,
283 g_mappings
.Get().extension_types
);
284 ParseEnumSet
<Context
>(value
, "contexts", &contexts_
,
285 g_mappings
.Get().contexts
);
286 ParseEnum
<Location
>(value
, "location", &location_
,
287 g_mappings
.Get().locations
);
288 ParseEnumSet
<Platform
>(value
, "platforms", &platforms_
,
289 g_mappings
.Get().platforms
);
290 value
->GetInteger("min_manifest_version", &min_manifest_version_
);
291 value
->GetInteger("max_manifest_version", &max_manifest_version_
);
294 value
->GetBoolean("noparent", &no_parent_
);
296 value
->GetBoolean("component_extensions_auto_granted",
297 &component_extensions_auto_granted_
);
299 value
->GetString("command_line_switch", &command_line_switch_
);
301 // NOTE: ideally we'd sanity check that "matches" can be specified if and
302 // only if there's a "web_page" or "webui" context, but without
303 // (Simple)Features being aware of their own heirarchy this is impossible.
305 // For example, we might have feature "foo" available to "web_page" context
306 // and "matches" google.com/*. Then a sub-feature "foo.bar" might override
307 // "matches" to be chromium.org/*. That sub-feature doesn't need to specify
308 // "web_page" context because it's inherited, but we don't know that here.
311 for (const auto& filter
: filters_
) {
312 result
= filter
->Parse(value
);
320 Feature::Availability
SimpleFeature::IsAvailableToManifest(
321 const std::string
& extension_id
,
323 Manifest::Location location
,
324 int manifest_version
,
325 Platform platform
) const {
326 // Check extension type first to avoid granting platform app permissions
327 // to component extensions.
328 // HACK(kalman): user script -> extension. Solve this in a more generic way
329 // when we compile feature files.
330 Manifest::Type type_to_check
= (type
== Manifest::TYPE_USER_SCRIPT
) ?
331 Manifest::TYPE_EXTENSION
: type
;
332 if (!extension_types_
.empty() &&
333 !ContainsKey(extension_types_
, type_to_check
)) {
334 return CreateAvailability(INVALID_TYPE
, type
);
337 if (IsIdInBlacklist(extension_id
))
338 return CreateAvailability(FOUND_IN_BLACKLIST
, type
);
340 // TODO(benwells): don't grant all component extensions.
341 // See http://crbug.com/370375 for more details.
342 // Component extensions can access any feature.
343 // NOTE: Deliberately does not match EXTERNAL_COMPONENT.
344 if (component_extensions_auto_granted_
&& location
== Manifest::COMPONENT
)
345 return CreateAvailability(IS_AVAILABLE
, type
);
347 if (!whitelist_
.empty()) {
348 if (!IsIdInWhitelist(extension_id
)) {
349 // TODO(aa): This is gross. There should be a better way to test the
351 base::CommandLine
* command_line
= base::CommandLine::ForCurrentProcess();
352 if (!command_line
->HasSwitch(switches::kWhitelistedExtensionID
))
353 return CreateAvailability(NOT_FOUND_IN_WHITELIST
, type
);
355 std::string whitelist_switch_value
=
356 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
357 switches::kWhitelistedExtensionID
);
358 if (extension_id
!= whitelist_switch_value
)
359 return CreateAvailability(NOT_FOUND_IN_WHITELIST
, type
);
363 if (!MatchesManifestLocation(location
))
364 return CreateAvailability(INVALID_LOCATION
, type
);
366 if (!platforms_
.empty() && !ContainsKey(platforms_
, platform
))
367 return CreateAvailability(INVALID_PLATFORM
, type
);
369 if (min_manifest_version_
!= 0 && manifest_version
< min_manifest_version_
)
370 return CreateAvailability(INVALID_MIN_MANIFEST_VERSION
, type
);
372 if (max_manifest_version_
!= 0 && manifest_version
> max_manifest_version_
)
373 return CreateAvailability(INVALID_MAX_MANIFEST_VERSION
, type
);
375 if (!command_line_switch_
.empty() &&
376 !IsCommandLineSwitchEnabled(command_line_switch_
)) {
377 return CreateAvailability(MISSING_COMMAND_LINE_SWITCH
, type
);
380 for (const auto& filter
: filters_
) {
381 Availability availability
= filter
->IsAvailableToManifest(
382 extension_id
, type
, location
, manifest_version
, platform
);
383 if (!availability
.is_available())
387 return CheckDependencies(base::Bind(&IsAvailableToManifestForBind
,
395 Feature::Availability
SimpleFeature::IsAvailableToContext(
396 const Extension
* extension
,
397 SimpleFeature::Context context
,
399 SimpleFeature::Platform platform
) const {
401 Availability result
= IsAvailableToManifest(extension
->id(),
402 extension
->GetType(),
403 extension
->location(),
404 extension
->manifest_version(),
406 if (!result
.is_available())
410 if (!contexts_
.empty() && !ContainsKey(contexts_
, context
))
411 return CreateAvailability(INVALID_CONTEXT
, context
);
413 // TODO(kalman): Consider checking |matches_| regardless of context type.
414 // Fewer surprises, and if the feature configuration wants to isolate
415 // "matches" from say "blessed_extension" then they can use complex features.
416 if ((context
== WEB_PAGE_CONTEXT
|| context
== WEBUI_CONTEXT
) &&
417 !matches_
.MatchesURL(url
)) {
418 return CreateAvailability(INVALID_URL
, url
);
421 for (const auto& filter
: filters_
) {
422 Availability availability
=
423 filter
->IsAvailableToContext(extension
, context
, url
, platform
);
424 if (!availability
.is_available())
428 // TODO(kalman): Assert that if the context was a webpage or WebUI context
429 // then at some point a "matches" restriction was checked.
430 return CheckDependencies(base::Bind(
431 &IsAvailableToContextForBind
, extension
, context
, url
, platform
));
434 std::string
SimpleFeature::GetAvailabilityMessage(
435 AvailabilityResult result
,
438 Context context
) const {
441 return std::string();
442 case NOT_FOUND_IN_WHITELIST
:
443 case FOUND_IN_BLACKLIST
:
444 return base::StringPrintf(
445 "'%s' is not allowed for specified extension ID.",
448 return base::StringPrintf("'%s' is not allowed on %s.",
449 name().c_str(), url
.spec().c_str());
451 return base::StringPrintf(
452 "'%s' is only allowed for %s, but this is a %s.",
454 ListDisplayNames(std::vector
<Manifest::Type
>(
455 extension_types_
.begin(), extension_types_
.end())).c_str(),
456 GetDisplayName(type
).c_str());
457 case INVALID_CONTEXT
:
458 return base::StringPrintf(
459 "'%s' is only allowed to run in %s, but this is a %s",
461 ListDisplayNames(std::vector
<Context
>(
462 contexts_
.begin(), contexts_
.end())).c_str(),
463 GetDisplayName(context
).c_str());
464 case INVALID_LOCATION
:
465 return base::StringPrintf(
466 "'%s' is not allowed for specified install location.",
468 case INVALID_PLATFORM
:
469 return base::StringPrintf(
470 "'%s' is not allowed for specified platform.",
472 case INVALID_MIN_MANIFEST_VERSION
:
473 return base::StringPrintf(
474 "'%s' requires manifest version of at least %d.",
476 min_manifest_version_
);
477 case INVALID_MAX_MANIFEST_VERSION
:
478 return base::StringPrintf(
479 "'%s' requires manifest version of %d or lower.",
481 max_manifest_version_
);
483 return base::StringPrintf(
484 "'%s' requires a different Feature that is not present.",
486 case UNSUPPORTED_CHANNEL
:
487 return base::StringPrintf(
488 "'%s' is unsupported in this version of the platform.",
490 case MISSING_COMMAND_LINE_SWITCH
:
491 return base::StringPrintf(
492 "'%s' requires the '%s' command line switch to be enabled.",
493 name().c_str(), command_line_switch_
.c_str());
497 return std::string();
500 Feature::Availability
SimpleFeature::CreateAvailability(
501 AvailabilityResult result
) const {
503 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, GURL(),
504 UNSPECIFIED_CONTEXT
));
507 Feature::Availability
SimpleFeature::CreateAvailability(
508 AvailabilityResult result
, Manifest::Type type
) const {
509 return Availability(result
, GetAvailabilityMessage(result
, type
, GURL(),
510 UNSPECIFIED_CONTEXT
));
513 Feature::Availability
SimpleFeature::CreateAvailability(
514 AvailabilityResult result
,
515 const GURL
& url
) const {
517 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, url
,
518 UNSPECIFIED_CONTEXT
));
521 Feature::Availability
SimpleFeature::CreateAvailability(
522 AvailabilityResult result
,
523 Context context
) const {
525 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, GURL(),
529 bool SimpleFeature::IsInternal() const {
533 bool SimpleFeature::IsIdInBlacklist(const std::string
& extension_id
) const {
534 return IsIdInList(extension_id
, blacklist_
);
537 bool SimpleFeature::IsIdInWhitelist(const std::string
& extension_id
) const {
538 return IsIdInList(extension_id
, whitelist_
);
542 bool SimpleFeature::IsIdInList(const std::string
& extension_id
,
543 const std::set
<std::string
>& list
) {
544 // Belt-and-suspenders philosophy here. We should be pretty confident by this
545 // point that we've validated the extension ID format, but in case something
546 // slips through, we avoid a class of attack where creative ID manipulation
547 // leads to hash collisions.
548 if (extension_id
.length() != 32) // 128 bits / 4 = 32 mpdecimal characters
551 return (ContainsKey(list
, extension_id
) ||
552 ContainsKey(list
, HashExtensionId(extension_id
)));
555 bool SimpleFeature::MatchesManifestLocation(
556 Manifest::Location manifest_location
) const {
558 case SimpleFeature::UNSPECIFIED_LOCATION
:
560 case SimpleFeature::COMPONENT_LOCATION
:
561 return manifest_location
== Manifest::COMPONENT
;
562 case SimpleFeature::EXTERNAL_COMPONENT_LOCATION
:
563 return manifest_location
== Manifest::EXTERNAL_COMPONENT
;
564 case SimpleFeature::POLICY_LOCATION
:
565 return manifest_location
== Manifest::EXTERNAL_POLICY
||
566 manifest_location
== Manifest::EXTERNAL_POLICY_DOWNLOAD
;
572 Feature::Availability
SimpleFeature::CheckDependencies(
573 const base::Callback
<Availability(const Feature
*)>& checker
) const {
574 for (const auto& dep_name
: dependencies_
) {
575 Feature
* dependency
=
576 ExtensionAPI::GetSharedInstance()->GetFeatureDependency(dep_name
);
578 return CreateAvailability(NOT_PRESENT
);
579 Availability dependency_availability
= checker
.Run(dependency
);
580 if (!dependency_availability
.is_available())
581 return dependency_availability
;
583 return CreateAvailability(IS_AVAILABLE
);
586 } // namespace extensions