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