• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
Mappingsextensions::__anond43ba7fd0111::Mappings24   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 
ParseSet(const base::DictionaryValue * value,const std::string & property,std::set<std::string> * set)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>
ParseEnum(const std::string & string_value,T * enum_value,const std::map<std::string,T> & mapping)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>
ParseEnum(const base::DictionaryValue * value,const std::string & property,T * enum_value,const std::map<std::string,T> & mapping)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>
ParseEnumSet(const base::DictionaryValue * value,const std::string & property,std::set<T> * enum_set,const std::map<std::string,T> & mapping)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 
ParseURLPatterns(const base::DictionaryValue * value,const std::string & key,URLPatternSet * set)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.
GetDisplayName(Manifest::Type type)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     case Manifest::NUM_LOAD_TYPES:
167       NOTREACHED();
168   }
169   NOTREACHED();
170   return "";
171 }
172 
173 // Gets a human-readable name for the given context type, suitable for giving
174 // to developers in an error message.
GetDisplayName(Feature::Context context)175 std::string GetDisplayName(Feature::Context context) {
176   switch (context) {
177     case Feature::UNSPECIFIED_CONTEXT:
178       return "unknown";
179     case Feature::BLESSED_EXTENSION_CONTEXT:
180       // "privileged" is vague but hopefully the developer will understand that
181       // means background or app window.
182       return "privileged page";
183     case Feature::UNBLESSED_EXTENSION_CONTEXT:
184       // "iframe" is a bit of a lie/oversimplification, but that's the most
185       // common unblessed context.
186       return "extension iframe";
187     case Feature::CONTENT_SCRIPT_CONTEXT:
188       return "content script";
189     case Feature::WEB_PAGE_CONTEXT:
190       return "web page";
191     case Feature::BLESSED_WEB_PAGE_CONTEXT:
192       return "hosted app";
193   }
194   NOTREACHED();
195   return "";
196 }
197 
198 // Gets a human-readable list of the display names (pluralized, comma separated
199 // with the "and" in the correct place) for each of |enum_types|.
200 template <typename EnumType>
ListDisplayNames(const std::vector<EnumType> enum_types)201 std::string ListDisplayNames(const std::vector<EnumType> enum_types) {
202   std::string display_name_list;
203   for (size_t i = 0; i < enum_types.size(); ++i) {
204     // Pluralize type name.
205     display_name_list += GetDisplayName(enum_types[i]) + "s";
206     // Comma-separate entries, with an Oxford comma if there is more than 2
207     // total entries.
208     if (enum_types.size() > 2) {
209       if (i < enum_types.size() - 2)
210         display_name_list += ", ";
211       else if (i == enum_types.size() - 2)
212         display_name_list += ", and ";
213     } else if (enum_types.size() == 2 && i == 0) {
214       display_name_list += " and ";
215     }
216   }
217   return display_name_list;
218 }
219 
HashExtensionId(const std::string & extension_id)220 std::string HashExtensionId(const std::string& extension_id) {
221   const std::string id_hash = base::SHA1HashString(extension_id);
222   DCHECK(id_hash.length() == base::kSHA1Length);
223   return base::HexEncode(id_hash.c_str(), id_hash.length());
224 }
225 
226 }  // namespace
227 
SimpleFeature()228 SimpleFeature::SimpleFeature()
229     : location_(UNSPECIFIED_LOCATION),
230       min_manifest_version_(0),
231       max_manifest_version_(0),
232       has_parent_(false),
233       component_extensions_auto_granted_(true) {}
234 
~SimpleFeature()235 SimpleFeature::~SimpleFeature() {}
236 
AddFilter(scoped_ptr<SimpleFeatureFilter> filter)237 void SimpleFeature::AddFilter(scoped_ptr<SimpleFeatureFilter> filter) {
238   filters_.push_back(make_linked_ptr(filter.release()));
239 }
240 
Parse(const base::DictionaryValue * value)241 std::string SimpleFeature::Parse(const base::DictionaryValue* value) {
242   ParseURLPatterns(value, "matches", &matches_);
243   ParseSet(value, "blacklist", &blacklist_);
244   ParseSet(value, "whitelist", &whitelist_);
245   ParseSet(value, "dependencies", &dependencies_);
246   ParseEnumSet<Manifest::Type>(value, "extension_types", &extension_types_,
247                                 g_mappings.Get().extension_types);
248   ParseEnumSet<Context>(value, "contexts", &contexts_,
249                         g_mappings.Get().contexts);
250   ParseEnum<Location>(value, "location", &location_,
251                       g_mappings.Get().locations);
252   ParseEnumSet<Platform>(value, "platforms", &platforms_,
253                          g_mappings.Get().platforms);
254   value->GetInteger("min_manifest_version", &min_manifest_version_);
255   value->GetInteger("max_manifest_version", &max_manifest_version_);
256 
257   no_parent_ = false;
258   value->GetBoolean("noparent", &no_parent_);
259 
260   component_extensions_auto_granted_ = true;
261   value->GetBoolean("component_extensions_auto_granted",
262                     &component_extensions_auto_granted_);
263 
264   if (matches_.is_empty() && contexts_.count(WEB_PAGE_CONTEXT) != 0) {
265     return name() + ": Allowing web_page contexts requires supplying a value " +
266         "for matches.";
267   }
268 
269   for (FilterList::iterator filter_iter = filters_.begin();
270        filter_iter != filters_.end();
271        ++filter_iter) {
272     std::string result = (*filter_iter)->Parse(value);
273     if (!result.empty()) {
274       return result;
275     }
276   }
277 
278   return std::string();
279 }
280 
IsAvailableToManifest(const std::string & extension_id,Manifest::Type type,Manifest::Location location,int manifest_version,Platform platform) const281 Feature::Availability SimpleFeature::IsAvailableToManifest(
282     const std::string& extension_id,
283     Manifest::Type type,
284     Manifest::Location location,
285     int manifest_version,
286     Platform platform) const {
287   // Check extension type first to avoid granting platform app permissions
288   // to component extensions.
289   // HACK(kalman): user script -> extension. Solve this in a more generic way
290   // when we compile feature files.
291   Manifest::Type type_to_check = (type == Manifest::TYPE_USER_SCRIPT) ?
292       Manifest::TYPE_EXTENSION : type;
293   if (!extension_types_.empty() &&
294       extension_types_.find(type_to_check) == extension_types_.end()) {
295     return CreateAvailability(INVALID_TYPE, type);
296   }
297 
298   if (IsIdInBlacklist(extension_id))
299     return CreateAvailability(FOUND_IN_BLACKLIST, type);
300 
301   // TODO(benwells): don't grant all component extensions.
302   // See http://crbug.com/370375 for more details.
303   // Component extensions can access any feature.
304   // NOTE: Deliberately does not match EXTERNAL_COMPONENT.
305   if (component_extensions_auto_granted_ && location == Manifest::COMPONENT)
306     return CreateAvailability(IS_AVAILABLE, type);
307 
308   if (!whitelist_.empty()) {
309     if (!IsIdInWhitelist(extension_id)) {
310       // TODO(aa): This is gross. There should be a better way to test the
311       // whitelist.
312       CommandLine* command_line = CommandLine::ForCurrentProcess();
313       if (!command_line->HasSwitch(switches::kWhitelistedExtensionID))
314         return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
315 
316       std::string whitelist_switch_value =
317           CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
318               switches::kWhitelistedExtensionID);
319       if (extension_id != whitelist_switch_value)
320         return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
321     }
322   }
323 
324   if (!MatchesManifestLocation(location))
325     return CreateAvailability(INVALID_LOCATION, type);
326 
327   if (!platforms_.empty() &&
328       platforms_.find(platform) == platforms_.end())
329     return CreateAvailability(INVALID_PLATFORM, type);
330 
331   if (min_manifest_version_ != 0 && manifest_version < min_manifest_version_)
332     return CreateAvailability(INVALID_MIN_MANIFEST_VERSION, type);
333 
334   if (max_manifest_version_ != 0 && manifest_version > max_manifest_version_)
335     return CreateAvailability(INVALID_MAX_MANIFEST_VERSION, type);
336 
337   for (FilterList::const_iterator filter_iter = filters_.begin();
338        filter_iter != filters_.end();
339        ++filter_iter) {
340     Availability availability = (*filter_iter)->IsAvailableToManifest(
341         extension_id, type, location, manifest_version, platform);
342     if (!availability.is_available())
343       return availability;
344   }
345 
346   return CreateAvailability(IS_AVAILABLE, type);
347 }
348 
IsAvailableToContext(const Extension * extension,SimpleFeature::Context context,const GURL & url,SimpleFeature::Platform platform) const349 Feature::Availability SimpleFeature::IsAvailableToContext(
350     const Extension* extension,
351     SimpleFeature::Context context,
352     const GURL& url,
353     SimpleFeature::Platform platform) const {
354   if (extension) {
355     Availability result = IsAvailableToManifest(extension->id(),
356                                                 extension->GetType(),
357                                                 extension->location(),
358                                                 extension->manifest_version(),
359                                                 platform);
360     if (!result.is_available())
361       return result;
362   }
363 
364   if (!contexts_.empty() && contexts_.find(context) == contexts_.end())
365     return CreateAvailability(INVALID_CONTEXT, context);
366 
367   if (!matches_.is_empty() && !matches_.MatchesURL(url))
368     return CreateAvailability(INVALID_URL, url);
369 
370   for (FilterList::const_iterator filter_iter = filters_.begin();
371        filter_iter != filters_.end();
372        ++filter_iter) {
373     Availability availability =
374         (*filter_iter)->IsAvailableToContext(extension, context, url, platform);
375     if (!availability.is_available())
376       return availability;
377   }
378 
379   return CreateAvailability(IS_AVAILABLE);
380 }
381 
GetAvailabilityMessage(AvailabilityResult result,Manifest::Type type,const GURL & url,Context context) const382 std::string SimpleFeature::GetAvailabilityMessage(
383     AvailabilityResult result,
384     Manifest::Type type,
385     const GURL& url,
386     Context context) const {
387   switch (result) {
388     case IS_AVAILABLE:
389       return std::string();
390     case NOT_FOUND_IN_WHITELIST:
391     case FOUND_IN_BLACKLIST:
392       return base::StringPrintf(
393           "'%s' is not allowed for specified extension ID.",
394           name().c_str());
395     case INVALID_URL:
396       return base::StringPrintf("'%s' is not allowed on %s.",
397                                 name().c_str(), url.spec().c_str());
398     case INVALID_TYPE:
399       return base::StringPrintf(
400           "'%s' is only allowed for %s, but this is a %s.",
401           name().c_str(),
402           ListDisplayNames(std::vector<Manifest::Type>(
403               extension_types_.begin(), extension_types_.end())).c_str(),
404           GetDisplayName(type).c_str());
405     case INVALID_CONTEXT:
406       return base::StringPrintf(
407           "'%s' is only allowed to run in %s, but this is a %s",
408           name().c_str(),
409           ListDisplayNames(std::vector<Context>(
410               contexts_.begin(), contexts_.end())).c_str(),
411           GetDisplayName(context).c_str());
412     case INVALID_LOCATION:
413       return base::StringPrintf(
414           "'%s' is not allowed for specified install location.",
415           name().c_str());
416     case INVALID_PLATFORM:
417       return base::StringPrintf(
418           "'%s' is not allowed for specified platform.",
419           name().c_str());
420     case INVALID_MIN_MANIFEST_VERSION:
421       return base::StringPrintf(
422           "'%s' requires manifest version of at least %d.",
423           name().c_str(),
424           min_manifest_version_);
425     case INVALID_MAX_MANIFEST_VERSION:
426       return base::StringPrintf(
427           "'%s' requires manifest version of %d or lower.",
428           name().c_str(),
429           max_manifest_version_);
430     case NOT_PRESENT:
431       return base::StringPrintf(
432           "'%s' requires a different Feature that is not present.",
433           name().c_str());
434     case UNSUPPORTED_CHANNEL:
435       return base::StringPrintf(
436           "'%s' is unsupported in this version of the platform.",
437           name().c_str());
438   }
439 
440   NOTREACHED();
441   return std::string();
442 }
443 
CreateAvailability(AvailabilityResult result) const444 Feature::Availability SimpleFeature::CreateAvailability(
445     AvailabilityResult result) const {
446   return Availability(
447       result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
448                                      UNSPECIFIED_CONTEXT));
449 }
450 
CreateAvailability(AvailabilityResult result,Manifest::Type type) const451 Feature::Availability SimpleFeature::CreateAvailability(
452     AvailabilityResult result, Manifest::Type type) const {
453   return Availability(result, GetAvailabilityMessage(result, type, GURL(),
454                                                      UNSPECIFIED_CONTEXT));
455 }
456 
CreateAvailability(AvailabilityResult result,const GURL & url) const457 Feature::Availability SimpleFeature::CreateAvailability(
458     AvailabilityResult result,
459     const GURL& url) const {
460   return Availability(
461       result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, url,
462                                      UNSPECIFIED_CONTEXT));
463 }
464 
CreateAvailability(AvailabilityResult result,Context context) const465 Feature::Availability SimpleFeature::CreateAvailability(
466     AvailabilityResult result,
467     Context context) const {
468   return Availability(
469       result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
470                                      context));
471 }
472 
GetContexts()473 std::set<Feature::Context>* SimpleFeature::GetContexts() {
474   return &contexts_;
475 }
476 
IsInternal() const477 bool SimpleFeature::IsInternal() const {
478   return false;
479 }
480 
IsBlockedInServiceWorker() const481 bool SimpleFeature::IsBlockedInServiceWorker() const { return false; }
482 
IsIdInBlacklist(const std::string & extension_id) const483 bool SimpleFeature::IsIdInBlacklist(const std::string& extension_id) const {
484   return IsIdInList(extension_id, blacklist_);
485 }
486 
IsIdInWhitelist(const std::string & extension_id) const487 bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id) const {
488   return IsIdInList(extension_id, whitelist_);
489 }
490 
491 // static
IsIdInList(const std::string & extension_id,const std::set<std::string> & list)492 bool SimpleFeature::IsIdInList(const std::string& extension_id,
493                                const std::set<std::string>& list) {
494   // Belt-and-suspenders philosophy here. We should be pretty confident by this
495   // point that we've validated the extension ID format, but in case something
496   // slips through, we avoid a class of attack where creative ID manipulation
497   // leads to hash collisions.
498   if (extension_id.length() != 32)  // 128 bits / 4 = 32 mpdecimal characters
499     return false;
500 
501   if (list.find(extension_id) != list.end() ||
502       list.find(HashExtensionId(extension_id)) != list.end()) {
503     return true;
504   }
505 
506   return false;
507 }
508 
MatchesManifestLocation(Manifest::Location manifest_location) const509 bool SimpleFeature::MatchesManifestLocation(
510     Manifest::Location manifest_location) const {
511   switch (location_) {
512     case SimpleFeature::UNSPECIFIED_LOCATION:
513       return true;
514     case SimpleFeature::COMPONENT_LOCATION:
515       // TODO(kalman/asargent): Should this include EXTERNAL_COMPONENT too?
516       return manifest_location == Manifest::COMPONENT;
517     case SimpleFeature::POLICY_LOCATION:
518       return manifest_location == Manifest::EXTERNAL_POLICY ||
519              manifest_location == Manifest::EXTERNAL_POLICY_DOWNLOAD;
520   }
521   NOTREACHED();
522   return false;
523 }
524 
525 }  // namespace extensions
526