| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/installer/util/registry_util.h" |
| |
| #include <iterator> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/base_paths.h" |
| #include "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/path_service.h" |
| #include "base/strings/string_util.h" |
| #include "base/test/scoped_path_override.h" |
| #include "base/test/test_reg_util_win.h" |
| #include "base/win/registry.h" |
| #include "build/branding_buildflags.h" |
| #include "chrome/install_static/install_util.h" |
| #include "chrome/installer/util/google_update_constants.h" |
| #include "chrome/installer/util/install_util.h" |
| #include "chrome/installer/util/work_item.h" |
| #include "chrome/installer/util/work_item_list.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using base::win::RegKey; |
| using ::testing::_; |
| using ::testing::Not; |
| using ::testing::Return; |
| using ::testing::StrEq; |
| |
| namespace installer { |
| |
| class MockRegistryValuePredicate : public RegistryValuePredicate { |
| public: |
| MOCK_CONST_METHOD1(Evaluate, bool(const std::wstring&)); |
| }; |
| |
| class RegistryUtilTest : public testing::Test { |
| public: |
| RegistryUtilTest(const RegistryUtilTest&) = delete; |
| RegistryUtilTest& operator=(const RegistryUtilTest&) = delete; |
| |
| protected: |
| RegistryUtilTest() = default; |
| |
| void SetUp() override { ASSERT_NO_FATAL_FAILURE(ResetRegistryOverrides()); } |
| |
| void ResetRegistryOverrides() { |
| registry_override_manager_ = |
| std::make_unique<registry_util::RegistryOverrideManager>(); |
| ASSERT_NO_FATAL_FAILURE( |
| registry_override_manager_->OverrideRegistry(HKEY_CURRENT_USER)); |
| ASSERT_NO_FATAL_FAILURE( |
| registry_override_manager_->OverrideRegistry(HKEY_LOCAL_MACHINE)); |
| } |
| |
| private: |
| std::unique_ptr<registry_util::RegistryOverrideManager> |
| registry_override_manager_; |
| }; |
| |
| TEST_F(RegistryUtilTest, DeleteRegistryKeyIf) { |
| const HKEY root = HKEY_CURRENT_USER; |
| std::wstring parent_key_path(L"SomeKey\\ToDelete"); |
| std::wstring child_key_path(parent_key_path); |
| child_key_path.append(L"\\ChildKey\\WithAValue"); |
| const wchar_t value_name[] = L"some_value_name"; |
| const wchar_t value[] = L"hi mom"; |
| |
| // Nothing to delete if the keys aren't even there. |
| { |
| MockRegistryValuePredicate pred; |
| |
| EXPECT_CALL(pred, Evaluate(_)).Times(0); |
| ASSERT_FALSE( |
| RegKey(root, parent_key_path.c_str(), KEY_QUERY_VALUE).Valid()); |
| EXPECT_EQ(ConditionalDeleteResult::NOT_FOUND, |
| DeleteRegistryKeyIf(root, parent_key_path, child_key_path, |
| WorkItem::kWow64Default, value_name, pred)); |
| EXPECT_FALSE( |
| RegKey(root, parent_key_path.c_str(), KEY_QUERY_VALUE).Valid()); |
| } |
| |
| // Parent exists, but not child: no delete. |
| { |
| MockRegistryValuePredicate pred; |
| |
| EXPECT_CALL(pred, Evaluate(_)).Times(0); |
| ASSERT_TRUE(RegKey(root, parent_key_path.c_str(), KEY_SET_VALUE).Valid()); |
| EXPECT_EQ(ConditionalDeleteResult::NOT_FOUND, |
| DeleteRegistryKeyIf(root, parent_key_path, child_key_path, |
| WorkItem::kWow64Default, value_name, pred)); |
| EXPECT_TRUE(RegKey(root, parent_key_path.c_str(), KEY_QUERY_VALUE).Valid()); |
| } |
| |
| // Child exists, but no value: no delete. |
| { |
| MockRegistryValuePredicate pred; |
| |
| EXPECT_CALL(pred, Evaluate(_)).Times(0); |
| ASSERT_TRUE(RegKey(root, child_key_path.c_str(), KEY_SET_VALUE).Valid()); |
| EXPECT_EQ(ConditionalDeleteResult::NOT_FOUND, |
| DeleteRegistryKeyIf(root, parent_key_path, child_key_path, |
| WorkItem::kWow64Default, value_name, pred)); |
| EXPECT_TRUE(RegKey(root, parent_key_path.c_str(), KEY_QUERY_VALUE).Valid()); |
| } |
| |
| // Value exists, but doesn't match: no delete. |
| { |
| MockRegistryValuePredicate pred; |
| |
| EXPECT_CALL(pred, Evaluate(StrEq(L"foosball!"))).WillOnce(Return(false)); |
| ASSERT_EQ(ERROR_SUCCESS, RegKey(root, child_key_path.c_str(), KEY_SET_VALUE) |
| .WriteValue(value_name, L"foosball!")); |
| EXPECT_EQ(ConditionalDeleteResult::NOT_FOUND, |
| DeleteRegistryKeyIf(root, parent_key_path, child_key_path, |
| WorkItem::kWow64Default, value_name, pred)); |
| EXPECT_TRUE(RegKey(root, parent_key_path.c_str(), KEY_QUERY_VALUE).Valid()); |
| } |
| |
| // Value exists, and matches: delete. |
| { |
| MockRegistryValuePredicate pred; |
| |
| EXPECT_CALL(pred, Evaluate(StrEq(value))).WillOnce(Return(true)); |
| ASSERT_EQ(ERROR_SUCCESS, RegKey(root, child_key_path.c_str(), KEY_SET_VALUE) |
| .WriteValue(value_name, value)); |
| EXPECT_EQ(ConditionalDeleteResult::DELETED, |
| DeleteRegistryKeyIf(root, parent_key_path, child_key_path, |
| WorkItem::kWow64Default, value_name, pred)); |
| EXPECT_FALSE( |
| RegKey(root, parent_key_path.c_str(), KEY_QUERY_VALUE).Valid()); |
| } |
| |
| // Default value exists and matches: delete. |
| { |
| MockRegistryValuePredicate pred; |
| |
| EXPECT_CALL(pred, Evaluate(StrEq(value))).WillOnce(Return(true)); |
| ASSERT_EQ(ERROR_SUCCESS, RegKey(root, child_key_path.c_str(), KEY_SET_VALUE) |
| .WriteValue(nullptr, value)); |
| EXPECT_EQ(ConditionalDeleteResult::DELETED, |
| DeleteRegistryKeyIf(root, parent_key_path, child_key_path, |
| WorkItem::kWow64Default, nullptr, pred)); |
| EXPECT_FALSE( |
| RegKey(root, parent_key_path.c_str(), KEY_QUERY_VALUE).Valid()); |
| } |
| } |
| |
| TEST_F(RegistryUtilTest, DeleteRegistryValueIf) { |
| const HKEY root = HKEY_CURRENT_USER; |
| std::wstring key_path(L"SomeKey\\ToDelete"); |
| const wchar_t value_name[] = L"some_value_name"; |
| const wchar_t value[] = L"hi mom"; |
| |
| { |
| ASSERT_NO_FATAL_FAILURE(ResetRegistryOverrides()); |
| // Nothing to delete if the key isn't even there. |
| { |
| MockRegistryValuePredicate pred; |
| |
| EXPECT_CALL(pred, Evaluate(_)).Times(0); |
| ASSERT_FALSE(RegKey(root, key_path.c_str(), KEY_QUERY_VALUE).Valid()); |
| EXPECT_EQ( |
| ConditionalDeleteResult::NOT_FOUND, |
| DeleteRegistryValueIf(root, key_path.c_str(), WorkItem::kWow64Default, |
| value_name, pred)); |
| EXPECT_FALSE(RegKey(root, key_path.c_str(), KEY_QUERY_VALUE).Valid()); |
| } |
| |
| // Key exists, but no value: no delete. |
| { |
| MockRegistryValuePredicate pred; |
| |
| EXPECT_CALL(pred, Evaluate(_)).Times(0); |
| ASSERT_TRUE(RegKey(root, key_path.c_str(), KEY_SET_VALUE).Valid()); |
| EXPECT_EQ( |
| ConditionalDeleteResult::NOT_FOUND, |
| DeleteRegistryValueIf(root, key_path.c_str(), WorkItem::kWow64Default, |
| value_name, pred)); |
| EXPECT_TRUE(RegKey(root, key_path.c_str(), KEY_QUERY_VALUE).Valid()); |
| } |
| |
| // Value exists, but doesn't match: no delete. |
| { |
| MockRegistryValuePredicate pred; |
| |
| EXPECT_CALL(pred, Evaluate(StrEq(L"foosball!"))).WillOnce(Return(false)); |
| ASSERT_EQ(ERROR_SUCCESS, RegKey(root, key_path.c_str(), KEY_SET_VALUE) |
| .WriteValue(value_name, L"foosball!")); |
| EXPECT_EQ( |
| ConditionalDeleteResult::NOT_FOUND, |
| DeleteRegistryValueIf(root, key_path.c_str(), WorkItem::kWow64Default, |
| value_name, pred)); |
| EXPECT_TRUE(RegKey(root, key_path.c_str(), KEY_QUERY_VALUE).Valid()); |
| EXPECT_TRUE( |
| RegKey(root, key_path.c_str(), KEY_QUERY_VALUE).HasValue(value_name)); |
| } |
| |
| // Value exists, and matches: delete. |
| { |
| MockRegistryValuePredicate pred; |
| |
| EXPECT_CALL(pred, Evaluate(StrEq(value))).WillOnce(Return(true)); |
| ASSERT_EQ(ERROR_SUCCESS, RegKey(root, key_path.c_str(), KEY_SET_VALUE) |
| .WriteValue(value_name, value)); |
| EXPECT_EQ( |
| ConditionalDeleteResult::DELETED, |
| DeleteRegistryValueIf(root, key_path.c_str(), WorkItem::kWow64Default, |
| value_name, pred)); |
| EXPECT_TRUE(RegKey(root, key_path.c_str(), KEY_QUERY_VALUE).Valid()); |
| EXPECT_FALSE( |
| RegKey(root, key_path.c_str(), KEY_QUERY_VALUE).HasValue(value_name)); |
| } |
| } |
| |
| { |
| ASSERT_NO_FATAL_FAILURE(ResetRegistryOverrides()); |
| // Default value matches: delete using empty string. |
| { |
| MockRegistryValuePredicate pred; |
| |
| EXPECT_CALL(pred, Evaluate(StrEq(value))).WillOnce(Return(true)); |
| ASSERT_EQ( |
| ERROR_SUCCESS, |
| RegKey(root, key_path.c_str(), KEY_SET_VALUE).WriteValue(L"", value)); |
| EXPECT_EQ(ConditionalDeleteResult::DELETED, |
| DeleteRegistryValueIf(root, key_path.c_str(), |
| WorkItem::kWow64Default, L"", pred)); |
| EXPECT_TRUE(RegKey(root, key_path.c_str(), KEY_QUERY_VALUE).Valid()); |
| EXPECT_FALSE( |
| RegKey(root, key_path.c_str(), KEY_QUERY_VALUE).HasValue(L"")); |
| } |
| } |
| |
| { |
| ASSERT_NO_FATAL_FAILURE(ResetRegistryOverrides()); |
| // Default value matches: delete using nullptr. |
| { |
| MockRegistryValuePredicate pred; |
| |
| EXPECT_CALL(pred, Evaluate(StrEq(value))).WillOnce(Return(true)); |
| ASSERT_EQ( |
| ERROR_SUCCESS, |
| RegKey(root, key_path.c_str(), KEY_SET_VALUE).WriteValue(L"", value)); |
| EXPECT_EQ(ConditionalDeleteResult::DELETED, |
| DeleteRegistryValueIf(root, key_path.c_str(), |
| WorkItem::kWow64Default, nullptr, pred)); |
| EXPECT_TRUE(RegKey(root, key_path.c_str(), KEY_QUERY_VALUE).Valid()); |
| EXPECT_FALSE( |
| RegKey(root, key_path.c_str(), KEY_QUERY_VALUE).HasValue(L"")); |
| } |
| } |
| } |
| |
| TEST_F(RegistryUtilTest, ValueEquals) { |
| ValueEquals pred(L"howdy"); |
| |
| EXPECT_FALSE(pred.Evaluate(L"")); |
| EXPECT_FALSE(pred.Evaluate(L"Howdy")); |
| EXPECT_FALSE(pred.Evaluate(L"howdy!")); |
| EXPECT_FALSE(pred.Evaluate(L"!howdy")); |
| EXPECT_TRUE(pred.Evaluate(L"howdy")); |
| } |
| |
| // A matcher that returns true if its argument (a string) matches a given |
| // base::FilePath. |
| MATCHER_P(EqPathIgnoreCase, value, "") { |
| return base::FilePath::CompareEqualIgnoreCase(arg, value.value()); |
| } |
| |
| TEST_F(RegistryUtilTest, ProgramCompare) { |
| base::ScopedTempDir test_dir; |
| ASSERT_TRUE(test_dir.CreateUniqueTempDir()); |
| const base::FilePath some_long_dir( |
| test_dir.GetPath().Append(L"Some Long Directory Name")); |
| const base::FilePath expect(some_long_dir.Append(L"file.txt")); |
| const base::FilePath expect_upcase(some_long_dir.Append(L"FILE.txt")); |
| const base::FilePath other(some_long_dir.Append(L"otherfile.txt")); |
| |
| // Tests where the expected file doesn't exist. |
| |
| // Paths don't match. |
| EXPECT_FALSE(ProgramCompare(expect).Evaluate(L"\"" + other.value() + L"\"")); |
| // Paths match exactly. |
| EXPECT_TRUE(ProgramCompare(expect).Evaluate(L"\"" + expect.value() + L"\"")); |
| // Paths differ by case. |
| EXPECT_TRUE( |
| ProgramCompare(expect).Evaluate(L"\"" + expect_upcase.value() + L"\"")); |
| |
| // Tests where the expected file exists. |
| static const char data[] = "data"; |
| ASSERT_TRUE(base::CreateDirectory(some_long_dir)); |
| ASSERT_TRUE(base::WriteFile(expect, data)); |
| // Paths don't match. |
| EXPECT_FALSE(ProgramCompare(expect).Evaluate(L"\"" + other.value() + L"\"")); |
| // Paths match exactly. |
| EXPECT_TRUE(ProgramCompare(expect).Evaluate(L"\"" + expect.value() + L"\"")); |
| // Paths differ by case. |
| EXPECT_TRUE( |
| ProgramCompare(expect).Evaluate(L"\"" + expect_upcase.value() + L"\"")); |
| |
| // Test where strings don't match, but the same file is indicated. |
| std::wstring short_expect; |
| DWORD short_len = |
| GetShortPathName(expect.value().c_str(), |
| base::WriteInto(&short_expect, MAX_PATH), MAX_PATH); |
| ASSERT_NE(static_cast<DWORD>(0), short_len); |
| ASSERT_GT(static_cast<DWORD>(MAX_PATH), short_len); |
| short_expect.resize(short_len); |
| // GetShortPathName may return the original path in case there is no short |
| // form. Only perform the last expectation if the short form was found. |
| if (!base::FilePath::CompareEqualIgnoreCase(expect.value(), short_expect)) { |
| EXPECT_TRUE(ProgramCompare(expect).Evaluate(L"\"" + short_expect + L"\"")); |
| } |
| } |
| |
| } // namespace installer |