// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "extensions/common/extension_api.h" #include #include #include #include #include #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/macros.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/values.h" #include "extensions/common/extension.h" #include "extensions/common/extensions_client.h" #include "extensions/common/features/feature.h" #include "extensions/common/features/feature_provider.h" #include "extensions/common/features/simple_feature.h" #include "extensions/common/permissions/permission_set.h" #include "extensions/common/permissions/permissions_data.h" #include "url/gurl.h" namespace extensions { namespace { const char* const kChildKinds[] = {"functions", "events"}; std::unique_ptr LoadSchemaDictionary( const std::string& name, const base::StringPiece& schema) { std::string error_message; std::unique_ptr result(base::JSONReader::ReadAndReturnError( schema, base::JSON_PARSE_RFC | base::JSON_DETACHABLE_CHILDREN, // options NULL, // error code &error_message)); // Tracking down http://crbug.com/121424 char buf[128]; base::snprintf(buf, arraysize(buf), "%s: (%d) '%s'", name.c_str(), result.get() ? static_cast(result->GetType()) : -1, error_message.c_str()); CHECK(result.get()) << error_message << " for schema " << schema; CHECK(result->IsType(base::Value::Type::DICTIONARY)) << " for schema " << schema; return base::DictionaryValue::From(std::move(result)); } const base::DictionaryValue* FindListItem(const base::ListValue* list, const std::string& property_name, const std::string& property_value) { for (size_t i = 0; i < list->GetSize(); ++i) { const base::DictionaryValue* item = NULL; CHECK(list->GetDictionary(i, &item)) << property_value << "/" << property_name; std::string value; if (item->GetString(property_name, &value) && value == property_value) return item; } return NULL; } const base::DictionaryValue* GetSchemaChild( const base::DictionaryValue* schema_node, const std::string& child_name) { const base::DictionaryValue* child_node = NULL; for (size_t i = 0; i < arraysize(kChildKinds); ++i) { const base::ListValue* list_node = NULL; if (!schema_node->GetList(kChildKinds[i], &list_node)) continue; child_node = FindListItem(list_node, "name", child_name); if (child_node) return child_node; } return NULL; } struct Static { Static() : api(ExtensionAPI::CreateWithDefaultConfiguration()) { } std::unique_ptr api; }; base::LazyInstance::Leaky g_lazy_instance = LAZY_INSTANCE_INITIALIZER; // May override |g_lazy_instance| for a test. ExtensionAPI* g_shared_instance_for_test = NULL; } // namespace // static ExtensionAPI* ExtensionAPI::GetSharedInstance() { return g_shared_instance_for_test ? g_shared_instance_for_test : g_lazy_instance.Get().api.get(); } // static ExtensionAPI* ExtensionAPI::CreateWithDefaultConfiguration() { ExtensionAPI* api = new ExtensionAPI(); api->InitDefaultConfiguration(); return api; } // static void ExtensionAPI::SplitDependencyName(const std::string& full_name, std::string* feature_type, std::string* feature_name) { size_t colon_index = full_name.find(':'); if (colon_index == std::string::npos) { // TODO(aa): Remove this code when all API descriptions have been updated. *feature_type = "api"; *feature_name = full_name; return; } *feature_type = full_name.substr(0, colon_index); *feature_name = full_name.substr(colon_index + 1); } ExtensionAPI::OverrideSharedInstanceForTest::OverrideSharedInstanceForTest( ExtensionAPI* testing_api) : original_api_(g_shared_instance_for_test) { g_shared_instance_for_test = testing_api; } ExtensionAPI::OverrideSharedInstanceForTest::~OverrideSharedInstanceForTest() { g_shared_instance_for_test = original_api_; } void ExtensionAPI::LoadSchema(const std::string& name, const base::StringPiece& schema) { std::unique_ptr schema_dict( LoadSchemaDictionary(name, schema)); std::string schema_namespace; CHECK(schema_dict->GetString("namespace", &schema_namespace)); schemas_[schema_namespace] = std::move(schema_dict); } ExtensionAPI::ExtensionAPI() : default_configuration_initialized_(false) { } ExtensionAPI::~ExtensionAPI() { } void ExtensionAPI::InitDefaultConfiguration() { const char* names[] = {"api", "manifest", "permission"}; for (size_t i = 0; i < arraysize(names); ++i) RegisterDependencyProvider(names[i], FeatureProvider::GetByName(names[i])); default_configuration_initialized_ = true; } void ExtensionAPI::RegisterDependencyProvider(const std::string& name, const FeatureProvider* provider) { dependency_providers_[name] = provider; } bool ExtensionAPI::IsAnyFeatureAvailableToContext( const Feature& api, const Extension* extension, Feature::Context context, const GURL& url, CheckAliasStatus check_alias) { FeatureProviderMap::iterator provider = dependency_providers_.find("api"); CHECK(provider != dependency_providers_.end()); if (api.IsAvailableToContext(extension, context, url).is_available()) return true; // Check to see if there are any parts of this API that are allowed in this // context. const std::vector features = provider->second->GetChildren(api); for (std::vector::const_iterator it = features.begin(); it != features.end(); ++it) { if ((*it)->IsAvailableToContext(extension, context, url).is_available()) return true; } if (check_alias != CheckAliasStatus::ALLOWED) return false; const std::string& alias_name = api.alias(); if (alias_name.empty()) return false; const Feature* alias = provider->second->GetFeature(alias_name); CHECK(alias) << "Cannot find alias feature " << alias_name << " for API feature " << api.name(); return IsAnyFeatureAvailableToContext(*alias, extension, context, url, CheckAliasStatus::NOT_ALLOWED); } Feature::Availability ExtensionAPI::IsAvailable(const std::string& full_name, const Extension* extension, Feature::Context context, const GURL& url, CheckAliasStatus check_alias) { Feature* feature = GetFeatureDependency(full_name); if (!feature) { return Feature::Availability(Feature::NOT_PRESENT, std::string("Unknown feature: ") + full_name); } Feature::Availability availability = feature->IsAvailableToContext(extension, context, url); if (availability.is_available() || check_alias != CheckAliasStatus::ALLOWED) return availability; Feature::Availability alias_availability = IsAliasAvailable(full_name, feature, extension, context, url); return alias_availability.is_available() ? alias_availability : availability; } base::StringPiece ExtensionAPI::GetSchemaStringPiece( const std::string& api_name) { DCHECK_EQ(api_name, GetAPINameFromFullName(api_name, nullptr)); StringPieceMap::iterator cached = schema_strings_.find(api_name); if (cached != schema_strings_.end()) return cached->second; ExtensionsClient* client = ExtensionsClient::Get(); DCHECK(client); if (default_configuration_initialized_ && client->IsAPISchemaGenerated(api_name)) { base::StringPiece schema = client->GetAPISchema(api_name); CHECK(!schema.empty()); schema_strings_[api_name] = schema; return schema; } return base::StringPiece(); } const base::DictionaryValue* ExtensionAPI::GetSchema( const std::string& full_name) { std::string child_name; std::string api_name = GetAPINameFromFullName(full_name, &child_name); const base::DictionaryValue* result = NULL; SchemaMap::iterator maybe_schema = schemas_.find(api_name); if (maybe_schema != schemas_.end()) { result = maybe_schema->second.get(); } else { base::StringPiece schema_string = GetSchemaStringPiece(api_name); if (schema_string.empty()) return nullptr; LoadSchema(api_name, schema_string); maybe_schema = schemas_.find(api_name); CHECK(schemas_.end() != maybe_schema); result = maybe_schema->second.get(); } if (!child_name.empty()) result = GetSchemaChild(result, child_name); return result; } Feature* ExtensionAPI::GetFeatureDependency(const std::string& full_name) { std::string feature_type; std::string feature_name; SplitDependencyName(full_name, &feature_type, &feature_name); FeatureProviderMap::iterator provider = dependency_providers_.find(feature_type); if (provider == dependency_providers_.end()) return NULL; Feature* feature = provider->second->GetFeature(feature_name); // Try getting the feature for the parent API, if this was a child. if (!feature) { std::string child_name; feature = provider->second->GetFeature( GetAPINameFromFullName(feature_name, &child_name)); } return feature; } std::string ExtensionAPI::GetAPINameFromFullName(const std::string& full_name, std::string* child_name) { std::string api_name_candidate = full_name; ExtensionsClient* extensions_client = ExtensionsClient::Get(); DCHECK(extensions_client); while (true) { if (IsKnownAPI(api_name_candidate, extensions_client)) { if (child_name) { if (api_name_candidate.length() < full_name.length()) *child_name = full_name.substr(api_name_candidate.length() + 1); else *child_name = ""; } return api_name_candidate; } size_t last_dot_index = api_name_candidate.rfind('.'); if (last_dot_index == std::string::npos) break; api_name_candidate = api_name_candidate.substr(0, last_dot_index); } if (child_name) *child_name = ""; return std::string(); } bool ExtensionAPI::IsKnownAPI(const std::string& name, ExtensionsClient* client) { return schemas_.find(name) != schemas_.end() || client->IsAPISchemaGenerated(name); } Feature::Availability ExtensionAPI::IsAliasAvailable( const std::string& full_name, Feature* feature, const Extension* extension, Feature::Context context, const GURL& url) { const std::string& alias = feature->alias(); if (alias.empty()) return Feature::Availability(Feature::NOT_PRESENT, "Alias not defined"); FeatureProviderMap::iterator provider = dependency_providers_.find("api"); CHECK(provider != dependency_providers_.end()); // Check if there is a child feature associated with full name for alias API. // This is to ensure that the availability of the feature associated with the // aliased |full_name| is properly determined in case the feature in question // is a child feature. For example, if API foo has an alias fooAlias, which // has a child feature fooAlias.method, aliased foo.method availability should // be determined using fooAlias.method feature, rather than fooAlias feature. std::string child_name; GetAPINameFromFullName(full_name, &child_name); std::string full_alias_name = alias + "." + child_name; Feature* alias_feature = provider->second->GetFeature(full_alias_name); // If there is no child feature, use the alias API feature to check // availability. if (!alias_feature) alias_feature = provider->second->GetFeature(alias); CHECK(alias_feature) << "Cannot find alias feature " << alias << " for API feature " << feature->name(); return alias_feature->IsAvailableToContext(extension, context, url); } } // namespace extensions