extension_api_unittest.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/common/extensions/api/extension_api.h" 6 7#include <string> 8#include <vector> 9 10#include "base/file_path.h" 11#include "base/file_util.h" 12#include "base/json/json_writer.h" 13#include "base/memory/ref_counted.h" 14#include "base/memory/scoped_ptr.h" 15#include "base/path_service.h" 16#include "base/values.h" 17#include "chrome/common/chrome_paths.h" 18#include "chrome/common/extensions/extension.h" 19#include "testing/gtest/include/gtest/gtest.h" 20 21namespace extensions { 22namespace { 23 24class TestFeatureProvider : public FeatureProvider { 25 public: 26 explicit TestFeatureProvider(Feature::Context context) 27 : context_(context) { 28 } 29 30 virtual Feature* GetFeature(const std::string& name) OVERRIDE { 31 Feature* result = new Feature(); 32 result->set_name(name); 33 result->extension_types()->insert(Extension::TYPE_EXTENSION); 34 result->contexts()->insert(context_); 35 to_destroy_.push_back(make_linked_ptr(result)); 36 return result; 37 } 38 39 private: 40 std::vector<linked_ptr<Feature> > to_destroy_; 41 Feature::Context context_; 42}; 43 44TEST(ExtensionAPI, Creation) { 45 ExtensionAPI* shared_instance = ExtensionAPI::GetSharedInstance(); 46 EXPECT_EQ(shared_instance, ExtensionAPI::GetSharedInstance()); 47 48 scoped_ptr<ExtensionAPI> new_instance( 49 ExtensionAPI::CreateWithDefaultConfiguration()); 50 EXPECT_NE(new_instance.get(), 51 scoped_ptr<ExtensionAPI>( 52 ExtensionAPI::CreateWithDefaultConfiguration()).get()); 53 54 ExtensionAPI empty_instance; 55 56 struct { 57 ExtensionAPI* api; 58 bool expect_populated; 59 } test_data[] = { 60 { shared_instance, true }, 61 { new_instance.get(), true }, 62 { &empty_instance, false } 63 }; 64 65 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { 66 EXPECT_EQ(test_data[i].expect_populated, 67 test_data[i].api->GetSchema("bookmarks.create") != NULL); 68 } 69} 70 71TEST(ExtensionAPI, SplitDependencyName) { 72 struct { 73 std::string input; 74 std::string expected_feature_type; 75 std::string expected_feature_name; 76 } test_data[] = { 77 { "", "api", "" }, // assumes "api" when no type is present 78 { "foo", "api", "foo" }, 79 { "foo:", "foo", "" }, 80 { ":foo", "", "foo" }, 81 { "foo:bar", "foo", "bar" }, 82 { "foo:bar.baz", "foo", "bar.baz" } 83 }; 84 85 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { 86 std::string feature_type; 87 std::string feature_name; 88 ExtensionAPI::SplitDependencyName(test_data[i].input, &feature_type, 89 &feature_name); 90 EXPECT_EQ(test_data[i].expected_feature_type, feature_type) << i; 91 EXPECT_EQ(test_data[i].expected_feature_name, feature_name) << i; 92 } 93} 94 95TEST(ExtensionAPI, IsPrivileged) { 96 scoped_ptr<ExtensionAPI> extension_api( 97 ExtensionAPI::CreateWithDefaultConfiguration()); 98 99 EXPECT_FALSE(extension_api->IsPrivileged("extension.connect")); 100 EXPECT_FALSE(extension_api->IsPrivileged("extension.onConnect")); 101 102 // Properties are not supported yet. 103 EXPECT_TRUE(extension_api->IsPrivileged("extension.lastError")); 104 105 // Default unknown names to privileged for paranoia's sake. 106 EXPECT_TRUE(extension_api->IsPrivileged("")); 107 EXPECT_TRUE(extension_api->IsPrivileged("<unknown-namespace>")); 108 EXPECT_TRUE(extension_api->IsPrivileged("extension.<unknown-member>")); 109 110 // Exists, but privileged. 111 EXPECT_TRUE(extension_api->IsPrivileged("extension.getViews")); 112 EXPECT_TRUE(extension_api->IsPrivileged("history.search")); 113 114 // Whole APIs that are unprivileged. 115 EXPECT_FALSE(extension_api->IsPrivileged("app.getDetails")); 116 EXPECT_FALSE(extension_api->IsPrivileged("app.isInstalled")); 117 EXPECT_FALSE(extension_api->IsPrivileged("storage.local")); 118 EXPECT_FALSE(extension_api->IsPrivileged("storage.local.onChanged")); 119 EXPECT_FALSE(extension_api->IsPrivileged("storage.local.set")); 120 EXPECT_FALSE(extension_api->IsPrivileged("storage.local.MAX_ITEMS")); 121 EXPECT_FALSE(extension_api->IsPrivileged("storage.set")); 122} 123 124TEST(ExtensionAPI, IsPrivilegedFeatures) { 125 struct { 126 std::string filename; 127 std::string api_full_name; 128 bool expect_is_privilged; 129 Feature::Context test2_contexts; 130 } test_data[] = { 131 { "is_privileged_features_1.json", "test", false, 132 Feature::UNSPECIFIED_CONTEXT }, 133 { "is_privileged_features_2.json", "test", true, 134 Feature::UNSPECIFIED_CONTEXT }, 135 { "is_privileged_features_3.json", "test", false, 136 Feature::UNSPECIFIED_CONTEXT }, 137 { "is_privileged_features_4.json", "test.bar", false, 138 Feature::UNSPECIFIED_CONTEXT }, 139 { "is_privileged_features_5.json", "test.bar", true, 140 Feature::BLESSED_EXTENSION_CONTEXT }, 141 { "is_privileged_features_5.json", "test.bar", false, 142 Feature::UNBLESSED_EXTENSION_CONTEXT } 143 }; 144 145 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { 146 FilePath manifest_path; 147 PathService::Get(chrome::DIR_TEST_DATA, &manifest_path); 148 manifest_path = manifest_path.AppendASCII("extensions") 149 .AppendASCII("extension_api_unittest") 150 .AppendASCII(test_data[i].filename); 151 152 std::string manifest_str; 153 ASSERT_TRUE(file_util::ReadFileToString(manifest_path, &manifest_str)) 154 << test_data[i].filename; 155 156 ExtensionAPI api; 157 api.RegisterSchema("test", manifest_str); 158 159 TestFeatureProvider test2_provider(test_data[i].test2_contexts); 160 if (test_data[i].test2_contexts != Feature::UNSPECIFIED_CONTEXT) { 161 api.RegisterDependencyProvider("test2", &test2_provider); 162 } 163 164 api.LoadAllSchemas(); 165 EXPECT_EQ(test_data[i].expect_is_privilged, 166 api.IsPrivileged(test_data[i].api_full_name)) << i; 167 } 168} 169 170TEST(ExtensionAPI, LazyGetSchema) { 171 scoped_ptr<ExtensionAPI> apis(ExtensionAPI::CreateWithDefaultConfiguration()); 172 173 EXPECT_EQ(NULL, apis->GetSchema("")); 174 EXPECT_EQ(NULL, apis->GetSchema("")); 175 EXPECT_EQ(NULL, apis->GetSchema("experimental")); 176 EXPECT_EQ(NULL, apis->GetSchema("experimental")); 177 EXPECT_EQ(NULL, apis->GetSchema("foo")); 178 EXPECT_EQ(NULL, apis->GetSchema("foo")); 179 180 EXPECT_TRUE(apis->GetSchema("experimental.dns")); 181 EXPECT_TRUE(apis->GetSchema("experimental.dns")); 182 EXPECT_TRUE(apis->GetSchema("experimental.infobars")); 183 EXPECT_TRUE(apis->GetSchema("experimental.infobars")); 184 EXPECT_TRUE(apis->GetSchema("extension")); 185 EXPECT_TRUE(apis->GetSchema("extension")); 186 EXPECT_TRUE(apis->GetSchema("omnibox")); 187 EXPECT_TRUE(apis->GetSchema("omnibox")); 188 EXPECT_TRUE(apis->GetSchema("storage")); 189 EXPECT_TRUE(apis->GetSchema("storage")); 190} 191 192scoped_refptr<Extension> CreateExtensionWithPermissions( 193 const std::set<std::string>& permissions) { 194 DictionaryValue manifest; 195 manifest.SetString("name", "extension"); 196 manifest.SetString("version", "1.0"); 197 manifest.SetInteger("manifest_version", 2); 198 { 199 scoped_ptr<ListValue> permissions_list(new ListValue()); 200 for (std::set<std::string>::const_iterator i = permissions.begin(); 201 i != permissions.end(); ++i) { 202 permissions_list->Append(Value::CreateStringValue(*i)); 203 } 204 manifest.Set("permissions", permissions_list.release()); 205 } 206 207 std::string error; 208 scoped_refptr<Extension> extension(Extension::Create( 209 FilePath(), Extension::LOAD, manifest, Extension::NO_FLAGS, &error)); 210 CHECK(extension.get()); 211 CHECK(error.empty()); 212 213 return extension; 214} 215 216scoped_refptr<Extension> CreateExtensionWithPermission( 217 const std::string& permission) { 218 std::set<std::string> permissions; 219 permissions.insert(permission); 220 return CreateExtensionWithPermissions(permissions); 221} 222 223TEST(ExtensionAPI, ExtensionWithUnprivilegedAPIs) { 224 scoped_refptr<Extension> extension; 225 { 226 std::set<std::string> permissions; 227 permissions.insert("storage"); 228 permissions.insert("history"); 229 extension = CreateExtensionWithPermissions(permissions); 230 } 231 232 scoped_ptr<ExtensionAPI> extension_api( 233 ExtensionAPI::CreateWithDefaultConfiguration()); 234 235 scoped_ptr<std::set<std::string> > privileged_apis = 236 extension_api->GetAPIsForContext( 237 Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL()); 238 239 scoped_ptr<std::set<std::string> > unprivileged_apis = 240 extension_api->GetAPIsForContext( 241 Feature::UNBLESSED_EXTENSION_CONTEXT, extension.get(), GURL()); 242 243 scoped_ptr<std::set<std::string> > content_script_apis = 244 extension_api->GetAPIsForContext( 245 Feature::CONTENT_SCRIPT_CONTEXT, extension.get(), GURL()); 246 247 // "storage" is completely unprivileged. 248 EXPECT_EQ(1u, privileged_apis->count("storage")); 249 EXPECT_EQ(1u, unprivileged_apis->count("storage")); 250 EXPECT_EQ(1u, content_script_apis->count("storage")); 251 252 // "extension" is partially unprivileged. 253 EXPECT_EQ(1u, privileged_apis->count("extension")); 254 EXPECT_EQ(1u, unprivileged_apis->count("extension")); 255 EXPECT_EQ(1u, content_script_apis->count("extension")); 256 257 // "history" is entirely privileged. 258 EXPECT_EQ(1u, privileged_apis->count("history")); 259 EXPECT_EQ(0u, unprivileged_apis->count("history")); 260 EXPECT_EQ(0u, content_script_apis->count("history")); 261} 262 263TEST(ExtensionAPI, ExtensionWithDependencies) { 264 // Extension with the "ttsEngine" permission but not the "tts" permission; it 265 // must load TTS. 266 { 267 scoped_refptr<Extension> extension = 268 CreateExtensionWithPermission("ttsEngine"); 269 scoped_ptr<ExtensionAPI> api( 270 ExtensionAPI::CreateWithDefaultConfiguration()); 271 scoped_ptr<std::set<std::string> > apis = api->GetAPIsForContext( 272 Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL()); 273 EXPECT_EQ(1u, apis->count("ttsEngine")); 274 EXPECT_EQ(1u, apis->count("tts")); 275 } 276 277 // Conversely, extension with the "tts" permission but not the "ttsEngine" 278 // permission shouldn't get the "ttsEngine" permission. 279 { 280 scoped_refptr<Extension> extension = 281 CreateExtensionWithPermission("tts"); 282 scoped_ptr<ExtensionAPI> api( 283 ExtensionAPI::CreateWithDefaultConfiguration()); 284 scoped_ptr<std::set<std::string> > apis = api->GetAPIsForContext( 285 Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL()); 286 EXPECT_EQ(0u, apis->count("ttsEngine")); 287 EXPECT_EQ(1u, apis->count("tts")); 288 } 289} 290 291bool MatchesURL( 292 ExtensionAPI* api, const std::string& api_name, const std::string& url) { 293 scoped_ptr<std::set<std::string> > apis = 294 api->GetAPIsForContext(Feature::WEB_PAGE_CONTEXT, NULL, GURL(url)); 295 return apis->count(api_name); 296} 297 298TEST(ExtensionAPI, URLMatching) { 299 scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration()); 300 301 // "app" API is available to all URLs that content scripts can be injected. 302 EXPECT_TRUE(MatchesURL(api.get(), "app", "http://example.com/example.html")); 303 EXPECT_TRUE(MatchesURL(api.get(), "app", "https://blah.net")); 304 EXPECT_TRUE(MatchesURL(api.get(), "app", "file://somefile.html")); 305 306 // But not internal URLs (for chrome-extension:// the app API is injected by 307 // GetSchemasForExtension). 308 EXPECT_FALSE(MatchesURL(api.get(), "app", "about:flags")); 309 EXPECT_FALSE(MatchesURL(api.get(), "app", "chrome://flags")); 310 EXPECT_FALSE(MatchesURL(api.get(), "app", 311 "chrome-extension://fakeextension")); 312 313 // "storage" API (for example) isn't available to any URLs. 314 EXPECT_FALSE(MatchesURL(api.get(), "storage", 315 "http://example.com/example.html")); 316 EXPECT_FALSE(MatchesURL(api.get(), "storage", "https://blah.net")); 317 EXPECT_FALSE(MatchesURL(api.get(), "storage", "file://somefile.html")); 318 EXPECT_FALSE(MatchesURL(api.get(), "storage", "about:flags")); 319 EXPECT_FALSE(MatchesURL(api.get(), "storage", "chrome://flags")); 320 EXPECT_FALSE(MatchesURL(api.get(), "storage", 321 "chrome-extension://fakeextension")); 322} 323 324TEST(ExtensionAPI, GetAPINameFromFullName) { 325 struct { 326 std::string input; 327 std::string api_name; 328 std::string child_name; 329 } test_data[] = { 330 { "", "", "" }, 331 { "unknown", "", "" }, 332 { "bookmarks", "bookmarks", "" }, 333 { "bookmarks.", "bookmarks", "" }, 334 { ".bookmarks", "", "" }, 335 { "bookmarks.create", "bookmarks", "create" }, 336 { "bookmarks.create.", "bookmarks", "create." }, 337 { "bookmarks.create.monkey", "bookmarks", "create.monkey" }, 338 { "bookmarkManagerPrivate", "bookmarkManagerPrivate", "" }, 339 { "bookmarkManagerPrivate.copy", "bookmarkManagerPrivate", "copy" } 340 }; 341 342 scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration()); 343 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { 344 std::string child_name; 345 std::string api_name = api->GetAPINameFromFullName(test_data[i].input, 346 &child_name); 347 EXPECT_EQ(test_data[i].api_name, api_name) << test_data[i].input; 348 EXPECT_EQ(test_data[i].child_name, child_name) << test_data[i].input; 349 } 350} 351 352TEST(ExtensionAPI, DefaultConfigurationFeatures) { 353 scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration()); 354 355 Feature* bookmarks = api->GetFeature("bookmarks"); 356 Feature* bookmarks_create = api->GetFeature("bookmarks.create"); 357 358 struct { 359 Feature* feature; 360 // TODO(aa): More stuff to test over time. 361 } test_data[] = { 362 { bookmarks }, 363 { bookmarks_create } 364 }; 365 366 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { 367 Feature* feature = test_data[i].feature; 368 ASSERT_TRUE(feature) << i; 369 370 EXPECT_TRUE(feature->whitelist()->empty()); 371 EXPECT_TRUE(feature->extension_types()->empty()); 372 373 EXPECT_EQ(1u, feature->contexts()->size()); 374 EXPECT_TRUE(feature->contexts()->count( 375 Feature::BLESSED_EXTENSION_CONTEXT)); 376 377 EXPECT_EQ(Feature::UNSPECIFIED_LOCATION, feature->location()); 378 EXPECT_EQ(Feature::UNSPECIFIED_PLATFORM, feature->platform()); 379 EXPECT_EQ(0, feature->min_manifest_version()); 380 EXPECT_EQ(0, feature->max_manifest_version()); 381 } 382} 383 384TEST(ExtensionAPI, FeaturesRequireContexts) { 385 scoped_ptr<ListValue> schema1(new ListValue()); 386 DictionaryValue* feature_definition = new DictionaryValue(); 387 schema1->Append(feature_definition); 388 feature_definition->SetString("namespace", "test"); 389 feature_definition->SetBoolean("uses_feature_system", true); 390 391 scoped_ptr<ListValue> schema2(schema1->DeepCopy()); 392 393 ListValue* contexts = new ListValue(); 394 contexts->Append(Value::CreateStringValue("content_script")); 395 feature_definition->Set("contexts", contexts); 396 397 struct { 398 ListValue* schema; 399 bool expect_success; 400 } test_data[] = { 401 { schema1.get(), true }, 402 { schema2.get(), false } 403 }; 404 405 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { 406 std::string schema_source; 407 base::JSONWriter::Write(test_data[i].schema, &schema_source); 408 409 ExtensionAPI api; 410 api.RegisterSchema("test", base::StringPiece(schema_source)); 411 api.LoadAllSchemas(); 412 413 Feature* feature = api.GetFeature("test"); 414 EXPECT_EQ(test_data[i].expect_success, feature != NULL) << i; 415 } 416} 417 418static void GetDictionaryFromList(const DictionaryValue* schema, 419 const std::string& list_name, 420 const int list_index, 421 const DictionaryValue** out) { 422 const ListValue* list; 423 EXPECT_TRUE(schema->GetList(list_name, &list)); 424 EXPECT_TRUE(list->GetDictionary(list_index, out)); 425} 426 427TEST(ExtensionAPI, TypesHaveNamespace) { 428 FilePath manifest_path; 429 PathService::Get(chrome::DIR_TEST_DATA, &manifest_path); 430 manifest_path = manifest_path.AppendASCII("extensions") 431 .AppendASCII("extension_api_unittest") 432 .AppendASCII("types_have_namespace.json"); 433 434 std::string manifest_str; 435 ASSERT_TRUE(file_util::ReadFileToString(manifest_path, &manifest_str)) 436 << "Failed to load: " << manifest_path.value(); 437 438 ExtensionAPI api; 439 api.RegisterSchema("test.foo", manifest_str); 440 api.LoadAllSchemas(); 441 442 const DictionaryValue* schema = api.GetSchema("test.foo"); 443 444 const DictionaryValue* dict; 445 const DictionaryValue* sub_dict; 446 std::string type; 447 448 GetDictionaryFromList(schema, "types", 0, &dict); 449 EXPECT_TRUE(dict->GetString("id", &type)); 450 EXPECT_EQ("test.foo.TestType", type); 451 EXPECT_TRUE(dict->GetString("customBindings", &type)); 452 EXPECT_EQ("test.foo.TestType", type); 453 EXPECT_TRUE(dict->GetDictionary("properties", &sub_dict)); 454 const DictionaryValue* property; 455 EXPECT_TRUE(sub_dict->GetDictionary("foo", &property)); 456 EXPECT_TRUE(property->GetString("$ref", &type)); 457 EXPECT_EQ("test.foo.OtherType", type); 458 EXPECT_TRUE(sub_dict->GetDictionary("bar", &property)); 459 EXPECT_TRUE(property->GetString("$ref", &type)); 460 EXPECT_EQ("fully.qualified.Type", type); 461 462 GetDictionaryFromList(schema, "functions", 0, &dict); 463 GetDictionaryFromList(dict, "parameters", 0, &sub_dict); 464 EXPECT_TRUE(sub_dict->GetString("$ref", &type)); 465 EXPECT_EQ("test.foo.TestType", type); 466 EXPECT_TRUE(dict->GetDictionary("returns", &sub_dict)); 467 EXPECT_TRUE(sub_dict->GetString("$ref", &type)); 468 EXPECT_EQ("fully.qualified.Type", type); 469 470 GetDictionaryFromList(schema, "functions", 1, &dict); 471 GetDictionaryFromList(dict, "parameters", 0, &sub_dict); 472 EXPECT_TRUE(sub_dict->GetString("$ref", &type)); 473 EXPECT_EQ("fully.qualified.Type", type); 474 EXPECT_TRUE(dict->GetDictionary("returns", &sub_dict)); 475 EXPECT_TRUE(sub_dict->GetString("$ref", &type)); 476 EXPECT_EQ("test.foo.TestType", type); 477 478 GetDictionaryFromList(schema, "events", 0, &dict); 479 GetDictionaryFromList(dict, "parameters", 0, &sub_dict); 480 EXPECT_TRUE(sub_dict->GetString("$ref", &type)); 481 EXPECT_EQ("test.foo.TestType", type); 482 GetDictionaryFromList(dict, "parameters", 1, &sub_dict); 483 EXPECT_TRUE(sub_dict->GetString("$ref", &type)); 484 EXPECT_EQ("fully.qualified.Type", type); 485} 486 487} // namespace 488} // namespace extensions 489