| // Copyright 2017 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/browser/conflicts/enumerate_shell_extensions_win.h" |
| |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/files/file_path.h" |
| #include "base/path_service.h" |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/test/test_reg_util_win.h" |
| #include "chrome/browser/conflicts/module_info_util_win.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| class EnumerateShellExtensionsTest : public testing::Test { |
| public: |
| EnumerateShellExtensionsTest() = default; |
| ~EnumerateShellExtensionsTest() override = default; |
| |
| // Override all registry hives so that real shell extensions don't mess up |
| // the unit tests. |
| void SetUp() override { |
| ASSERT_NO_FATAL_FAILURE( |
| registry_override_manager_.OverrideRegistry(HKEY_CLASSES_ROOT)); |
| ASSERT_NO_FATAL_FAILURE( |
| registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER)); |
| ASSERT_NO_FATAL_FAILURE( |
| registry_override_manager_.OverrideRegistry(HKEY_LOCAL_MACHINE)); |
| } |
| |
| void RunUntilIdle() { scoped_task_environment_.RunUntilIdle(); } |
| |
| private: |
| base::test::ScopedTaskEnvironment scoped_task_environment_; |
| |
| registry_util::RegistryOverrideManager registry_override_manager_; |
| |
| DISALLOW_COPY_AND_ASSIGN(EnumerateShellExtensionsTest); |
| }; |
| |
| // Adds a fake shell extension entry to the registry that should be found by |
| // the ShellExtensionEnumerator. The call must be wrapped inside an |
| // ASSERT_NO_FATAL_FAILURE. |
| void RegisterFakeApprovedShellExtension(HKEY key, |
| const wchar_t* guid, |
| const wchar_t* path) { |
| base::win::RegKey class_id( |
| HKEY_CLASSES_ROOT, |
| base::StringPrintf(kClassIdRegistryKeyFormat, guid).c_str(), KEY_WRITE); |
| ASSERT_TRUE(class_id.Valid()); |
| |
| ASSERT_EQ(ERROR_SUCCESS, class_id.WriteValue(nullptr, path)); |
| |
| base::win::RegKey registration(key, kApprovedShellExtensionRegistryKey, |
| KEY_WRITE); |
| ASSERT_EQ(ERROR_SUCCESS, registration.WriteValue(guid, L"")); |
| } |
| |
| // Adds a fake shell extension entry to the registry that should be found by |
| // the ShellExtensionEnumerator. The call must be wrapped inside an |
| // ASSERT_NO_FATAL_FAILURE. |
| void RegisterFakeShellExtension(const wchar_t* guid, |
| const wchar_t* path, |
| const wchar_t* shell_extension_type, |
| const wchar_t* shell_object_type) { |
| base::win::RegKey class_id( |
| HKEY_CLASSES_ROOT, |
| base::StringPrintf(kClassIdRegistryKeyFormat, guid).c_str(), KEY_WRITE); |
| ASSERT_TRUE(class_id.Valid()); |
| |
| ASSERT_EQ(ERROR_SUCCESS, class_id.WriteValue(nullptr, path)); |
| |
| base::win::RegKey registration( |
| HKEY_CLASSES_ROOT, |
| base::StringPrintf(L"%ls\\shellex\\%ls\\&ls", shell_object_type, |
| shell_extension_type, guid) |
| .c_str(), |
| KEY_WRITE); |
| ASSERT_EQ(ERROR_SUCCESS, registration.WriteValue(nullptr, guid)); |
| } |
| |
| void OnShellExtensionPathEnumerated( |
| std::vector<base::FilePath>* shell_extension_paths, |
| const base::FilePath& path) { |
| shell_extension_paths->push_back(path); |
| } |
| |
| void OnShellExtensionEnumerated( |
| std::vector<base::FilePath>* shell_extension_paths, |
| const base::FilePath& path, |
| uint32_t size_of_image, |
| uint32_t time_date_stamp) { |
| shell_extension_paths->push_back(path); |
| } |
| |
| void OnEnumerationFinished(bool* is_enumeration_finished) { |
| *is_enumeration_finished = true; |
| } |
| |
| } // namespace |
| |
| // Registers a few fake approved shell extensions then see if |
| // EnumerateShellExtensionPaths() finds them. |
| TEST_F(EnumerateShellExtensionsTest, EnumerateApprovedShellExtensionPaths) { |
| struct { |
| HKEY hkey_root; |
| const wchar_t* guid; |
| const wchar_t* path; |
| } kTestCases[] = { |
| {HKEY_CURRENT_USER, L"{FAKE_GUID_0001}", L"c:\\module.dll"}, |
| {HKEY_LOCAL_MACHINE, L"{FAKE_GUID_0002}", L"c:\\dir\\shell_ext.dll"}, |
| {HKEY_LOCAL_MACHINE, L"{FAKE_GUID_0003}", L"c:\\path\\test.dll"}, |
| }; |
| |
| // Register all fake shell extensions in kTestCases. |
| for (const auto& test_case : kTestCases) { |
| ASSERT_NO_FATAL_FAILURE(RegisterFakeApprovedShellExtension( |
| test_case.hkey_root, test_case.guid, test_case.path)); |
| } |
| |
| std::vector<base::FilePath> shell_extension_paths; |
| internal::EnumerateShellExtensionPaths( |
| base::BindRepeating(&OnShellExtensionPathEnumerated, |
| base::Unretained(&shell_extension_paths))); |
| |
| ASSERT_EQ(3u, shell_extension_paths.size()); |
| for (size_t i = 0; i < base::size(kTestCases); i++) { |
| // The inefficiency is fine as long as the number of test cases stays small. |
| EXPECT_TRUE(base::ContainsValue(shell_extension_paths, |
| base::FilePath(kTestCases[i].path))); |
| } |
| } |
| |
| TEST_F(EnumerateShellExtensionsTest, EnumerateApprovedShellExtensions) { |
| // Use the current exe file as an arbitrary module that exists. |
| base::FilePath file_exe; |
| ASSERT_TRUE(base::PathService::Get(base::FILE_EXE, &file_exe)); |
| ASSERT_NO_FATAL_FAILURE(RegisterFakeApprovedShellExtension( |
| HKEY_LOCAL_MACHINE, L"{FAKE_GUID}", file_exe.value().c_str())); |
| |
| std::vector<base::FilePath> shell_extension_paths; |
| bool is_enumeration_finished = false; |
| EnumerateShellExtensions( |
| base::BindRepeating(&OnShellExtensionEnumerated, |
| base::Unretained(&shell_extension_paths)), |
| base::BindOnce(&OnEnumerationFinished, |
| base::Unretained(&is_enumeration_finished))); |
| |
| RunUntilIdle(); |
| |
| EXPECT_TRUE(is_enumeration_finished); |
| ASSERT_EQ(1u, shell_extension_paths.size()); |
| EXPECT_EQ(file_exe, shell_extension_paths[0]); |
| } |
| |
| TEST_F(EnumerateShellExtensionsTest, EnumerateShellExtensionPaths) { |
| struct { |
| const wchar_t* guid; |
| const wchar_t* path; |
| const wchar_t* shell_extension_type; |
| const wchar_t* shell_object_type; |
| } kTestCases[] = { |
| {L"{FAKE_GUID_0001}", L"c:\\module.dll", L"ColumnHandlers", L"Folder"}, |
| {L"{FAKE_GUID_0002}", L"c:\\dir\\shell_ext.dll", L"ContextMenuHandlers", |
| L"*"}, |
| {L"{FAKE_GUID_0003}", L"c:\\path\\test.dll", L"CopyHookHandlers", |
| L"Printers"}, |
| {L"{FAKE_GUID_0004}", L"c:\\foo\\bar.dll", L"DragDropHandlers", L"Drive"}, |
| {L"{FAKE_GUID_0005}", L"c:\\foo\\baz.dll", L"PropertySheetHandlers", |
| L"AllFileSystemObjects"}, |
| }; |
| |
| // Register all fake shell extensions in kTestCases. |
| for (const auto& test_case : kTestCases) { |
| ASSERT_NO_FATAL_FAILURE(RegisterFakeShellExtension( |
| test_case.guid, test_case.path, test_case.shell_extension_type, |
| test_case.shell_object_type)); |
| } |
| |
| std::vector<base::FilePath> shell_extension_paths; |
| internal::EnumerateShellExtensionPaths( |
| base::BindRepeating(&OnShellExtensionPathEnumerated, |
| base::Unretained(&shell_extension_paths))); |
| |
| ASSERT_EQ(5u, shell_extension_paths.size()); |
| for (size_t i = 0; i < base::size(kTestCases); ++i) { |
| // The inefficiency is fine as long as the number of test cases stays small. |
| EXPECT_TRUE(base::ContainsValue(shell_extension_paths, |
| base::FilePath(kTestCases[i].path))); |
| } |
| } |