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 19 using chrome::VersionInfo; 20 21 namespace extensions { 22 23 namespace { 24 25 struct Mappings { 26 Mappings() { 27 extension_types["extension"] = Manifest::TYPE_EXTENSION; 28 extension_types["theme"] = Manifest::TYPE_THEME; 29 extension_types["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 43 channels["trunk"] = VersionInfo::CHANNEL_UNKNOWN; 44 channels["canary"] = VersionInfo::CHANNEL_CANARY; 45 channels["dev"] = VersionInfo::CHANNEL_DEV; 46 channels["beta"] = VersionInfo::CHANNEL_BETA; 47 channels["stable"] = VersionInfo::CHANNEL_STABLE; 48 } 49 50 std::map<std::string, Manifest::Type> extension_types; 51 std::map<std::string, Feature::Context> contexts; 52 std::map<std::string, Feature::Location> locations; 53 std::map<std::string, Feature::Platform> platforms; 54 std::map<std::string, VersionInfo::Channel> channels; 55 }; 56 57 base::LazyInstance<Mappings> g_mappings = LAZY_INSTANCE_INITIALIZER; 58 59 std::string GetChannelName(VersionInfo::Channel channel) { 60 typedef std::map<std::string, VersionInfo::Channel> ChannelsMap; 61 ChannelsMap channels = g_mappings.Get().channels; 62 for (ChannelsMap::iterator i = channels.begin(); i != channels.end(); ++i) { 63 if (i->second == channel) 64 return i->first; 65 } 66 NOTREACHED(); 67 return "unknown"; 68 } 69 70 // TODO(aa): Can we replace all this manual parsing with JSON schema stuff? 71 72 void ParseSet(const base::DictionaryValue* value, 73 const std::string& property, 74 std::set<std::string>* set) { 75 const base::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 base::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 base::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 void ParseURLPatterns(const base::DictionaryValue* value, 141 const std::string& key, 142 URLPatternSet* set) { 143 const base::ListValue* matches = NULL; 144 if (value->GetList(key, &matches)) { 145 set->ClearPatterns(); 146 for (size_t i = 0; i < matches->GetSize(); ++i) { 147 std::string pattern; 148 CHECK(matches->GetString(i, &pattern)); 149 set->AddPattern(URLPattern(URLPattern::SCHEME_ALL, pattern)); 150 } 151 } 152 } 153 154 // Gets a human-readable name for the given extension type. 155 std::string GetDisplayTypeName(Manifest::Type type) { 156 switch (type) { 157 case Manifest::TYPE_UNKNOWN: 158 return "unknown"; 159 case Manifest::TYPE_EXTENSION: 160 return "extension"; 161 case Manifest::TYPE_HOSTED_APP: 162 return "hosted app"; 163 case Manifest::TYPE_LEGACY_PACKAGED_APP: 164 return "legacy packaged app"; 165 case Manifest::TYPE_PLATFORM_APP: 166 return "packaged app"; 167 case Manifest::TYPE_THEME: 168 return "theme"; 169 case Manifest::TYPE_USER_SCRIPT: 170 return "user script"; 171 case Manifest::TYPE_SHARED_MODULE: 172 return "shared module"; 173 } 174 175 NOTREACHED(); 176 return std::string(); 177 } 178 179 std::string HashExtensionId(const std::string& extension_id) { 180 const std::string id_hash = base::SHA1HashString(extension_id); 181 DCHECK(id_hash.length() == base::kSHA1Length); 182 return base::HexEncode(id_hash.c_str(), id_hash.length()); 183 } 184 185 } // namespace 186 187 SimpleFeature::SimpleFeature() 188 : location_(UNSPECIFIED_LOCATION), 189 platform_(UNSPECIFIED_PLATFORM), 190 min_manifest_version_(0), 191 max_manifest_version_(0), 192 channel_(VersionInfo::CHANNEL_UNKNOWN), 193 has_parent_(false), 194 channel_has_been_set_(false) { 195 } 196 197 SimpleFeature::SimpleFeature(const SimpleFeature& other) 198 : whitelist_(other.whitelist_), 199 extension_types_(other.extension_types_), 200 contexts_(other.contexts_), 201 matches_(other.matches_), 202 location_(other.location_), 203 platform_(other.platform_), 204 min_manifest_version_(other.min_manifest_version_), 205 max_manifest_version_(other.max_manifest_version_), 206 channel_(other.channel_), 207 has_parent_(other.has_parent_), 208 channel_has_been_set_(other.channel_has_been_set_) { 209 } 210 211 SimpleFeature::~SimpleFeature() { 212 } 213 214 bool SimpleFeature::Equals(const SimpleFeature& other) const { 215 return whitelist_ == other.whitelist_ && 216 extension_types_ == other.extension_types_ && 217 contexts_ == other.contexts_ && 218 matches_ == other.matches_ && 219 location_ == other.location_ && 220 platform_ == other.platform_ && 221 min_manifest_version_ == other.min_manifest_version_ && 222 max_manifest_version_ == other.max_manifest_version_ && 223 channel_ == other.channel_ && 224 has_parent_ == other.has_parent_ && 225 channel_has_been_set_ == other.channel_has_been_set_; 226 } 227 228 std::string SimpleFeature::Parse(const base::DictionaryValue* value) { 229 ParseURLPatterns(value, "matches", &matches_); 230 ParseSet(value, "whitelist", &whitelist_); 231 ParseSet(value, "dependencies", &dependencies_); 232 ParseEnumSet<Manifest::Type>(value, "extension_types", &extension_types_, 233 g_mappings.Get().extension_types); 234 ParseEnumSet<Context>(value, "contexts", &contexts_, 235 g_mappings.Get().contexts); 236 ParseEnum<Location>(value, "location", &location_, 237 g_mappings.Get().locations); 238 ParseEnum<Platform>(value, "platform", &platform_, 239 g_mappings.Get().platforms); 240 value->GetInteger("min_manifest_version", &min_manifest_version_); 241 value->GetInteger("max_manifest_version", &max_manifest_version_); 242 ParseEnum<VersionInfo::Channel>( 243 value, "channel", &channel_, 244 g_mappings.Get().channels); 245 246 no_parent_ = false; 247 value->GetBoolean("noparent", &no_parent_); 248 249 // The "trunk" channel uses VersionInfo::CHANNEL_UNKNOWN, so we need to keep 250 // track of whether the channel has been set or not separately. 251 channel_has_been_set_ |= value->HasKey("channel"); 252 if (!channel_has_been_set_ && dependencies_.empty()) 253 return name() + ": Must supply a value for channel or dependencies."; 254 255 if (matches_.is_empty() && contexts_.count(WEB_PAGE_CONTEXT) != 0) { 256 return name() + ": Allowing web_page contexts requires supplying a value " + 257 "for matches."; 258 } 259 260 return std::string(); 261 } 262 263 Feature::Availability SimpleFeature::IsAvailableToManifest( 264 const std::string& extension_id, 265 Manifest::Type type, 266 Location location, 267 int manifest_version, 268 Platform platform) const { 269 // Component extensions can access any feature. 270 if (location == COMPONENT_LOCATION) 271 return CreateAvailability(IS_AVAILABLE, type); 272 273 if (!whitelist_.empty()) { 274 if (!IsIdInWhitelist(extension_id)) { 275 // TODO(aa): This is gross. There should be a better way to test the 276 // whitelist. 277 CommandLine* command_line = CommandLine::ForCurrentProcess(); 278 if (!command_line->HasSwitch(switches::kWhitelistedExtensionID)) 279 return CreateAvailability(NOT_FOUND_IN_WHITELIST, type); 280 281 std::string whitelist_switch_value = 282 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 283 switches::kWhitelistedExtensionID); 284 if (extension_id != whitelist_switch_value) 285 return CreateAvailability(NOT_FOUND_IN_WHITELIST, type); 286 } 287 } 288 289 // HACK(kalman): user script -> extension. Solve this in a more generic way 290 // when we compile feature files. 291 Manifest::Type type_to_check = (type == Manifest::TYPE_USER_SCRIPT) ? 292 Manifest::TYPE_EXTENSION : type; 293 if (!extension_types_.empty() && 294 extension_types_.find(type_to_check) == extension_types_.end()) { 295 return CreateAvailability(INVALID_TYPE, type); 296 } 297 298 if (location_ != UNSPECIFIED_LOCATION && location_ != location) 299 return CreateAvailability(INVALID_LOCATION, type); 300 301 if (platform_ != UNSPECIFIED_PLATFORM && platform_ != platform) 302 return CreateAvailability(INVALID_PLATFORM, type); 303 304 if (min_manifest_version_ != 0 && manifest_version < min_manifest_version_) 305 return CreateAvailability(INVALID_MIN_MANIFEST_VERSION, type); 306 307 if (max_manifest_version_ != 0 && manifest_version > max_manifest_version_) 308 return CreateAvailability(INVALID_MAX_MANIFEST_VERSION, type); 309 310 if (channel_has_been_set_ && channel_ < GetCurrentChannel()) 311 return CreateAvailability(UNSUPPORTED_CHANNEL, type); 312 313 return CreateAvailability(IS_AVAILABLE, type); 314 } 315 316 Feature::Availability SimpleFeature::IsAvailableToContext( 317 const Extension* extension, 318 SimpleFeature::Context context, 319 const GURL& url, 320 SimpleFeature::Platform platform) const { 321 if (extension) { 322 Availability result = IsAvailableToManifest( 323 extension->id(), 324 extension->GetType(), 325 ConvertLocation(extension->location()), 326 extension->manifest_version(), 327 platform); 328 if (!result.is_available()) 329 return result; 330 } 331 332 if (!contexts_.empty() && contexts_.find(context) == contexts_.end()) { 333 return extension ? 334 CreateAvailability(INVALID_CONTEXT, extension->GetType()) : 335 CreateAvailability(INVALID_CONTEXT); 336 } 337 338 if (!matches_.is_empty() && !matches_.MatchesURL(url)) 339 return CreateAvailability(INVALID_URL, url); 340 341 return CreateAvailability(IS_AVAILABLE); 342 } 343 344 std::string SimpleFeature::GetAvailabilityMessage( 345 AvailabilityResult result, Manifest::Type type, const GURL& url) const { 346 switch (result) { 347 case IS_AVAILABLE: 348 return std::string(); 349 case NOT_FOUND_IN_WHITELIST: 350 return base::StringPrintf( 351 "'%s' is not allowed for specified extension ID.", 352 name().c_str()); 353 case INVALID_URL: 354 return base::StringPrintf("'%s' is not allowed on %s.", 355 name().c_str(), url.spec().c_str()); 356 case INVALID_TYPE: { 357 std::string allowed_type_names; 358 // Turn the set of allowed types into a vector so that it's easier to 359 // inject the appropriate separator into the display string. 360 std::vector<Manifest::Type> extension_types( 361 extension_types_.begin(), extension_types_.end()); 362 for (size_t i = 0; i < extension_types.size(); i++) { 363 // Pluralize type name. 364 allowed_type_names += GetDisplayTypeName(extension_types[i]) + "s"; 365 if (i == extension_types_.size() - 2) { 366 allowed_type_names += " and "; 367 } else if (i != extension_types_.size() - 1) { 368 allowed_type_names += ", "; 369 } 370 } 371 372 return base::StringPrintf( 373 "'%s' is only allowed for %s, and this is a %s.", 374 name().c_str(), 375 allowed_type_names.c_str(), 376 GetDisplayTypeName(type).c_str()); 377 } 378 case INVALID_CONTEXT: 379 return base::StringPrintf( 380 "'%s' is not allowed for specified context type content script, " 381 " extension page, web page, etc.).", 382 name().c_str()); 383 case INVALID_LOCATION: 384 return base::StringPrintf( 385 "'%s' is not allowed for specified install location.", 386 name().c_str()); 387 case INVALID_PLATFORM: 388 return base::StringPrintf( 389 "'%s' is not allowed for specified platform.", 390 name().c_str()); 391 case INVALID_MIN_MANIFEST_VERSION: 392 return base::StringPrintf( 393 "'%s' requires manifest version of at least %d.", 394 name().c_str(), 395 min_manifest_version_); 396 case INVALID_MAX_MANIFEST_VERSION: 397 return base::StringPrintf( 398 "'%s' requires manifest version of %d or lower.", 399 name().c_str(), 400 max_manifest_version_); 401 case NOT_PRESENT: 402 return base::StringPrintf( 403 "'%s' requires a different Feature that is not present.", 404 name().c_str()); 405 case UNSUPPORTED_CHANNEL: 406 return base::StringPrintf( 407 "'%s' requires Google Chrome %s channel or newer, and this is the " 408 "%s channel.", 409 name().c_str(), 410 GetChannelName(channel_).c_str(), 411 GetChannelName(GetCurrentChannel()).c_str()); 412 } 413 414 NOTREACHED(); 415 return std::string(); 416 } 417 418 Feature::Availability SimpleFeature::CreateAvailability( 419 AvailabilityResult result) const { 420 return Availability( 421 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL())); 422 } 423 424 Feature::Availability SimpleFeature::CreateAvailability( 425 AvailabilityResult result, Manifest::Type type) const { 426 return Availability(result, GetAvailabilityMessage(result, type, GURL())); 427 } 428 429 Feature::Availability SimpleFeature::CreateAvailability( 430 AvailabilityResult result, 431 const GURL& url) const { 432 return Availability( 433 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, url)); 434 } 435 436 std::set<Feature::Context>* SimpleFeature::GetContexts() { 437 return &contexts_; 438 } 439 440 bool SimpleFeature::IsInternal() const { 441 NOTREACHED(); 442 return false; 443 } 444 445 bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id) const { 446 // Belt-and-suspenders philosophy here. We should be pretty confident by this 447 // point that we've validated the extension ID format, but in case something 448 // slips through, we avoid a class of attack where creative ID manipulation 449 // leads to hash collisions. 450 if (extension_id.length() != 32) // 128 bits / 4 = 32 mpdecimal characters 451 return false; 452 453 if (whitelist_.find(extension_id) != whitelist_.end() || 454 whitelist_.find(HashExtensionId(extension_id)) != whitelist_.end()) 455 return true; 456 457 return false; 458 } 459 460 } // namespace extensions 461