| // 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 <string> |
| #include <vector> |
| |
| #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<linked_ptr<Feature> > to_destroy_; |
| Feature::Context context_; |
| }; |
| |
| TEST(ExtensionAPI, Creation) { |
| ExtensionAPI* shared_instance = ExtensionAPI::GetSharedInstance(); |
| EXPECT_EQ(shared_instance, ExtensionAPI::GetSharedInstance()); |
| |
| scoped_ptr<ExtensionAPI> new_instance( |
| ExtensionAPI::CreateWithDefaultConfiguration()); |
| EXPECT_NE(new_instance.get(), |
| scoped_ptr<ExtensionAPI>( |
| 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<ExtensionAPI> 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("<unknown-namespace>")); |
| EXPECT_TRUE(extension_api->IsPrivileged("extension.<unknown-member>")); |
| |
| // 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<ExtensionAPI> 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<Extension> CreateExtensionWithPermissions( |
| const std::set<std::string>& permissions) { |
| DictionaryValue manifest; |
| manifest.SetString("name", "extension"); |
| manifest.SetString("version", "1.0"); |
| manifest.SetInteger("manifest_version", 2); |
| { |
| scoped_ptr<ListValue> permissions_list(new ListValue()); |
| for (std::set<std::string>::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(Extension::Create( |
| FilePath(), Extension::LOAD, manifest, Extension::NO_FLAGS, &error)); |
| CHECK(extension.get()); |
| CHECK(error.empty()); |
| |
| return extension; |
| } |
| |
| scoped_refptr<Extension> CreateExtensionWithPermission( |
| const std::string& permission) { |
| std::set<std::string> permissions; |
| permissions.insert(permission); |
| return CreateExtensionWithPermissions(permissions); |
| } |
| |
| TEST(ExtensionAPI, ExtensionWithUnprivilegedAPIs) { |
| scoped_refptr<Extension> extension; |
| { |
| std::set<std::string> permissions; |
| permissions.insert("storage"); |
| permissions.insert("history"); |
| extension = CreateExtensionWithPermissions(permissions); |
| } |
| |
| scoped_ptr<ExtensionAPI> extension_api( |
| ExtensionAPI::CreateWithDefaultConfiguration()); |
| |
| scoped_ptr<std::set<std::string> > privileged_apis = |
| extension_api->GetAPIsForContext( |
| Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL()); |
| |
| scoped_ptr<std::set<std::string> > unprivileged_apis = |
| extension_api->GetAPIsForContext( |
| Feature::UNBLESSED_EXTENSION_CONTEXT, extension.get(), GURL()); |
| |
| scoped_ptr<std::set<std::string> > 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> extension = |
| CreateExtensionWithPermission("ttsEngine"); |
| scoped_ptr<ExtensionAPI> api( |
| ExtensionAPI::CreateWithDefaultConfiguration()); |
| scoped_ptr<std::set<std::string> > 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> extension = |
| CreateExtensionWithPermission("tts"); |
| scoped_ptr<ExtensionAPI> api( |
| ExtensionAPI::CreateWithDefaultConfiguration()); |
| scoped_ptr<std::set<std::string> > 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<std::set<std::string> > apis = |
| api->GetAPIsForContext(Feature::WEB_PAGE_CONTEXT, NULL, GURL(url)); |
| return apis->count(api_name); |
| } |
| |
| TEST(ExtensionAPI, URLMatching) { |
| scoped_ptr<ExtensionAPI> 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" }, |
| { "bookmarkManagerPrivate", "bookmarkManagerPrivate", "" }, |
| { "bookmarkManagerPrivate.copy", "bookmarkManagerPrivate", "copy" } |
| }; |
| |
| scoped_ptr<ExtensionAPI> 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<ExtensionAPI> 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<ListValue> 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<ListValue> 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, |
| const DictionaryValue** out) { |
| const 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"); |
| |
| const DictionaryValue* dict; |
| const 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 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 |