OLD | NEW |
| (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 | |
OLD | NEW |