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/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
{
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 CRASH_WITH_MINIDUMP("Enum value not found: " + string_value
);
80 *enum_value
= iter
->second
;
84 void ParseEnum(const base::DictionaryValue
* value
,
85 const std::string
& property
,
87 const std::map
<std::string
, T
>& mapping
) {
88 std::string string_value
;
89 if (!value
->GetString(property
, &string_value
))
92 ParseEnum(string_value
, enum_value
, mapping
);
96 void ParseEnumVector(const base::Value
* value
,
97 std::vector
<T
>* enum_vector
,
98 const std::map
<std::string
, T
>& mapping
) {
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());
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
) {
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
) {
140 case Manifest::TYPE_UNKNOWN
:
142 case Manifest::TYPE_EXTENSION
:
144 case Manifest::TYPE_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
:
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
:
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
) {
167 case Feature::UNSPECIFIED_CONTEXT
:
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
:
181 case Feature::BLESSED_WEB_PAGE_CONTEXT
:
183 case Feature::WEBUI_CONTEXT
:
185 case Feature::SERVICE_WORKER_CONTEXT
:
186 return "service worker";
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
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"))
218 if (command_line
->HasSwitch(std::string("enable-") + switch_name
))
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
;
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
{
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
;
304 for (base::DictionaryValue::Iterator
it(*dictionary
);
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.
353 for (const auto& filter
: filters_
) {
354 result
= filter
->Parse(dictionary
);
362 Feature::Availability
SimpleFeature::IsAvailableToManifest(
363 const std::string
& extension_id
,
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())
418 return CheckDependencies(base::Bind(&IsAvailableToManifestForBind
,
426 Feature::Availability
SimpleFeature::IsAvailableToContext(
427 const Extension
* extension
,
428 SimpleFeature::Context context
,
430 SimpleFeature::Platform platform
) const {
432 Availability result
= IsAvailableToManifest(extension
->id(),
433 extension
->GetType(),
434 extension
->location(),
435 extension
->manifest_version(),
437 if (!result
.is_available())
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())
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
,
469 Context context
) const {
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.",
479 return base::StringPrintf("'%s' is not allowed on %s.",
480 name().c_str(), url
.spec().c_str());
482 return base::StringPrintf(
483 "'%s' is only allowed for %s, but this is a %s.",
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",
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.",
499 case INVALID_PLATFORM
:
500 return base::StringPrintf(
501 "'%s' is not allowed for specified platform.",
503 case INVALID_MIN_MANIFEST_VERSION
:
504 return base::StringPrintf(
505 "'%s' requires manifest version of at least %d.",
507 min_manifest_version_
);
508 case INVALID_MAX_MANIFEST_VERSION
:
509 return base::StringPrintf(
510 "'%s' requires manifest version of %d or lower.",
512 max_manifest_version_
);
514 return base::StringPrintf(
515 "'%s' requires a different Feature that is not present.",
517 case UNSUPPORTED_CHANNEL
:
518 return base::StringPrintf(
519 "'%s' is unsupported in this version of the platform.",
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());
528 return std::string();
531 Feature::Availability
SimpleFeature::CreateAvailability(
532 AvailabilityResult result
) const {
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 {
548 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, url
,
549 UNSPECIFIED_CONTEXT
));
552 Feature::Availability
SimpleFeature::CreateAvailability(
553 AvailabilityResult result
,
554 Context context
) const {
556 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, GURL(),
560 bool SimpleFeature::IsInternal() const {
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_
);
573 bool SimpleFeature::IsIdInArray(const std::string
& extension_id
,
574 const char* const array
[],
575 size_t array_length
) {
576 if (!IsValidExtensionId(extension_id
))
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
));
587 bool SimpleFeature::IsIdInList(const std::string
& extension_id
,
588 const std::vector
<std::string
>& list
) {
589 if (!IsValidExtensionId(extension_id
))
592 return (ContainsValue(list
, extension_id
) ||
593 ContainsValue(list
, HashedIdInHex(extension_id
)));
596 bool SimpleFeature::MatchesManifestLocation(
597 Manifest::Location manifest_location
) const {
599 case SimpleFeature::UNSPECIFIED_LOCATION
:
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
;
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
);
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
);
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