Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(51)

Side by Side Diff: chrome/common/extensions/features/simple_feature.cc

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

Powered by Google App Engine
This is Rietveld 408576698