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