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