Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / common / extensions / features / simple_feature.cc
blob01431ced016ec21768d34b7cd3e5f973154bd007
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"
7 #include <map>
8 #include <vector>
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 {
23 namespace {
25 struct Mappings {
26 Mappings() {
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)
68 return i->first;
70 NOTREACHED();
71 return "unknown";
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))
81 return;
83 set->clear();
84 for (size_t i = 0; i < list_value->GetSize(); ++i) {
85 std::string str_val;
86 CHECK(list_value->GetString(i, &str_val)) << property << " " << i;
87 set->insert(str_val);
91 template<typename T>
92 void ParseEnum(const std::string& string_value,
93 T* enum_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;
101 template<typename T>
102 void ParseEnum(const base::DictionaryValue* value,
103 const std::string& property,
104 T* enum_value,
105 const std::map<std::string, T>& mapping) {
106 std::string string_value;
107 if (!value->GetString(property, &string_value))
108 return;
110 ParseEnum(string_value, enum_value, mapping);
113 template<typename T>
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))
119 return;
121 enum_set->clear();
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);
131 return;
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) {
151 std::string pattern;
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) {
161 switch (type) {
162 case Manifest::TYPE_UNKNOWN:
163 return "unknown";
164 case Manifest::TYPE_EXTENSION:
165 return "extension";
166 case Manifest::TYPE_HOSTED_APP:
167 return "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:
173 return "theme";
174 case Manifest::TYPE_USER_SCRIPT:
175 return "user script";
176 case Manifest::TYPE_SHARED_MODULE:
177 return "shared module";
179 NOTREACHED();
180 return "";
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) {
186 switch (context) {
187 case Feature::UNSPECIFIED_CONTEXT:
188 return "unknown";
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:
200 return "web page";
201 case Feature::BLESSED_WEB_PAGE_CONTEXT:
202 return "hosted app";
204 NOTREACHED();
205 return "";
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
217 // total entries.
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());
236 } // namespace
238 SimpleFeature::SimpleFeature()
239 : location_(UNSPECIFIED_LOCATION),
240 min_manifest_version_(0),
241 max_manifest_version_(0),
242 channel_(VersionInfo::CHANNEL_UNKNOWN),
243 has_parent_(false),
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);
296 no_parent_ = false;
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 " +
307 "for matches.";
310 return std::string();
313 Feature::Availability SimpleFeature::IsAvailableToManifest(
314 const std::string& extension_id,
315 Manifest::Type type,
316 Location location,
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
326 // whitelist.
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,
370 const GURL& url,
371 SimpleFeature::Platform platform) const {
372 if (extension) {
373 Availability result = IsAvailableToManifest(
374 extension->id(),
375 extension->GetType(),
376 ConvertLocation(extension->location()),
377 extension->manifest_version(),
378 platform);
379 if (!result.is_available())
380 return result;
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,
394 Manifest::Type type,
395 const GURL& url,
396 Context context) const {
397 switch (result) {
398 case IS_AVAILABLE:
399 return std::string();
400 case NOT_FOUND_IN_WHITELIST:
401 return base::StringPrintf(
402 "'%s' is not allowed for specified extension ID.",
403 name().c_str());
404 case INVALID_URL:
405 return base::StringPrintf("'%s' is not allowed on %s.",
406 name().c_str(), url.spec().c_str());
407 case INVALID_TYPE:
408 return base::StringPrintf(
409 "'%s' is only allowed for %s, but this is a %s.",
410 name().c_str(),
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",
417 name().c_str(),
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.",
424 name().c_str());
425 case INVALID_PLATFORM:
426 return base::StringPrintf(
427 "'%s' is not allowed for specified platform.",
428 name().c_str());
429 case INVALID_MIN_MANIFEST_VERSION:
430 return base::StringPrintf(
431 "'%s' requires manifest version of at least %d.",
432 name().c_str(),
433 min_manifest_version_);
434 case INVALID_MAX_MANIFEST_VERSION:
435 return base::StringPrintf(
436 "'%s' requires manifest version of %d or lower.",
437 name().c_str(),
438 max_manifest_version_);
439 case NOT_PRESENT:
440 return base::StringPrintf(
441 "'%s' requires a different Feature that is not present.",
442 name().c_str());
443 case UNSUPPORTED_CHANNEL:
444 return base::StringPrintf(
445 "'%s' requires Google Chrome %s channel or newer, but this is the "
446 "%s channel.",
447 name().c_str(),
448 GetChannelName(channel_).c_str(),
449 GetChannelName(GetCurrentChannel()).c_str());
452 NOTREACHED();
453 return std::string();
456 Feature::Availability SimpleFeature::CreateAvailability(
457 AvailabilityResult result) const {
458 return Availability(
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 {
472 return Availability(
473 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, url,
474 UNSPECIFIED_CONTEXT));
477 Feature::Availability SimpleFeature::CreateAvailability(
478 AvailabilityResult result,
479 Context context) const {
480 return Availability(
481 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
482 context));
485 std::set<Feature::Context>* SimpleFeature::GetContexts() {
486 return &contexts_;
489 bool SimpleFeature::IsInternal() const {
490 NOTREACHED();
491 return false;
494 bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id) const {
495 return IsIdInWhitelist(extension_id, whitelist_);
498 // static
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
506 return false;
508 if (whitelist.find(extension_id) != whitelist.end() ||
509 whitelist.find(HashExtensionId(extension_id)) != whitelist.end()) {
510 return true;
513 return false;
516 } // namespace extensions