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