// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/common/extensions/extension.h" #if defined(TOOLKIT_GTK) #include <gtk/gtk.h> #endif #include "base/format_macros.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/i18n/rtl.h" #include "base/path_service.h" #include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/extensions/extension_action.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_error_utils.h" #include "chrome/common/extensions/extension_resource.h" #include "chrome/common/url_constants.h" #include "content/common/json_value_serializer.h" #include "googleurl/src/gurl.h" #include "net/base/mime_sniffer.h" #include "skia/ext/image_operations.h" #include "chrome/test/ui_test_utils.h" #include "net/base/mock_host_resolver.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/base/l10n/l10n_util.h" #include "ui/gfx/codec/png_codec.h" namespace keys = extension_manifest_keys; namespace values = extension_manifest_values; namespace errors = extension_manifest_errors; namespace { void CompareLists(const std::vector<std::string>& expected, const std::vector<std::string>& actual) { ASSERT_EQ(expected.size(), actual.size()); for (size_t i = 0; i < expected.size(); ++i) { EXPECT_EQ(expected[i], actual[i]); } } static void AddPattern(ExtensionExtent* extent, const std::string& pattern) { int schemes = URLPattern::SCHEME_ALL; extent->AddPattern(URLPattern(schemes, pattern)); } } class ExtensionTest : public testing::Test { }; // We persist location values in the preferences, so this is a sanity test that // someone doesn't accidentally change them. TEST(ExtensionTest, LocationValuesTest) { ASSERT_EQ(0, Extension::INVALID); ASSERT_EQ(1, Extension::INTERNAL); ASSERT_EQ(2, Extension::EXTERNAL_PREF); ASSERT_EQ(3, Extension::EXTERNAL_REGISTRY); ASSERT_EQ(4, Extension::LOAD); ASSERT_EQ(5, Extension::COMPONENT); ASSERT_EQ(6, Extension::EXTERNAL_PREF_DOWNLOAD); ASSERT_EQ(7, Extension::EXTERNAL_POLICY_DOWNLOAD); } TEST(ExtensionTest, LocationPriorityTest) { for (int i = 0; i < Extension::NUM_LOCATIONS; i++) { Extension::Location loc = static_cast<Extension::Location>(i); // INVALID is not a valid location. if (loc == Extension::INVALID) continue; // Comparing a location that has no rank will hit a CHECK. Do a // compare with every valid location, to be sure each one is covered. // Check that no install source can override a componenet extension. ASSERT_EQ(Extension::COMPONENT, Extension::GetHigherPriorityLocation(Extension::COMPONENT, loc)); ASSERT_EQ(Extension::COMPONENT, Extension::GetHigherPriorityLocation(loc, Extension::COMPONENT)); // Check that any source can override a user install. This might change // in the future, in which case this test should be updated. ASSERT_EQ(loc, Extension::GetHigherPriorityLocation(Extension::INTERNAL, loc)); ASSERT_EQ(loc, Extension::GetHigherPriorityLocation(loc, Extension::INTERNAL)); } // Check a few interesting cases that we know can happen: ASSERT_EQ(Extension::EXTERNAL_POLICY_DOWNLOAD, Extension::GetHigherPriorityLocation( Extension::EXTERNAL_POLICY_DOWNLOAD, Extension::EXTERNAL_PREF)); ASSERT_EQ(Extension::EXTERNAL_PREF, Extension::GetHigherPriorityLocation( Extension::INTERNAL, Extension::EXTERNAL_PREF)); } // Please don't put any more manifest tests here!! // Move them to extension_manifest_unittest.cc instead and make them use the // more data-driven style there instead. // Bug: http://crbug.com/38462 TEST(ExtensionTest, InitFromValueInvalid) { #if defined(OS_WIN) FilePath path(FILE_PATH_LITERAL("c:\\foo")); #elif defined(OS_POSIX) FilePath path(FILE_PATH_LITERAL("/foo")); #endif scoped_refptr<Extension> extension_ptr(new Extension(path, Extension::INVALID)); Extension& extension = *extension_ptr; int error_code = 0; std::string error; // Start with a valid extension manifest FilePath extensions_path; ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path)); extensions_path = extensions_path.AppendASCII("extensions") .AppendASCII("good") .AppendASCII("Extensions") .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj") .AppendASCII("1.0.0.0") .Append(Extension::kManifestFilename); JSONFileValueSerializer serializer(extensions_path); scoped_ptr<DictionaryValue> valid_value( static_cast<DictionaryValue*>(serializer.Deserialize(&error_code, &error))); EXPECT_EQ("", error); EXPECT_EQ(0, error_code); ASSERT_TRUE(valid_value.get()); ASSERT_TRUE(extension.InitFromValue(*valid_value, Extension::REQUIRE_KEY, &error)); ASSERT_EQ("", error); EXPECT_EQ("en_US", extension.default_locale()); scoped_ptr<DictionaryValue> input_value; // Test missing and invalid versions input_value.reset(valid_value->DeepCopy()); input_value->Remove(keys::kVersion, NULL); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_EQ(errors::kInvalidVersion, error); input_value->SetInteger(keys::kVersion, 42); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_EQ(errors::kInvalidVersion, error); // Test missing and invalid names. input_value.reset(valid_value->DeepCopy()); input_value->Remove(keys::kName, NULL); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_EQ(errors::kInvalidName, error); input_value->SetInteger(keys::kName, 42); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_EQ(errors::kInvalidName, error); // Test invalid description input_value.reset(valid_value->DeepCopy()); input_value->SetInteger(keys::kDescription, 42); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_EQ(errors::kInvalidDescription, error); // Test invalid icons input_value.reset(valid_value->DeepCopy()); input_value->SetInteger(keys::kIcons, 42); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_EQ(errors::kInvalidIcons, error); // Test invalid icon paths input_value.reset(valid_value->DeepCopy()); DictionaryValue* icons = NULL; input_value->GetDictionary(keys::kIcons, &icons); ASSERT_FALSE(NULL == icons); icons->SetInteger(base::IntToString(128), 42); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidIconPath)); // Test invalid user scripts list input_value.reset(valid_value->DeepCopy()); input_value->SetInteger(keys::kContentScripts, 42); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_EQ(errors::kInvalidContentScriptsList, error); // Test invalid user script item input_value.reset(valid_value->DeepCopy()); ListValue* content_scripts = NULL; input_value->GetList(keys::kContentScripts, &content_scripts); ASSERT_FALSE(NULL == content_scripts); content_scripts->Set(0, Value::CreateIntegerValue(42)); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidContentScript)); // Test missing and invalid matches array input_value.reset(valid_value->DeepCopy()); input_value->GetList(keys::kContentScripts, &content_scripts); DictionaryValue* user_script = NULL; content_scripts->GetDictionary(0, &user_script); user_script->Remove(keys::kMatches, NULL); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidMatches)); user_script->Set(keys::kMatches, Value::CreateIntegerValue(42)); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidMatches)); ListValue* matches = new ListValue; user_script->Set(keys::kMatches, matches); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidMatchCount)); // Test invalid match element matches->Set(0, Value::CreateIntegerValue(42)); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidMatch)); matches->Set(0, Value::CreateStringValue("chrome://*/*")); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidMatch)); // Test missing and invalid files array input_value.reset(valid_value->DeepCopy()); input_value->GetList(keys::kContentScripts, &content_scripts); content_scripts->GetDictionary(0, &user_script); user_script->Remove(keys::kJs, NULL); user_script->Remove(keys::kCss, NULL); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kMissingFile)); user_script->Set(keys::kJs, Value::CreateIntegerValue(42)); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidJsList)); user_script->Set(keys::kCss, new ListValue); user_script->Set(keys::kJs, new ListValue); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kMissingFile)); user_script->Remove(keys::kCss, NULL); ListValue* files = new ListValue; user_script->Set(keys::kJs, files); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kMissingFile)); // Test invalid file element files->Set(0, Value::CreateIntegerValue(42)); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidJs)); user_script->Remove(keys::kJs, NULL); // Test the css element user_script->Set(keys::kCss, Value::CreateIntegerValue(42)); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidCssList)); // Test invalid file element ListValue* css_files = new ListValue; user_script->Set(keys::kCss, css_files); css_files->Set(0, Value::CreateIntegerValue(42)); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidCss)); // Test missing and invalid permissions array input_value.reset(valid_value->DeepCopy()); EXPECT_TRUE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); ListValue* permissions = NULL; input_value->GetList(keys::kPermissions, &permissions); ASSERT_FALSE(NULL == permissions); permissions = new ListValue; input_value->Set(keys::kPermissions, permissions); EXPECT_TRUE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); input_value->Set(keys::kPermissions, Value::CreateIntegerValue(9)); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidPermissions)); input_value.reset(valid_value->DeepCopy()); input_value->GetList(keys::kPermissions, &permissions); permissions->Set(0, Value::CreateIntegerValue(24)); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidPermission)); // We allow unknown API permissions, so this will be valid until we better // distinguish between API and host permissions. permissions->Set(0, Value::CreateStringValue("www.google.com")); EXPECT_TRUE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); // Multiple page actions are not allowed. input_value.reset(valid_value->DeepCopy()); DictionaryValue* action = new DictionaryValue; action->SetString(keys::kPageActionId, "MyExtensionActionId"); action->SetString(keys::kName, "MyExtensionActionName"); ListValue* action_list = new ListValue; action_list->Append(action->DeepCopy()); action_list->Append(action); input_value->Set(keys::kPageActions, action_list); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_STREQ(errors::kInvalidPageActionsListSize, error.c_str()); // Test invalid options page url. input_value.reset(valid_value->DeepCopy()); input_value->Set(keys::kOptionsPage, Value::CreateNullValue()); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidOptionsPage)); // Test invalid/empty default locale. input_value.reset(valid_value->DeepCopy()); input_value->Set(keys::kDefaultLocale, Value::CreateIntegerValue(5)); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidDefaultLocale)); input_value->Set(keys::kDefaultLocale, Value::CreateStringValue("")); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidDefaultLocale)); // Test invalid minimum_chrome_version. input_value.reset(valid_value->DeepCopy()); input_value->Set(keys::kMinimumChromeVersion, Value::CreateIntegerValue(42)); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidMinimumChromeVersion)); #if !defined(OS_MACOSX) // TODO(aa): The version isn't stamped into the unit test binary on mac. input_value->Set(keys::kMinimumChromeVersion, Value::CreateStringValue("88.8")); EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY, &error)); EXPECT_TRUE(MatchPattern(error, errors::kChromeVersionTooLow)); #endif } TEST(ExtensionTest, InitFromValueValid) { #if defined(OS_WIN) FilePath path(FILE_PATH_LITERAL("C:\\foo")); #elif defined(OS_POSIX) FilePath path(FILE_PATH_LITERAL("/foo")); #endif scoped_refptr<Extension> extension_ptr(new Extension(path, Extension::INVALID)); Extension& extension = *extension_ptr; std::string error; DictionaryValue input_value; // Test minimal extension input_value.SetString(keys::kVersion, "1.0.0.0"); input_value.SetString(keys::kName, "my extension"); EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS, &error)); EXPECT_EQ("", error); EXPECT_TRUE(Extension::IdIsValid(extension.id())); EXPECT_EQ("1.0.0.0", extension.VersionString()); EXPECT_EQ("my extension", extension.name()); EXPECT_EQ(extension.id(), extension.url().host()); EXPECT_EQ(path.value(), extension.path().value()); // Test permissions scheme. ListValue* permissions = new ListValue; permissions->Set(0, Value::CreateStringValue("file:///C:/foo.txt")); input_value.Set(keys::kPermissions, permissions); // We allow unknown API permissions, so this will be valid until we better // distinguish between API and host permissions. EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS, &error)); input_value.Remove(keys::kPermissions, NULL); // Test with an options page. input_value.SetString(keys::kOptionsPage, "options.html"); EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS, &error)); EXPECT_EQ("", error); EXPECT_EQ("chrome-extension", extension.options_url().scheme()); EXPECT_EQ("/options.html", extension.options_url().path()); // Test that an empty list of page actions does not stop a browser action // from being loaded. ListValue* empty_list = new ListValue; input_value.Set(keys::kPageActions, empty_list); EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS, &error)); EXPECT_EQ("", error); #if !defined(OS_MACOSX) // TODO(aa): The version isn't stamped into the unit test binary on mac. // Test with a minimum_chrome_version. input_value.SetString(keys::kMinimumChromeVersion, "1.0"); EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS, &error)); EXPECT_EQ("", error); // The minimum chrome version is not stored in the Extension object. #endif } TEST(ExtensionTest, InitFromValueValidNameInRTL) { #if defined(TOOLKIT_GTK) GtkTextDirection gtk_dir = gtk_widget_get_default_direction(); gtk_widget_set_default_direction(GTK_TEXT_DIR_RTL); #else std::string locale = l10n_util::GetApplicationLocale(""); base::i18n::SetICUDefaultLocale("he"); #endif #if defined(OS_WIN) FilePath path(FILE_PATH_LITERAL("C:\\foo")); #elif defined(OS_POSIX) FilePath path(FILE_PATH_LITERAL("/foo")); #endif scoped_refptr<Extension> extension_ptr(new Extension(path, Extension::INVALID)); Extension& extension = *extension_ptr; std::string error; DictionaryValue input_value; input_value.SetString(keys::kVersion, "1.0.0.0"); // No strong RTL characters in name. std::wstring name(L"Dictionary (by Google)"); input_value.SetString(keys::kName, WideToUTF16Hack(name)); EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS, &error)); EXPECT_EQ("", error); std::wstring localized_name(name); base::i18n::AdjustStringForLocaleDirection(&localized_name); EXPECT_EQ(localized_name, UTF8ToWide(extension.name())); // Strong RTL characters in name. name = L"Dictionary (\x05D1\x05D2"L" Google)"; input_value.SetString(keys::kName, WideToUTF16Hack(name)); EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS, &error)); EXPECT_EQ("", error); localized_name = name; base::i18n::AdjustStringForLocaleDirection(&localized_name); EXPECT_EQ(localized_name, UTF8ToWide(extension.name())); // Reset locale. #if defined(TOOLKIT_GTK) gtk_widget_set_default_direction(gtk_dir); #else base::i18n::SetICUDefaultLocale(locale); #endif } TEST(ExtensionTest, GetResourceURLAndPath) { #if defined(OS_WIN) FilePath path(FILE_PATH_LITERAL("C:\\foo")); #elif defined(OS_POSIX) FilePath path(FILE_PATH_LITERAL("/foo")); #endif DictionaryValue input_value; input_value.SetString(keys::kVersion, "1.0.0.0"); input_value.SetString(keys::kName, "my extension"); scoped_refptr<Extension> extension(Extension::Create(path, Extension::INVALID, input_value, Extension::STRICT_ERROR_CHECKS, NULL)); EXPECT_TRUE(extension.get()); EXPECT_EQ(extension->url().spec() + "bar/baz.js", Extension::GetResourceURL(extension->url(), "bar/baz.js").spec()); EXPECT_EQ(extension->url().spec() + "baz.js", Extension::GetResourceURL(extension->url(), "bar/../baz.js").spec()); EXPECT_EQ(extension->url().spec() + "baz.js", Extension::GetResourceURL(extension->url(), "../baz.js").spec()); } TEST(ExtensionTest, LoadPageActionHelper) { #if defined(OS_WIN) FilePath path(base::StringPrintf(L"c:\\extension")); #else FilePath path(base::StringPrintf("/extension")); #endif scoped_refptr<Extension> extension_ptr(new Extension(path, Extension::INVALID)); Extension& extension = *extension_ptr; std::string error_msg; scoped_ptr<ExtensionAction> action; DictionaryValue input; // First try with an empty dictionary. action.reset(extension.LoadExtensionActionHelper(&input, &error_msg)); ASSERT_TRUE(action != NULL); ASSERT_TRUE(error_msg.empty()); // Now setup some values to use in the action. const std::string id("MyExtensionActionId"); const std::string name("MyExtensionActionName"); std::string img1("image1.png"); std::string img2("image2.png"); // Add the dictionary for the contextual action. input.SetString(keys::kPageActionId, id); input.SetString(keys::kName, name); ListValue* icons = new ListValue; icons->Set(0, Value::CreateStringValue(img1)); icons->Set(1, Value::CreateStringValue(img2)); input.Set(keys::kPageActionIcons, icons); // Parse and read back the values from the object. action.reset(extension.LoadExtensionActionHelper(&input, &error_msg)); ASSERT_TRUE(NULL != action.get()); ASSERT_TRUE(error_msg.empty()); ASSERT_EQ(id, action->id()); // No title, so fall back to name. ASSERT_EQ(name, action->GetTitle(1)); ASSERT_EQ(2u, action->icon_paths()->size()); ASSERT_EQ(img1, (*action->icon_paths())[0]); ASSERT_EQ(img2, (*action->icon_paths())[1]); // Explicitly set the same type and parse again. input.SetString(keys::kType, values::kPageActionTypeTab); action.reset(extension.LoadExtensionActionHelper(&input, &error_msg)); ASSERT_TRUE(NULL != action.get()); ASSERT_TRUE(error_msg.empty()); // Make a deep copy of the input and remove one key at a time and see if we // get the right error. scoped_ptr<DictionaryValue> copy; // First remove id key. copy.reset(input.DeepCopy()); copy->Remove(keys::kPageActionId, NULL); action.reset(extension.LoadExtensionActionHelper(copy.get(), &error_msg)); ASSERT_TRUE(NULL != action.get()); // Then remove the name key. It's optional, so no error. copy.reset(input.DeepCopy()); copy->Remove(keys::kName, NULL); action.reset(extension.LoadExtensionActionHelper(copy.get(), &error_msg)); ASSERT_TRUE(NULL != action.get()); ASSERT_TRUE(action->GetTitle(1).empty()); ASSERT_TRUE(error_msg.empty()); // Then remove the icon paths key. copy.reset(input.DeepCopy()); copy->Remove(keys::kPageActionIcons, NULL); action.reset(extension.LoadExtensionActionHelper(copy.get(), &error_msg)); ASSERT_TRUE(NULL != action.get()); error_msg = ""; // Now test that we can parse the new format for page actions. // Now setup some values to use in the page action. const std::string kTitle("MyExtensionActionTitle"); const std::string kIcon("image1.png"); const std::string kPopupHtmlFile("a_popup.html"); // Add the dictionary for the contextual action. input.Clear(); input.SetString(keys::kPageActionDefaultTitle, kTitle); input.SetString(keys::kPageActionDefaultIcon, kIcon); // Parse and read back the values from the object. action.reset(extension.LoadExtensionActionHelper(&input, &error_msg)); ASSERT_TRUE(action.get()); ASSERT_TRUE(error_msg.empty()); ASSERT_EQ(kTitle, action->GetTitle(1)); ASSERT_EQ(0u, action->icon_paths()->size()); // Invalid title should give an error even with a valid name. input.Clear(); input.SetInteger(keys::kPageActionDefaultTitle, 42); input.SetString(keys::kName, name); action.reset(extension.LoadExtensionActionHelper(&input, &error_msg)); ASSERT_TRUE(NULL == action.get()); ASSERT_STREQ(errors::kInvalidPageActionDefaultTitle, error_msg.c_str()); error_msg = ""; // Invalid name should give an error only with no title. input.SetString(keys::kPageActionDefaultTitle, kTitle); input.SetInteger(keys::kName, 123); action.reset(extension.LoadExtensionActionHelper(&input, &error_msg)); ASSERT_TRUE(NULL != action.get()); ASSERT_EQ(kTitle, action->GetTitle(1)); ASSERT_TRUE(error_msg.empty()); input.Remove(keys::kPageActionDefaultTitle, NULL); action.reset(extension.LoadExtensionActionHelper(&input, &error_msg)); ASSERT_TRUE(NULL == action.get()); ASSERT_STREQ(errors::kInvalidPageActionName, error_msg.c_str()); error_msg = ""; // Test that keys "popup" and "default_popup" both work, but can not // be used at the same time. input.Clear(); input.SetString(keys::kPageActionDefaultTitle, kTitle); input.SetString(keys::kPageActionDefaultIcon, kIcon); // LoadExtensionActionHelper expects the extension member |extension_url| // to be set. extension.extension_url_ = GURL(std::string(chrome::kExtensionScheme) + chrome::kStandardSchemeSeparator + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/"); // Add key "popup", expect success. input.SetString(keys::kPageActionPopup, kPopupHtmlFile); action.reset(extension.LoadExtensionActionHelper(&input, &error_msg)); ASSERT_TRUE(NULL != action.get()); ASSERT_TRUE(error_msg.empty()); ASSERT_STREQ( extension.url().Resolve(kPopupHtmlFile).spec().c_str(), action->GetPopupUrl(ExtensionAction::kDefaultTabId).spec().c_str()); // Add key "default_popup", expect failure. input.SetString(keys::kPageActionDefaultPopup, kPopupHtmlFile); action.reset(extension.LoadExtensionActionHelper(&input, &error_msg)); ASSERT_TRUE(NULL == action.get()); ASSERT_STREQ( ExtensionErrorUtils::FormatErrorMessage( errors::kInvalidPageActionOldAndNewKeys, keys::kPageActionDefaultPopup, keys::kPageActionPopup).c_str(), error_msg.c_str()); error_msg = ""; // Remove key "popup", expect success. input.Remove(keys::kPageActionPopup, NULL); action.reset(extension.LoadExtensionActionHelper(&input, &error_msg)); ASSERT_TRUE(NULL != action.get()); ASSERT_TRUE(error_msg.empty()); ASSERT_STREQ( extension.url().Resolve(kPopupHtmlFile).spec().c_str(), action->GetPopupUrl(ExtensionAction::kDefaultTabId).spec().c_str()); // Setting default_popup to "" is the same as having no popup. input.Remove(keys::kPageActionDefaultPopup, NULL); input.SetString(keys::kPageActionDefaultPopup, ""); action.reset(extension.LoadExtensionActionHelper(&input, &error_msg)); ASSERT_TRUE(NULL != action.get()); ASSERT_TRUE(error_msg.empty()); EXPECT_FALSE(action->HasPopup(ExtensionAction::kDefaultTabId)); ASSERT_STREQ( "", action->GetPopupUrl(ExtensionAction::kDefaultTabId).spec().c_str()); // Setting popup to "" is the same as having no popup. input.Remove(keys::kPageActionDefaultPopup, NULL); input.SetString(keys::kPageActionPopup, ""); action.reset(extension.LoadExtensionActionHelper(&input, &error_msg)); ASSERT_TRUE(NULL != action.get()); ASSERT_TRUE(error_msg.empty()); EXPECT_FALSE(action->HasPopup(ExtensionAction::kDefaultTabId)); ASSERT_STREQ( "", action->GetPopupUrl(ExtensionAction::kDefaultTabId).spec().c_str()); } TEST(ExtensionTest, IdIsValid) { EXPECT_TRUE(Extension::IdIsValid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); EXPECT_TRUE(Extension::IdIsValid("pppppppppppppppppppppppppppppppp")); EXPECT_TRUE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmnop")); EXPECT_TRUE(Extension::IdIsValid("ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP")); EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmno")); EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmnopa")); EXPECT_FALSE(Extension::IdIsValid("0123456789abcdef0123456789abcdef")); EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmnoq")); EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmno0")); } TEST(ExtensionTest, GenerateID) { const uint8 public_key_info[] = { 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xb8, 0x7f, 0x2b, 0x20, 0xdc, 0x7c, 0x9b, 0x0c, 0xdc, 0x51, 0x61, 0x99, 0x0d, 0x36, 0x0f, 0xd4, 0x66, 0x88, 0x08, 0x55, 0x84, 0xd5, 0x3a, 0xbf, 0x2b, 0xa4, 0x64, 0x85, 0x7b, 0x0c, 0x04, 0x13, 0x3f, 0x8d, 0xf4, 0xbc, 0x38, 0x0d, 0x49, 0xfe, 0x6b, 0xc4, 0x5a, 0xb0, 0x40, 0x53, 0x3a, 0xd7, 0x66, 0x09, 0x0f, 0x9e, 0x36, 0x74, 0x30, 0xda, 0x8a, 0x31, 0x4f, 0x1f, 0x14, 0x50, 0xd7, 0xc7, 0x20, 0x94, 0x17, 0xde, 0x4e, 0xb9, 0x57, 0x5e, 0x7e, 0x0a, 0xe5, 0xb2, 0x65, 0x7a, 0x89, 0x4e, 0xb6, 0x47, 0xff, 0x1c, 0xbd, 0xb7, 0x38, 0x13, 0xaf, 0x47, 0x85, 0x84, 0x32, 0x33, 0xf3, 0x17, 0x49, 0xbf, 0xe9, 0x96, 0xd0, 0xd6, 0x14, 0x6f, 0x13, 0x8d, 0xc5, 0xfc, 0x2c, 0x72, 0xba, 0xac, 0xea, 0x7e, 0x18, 0x53, 0x56, 0xa6, 0x83, 0xa2, 0xce, 0x93, 0x93, 0xe7, 0x1f, 0x0f, 0xe6, 0x0f, 0x02, 0x03, 0x01, 0x00, 0x01 }; std::string extension_id; EXPECT_TRUE( Extension::GenerateId( std::string(reinterpret_cast<const char*>(&public_key_info[0]), arraysize(public_key_info)), &extension_id)); EXPECT_EQ("melddjfinppjdikinhbgehiennejpfhp", extension_id); } TEST(ExtensionTest, UpdateUrls) { // Test several valid update urls std::vector<std::string> valid; valid.push_back("http://test.com"); valid.push_back("http://test.com/"); valid.push_back("http://test.com/update"); valid.push_back("http://test.com/update?check=true"); for (size_t i = 0; i < valid.size(); i++) { GURL url(valid[i]); EXPECT_TRUE(url.is_valid()); DictionaryValue input_value; #if defined(OS_WIN) // (Why %Iu below? This is the single file in the whole code base that // might make use of a WidePRIuS; let's not encourage any more.) FilePath path(base::StringPrintf(L"c:\\extension%Iu", i)); #else FilePath path(base::StringPrintf("/extension%" PRIuS, i)); #endif std::string error; input_value.SetString(keys::kVersion, "1.0"); input_value.SetString(keys::kName, "Test"); input_value.SetString(keys::kUpdateURL, url.spec()); scoped_refptr<Extension> extension(Extension::Create( path, Extension::INVALID, input_value, Extension::STRICT_ERROR_CHECKS, &error)); EXPECT_TRUE(extension.get()) << error; } // Test some invalid update urls std::vector<std::string> invalid; invalid.push_back(""); invalid.push_back("test.com"); valid.push_back("http://test.com/update#whatever"); for (size_t i = 0; i < invalid.size(); i++) { DictionaryValue input_value; #if defined(OS_WIN) // (Why %Iu below? This is the single file in the whole code base that // might make use of a WidePRIuS; let's not encourage any more.) FilePath path(base::StringPrintf(L"c:\\extension%Iu", i)); #else FilePath path(base::StringPrintf("/extension%" PRIuS, i)); #endif std::string error; input_value.SetString(keys::kVersion, "1.0"); input_value.SetString(keys::kName, "Test"); input_value.SetString(keys::kUpdateURL, invalid[i]); scoped_refptr<Extension> extension(Extension::Create( path, Extension::INVALID, input_value, Extension::STRICT_ERROR_CHECKS, &error)); EXPECT_FALSE(extension.get()); EXPECT_TRUE(MatchPattern(error, errors::kInvalidUpdateURL)); } } // This test ensures that the mimetype sniffing code stays in sync with the // actual crx files that we test other parts of the system with. TEST(ExtensionTest, MimeTypeSniffing) { FilePath path; ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path)); path = path.AppendASCII("extensions").AppendASCII("good.crx"); std::string data; ASSERT_TRUE(file_util::ReadFileToString(path, &data)); std::string result; EXPECT_TRUE(net::SniffMimeType(data.c_str(), data.size(), GURL("http://www.example.com/foo.crx"), "", &result)); EXPECT_EQ(std::string(Extension::kMimeType), result); data.clear(); result.clear(); path = path.DirName().AppendASCII("bad_magic.crx"); ASSERT_TRUE(file_util::ReadFileToString(path, &data)); EXPECT_TRUE(net::SniffMimeType(data.c_str(), data.size(), GURL("http://www.example.com/foo.crx"), "", &result)); EXPECT_EQ("application/octet-stream", result); } static scoped_refptr<Extension> LoadManifest(const std::string& dir, const std::string& test_file, int extra_flags) { FilePath path; PathService::Get(chrome::DIR_TEST_DATA, &path); path = path.AppendASCII("extensions") .AppendASCII(dir) .AppendASCII(test_file); JSONFileValueSerializer serializer(path); std::string error; scoped_ptr<Value> result(serializer.Deserialize(NULL, &error)); if (!result.get()) { EXPECT_EQ("", error); return NULL; } scoped_refptr<Extension> extension = Extension::Create( path.DirName(), Extension::INVALID, *static_cast<DictionaryValue*>(result.get()), Extension::STRICT_ERROR_CHECKS | extra_flags, &error); EXPECT_TRUE(extension) << error; return extension; } static scoped_refptr<Extension> LoadManifest(const std::string& dir, const std::string& test_file) { return LoadManifest(dir, test_file, Extension::NO_FLAGS); } TEST(ExtensionTest, EffectiveHostPermissions) { scoped_refptr<Extension> extension; ExtensionExtent hosts; extension = LoadManifest("effective_host_permissions", "empty.json"); EXPECT_EQ(0u, extension->GetEffectiveHostPermissions().patterns().size()); EXPECT_FALSE(hosts.ContainsURL(GURL("http://www.google.com"))); EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts()); extension = LoadManifest("effective_host_permissions", "one_host.json"); hosts = extension->GetEffectiveHostPermissions(); EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.google.com"))); EXPECT_FALSE(hosts.ContainsURL(GURL("https://www.google.com"))); EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts()); extension = LoadManifest("effective_host_permissions", "one_host_wildcard.json"); hosts = extension->GetEffectiveHostPermissions(); EXPECT_TRUE(hosts.ContainsURL(GURL("http://google.com"))); EXPECT_TRUE(hosts.ContainsURL(GURL("http://foo.google.com"))); EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts()); extension = LoadManifest("effective_host_permissions", "two_hosts.json"); hosts = extension->GetEffectiveHostPermissions(); EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.google.com"))); EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.reddit.com"))); EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts()); extension = LoadManifest("effective_host_permissions", "https_not_considered.json"); hosts = extension->GetEffectiveHostPermissions(); EXPECT_TRUE(hosts.ContainsURL(GURL("http://google.com"))); EXPECT_TRUE(hosts.ContainsURL(GURL("https://google.com"))); EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts()); extension = LoadManifest("effective_host_permissions", "two_content_scripts.json"); hosts = extension->GetEffectiveHostPermissions(); EXPECT_TRUE(hosts.ContainsURL(GURL("http://google.com"))); EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.reddit.com"))); EXPECT_TRUE(hosts.ContainsURL(GURL("http://news.ycombinator.com"))); EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts()); extension = LoadManifest("effective_host_permissions", "all_hosts.json"); hosts = extension->GetEffectiveHostPermissions(); EXPECT_TRUE(hosts.ContainsURL(GURL("http://test/"))); EXPECT_FALSE(hosts.ContainsURL(GURL("https://test/"))); EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.google.com"))); EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts()); extension = LoadManifest("effective_host_permissions", "all_hosts2.json"); hosts = extension->GetEffectiveHostPermissions(); EXPECT_TRUE(hosts.ContainsURL(GURL("http://test/"))); EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.google.com"))); EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts()); extension = LoadManifest("effective_host_permissions", "all_hosts3.json"); hosts = extension->GetEffectiveHostPermissions(); EXPECT_FALSE(hosts.ContainsURL(GURL("http://test/"))); EXPECT_TRUE(hosts.ContainsURL(GURL("https://test/"))); EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.google.com"))); EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts()); } TEST(ExtensionTest, IsPrivilegeIncrease) { const struct { const char* base_name; // Increase these sizes if you have more than 10. const char* granted_apis[10]; const char* granted_hosts[10]; bool full_access; bool expect_increase; } kTests[] = { { "allhosts1", {NULL}, {"http://*/", NULL}, false, false }, // all -> all { "allhosts2", {NULL}, {"http://*/", NULL}, false, false }, // all -> one { "allhosts3", {NULL}, {NULL}, false, true }, // one -> all { "hosts1", {NULL}, {"http://www.google.com/", "http://www.reddit.com/", NULL}, false, false }, // http://a,http://b -> http://a,http://b { "hosts2", {NULL}, {"http://www.google.com/", "http://www.reddit.com/", NULL}, false, true }, // http://a,http://b -> https://a,http://*.b { "hosts3", {NULL}, {"http://www.google.com/", "http://www.reddit.com/", NULL}, false, false }, // http://a,http://b -> http://a { "hosts4", {NULL}, {"http://www.google.com/", NULL}, false, true }, // http://a -> http://a,http://b { "hosts5", {"tabs", "notifications", NULL}, {"http://*.example.com/", "http://*.example.com/*", "http://*.example.co.uk/*", "http://*.example.com.au/*", NULL}, false, false }, // http://a,b,c -> http://a,b,c + https://a,b,c { "hosts6", {"tabs", "notifications", NULL}, {"http://*.example.com/", "http://*.example.com/*", NULL}, false, false }, // http://a.com -> http://a.com + http://a.co.uk { "permissions1", {"tabs", NULL}, {NULL}, false, false }, // tabs -> tabs { "permissions2", {"tabs", NULL}, {NULL}, false, true }, // tabs -> tabs,bookmarks { "permissions3", {NULL}, {"http://*/*", NULL}, false, true }, // http://a -> http://a,tabs { "permissions5", {"bookmarks", NULL}, {NULL}, false, true }, // bookmarks -> bookmarks,history #if !defined(OS_CHROMEOS) // plugins aren't allowed in ChromeOS { "permissions4", {NULL}, {NULL}, true, false }, // plugin -> plugin,tabs { "plugin1", {NULL}, {NULL}, true, false }, // plugin -> plugin { "plugin2", {NULL}, {NULL}, true, false }, // plugin -> none { "plugin3", {NULL}, {NULL}, false, true }, // none -> plugin #endif { "storage", {NULL}, {NULL}, false, false }, // none -> storage { "notifications", {NULL}, {NULL}, false, false } // none -> notifications }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) { scoped_refptr<Extension> old_extension( LoadManifest("allow_silent_upgrade", std::string(kTests[i].base_name) + "_old.json")); scoped_refptr<Extension> new_extension( LoadManifest("allow_silent_upgrade", std::string(kTests[i].base_name) + "_new.json")); std::set<std::string> granted_apis; for (size_t j = 0; kTests[i].granted_apis[j] != NULL; ++j) granted_apis.insert(kTests[i].granted_apis[j]); ExtensionExtent granted_hosts; for (size_t j = 0; kTests[i].granted_hosts[j] != NULL; ++j) AddPattern(&granted_hosts, kTests[i].granted_hosts[j]); EXPECT_TRUE(new_extension.get()) << kTests[i].base_name << "_new.json"; if (!new_extension.get()) continue; EXPECT_EQ(kTests[i].expect_increase, Extension::IsPrivilegeIncrease(kTests[i].full_access, granted_apis, granted_hosts, new_extension.get())) << kTests[i].base_name; } } TEST(ExtensionTest, PermissionMessages) { // Ensure that all permissions that needs to show install UI actually have // strings associated with them. std::set<std::string> skip; // These are considered "nuisance" or "trivial" permissions that don't need // a prompt. skip.insert(Extension::kContextMenusPermission); skip.insert(Extension::kIdlePermission); skip.insert(Extension::kNotificationPermission); skip.insert(Extension::kUnlimitedStoragePermission); skip.insert(Extension::kContentSettingsPermission); // TODO(erikkay) add a string for this permission. skip.insert(Extension::kBackgroundPermission); // The cookie permission does nothing unless you have associated host // permissions. skip.insert(Extension::kCookiePermission); // The proxy permission is warned as part of host permission checks. skip.insert(Extension::kProxyPermission); // This permission requires explicit user action (context menu handler) // so we won't prompt for it for now. skip.insert(Extension::kFileBrowserHandlerPermission); // If you've turned on the experimental command-line flag, we don't need // to warn you further. skip.insert(Extension::kExperimentalPermission); // These are only usable by component extensions. skip.insert(Extension::kWebstorePrivatePermission); skip.insert(Extension::kFileBrowserPrivatePermission); skip.insert(Extension::kChromeosInfoPrivatePermissions); const Extension::PermissionMessage::MessageId ID_NONE = Extension::PermissionMessage::ID_NONE; for (size_t i = 0; i < Extension::kNumPermissions; ++i) { Extension::Permission permission = Extension::kPermissions[i]; if (skip.count(permission.name)) { EXPECT_EQ(ID_NONE, permission.message_id) << "unexpected message_id for " << permission.name; } else { EXPECT_NE(ID_NONE, permission.message_id) << "missing message_id for " << permission.name; } } } // Returns a copy of |source| resized to |size| x |size|. static SkBitmap ResizedCopy(const SkBitmap& source, int size) { return skia::ImageOperations::Resize(source, skia::ImageOperations::RESIZE_LANCZOS3, size, size); } static bool SizeEquals(const SkBitmap& bitmap, const gfx::Size& size) { return bitmap.width() == size.width() && bitmap.height() == size.height(); } TEST(ExtensionTest, ImageCaching) { FilePath path; ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path)); path = path.AppendASCII("extensions"); // Initialize the Extension. std::string errors; DictionaryValue values; values.SetString(keys::kName, "test"); values.SetString(keys::kVersion, "0.1"); scoped_refptr<Extension> extension(Extension::Create( path, Extension::INVALID, values, Extension::STRICT_ERROR_CHECKS, &errors)); ASSERT_TRUE(extension.get()); // Create an ExtensionResource pointing at an icon. FilePath icon_relative_path(FILE_PATH_LITERAL("icon3.png")); ExtensionResource resource(extension->id(), extension->path(), icon_relative_path); // Read in the icon file. FilePath icon_absolute_path = extension->path().Append(icon_relative_path); std::string raw_png; ASSERT_TRUE(file_util::ReadFileToString(icon_absolute_path, &raw_png)); SkBitmap image; ASSERT_TRUE(gfx::PNGCodec::Decode( reinterpret_cast<const unsigned char*>(raw_png.data()), raw_png.length(), &image)); // Make sure the icon file is the size we expect. gfx::Size original_size(66, 66); ASSERT_EQ(image.width(), original_size.width()); ASSERT_EQ(image.height(), original_size.height()); // Create two resized versions at size 16x16 and 24x24. SkBitmap image16 = ResizedCopy(image, 16); SkBitmap image24 = ResizedCopy(image, 24); gfx::Size size16(16, 16); gfx::Size size24(24, 24); // Cache the 16x16 copy. EXPECT_FALSE(extension->HasCachedImage(resource, size16)); extension->SetCachedImage(resource, image16, original_size); EXPECT_TRUE(extension->HasCachedImage(resource, size16)); EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, size16), size16)); EXPECT_FALSE(extension->HasCachedImage(resource, size24)); EXPECT_FALSE(extension->HasCachedImage(resource, original_size)); // Cache the 24x24 copy. extension->SetCachedImage(resource, image24, original_size); EXPECT_TRUE(extension->HasCachedImage(resource, size24)); EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, size24), size24)); EXPECT_FALSE(extension->HasCachedImage(resource, original_size)); // Cache the original, and verify that it gets returned when we ask for a // max_size that is larger than the original. gfx::Size size128(128, 128); EXPECT_TRUE(image.width() < size128.width() && image.height() < size128.height()); extension->SetCachedImage(resource, image, original_size); EXPECT_TRUE(extension->HasCachedImage(resource, original_size)); EXPECT_TRUE(extension->HasCachedImage(resource, size128)); EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, original_size), original_size)); EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, size128), original_size)); EXPECT_EQ(extension->GetCachedImage(resource, original_size).getPixels(), extension->GetCachedImage(resource, size128).getPixels()); } // Tests that the old permission name "unlimited_storage" still works for // backwards compatibility (we renamed it to "unlimitedStorage"). TEST(ExtensionTest, OldUnlimitedStoragePermission) { ScopedTempDir directory; ASSERT_TRUE(directory.CreateUniqueTempDir()); FilePath extension_path = directory.path(); DictionaryValue dictionary; // The two required keys. dictionary.SetString(extension_manifest_keys::kName, "test"); dictionary.SetString(extension_manifest_keys::kVersion, "0.1"); // Create a permissions list containing "unlimited_storage" and add it. ListValue* permissions = new ListValue(); const char* old_unlimited = "unlimited_storage"; EXPECT_STREQ(old_unlimited, Extension::kOldUnlimitedStoragePermission); permissions->Append(Value::CreateStringValue(old_unlimited)); dictionary.Set(extension_manifest_keys::kPermissions, permissions); // Initialize the extension and make sure the permission for unlimited storage // is present. std::string errors; scoped_refptr<Extension> extension(Extension::Create( extension_path, Extension::INVALID, dictionary, Extension::STRICT_ERROR_CHECKS, &errors)); EXPECT_TRUE(extension.get()); EXPECT_TRUE(extension->HasApiPermission( Extension::kUnlimitedStoragePermission)); } // This tests the API permissions with an empty manifest (one that just // specifies a name and a version and nothing else). TEST(ExtensionTest, ApiPermissions) { const struct { const char* permission_name; bool expect_success; } kTests[] = { // Negative test. { "non_existing_permission", false }, // Test default module/package permission. { "browserAction", true }, { "browserActions", true }, { "devtools", true }, { "extension", true }, { "i18n", true }, { "pageAction", true }, { "pageActions", true }, { "test", true }, // Some negative tests. { "bookmarks", false }, { "cookies", false }, { "history", false }, { "tabs.onUpdated", false }, // Make sure we find the module name after stripping '.' and '/'. { "browserAction/abcd/onClick", true }, { "browserAction.abcd.onClick", true }, // Test Tabs functions. { "tabs.create", true}, { "tabs.update", true}, { "tabs.getSelected", false}, }; scoped_refptr<Extension> extension; extension = LoadManifest("empty_manifest", "empty.json"); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) { EXPECT_EQ(kTests[i].expect_success, extension->HasApiPermission(kTests[i].permission_name)) << "Permission being tested: " << kTests[i].permission_name; } } TEST(ExtensionTest, GetHostPermissionMessages_ManyHosts) { scoped_refptr<Extension> extension; extension = LoadManifest("permissions", "many-hosts.json"); std::vector<string16> warnings = extension->GetPermissionMessageStrings(); ASSERT_EQ(1u, warnings.size()); EXPECT_EQ("Your data on www.google.com and encrypted.google.com", UTF16ToUTF8(warnings[0])); } TEST(ExtensionTest, GetPermissionMessages_Plugins) { scoped_refptr<Extension> extension; extension = LoadManifest("permissions", "plugins.json"); std::vector<string16> warnings = extension->GetPermissionMessageStrings(); // We don't parse the plugins key on Chrome OS, so it should not ask for any // permissions. #if defined(OS_CHROMEOS) ASSERT_EQ(0u, warnings.size()); #else ASSERT_EQ(1u, warnings.size()); EXPECT_EQ("All data on your computer and the websites you visit", UTF16ToUTF8(warnings[0])); #endif } TEST(ExtensionTest, WantsFileAccess) { scoped_refptr<Extension> extension; GURL file_url("file:///etc/passwd"); // <all_urls> permission extension = LoadManifest("permissions", "permissions_all_urls.json"); EXPECT_TRUE(extension->wants_file_access()); EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL)); extension = LoadManifest( "permissions", "permissions_all_urls.json", Extension::ALLOW_FILE_ACCESS); EXPECT_TRUE(extension->wants_file_access()); EXPECT_TRUE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL)); // file:///* permission extension = LoadManifest("permissions", "permissions_file_scheme.json"); EXPECT_TRUE(extension->wants_file_access()); EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL)); extension = LoadManifest("permissions", "permissions_file_scheme.json", Extension::ALLOW_FILE_ACCESS); EXPECT_TRUE(extension->wants_file_access()); EXPECT_TRUE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL)); // http://* permission extension = LoadManifest("permissions", "permissions_http_scheme.json"); EXPECT_FALSE(extension->wants_file_access()); EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL)); extension = LoadManifest("permissions", "permissions_http_scheme.json", Extension::ALLOW_FILE_ACCESS); EXPECT_FALSE(extension->wants_file_access()); EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL)); // <all_urls> content script match extension = LoadManifest("permissions", "content_script_all_urls.json"); EXPECT_TRUE(extension->wants_file_access()); EXPECT_FALSE(extension->CanExecuteScriptOnPage( file_url, &extension->content_scripts()[0], NULL)); extension = LoadManifest("permissions", "content_script_all_urls.json", Extension::ALLOW_FILE_ACCESS); EXPECT_TRUE(extension->wants_file_access()); EXPECT_TRUE(extension->CanExecuteScriptOnPage( file_url, &extension->content_scripts()[0], NULL)); // file:///* content script match extension = LoadManifest("permissions", "content_script_file_scheme.json"); EXPECT_TRUE(extension->wants_file_access()); EXPECT_FALSE(extension->CanExecuteScriptOnPage( file_url, &extension->content_scripts()[0], NULL)); extension = LoadManifest("permissions", "content_script_file_scheme.json", Extension::ALLOW_FILE_ACCESS); EXPECT_TRUE(extension->wants_file_access()); EXPECT_TRUE(extension->CanExecuteScriptOnPage( file_url, &extension->content_scripts()[0], NULL)); // http://* content script match extension = LoadManifest("permissions", "content_script_http_scheme.json"); EXPECT_FALSE(extension->wants_file_access()); EXPECT_FALSE(extension->CanExecuteScriptOnPage( file_url, &extension->content_scripts()[0], NULL)); extension = LoadManifest("permissions", "content_script_http_scheme.json", Extension::ALLOW_FILE_ACCESS); EXPECT_FALSE(extension->wants_file_access()); EXPECT_FALSE(extension->CanExecuteScriptOnPage( file_url, &extension->content_scripts()[0], NULL)); } // Base class for testing the CanExecuteScriptOnPage and CanCaptureVisiblePage // methods of Extension for extensions with various permissions. class ExtensionScriptAndCaptureVisibleTest : public testing::Test { public: ExtensionScriptAndCaptureVisibleTest() { PathService::Get(chrome::DIR_TEST_DATA, &dirpath_); } scoped_refptr<Extension> MakeExtension(const std::string& permissions, Extension::Location location) { // Replace single-quotes with double-quotes in permissions, since JSON // mandates double-quotes. std::string munged_permissions = permissions; ReplaceSubstringsAfterOffset(&munged_permissions, 0, "'", "\""); DictionaryValue dictionary; dictionary.SetString(keys::kName, "permission test"); dictionary.SetString(keys::kVersion, "1"); std::string error; JSONStringValueSerializer serializer(munged_permissions); scoped_ptr<Value> permission_value(serializer.Deserialize(NULL, &error)); EXPECT_EQ("", error); if (!permission_value.get()) return NULL; EXPECT_TRUE(permission_value->IsType(Value::TYPE_LIST)); dictionary.Set(keys::kPermissions, permission_value.release()); FilePath dirpath; PathService::Get(chrome::DIR_TEST_DATA, &dirpath); dirpath = dirpath.AppendASCII("extensions").AppendASCII("permissions"); scoped_refptr<Extension> extension = Extension::Create( dirpath, location, dictionary, Extension::STRICT_ERROR_CHECKS, &error); if (!extension) VLOG(1) << error; return extension; } bool Allowed(const Extension* extension, const GURL& url) { return (extension->CanExecuteScriptOnPage(url, NULL, NULL) && extension->CanCaptureVisiblePage(url, NULL)); } bool CaptureOnly(const Extension* extension, const GURL& url) { return !extension->CanExecuteScriptOnPage(url, NULL, NULL) && extension->CanCaptureVisiblePage(url, NULL); } bool Blocked(const Extension* extension, const GURL& url) { return !(extension->CanExecuteScriptOnPage(url, NULL, NULL) || extension->CanCaptureVisiblePage(url, NULL)); } protected: FilePath dirpath_; }; TEST_F(ExtensionScriptAndCaptureVisibleTest, Permissions) { scoped_refptr<Extension> extension; // URLs that are "safe" to provide scripting and capture visible tab access // to if the permissions allow it. GURL http_url("http://www.google.com"); GURL https_url("https://www.google.com"); GURL file_url("file:///foo/bar"); // We should allow host permission but not scripting permission for favicon // urls. GURL favicon_url("chrome://favicon/http://www.google.com"); std::string dummy_id = Extension::GenerateIdForPath(FilePath(FILE_PATH_LITERAL("whatever"))); // URLs that regular extensions should never get access to. GURL extension_url("chrome-extension://" + dummy_id); GURL settings_url("chrome://settings"); GURL about_url("about:flags"); // Test <all_urls> for regular extensions. extension = MakeExtension("['tabs','<all_urls>']", Extension::INTERNAL); EXPECT_TRUE(Allowed(extension, http_url)); EXPECT_TRUE(Allowed(extension, https_url)); EXPECT_TRUE(Blocked(extension, file_url)); EXPECT_TRUE(Blocked(extension, settings_url)); EXPECT_TRUE(CaptureOnly(extension, favicon_url)); EXPECT_TRUE(Blocked(extension, about_url)); EXPECT_TRUE(Blocked(extension, extension_url)); EXPECT_FALSE(extension->HasHostPermission(settings_url)); EXPECT_FALSE(extension->HasHostPermission(about_url)); EXPECT_TRUE(extension->HasHostPermission(favicon_url)); // Test * for scheme, which implies just the http/https schemes. extension = MakeExtension("['tabs','*://*/']", Extension::INTERNAL); EXPECT_TRUE(Allowed(extension, http_url)); EXPECT_TRUE(Allowed(extension, https_url)); EXPECT_TRUE(Blocked(extension, settings_url)); EXPECT_TRUE(Blocked(extension, about_url)); EXPECT_TRUE(Blocked(extension, file_url)); EXPECT_TRUE(Blocked(extension, favicon_url)); extension = MakeExtension("['tabs','*://settings/*']", Extension::INTERNAL); EXPECT_TRUE(Blocked(extension, settings_url)); // Having chrome://*/ should not work for regular extensions. Note that // for favicon access, we require the explicit pattern chrome://favicon/*. extension = MakeExtension("['tabs','chrome://*/']", Extension::INTERNAL); EXPECT_TRUE(extension == NULL); // Having chrome://favicon/* should not give you chrome://* extension = MakeExtension("['tabs','chrome://favicon/*']", Extension::INTERNAL); EXPECT_TRUE(Blocked(extension, settings_url)); EXPECT_TRUE(CaptureOnly(extension, favicon_url)); EXPECT_TRUE(Blocked(extension, about_url)); EXPECT_TRUE(extension->HasHostPermission(favicon_url)); // Having http://favicon should not give you chrome://favicon extension = MakeExtension("['tabs', 'http://favicon/']", Extension::INTERNAL); EXPECT_TRUE(Blocked(extension, settings_url)); EXPECT_TRUE(Blocked(extension, favicon_url)); // Component extensions with <all_urls> should get everything. extension = MakeExtension("['tabs','<all_urls>']", Extension::COMPONENT); EXPECT_TRUE(Allowed(extension, http_url)); EXPECT_TRUE(Allowed(extension, https_url)); EXPECT_TRUE(Allowed(extension, settings_url)); EXPECT_TRUE(Allowed(extension, about_url)); EXPECT_TRUE(Allowed(extension, favicon_url)); EXPECT_TRUE(extension->HasHostPermission(favicon_url)); // Component extensions should only get access to what they ask for. extension = MakeExtension("['tabs', 'http://www.google.com/']", Extension::COMPONENT); EXPECT_TRUE(Allowed(extension, http_url)); EXPECT_TRUE(Blocked(extension, https_url)); EXPECT_TRUE(Blocked(extension, file_url)); EXPECT_TRUE(Blocked(extension, settings_url)); EXPECT_TRUE(Blocked(extension, favicon_url)); EXPECT_TRUE(Blocked(extension, about_url)); EXPECT_TRUE(Blocked(extension, extension_url)); EXPECT_FALSE(extension->HasHostPermission(settings_url)); } TEST(ExtensionTest, GetDistinctHostsForDisplay) { std::vector<std::string> expected; expected.push_back("www.foo.com"); expected.push_back("www.bar.com"); expected.push_back("www.baz.com"); URLPatternList actual; { SCOPED_TRACE("no dupes"); // Simple list with no dupes. actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path")); CompareLists(expected, Extension::GetDistinctHostsForDisplay(actual)); } { SCOPED_TRACE("two dupes"); // Add some dupes. actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path")); CompareLists(expected, Extension::GetDistinctHostsForDisplay(actual)); } { SCOPED_TRACE("schemes differ"); // Add a pattern that differs only by scheme. This should be filtered out. actual.push_back( URLPattern(URLPattern::SCHEME_HTTPS, "https://www.bar.com/path")); CompareLists(expected, Extension::GetDistinctHostsForDisplay(actual)); } { SCOPED_TRACE("paths differ"); // Add some dupes by path. actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/pathypath")); CompareLists(expected, Extension::GetDistinctHostsForDisplay(actual)); } { SCOPED_TRACE("subdomains differ"); // We don't do anything special for subdomains. actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://monkey.www.bar.com/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://bar.com/path")); expected.push_back("monkey.www.bar.com"); expected.push_back("bar.com"); CompareLists(expected, Extension::GetDistinctHostsForDisplay(actual)); } { SCOPED_TRACE("RCDs differ"); // Now test for RCD uniquing. actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.de/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca.us/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com.my/path")); // This is an unknown RCD, which shouldn't be uniqued out. actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.xyzzy/path")); // But it should only occur once. actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.xyzzy/path")); expected.push_back("www.foo.xyzzy"); CompareLists(expected, Extension::GetDistinctHostsForDisplay(actual)); } { SCOPED_TRACE("wildcards"); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com/*")); expected.push_back("*.google.com"); CompareLists(expected, Extension::GetDistinctHostsForDisplay(actual)); } } TEST(ExtensionTest, GetDistinctHostsForDisplay_ComIsBestRcd) { URLPatternList actual; actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path")); std::vector<std::string> expected; expected.push_back("www.foo.com"); CompareLists(expected, Extension::GetDistinctHostsForDisplay(actual)); } TEST(ExtensionTest, GetDistinctHostsForDisplay_NetIs2ndBestRcd) { URLPatternList actual; actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path")); // No http://www.foo.com/path std::vector<std::string> expected; expected.push_back("www.foo.net"); CompareLists(expected, Extension::GetDistinctHostsForDisplay(actual)); } TEST(ExtensionTest, GetDistinctHostsForDisplay_OrgIs3rdBestRcd) { URLPatternList actual; actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path")); actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path")); // No http://www.foo.net/path actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path")); // No http://www.foo.com/path std::vector<std::string> expected; expected.push_back("www.foo.org"); CompareLists(expected, Extension::GetDistinctHostsForDisplay(actual)); } TEST(ExtensionTest, GetDistinctHostsForDisplay_FirstInListIs4thBestRcd) { URLPatternList actual; actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path")); // No http://www.foo.org/path actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path")); // No http://www.foo.net/path actual.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path")); // No http://www.foo.com/path std::vector<std::string> expected; expected.push_back("www.foo.ca"); CompareLists(expected, Extension::GetDistinctHostsForDisplay(actual)); } TEST(ExtensionTest, IsElevatedHostList) { URLPatternList list1; URLPatternList list2; list1.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path")); list1.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path")); // Test that the host order does not matter. list2.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path")); list2.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path")); EXPECT_FALSE(Extension::IsElevatedHostList(list1, list2)); EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1)); // Test that paths are ignored. list2.clear(); list2.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/*")); EXPECT_FALSE(Extension::IsElevatedHostList(list1, list2)); EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1)); // Test that RCDs are ignored. list2.clear(); list2.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/*")); EXPECT_FALSE(Extension::IsElevatedHostList(list1, list2)); EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1)); // Test that subdomain wildcards are handled properly. list2.clear(); list2.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com.hk/*")); EXPECT_TRUE(Extension::IsElevatedHostList(list1, list2)); //TODO(jstritar): Does not match subdomains properly. http://crbug.com/65337 //EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1)); // Test that different domains count as different hosts. list2.clear(); list2.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path")); list2.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://www.example.org/path")); EXPECT_TRUE(Extension::IsElevatedHostList(list1, list2)); EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1)); // Test that different subdomains count as different hosts. list2.clear(); list2.push_back( URLPattern(URLPattern::SCHEME_HTTP, "http://mail.google.com/*")); EXPECT_TRUE(Extension::IsElevatedHostList(list1, list2)); EXPECT_TRUE(Extension::IsElevatedHostList(list2, list1)); } TEST(ExtensionTest, GenerateId) { std::string result; EXPECT_TRUE(Extension::GenerateId("", &result)); EXPECT_TRUE(Extension::GenerateId("test", &result)); EXPECT_EQ(result, "jpignaibiiemhngfjkcpokkamffknabf"); EXPECT_TRUE(Extension::GenerateId("_", &result)); EXPECT_EQ(result, "ncocknphbhhlhkikpnnlmbcnbgdempcd"); EXPECT_TRUE(Extension::GenerateId( "this_string_is_longer_than_a_single_sha256_hash_digest", &result)); EXPECT_EQ(result, "jimneklojkjdibfkgiiophfhjhbdgcfi"); }