simple_feature.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
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
19using chrome::VersionInfo;
20
21namespace extensions {
22
23namespace {
24
25struct 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
60base::LazyInstance<Mappings> g_mappings = LAZY_INSTANCE_INITIALIZER;
61
62std::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
75void 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
90template<typename T>
91void 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
100template<typename T>
101void 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
112template<typename T>
113void 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
143void 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.
159std::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.
184std::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|.
207template <typename EnumType>
208std::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
227std::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
235SimpleFeature::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
244SimpleFeature::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
258SimpleFeature::~SimpleFeature() {
259}
260
261bool 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
275std::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
310Feature::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
364Feature::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
389std::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
453Feature::Availability SimpleFeature::CreateAvailability(
454    AvailabilityResult result) const {
455  return Availability(
456      result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
457                                     UNSPECIFIED_CONTEXT));
458}
459
460Feature::Availability SimpleFeature::CreateAvailability(
461    AvailabilityResult result, Manifest::Type type) const {
462  return Availability(result, GetAvailabilityMessage(result, type, GURL(),
463                                                     UNSPECIFIED_CONTEXT));
464}
465
466Feature::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
474Feature::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
482std::set<Feature::Context>* SimpleFeature::GetContexts() {
483  return &contexts_;
484}
485
486bool SimpleFeature::IsInternal() const {
487  NOTREACHED();
488  return false;
489}
490
491bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id) const {
492  return IsIdInWhitelist(extension_id, whitelist_);
493}
494
495// static
496bool 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
514