// 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 #include #include "base/file_path.h" #include "base/file_util.h" #include "base/json/json_writer.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/path_service.h" #include "base/values.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/extensions/extension.h" #include "testing/gtest/include/gtest/gtest.h" namespace extensions { namespace { class TestFeatureProvider : public FeatureProvider { public: explicit TestFeatureProvider(Feature::Context context) : context_(context) { } virtual Feature* GetFeature(const std::string& name) OVERRIDE { Feature* result = new Feature(); result->set_name(name); result->extension_types()->insert(Extension::TYPE_EXTENSION); result->contexts()->insert(context_); to_destroy_.push_back(make_linked_ptr(result)); return result; } private: std::vector > to_destroy_; Feature::Context context_; }; TEST(ExtensionAPI, Creation) { ExtensionAPI* shared_instance = ExtensionAPI::GetSharedInstance(); EXPECT_EQ(shared_instance, ExtensionAPI::GetSharedInstance()); scoped_ptr new_instance( ExtensionAPI::CreateWithDefaultConfiguration()); EXPECT_NE(new_instance.get(), scoped_ptr( ExtensionAPI::CreateWithDefaultConfiguration()).get()); ExtensionAPI empty_instance; struct { ExtensionAPI* api; bool expect_populated; } test_data[] = { { shared_instance, true }, { new_instance.get(), true }, { &empty_instance, false } }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { EXPECT_EQ(test_data[i].expect_populated, test_data[i].api->GetSchema("bookmarks.create") != NULL); } } TEST(ExtensionAPI, SplitDependencyName) { struct { std::string input; std::string expected_feature_type; std::string expected_feature_name; } test_data[] = { { "", "api", "" }, // assumes "api" when no type is present { "foo", "api", "foo" }, { "foo:", "foo", "" }, { ":foo", "", "foo" }, { "foo:bar", "foo", "bar" }, { "foo:bar.baz", "foo", "bar.baz" } }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { std::string feature_type; std::string feature_name; ExtensionAPI::SplitDependencyName(test_data[i].input, &feature_type, &feature_name); EXPECT_EQ(test_data[i].expected_feature_type, feature_type) << i; EXPECT_EQ(test_data[i].expected_feature_name, feature_name) << i; } } TEST(ExtensionAPI, IsPrivileged) { scoped_ptr extension_api( ExtensionAPI::CreateWithDefaultConfiguration()); EXPECT_FALSE(extension_api->IsPrivileged("extension.connect")); EXPECT_FALSE(extension_api->IsPrivileged("extension.onConnect")); // Properties are not supported yet. EXPECT_TRUE(extension_api->IsPrivileged("extension.lastError")); // Default unknown names to privileged for paranoia's sake. EXPECT_TRUE(extension_api->IsPrivileged("")); EXPECT_TRUE(extension_api->IsPrivileged("")); EXPECT_TRUE(extension_api->IsPrivileged("extension.")); // Exists, but privileged. EXPECT_TRUE(extension_api->IsPrivileged("extension.getViews")); EXPECT_TRUE(extension_api->IsPrivileged("history.search")); // Whole APIs that are unprivileged. EXPECT_FALSE(extension_api->IsPrivileged("app.getDetails")); EXPECT_FALSE(extension_api->IsPrivileged("app.isInstalled")); EXPECT_FALSE(extension_api->IsPrivileged("storage.local")); EXPECT_FALSE(extension_api->IsPrivileged("storage.local.onChanged")); EXPECT_FALSE(extension_api->IsPrivileged("storage.local.set")); EXPECT_FALSE(extension_api->IsPrivileged("storage.local.MAX_ITEMS")); EXPECT_FALSE(extension_api->IsPrivileged("storage.set")); } TEST(ExtensionAPI, IsPrivilegedFeatures) { struct { std::string filename; std::string api_full_name; bool expect_is_privilged; Feature::Context test2_contexts; } test_data[] = { { "is_privileged_features_1.json", "test", false, Feature::UNSPECIFIED_CONTEXT }, { "is_privileged_features_2.json", "test", true, Feature::UNSPECIFIED_CONTEXT }, { "is_privileged_features_3.json", "test", false, Feature::UNSPECIFIED_CONTEXT }, { "is_privileged_features_4.json", "test.bar", false, Feature::UNSPECIFIED_CONTEXT }, { "is_privileged_features_5.json", "test.bar", true, Feature::BLESSED_EXTENSION_CONTEXT }, { "is_privileged_features_5.json", "test.bar", false, Feature::UNBLESSED_EXTENSION_CONTEXT } }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { FilePath manifest_path; PathService::Get(chrome::DIR_TEST_DATA, &manifest_path); manifest_path = manifest_path.AppendASCII("extensions") .AppendASCII("extension_api_unittest") .AppendASCII(test_data[i].filename); std::string manifest_str; ASSERT_TRUE(file_util::ReadFileToString(manifest_path, &manifest_str)) << test_data[i].filename; ExtensionAPI api; api.RegisterSchema("test", manifest_str); TestFeatureProvider test2_provider(test_data[i].test2_contexts); if (test_data[i].test2_contexts != Feature::UNSPECIFIED_CONTEXT) { api.RegisterDependencyProvider("test2", &test2_provider); } api.LoadAllSchemas(); EXPECT_EQ(test_data[i].expect_is_privilged, api.IsPrivileged(test_data[i].api_full_name)) << i; } } TEST(ExtensionAPI, LazyGetSchema) { scoped_ptr apis(ExtensionAPI::CreateWithDefaultConfiguration()); EXPECT_EQ(NULL, apis->GetSchema("")); EXPECT_EQ(NULL, apis->GetSchema("")); EXPECT_EQ(NULL, apis->GetSchema("experimental")); EXPECT_EQ(NULL, apis->GetSchema("experimental")); EXPECT_EQ(NULL, apis->GetSchema("foo")); EXPECT_EQ(NULL, apis->GetSchema("foo")); EXPECT_TRUE(apis->GetSchema("experimental.dns")); EXPECT_TRUE(apis->GetSchema("experimental.dns")); EXPECT_TRUE(apis->GetSchema("experimental.infobars")); EXPECT_TRUE(apis->GetSchema("experimental.infobars")); EXPECT_TRUE(apis->GetSchema("extension")); EXPECT_TRUE(apis->GetSchema("extension")); EXPECT_TRUE(apis->GetSchema("omnibox")); EXPECT_TRUE(apis->GetSchema("omnibox")); EXPECT_TRUE(apis->GetSchema("storage")); EXPECT_TRUE(apis->GetSchema("storage")); } scoped_refptr CreateExtensionWithPermissions( const std::set& permissions) { DictionaryValue manifest; manifest.SetString("name", "extension"); manifest.SetString("version", "1.0"); manifest.SetInteger("manifest_version", 2); { scoped_ptr permissions_list(new ListValue()); for (std::set::const_iterator i = permissions.begin(); i != permissions.end(); ++i) { permissions_list->Append(Value::CreateStringValue(*i)); } manifest.Set("permissions", permissions_list.release()); } std::string error; scoped_refptr extension(Extension::Create( FilePath(), Extension::LOAD, manifest, Extension::NO_FLAGS, &error)); CHECK(extension.get()); CHECK(error.empty()); return extension; } scoped_refptr CreateExtensionWithPermission( const std::string& permission) { std::set permissions; permissions.insert(permission); return CreateExtensionWithPermissions(permissions); } TEST(ExtensionAPI, ExtensionWithUnprivilegedAPIs) { scoped_refptr extension; { std::set permissions; permissions.insert("storage"); permissions.insert("history"); extension = CreateExtensionWithPermissions(permissions); } scoped_ptr extension_api( ExtensionAPI::CreateWithDefaultConfiguration()); scoped_ptr > privileged_apis = extension_api->GetAPIsForContext( Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL()); scoped_ptr > unprivileged_apis = extension_api->GetAPIsForContext( Feature::UNBLESSED_EXTENSION_CONTEXT, extension.get(), GURL()); scoped_ptr > content_script_apis = extension_api->GetAPIsForContext( Feature::CONTENT_SCRIPT_CONTEXT, extension.get(), GURL()); // "storage" is completely unprivileged. EXPECT_EQ(1u, privileged_apis->count("storage")); EXPECT_EQ(1u, unprivileged_apis->count("storage")); EXPECT_EQ(1u, content_script_apis->count("storage")); // "extension" is partially unprivileged. EXPECT_EQ(1u, privileged_apis->count("extension")); EXPECT_EQ(1u, unprivileged_apis->count("extension")); EXPECT_EQ(1u, content_script_apis->count("extension")); // "history" is entirely privileged. EXPECT_EQ(1u, privileged_apis->count("history")); EXPECT_EQ(0u, unprivileged_apis->count("history")); EXPECT_EQ(0u, content_script_apis->count("history")); } TEST(ExtensionAPI, ExtensionWithDependencies) { // Extension with the "ttsEngine" permission but not the "tts" permission; it // must load TTS. { scoped_refptr extension = CreateExtensionWithPermission("ttsEngine"); scoped_ptr api( ExtensionAPI::CreateWithDefaultConfiguration()); scoped_ptr > apis = api->GetAPIsForContext( Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL()); EXPECT_EQ(1u, apis->count("ttsEngine")); EXPECT_EQ(1u, apis->count("tts")); } // Conversely, extension with the "tts" permission but not the "ttsEngine" // permission shouldn't get the "ttsEngine" permission. { scoped_refptr extension = CreateExtensionWithPermission("tts"); scoped_ptr api( ExtensionAPI::CreateWithDefaultConfiguration()); scoped_ptr > apis = api->GetAPIsForContext( Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL()); EXPECT_EQ(0u, apis->count("ttsEngine")); EXPECT_EQ(1u, apis->count("tts")); } } bool MatchesURL( ExtensionAPI* api, const std::string& api_name, const std::string& url) { scoped_ptr > apis = api->GetAPIsForContext(Feature::WEB_PAGE_CONTEXT, NULL, GURL(url)); return apis->count(api_name); } TEST(ExtensionAPI, URLMatching) { scoped_ptr api(ExtensionAPI::CreateWithDefaultConfiguration()); // "app" API is available to all URLs that content scripts can be injected. EXPECT_TRUE(MatchesURL(api.get(), "app", "http://example.com/example.html")); EXPECT_TRUE(MatchesURL(api.get(), "app", "https://blah.net")); EXPECT_TRUE(MatchesURL(api.get(), "app", "file://somefile.html")); // But not internal URLs (for chrome-extension:// the app API is injected by // GetSchemasForExtension). EXPECT_FALSE(MatchesURL(api.get(), "app", "about:flags")); EXPECT_FALSE(MatchesURL(api.get(), "app", "chrome://flags")); EXPECT_FALSE(MatchesURL(api.get(), "app", "chrome-extension://fakeextension")); // "storage" API (for example) isn't available to any URLs. EXPECT_FALSE(MatchesURL(api.get(), "storage", "http://example.com/example.html")); EXPECT_FALSE(MatchesURL(api.get(), "storage", "https://blah.net")); EXPECT_FALSE(MatchesURL(api.get(), "storage", "file://somefile.html")); EXPECT_FALSE(MatchesURL(api.get(), "storage", "about:flags")); EXPECT_FALSE(MatchesURL(api.get(), "storage", "chrome://flags")); EXPECT_FALSE(MatchesURL(api.get(), "storage", "chrome-extension://fakeextension")); } TEST(ExtensionAPI, GetAPINameFromFullName) { struct { std::string input; std::string api_name; std::string child_name; } test_data[] = { { "", "", "" }, { "unknown", "", "" }, { "bookmarks", "bookmarks", "" }, { "bookmarks.", "bookmarks", "" }, { ".bookmarks", "", "" }, { "bookmarks.create", "bookmarks", "create" }, { "bookmarks.create.", "bookmarks", "create." }, { "bookmarks.create.monkey", "bookmarks", "create.monkey" }, { "experimental.bookmarkManager", "experimental.bookmarkManager", "" }, { "experimental.bookmarkManager.copy", "experimental.bookmarkManager", "copy" } }; scoped_ptr api(ExtensionAPI::CreateWithDefaultConfiguration()); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { std::string child_name; std::string api_name = api->GetAPINameFromFullName(test_data[i].input, &child_name); EXPECT_EQ(test_data[i].api_name, api_name) << test_data[i].input; EXPECT_EQ(test_data[i].child_name, child_name) << test_data[i].input; } } TEST(ExtensionAPI, DefaultConfigurationFeatures) { scoped_ptr api(ExtensionAPI::CreateWithDefaultConfiguration()); Feature* bookmarks = api->GetFeature("bookmarks"); Feature* bookmarks_create = api->GetFeature("bookmarks.create"); struct { Feature* feature; // TODO(aa): More stuff to test over time. } test_data[] = { { bookmarks }, { bookmarks_create } }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { Feature* feature = test_data[i].feature; ASSERT_TRUE(feature) << i; EXPECT_TRUE(feature->whitelist()->empty()); EXPECT_TRUE(feature->extension_types()->empty()); EXPECT_EQ(1u, feature->contexts()->size()); EXPECT_TRUE(feature->contexts()->count( Feature::BLESSED_EXTENSION_CONTEXT)); EXPECT_EQ(Feature::UNSPECIFIED_LOCATION, feature->location()); EXPECT_EQ(Feature::UNSPECIFIED_PLATFORM, feature->platform()); EXPECT_EQ(0, feature->min_manifest_version()); EXPECT_EQ(0, feature->max_manifest_version()); } } TEST(ExtensionAPI, FeaturesRequireContexts) { scoped_ptr schema1(new ListValue()); DictionaryValue* feature_definition = new DictionaryValue(); schema1->Append(feature_definition); feature_definition->SetString("namespace", "test"); feature_definition->SetBoolean("uses_feature_system", true); scoped_ptr schema2(schema1->DeepCopy()); ListValue* contexts = new ListValue(); contexts->Append(Value::CreateStringValue("content_script")); feature_definition->Set("contexts", contexts); struct { ListValue* schema; bool expect_success; } test_data[] = { { schema1.get(), true }, { schema2.get(), false } }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { std::string schema_source; base::JSONWriter::Write(test_data[i].schema, &schema_source); ExtensionAPI api; api.RegisterSchema("test", base::StringPiece(schema_source)); api.LoadAllSchemas(); Feature* feature = api.GetFeature("test"); EXPECT_EQ(test_data[i].expect_success, feature != NULL) << i; } } static void GetDictionaryFromList(const DictionaryValue* schema, const std::string& list_name, const int list_index, DictionaryValue** out) { ListValue* list; EXPECT_TRUE(schema->GetList(list_name, &list)); EXPECT_TRUE(list->GetDictionary(list_index, out)); } TEST(ExtensionAPI, TypesHaveNamespace) { FilePath manifest_path; PathService::Get(chrome::DIR_TEST_DATA, &manifest_path); manifest_path = manifest_path.AppendASCII("extensions") .AppendASCII("extension_api_unittest") .AppendASCII("types_have_namespace.json"); std::string manifest_str; ASSERT_TRUE(file_util::ReadFileToString(manifest_path, &manifest_str)) << "Failed to load: " << manifest_path.value(); ExtensionAPI api; api.RegisterSchema("test.foo", manifest_str); api.LoadAllSchemas(); const DictionaryValue* schema = api.GetSchema("test.foo"); DictionaryValue* dict; DictionaryValue* sub_dict; std::string type; GetDictionaryFromList(schema, "types", 0, &dict); EXPECT_TRUE(dict->GetString("id", &type)); EXPECT_EQ("test.foo.TestType", type); EXPECT_TRUE(dict->GetString("customBindings", &type)); EXPECT_EQ("test.foo.TestType", type); EXPECT_TRUE(dict->GetDictionary("properties", &sub_dict)); DictionaryValue* property; EXPECT_TRUE(sub_dict->GetDictionary("foo", &property)); EXPECT_TRUE(property->GetString("$ref", &type)); EXPECT_EQ("test.foo.OtherType", type); EXPECT_TRUE(sub_dict->GetDictionary("bar", &property)); EXPECT_TRUE(property->GetString("$ref", &type)); EXPECT_EQ("fully.qualified.Type", type); GetDictionaryFromList(schema, "functions", 0, &dict); GetDictionaryFromList(dict, "parameters", 0, &sub_dict); EXPECT_TRUE(sub_dict->GetString("$ref", &type)); EXPECT_EQ("test.foo.TestType", type); EXPECT_TRUE(dict->GetDictionary("returns", &sub_dict)); EXPECT_TRUE(sub_dict->GetString("$ref", &type)); EXPECT_EQ("fully.qualified.Type", type); GetDictionaryFromList(schema, "functions", 1, &dict); GetDictionaryFromList(dict, "parameters", 0, &sub_dict); EXPECT_TRUE(sub_dict->GetString("$ref", &type)); EXPECT_EQ("fully.qualified.Type", type); EXPECT_TRUE(dict->GetDictionary("returns", &sub_dict)); EXPECT_TRUE(sub_dict->GetString("$ref", &type)); EXPECT_EQ("test.foo.TestType", type); GetDictionaryFromList(schema, "events", 0, &dict); GetDictionaryFromList(dict, "parameters", 0, &sub_dict); EXPECT_TRUE(sub_dict->GetString("$ref", &type)); EXPECT_EQ("test.foo.TestType", type); GetDictionaryFromList(dict, "parameters", 1, &sub_dict); EXPECT_TRUE(sub_dict->GetString("$ref", &type)); EXPECT_EQ("fully.qualified.Type", type); } } // namespace } // namespace extensions