// Copyright (c) 2012 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 "chrome/common/extensions/api/extension_api.h" #include <algorithm> #include <string> #include <vector> #include "base/json/json_reader.h" #include "base/logging.h" #include "base/string_split.h" #include "base/string_util.h" #include "base/values.h" #include "chrome/common/extensions/api/generated_schemas.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_permission_set.h" #include "googleurl/src/gurl.h" #include "grit/common_resources.h" #include "ui/base/resource/resource_bundle.h" using base::DictionaryValue; using base::ListValue; using base::Value; namespace extensions { using api::GeneratedSchemas; namespace { // Returns whether the list at |name_space_node|.|child_kind| contains any // children with an { "unprivileged": true } property. bool HasUnprivilegedChild(const DictionaryValue* name_space_node, const std::string& child_kind) { ListValue* child_list = NULL; name_space_node->GetList(child_kind, &child_list); if (!child_list) return false; for (size_t i = 0; i < child_list->GetSize(); ++i) { DictionaryValue* item = NULL; CHECK(child_list->GetDictionary(i, &item)); bool unprivileged = false; if (item->GetBoolean("unprivileged", &unprivileged)) return unprivileged; } return false; } base::StringPiece ReadFromResource(int resource_id) { return ResourceBundle::GetSharedInstance().GetRawDataResource(resource_id); } scoped_ptr<ListValue> LoadSchemaList(const base::StringPiece& schema) { std::string error_message; scoped_ptr<Value> result( base::JSONReader::ReadAndReturnError( schema.as_string(), false, // allow trailing commas NULL, // error code &error_message)); CHECK(result.get()) << error_message; CHECK(result->IsType(Value::TYPE_LIST)); return scoped_ptr<ListValue>(static_cast<ListValue*>(result.release())); } } // namespace // static ExtensionAPI* ExtensionAPI::GetInstance() { return Singleton<ExtensionAPI>::get(); } void ExtensionAPI::LoadSchema(const base::StringPiece& schema) { scoped_ptr<ListValue> schema_list(LoadSchemaList(schema)); std::string schema_namespace; while (!schema_list->empty()) { const DictionaryValue* schema = NULL; { Value* value = NULL; schema_list->Remove(schema_list->GetSize() - 1, &value); CHECK(value->IsType(Value::TYPE_DICTIONARY)); schema = static_cast<const DictionaryValue*>(value); } CHECK(schema->GetString("namespace", &schema_namespace)); schemas_[schema_namespace] = make_linked_ptr(schema); unloaded_schemas_.erase(schema_namespace); // Populate |{completely,partially}_unprivileged_apis_|. // // For "partially", only need to look at functions/events; even though // there are unprivileged properties (e.g. in extensions), access to those // never reaches C++ land. if (schema->HasKey("unprivileged")) { completely_unprivileged_apis_.insert(schema_namespace); } else if (HasUnprivilegedChild(schema, "functions") || HasUnprivilegedChild(schema, "events")) { partially_unprivileged_apis_.insert(schema_namespace); } // Populate |url_matching_apis_|. ListValue* matches = NULL; if (schema->GetList("matches", &matches)) { URLPatternSet pattern_set; for (size_t i = 0; i < matches->GetSize(); ++i) { std::string pattern; CHECK(matches->GetString(i, &pattern)); pattern_set.AddPattern( URLPattern(UserScript::kValidUserScriptSchemes, pattern)); } url_matching_apis_[schema_namespace] = pattern_set; } } } ExtensionAPI::ExtensionAPI() { // Schemas to be loaded from resources. unloaded_schemas_["app"] = ReadFromResource( IDR_EXTENSION_API_JSON_APP); unloaded_schemas_["bookmarks"] = ReadFromResource( IDR_EXTENSION_API_JSON_BOOKMARKS); unloaded_schemas_["browserAction"] = ReadFromResource( IDR_EXTENSION_API_JSON_BROWSERACTION); unloaded_schemas_["browsingData"] = ReadFromResource( IDR_EXTENSION_API_JSON_BROWSINGDATA); unloaded_schemas_["chromeAuthPrivate"] = ReadFromResource( IDR_EXTENSION_API_JSON_CHROMEAUTHPRIVATE); unloaded_schemas_["chromeosInfoPrivate"] = ReadFromResource( IDR_EXTENSION_API_JSON_CHROMEOSINFOPRIVATE); unloaded_schemas_["contentSettings"] = ReadFromResource( IDR_EXTENSION_API_JSON_CONTENTSETTINGS); unloaded_schemas_["contextMenus"] = ReadFromResource( IDR_EXTENSION_API_JSON_CONTEXTMENUS); unloaded_schemas_["cookies"] = ReadFromResource( IDR_EXTENSION_API_JSON_COOKIES); unloaded_schemas_["debugger"] = ReadFromResource( IDR_EXTENSION_API_JSON_DEBUGGER); unloaded_schemas_["devtools"] = ReadFromResource( IDR_EXTENSION_API_JSON_DEVTOOLS); unloaded_schemas_["experimental.accessibility"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_ACCESSIBILITY); unloaded_schemas_["experimental.alarms"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_ALARMS); unloaded_schemas_["experimental.app"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_APP); unloaded_schemas_["experimental.bookmarkManager"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_BOOKMARKMANAGER); unloaded_schemas_["experimental.declarative"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_DECLARATIVE); unloaded_schemas_["experimental.downloads"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_DOWNLOADS); unloaded_schemas_["experimental.extension"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_EXTENSION); unloaded_schemas_["experimental.fontSettings"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_FONTSSETTINGS); unloaded_schemas_["experimental.identity"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_IDENTITY); unloaded_schemas_["experimental.infobars"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_INFOBARS); unloaded_schemas_["experimental.input.ui"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_INPUT_UI); unloaded_schemas_["experimental.input.virtualKeyboard"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_INPUT_VIRTUALKEYBOARD); unloaded_schemas_["experimental.keybinding"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_KEYBINDING); unloaded_schemas_["experimental.managedMode"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_MANAGEDMODE); unloaded_schemas_["experimental.offscreenTabs"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_OFFSCREENTABS); unloaded_schemas_["experimental.processes"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_PROCESSES); unloaded_schemas_["experimental.rlz"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_RLZ); unloaded_schemas_["experimental.serial"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_SERIAL); unloaded_schemas_["experimental.socket"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_SOCKET); unloaded_schemas_["experimental.speechInput"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_SPEECHINPUT); unloaded_schemas_["experimental.webRequest"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXPERIMENTAL_WEBREQUEST); unloaded_schemas_["extension"] = ReadFromResource( IDR_EXTENSION_API_JSON_EXTENSION); unloaded_schemas_["fileBrowserHandler"] = ReadFromResource( IDR_EXTENSION_API_JSON_FILEBROWSERHANDLER); unloaded_schemas_["fileBrowserPrivate"] = ReadFromResource( IDR_EXTENSION_API_JSON_FILEBROWSERPRIVATE); unloaded_schemas_["history"] = ReadFromResource( IDR_EXTENSION_API_JSON_HISTORY); unloaded_schemas_["i18n"] = ReadFromResource( IDR_EXTENSION_API_JSON_I18N); unloaded_schemas_["idle"] = ReadFromResource( IDR_EXTENSION_API_JSON_IDLE); unloaded_schemas_["input.ime"] = ReadFromResource( IDR_EXTENSION_API_JSON_INPUT_IME); unloaded_schemas_["inputMethodPrivate"] = ReadFromResource( IDR_EXTENSION_API_JSON_INPUTMETHODPRIVATE); unloaded_schemas_["management"] = ReadFromResource( IDR_EXTENSION_API_JSON_MANAGEMENT); unloaded_schemas_["mediaPlayerPrivate"] = ReadFromResource( IDR_EXTENSION_API_JSON_MEDIAPLAYERPRIVATE); unloaded_schemas_["metricsPrivate"] = ReadFromResource( IDR_EXTENSION_API_JSON_METRICSPRIVATE); unloaded_schemas_["offersPrivate"] = ReadFromResource( IDR_EXTENSION_API_JSON_OFFERSPRIVATE); unloaded_schemas_["omnibox"] = ReadFromResource( IDR_EXTENSION_API_JSON_OMNIBOX); unloaded_schemas_["pageAction"] = ReadFromResource( IDR_EXTENSION_API_JSON_PAGEACTION); unloaded_schemas_["pageActions"] = ReadFromResource( IDR_EXTENSION_API_JSON_PAGEACTIONS); unloaded_schemas_["pageCapture"] = ReadFromResource( IDR_EXTENSION_API_JSON_PAGECAPTURE); unloaded_schemas_["permissions"] = ReadFromResource( IDR_EXTENSION_API_JSON_PERMISSIONS); unloaded_schemas_["privacy"] = ReadFromResource( IDR_EXTENSION_API_JSON_PRIVACY); unloaded_schemas_["proxy"] = ReadFromResource( IDR_EXTENSION_API_JSON_PROXY); unloaded_schemas_["storage"] = ReadFromResource( IDR_EXTENSION_API_JSON_STORAGE); unloaded_schemas_["systemPrivate"] = ReadFromResource( IDR_EXTENSION_API_JSON_SYSTEMPRIVATE); unloaded_schemas_["tabs"] = ReadFromResource( IDR_EXTENSION_API_JSON_TABS); unloaded_schemas_["terminalPrivate"] = ReadFromResource( IDR_EXTENSION_API_JSON_TERMINALPRIVATE); unloaded_schemas_["test"] = ReadFromResource( IDR_EXTENSION_API_JSON_TEST); unloaded_schemas_["topSites"] = ReadFromResource( IDR_EXTENSION_API_JSON_TOPSITES); unloaded_schemas_["ttsEngine"] = ReadFromResource( IDR_EXTENSION_API_JSON_TTSENGINE); unloaded_schemas_["tts"] = ReadFromResource( IDR_EXTENSION_API_JSON_TTS); unloaded_schemas_["types"] = ReadFromResource( IDR_EXTENSION_API_JSON_TYPES); unloaded_schemas_["webNavigation"] = ReadFromResource( IDR_EXTENSION_API_JSON_WEBNAVIGATION); unloaded_schemas_["webRequest"] = ReadFromResource( IDR_EXTENSION_API_JSON_WEBREQUEST); unloaded_schemas_["webSocketProxyPrivate"] = ReadFromResource( IDR_EXTENSION_API_JSON_WEBSOCKETPROXYPRIVATE); unloaded_schemas_["webstore"] = ReadFromResource( IDR_EXTENSION_API_JSON_WEBSTORE); unloaded_schemas_["webstorePrivate"] = ReadFromResource( IDR_EXTENSION_API_JSON_WEBSTOREPRIVATE); unloaded_schemas_["windows"] = ReadFromResource( IDR_EXTENSION_API_JSON_WINDOWS); // Schemas to be loaded via JSON generated from IDL files. GeneratedSchemas::Get(&unloaded_schemas_); } ExtensionAPI::~ExtensionAPI() { } bool ExtensionAPI::IsPrivileged(const std::string& full_name) { std::string api_name; std::string child_name; { std::vector<std::string> split; base::SplitString(full_name, '.', &split); std::reverse(split.begin(), split.end()); api_name = split.back(); split.pop_back(); if (api_name == "experimental") { api_name += "." + split.back(); split.pop_back(); } // This only really works properly if split.size() == 1, however: // - if it's empty, it's ok to leave child_name empty; presumably there's // no functions or events with empty names. // - if it's > 1, we can just do our best. if (split.size() > 0) child_name = split[0]; } // GetSchema to ensure that it gets loaded before any checks. const DictionaryValue* schema = GetSchema(api_name); if (completely_unprivileged_apis_.count(api_name)) return false; if (partially_unprivileged_apis_.count(api_name)) { return IsChildNamePrivileged(schema, "functions", child_name) && IsChildNamePrivileged(schema, "events", child_name); } return true; } DictionaryValue* ExtensionAPI::FindListItem( const ListValue* list, const std::string& property_name, const std::string& property_value) { for (size_t i = 0; i < list->GetSize(); ++i) { 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; } bool ExtensionAPI::IsChildNamePrivileged(const DictionaryValue* name_space_node, const std::string& child_kind, const std::string& child_name) { ListValue* child_list = NULL; name_space_node->GetList(child_kind, &child_list); if (!child_list) return true; bool unprivileged = false; DictionaryValue* child = FindListItem(child_list, "name", child_name); if (!child || !child->GetBoolean("unprivileged", &unprivileged)) return true; return !unprivileged; } const DictionaryValue* ExtensionAPI::GetSchema(const std::string& api_name) { SchemaMap::const_iterator maybe_schema = schemas_.find(api_name); if (maybe_schema != schemas_.end()) return maybe_schema->second.get(); // Might not have loaded yet; or might just not exist. std::map<std::string, base::StringPiece>::iterator maybe_schema_resource = unloaded_schemas_.find(api_name); if (maybe_schema_resource == unloaded_schemas_.end()) return NULL; LoadSchema(maybe_schema_resource->second); maybe_schema = schemas_.find(api_name); CHECK(schemas_.end() != maybe_schema); return maybe_schema->second.get(); } scoped_ptr<std::set<std::string> > ExtensionAPI::GetAPIsForContext( Feature::Context context, const Extension* extension, const GURL& url) { // We're forced to load all schemas now because we need to know the metadata // about every API -- and the metadata is stored in the schemas themselves. // This is a shame. // TODO(aa/kalman): store metadata in a separate file and don't load all // schemas. LoadAllSchemas(); scoped_ptr<std::set<std::string> > result(new std::set<std::string>()); switch (context) { case Feature::UNSPECIFIED_CONTEXT: break; case Feature::BLESSED_EXTENSION_CONTEXT: // Availability is determined by the permissions of the extension. CHECK(extension); GetAllowedAPIs(extension, result.get()); ResolveDependencies(result.get()); break; case Feature::UNBLESSED_EXTENSION_CONTEXT: case Feature::CONTENT_SCRIPT_CONTEXT: // Same as BLESSED_EXTENSION_CONTEXT, but only those APIs that are // unprivileged. CHECK(extension); GetAllowedAPIs(extension, result.get()); // Resolving dependencies before removing unprivileged APIs means that // some unprivileged APIs may have unrealised dependencies. Too bad! ResolveDependencies(result.get()); RemovePrivilegedAPIs(result.get()); break; case Feature::WEB_PAGE_CONTEXT: // Availablility is determined by the url. CHECK(url.is_valid()); GetAPIsMatchingURL(url, result.get()); break; } return result.Pass(); } void ExtensionAPI::GetAllowedAPIs( const Extension* extension, std::set<std::string>* out) { for (SchemaMap::const_iterator i = schemas_.begin(); i != schemas_.end(); ++i) { if (extension->required_permission_set()->HasAnyAccessToAPI(i->first) || extension->optional_permission_set()->HasAnyAccessToAPI(i->first)) { out->insert(i->first); } } } void ExtensionAPI::ResolveDependencies(std::set<std::string>* out) { std::set<std::string> missing_dependencies; for (std::set<std::string>::iterator i = out->begin(); i != out->end(); ++i) GetMissingDependencies(*i, *out, &missing_dependencies); while (missing_dependencies.size()) { std::string next = *missing_dependencies.begin(); missing_dependencies.erase(next); out->insert(next); GetMissingDependencies(next, *out, &missing_dependencies); } } void ExtensionAPI::GetMissingDependencies( const std::string& api_name, const std::set<std::string>& excluding, std::set<std::string>* out) { const DictionaryValue* schema = GetSchema(api_name); CHECK(schema) << "Schema for " << api_name << " not found"; ListValue* dependencies = NULL; if (!schema->GetList("dependencies", &dependencies)) return; for (size_t i = 0; i < dependencies->GetSize(); ++i) { std::string api_name; if (dependencies->GetString(i, &api_name) && !excluding.count(api_name)) out->insert(api_name); } } void ExtensionAPI::RemovePrivilegedAPIs(std::set<std::string>* apis) { std::set<std::string> privileged_apis; for (std::set<std::string>::iterator i = apis->begin(); i != apis->end(); ++i) { if (!completely_unprivileged_apis_.count(*i) && !partially_unprivileged_apis_.count(*i)) { privileged_apis.insert(*i); } } for (std::set<std::string>::iterator i = privileged_apis.begin(); i != privileged_apis.end(); ++i) { apis->erase(*i); } } void ExtensionAPI::GetAPIsMatchingURL(const GURL& url, std::set<std::string>* out) { for (std::map<std::string, URLPatternSet>::const_iterator i = url_matching_apis_.begin(); i != url_matching_apis_.end(); ++i) { if (i->second.MatchesURL(url)) out->insert(i->first); } } void ExtensionAPI::LoadAllSchemas() { while (unloaded_schemas_.size()) { LoadSchema(unloaded_schemas_.begin()->second); } } } // namespace extensions