/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmConfigure.h" // IWYU pragma: keep #include "cmWindowsRegistry.h" #include #include #include #include #include #include #include "cmsys/RegularExpression.hxx" #if defined(_WIN32) && !defined(__CYGWIN__) # include # include # include # include # include # include # include # include "cmMakefile.h" # include "cmStringAlgorithms.h" # include "cmValue.h" #endif namespace { // Case-independent string comparison int Strucmp(cm::string_view l, cm::string_view r) { if (l.empty() && r.empty()) { return 0; } if (l.empty() || r.empty()) { return static_cast(l.size() - r.size()); } int lc; int rc; cm::string_view::size_type li = 0; cm::string_view::size_type ri = 0; do { lc = std::tolower(l[li++]); rc = std::tolower(r[ri++]); } while (lc == rc && li < l.size() && ri < r.size()); return lc == rc ? static_cast(l.size() - r.size()) : lc - rc; } #if defined(_WIN32) && !defined(__CYGWIN__) bool Is64BitWindows() { # if defined(_WIN64) // 64-bit programs run only on Win64 return true; # else // 32-bit programs run on both 32-bit and 64-bit Windows, so we must check. BOOL isWow64 = false; return IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64; # endif } // Helper to translate Windows registry value type to enum ValueType cm::optional ToValueType(DWORD type) { using ValueType = cmWindowsRegistry::ValueType; static std::unordered_map ValueTypes{ { REG_SZ, ValueType::Reg_SZ }, { REG_EXPAND_SZ, ValueType::Reg_EXPAND_SZ }, { REG_MULTI_SZ, ValueType::Reg_MULTI_SZ }, { REG_DWORD, ValueType::Reg_DWORD }, { REG_QWORD, ValueType::Reg_QWORD } }; auto it = ValueTypes.find(type); return it == ValueTypes.end() ? cm::nullopt : cm::optional{ it->second }; } // class registry_exception class registry_error : public std::exception { public: registry_error(std::string msg) : What(std::move(msg)) { } ~registry_error() override = default; const char* what() const noexcept override { return What.c_str(); } private: std::string What; }; // Class KeyHandler class KeyHandler { public: using View = cmWindowsRegistry::View; using ValueTypeSet = cmWindowsRegistry::ValueTypeSet; KeyHandler(HKEY hkey) : Handler(hkey) { } ~KeyHandler() { RegCloseKey(this->Handler); } static KeyHandler OpenKey(cm::string_view rootKey, cm::string_view subKey, View view); static KeyHandler OpenKey(cm::string_view key, View view); std::string ReadValue( cm::string_view name, ValueTypeSet supportedTypes = cmWindowsRegistry::AllTypes, cm::string_view separator = "\0"_s); std::vector GetValueNames(); std::vector GetSubKeys(); private: static std::string FormatSystemError(LSTATUS status); static std::wstring ToWide(cm::string_view str); static std::string ToNarrow(const wchar_t* str, int size = -1); HKEY Handler; }; KeyHandler KeyHandler::OpenKey(cm::string_view rootKey, cm::string_view subKey, View view) { if (view == View::Reg64 && !Is64BitWindows()) { throw registry_error("No 64bit registry on Windows32."); } HKEY hRootKey; if (rootKey == "HKCU"_s || rootKey == "HKEY_CURRENT_USER"_s) { hRootKey = HKEY_CURRENT_USER; } else if (rootKey == "HKLM"_s || rootKey == "HKEY_LOCAL_MACHINE"_s) { hRootKey = HKEY_LOCAL_MACHINE; } else if (rootKey == "HKCR"_s || rootKey == "HKEY_CLASSES_ROOT"_s) { hRootKey = HKEY_CLASSES_ROOT; } else if (rootKey == "HKCC"_s || rootKey == "HKEY_CURRENT_CONFIG"_s) { hRootKey = HKEY_CURRENT_CONFIG; } else if (rootKey == "HKU"_s || rootKey == "HKEY_USERS"_s) { hRootKey = HKEY_USERS; } else { throw registry_error(cmStrCat(rootKey, ": invalid root key.")); } // Update path format auto key = ToWide(subKey); std::replace(key.begin(), key.end(), L'/', L'\\'); REGSAM options = KEY_READ; if (Is64BitWindows()) { options |= view == View::Reg64 ? KEY_WOW64_64KEY : KEY_WOW64_32KEY; } HKEY hKey; LSTATUS status; if ((status = RegOpenKeyExW(hRootKey, key.c_str(), 0, options, &hKey)) != ERROR_SUCCESS) { throw registry_error(FormatSystemError(status)); } return KeyHandler(hKey); } KeyHandler KeyHandler::OpenKey(cm::string_view key, View view) { auto start = key.find_first_of("\\/"_s); return OpenKey(key.substr(0, start), start == cm::string_view::npos ? cm::string_view{ ""_s } : key.substr(start + 1), view); } std::string KeyHandler::FormatSystemError(LSTATUS status) { std::string formattedMessage{ "Windows Registry: unexpected error." }; LPWSTR message = nullptr; DWORD size = 1024; if (FormatMessageW( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, nullptr, status, 0, reinterpret_cast(&message), size, nullptr) != 0) { try { formattedMessage = cmTrimWhitespace(ToNarrow(message)); } catch (...) { // ignore any exception because this method can be called // as part of the raise of an exception } } LocalFree(message); return formattedMessage; } std::wstring KeyHandler::ToWide(cm::string_view str) { std::wstring wstr; if (str.empty()) { return wstr; } const auto wlength = MultiByteToWideChar(KWSYS_ENCODING_DEFAULT_CODEPAGE, 0, str.data(), int(str.size()), nullptr, 0); if (wlength > 0) { auto wdata = cm::make_unique(wlength); const auto r = MultiByteToWideChar(KWSYS_ENCODING_DEFAULT_CODEPAGE, 0, str.data(), int(str.size()), wdata.get(), wlength); if (r > 0) { wstr = std::wstring(wdata.get(), wlength); } else { throw registry_error(FormatSystemError(GetLastError())); } } else { throw registry_error(FormatSystemError(GetLastError())); } return wstr; } std::string KeyHandler::ToNarrow(const wchar_t* wstr, int size) { std::string str; if (size == 0 || (size == -1 && wstr[0] == L'\0')) { return str; } const auto length = WideCharToMultiByte(KWSYS_ENCODING_DEFAULT_CODEPAGE, 0, wstr, size, nullptr, 0, nullptr, nullptr); if (length > 0) { auto data = cm::make_unique(length); const auto r = WideCharToMultiByte(KWSYS_ENCODING_DEFAULT_CODEPAGE, 0, wstr, size, data.get(), length, nullptr, nullptr); if (r > 0) { if (size == -1) { str = std::string(data.get()); } else { str = std::string(data.get(), length); } } else { throw registry_error(FormatSystemError(GetLastError())); } } else { throw registry_error(FormatSystemError(GetLastError())); } return str; } std::string KeyHandler::ReadValue(cm::string_view name, ValueTypeSet supportedTypes, cm::string_view separator) { LSTATUS status; DWORD size; // pick-up maximum size for value if ((status = RegQueryInfoKeyW(this->Handler, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &size, nullptr, nullptr)) != ERROR_SUCCESS) { throw registry_error(this->FormatSystemError(status)); } auto data = cm::make_unique(size); DWORD type; auto valueName = this->ToWide(name); if ((status = RegQueryValueExW(this->Handler, valueName.c_str(), nullptr, &type, data.get(), &size)) != ERROR_SUCCESS) { throw registry_error(this->FormatSystemError(status)); } auto valueType = ToValueType(type); if (!valueType || !supportedTypes.contains(*valueType)) { throw registry_error(cmStrCat(type, ": unsupported type.")); } switch (type) { case REG_SZ: return this->ToNarrow(reinterpret_cast(data.get())); break; case REG_EXPAND_SZ: { auto expandSize = ExpandEnvironmentStringsW( reinterpret_cast(data.get()), nullptr, 0); auto expandData = cm::make_unique(expandSize + 1); if (ExpandEnvironmentStringsW(reinterpret_cast(data.get()), expandData.get(), expandSize + 1) == 0) { throw registry_error(this->FormatSystemError(GetLastError())); } else { return this->ToNarrow(expandData.get()); } } break; case REG_DWORD: return std::to_string(*reinterpret_cast(data.get())); break; case REG_QWORD: return std::to_string(*reinterpret_cast(data.get())); break; case REG_MULTI_SZ: { // replace separator with semicolon auto sep = this->ToWide(separator)[0]; std::replace(reinterpret_cast(data.get()), reinterpret_cast(data.get()) + (size / sizeof(wchar_t)) - 1, sep, L';'); return this->ToNarrow(reinterpret_cast(data.get())); } break; default: throw registry_error(cmStrCat(type, ": unsupported type.")); } } std::vector KeyHandler::GetValueNames() { LSTATUS status; DWORD maxSize; // pick-up maximum size for value names if ((status = RegQueryInfoKeyW( this->Handler, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &maxSize, nullptr, nullptr, nullptr)) != ERROR_SUCCESS) { throw registry_error(this->FormatSystemError(status)); } // increment size for final null auto data = cm::make_unique(++maxSize); DWORD index = 0; DWORD size = maxSize; std::vector valueNames; while ((status = RegEnumValueW(this->Handler, index, data.get(), &size, nullptr, nullptr, nullptr, nullptr)) == ERROR_SUCCESS) { auto name = this->ToNarrow(data.get()); valueNames.push_back(name.empty() ? "(default)" : name); size = maxSize; ++index; } if (status != ERROR_NO_MORE_ITEMS) { throw registry_error(this->FormatSystemError(status)); } return valueNames; } std::vector KeyHandler::GetSubKeys() { LSTATUS status; DWORD size; // pick-up maximum size for subkeys if ((status = RegQueryInfoKeyW( this->Handler, nullptr, nullptr, nullptr, nullptr, &size, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) != ERROR_SUCCESS) { throw registry_error(this->FormatSystemError(status)); } // increment size for final null auto data = cm::make_unique(++size); DWORD index = 0; std::vector subKeys; while ((status = RegEnumKeyW(this->Handler, index, data.get(), size)) == ERROR_SUCCESS) { subKeys.push_back(this->ToNarrow(data.get())); ++index; } if (status != ERROR_NO_MORE_ITEMS) { throw registry_error(this->FormatSystemError(status)); } return subKeys; } #endif // ExpressionParser: Helper to parse expression holding multiple // registry specifications class ExpressionParser { public: ExpressionParser(cm::string_view expression) : Expression(expression) , Separator(";"_s) , RegistryFormat{ "\\[({.+})?(HKCU|HKEY_CURRENT_USER|HKLM|HKEY_LOCAL_MACHINE|HKCR|HKEY_" "CLASSES_" "ROOT|HKCC|HKEY_CURRENT_CONFIG|HKU|HKEY_USERS)[/\\]?([^]]*)\\]" } { } bool Find() { // reset result members this->RootKey = cm::string_view{}; this->SubKey = cm::string_view{}; this->ValueName = cm::string_view{}; auto result = this->RegistryFormat.find(this->Expression); if (result) { auto separator = cm::string_view{ this->Expression.data() + this->RegistryFormat.start(1), this->RegistryFormat.end(1) - this->RegistryFormat.start(1) }; if (separator.empty()) { separator = this->Separator; } else { separator = separator.substr(1, separator.length() - 2); } this->RootKey = cm::string_view{ this->Expression.data() + this->RegistryFormat.start(2), this->RegistryFormat.end(2) - this->RegistryFormat.start(2) }; this->SubKey = cm::string_view{ this->Expression.data() + this->RegistryFormat.start(3), this->RegistryFormat.end(3) - this->RegistryFormat.start(3) }; auto pos = this->SubKey.find(separator); if (pos != cm::string_view::npos) { // split in ValueName and SubKey this->ValueName = this->SubKey.substr(pos + separator.size()); if (Strucmp(this->ValueName, "(default)") == 0) { // handle magic name for default value this->ValueName = ""_s; } this->SubKey = this->SubKey.substr(0, pos); } else { this->ValueName = ""_s; } } return result; } #if defined(_WIN32) && !defined(__CYGWIN__) void Replace(const std::string& value) { this->Expression.replace( this->RegistryFormat.start(), this->RegistryFormat.end() - this->RegistryFormat.start(), value); } cm::string_view GetRootKey() const { return this->RootKey; } cm::string_view GetSubKey() const { return this->SubKey; } cm::string_view GetValueName() const { return this->ValueName; } const std::string& GetExpression() const { return this->Expression; } #endif private: std::string Expression; cm::string_view Separator; cmsys::RegularExpression RegistryFormat; cm::string_view RootKey; cm::string_view SubKey; cm::string_view ValueName; }; } // class cmWindowsRegistry const cmWindowsRegistry::ValueTypeSet cmWindowsRegistry::SimpleTypes = cmWindowsRegistry::ValueTypeSet{ cmWindowsRegistry::ValueType::Reg_SZ, cmWindowsRegistry::ValueType::Reg_EXPAND_SZ, cmWindowsRegistry::ValueType::Reg_DWORD, cmWindowsRegistry::ValueType::Reg_QWORD }; const cmWindowsRegistry::ValueTypeSet cmWindowsRegistry::AllTypes = cmWindowsRegistry::SimpleTypes + cmWindowsRegistry::ValueType::Reg_MULTI_SZ; cmWindowsRegistry::cmWindowsRegistry(cmMakefile& makefile, const ValueTypeSet& supportedTypes) #if defined(_WIN32) && !defined(__CYGWIN__) : SupportedTypes(supportedTypes) #else : LastError("No Registry on this platform.") #endif { #if defined(_WIN32) && !defined(__CYGWIN__) if (cmValue targetSize = makefile.GetDefinition("CMAKE_SIZEOF_VOID_P")) { this->TargetSize = targetSize == "8" ? 64 : 32; } #else (void)makefile; (void)supportedTypes; #endif } cm::optional cmWindowsRegistry::ToView( cm::string_view name) { static std::unordered_map ViewDefinitions{ { "BOTH"_s, View::Both }, { "HOST"_s, View::Host }, { "TARGET"_s, View::Target }, { "32"_s, View::Reg32 }, { "64"_s, View::Reg64 }, { "32_64"_s, View::Reg32_64 }, { "64_32"_s, View::Reg64_32 } }; auto it = ViewDefinitions.find(name); return it == ViewDefinitions.end() ? cm::nullopt : cm::optional{ it->second }; } // define hash structure required by std::unordered_map namespace std { template <> struct hash { size_t operator()(cmWindowsRegistry::View const& v) const noexcept { return static_cast< typename underlying_type::type>(v); } }; } cm::string_view cmWindowsRegistry::FromView(View view) { static std::unordered_map ViewDefinitions{ { View::Both, "BOTH"_s }, { View::Host, "HOST"_s }, { View::Target, "TARGET"_s }, { View::Reg32, "32"_s }, { View::Reg64, "64"_s }, { View::Reg32_64, "32_64"_s }, { View::Reg64_32, "64_32"_s } }; auto it = ViewDefinitions.find(view); return it == ViewDefinitions.end() ? ""_s : it->second; } cm::string_view cmWindowsRegistry::GetLastError() const { return this->LastError; } #if defined(_WIN32) && !defined(__CYGWIN__) std::vector cmWindowsRegistry::ComputeViews(View view) { switch (view) { case View::Both: switch (this->TargetSize) { case 64: return std::vector{ View::Reg64, View::Reg32 }; break; case 32: return Is64BitWindows() ? std::vector{ View::Reg32, View::Reg64 } : std::vector{ View::Reg32 }; break; default: // No language specified, fallback to host architecture return Is64BitWindows() ? std::vector{ View::Reg64, View::Reg32 } : std::vector{ View::Reg32 }; break; } break; case View::Target: switch (this->TargetSize) { case 64: return std::vector{ View::Reg64 }; break; case 32: return std::vector{ View::Reg32 }; break; default: break; } CM_FALLTHROUGH; case View::Host: return std::vector{ Is64BitWindows() ? View::Reg64 : View::Reg32 }; break; case View::Reg64_32: return Is64BitWindows() ? std::vector{ View::Reg64, View::Reg32 } : std::vector{ View::Reg32 }; break; case View::Reg32_64: return Is64BitWindows() ? std::vector{ View::Reg32, View::Reg64 } : std::vector{ View::Reg32 }; break; default: return std::vector{ view }; break; } } #endif cm::optional cmWindowsRegistry::ReadValue( cm::string_view key, cm::string_view name, View view, cm::string_view separator) { #if defined(_WIN32) && !defined(__CYGWIN__) // compute list of registry views auto views = this->ComputeViews(view); if (Strucmp(name, "(default)") == 0) { // handle magic name for default value name = ""_s; } if (separator.empty()) { separator = "\0"_s; } for (auto v : views) { try { this->LastError.clear(); auto handler = KeyHandler::OpenKey(key, v); return handler.ReadValue(name, this->SupportedTypes, separator); } catch (const registry_error& e) { this->LastError = e.what(); continue; } } #else (void)key; (void)name; (void)view; (void)separator; #endif return cm::nullopt; } cm::optional> cmWindowsRegistry::GetValueNames( cm::string_view key, View view) { #if defined(_WIN32) && !defined(__CYGWIN__) this->LastError.clear(); // compute list of registry views auto views = this->ComputeViews(view); std::vector valueNames; bool querySuccessful = false; for (auto v : views) { try { auto handler = KeyHandler::OpenKey(key, v); auto list = handler.GetValueNames(); std::move(list.begin(), list.end(), std::back_inserter(valueNames)); querySuccessful = true; } catch (const registry_error& e) { this->LastError = e.what(); continue; } } if (!valueNames.empty()) { // value names must be unique and sorted std::sort(valueNames.begin(), valueNames.end()); valueNames.erase(std::unique(valueNames.begin(), valueNames.end()), valueNames.end()); } if (querySuccessful) { // At least one query was successful, so clean-up any error message this->LastError.clear(); return valueNames; } #else (void)key; (void)view; #endif return cm::nullopt; } cm::optional> cmWindowsRegistry::GetSubKeys( cm::string_view key, View view) { #if defined(_WIN32) && !defined(__CYGWIN__) this->LastError.clear(); // compute list of registry views auto views = this->ComputeViews(view); std::vector subKeys; bool querySuccessful = false; for (auto v : views) { try { auto handler = KeyHandler::OpenKey(key, v); auto list = handler.GetSubKeys(); std::move(list.begin(), list.end(), std::back_inserter(subKeys)); querySuccessful = true; } catch (const registry_error& e) { this->LastError = e.what(); continue; } } if (!subKeys.empty()) { // keys must be unique and sorted std::sort(subKeys.begin(), subKeys.end()); subKeys.erase(std::unique(subKeys.begin(), subKeys.end()), subKeys.end()); } if (querySuccessful) { // At least one query was successful, so clean-up any error message this->LastError.clear(); return subKeys; } #else (void)key; (void)view; #endif return cm::nullopt; } cm::optional> cmWindowsRegistry::ExpandExpression( cm::string_view expression, View view, cm::string_view separator) { #if defined(_WIN32) && !defined(__CYGWIN__) static std::string NOTFOUND{ "/REGISTRY-NOTFOUND" }; this->LastError.clear(); // compute list of registry views auto views = this->ComputeViews(view); std::vector result; for (auto v : views) { ExpressionParser parser(expression); while (parser.Find()) { try { auto handler = KeyHandler::OpenKey(parser.GetRootKey(), parser.GetSubKey(), v); auto data = handler.ReadValue(parser.GetValueName(), this->SupportedTypes, separator); parser.Replace(data); } catch (const registry_error& e) { this->LastError = e.what(); parser.Replace(NOTFOUND); continue; } } result.emplace_back(parser.GetExpression()); if (expression == parser.GetExpression()) { // there no substitutions, so can ignore other views break; } } return result; #else (void)view; (void)separator; ExpressionParser parser(expression); if (parser.Find()) { // expression holds unsupported registry access // so the expression cannot be used on this platform return cm::nullopt; } return std::vector{ std::string{ expression } }; #endif }