OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/common/extensions/features/feature.h" | 5 #include "chrome/common/extensions/features/feature.h" |
6 | 6 |
7 #include <map> | 7 #include <map> |
8 | 8 |
9 #include "base/command_line.h" | 9 #include "base/command_line.h" |
10 #include "base/lazy_instance.h" | 10 #include "base/lazy_instance.h" |
11 #include "base/stringprintf.h" | 11 #include "base/stringprintf.h" |
12 #include "base/string_util.h" | 12 #include "base/string_util.h" |
13 #include "chrome/common/chrome_switches.h" | 13 #include "chrome/common/chrome_switches.h" |
14 | 14 |
15 using chrome::VersionInfo; | 15 using chrome::VersionInfo; |
16 using extensions::Extension; | 16 using extensions::Extension; |
17 | 17 |
18 namespace { | 18 namespace { |
19 | 19 |
20 struct Mappings { | |
21 Mappings() { | |
22 extension_types["extension"] = Extension::TYPE_EXTENSION; | |
23 extension_types["theme"] = Extension::TYPE_THEME; | |
24 extension_types["packaged_app"] | |
25 = Extension::TYPE_LEGACY_PACKAGED_APP; | |
26 extension_types["hosted_app"] = Extension::TYPE_HOSTED_APP; | |
27 extension_types["platform_app"] = Extension::TYPE_PLATFORM_APP; | |
28 | |
29 contexts["blessed_extension"] = | |
30 extensions::Feature::BLESSED_EXTENSION_CONTEXT; | |
31 contexts["unblessed_extension"] = | |
32 extensions::Feature::UNBLESSED_EXTENSION_CONTEXT; | |
33 contexts["content_script"] = extensions::Feature::CONTENT_SCRIPT_CONTEXT; | |
34 contexts["web_page"] = extensions::Feature::WEB_PAGE_CONTEXT; | |
35 | |
36 locations["component"] = extensions::Feature::COMPONENT_LOCATION; | |
37 | |
38 platforms["chromeos"] = extensions::Feature::CHROMEOS_PLATFORM; | |
39 | |
40 channels["trunk"] = VersionInfo::CHANNEL_UNKNOWN; | |
41 channels["canary"] = VersionInfo::CHANNEL_CANARY; | |
42 channels["dev"] = VersionInfo::CHANNEL_DEV; | |
43 channels["beta"] = VersionInfo::CHANNEL_BETA; | |
44 channels["stable"] = VersionInfo::CHANNEL_STABLE; | |
45 } | |
46 | |
47 std::map<std::string, Extension::Type> extension_types; | |
48 std::map<std::string, extensions::Feature::Context> contexts; | |
49 std::map<std::string, extensions::Feature::Location> locations; | |
50 std::map<std::string, extensions::Feature::Platform> platforms; | |
51 std::map<std::string, VersionInfo::Channel> channels; | |
52 }; | |
53 | |
54 base::LazyInstance<Mappings> g_mappings = LAZY_INSTANCE_INITIALIZER; | |
55 | |
56 std::string GetChannelName(VersionInfo::Channel channel) { | |
57 typedef std::map<std::string, VersionInfo::Channel> ChannelsMap; | |
58 ChannelsMap channels = g_mappings.Get().channels; | |
59 for (ChannelsMap::iterator i = channels.begin(); i != channels.end(); ++i) { | |
60 if (i->second == channel) | |
61 return i->first; | |
62 } | |
63 NOTREACHED(); | |
64 return "unknown"; | |
65 } | |
66 | |
67 const VersionInfo::Channel kDefaultChannel = VersionInfo::CHANNEL_STABLE; | 20 const VersionInfo::Channel kDefaultChannel = VersionInfo::CHANNEL_STABLE; |
68 VersionInfo::Channel g_current_channel = kDefaultChannel; | 21 VersionInfo::Channel g_current_channel = kDefaultChannel; |
69 | 22 |
70 // TODO(aa): Can we replace all this manual parsing with JSON schema stuff? | |
71 | |
72 void ParseSet(const DictionaryValue* value, | |
73 const std::string& property, | |
74 std::set<std::string>* set) { | |
75 const ListValue* list_value = NULL; | |
76 if (!value->GetList(property, &list_value)) | |
77 return; | |
78 | |
79 set->clear(); | |
80 for (size_t i = 0; i < list_value->GetSize(); ++i) { | |
81 std::string str_val; | |
82 CHECK(list_value->GetString(i, &str_val)) << property << " " << i; | |
83 set->insert(str_val); | |
84 } | |
85 } | |
86 | |
87 template<typename T> | |
88 void ParseEnum(const std::string& string_value, | |
89 T* enum_value, | |
90 const std::map<std::string, T>& mapping) { | |
91 typename std::map<std::string, T>::const_iterator iter = | |
92 mapping.find(string_value); | |
93 CHECK(iter != mapping.end()) << string_value; | |
94 *enum_value = iter->second; | |
95 } | |
96 | |
97 template<typename T> | |
98 void ParseEnum(const DictionaryValue* value, | |
99 const std::string& property, | |
100 T* enum_value, | |
101 const std::map<std::string, T>& mapping) { | |
102 std::string string_value; | |
103 if (!value->GetString(property, &string_value)) | |
104 return; | |
105 | |
106 ParseEnum(string_value, enum_value, mapping); | |
107 } | |
108 | |
109 template<typename T> | |
110 void ParseEnumSet(const DictionaryValue* value, | |
111 const std::string& property, | |
112 std::set<T>* enum_set, | |
113 const std::map<std::string, T>& mapping) { | |
114 if (!value->HasKey(property)) | |
115 return; | |
116 | |
117 enum_set->clear(); | |
118 | |
119 std::string property_string; | |
120 if (value->GetString(property, &property_string)) { | |
121 if (property_string == "all") { | |
122 for (typename std::map<std::string, T>::const_iterator j = | |
123 mapping.begin(); j != mapping.end(); ++j) { | |
124 enum_set->insert(j->second); | |
125 } | |
126 } | |
127 return; | |
128 } | |
129 | |
130 std::set<std::string> string_set; | |
131 ParseSet(value, property, &string_set); | |
132 for (std::set<std::string>::iterator iter = string_set.begin(); | |
133 iter != string_set.end(); ++iter) { | |
134 T enum_value = static_cast<T>(0); | |
135 ParseEnum(*iter, &enum_value, mapping); | |
136 enum_set->insert(enum_value); | |
137 } | |
138 } | |
139 | |
140 // Gets a human-readable name for the given extension type. | |
141 std::string GetDisplayTypeName(Extension::Type type) { | |
142 switch (type) { | |
143 case Extension::TYPE_UNKNOWN: | |
144 return "unknown"; | |
145 case Extension::TYPE_EXTENSION: | |
146 return "extension"; | |
147 case Extension::TYPE_HOSTED_APP: | |
148 return "hosted app"; | |
149 case Extension::TYPE_LEGACY_PACKAGED_APP: | |
150 return "legacy packaged app"; | |
151 case Extension::TYPE_PLATFORM_APP: | |
152 return "packaged app"; | |
153 case Extension::TYPE_THEME: | |
154 return "theme"; | |
155 case Extension::TYPE_USER_SCRIPT: | |
156 return "user script"; | |
157 } | |
158 | |
159 NOTREACHED(); | |
160 return ""; | |
161 } | |
162 | |
163 } // namespace | 23 } // namespace |
164 | 24 |
165 namespace extensions { | 25 namespace extensions { |
166 | 26 |
167 Feature::Feature() | |
168 : location_(UNSPECIFIED_LOCATION), | |
169 platform_(UNSPECIFIED_PLATFORM), | |
170 min_manifest_version_(0), | |
171 max_manifest_version_(0), | |
172 channel_(VersionInfo::CHANNEL_UNKNOWN) { | |
173 } | |
174 | |
175 Feature::Feature(const Feature& other) | |
176 : whitelist_(other.whitelist_), | |
177 extension_types_(other.extension_types_), | |
178 contexts_(other.contexts_), | |
179 location_(other.location_), | |
180 platform_(other.platform_), | |
181 min_manifest_version_(other.min_manifest_version_), | |
182 max_manifest_version_(other.max_manifest_version_), | |
183 channel_(other.channel_) { | |
184 } | |
185 | |
186 Feature::~Feature() { | |
187 } | |
188 | |
189 bool Feature::Equals(const Feature& other) const { | |
190 return whitelist_ == other.whitelist_ && | |
191 extension_types_ == other.extension_types_ && | |
192 contexts_ == other.contexts_ && | |
193 location_ == other.location_ && | |
194 platform_ == other.platform_ && | |
195 min_manifest_version_ == other.min_manifest_version_ && | |
196 max_manifest_version_ == other.max_manifest_version_ && | |
197 channel_ == other.channel_; | |
198 } | |
199 | |
200 // static | 27 // static |
201 Feature::Platform Feature::GetCurrentPlatform() { | 28 Feature::Platform Feature::GetCurrentPlatform() { |
202 #if defined(OS_CHROMEOS) | 29 #if defined(OS_CHROMEOS) |
203 return CHROMEOS_PLATFORM; | 30 return CHROMEOS_PLATFORM; |
204 #else | 31 #else |
205 return UNSPECIFIED_PLATFORM; | 32 return UNSPECIFIED_PLATFORM; |
206 #endif | 33 #endif |
207 } | 34 } |
208 | 35 |
209 // static | 36 // static |
210 Feature::Location Feature::ConvertLocation(Extension::Location location) { | 37 Feature::Location Feature::ConvertLocation(Extension::Location location) { |
211 if (location == Extension::COMPONENT) | 38 if (location == Extension::COMPONENT) |
212 return COMPONENT_LOCATION; | 39 return COMPONENT_LOCATION; |
213 else | 40 else |
214 return UNSPECIFIED_LOCATION; | 41 return UNSPECIFIED_LOCATION; |
215 } | 42 } |
216 | 43 |
217 void Feature::Parse(const DictionaryValue* value) { | |
218 ParseSet(value, "whitelist", &whitelist_); | |
219 ParseEnumSet<Extension::Type>(value, "extension_types", &extension_types_, | |
220 g_mappings.Get().extension_types); | |
221 ParseEnumSet<Context>(value, "contexts", &contexts_, | |
222 g_mappings.Get().contexts); | |
223 ParseEnum<Location>(value, "location", &location_, | |
224 g_mappings.Get().locations); | |
225 ParseEnum<Platform>(value, "platform", &platform_, | |
226 g_mappings.Get().platforms); | |
227 value->GetInteger("min_manifest_version", &min_manifest_version_); | |
228 value->GetInteger("max_manifest_version", &max_manifest_version_); | |
229 ParseEnum<VersionInfo::Channel>( | |
230 value, "channel", &channel_, | |
231 g_mappings.Get().channels); | |
232 } | |
233 | |
234 Feature::Availability Feature::IsAvailableToManifest( | |
235 const std::string& extension_id, | |
236 Extension::Type type, | |
237 Location location, | |
238 int manifest_version, | |
239 Platform platform) const { | |
240 // Component extensions can access any feature. | |
241 if (location == COMPONENT_LOCATION) | |
242 return CreateAvailability(IS_AVAILABLE, type); | |
243 | |
244 if (!whitelist_.empty()) { | |
245 if (whitelist_.find(extension_id) == whitelist_.end()) { | |
246 // TODO(aa): This is gross. There should be a better way to test the | |
247 // whitelist. | |
248 CommandLine* command_line = CommandLine::ForCurrentProcess(); | |
249 if (!command_line->HasSwitch(switches::kWhitelistedExtensionID)) | |
250 return CreateAvailability(NOT_FOUND_IN_WHITELIST, type); | |
251 | |
252 std::string whitelist_switch_value = | |
253 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( | |
254 switches::kWhitelistedExtensionID); | |
255 if (extension_id != whitelist_switch_value) | |
256 return CreateAvailability(NOT_FOUND_IN_WHITELIST, type); | |
257 } | |
258 } | |
259 | |
260 if (!extension_types_.empty() && | |
261 extension_types_.find(type) == extension_types_.end()) { | |
262 return CreateAvailability(INVALID_TYPE, type); | |
263 } | |
264 | |
265 if (location_ != UNSPECIFIED_LOCATION && location_ != location) | |
266 return CreateAvailability(INVALID_LOCATION, type); | |
267 | |
268 if (platform_ != UNSPECIFIED_PLATFORM && platform_ != platform) | |
269 return CreateAvailability(INVALID_PLATFORM, type); | |
270 | |
271 if (min_manifest_version_ != 0 && manifest_version < min_manifest_version_) | |
272 return CreateAvailability(INVALID_MIN_MANIFEST_VERSION, type); | |
273 | |
274 if (max_manifest_version_ != 0 && manifest_version > max_manifest_version_) | |
275 return CreateAvailability(INVALID_MAX_MANIFEST_VERSION, type); | |
276 | |
277 if (channel_ < g_current_channel) | |
278 return CreateAvailability(UNSUPPORTED_CHANNEL, type); | |
279 | |
280 return CreateAvailability(IS_AVAILABLE, type); | |
281 } | |
282 | |
283 Feature::Availability Feature::IsAvailableToContext( | |
284 const Extension* extension, | |
285 Feature::Context context, | |
286 Feature::Platform platform) const { | |
287 Availability result = IsAvailableToManifest( | |
288 extension->id(), | |
289 extension->GetType(), | |
290 ConvertLocation(extension->location()), | |
291 extension->manifest_version(), | |
292 platform); | |
293 if (!result.is_available()) | |
294 return result; | |
295 | |
296 if (!contexts_.empty() && | |
297 contexts_.find(context) == contexts_.end()) { | |
298 return CreateAvailability(INVALID_CONTEXT, extension->GetType()); | |
299 } | |
300 | |
301 return CreateAvailability(IS_AVAILABLE); | |
302 } | |
303 | |
304 // static | 44 // static |
305 chrome::VersionInfo::Channel Feature::GetCurrentChannel() { | 45 chrome::VersionInfo::Channel Feature::GetCurrentChannel() { |
306 return g_current_channel; | 46 return g_current_channel; |
307 } | 47 } |
308 | 48 |
309 // static | 49 // static |
310 void Feature::SetCurrentChannel(VersionInfo::Channel channel) { | 50 void Feature::SetCurrentChannel(VersionInfo::Channel channel) { |
311 g_current_channel = channel; | 51 g_current_channel = channel; |
312 } | 52 } |
313 | 53 |
314 // static | 54 // static |
315 chrome::VersionInfo::Channel Feature::GetDefaultChannel() { | 55 chrome::VersionInfo::Channel Feature::GetDefaultChannel() { |
316 return kDefaultChannel; | 56 return kDefaultChannel; |
317 } | 57 } |
318 | 58 |
319 Feature::Availability Feature::CreateAvailability( | 59 Feature::~Feature() {} |
320 AvailabilityResult result) const { | |
321 return Availability( | |
322 result, GetAvailabilityMessage(result, Extension::TYPE_UNKNOWN)); | |
323 } | |
324 | |
325 Feature::Availability Feature::CreateAvailability( | |
326 AvailabilityResult result, Extension::Type type) const { | |
327 return Availability(result, GetAvailabilityMessage(result, type)); | |
328 } | |
329 | |
330 std::string Feature::GetAvailabilityMessage( | |
331 AvailabilityResult result, Extension::Type type) const { | |
332 switch (result) { | |
333 case IS_AVAILABLE: | |
334 return ""; | |
335 case NOT_FOUND_IN_WHITELIST: | |
336 return base::StringPrintf( | |
337 "'%s' is not allowed for specified extension ID.", | |
338 name().c_str()); | |
339 case INVALID_TYPE: { | |
340 std::string allowed_type_names; | |
341 // Turn the set of allowed types into a vector so that it's easier to | |
342 // inject the appropriate separator into the display string. | |
343 std::vector<Extension::Type> extension_types( | |
344 extension_types_.begin(), extension_types_.end()); | |
345 for (size_t i = 0; i < extension_types.size(); i++) { | |
346 // Pluralize type name. | |
347 allowed_type_names += GetDisplayTypeName(extension_types[i]) + "s"; | |
348 if (i == extension_types_.size() - 2) { | |
349 allowed_type_names += " and "; | |
350 } else if (i != extension_types_.size() - 1) { | |
351 allowed_type_names += ", "; | |
352 } | |
353 } | |
354 | |
355 return base::StringPrintf( | |
356 "'%s' is only allowed for %s, and this is a %s.", | |
357 name().c_str(), | |
358 allowed_type_names.c_str(), | |
359 GetDisplayTypeName(type).c_str()); | |
360 } | |
361 case INVALID_CONTEXT: | |
362 return base::StringPrintf( | |
363 "'%s' is not allowed for specified context type content script, " | |
364 " extension page, web page, etc.).", | |
365 name().c_str()); | |
366 case INVALID_LOCATION: | |
367 return base::StringPrintf( | |
368 "'%s' is not allowed for specified install location.", | |
369 name().c_str()); | |
370 case INVALID_PLATFORM: | |
371 return base::StringPrintf( | |
372 "'%s' is not allowed for specified platform.", | |
373 name().c_str()); | |
374 case INVALID_MIN_MANIFEST_VERSION: | |
375 return base::StringPrintf( | |
376 "'%s' requires manifest version of at least %d.", | |
377 name().c_str(), | |
378 min_manifest_version_); | |
379 case INVALID_MAX_MANIFEST_VERSION: | |
380 return base::StringPrintf( | |
381 "'%s' requires manifest version of %d or lower.", | |
382 name().c_str(), | |
383 max_manifest_version_); | |
384 case NOT_PRESENT: | |
385 return base::StringPrintf( | |
386 "'%s' requires a different Feature that is not present.", | |
387 name().c_str()); | |
388 case UNSUPPORTED_CHANNEL: | |
389 return base::StringPrintf( | |
390 "'%s' requires Google Chrome %s channel or newer, and this is the " | |
391 "%s channel.", | |
392 name().c_str(), | |
393 GetChannelName(channel_).c_str(), | |
394 GetChannelName(GetCurrentChannel()).c_str()); | |
395 } | |
396 | |
397 NOTREACHED(); | |
398 return ""; | |
399 } | |
400 | |
401 | 60 |
402 } // namespace extensions | 61 } // namespace extensions |
OLD | NEW |