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