| 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::Availability Feature::CreateAvailability( |
| 320 AvailabilityResult result) const { | 60 AvailabilityResult result) const { |
| 321 return Availability( | 61 return Availability( |
| 322 result, GetAvailabilityMessage(result, Extension::TYPE_UNKNOWN)); | 62 result, GetAvailabilityMessage(result, Extension::TYPE_UNKNOWN)); |
| 323 } | 63 } |
| 324 | 64 |
| 325 Feature::Availability Feature::CreateAvailability( | 65 Feature::Availability Feature::CreateAvailability( |
| 326 AvailabilityResult result, Extension::Type type) const { | 66 AvailabilityResult result, Extension::Type type) const { |
| 327 return Availability(result, GetAvailabilityMessage(result, type)); | 67 return Availability(result, GetAvailabilityMessage(result, type)); |
| 328 } | 68 } |
| 329 | 69 |
| 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 | |
| 402 } // namespace extensions | 70 } // namespace extensions |
| OLD | NEW |