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