1 // Copyright (c) 2012 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 "chrome/common/extensions/features/simple_feature.h"
10 #include "base/command_line.h"
11 #include "base/lazy_instance.h"
12 #include "base/sha1.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "chrome/common/chrome_switches.h"
17 #include "chrome/common/extensions/features/feature_channel.h"
19 using chrome::VersionInfo
;
21 namespace extensions
{
27 extension_types
["extension"] = Manifest::TYPE_EXTENSION
;
28 extension_types
["theme"] = Manifest::TYPE_THEME
;
29 extension_types
["legacy_packaged_app"] = Manifest::TYPE_LEGACY_PACKAGED_APP
;
30 extension_types
["hosted_app"] = Manifest::TYPE_HOSTED_APP
;
31 extension_types
["platform_app"] = Manifest::TYPE_PLATFORM_APP
;
32 extension_types
["shared_module"] = Manifest::TYPE_SHARED_MODULE
;
34 contexts
["blessed_extension"] = Feature::BLESSED_EXTENSION_CONTEXT
;
35 contexts
["unblessed_extension"] = Feature::UNBLESSED_EXTENSION_CONTEXT
;
36 contexts
["content_script"] = Feature::CONTENT_SCRIPT_CONTEXT
;
37 contexts
["web_page"] = Feature::WEB_PAGE_CONTEXT
;
38 contexts
["blessed_web_page"] = Feature::BLESSED_WEB_PAGE_CONTEXT
;
40 locations
["component"] = Feature::COMPONENT_LOCATION
;
42 platforms
["chromeos"] = Feature::CHROMEOS_PLATFORM
;
43 platforms
["linux"] = Feature::LINUX_PLATFORM
;
44 platforms
["mac"] = Feature::MACOSX_PLATFORM
;
45 platforms
["win"] = Feature::WIN_PLATFORM
;
47 channels
["trunk"] = VersionInfo::CHANNEL_UNKNOWN
;
48 channels
["canary"] = VersionInfo::CHANNEL_CANARY
;
49 channels
["dev"] = VersionInfo::CHANNEL_DEV
;
50 channels
["beta"] = VersionInfo::CHANNEL_BETA
;
51 channels
["stable"] = VersionInfo::CHANNEL_STABLE
;
54 std::map
<std::string
, Manifest::Type
> extension_types
;
55 std::map
<std::string
, Feature::Context
> contexts
;
56 std::map
<std::string
, Feature::Location
> locations
;
57 std::map
<std::string
, Feature::Platform
> platforms
;
58 std::map
<std::string
, VersionInfo::Channel
> channels
;
61 base::LazyInstance
<Mappings
> g_mappings
= LAZY_INSTANCE_INITIALIZER
;
63 std::string
GetChannelName(VersionInfo::Channel channel
) {
64 typedef std::map
<std::string
, VersionInfo::Channel
> ChannelsMap
;
65 ChannelsMap channels
= g_mappings
.Get().channels
;
66 for (ChannelsMap::iterator i
= channels
.begin(); i
!= channels
.end(); ++i
) {
67 if (i
->second
== channel
)
74 // TODO(aa): Can we replace all this manual parsing with JSON schema stuff?
76 void ParseSet(const base::DictionaryValue
* value
,
77 const std::string
& property
,
78 std::set
<std::string
>* set
) {
79 const base::ListValue
* list_value
= NULL
;
80 if (!value
->GetList(property
, &list_value
))
84 for (size_t i
= 0; i
< list_value
->GetSize(); ++i
) {
86 CHECK(list_value
->GetString(i
, &str_val
)) << property
<< " " << i
;
92 void ParseEnum(const std::string
& string_value
,
94 const std::map
<std::string
, T
>& mapping
) {
95 typename
std::map
<std::string
, T
>::const_iterator iter
=
96 mapping
.find(string_value
);
97 CHECK(iter
!= mapping
.end()) << string_value
;
98 *enum_value
= iter
->second
;
102 void ParseEnum(const base::DictionaryValue
* value
,
103 const std::string
& property
,
105 const std::map
<std::string
, T
>& mapping
) {
106 std::string string_value
;
107 if (!value
->GetString(property
, &string_value
))
110 ParseEnum(string_value
, enum_value
, mapping
);
114 void ParseEnumSet(const base::DictionaryValue
* value
,
115 const std::string
& property
,
116 std::set
<T
>* enum_set
,
117 const std::map
<std::string
, T
>& mapping
) {
118 if (!value
->HasKey(property
))
123 std::string property_string
;
124 if (value
->GetString(property
, &property_string
)) {
125 if (property_string
== "all") {
126 for (typename
std::map
<std::string
, T
>::const_iterator j
=
127 mapping
.begin(); j
!= mapping
.end(); ++j
) {
128 enum_set
->insert(j
->second
);
134 std::set
<std::string
> string_set
;
135 ParseSet(value
, property
, &string_set
);
136 for (std::set
<std::string
>::iterator iter
= string_set
.begin();
137 iter
!= string_set
.end(); ++iter
) {
138 T enum_value
= static_cast<T
>(0);
139 ParseEnum(*iter
, &enum_value
, mapping
);
140 enum_set
->insert(enum_value
);
144 void ParseURLPatterns(const base::DictionaryValue
* value
,
145 const std::string
& key
,
146 URLPatternSet
* set
) {
147 const base::ListValue
* matches
= NULL
;
148 if (value
->GetList(key
, &matches
)) {
149 set
->ClearPatterns();
150 for (size_t i
= 0; i
< matches
->GetSize(); ++i
) {
152 CHECK(matches
->GetString(i
, &pattern
));
153 set
->AddPattern(URLPattern(URLPattern::SCHEME_ALL
, pattern
));
158 // Gets a human-readable name for the given extension type, suitable for giving
159 // to developers in an error message.
160 std::string
GetDisplayName(Manifest::Type type
) {
162 case Manifest::TYPE_UNKNOWN
:
164 case Manifest::TYPE_EXTENSION
:
166 case Manifest::TYPE_HOSTED_APP
:
168 case Manifest::TYPE_LEGACY_PACKAGED_APP
:
169 return "legacy packaged app";
170 case Manifest::TYPE_PLATFORM_APP
:
171 return "packaged app";
172 case Manifest::TYPE_THEME
:
174 case Manifest::TYPE_USER_SCRIPT
:
175 return "user script";
176 case Manifest::TYPE_SHARED_MODULE
:
177 return "shared module";
183 // Gets a human-readable name for the given context type, suitable for giving
184 // to developers in an error message.
185 std::string
GetDisplayName(Feature::Context context
) {
187 case Feature::UNSPECIFIED_CONTEXT
:
189 case Feature::BLESSED_EXTENSION_CONTEXT
:
190 // "privileged" is vague but hopefully the developer will understand that
191 // means background or app window.
192 return "privileged page";
193 case Feature::UNBLESSED_EXTENSION_CONTEXT
:
194 // "iframe" is a bit of a lie/oversimplification, but that's the most
195 // common unblessed context.
196 return "extension iframe";
197 case Feature::CONTENT_SCRIPT_CONTEXT
:
198 return "content script";
199 case Feature::WEB_PAGE_CONTEXT
:
201 case Feature::BLESSED_WEB_PAGE_CONTEXT
:
208 // Gets a human-readable list of the display names (pluralized, comma separated
209 // with the "and" in the correct place) for each of |enum_types|.
210 template <typename EnumType
>
211 std::string
ListDisplayNames(const std::vector
<EnumType
> enum_types
) {
212 std::string display_name_list
;
213 for (size_t i
= 0; i
< enum_types
.size(); ++i
) {
214 // Pluralize type name.
215 display_name_list
+= GetDisplayName(enum_types
[i
]) + "s";
216 // Comma-separate entries, with an Oxford comma if there is more than 2
218 if (enum_types
.size() > 2) {
219 if (i
< enum_types
.size() - 2)
220 display_name_list
+= ", ";
221 else if (i
== enum_types
.size() - 2)
222 display_name_list
+= ", and ";
223 } else if (enum_types
.size() == 2 && i
== 0) {
224 display_name_list
+= " and ";
227 return display_name_list
;
230 std::string
HashExtensionId(const std::string
& extension_id
) {
231 const std::string id_hash
= base::SHA1HashString(extension_id
);
232 DCHECK(id_hash
.length() == base::kSHA1Length
);
233 return base::HexEncode(id_hash
.c_str(), id_hash
.length());
238 SimpleFeature::SimpleFeature()
239 : location_(UNSPECIFIED_LOCATION
),
240 min_manifest_version_(0),
241 max_manifest_version_(0),
242 channel_(VersionInfo::CHANNEL_UNKNOWN
),
244 channel_has_been_set_(false) {
247 SimpleFeature::SimpleFeature(const SimpleFeature
& other
)
248 : whitelist_(other
.whitelist_
),
249 extension_types_(other
.extension_types_
),
250 contexts_(other
.contexts_
),
251 matches_(other
.matches_
),
252 location_(other
.location_
),
253 platforms_(other
.platforms_
),
254 min_manifest_version_(other
.min_manifest_version_
),
255 max_manifest_version_(other
.max_manifest_version_
),
256 channel_(other
.channel_
),
257 has_parent_(other
.has_parent_
),
258 channel_has_been_set_(other
.channel_has_been_set_
) {
261 SimpleFeature::~SimpleFeature() {
264 bool SimpleFeature::Equals(const SimpleFeature
& other
) const {
265 return whitelist_
== other
.whitelist_
&&
266 extension_types_
== other
.extension_types_
&&
267 contexts_
== other
.contexts_
&&
268 matches_
== other
.matches_
&&
269 location_
== other
.location_
&&
270 platforms_
== other
.platforms_
&&
271 min_manifest_version_
== other
.min_manifest_version_
&&
272 max_manifest_version_
== other
.max_manifest_version_
&&
273 channel_
== other
.channel_
&&
274 has_parent_
== other
.has_parent_
&&
275 channel_has_been_set_
== other
.channel_has_been_set_
;
278 std::string
SimpleFeature::Parse(const base::DictionaryValue
* value
) {
279 ParseURLPatterns(value
, "matches", &matches_
);
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_
);
292 ParseEnum
<VersionInfo::Channel
>(
293 value
, "channel", &channel_
,
294 g_mappings
.Get().channels
);
297 value
->GetBoolean("noparent", &no_parent_
);
299 // The "trunk" channel uses VersionInfo::CHANNEL_UNKNOWN, so we need to keep
300 // track of whether the channel has been set or not separately.
301 channel_has_been_set_
|= value
->HasKey("channel");
302 if (!channel_has_been_set_
&& dependencies_
.empty())
303 return name() + ": Must supply a value for channel or dependencies.";
305 if (matches_
.is_empty() && contexts_
.count(WEB_PAGE_CONTEXT
) != 0) {
306 return name() + ": Allowing web_page contexts requires supplying a value " +
310 return std::string();
313 Feature::Availability
SimpleFeature::IsAvailableToManifest(
314 const std::string
& extension_id
,
317 int manifest_version
,
318 Platform platform
) const {
319 // Component extensions can access any feature.
320 if (location
== COMPONENT_LOCATION
)
321 return CreateAvailability(IS_AVAILABLE
, type
);
323 if (!whitelist_
.empty()) {
324 if (!IsIdInWhitelist(extension_id
)) {
325 // TODO(aa): This is gross. There should be a better way to test the
327 CommandLine
* command_line
= CommandLine::ForCurrentProcess();
328 if (!command_line
->HasSwitch(switches::kWhitelistedExtensionID
))
329 return CreateAvailability(NOT_FOUND_IN_WHITELIST
, type
);
331 std::string whitelist_switch_value
=
332 CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
333 switches::kWhitelistedExtensionID
);
334 if (extension_id
!= whitelist_switch_value
)
335 return CreateAvailability(NOT_FOUND_IN_WHITELIST
, type
);
339 // HACK(kalman): user script -> extension. Solve this in a more generic way
340 // when we compile feature files.
341 Manifest::Type type_to_check
= (type
== Manifest::TYPE_USER_SCRIPT
) ?
342 Manifest::TYPE_EXTENSION
: type
;
343 if (!extension_types_
.empty() &&
344 extension_types_
.find(type_to_check
) == extension_types_
.end()) {
345 return CreateAvailability(INVALID_TYPE
, type
);
348 if (location_
!= UNSPECIFIED_LOCATION
&& location_
!= location
)
349 return CreateAvailability(INVALID_LOCATION
, type
);
351 if (!platforms_
.empty() &&
352 platforms_
.find(platform
) == platforms_
.end())
353 return CreateAvailability(INVALID_PLATFORM
, type
);
355 if (min_manifest_version_
!= 0 && manifest_version
< min_manifest_version_
)
356 return CreateAvailability(INVALID_MIN_MANIFEST_VERSION
, type
);
358 if (max_manifest_version_
!= 0 && manifest_version
> max_manifest_version_
)
359 return CreateAvailability(INVALID_MAX_MANIFEST_VERSION
, type
);
361 if (channel_has_been_set_
&& channel_
< GetCurrentChannel())
362 return CreateAvailability(UNSUPPORTED_CHANNEL
, type
);
364 return CreateAvailability(IS_AVAILABLE
, type
);
367 Feature::Availability
SimpleFeature::IsAvailableToContext(
368 const Extension
* extension
,
369 SimpleFeature::Context context
,
371 SimpleFeature::Platform platform
) const {
373 Availability result
= IsAvailableToManifest(
375 extension
->GetType(),
376 ConvertLocation(extension
->location()),
377 extension
->manifest_version(),
379 if (!result
.is_available())
383 if (!contexts_
.empty() && contexts_
.find(context
) == contexts_
.end())
384 return CreateAvailability(INVALID_CONTEXT
, context
);
386 if (!matches_
.is_empty() && !matches_
.MatchesURL(url
))
387 return CreateAvailability(INVALID_URL
, url
);
389 return CreateAvailability(IS_AVAILABLE
);
392 std::string
SimpleFeature::GetAvailabilityMessage(
393 AvailabilityResult result
,
396 Context context
) const {
399 return std::string();
400 case NOT_FOUND_IN_WHITELIST
:
401 return base::StringPrintf(
402 "'%s' is not allowed for specified extension ID.",
405 return base::StringPrintf("'%s' is not allowed on %s.",
406 name().c_str(), url
.spec().c_str());
408 return base::StringPrintf(
409 "'%s' is only allowed for %s, but this is a %s.",
411 ListDisplayNames(std::vector
<Manifest::Type
>(
412 extension_types_
.begin(), extension_types_
.end())).c_str(),
413 GetDisplayName(type
).c_str());
414 case INVALID_CONTEXT
:
415 return base::StringPrintf(
416 "'%s' is only allowed to run in %s, but this is a %s",
418 ListDisplayNames(std::vector
<Context
>(
419 contexts_
.begin(), contexts_
.end())).c_str(),
420 GetDisplayName(context
).c_str());
421 case INVALID_LOCATION
:
422 return base::StringPrintf(
423 "'%s' is not allowed for specified install location.",
425 case INVALID_PLATFORM
:
426 return base::StringPrintf(
427 "'%s' is not allowed for specified platform.",
429 case INVALID_MIN_MANIFEST_VERSION
:
430 return base::StringPrintf(
431 "'%s' requires manifest version of at least %d.",
433 min_manifest_version_
);
434 case INVALID_MAX_MANIFEST_VERSION
:
435 return base::StringPrintf(
436 "'%s' requires manifest version of %d or lower.",
438 max_manifest_version_
);
440 return base::StringPrintf(
441 "'%s' requires a different Feature that is not present.",
443 case UNSUPPORTED_CHANNEL
:
444 return base::StringPrintf(
445 "'%s' requires Google Chrome %s channel or newer, but this is the "
448 GetChannelName(channel_
).c_str(),
449 GetChannelName(GetCurrentChannel()).c_str());
453 return std::string();
456 Feature::Availability
SimpleFeature::CreateAvailability(
457 AvailabilityResult result
) const {
459 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, GURL(),
460 UNSPECIFIED_CONTEXT
));
463 Feature::Availability
SimpleFeature::CreateAvailability(
464 AvailabilityResult result
, Manifest::Type type
) const {
465 return Availability(result
, GetAvailabilityMessage(result
, type
, GURL(),
466 UNSPECIFIED_CONTEXT
));
469 Feature::Availability
SimpleFeature::CreateAvailability(
470 AvailabilityResult result
,
471 const GURL
& url
) const {
473 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, url
,
474 UNSPECIFIED_CONTEXT
));
477 Feature::Availability
SimpleFeature::CreateAvailability(
478 AvailabilityResult result
,
479 Context context
) const {
481 result
, GetAvailabilityMessage(result
, Manifest::TYPE_UNKNOWN
, GURL(),
485 std::set
<Feature::Context
>* SimpleFeature::GetContexts() {
489 bool SimpleFeature::IsInternal() const {
494 bool SimpleFeature::IsIdInWhitelist(const std::string
& extension_id
) const {
495 return IsIdInWhitelist(extension_id
, whitelist_
);
499 bool SimpleFeature::IsIdInWhitelist(const std::string
& extension_id
,
500 const std::set
<std::string
>& whitelist
) {
501 // Belt-and-suspenders philosophy here. We should be pretty confident by this
502 // point that we've validated the extension ID format, but in case something
503 // slips through, we avoid a class of attack where creative ID manipulation
504 // leads to hash collisions.
505 if (extension_id
.length() != 32) // 128 bits / 4 = 32 mpdecimal characters
508 if (whitelist
.find(extension_id
) != whitelist
.end() ||
509 whitelist
.find(HashExtensionId(extension_id
)) != whitelist
.end()) {
516 } // namespace extensions