diff options
Diffstat (limited to 'chromium/chrome/common/extensions/extension_unittest.cc')
-rw-r--r-- | chromium/chrome/common/extensions/extension_unittest.cc | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/chromium/chrome/common/extensions/extension_unittest.cc b/chromium/chrome/common/extensions/extension_unittest.cc new file mode 100644 index 00000000000..09fe2826445 --- /dev/null +++ b/chromium/chrome/common/extensions/extension_unittest.cc @@ -0,0 +1,447 @@ +// 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 <stddef.h> + +#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<Manifest::Location>(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<const Extension> 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<const Extension> 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<const Extension> 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"); // <NOTE> Moved this here to avoid the MergeManifest call. + scoped_refptr<const Extension> 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> 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("mani<fest.json")), + FilePath(FILE_PATH_LITERAL("mani*fest.json")), + FilePath(FILE_PATH_LITERAL("mani:fest.json")), + FilePath(FILE_PATH_LITERAL("mani?fest.json")), + FilePath(FILE_PATH_LITERAL("mani|fest.json")), + // Reserved Characters without extension + FilePath(FILE_PATH_LITERAL("mani>fest")), + FilePath(FILE_PATH_LITERAL("mani<fest")), + FilePath(FILE_PATH_LITERAL("mani*fest")), + FilePath(FILE_PATH_LITERAL("mani:fest")), + FilePath(FILE_PATH_LITERAL("mani?fest")), + FilePath(FILE_PATH_LITERAL("mani|fest")), + // Reserved Names with extension. + FilePath(FILE_PATH_LITERAL("com1.json")), + FilePath(FILE_PATH_LITERAL("com9.json")), + FilePath(FILE_PATH_LITERAL("LPT1.json")), + FilePath(FILE_PATH_LITERAL("LPT9.json")), + FilePath(FILE_PATH_LITERAL("CON.json")), + FilePath(FILE_PATH_LITERAL("PRN.json")), + FilePath(FILE_PATH_LITERAL("AUX.json")), + FilePath(FILE_PATH_LITERAL("NUL.json")), + // Reserved Names without extension. + FilePath(FILE_PATH_LITERAL("com1")), + FilePath(FILE_PATH_LITERAL("com9")), + FilePath(FILE_PATH_LITERAL("LPT1")), + FilePath(FILE_PATH_LITERAL("LPT9")), + FilePath(FILE_PATH_LITERAL("CON")), + FilePath(FILE_PATH_LITERAL("PRN")), + FilePath(FILE_PATH_LITERAL("AUX")), + FilePath(FILE_PATH_LITERAL("NUL")), + // Reserved Names as directory. + FilePath(FILE_PATH_LITERAL("com1/manifest.json")), + FilePath(FILE_PATH_LITERAL("com9/manifest.json")), + FilePath(FILE_PATH_LITERAL("LPT1/manifest.json")), + FilePath(FILE_PATH_LITERAL("LPT9/manifest.json")), + FilePath(FILE_PATH_LITERAL("CON/manifest.json")), + FilePath(FILE_PATH_LITERAL("PRN/manifest.json")), + FilePath(FILE_PATH_LITERAL("AUX/manifest.json")), + FilePath(FILE_PATH_LITERAL("NUL/manifest.json")), + }; + + scoped_refptr<Extension> 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> extension = LoadManifestStrict("absolute_path", + "absolute.json"); + EXPECT_TRUE(extension.get()); + std::string err; + std::vector<InstallWarning> 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.c_str(), data.size(), 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> extension; + GURL file_url("file:///etc/passwd"); + + // Ignore the policy delegate for this test. + PermissionsData::SetPolicyDelegate(NULL); + + // <all_urls> 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)); + + // <all_urls> 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; + 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 |