extension_manifests_unittest.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
1// Copyright (c) 2010 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 "base/command_line.h"
6#include "base/file_path.h"
7#include "base/file_util.h"
8#include "base/path_service.h"
9#include "base/scoped_ptr.h"
10#include "base/string_util.h"
11#include "base/utf_string_conversions.h"
12#include "chrome/common/chrome_paths.h"
13#include "chrome/common/chrome_switches.h"
14#include "chrome/common/extensions/extension.h"
15#include "chrome/common/extensions/extension_constants.h"
16#include "chrome/common/extensions/extension_error_utils.h"
17#include "chrome/common/extensions/extension_sidebar_defaults.h"
18#include "chrome/common/json_value_serializer.h"
19#include "testing/gtest/include/gtest/gtest.h"
20
21namespace errors = extension_manifest_errors;
22namespace keys = extension_manifest_keys;
23
24class ExtensionManifestTest : public testing::Test {
25 public:
26  ExtensionManifestTest() : enable_apps_(true) {}
27
28 protected:
29  DictionaryValue* LoadManifestFile(const std::string& filename,
30                                    std::string* error) {
31    FilePath path;
32    PathService::Get(chrome::DIR_TEST_DATA, &path);
33    path = path.AppendASCII("extensions")
34        .AppendASCII("manifest_tests")
35        .AppendASCII(filename.c_str());
36    EXPECT_TRUE(file_util::PathExists(path));
37
38    JSONFileValueSerializer serializer(path);
39    return static_cast<DictionaryValue*>(serializer.Deserialize(NULL, error));
40  }
41
42  scoped_refptr<Extension> LoadExtensionWithLocation(
43      DictionaryValue* value,
44      Extension::Location location,
45      std::string* error) {
46    FilePath path;
47    PathService::Get(chrome::DIR_TEST_DATA, &path);
48    path = path.AppendASCII("extensions").AppendASCII("manifest_tests");
49    return Extension::Create(path.DirName(), location, *value, false, error);
50  }
51
52  scoped_refptr<Extension> LoadExtension(const std::string& name,
53                                         std::string* error) {
54    return LoadExtensionWithLocation(name, Extension::INTERNAL, error);
55  }
56
57  scoped_refptr<Extension> LoadExtension(DictionaryValue* value,
58                                         std::string* error) {
59    return LoadExtensionWithLocation(value, Extension::INTERNAL, error);
60  }
61
62  scoped_refptr<Extension> LoadExtensionWithLocation(
63      const std::string& name,
64      Extension::Location location,
65      std::string* error) {
66    scoped_ptr<DictionaryValue> value(LoadManifestFile(name, error));
67    if (!value.get())
68      return NULL;
69    return LoadExtensionWithLocation(value.get(), location, error);
70  }
71
72  scoped_refptr<Extension> LoadAndExpectSuccess(const std::string& name) {
73    std::string error;
74    scoped_refptr<Extension> extension = LoadExtension(name, &error);
75    EXPECT_TRUE(extension) << name;
76    EXPECT_EQ("", error) << name;
77    return extension;
78  }
79
80  scoped_refptr<Extension> LoadAndExpectSuccess(DictionaryValue* manifest,
81                                                const std::string& name) {
82    std::string error;
83    scoped_refptr<Extension> extension = LoadExtension(manifest, &error);
84    EXPECT_TRUE(extension) << "Unexpected success for " << name;
85    EXPECT_EQ("", error) << "Unexpected no error for " << name;
86    return extension;
87  }
88
89  void VerifyExpectedError(Extension* extension,
90                           const std::string& name,
91                           const std::string& error,
92                           const std::string& expected_error) {
93    EXPECT_FALSE(extension) <<
94        "Expected failure loading extension '" << name <<
95        "', but didn't get one.";
96    EXPECT_TRUE(MatchPattern(error, expected_error)) << name <<
97        " expected '" << expected_error << "' but got '" << error << "'";
98  }
99
100  void LoadAndExpectError(const std::string& name,
101                          const std::string& expected_error) {
102    std::string error;
103    scoped_refptr<Extension> extension(LoadExtension(name, &error));
104    VerifyExpectedError(extension.get(), name, error, expected_error);
105  }
106
107  void LoadAndExpectError(DictionaryValue* manifest,
108                          const std::string& name,
109                          const std::string& expected_error) {
110    std::string error;
111    scoped_refptr<Extension> extension(LoadExtension(manifest, &error));
112    VerifyExpectedError(extension.get(), name, error, expected_error);
113  }
114
115  bool enable_apps_;
116};
117
118TEST_F(ExtensionManifestTest, ValidApp) {
119  scoped_refptr<Extension> extension(LoadAndExpectSuccess("valid_app.json"));
120  ASSERT_EQ(2u, extension->web_extent().patterns().size());
121  EXPECT_EQ("http://www.google.com/mail/*",
122            extension->web_extent().patterns()[0].GetAsString());
123  EXPECT_EQ("http://www.google.com/foobar/*",
124            extension->web_extent().patterns()[1].GetAsString());
125  EXPECT_EQ(extension_misc::LAUNCH_TAB, extension->launch_container());
126  EXPECT_EQ("http://www.google.com/mail/", extension->launch_web_url());
127}
128
129TEST_F(ExtensionManifestTest, AppWebUrls) {
130  LoadAndExpectError("web_urls_wrong_type.json",
131                     errors::kInvalidWebURLs);
132  LoadAndExpectError("web_urls_invalid_1.json",
133                     ExtensionErrorUtils::FormatErrorMessage(
134                         errors::kInvalidWebURL, "0"));
135  LoadAndExpectError("web_urls_invalid_2.json",
136                     ExtensionErrorUtils::FormatErrorMessage(
137                         errors::kInvalidWebURL, "0"));
138  LoadAndExpectError("web_urls_invalid_3.json",
139                     ExtensionErrorUtils::FormatErrorMessage(
140                         errors::kInvalidWebURL, "0"));
141  LoadAndExpectError("web_urls_invalid_4.json",
142                     ExtensionErrorUtils::FormatErrorMessage(
143                         errors::kInvalidWebURL, "0"));
144
145  scoped_refptr<Extension> extension(
146      LoadAndExpectSuccess("web_urls_default.json"));
147  ASSERT_EQ(1u, extension->web_extent().patterns().size());
148  EXPECT_EQ("*://www.google.com/*",
149            extension->web_extent().patterns()[0].GetAsString());
150}
151
152TEST_F(ExtensionManifestTest, AppLaunchContainer) {
153  scoped_refptr<Extension> extension;
154
155  extension = LoadAndExpectSuccess("launch_tab.json");
156  EXPECT_EQ(extension_misc::LAUNCH_TAB, extension->launch_container());
157
158  extension = LoadAndExpectSuccess("launch_panel.json");
159  EXPECT_EQ(extension_misc::LAUNCH_PANEL, extension->launch_container());
160
161  extension = LoadAndExpectSuccess("launch_default.json");
162  EXPECT_EQ(extension_misc::LAUNCH_TAB, extension->launch_container());
163
164  extension = LoadAndExpectSuccess("launch_width.json");
165  EXPECT_EQ(640, extension->launch_width());
166
167  extension = LoadAndExpectSuccess("launch_height.json");
168  EXPECT_EQ(480, extension->launch_height());
169
170  LoadAndExpectError("launch_window.json",
171                     errors::kInvalidLaunchContainer);
172  LoadAndExpectError("launch_container_invalid_type.json",
173                     errors::kInvalidLaunchContainer);
174  LoadAndExpectError("launch_container_invalid_value.json",
175                     errors::kInvalidLaunchContainer);
176  LoadAndExpectError("launch_container_without_launch_url.json",
177                     errors::kLaunchURLRequired);
178  LoadAndExpectError("launch_width_invalid.json",
179                     errors::kInvalidLaunchWidthContainer);
180  LoadAndExpectError("launch_width_negative.json",
181                     errors::kInvalidLaunchWidth);
182  LoadAndExpectError("launch_height_invalid.json",
183                     errors::kInvalidLaunchHeightContainer);
184  LoadAndExpectError("launch_height_negative.json",
185                     errors::kInvalidLaunchHeight);
186}
187
188TEST_F(ExtensionManifestTest, AppLaunchURL) {
189  LoadAndExpectError("launch_path_and_url.json",
190                     errors::kLaunchPathAndURLAreExclusive);
191  LoadAndExpectError("launch_path_invalid_type.json",
192                     errors::kInvalidLaunchLocalPath);
193  LoadAndExpectError("launch_path_invalid_value.json",
194                     errors::kInvalidLaunchLocalPath);
195  LoadAndExpectError("launch_url_invalid_type.json",
196                     errors::kInvalidLaunchWebURL);
197
198  scoped_refptr<Extension> extension;
199  extension = LoadAndExpectSuccess("launch_local_path.json");
200  EXPECT_EQ(extension->url().spec() + "launch.html",
201            extension->GetFullLaunchURL().spec());
202
203  LoadAndExpectError("launch_web_url_relative.json",
204                     errors::kInvalidLaunchWebURL);
205
206  extension = LoadAndExpectSuccess("launch_web_url_absolute.json");
207  EXPECT_EQ(GURL("http://www.google.com/launch.html"),
208            extension->GetFullLaunchURL());
209}
210
211TEST_F(ExtensionManifestTest, Override) {
212  LoadAndExpectError("override_newtab_and_history.json",
213                     errors::kMultipleOverrides);
214  LoadAndExpectError("override_invalid_page.json",
215                     errors::kInvalidChromeURLOverrides);
216
217  scoped_refptr<Extension> extension;
218
219  extension = LoadAndExpectSuccess("override_new_tab.json");
220  EXPECT_EQ(extension->url().spec() + "newtab.html",
221            extension->GetChromeURLOverrides().find("newtab")->second.spec());
222
223  extension = LoadAndExpectSuccess("override_history.json");
224  EXPECT_EQ(extension->url().spec() + "history.html",
225            extension->GetChromeURLOverrides().find("history")->second.spec());
226}
227
228TEST_F(ExtensionManifestTest, ChromeURLPermissionInvalid) {
229  LoadAndExpectError("permission_chrome_url_invalid.json",
230      errors::kInvalidPermissionScheme);
231}
232
233TEST_F(ExtensionManifestTest, ChromeResourcesPermissionValidOnlyForComponents) {
234  LoadAndExpectError("permission_chrome_resources_url.json",
235      errors::kInvalidPermissionScheme);
236  std::string error;
237  scoped_refptr<Extension> extension;
238  extension = LoadExtensionWithLocation(
239      "permission_chrome_resources_url.json",
240      Extension::COMPONENT,
241      &error);
242  EXPECT_EQ("", error);
243}
244
245TEST_F(ExtensionManifestTest, ChromeURLContentScriptInvalid) {
246  LoadAndExpectError("content_script_chrome_url_invalid.json",
247      errors::kInvalidMatch);
248}
249
250TEST_F(ExtensionManifestTest, ExperimentalPermission) {
251  LoadAndExpectError("experimental.json", errors::kExperimentalFlagRequired);
252  CommandLine old_command_line = *CommandLine::ForCurrentProcess();
253  CommandLine::ForCurrentProcess()->AppendSwitch(
254      switches::kEnableExperimentalExtensionApis);
255  LoadAndExpectSuccess("experimental.json");
256  *CommandLine::ForCurrentProcess() = old_command_line;
257}
258
259TEST_F(ExtensionManifestTest, DevToolsExtensions) {
260  LoadAndExpectError("devtools_extension_no_permissions.json",
261      errors::kDevToolsExperimental);
262  LoadAndExpectError("devtools_extension_url_invalid_type.json",
263      errors::kInvalidDevToolsPage);
264
265  CommandLine old_command_line = *CommandLine::ForCurrentProcess();
266  CommandLine::ForCurrentProcess()->AppendSwitch(
267      switches::kEnableExperimentalExtensionApis);
268
269  scoped_refptr<Extension> extension;
270  extension = LoadAndExpectSuccess("devtools_extension.json");
271  EXPECT_EQ(extension->url().spec() + "devtools.html",
272            extension->devtools_url().spec());
273  *CommandLine::ForCurrentProcess() = old_command_line;
274}
275
276TEST_F(ExtensionManifestTest, Sidebar) {
277  LoadAndExpectError("sidebar.json",
278      errors::kExperimentalFlagRequired);
279
280  CommandLine old_command_line = *CommandLine::ForCurrentProcess();
281  CommandLine::ForCurrentProcess()->AppendSwitch(
282      switches::kEnableExperimentalExtensionApis);
283
284  LoadAndExpectError("sidebar_no_permissions.json",
285      errors::kSidebarExperimental);
286
287  LoadAndExpectError("sidebar_icon_empty.json",
288      errors::kInvalidSidebarDefaultIconPath);
289  LoadAndExpectError("sidebar_icon_invalid_type.json",
290      errors::kInvalidSidebarDefaultIconPath);
291  LoadAndExpectError("sidebar_page_empty.json",
292      errors::kInvalidSidebarDefaultPage);
293  LoadAndExpectError("sidebar_page_invalid_type.json",
294      errors::kInvalidSidebarDefaultPage);
295  LoadAndExpectError("sidebar_title_invalid_type.json",
296      errors::kInvalidSidebarDefaultTitle);
297
298  scoped_refptr<Extension> extension(LoadAndExpectSuccess("sidebar.json"));
299  ASSERT_TRUE(extension->sidebar_defaults() != NULL);
300  EXPECT_EQ(extension->sidebar_defaults()->default_title(),
301            ASCIIToUTF16("Default title"));
302  EXPECT_EQ(extension->sidebar_defaults()->default_icon_path(),
303            "icon.png");
304  EXPECT_EQ(extension->url().spec() + "sidebar.html",
305            extension->sidebar_defaults()->default_page().spec());
306
307  *CommandLine::ForCurrentProcess() = old_command_line;
308}
309
310TEST_F(ExtensionManifestTest, DisallowHybridApps) {
311  LoadAndExpectError("disallow_hybrid_1.json",
312                     errors::kHostedAppsCannotIncludeExtensionFeatures);
313  LoadAndExpectError("disallow_hybrid_2.json",
314                     errors::kHostedAppsCannotIncludeExtensionFeatures);
315}
316
317TEST_F(ExtensionManifestTest, OptionsPageInApps) {
318  scoped_refptr<Extension> extension;
319
320  // Allow options page with absolute URL in hosed apps.
321  extension = LoadAndExpectSuccess("hosted_app_absolute_options.json");
322  EXPECT_STREQ("http",
323               extension->options_url().scheme().c_str());
324  EXPECT_STREQ("example.com",
325               extension->options_url().host().c_str());
326  EXPECT_STREQ("options.html",
327               extension->options_url().ExtractFileName().c_str());
328
329  // Forbid options page with relative URL in hosted apps.
330  LoadAndExpectError("hosted_app_relative_options.json",
331                     errors::kInvalidOptionsPageInHostedApp);
332
333  // Forbid options page with non-(http|https) scheme in hosted app.
334  LoadAndExpectError("hosted_app_file_options.json",
335                     errors::kInvalidOptionsPageInHostedApp);
336
337  // Forbid absolute URL for options page in packaged apps.
338  LoadAndExpectError("packaged_app_absolute_options.json",
339                     errors::kInvalidOptionsPageExpectUrlInPackage);
340}
341
342TEST_F(ExtensionManifestTest, AllowUnrecognizedPermissions) {
343  std::string error;
344  scoped_ptr<DictionaryValue> manifest(
345      LoadManifestFile("valid_app.json", &error));
346  ASSERT_TRUE(manifest.get());
347
348  ListValue *permissions = new ListValue();
349  manifest->Set(keys::kPermissions, permissions);
350  for (size_t i = 0; i < Extension::kNumPermissions; i++) {
351    const char* name = Extension::kPermissions[i].name;
352    StringValue* p = new StringValue(name);
353    permissions->Clear();
354    permissions->Append(p);
355    std::string message_name = base::StringPrintf("permission-%s", name);
356
357    // Extensions are allowed to contain unrecognized API permissions,
358    // so there shouldn't be any errors.
359    scoped_refptr<Extension> extension;
360    extension = LoadAndExpectSuccess(manifest.get(), message_name);
361  }
362}
363
364TEST_F(ExtensionManifestTest, NormalizeIconPaths) {
365  scoped_refptr<Extension> extension(
366      LoadAndExpectSuccess("normalize_icon_paths.json"));
367  EXPECT_EQ("16.png",
368            extension->icons().Get(16, ExtensionIconSet::MATCH_EXACTLY));
369  EXPECT_EQ("48.png",
370            extension->icons().Get(48, ExtensionIconSet::MATCH_EXACTLY));
371}
372
373TEST_F(ExtensionManifestTest, DisallowMultipleUISurfaces) {
374  LoadAndExpectError("multiple_ui_surfaces_1.json", errors::kOneUISurfaceOnly);
375  LoadAndExpectError("multiple_ui_surfaces_2.json", errors::kOneUISurfaceOnly);
376  LoadAndExpectError("multiple_ui_surfaces_3.json", errors::kOneUISurfaceOnly);
377}
378
379TEST_F(ExtensionManifestTest, ParseHomepageURLs) {
380  scoped_refptr<Extension> extension(
381      LoadAndExpectSuccess("homepage_valid.json"));
382  LoadAndExpectError("homepage_empty.json",
383                     extension_manifest_errors::kInvalidHomepageURL);
384  LoadAndExpectError("homepage_invalid.json",
385                     extension_manifest_errors::kInvalidHomepageURL);
386}
387
388TEST_F(ExtensionManifestTest, GetHomepageURL) {
389  scoped_refptr<Extension> extension(
390      LoadAndExpectSuccess("homepage_valid.json"));
391  EXPECT_EQ(GURL("http://foo.com#bar"), extension->GetHomepageURL());
392
393  // The Google Gallery URL ends with the id, which depends on the path, which
394  // can be different in testing, so we just check the part before id.
395  extension = LoadAndExpectSuccess("homepage_google_hosted.json");
396  EXPECT_TRUE(StartsWithASCII(extension->GetHomepageURL().spec(),
397                              "https://chrome.google.com/webstore/detail/",
398                              false));
399
400  extension = LoadAndExpectSuccess("homepage_externally_hosted.json");
401  EXPECT_EQ(GURL(), extension->GetHomepageURL());
402}
403
404TEST_F(ExtensionManifestTest, DefaultPathForExtent) {
405  scoped_refptr<Extension> extension(
406      LoadAndExpectSuccess("default_path_for_extent.json"));
407
408  ASSERT_EQ(1u, extension->web_extent().patterns().size());
409  EXPECT_EQ("/*", extension->web_extent().patterns()[0].path());
410  EXPECT_TRUE(extension->web_extent().ContainsURL(
411      GURL("http://www.google.com/monkey")));
412}
413
414TEST_F(ExtensionManifestTest, DefaultLocale) {
415  LoadAndExpectError("default_locale_invalid.json",
416                     extension_manifest_errors::kInvalidDefaultLocale);
417
418  scoped_refptr<Extension> extension(
419      LoadAndExpectSuccess("default_locale_valid.json"));
420  EXPECT_EQ("de-AT", extension->default_locale());
421}
422
423TEST_F(ExtensionManifestTest, TtsProvider) {
424  LoadAndExpectError("tts_provider_invalid_1.json",
425                     extension_manifest_errors::kInvalidTts);
426  LoadAndExpectError("tts_provider_invalid_2.json",
427                     extension_manifest_errors::kInvalidTtsVoices);
428  LoadAndExpectError("tts_provider_invalid_3.json",
429                     extension_manifest_errors::kInvalidTtsVoices);
430  LoadAndExpectError("tts_provider_invalid_4.json",
431                     extension_manifest_errors::kInvalidTtsVoicesVoiceName);
432  LoadAndExpectError("tts_provider_invalid_5.json",
433                     extension_manifest_errors::kInvalidTtsVoicesLocale);
434  LoadAndExpectError("tts_provider_invalid_6.json",
435                     extension_manifest_errors::kInvalidTtsVoicesLocale);
436  LoadAndExpectError("tts_provider_invalid_7.json",
437                     extension_manifest_errors::kInvalidTtsVoicesGender);
438
439  scoped_refptr<Extension> extension(
440      LoadAndExpectSuccess("tts_provider_valid.json"));
441
442  ASSERT_EQ(1u, extension->tts_voices().size());
443  EXPECT_EQ("name", extension->tts_voices()[0].voice_name);
444  EXPECT_EQ("en-US", extension->tts_voices()[0].locale);
445  EXPECT_EQ("female", extension->tts_voices()[0].gender);
446}
447