extension_manifests_unittest.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
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 "chrome/common/chrome_paths.h"
12#include "chrome/common/chrome_switches.h"
13#include "chrome/common/extensions/extension.h"
14#include "chrome/common/extensions/extension_constants.h"
15#include "chrome/common/extensions/extension_error_utils.h"
16#include "chrome/common/json_value_serializer.h"
17#include "testing/gtest/include/gtest/gtest.h"
18
19namespace errors = extension_manifest_errors;
20namespace keys = extension_manifest_keys;
21
22class ExtensionManifestTest : public testing::Test {
23 public:
24  ExtensionManifestTest() : enable_apps_(true) {}
25
26 protected:
27  DictionaryValue* LoadManifestFile(const std::string& filename,
28                                    std::string* error) {
29    FilePath path;
30    PathService::Get(chrome::DIR_TEST_DATA, &path);
31    path = path.AppendASCII("extensions")
32        .AppendASCII("manifest_tests")
33        .AppendASCII(filename.c_str());
34    EXPECT_TRUE(file_util::PathExists(path));
35
36    JSONFileValueSerializer serializer(path);
37    return static_cast<DictionaryValue*>(serializer.Deserialize(NULL, error));
38  }
39
40  Extension* LoadExtensionWithLocation(DictionaryValue* value,
41                                       Extension::Location location,
42                                       std::string* error) {
43    FilePath path;
44    PathService::Get(chrome::DIR_TEST_DATA, &path);
45    path = path.AppendASCII("extensions").AppendASCII("manifest_tests");
46
47    scoped_ptr<Extension> extension(new Extension(path.DirName()));
48    extension->set_location(location);
49
50    if (!extension->InitFromValue(*value, false, error))
51      return NULL;
52
53    return extension.release();
54  }
55
56  Extension* LoadExtension(const std::string& name,
57                           std::string* error) {
58    return LoadExtensionWithLocation(name, Extension::INTERNAL, error);
59  }
60
61  Extension* LoadExtension(DictionaryValue* value,
62                           std::string* error) {
63    return LoadExtensionWithLocation(value, Extension::INTERNAL, error);
64  }
65
66  Extension* LoadExtensionWithLocation(const std::string& name,
67                                       Extension::Location location,
68                                       std::string* error) {
69    scoped_ptr<DictionaryValue> value(LoadManifestFile(name, error));
70    if (!value.get())
71      return NULL;
72    return LoadExtensionWithLocation(value.get(), location, error);
73  }
74
75  Extension* LoadAndExpectSuccess(const std::string& name) {
76    std::string error;
77    Extension* extension = LoadExtension(name, &error);
78    EXPECT_TRUE(extension) << name;
79    EXPECT_EQ("", error) << name;
80    return extension;
81  }
82
83  Extension* LoadAndExpectSuccess(DictionaryValue* manifest,
84                                  const std::string& name) {
85    std::string error;
86    Extension* extension = LoadExtension(manifest, &error);
87    EXPECT_TRUE(extension) << "Unexpected success for " << name;
88    EXPECT_EQ("", error) << "Unexpected no error for " << name;
89    return extension;
90  }
91
92  void VerifyExpectedError(Extension* extension,
93                           const std::string& name,
94                           const std::string& error,
95                           const std::string& expected_error) {
96    EXPECT_FALSE(extension) <<
97        "Expected failure loading extension '" << name <<
98        "', but didn't get one.";
99    EXPECT_TRUE(MatchPattern(error, expected_error)) << name <<
100        " expected '" << expected_error << "' but got '" << error << "'";
101  }
102
103  void LoadAndExpectError(const std::string& name,
104                          const std::string& expected_error) {
105    std::string error;
106    scoped_ptr<Extension> extension(LoadExtension(name, &error));
107    VerifyExpectedError(extension.get(), name, error, expected_error);
108  }
109
110  void LoadAndExpectError(DictionaryValue* manifest,
111                          const std::string& name,
112                          const std::string& expected_error) {
113    std::string error;
114    scoped_ptr<Extension> extension(LoadExtension(manifest, &error));
115    VerifyExpectedError(extension.get(), name, error, expected_error);
116  }
117
118  bool enable_apps_;
119};
120
121TEST_F(ExtensionManifestTest, ValidApp) {
122  scoped_ptr<Extension> extension(LoadAndExpectSuccess("valid_app.json"));
123  ASSERT_EQ(2u, extension->web_extent().patterns().size());
124  EXPECT_EQ("http://www.google.com/mail/*",
125            extension->web_extent().patterns()[0].GetAsString());
126  EXPECT_EQ("http://www.google.com/foobar/*",
127            extension->web_extent().patterns()[1].GetAsString());
128  EXPECT_EQ(extension_misc::LAUNCH_TAB, extension->launch_container());
129  EXPECT_EQ("http://www.google.com/mail/", extension->launch_web_url());
130}
131
132TEST_F(ExtensionManifestTest, AppWebUrls) {
133  LoadAndExpectError("web_urls_wrong_type.json",
134                     errors::kInvalidWebURLs);
135  LoadAndExpectError("web_urls_invalid_1.json",
136                     ExtensionErrorUtils::FormatErrorMessage(
137                         errors::kInvalidWebURL, "0"));
138  LoadAndExpectError("web_urls_invalid_2.json",
139                     ExtensionErrorUtils::FormatErrorMessage(
140                         errors::kInvalidWebURL, "0"));
141  LoadAndExpectError("web_urls_invalid_3.json",
142                     ExtensionErrorUtils::FormatErrorMessage(
143                         errors::kInvalidWebURL, "0"));
144  LoadAndExpectError("web_urls_invalid_4.json",
145                     ExtensionErrorUtils::FormatErrorMessage(
146                         errors::kInvalidWebURL, "0"));
147
148  scoped_ptr<Extension> extension(
149      LoadAndExpectSuccess("web_urls_default.json"));
150  ASSERT_EQ(1u, extension->web_extent().patterns().size());
151  EXPECT_EQ("*://www.google.com/*",
152            extension->web_extent().patterns()[0].GetAsString());
153}
154
155TEST_F(ExtensionManifestTest, AppLaunchContainer) {
156  scoped_ptr<Extension> extension;
157
158  extension.reset(LoadAndExpectSuccess("launch_tab.json"));
159  EXPECT_EQ(extension_misc::LAUNCH_TAB, extension->launch_container());
160
161  extension.reset(LoadAndExpectSuccess("launch_panel.json"));
162  EXPECT_EQ(extension_misc::LAUNCH_PANEL, extension->launch_container());
163
164  extension.reset(LoadAndExpectSuccess("launch_default.json"));
165  EXPECT_EQ(extension_misc::LAUNCH_TAB, extension->launch_container());
166
167  extension.reset(LoadAndExpectSuccess("launch_width.json"));
168  EXPECT_EQ(640, extension->launch_width());
169
170  extension.reset(LoadAndExpectSuccess("launch_height.json"));
171  EXPECT_EQ(480, extension->launch_height());
172
173  LoadAndExpectError("launch_window.json",
174                     errors::kInvalidLaunchContainer);
175  LoadAndExpectError("launch_container_invalid_type.json",
176                     errors::kInvalidLaunchContainer);
177  LoadAndExpectError("launch_container_invalid_value.json",
178                     errors::kInvalidLaunchContainer);
179  LoadAndExpectError("launch_container_without_launch_url.json",
180                     errors::kLaunchURLRequired);
181  LoadAndExpectError("launch_width_invalid.json",
182                     errors::kInvalidLaunchWidthContainer);
183  LoadAndExpectError("launch_width_negative.json",
184                     errors::kInvalidLaunchWidth);
185  LoadAndExpectError("launch_height_invalid.json",
186                     errors::kInvalidLaunchHeightContainer);
187  LoadAndExpectError("launch_height_negative.json",
188                     errors::kInvalidLaunchHeight);
189}
190
191TEST_F(ExtensionManifestTest, AppLaunchURL) {
192  LoadAndExpectError("launch_path_and_url.json",
193                     errors::kLaunchPathAndURLAreExclusive);
194  LoadAndExpectError("launch_path_invalid_type.json",
195                     errors::kInvalidLaunchLocalPath);
196  LoadAndExpectError("launch_path_invalid_value.json",
197                     errors::kInvalidLaunchLocalPath);
198  LoadAndExpectError("launch_url_invalid_type.json",
199                     errors::kInvalidLaunchWebURL);
200
201  scoped_ptr<Extension> extension;
202  extension.reset(LoadAndExpectSuccess("launch_local_path.json"));
203  EXPECT_EQ(extension->url().spec() + "launch.html",
204            extension->GetFullLaunchURL().spec());
205
206  LoadAndExpectError("launch_web_url_relative.json",
207                     errors::kInvalidLaunchWebURL);
208
209  extension.reset(LoadAndExpectSuccess("launch_web_url_absolute.json"));
210  EXPECT_EQ(GURL("http://www.google.com/launch.html"),
211            extension->GetFullLaunchURL());
212}
213
214TEST_F(ExtensionManifestTest, Override) {
215  LoadAndExpectError("override_newtab_and_history.json",
216                     errors::kMultipleOverrides);
217  LoadAndExpectError("override_invalid_page.json",
218                     errors::kInvalidChromeURLOverrides);
219
220  scoped_ptr<Extension> extension;
221
222  extension.reset(LoadAndExpectSuccess("override_new_tab.json"));
223  EXPECT_EQ(extension->url().spec() + "newtab.html",
224            extension->GetChromeURLOverrides().find("newtab")->second.spec());
225
226  extension.reset(LoadAndExpectSuccess("override_history.json"));
227  EXPECT_EQ(extension->url().spec() + "history.html",
228            extension->GetChromeURLOverrides().find("history")->second.spec());
229}
230
231TEST_F(ExtensionManifestTest, ChromeURLPermissionInvalid) {
232  LoadAndExpectError("permission_chrome_url_invalid.json",
233      errors::kInvalidPermissionScheme);
234}
235
236TEST_F(ExtensionManifestTest, ChromeResourcesPermissionValidOnlyForComponents) {
237  LoadAndExpectError("permission_chrome_resources_url.json",
238      errors::kInvalidPermissionScheme);
239  std::string error;
240  scoped_ptr<Extension> extension;
241  extension.reset(LoadExtensionWithLocation(
242      "permission_chrome_resources_url.json",
243      Extension::COMPONENT,
244      &error));
245  EXPECT_EQ("", error);
246}
247
248TEST_F(ExtensionManifestTest, ChromeURLContentScriptInvalid) {
249  LoadAndExpectError("content_script_chrome_url_invalid.json",
250      errors::kInvalidMatch);
251}
252
253TEST_F(ExtensionManifestTest, DevToolsExtensions) {
254  LoadAndExpectError("devtools_extension_no_permissions.json",
255      errors::kDevToolsExperimental);
256  LoadAndExpectError("devtools_extension_url_invalid_type.json",
257      errors::kInvalidDevToolsPage);
258
259  CommandLine old_command_line = *CommandLine::ForCurrentProcess();
260  CommandLine::ForCurrentProcess()->AppendSwitch(
261      switches::kEnableExperimentalExtensionApis);
262
263  scoped_ptr<Extension> extension;
264  extension.reset(LoadAndExpectSuccess("devtools_extension.json"));
265  EXPECT_EQ(extension->url().spec() + "devtools.html",
266            extension->devtools_url().spec());
267  *CommandLine::ForCurrentProcess() = old_command_line;
268}
269
270TEST_F(ExtensionManifestTest, DisallowHybridApps) {
271  LoadAndExpectError("disallow_hybrid_1.json",
272                     errors::kHostedAppsCannotIncludeExtensionFeatures);
273  LoadAndExpectError("disallow_hybrid_2.json",
274                     errors::kHostedAppsCannotIncludeExtensionFeatures);
275}
276
277TEST_F(ExtensionManifestTest, OptionsPageInApps) {
278  scoped_ptr<Extension> extension;
279
280  // Allow options page with absolute URL in hosed apps.
281  extension.reset(
282      LoadAndExpectSuccess("hosted_app_absolute_options.json"));
283  EXPECT_STREQ("http",
284               extension->options_url().scheme().c_str());
285  EXPECT_STREQ("example.com",
286               extension->options_url().host().c_str());
287  EXPECT_STREQ("options.html",
288               extension->options_url().ExtractFileName().c_str());
289
290  // Forbid options page with relative URL in hosted apps.
291  LoadAndExpectError("hosted_app_relative_options.json",
292                     errors::kInvalidOptionsPageInHostedApp);
293
294  // Forbid options page with non-(http|https) scheme in hosted app.
295  LoadAndExpectError("hosted_app_file_options.json",
296                     errors::kInvalidOptionsPageInHostedApp);
297
298  // Forbid absolute URL for options page in packaged apps.
299  LoadAndExpectError("packaged_app_absolute_options.json",
300                     errors::kInvalidOptionsPageExpectUrlInPackage);
301}
302
303TEST_F(ExtensionManifestTest, DisallowExtensionPermissions) {
304  std::string error;
305  scoped_ptr<DictionaryValue> manifest(
306      LoadManifestFile("valid_app.json", &error));
307  ASSERT_TRUE(manifest.get());
308
309  ListValue *permissions = new ListValue();
310  manifest->Set(keys::kPermissions, permissions);
311  for (size_t i = 0; i < Extension::kNumPermissions; i++) {
312    const char* name = Extension::kPermissions[i].name;
313    StringValue* p = new StringValue(name);
314    permissions->Clear();
315    permissions->Append(p);
316    std::string message_name = StringPrintf("permission-%s", name);
317    if (Extension::IsHostedAppPermission(name)) {
318      scoped_ptr<Extension> extension;
319      extension.reset(LoadAndExpectSuccess(manifest.get(), message_name));
320    } else {
321      LoadAndExpectError(manifest.get(), message_name,
322                         errors::kInvalidPermission);
323    }
324  }
325}
326
327TEST_F(ExtensionManifestTest, NormalizeIconPaths) {
328  scoped_ptr<Extension> extension(
329      LoadAndExpectSuccess("normalize_icon_paths.json"));
330  EXPECT_EQ("16.png",
331            extension->icons().Get(16, ExtensionIconSet::MATCH_EXACTLY));
332  EXPECT_EQ("48.png",
333            extension->icons().Get(48, ExtensionIconSet::MATCH_EXACTLY));
334}
335
336TEST_F(ExtensionManifestTest, DisallowMultipleUISurfaces) {
337  LoadAndExpectError("multiple_ui_surfaces_1.json", errors::kOneUISurfaceOnly);
338  LoadAndExpectError("multiple_ui_surfaces_2.json", errors::kOneUISurfaceOnly);
339  LoadAndExpectError("multiple_ui_surfaces_3.json", errors::kOneUISurfaceOnly);
340}
341