// Copyright (c) 2013 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 #include "base/files/file_util.h" #include "base/format_macros.h" #include "base/path_service.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/extensions/command.h" #include "chrome/common/extensions/extension_test_util.h" #include "chrome/common/url_constants.h" #include "chrome/grit/generated_resources.h" #include "components/crx_file/id_util.h" #include "extensions/common/extension.h" #include "extensions/common/extension_builder.h" #include "extensions/common/extension_resource.h" #include "extensions/common/file_util.h" #include "extensions/common/manifest.h" #include "extensions/common/manifest_handlers/content_scripts_handler.h" #include "extensions/common/permissions/permissions_data.h" #include "extensions/common/value_builder.h" #include "extensions/test/test_extension_dir.h" #include "net/base/mime_sniffer.h" #include "net/dns/mock_host_resolver.h" #include "skia/ext/image_operations.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" #include "url/gurl.h" using extension_test_util::LoadManifest; using extension_test_util::LoadManifestStrict; using base::FilePath; namespace extensions { // 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, Manifest::INVALID_LOCATION); ASSERT_EQ(1, Manifest::INTERNAL); ASSERT_EQ(2, Manifest::EXTERNAL_PREF); ASSERT_EQ(3, Manifest::EXTERNAL_REGISTRY); ASSERT_EQ(4, Manifest::UNPACKED); ASSERT_EQ(5, Manifest::COMPONENT); ASSERT_EQ(6, Manifest::EXTERNAL_PREF_DOWNLOAD); ASSERT_EQ(7, Manifest::EXTERNAL_POLICY_DOWNLOAD); ASSERT_EQ(8, Manifest::COMMAND_LINE); ASSERT_EQ(9, Manifest::EXTERNAL_POLICY); } TEST(ExtensionTest, LocationPriorityTest) { for (int i = 0; i < Manifest::NUM_LOCATIONS; i++) { Manifest::Location loc = static_cast(i); // INVALID is not a valid location. if (loc == Manifest::INVALID_LOCATION) 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(Manifest::COMPONENT, Manifest::GetHigherPriorityLocation(Manifest::COMPONENT, loc)); ASSERT_EQ(Manifest::COMPONENT, Manifest::GetHigherPriorityLocation(loc, Manifest::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, Manifest::GetHigherPriorityLocation(Manifest::INTERNAL, loc)); ASSERT_EQ(loc, Manifest::GetHigherPriorityLocation(loc, Manifest::INTERNAL)); } // Check a few interesting cases that we know can happen: ASSERT_EQ(Manifest::EXTERNAL_POLICY_DOWNLOAD, Manifest::GetHigherPriorityLocation( Manifest::EXTERNAL_POLICY_DOWNLOAD, Manifest::EXTERNAL_PREF)); ASSERT_EQ(Manifest::EXTERNAL_PREF, Manifest::GetHigherPriorityLocation( Manifest::INTERNAL, Manifest::EXTERNAL_PREF)); } TEST(ExtensionTest, EnsureNewLinesInExtensionNameAreCollapsed) { DictionaryBuilder manifest; std::string unsanitized_name = "Test\n\n\n\n\n\n\n\n\n\n\n\nNew lines\u0085"; manifest.Set("name", unsanitized_name) .Set("manifest_version", 2) .Set("description", "some description"); scoped_refptr extension = ExtensionBuilder() .SetManifest(manifest.Build()) .MergeManifest(DictionaryBuilder().Set("version", "0.1").Build()) .Build(); ASSERT_TRUE(extension.get()); EXPECT_EQ("TestNew lines", extension->name()); // Ensure that non-localized name is not sanitized. EXPECT_EQ(unsanitized_name, extension->non_localized_name()); } TEST(ExtensionTest, EnsureWhitespacesInExtensionNameAreCollapsed) { DictionaryBuilder manifest; std::string unsanitized_name = "Test Whitespace"; manifest.Set("name", unsanitized_name) .Set("manifest_version", 2) .Set("description", "some description"); scoped_refptr extension = ExtensionBuilder() .SetManifest(manifest.Build()) .MergeManifest(DictionaryBuilder().Set("version", "0.1").Build()) .Build(); ASSERT_TRUE(extension.get()); EXPECT_EQ("Test Whitespace", extension->name()); // Ensure that non-localized name is not sanitized. EXPECT_EQ(unsanitized_name, extension->non_localized_name()); } // TODO(crbug.com/794252): Disallow empty extension names from being locally // loaded. TEST(ExtensionTest, EmptyName) { DictionaryBuilder manifest1; manifest1.Set("name", "") .Set("manifest_version", 2) .Set("description", "some description"); scoped_refptr extension = ExtensionBuilder() .SetManifest(manifest1.Build()) .MergeManifest(DictionaryBuilder().Set("version", "0.1").Build()) .Build(); ASSERT_TRUE(extension.get()); EXPECT_EQ("", extension->name()); DictionaryBuilder manifest2; manifest2.Set("name", " ") .Set("manifest_version", 2) .Set("description", "some description"); extension = ExtensionBuilder() .SetManifest(manifest2.Build()) .MergeManifest(DictionaryBuilder().Set("version", "0.1").Build()) .Build(); ASSERT_TRUE(extension.get()); EXPECT_EQ("", extension->name()); } TEST(ExtensionTest, RTLNameInLTRLocale) { // Test the case when a directional override is the first character. auto run_rtl_test = [](const wchar_t* name, const wchar_t* expected) { SCOPED_TRACE( base::StringPrintf("Name: %ls, Expected: %ls", name, expected)); DictionaryBuilder manifest; manifest.Set("name", base::WideToUTF8(name)) .Set("manifest_version", 2) .Set("description", "some description") .Set("version", "0.1"); // Moved this here to avoid the MergeManifest call. scoped_refptr extension = ExtensionBuilder().SetManifest(manifest.Build()).Build(); ASSERT_TRUE(extension); const int kResourceId = IDS_EXTENSION_PERMISSIONS_PROMPT_TITLE; const base::string16 expected_utf16 = base::WideToUTF16(expected); EXPECT_EQ(l10n_util::GetStringFUTF16(kResourceId, expected_utf16), l10n_util::GetStringFUTF16(kResourceId, base::UTF8ToUTF16(extension->name()))); EXPECT_EQ(base::WideToUTF8(expected), extension->name()); }; run_rtl_test(L"\x202emoc.elgoog", L"\x202emoc.elgoog\x202c"); run_rtl_test(L"\x202egoogle\x202e.com/\x202eguest", L"\x202egoogle\x202e.com/\x202eguest\x202c\x202c\x202c"); run_rtl_test(L"google\x202e.com", L"google\x202e.com\x202c"); run_rtl_test(L"كبير Google التطبيق", #if !defined(OS_WIN) L"\x200e\x202bكبير Google التطبيق\x202c\x200e"); #else // On Windows for an LTR locale, no changes to the string are // made. L"كبير Google التطبيق"); #endif // !OS_WIN } TEST(ExtensionTest, GetResourceURLAndPath) { scoped_refptr extension = LoadManifestStrict("empty_manifest", "empty.json"); 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 that absolute-looking paths ("/"-prefixed) are pasted correctly. EXPECT_EQ(extension->url().spec() + "test.html", extension->GetResourceURL("/test.html").spec()); } TEST(ExtensionTest, GetResource) { const FilePath valid_path_test_cases[] = { FilePath(FILE_PATH_LITERAL("manifest.json")), FilePath(FILE_PATH_LITERAL("a/b/c/manifest.json")), FilePath(FILE_PATH_LITERAL("com/manifest.json")), FilePath(FILE_PATH_LITERAL("lpt/manifest.json")), }; const FilePath invalid_path_test_cases[] = { // Directory name FilePath(FILE_PATH_LITERAL("src/")), // Contains a drive letter specification. FilePath(FILE_PATH_LITERAL("C:\\manifest.json")), // Use backslash '\\' as separator. FilePath(FILE_PATH_LITERAL("a\\b\\c\\manifest.json")), // Reserved Characters with extension FilePath(FILE_PATH_LITERAL("mani>fest.json")), FilePath(FILE_PATH_LITERAL("manifest")), FilePath(FILE_PATH_LITERAL("mani extension = LoadManifestStrict("empty_manifest", "empty.json"); EXPECT_TRUE(extension.get()); for (size_t i = 0; i < base::size(valid_path_test_cases); ++i) EXPECT_TRUE(!extension->GetResource(valid_path_test_cases[i]).empty()); for (size_t i = 0; i < base::size(invalid_path_test_cases); ++i) EXPECT_TRUE(extension->GetResource(invalid_path_test_cases[i]).empty()); } TEST(ExtensionTest, GetAbsolutePathNoError) { scoped_refptr extension = LoadManifestStrict("absolute_path", "absolute.json"); EXPECT_TRUE(extension.get()); std::string err; std::vector warnings; EXPECT_TRUE(file_util::ValidateExtension(extension.get(), &err, &warnings)); EXPECT_EQ(0U, warnings.size()); EXPECT_EQ(extension->path().AppendASCII("test.html").value(), extension->GetResource("test.html").GetFilePath().value()); EXPECT_EQ(extension->path().AppendASCII("test.js").value(), extension->GetResource("test.js").GetFilePath().value()); } TEST(ExtensionTest, IdIsValid) { EXPECT_TRUE(crx_file::id_util::IdIsValid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); EXPECT_TRUE(crx_file::id_util::IdIsValid("pppppppppppppppppppppppppppppppp")); EXPECT_TRUE(crx_file::id_util::IdIsValid("abcdefghijklmnopabcdefghijklmnop")); EXPECT_TRUE(crx_file::id_util::IdIsValid("ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP")); EXPECT_FALSE(crx_file::id_util::IdIsValid("abcdefghijklmnopabcdefghijklmno")); EXPECT_FALSE( crx_file::id_util::IdIsValid("abcdefghijklmnopabcdefghijklmnopa")); EXPECT_FALSE( crx_file::id_util::IdIsValid("0123456789abcdef0123456789abcdef")); EXPECT_FALSE( crx_file::id_util::IdIsValid("abcdefghijklmnopabcdefghijklmnoq")); EXPECT_FALSE( crx_file::id_util::IdIsValid("abcdefghijklmnopabcdefghijklmno0")); } // 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) { auto get_mime_type_from_crx = [](const base::FilePath& file_path) { SCOPED_TRACE(file_path.AsUTF8Unsafe()); std::string data; EXPECT_TRUE(base::ReadFileToString(file_path, &data)); std::string result; EXPECT_TRUE(net::SniffMimeType( data, GURL("http://www.example.com/foo.crx"), std::string(), net::ForceSniffFileUrlsForHtml::kDisabled, &result)); return result; }; base::FilePath dir_path; ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &dir_path)); dir_path = dir_path.AppendASCII("extensions"); // First, test an extension packed a long time ago (but in this galaxy). // Specifically, this package is using the crx2 format, whereas modern chrome // uses crx3. EXPECT_EQ( Extension::kMimeType, get_mime_type_from_crx(dir_path.AppendASCII("legacy_crx_package.crx"))); // Then, an extension whose crx has a bad magic number (it should be Cr24). EXPECT_EQ("application/octet-stream", get_mime_type_from_crx(dir_path.AppendASCII("bad_magic.crx"))); // Finally, an extension that we pack right. This. Instant. // This verifies that the modern extensions Chrome packs are always // recognized as the extension mime type. // Regression test for https://crbug.com/831284. TestExtensionDir test_dir; test_dir.WriteManifest(R"( { "name": "New extension", "version": "0.2", "manifest_version": 2 })"); EXPECT_EQ(Extension::kMimeType, get_mime_type_from_crx(test_dir.Pack())); } TEST(ExtensionTest, WantsFileAccess) { scoped_refptr extension; GURL file_url("file:///etc/passwd"); // Ignore the policy delegate for this test. PermissionsData::SetPolicyDelegate(NULL); // permission extension = LoadManifest("permissions", "permissions_all_urls.json"); EXPECT_TRUE(extension->wants_file_access()); EXPECT_FALSE( extension->permissions_data()->CanAccessPage(file_url, -1, nullptr)); extension = LoadManifest( "permissions", "permissions_all_urls.json", Extension::ALLOW_FILE_ACCESS); EXPECT_TRUE(extension->wants_file_access()); EXPECT_TRUE( extension->permissions_data()->CanAccessPage(file_url, -1, nullptr)); // file:///* permission extension = LoadManifest("permissions", "permissions_file_scheme.json"); EXPECT_TRUE(extension->wants_file_access()); EXPECT_FALSE( extension->permissions_data()->CanAccessPage(file_url, -1, nullptr)); extension = LoadManifest("permissions", "permissions_file_scheme.json", Extension::ALLOW_FILE_ACCESS); EXPECT_TRUE(extension->wants_file_access()); EXPECT_TRUE( extension->permissions_data()->CanAccessPage(file_url, -1, nullptr)); // http://* permission extension = LoadManifest("permissions", "permissions_http_scheme.json"); EXPECT_FALSE(extension->wants_file_access()); EXPECT_FALSE( extension->permissions_data()->CanAccessPage(file_url, -1, nullptr)); extension = LoadManifest("permissions", "permissions_http_scheme.json", Extension::ALLOW_FILE_ACCESS); EXPECT_FALSE(extension->wants_file_access()); EXPECT_FALSE( extension->permissions_data()->CanAccessPage(file_url, -1, nullptr)); // content script match extension = LoadManifest("permissions", "content_script_all_urls.json"); EXPECT_TRUE(extension->wants_file_access()); EXPECT_FALSE(extension->permissions_data()->CanRunContentScriptOnPage( file_url, -1, nullptr)); extension = LoadManifest("permissions", "content_script_all_urls.json", Extension::ALLOW_FILE_ACCESS); EXPECT_TRUE(extension->wants_file_access()); EXPECT_TRUE(extension->permissions_data()->CanRunContentScriptOnPage( file_url, -1, nullptr)); // file:///* content script match extension = LoadManifest("permissions", "content_script_file_scheme.json"); EXPECT_TRUE(extension->wants_file_access()); EXPECT_FALSE(extension->permissions_data()->CanRunContentScriptOnPage( file_url, -1, nullptr)); extension = LoadManifest("permissions", "content_script_file_scheme.json", Extension::ALLOW_FILE_ACCESS); EXPECT_TRUE(extension->wants_file_access()); EXPECT_TRUE(extension->permissions_data()->CanRunContentScriptOnPage( file_url, -1, nullptr)); // http://* content script match extension = LoadManifest("permissions", "content_script_http_scheme.json"); EXPECT_FALSE(extension->wants_file_access()); EXPECT_FALSE(extension->permissions_data()->CanRunContentScriptOnPage( file_url, -1, nullptr)); extension = LoadManifest("permissions", "content_script_http_scheme.json", Extension::ALLOW_FILE_ACCESS); EXPECT_FALSE(extension->wants_file_access()); EXPECT_FALSE(extension->permissions_data()->CanRunContentScriptOnPage( file_url, -1, nullptr)); } TEST(ExtensionTest, ExtraFlags) { scoped_refptr extension; extension = LoadManifest("app", "manifest.json", Extension::FROM_WEBSTORE); EXPECT_TRUE(extension->from_webstore()); extension = LoadManifest("app", "manifest.json", Extension::FROM_BOOKMARK); EXPECT_TRUE(extension->from_bookmark()); extension = LoadManifest("app", "manifest.json", Extension::NO_FLAGS); EXPECT_FALSE(extension->from_bookmark()); EXPECT_FALSE(extension->from_webstore()); } } // namespace extensions