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