- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / features / simple_feature.cc
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.
4
5 #include "chrome/common/extensions/features/simple_feature.h"
6
7 #include <map>
8 #include <vector>
9
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"
18
19 using chrome::VersionInfo;
20
21 namespace extensions {
22
23 namespace {
24
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;
33
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
39     locations["component"] = Feature::COMPONENT_LOCATION;
40
41     platforms["chromeos"] = Feature::CHROMEOS_PLATFORM;
42     platforms["linux"] = Feature::LINUX_PLATFORM;
43     platforms["macosx"] = Feature::MACOSX_PLATFORM;
44     platforms["windows"] = Feature::WIN_PLATFORM;
45
46     channels["trunk"] = VersionInfo::CHANNEL_UNKNOWN;
47     channels["canary"] = VersionInfo::CHANNEL_CANARY;
48     channels["dev"] = VersionInfo::CHANNEL_DEV;
49     channels["beta"] = VersionInfo::CHANNEL_BETA;
50     channels["stable"] = VersionInfo::CHANNEL_STABLE;
51   }
52
53   std::map<std::string, Manifest::Type> extension_types;
54   std::map<std::string, Feature::Context> contexts;
55   std::map<std::string, Feature::Location> locations;
56   std::map<std::string, Feature::Platform> platforms;
57   std::map<std::string, VersionInfo::Channel> channels;
58 };
59
60 base::LazyInstance<Mappings> g_mappings = LAZY_INSTANCE_INITIALIZER;
61
62 std::string GetChannelName(VersionInfo::Channel channel) {
63   typedef std::map<std::string, VersionInfo::Channel> ChannelsMap;
64   ChannelsMap channels = g_mappings.Get().channels;
65   for (ChannelsMap::iterator i = channels.begin(); i != channels.end(); ++i) {
66     if (i->second == channel)
67       return i->first;
68   }
69   NOTREACHED();
70   return "unknown";
71 }
72
73 // TODO(aa): Can we replace all this manual parsing with JSON schema stuff?
74
75 void ParseSet(const base::DictionaryValue* value,
76               const std::string& property,
77               std::set<std::string>* set) {
78   const base::ListValue* list_value = NULL;
79   if (!value->GetList(property, &list_value))
80     return;
81
82   set->clear();
83   for (size_t i = 0; i < list_value->GetSize(); ++i) {
84     std::string str_val;
85     CHECK(list_value->GetString(i, &str_val)) << property << " " << i;
86     set->insert(str_val);
87   }
88 }
89
90 template<typename T>
91 void ParseEnum(const std::string& string_value,
92                T* enum_value,
93                const std::map<std::string, T>& mapping) {
94   typename std::map<std::string, T>::const_iterator iter =
95       mapping.find(string_value);
96   CHECK(iter != mapping.end()) << string_value;
97   *enum_value = iter->second;
98 }
99
100 template<typename T>
101 void ParseEnum(const base::DictionaryValue* value,
102                const std::string& property,
103                T* enum_value,
104                const std::map<std::string, T>& mapping) {
105   std::string string_value;
106   if (!value->GetString(property, &string_value))
107     return;
108
109   ParseEnum(string_value, enum_value, mapping);
110 }
111
112 template<typename T>
113 void ParseEnumSet(const base::DictionaryValue* value,
114                   const std::string& property,
115                   std::set<T>* enum_set,
116                   const std::map<std::string, T>& mapping) {
117   if (!value->HasKey(property))
118     return;
119
120   enum_set->clear();
121
122   std::string property_string;
123   if (value->GetString(property, &property_string)) {
124     if (property_string == "all") {
125       for (typename std::map<std::string, T>::const_iterator j =
126                mapping.begin(); j != mapping.end(); ++j) {
127         enum_set->insert(j->second);
128       }
129     }
130     return;
131   }
132
133   std::set<std::string> string_set;
134   ParseSet(value, property, &string_set);
135   for (std::set<std::string>::iterator iter = string_set.begin();
136        iter != string_set.end(); ++iter) {
137     T enum_value = static_cast<T>(0);
138     ParseEnum(*iter, &enum_value, mapping);
139     enum_set->insert(enum_value);
140   }
141 }
142
143 void ParseURLPatterns(const base::DictionaryValue* value,
144                       const std::string& key,
145                       URLPatternSet* set) {
146   const base::ListValue* matches = NULL;
147   if (value->GetList(key, &matches)) {
148     set->ClearPatterns();
149     for (size_t i = 0; i < matches->GetSize(); ++i) {
150       std::string pattern;
151       CHECK(matches->GetString(i, &pattern));
152       set->AddPattern(URLPattern(URLPattern::SCHEME_ALL, pattern));
153     }
154   }
155 }
156
157 // Gets a human-readable name for the given extension type, suitable for giving
158 // to developers in an error message.
159 std::string GetDisplayName(Manifest::Type type) {
160   switch (type) {
161     case Manifest::TYPE_UNKNOWN:
162       return "unknown";
163     case Manifest::TYPE_EXTENSION:
164       return "extension";
165     case Manifest::TYPE_HOSTED_APP:
166       return "hosted app";
167     case Manifest::TYPE_LEGACY_PACKAGED_APP:
168       return "legacy packaged app";
169     case Manifest::TYPE_PLATFORM_APP:
170       return "packaged app";
171     case Manifest::TYPE_THEME:
172       return "theme";
173     case Manifest::TYPE_USER_SCRIPT:
174       return "user script";
175     case Manifest::TYPE_SHARED_MODULE:
176       return "shared module";
177   }
178   NOTREACHED();
179   return "";
180 }
181
182 // Gets a human-readable name for the given context type, suitable for giving
183 // to developers in an error message.
184 std::string GetDisplayName(Feature::Context context) {
185   switch (context) {
186     case Feature::UNSPECIFIED_CONTEXT:
187       return "unknown";
188     case Feature::BLESSED_EXTENSION_CONTEXT:
189       // "privileged" is vague but hopefully the developer will understand that
190       // means background or app window.
191       return "privileged page";
192     case Feature::UNBLESSED_EXTENSION_CONTEXT:
193       // "iframe" is a bit of a lie/oversimplification, but that's the most
194       // common unblessed context.
195       return "extension iframe";
196     case Feature::CONTENT_SCRIPT_CONTEXT:
197       return "content script";
198     case Feature::WEB_PAGE_CONTEXT:
199       return "web page";
200   }
201   NOTREACHED();
202   return "";
203 }
204
205 // Gets a human-readable list of the display names (pluralized, comma separated
206 // with the "and" in the correct place) for each of |enum_types|.
207 template <typename EnumType>
208 std::string ListDisplayNames(const std::vector<EnumType> enum_types) {
209   std::string display_name_list;
210   for (size_t i = 0; i < enum_types.size(); ++i) {
211     // Pluralize type name.
212     display_name_list += GetDisplayName(enum_types[i]) + "s";
213     // Comma-separate entries, with an Oxford comma if there is more than 2
214     // total entries.
215     if (enum_types.size() > 2) {
216       if (i < enum_types.size() - 2)
217         display_name_list += ", ";
218       else if (i == enum_types.size() - 2)
219         display_name_list += ", and ";
220     } else if (enum_types.size() == 2 && i == 0) {
221       display_name_list += " and ";
222     }
223   }
224   return display_name_list;
225 }
226
227 std::string HashExtensionId(const std::string& extension_id) {
228   const std::string id_hash = base::SHA1HashString(extension_id);
229   DCHECK(id_hash.length() == base::kSHA1Length);
230   return base::HexEncode(id_hash.c_str(), id_hash.length());
231 }
232
233 }  // namespace
234
235 SimpleFeature::SimpleFeature()
236   : location_(UNSPECIFIED_LOCATION),
237     min_manifest_version_(0),
238     max_manifest_version_(0),
239     channel_(VersionInfo::CHANNEL_UNKNOWN),
240     has_parent_(false),
241     channel_has_been_set_(false) {
242 }
243
244 SimpleFeature::SimpleFeature(const SimpleFeature& other)
245     : whitelist_(other.whitelist_),
246       extension_types_(other.extension_types_),
247       contexts_(other.contexts_),
248       matches_(other.matches_),
249       location_(other.location_),
250       platforms_(other.platforms_),
251       min_manifest_version_(other.min_manifest_version_),
252       max_manifest_version_(other.max_manifest_version_),
253       channel_(other.channel_),
254       has_parent_(other.has_parent_),
255       channel_has_been_set_(other.channel_has_been_set_) {
256 }
257
258 SimpleFeature::~SimpleFeature() {
259 }
260
261 bool SimpleFeature::Equals(const SimpleFeature& other) const {
262   return whitelist_ == other.whitelist_ &&
263       extension_types_ == other.extension_types_ &&
264       contexts_ == other.contexts_ &&
265       matches_ == other.matches_ &&
266       location_ == other.location_ &&
267       platforms_ == other.platforms_ &&
268       min_manifest_version_ == other.min_manifest_version_ &&
269       max_manifest_version_ == other.max_manifest_version_ &&
270       channel_ == other.channel_ &&
271       has_parent_ == other.has_parent_ &&
272       channel_has_been_set_ == other.channel_has_been_set_;
273 }
274
275 std::string SimpleFeature::Parse(const base::DictionaryValue* value) {
276   ParseURLPatterns(value, "matches", &matches_);
277   ParseSet(value, "whitelist", &whitelist_);
278   ParseSet(value, "dependencies", &dependencies_);
279   ParseEnumSet<Manifest::Type>(value, "extension_types", &extension_types_,
280                                 g_mappings.Get().extension_types);
281   ParseEnumSet<Context>(value, "contexts", &contexts_,
282                         g_mappings.Get().contexts);
283   ParseEnum<Location>(value, "location", &location_,
284                       g_mappings.Get().locations);
285   ParseEnumSet<Platform>(value, "platforms", &platforms_,
286                          g_mappings.Get().platforms);
287   value->GetInteger("min_manifest_version", &min_manifest_version_);
288   value->GetInteger("max_manifest_version", &max_manifest_version_);
289   ParseEnum<VersionInfo::Channel>(
290       value, "channel", &channel_,
291       g_mappings.Get().channels);
292
293   no_parent_ = false;
294   value->GetBoolean("noparent", &no_parent_);
295
296   // The "trunk" channel uses VersionInfo::CHANNEL_UNKNOWN, so we need to keep
297   // track of whether the channel has been set or not separately.
298   channel_has_been_set_ |= value->HasKey("channel");
299   if (!channel_has_been_set_ && dependencies_.empty())
300     return name() + ": Must supply a value for channel or dependencies.";
301
302   if (matches_.is_empty() && contexts_.count(WEB_PAGE_CONTEXT) != 0) {
303     return name() + ": Allowing web_page contexts requires supplying a value " +
304         "for matches.";
305   }
306
307   return std::string();
308 }
309
310 Feature::Availability SimpleFeature::IsAvailableToManifest(
311     const std::string& extension_id,
312     Manifest::Type type,
313     Location location,
314     int manifest_version,
315     Platform platform) const {
316   // Component extensions can access any feature.
317   if (location == COMPONENT_LOCATION)
318     return CreateAvailability(IS_AVAILABLE, type);
319
320   if (!whitelist_.empty()) {
321     if (!IsIdInWhitelist(extension_id)) {
322       // TODO(aa): This is gross. There should be a better way to test the
323       // whitelist.
324       CommandLine* command_line = CommandLine::ForCurrentProcess();
325       if (!command_line->HasSwitch(switches::kWhitelistedExtensionID))
326         return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
327
328       std::string whitelist_switch_value =
329           CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
330               switches::kWhitelistedExtensionID);
331       if (extension_id != whitelist_switch_value)
332         return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
333     }
334   }
335
336   // HACK(kalman): user script -> extension. Solve this in a more generic way
337   // when we compile feature files.
338   Manifest::Type type_to_check = (type == Manifest::TYPE_USER_SCRIPT) ?
339       Manifest::TYPE_EXTENSION : type;
340   if (!extension_types_.empty() &&
341       extension_types_.find(type_to_check) == extension_types_.end()) {
342     return CreateAvailability(INVALID_TYPE, type);
343   }
344
345   if (location_ != UNSPECIFIED_LOCATION && location_ != location)
346     return CreateAvailability(INVALID_LOCATION, type);
347
348   if (!platforms_.empty() &&
349       platforms_.find(platform) == platforms_.end())
350     return CreateAvailability(INVALID_PLATFORM, type);
351
352   if (min_manifest_version_ != 0 && manifest_version < min_manifest_version_)
353     return CreateAvailability(INVALID_MIN_MANIFEST_VERSION, type);
354
355   if (max_manifest_version_ != 0 && manifest_version > max_manifest_version_)
356     return CreateAvailability(INVALID_MAX_MANIFEST_VERSION, type);
357
358   if (channel_has_been_set_ && channel_ < GetCurrentChannel())
359     return CreateAvailability(UNSUPPORTED_CHANNEL, type);
360
361   return CreateAvailability(IS_AVAILABLE, type);
362 }
363
364 Feature::Availability SimpleFeature::IsAvailableToContext(
365     const Extension* extension,
366     SimpleFeature::Context context,
367     const GURL& url,
368     SimpleFeature::Platform platform) const {
369   if (extension) {
370     Availability result = IsAvailableToManifest(
371         extension->id(),
372         extension->GetType(),
373         ConvertLocation(extension->location()),
374         extension->manifest_version(),
375         platform);
376     if (!result.is_available())
377       return result;
378   }
379
380   if (!contexts_.empty() && contexts_.find(context) == contexts_.end())
381     return CreateAvailability(INVALID_CONTEXT, context);
382
383   if (!matches_.is_empty() && !matches_.MatchesURL(url))
384     return CreateAvailability(INVALID_URL, url);
385
386   return CreateAvailability(IS_AVAILABLE);
387 }
388
389 std::string SimpleFeature::GetAvailabilityMessage(
390     AvailabilityResult result,
391     Manifest::Type type,
392     const GURL& url,
393     Context context) const {
394   switch (result) {
395     case IS_AVAILABLE:
396       return std::string();
397     case NOT_FOUND_IN_WHITELIST:
398       return base::StringPrintf(
399           "'%s' is not allowed for specified extension ID.",
400           name().c_str());
401     case INVALID_URL:
402       return base::StringPrintf("'%s' is not allowed on %s.",
403                                 name().c_str(), url.spec().c_str());
404     case INVALID_TYPE:
405       return base::StringPrintf(
406           "'%s' is only allowed for %s, but this is a %s.",
407           name().c_str(),
408           ListDisplayNames(std::vector<Manifest::Type>(
409               extension_types_.begin(), extension_types_.end())).c_str(),
410           GetDisplayName(type).c_str());
411     case INVALID_CONTEXT:
412       return base::StringPrintf(
413           "'%s' is only allowed to run in %s, but this is a %s",
414           name().c_str(),
415           ListDisplayNames(std::vector<Context>(
416               contexts_.begin(), contexts_.end())).c_str(),
417           GetDisplayName(context).c_str());
418     case INVALID_LOCATION:
419       return base::StringPrintf(
420           "'%s' is not allowed for specified install location.",
421           name().c_str());
422     case INVALID_PLATFORM:
423       return base::StringPrintf(
424           "'%s' is not allowed for specified platform.",
425           name().c_str());
426     case INVALID_MIN_MANIFEST_VERSION:
427       return base::StringPrintf(
428           "'%s' requires manifest version of at least %d.",
429           name().c_str(),
430           min_manifest_version_);
431     case INVALID_MAX_MANIFEST_VERSION:
432       return base::StringPrintf(
433           "'%s' requires manifest version of %d or lower.",
434           name().c_str(),
435           max_manifest_version_);
436     case NOT_PRESENT:
437       return base::StringPrintf(
438           "'%s' requires a different Feature that is not present.",
439           name().c_str());
440     case UNSUPPORTED_CHANNEL:
441       return base::StringPrintf(
442           "'%s' requires Google Chrome %s channel or newer, but this is the "
443               "%s channel.",
444           name().c_str(),
445           GetChannelName(channel_).c_str(),
446           GetChannelName(GetCurrentChannel()).c_str());
447   }
448
449   NOTREACHED();
450   return std::string();
451 }
452
453 Feature::Availability SimpleFeature::CreateAvailability(
454     AvailabilityResult result) const {
455   return Availability(
456       result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
457                                      UNSPECIFIED_CONTEXT));
458 }
459
460 Feature::Availability SimpleFeature::CreateAvailability(
461     AvailabilityResult result, Manifest::Type type) const {
462   return Availability(result, GetAvailabilityMessage(result, type, GURL(),
463                                                      UNSPECIFIED_CONTEXT));
464 }
465
466 Feature::Availability SimpleFeature::CreateAvailability(
467     AvailabilityResult result,
468     const GURL& url) const {
469   return Availability(
470       result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, url,
471                                      UNSPECIFIED_CONTEXT));
472 }
473
474 Feature::Availability SimpleFeature::CreateAvailability(
475     AvailabilityResult result,
476     Context context) const {
477   return Availability(
478       result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
479                                      context));
480 }
481
482 std::set<Feature::Context>* SimpleFeature::GetContexts() {
483   return &contexts_;
484 }
485
486 bool SimpleFeature::IsInternal() const {
487   NOTREACHED();
488   return false;
489 }
490
491 bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id) const {
492   return IsIdInWhitelist(extension_id, whitelist_);
493 }
494
495 // static
496 bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id,
497                                     const std::set<std::string>& whitelist) {
498   // Belt-and-suspenders philosophy here. We should be pretty confident by this
499   // point that we've validated the extension ID format, but in case something
500   // slips through, we avoid a class of attack where creative ID manipulation
501   // leads to hash collisions.
502   if (extension_id.length() != 32)  // 128 bits / 4 = 32 mpdecimal characters
503     return false;
504
505   if (whitelist.find(extension_id) != whitelist.end() ||
506       whitelist.find(HashExtensionId(extension_id)) != whitelist.end()) {
507     return true;
508   }
509
510   return false;
511 }
512
513 }  // namespace extensions