// 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_util.h" #include "base/files/file_path.h" #include "base/json/json_reader.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/strings/stringprintf.h" #include "base/values.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_builder.h" #include "chrome/common/extensions/extension_test_util.h" #include "chrome/common/extensions/features/api_feature.h" #include "chrome/common/extensions/features/base_feature_provider.h" #include "chrome/common/extensions/features/simple_feature.h" #include "chrome/common/extensions/value_builder.h" #include "extensions/common/manifest.h" #include "extensions/common/manifest_constants.h" #include "testing/gtest/include/gtest/gtest.h" namespace extensions { using extension_test_util::BuildExtension; SimpleFeature* CreateAPIFeature() { return new APIFeature(); } TEST(ExtensionAPITest, 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(ExtensionAPITest, 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(ExtensionAPITest, IsPrivileged) { scoped_ptr extension_api( ExtensionAPI::CreateWithDefaultConfiguration()); EXPECT_FALSE(extension_api->IsPrivileged("runtime.connect")); EXPECT_FALSE(extension_api->IsPrivileged("runtime.onConnect")); EXPECT_FALSE(extension_api->IsPrivileged("runtime.lastError")); // 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(ExtensionAPITest, IsPrivilegedFeatures) { struct { std::string api_full_name; bool expect_is_privilged; } test_data[] = { { "test1", false }, { "test1.foo", true }, { "test2", true }, { "test2.foo", false }, { "test2.bar", false }, { "test2.baz", true }, { "test3", false }, { "test3.foo", true }, { "test4", false } }; base::FilePath api_features_path; PathService::Get(chrome::DIR_TEST_DATA, &api_features_path); api_features_path = api_features_path.AppendASCII("extensions") .AppendASCII("extension_api_unittest") .AppendASCII("privileged_api_features.json"); std::string api_features_str; ASSERT_TRUE(base::ReadFileToString( api_features_path, &api_features_str)) << "privileged_api_features.json"; scoped_ptr value(static_cast( base::JSONReader::Read(api_features_str))); BaseFeatureProvider api_feature_provider(*value, CreateAPIFeature); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { ExtensionAPI api; api.RegisterDependencyProvider("api", &api_feature_provider); EXPECT_EQ(test_data[i].expect_is_privilged, api.IsPrivileged(test_data[i].api_full_name)) << i; } } TEST(ExtensionAPITest, APIFeatures) { struct { std::string api_full_name; bool expect_is_available; Feature::Context context; GURL url; } test_data[] = { { "test1", false, Feature::WEB_PAGE_CONTEXT, GURL() }, { "test1", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() }, { "test1", true, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() }, { "test1", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() }, { "test2", true, Feature::WEB_PAGE_CONTEXT, GURL("http://google.com") }, { "test2", false, Feature::BLESSED_EXTENSION_CONTEXT, GURL("http://google.com") }, { "test2.foo", false, Feature::WEB_PAGE_CONTEXT, GURL("http://google.com") }, { "test2.foo", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() }, { "test3", false, Feature::WEB_PAGE_CONTEXT, GURL("http://google.com") }, { "test3.foo", true, Feature::WEB_PAGE_CONTEXT, GURL("http://google.com") }, { "test3.foo", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL("http://bad.com") }, { "test4", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL("http://bad.com") }, { "test4.foo", false, Feature::BLESSED_EXTENSION_CONTEXT, GURL("http://bad.com") }, { "test4.foo", false, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL("http://bad.com") }, { "test4.foo.foo", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() }, { "test5", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") }, { "test5", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") }, { "test5.blah", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") }, { "test5.blah", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") }, { "test6", false, Feature::BLESSED_EXTENSION_CONTEXT, GURL() }, { "test6.foo", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() }, { "test7", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") }, { "test7.foo", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") }, { "test7.foo", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") }, { "test7.bar", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") }, { "test7.bar", false, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") }, // Test parent/child. { "parent1", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() }, { "parent1", false, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") }, { "parent1.child1", false, Feature::CONTENT_SCRIPT_CONTEXT, GURL() }, { "parent1.child1", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") }, { "parent1.child2", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() }, { "parent1.child2", false, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") }, { "parent2", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() }, { "parent2", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() }, { "parent2", true, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() }, { "parent2.child3", false, Feature::CONTENT_SCRIPT_CONTEXT, GURL() }, { "parent2.child3", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() }, { "parent2.child3", false, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() }, { "parent2.child3.child.child", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() }, { "parent2.child3.child.child", false, Feature::BLESSED_EXTENSION_CONTEXT, GURL() }, { "parent2.child3.child.child", true, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() }, { "parent3", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() }, { "parent3", false, Feature::BLESSED_EXTENSION_CONTEXT, GURL() }, { "parent3", false, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() }, { "parent3.noparent", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() }, { "parent3.noparent", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() }, { "parent3.noparent", true, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() }, { "parent3.noparent.child", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() }, { "parent3.noparent.child", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() }, { "parent3.noparent.child", true, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() } }; base::FilePath api_features_path; PathService::Get(chrome::DIR_TEST_DATA, &api_features_path); api_features_path = api_features_path.AppendASCII("extensions") .AppendASCII("extension_api_unittest") .AppendASCII("api_features.json"); std::string api_features_str; ASSERT_TRUE(base::ReadFileToString( api_features_path, &api_features_str)) << "api_features.json"; scoped_ptr value(static_cast( base::JSONReader::Read(api_features_str))); BaseFeatureProvider api_feature_provider(*value, CreateAPIFeature); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { ExtensionAPI api; api.RegisterDependencyProvider("api", &api_feature_provider); for (base::DictionaryValue::Iterator iter(*value); !iter.IsAtEnd(); iter.Advance()) { if (iter.key().find(".") == std::string::npos) api.RegisterSchemaResource(iter.key(), 0); } EXPECT_EQ(test_data[i].expect_is_available, api.IsAvailable(test_data[i].api_full_name, NULL, test_data[i].context, test_data[i].url).is_available()) << i; } } TEST(ExtensionAPITest, IsAnyFeatureAvailableToContext) { scoped_refptr app = ExtensionBuilder() .SetManifest(DictionaryBuilder() .Set("name", "app") .Set("app", DictionaryBuilder() .Set("background", DictionaryBuilder() .Set("scripts", ListBuilder().Append("background.js")))) .Set("version", "1") .Set("manifest_version", 2)).Build(); scoped_refptr extension = ExtensionBuilder() .SetManifest(DictionaryBuilder() .Set("name", "extension") .Set("version", "1") .Set("manifest_version", 2)).Build(); struct { std::string api_full_name; bool expect_is_available; Feature::Context context; const Extension* extension; GURL url; } test_data[] = { { "test1", false, Feature::WEB_PAGE_CONTEXT, NULL, GURL() }, { "test1", true, Feature::UNBLESSED_EXTENSION_CONTEXT, NULL, GURL() }, { "test1", false, Feature::UNBLESSED_EXTENSION_CONTEXT, app.get(), GURL() }, { "test1", true, Feature::UNBLESSED_EXTENSION_CONTEXT, extension.get(), GURL() }, { "test2", true, Feature::CONTENT_SCRIPT_CONTEXT, NULL, GURL() }, { "test2", true, Feature::WEB_PAGE_CONTEXT, NULL, GURL("http://google.com") }, { "test2.foo", false, Feature::WEB_PAGE_CONTEXT, NULL, GURL("http://google.com") }, { "test3", true, Feature::CONTENT_SCRIPT_CONTEXT, NULL, GURL() }, { "test3", true, Feature::WEB_PAGE_CONTEXT, NULL, GURL("http://foo.com") }, { "test4.foo", true, Feature::CONTENT_SCRIPT_CONTEXT, NULL, GURL() }, { "test7", false, Feature::WEB_PAGE_CONTEXT, NULL, GURL("http://google.com") }, { "test7", true, Feature::WEB_PAGE_CONTEXT, NULL, GURL("http://foo.com") }, { "test7", false, Feature::WEB_PAGE_CONTEXT, NULL, GURL("http://bar.com") } }; base::FilePath api_features_path; PathService::Get(chrome::DIR_TEST_DATA, &api_features_path); api_features_path = api_features_path.AppendASCII("extensions") .AppendASCII("extension_api_unittest") .AppendASCII("api_features.json"); std::string api_features_str; ASSERT_TRUE(base::ReadFileToString( api_features_path, &api_features_str)) << "api_features.json"; scoped_ptr value(static_cast( base::JSONReader::Read(api_features_str))); BaseFeatureProvider api_feature_provider(*value, CreateAPIFeature); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { ExtensionAPI api; api.RegisterDependencyProvider("api", &api_feature_provider); for (base::DictionaryValue::Iterator iter(*value); !iter.IsAtEnd(); iter.Advance()) { if (iter.key().find(".") == std::string::npos) api.RegisterSchemaResource(iter.key(), 0); } EXPECT_EQ(test_data[i].expect_is_available, api.IsAnyFeatureAvailableToContext(test_data[i].api_full_name, test_data[i].extension, test_data[i].context, test_data[i].url)) << i; } } TEST(ExtensionAPITest, LazyGetSchema) { scoped_ptr apis(ExtensionAPI::CreateWithDefaultConfiguration()); EXPECT_EQ(NULL, apis->GetSchema(std::string())); EXPECT_EQ(NULL, apis->GetSchema(std::string())); 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("dns")); EXPECT_TRUE(apis->GetSchema("dns")); EXPECT_TRUE(apis->GetSchema("extension")); EXPECT_TRUE(apis->GetSchema("extension")); EXPECT_TRUE(apis->GetSchema("infobars")); EXPECT_TRUE(apis->GetSchema("infobars")); 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) { base::DictionaryValue manifest; manifest.SetString("name", "extension"); manifest.SetString("version", "1.0"); manifest.SetInteger("manifest_version", 2); { scoped_ptr permissions_list(new base::ListValue()); for (std::set::const_iterator i = permissions.begin(); i != permissions.end(); ++i) { permissions_list->Append(new base::StringValue(*i)); } manifest.Set("permissions", permissions_list.release()); } std::string error; scoped_refptr extension(Extension::Create( base::FilePath(), Manifest::UNPACKED, 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(ExtensionAPITest, ExtensionWithUnprivilegedAPIs) { scoped_refptr extension; { std::set permissions; permissions.insert("storage"); permissions.insert("history"); extension = CreateExtensionWithPermissions(permissions); } scoped_ptr extension_api( ExtensionAPI::CreateWithDefaultConfiguration()); // "runtime" has privileged parts that should not be accessed by content // scripts. EXPECT_FALSE(extension_api->IsAnyFeatureAvailableToContext( "runtime.getBackgroundPage", NULL, Feature::CONTENT_SCRIPT_CONTEXT, GURL())); EXPECT_FALSE(extension_api->IsAnyFeatureAvailableToContext( "runtime.sendNativeMessage", NULL, Feature::CONTENT_SCRIPT_CONTEXT, GURL())); // "runtime" also has unprivileged parts. EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( "runtime.sendMessage", NULL, Feature::CONTENT_SCRIPT_CONTEXT, GURL())); EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( "runtime.id", NULL, Feature::CONTENT_SCRIPT_CONTEXT, GURL())); // "storage" is completely unprivileged. EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( "storage", NULL, Feature::BLESSED_EXTENSION_CONTEXT, GURL())); EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( "storage", NULL, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL())); EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( "storage", NULL, Feature::CONTENT_SCRIPT_CONTEXT, GURL())); // "extension" is partially unprivileged. EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( "extension", NULL, Feature::BLESSED_EXTENSION_CONTEXT, GURL())); EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( "extension", NULL, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL())); EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( "extension", NULL, Feature::CONTENT_SCRIPT_CONTEXT, GURL())); // "history" is entirely privileged. EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( "history", NULL, Feature::BLESSED_EXTENSION_CONTEXT, GURL())); EXPECT_FALSE(extension_api->IsAnyFeatureAvailableToContext( "history", NULL, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL())); EXPECT_FALSE(extension_api->IsAnyFeatureAvailableToContext( "history", NULL, Feature::CONTENT_SCRIPT_CONTEXT, GURL())); } scoped_refptr CreateHostedApp() { base::DictionaryValue values; values.SetString(manifest_keys::kName, "test"); values.SetString(manifest_keys::kVersion, "0.1"); values.Set(manifest_keys::kWebURLs, new base::ListValue()); values.SetString(manifest_keys::kLaunchWebURL, "http://www.example.com"); std::string error; scoped_refptr extension(Extension::Create( base::FilePath(), Manifest::INTERNAL, values, Extension::NO_FLAGS, &error)); CHECK(extension.get()); return extension; } scoped_refptr CreatePackagedAppWithPermissions( const std::set& permissions) { base::DictionaryValue values; values.SetString(manifest_keys::kName, "test"); values.SetString(manifest_keys::kVersion, "0.1"); values.SetString(manifest_keys::kPlatformAppBackground, "http://www.example.com"); base::DictionaryValue* app = new base::DictionaryValue(); base::DictionaryValue* background = new base::DictionaryValue(); base::ListValue* scripts = new base::ListValue(); scripts->Append(new base::StringValue("test.js")); background->Set("scripts", scripts); app->Set("background", background); values.Set(manifest_keys::kApp, app); { scoped_ptr permissions_list(new base::ListValue()); for (std::set::const_iterator i = permissions.begin(); i != permissions.end(); ++i) { permissions_list->Append(new base::StringValue(*i)); } values.Set("permissions", permissions_list.release()); } std::string error; scoped_refptr extension(Extension::Create( base::FilePath(), Manifest::INTERNAL, values, Extension::NO_FLAGS, &error)); CHECK(extension.get()) << error; return extension; } TEST(ExtensionAPITest, HostedAppPermissions) { scoped_refptr extension = CreateHostedApp(); scoped_ptr extension_api( ExtensionAPI::CreateWithDefaultConfiguration()); // "runtime" and "tabs" should not be available in hosted apps. EXPECT_FALSE(extension_api->IsAvailable("runtime", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL()).is_available()); EXPECT_FALSE(extension_api->IsAvailable("runtime.id", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL()).is_available()); EXPECT_FALSE(extension_api->IsAvailable("runtime.sendMessage", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL()).is_available()); EXPECT_FALSE(extension_api->IsAvailable("runtime.sendNativeMessage", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL()).is_available()); EXPECT_FALSE(extension_api->IsAvailable("tabs.create", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL()).is_available()); } TEST(ExtensionAPITest, AppAndFriendsAvailability) { scoped_ptr extension_api( ExtensionAPI::CreateWithDefaultConfiguration()); // Make sure chrome.app.runtime and chrome.app.window are available to apps, // and chrome.app is not. { std::set permissions; permissions.insert("app.runtime"); permissions.insert("app.window"); scoped_refptr extension = CreatePackagedAppWithPermissions(permissions); EXPECT_FALSE(extension_api->IsAvailable( "app", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL("http://foo.com")).is_available()); EXPECT_TRUE(extension_api->IsAvailable( "app.runtime", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL("http://foo.com")).is_available()); EXPECT_TRUE(extension_api->IsAvailable( "app.window", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL("http://foo.com")).is_available()); } // Make sure chrome.app.runtime and chrome.app.window are not available to // extensions, and chrome.app is. { std::set permissions; scoped_refptr extension = CreateExtensionWithPermissions(permissions); EXPECT_TRUE(extension_api->IsAvailable( "app", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL("http://foo.com")).is_available()); EXPECT_FALSE(extension_api->IsAvailable( "app.runtime", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL("http://foo.com")).is_available()); EXPECT_FALSE(extension_api->IsAvailable( "app.window", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL("http://foo.com")).is_available()); } } TEST(ExtensionAPITest, ExtensionWithDependencies) { // Extension with the "ttsEngine" permission but not the "tts" permission; it // should not automatically get "tts" permission. { scoped_refptr extension = CreateExtensionWithPermission("ttsEngine"); scoped_ptr api( ExtensionAPI::CreateWithDefaultConfiguration()); EXPECT_TRUE(api->IsAvailable("ttsEngine", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL()).is_available()); EXPECT_FALSE(api->IsAvailable("tts", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL()).is_available()); } // 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()); EXPECT_FALSE(api->IsAvailable("ttsEngine", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL()).is_available()); EXPECT_TRUE(api->IsAvailable("tts", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL()).is_available()); } } bool MatchesURL( ExtensionAPI* api, const std::string& api_name, const std::string& url) { return api->IsAvailable( api_name, NULL, Feature::WEB_PAGE_CONTEXT, GURL(url)).is_available(); } TEST(ExtensionAPITest, 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. EXPECT_FALSE(MatchesURL(api.get(), "app", "about:flags")); EXPECT_FALSE(MatchesURL(api.get(), "app", "chrome://flags")); // "app" should be available to chrome-extension URLs. EXPECT_TRUE(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(ExtensionAPITest, 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" }, { "bookmarkManagerPrivate", "bookmarkManagerPrivate", "" }, { "bookmarkManagerPrivate.copy", "bookmarkManagerPrivate", "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(ExtensionAPITest, DefaultConfigurationFeatures) { scoped_ptr api(ExtensionAPI::CreateWithDefaultConfiguration()); SimpleFeature* bookmarks = static_cast( api->GetFeatureDependency("api:bookmarks")); SimpleFeature* bookmarks_create = static_cast( api->GetFeatureDependency("api:bookmarks.create")); struct { SimpleFeature* 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) { SimpleFeature* feature = test_data[i].feature; ASSERT_TRUE(feature) << i; EXPECT_TRUE(feature->whitelist()->empty()); EXPECT_TRUE(feature->extension_types()->empty()); EXPECT_EQ(1u, feature->GetContexts()->size()); EXPECT_TRUE(feature->GetContexts()->count( Feature::BLESSED_EXTENSION_CONTEXT)); EXPECT_EQ(Feature::UNSPECIFIED_LOCATION, feature->location()); EXPECT_TRUE(feature->platforms()->empty()); EXPECT_EQ(0, feature->min_manifest_version()); EXPECT_EQ(0, feature->max_manifest_version()); } } TEST(ExtensionAPITest, FeaturesRequireContexts) { // TODO(cduvall): Make this check API featues. scoped_ptr api_features1(new base::DictionaryValue()); scoped_ptr api_features2(new base::DictionaryValue()); base::DictionaryValue* test1 = new base::DictionaryValue(); base::DictionaryValue* test2 = new base::DictionaryValue(); base::ListValue* contexts = new base::ListValue(); contexts->Append(new base::StringValue("content_script")); test1->Set("contexts", contexts); test1->SetString("channel", "stable"); test2->SetString("channel", "stable"); api_features1->Set("test", test1); api_features2->Set("test", test2); struct { base::DictionaryValue* api_features; bool expect_success; } test_data[] = { { api_features1.get(), true }, { api_features2.get(), false } }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { BaseFeatureProvider api_feature_provider(*test_data[i].api_features, CreateAPIFeature); Feature* feature = api_feature_provider.GetFeature("test"); EXPECT_EQ(test_data[i].expect_success, feature != NULL) << i; } } static void GetDictionaryFromList(const base::DictionaryValue* schema, const std::string& list_name, const int list_index, const base::DictionaryValue** out) { const base::ListValue* list; EXPECT_TRUE(schema->GetList(list_name, &list)); EXPECT_TRUE(list->GetDictionary(list_index, out)); } TEST(ExtensionAPITest, TypesHaveNamespace) { base::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(base::ReadFileToString(manifest_path, &manifest_str)) << "Failed to load: " << manifest_path.value(); ExtensionAPI api; api.RegisterSchemaResource("test.foo", 0); api.LoadSchema("test.foo", manifest_str); const base::DictionaryValue* schema = api.GetSchema("test.foo"); const base::DictionaryValue* dict; const base::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)); const base::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); } // Tests API availability with an empty manifest. TEST(ExtensionAPITest, NoPermissions) { const struct { const char* permission_name; bool expect_success; } kTests[] = { // Test default module/package permission. { "extension", true }, { "i18n", true }, { "permissions", true }, { "runtime", true }, { "test", true }, // These require manifest keys. { "browserAction", false }, { "pageAction", false }, { "pageActions", false }, // Some negative tests. { "bookmarks", false }, { "cookies", false }, { "history", false }, // Make sure we find the module name after stripping '.' { "runtime.abcd.onStartup", true }, // Test Tabs/Windows functions. { "tabs.create", true }, { "tabs.duplicate", true }, { "tabs.onRemoved", true }, { "tabs.remove", true }, { "tabs.update", true }, { "tabs.getSelected", true }, { "tabs.onUpdated", true }, { "windows.get", true }, { "windows.create", true }, { "windows.remove", true }, { "windows.update", true }, // Test some whitelisted functions. These require no permissions. { "app.getDetails", true }, { "app.getDetailsForFrame", true }, { "app.getIsInstalled", true }, { "app.installState", true }, { "app.runningState", true }, { "management.getPermissionWarningsByManifest", true }, { "management.uninstallSelf", true }, // But other functions in those modules do. { "management.getPermissionWarningsById", false }, }; scoped_ptr extension_api( ExtensionAPI::CreateWithDefaultConfiguration()); scoped_refptr extension = BuildExtension(ExtensionBuilder().Pass()).Build(); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) { EXPECT_EQ(kTests[i].expect_success, extension_api->IsAvailable(kTests[i].permission_name, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL()).is_available()) << "Permission being tested: " << kTests[i].permission_name; } } // Tests that permissions that require manifest keys are available when those // keys are present. TEST(ExtensionAPITest, ManifestKeys) { scoped_ptr extension_api( ExtensionAPI::CreateWithDefaultConfiguration()); scoped_refptr extension = BuildExtension(ExtensionBuilder().Pass()) .MergeManifest(DictionaryBuilder().Set("browser_action", DictionaryBuilder().Pass())) .Build(); EXPECT_TRUE(extension_api->IsAvailable("browserAction", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL()).is_available()); EXPECT_FALSE(extension_api->IsAvailable("pageAction", extension.get(), Feature::BLESSED_EXTENSION_CONTEXT, GURL()).is_available()); } } // namespace extensions