1 // Copyright 2014 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 "extensions/common/features/simple_feature.h" 6 7 #include <map> 8 #include <vector> 9 10 #include "base/command_line.h" 11 #include "base/debug/alias.h" 12 #include "base/lazy_instance.h" 13 #include "base/sha1.h" 14 #include "base/strings/string_number_conversions.h" 15 #include "base/strings/string_util.h" 16 #include "base/strings/stringprintf.h" 17 #include "extensions/common/switches.h" 18 19 namespace extensions { 20 21 namespace { 22 23 struct Mappings { 24 Mappings() { 25 extension_types["extension"] = Manifest::TYPE_EXTENSION; 26 extension_types["theme"] = Manifest::TYPE_THEME; 27 extension_types["legacy_packaged_app"] = Manifest::TYPE_LEGACY_PACKAGED_APP; 28 extension_types["hosted_app"] = Manifest::TYPE_HOSTED_APP; 29 extension_types["platform_app"] = Manifest::TYPE_PLATFORM_APP; 30 extension_types["shared_module"] = Manifest::TYPE_SHARED_MODULE; 31 32 contexts["blessed_extension"] = Feature::BLESSED_EXTENSION_CONTEXT; 33 contexts["unblessed_extension"] = Feature::UNBLESSED_EXTENSION_CONTEXT; 34 contexts["content_script"] = Feature::CONTENT_SCRIPT_CONTEXT; 35 contexts["web_page"] = Feature::WEB_PAGE_CONTEXT; 36 contexts["blessed_web_page"] = Feature::BLESSED_WEB_PAGE_CONTEXT; 37 38 locations["component"] = SimpleFeature::COMPONENT_LOCATION; 39 locations["policy"] = SimpleFeature::POLICY_LOCATION; 40 41 platforms["chromeos"] = Feature::CHROMEOS_PLATFORM; 42 platforms["linux"] = Feature::LINUX_PLATFORM; 43 platforms["mac"] = Feature::MACOSX_PLATFORM; 44 platforms["win"] = Feature::WIN_PLATFORM; 45 } 46 47 std::map<std::string, Manifest::Type> extension_types; 48 std::map<std::string, Feature::Context> contexts; 49 std::map<std::string, SimpleFeature::Location> locations; 50 std::map<std::string, Feature::Platform> platforms; 51 }; 52 53 base::LazyInstance<Mappings> g_mappings = LAZY_INSTANCE_INITIALIZER; 54 55 // TODO(aa): Can we replace all this manual parsing with JSON schema stuff? 56 57 void ParseSet(const base::DictionaryValue* value, 58 const std::string& property, 59 std::set<std::string>* set) { 60 const base::ListValue* list_value = NULL; 61 if (!value->GetList(property, &list_value)) 62 return; 63 64 set->clear(); 65 for (size_t i = 0; i < list_value->GetSize(); ++i) { 66 std::string str_val; 67 CHECK(list_value->GetString(i, &str_val)) << property << " " << i; 68 set->insert(str_val); 69 } 70 } 71 72 template<typename T> 73 void ParseEnum(const std::string& string_value, 74 T* enum_value, 75 const std::map<std::string, T>& mapping) { 76 typename std::map<std::string, T>::const_iterator iter = 77 mapping.find(string_value); 78 if (iter == mapping.end()) { 79 // For http://crbug.com/365192. 80 char minidump[256]; 81 base::debug::Alias(&minidump); 82 base::snprintf(minidump, arraysize(minidump), 83 "e::simple_feature.cc:%d:\"%s\"", __LINE__, string_value.c_str()); 84 CHECK(false) << string_value; 85 } 86 *enum_value = iter->second; 87 } 88 89 template<typename T> 90 void ParseEnum(const base::DictionaryValue* value, 91 const std::string& property, 92 T* enum_value, 93 const std::map<std::string, T>& mapping) { 94 std::string string_value; 95 if (!value->GetString(property, &string_value)) 96 return; 97 98 ParseEnum(string_value, enum_value, mapping); 99 } 100 101 template<typename T> 102 void ParseEnumSet(const base::DictionaryValue* value, 103 const std::string& property, 104 std::set<T>* enum_set, 105 const std::map<std::string, T>& mapping) { 106 if (!value->HasKey(property)) 107 return; 108 109 enum_set->clear(); 110 111 std::string property_string; 112 if (value->GetString(property, &property_string)) { 113 if (property_string == "all") { 114 for (typename std::map<std::string, T>::const_iterator j = 115 mapping.begin(); j != mapping.end(); ++j) { 116 enum_set->insert(j->second); 117 } 118 } 119 return; 120 } 121 122 std::set<std::string> string_set; 123 ParseSet(value, property, &string_set); 124 for (std::set<std::string>::iterator iter = string_set.begin(); 125 iter != string_set.end(); ++iter) { 126 T enum_value = static_cast<T>(0); 127 ParseEnum(*iter, &enum_value, mapping); 128 enum_set->insert(enum_value); 129 } 130 } 131 132 void ParseURLPatterns(const base::DictionaryValue* value, 133 const std::string& key, 134 URLPatternSet* set) { 135 const base::ListValue* matches = NULL; 136 if (value->GetList(key, &matches)) { 137 set->ClearPatterns(); 138 for (size_t i = 0; i < matches->GetSize(); ++i) { 139 std::string pattern; 140 CHECK(matches->GetString(i, &pattern)); 141 set->AddPattern(URLPattern(URLPattern::SCHEME_ALL, pattern)); 142 } 143 } 144 } 145 146 // Gets a human-readable name for the given extension type, suitable for giving 147 // to developers in an error message. 148 std::string GetDisplayName(Manifest::Type type) { 149 switch (type) { 150 case Manifest::TYPE_UNKNOWN: 151 return "unknown"; 152 case Manifest::TYPE_EXTENSION: 153 return "extension"; 154 case Manifest::TYPE_HOSTED_APP: 155 return "hosted app"; 156 case Manifest::TYPE_LEGACY_PACKAGED_APP: 157 return "legacy packaged app"; 158 case Manifest::TYPE_PLATFORM_APP: 159 return "packaged app"; 160 case Manifest::TYPE_THEME: 161 return "theme"; 162 case Manifest::TYPE_USER_SCRIPT: 163 return "user script"; 164 case Manifest::TYPE_SHARED_MODULE: 165 return "shared module"; 166 case Manifest::NUM_LOAD_TYPES: 167 NOTREACHED(); 168 } 169 NOTREACHED(); 170 return ""; 171 } 172 173 // Gets a human-readable name for the given context type, suitable for giving 174 // to developers in an error message. 175 std::string GetDisplayName(Feature::Context context) { 176 switch (context) { 177 case Feature::UNSPECIFIED_CONTEXT: 178 return "unknown"; 179 case Feature::BLESSED_EXTENSION_CONTEXT: 180 // "privileged" is vague but hopefully the developer will understand that 181 // means background or app window. 182 return "privileged page"; 183 case Feature::UNBLESSED_EXTENSION_CONTEXT: 184 // "iframe" is a bit of a lie/oversimplification, but that's the most 185 // common unblessed context. 186 return "extension iframe"; 187 case Feature::CONTENT_SCRIPT_CONTEXT: 188 return "content script"; 189 case Feature::WEB_PAGE_CONTEXT: 190 return "web page"; 191 case Feature::BLESSED_WEB_PAGE_CONTEXT: 192 return "hosted app"; 193 } 194 NOTREACHED(); 195 return ""; 196 } 197 198 // Gets a human-readable list of the display names (pluralized, comma separated 199 // with the "and" in the correct place) for each of |enum_types|. 200 template <typename EnumType> 201 std::string ListDisplayNames(const std::vector<EnumType> enum_types) { 202 std::string display_name_list; 203 for (size_t i = 0; i < enum_types.size(); ++i) { 204 // Pluralize type name. 205 display_name_list += GetDisplayName(enum_types[i]) + "s"; 206 // Comma-separate entries, with an Oxford comma if there is more than 2 207 // total entries. 208 if (enum_types.size() > 2) { 209 if (i < enum_types.size() - 2) 210 display_name_list += ", "; 211 else if (i == enum_types.size() - 2) 212 display_name_list += ", and "; 213 } else if (enum_types.size() == 2 && i == 0) { 214 display_name_list += " and "; 215 } 216 } 217 return display_name_list; 218 } 219 220 std::string HashExtensionId(const std::string& extension_id) { 221 const std::string id_hash = base::SHA1HashString(extension_id); 222 DCHECK(id_hash.length() == base::kSHA1Length); 223 return base::HexEncode(id_hash.c_str(), id_hash.length()); 224 } 225 226 } // namespace 227 228 SimpleFeature::SimpleFeature() 229 : location_(UNSPECIFIED_LOCATION), 230 min_manifest_version_(0), 231 max_manifest_version_(0), 232 has_parent_(false), 233 component_extensions_auto_granted_(true) {} 234 235 SimpleFeature::~SimpleFeature() {} 236 237 void SimpleFeature::AddFilter(scoped_ptr<SimpleFeatureFilter> filter) { 238 filters_.push_back(make_linked_ptr(filter.release())); 239 } 240 241 std::string SimpleFeature::Parse(const base::DictionaryValue* value) { 242 ParseURLPatterns(value, "matches", &matches_); 243 ParseSet(value, "blacklist", &blacklist_); 244 ParseSet(value, "whitelist", &whitelist_); 245 ParseSet(value, "dependencies", &dependencies_); 246 ParseEnumSet<Manifest::Type>(value, "extension_types", &extension_types_, 247 g_mappings.Get().extension_types); 248 ParseEnumSet<Context>(value, "contexts", &contexts_, 249 g_mappings.Get().contexts); 250 ParseEnum<Location>(value, "location", &location_, 251 g_mappings.Get().locations); 252 ParseEnumSet<Platform>(value, "platforms", &platforms_, 253 g_mappings.Get().platforms); 254 value->GetInteger("min_manifest_version", &min_manifest_version_); 255 value->GetInteger("max_manifest_version", &max_manifest_version_); 256 257 no_parent_ = false; 258 value->GetBoolean("noparent", &no_parent_); 259 260 component_extensions_auto_granted_ = true; 261 value->GetBoolean("component_extensions_auto_granted", 262 &component_extensions_auto_granted_); 263 264 if (matches_.is_empty() && contexts_.count(WEB_PAGE_CONTEXT) != 0) { 265 return name() + ": Allowing web_page contexts requires supplying a value " + 266 "for matches."; 267 } 268 269 for (FilterList::iterator filter_iter = filters_.begin(); 270 filter_iter != filters_.end(); 271 ++filter_iter) { 272 std::string result = (*filter_iter)->Parse(value); 273 if (!result.empty()) { 274 return result; 275 } 276 } 277 278 return std::string(); 279 } 280 281 Feature::Availability SimpleFeature::IsAvailableToManifest( 282 const std::string& extension_id, 283 Manifest::Type type, 284 Manifest::Location location, 285 int manifest_version, 286 Platform platform) const { 287 // Check extension type first to avoid granting platform app permissions 288 // to component extensions. 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 (IsIdInBlacklist(extension_id)) 299 return CreateAvailability(FOUND_IN_BLACKLIST, type); 300 301 // TODO(benwells): don't grant all component extensions. 302 // See http://crbug.com/370375 for more details. 303 // Component extensions can access any feature. 304 // NOTE: Deliberately does not match EXTERNAL_COMPONENT. 305 if (component_extensions_auto_granted_ && location == Manifest::COMPONENT) 306 return CreateAvailability(IS_AVAILABLE, type); 307 308 if (!whitelist_.empty()) { 309 if (!IsIdInWhitelist(extension_id)) { 310 // TODO(aa): This is gross. There should be a better way to test the 311 // whitelist. 312 CommandLine* command_line = CommandLine::ForCurrentProcess(); 313 if (!command_line->HasSwitch(switches::kWhitelistedExtensionID)) 314 return CreateAvailability(NOT_FOUND_IN_WHITELIST, type); 315 316 std::string whitelist_switch_value = 317 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 318 switches::kWhitelistedExtensionID); 319 if (extension_id != whitelist_switch_value) 320 return CreateAvailability(NOT_FOUND_IN_WHITELIST, type); 321 } 322 } 323 324 if (!MatchesManifestLocation(location)) 325 return CreateAvailability(INVALID_LOCATION, type); 326 327 if (!platforms_.empty() && 328 platforms_.find(platform) == platforms_.end()) 329 return CreateAvailability(INVALID_PLATFORM, type); 330 331 if (min_manifest_version_ != 0 && manifest_version < min_manifest_version_) 332 return CreateAvailability(INVALID_MIN_MANIFEST_VERSION, type); 333 334 if (max_manifest_version_ != 0 && manifest_version > max_manifest_version_) 335 return CreateAvailability(INVALID_MAX_MANIFEST_VERSION, type); 336 337 for (FilterList::const_iterator filter_iter = filters_.begin(); 338 filter_iter != filters_.end(); 339 ++filter_iter) { 340 Availability availability = (*filter_iter)->IsAvailableToManifest( 341 extension_id, type, location, manifest_version, platform); 342 if (!availability.is_available()) 343 return availability; 344 } 345 346 return CreateAvailability(IS_AVAILABLE, type); 347 } 348 349 Feature::Availability SimpleFeature::IsAvailableToContext( 350 const Extension* extension, 351 SimpleFeature::Context context, 352 const GURL& url, 353 SimpleFeature::Platform platform) const { 354 if (extension) { 355 Availability result = IsAvailableToManifest(extension->id(), 356 extension->GetType(), 357 extension->location(), 358 extension->manifest_version(), 359 platform); 360 if (!result.is_available()) 361 return result; 362 } 363 364 if (!contexts_.empty() && contexts_.find(context) == contexts_.end()) 365 return CreateAvailability(INVALID_CONTEXT, context); 366 367 if (!matches_.is_empty() && !matches_.MatchesURL(url)) 368 return CreateAvailability(INVALID_URL, url); 369 370 for (FilterList::const_iterator filter_iter = filters_.begin(); 371 filter_iter != filters_.end(); 372 ++filter_iter) { 373 Availability availability = 374 (*filter_iter)->IsAvailableToContext(extension, context, url, platform); 375 if (!availability.is_available()) 376 return availability; 377 } 378 379 return CreateAvailability(IS_AVAILABLE); 380 } 381 382 std::string SimpleFeature::GetAvailabilityMessage( 383 AvailabilityResult result, 384 Manifest::Type type, 385 const GURL& url, 386 Context context) const { 387 switch (result) { 388 case IS_AVAILABLE: 389 return std::string(); 390 case NOT_FOUND_IN_WHITELIST: 391 case FOUND_IN_BLACKLIST: 392 return base::StringPrintf( 393 "'%s' is not allowed for specified extension ID.", 394 name().c_str()); 395 case INVALID_URL: 396 return base::StringPrintf("'%s' is not allowed on %s.", 397 name().c_str(), url.spec().c_str()); 398 case INVALID_TYPE: 399 return base::StringPrintf( 400 "'%s' is only allowed for %s, but this is a %s.", 401 name().c_str(), 402 ListDisplayNames(std::vector<Manifest::Type>( 403 extension_types_.begin(), extension_types_.end())).c_str(), 404 GetDisplayName(type).c_str()); 405 case INVALID_CONTEXT: 406 return base::StringPrintf( 407 "'%s' is only allowed to run in %s, but this is a %s", 408 name().c_str(), 409 ListDisplayNames(std::vector<Context>( 410 contexts_.begin(), contexts_.end())).c_str(), 411 GetDisplayName(context).c_str()); 412 case INVALID_LOCATION: 413 return base::StringPrintf( 414 "'%s' is not allowed for specified install location.", 415 name().c_str()); 416 case INVALID_PLATFORM: 417 return base::StringPrintf( 418 "'%s' is not allowed for specified platform.", 419 name().c_str()); 420 case INVALID_MIN_MANIFEST_VERSION: 421 return base::StringPrintf( 422 "'%s' requires manifest version of at least %d.", 423 name().c_str(), 424 min_manifest_version_); 425 case INVALID_MAX_MANIFEST_VERSION: 426 return base::StringPrintf( 427 "'%s' requires manifest version of %d or lower.", 428 name().c_str(), 429 max_manifest_version_); 430 case NOT_PRESENT: 431 return base::StringPrintf( 432 "'%s' requires a different Feature that is not present.", 433 name().c_str()); 434 case UNSUPPORTED_CHANNEL: 435 return base::StringPrintf( 436 "'%s' is unsupported in this version of the platform.", 437 name().c_str()); 438 } 439 440 NOTREACHED(); 441 return std::string(); 442 } 443 444 Feature::Availability SimpleFeature::CreateAvailability( 445 AvailabilityResult result) const { 446 return Availability( 447 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(), 448 UNSPECIFIED_CONTEXT)); 449 } 450 451 Feature::Availability SimpleFeature::CreateAvailability( 452 AvailabilityResult result, Manifest::Type type) const { 453 return Availability(result, GetAvailabilityMessage(result, type, GURL(), 454 UNSPECIFIED_CONTEXT)); 455 } 456 457 Feature::Availability SimpleFeature::CreateAvailability( 458 AvailabilityResult result, 459 const GURL& url) const { 460 return Availability( 461 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, url, 462 UNSPECIFIED_CONTEXT)); 463 } 464 465 Feature::Availability SimpleFeature::CreateAvailability( 466 AvailabilityResult result, 467 Context context) const { 468 return Availability( 469 result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(), 470 context)); 471 } 472 473 std::set<Feature::Context>* SimpleFeature::GetContexts() { 474 return &contexts_; 475 } 476 477 bool SimpleFeature::IsInternal() const { 478 return false; 479 } 480 481 bool SimpleFeature::IsBlockedInServiceWorker() const { return false; } 482 483 bool SimpleFeature::IsIdInBlacklist(const std::string& extension_id) const { 484 return IsIdInList(extension_id, blacklist_); 485 } 486 487 bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id) const { 488 return IsIdInList(extension_id, whitelist_); 489 } 490 491 // static 492 bool SimpleFeature::IsIdInList(const std::string& extension_id, 493 const std::set<std::string>& list) { 494 // Belt-and-suspenders philosophy here. We should be pretty confident by this 495 // point that we've validated the extension ID format, but in case something 496 // slips through, we avoid a class of attack where creative ID manipulation 497 // leads to hash collisions. 498 if (extension_id.length() != 32) // 128 bits / 4 = 32 mpdecimal characters 499 return false; 500 501 if (list.find(extension_id) != list.end() || 502 list.find(HashExtensionId(extension_id)) != list.end()) { 503 return true; 504 } 505 506 return false; 507 } 508 509 bool SimpleFeature::MatchesManifestLocation( 510 Manifest::Location manifest_location) const { 511 switch (location_) { 512 case SimpleFeature::UNSPECIFIED_LOCATION: 513 return true; 514 case SimpleFeature::COMPONENT_LOCATION: 515 // TODO(kalman/asargent): Should this include EXTERNAL_COMPONENT too? 516 return manifest_location == Manifest::COMPONENT; 517 case SimpleFeature::POLICY_LOCATION: 518 return manifest_location == Manifest::EXTERNAL_POLICY || 519 manifest_location == Manifest::EXTERNAL_POLICY_DOWNLOAD; 520 } 521 NOTREACHED(); 522 return false; 523 } 524 525 } // namespace extensions 526