From 584213c5ffeca09b3fe24bd5e92f73fd057ac642 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 3 Jun 2022 17:49:15 -0500 Subject: Add RegReadUnexpandedString to get an unexpanded REG_EXPAND_SZ value. --- src/burn/engine/search.cpp | 50 +- src/burn/test/BurnUnitTest/TestRegistryFixture.cpp | 4 +- src/libs/dutil/WixToolset.DUtil/inc/regutil.h | 44 +- src/libs/dutil/WixToolset.DUtil/regutil.cpp | 420 ++++++++----- .../dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj | 1 + .../DUtilUnitTest/DUtilUnitTest.vcxproj.filters | 3 + src/libs/dutil/test/DUtilUnitTest/MonUtilTest.cpp | 2 + src/libs/dutil/test/DUtilUnitTest/RegUtilTest.cpp | 679 +++++++++++++++++++++ 8 files changed, 1007 insertions(+), 196 deletions(-) create mode 100644 src/libs/dutil/test/DUtilUnitTest/RegUtilTest.cpp (limited to 'src') diff --git a/src/burn/engine/search.cpp b/src/burn/engine/search.cpp index f521cdbd..b37dc9bd 100644 --- a/src/burn/engine/search.cpp +++ b/src/burn/engine/search.cpp @@ -955,15 +955,15 @@ static HRESULT RegistrySearchValue( ) { HRESULT hr = S_OK; - DWORD er = ERROR_SUCCESS; LPWSTR sczKey = NULL; LPWSTR sczValue = NULL; HKEY hKey = NULL; DWORD dwType = 0; - DWORD cbData = 0; + SIZE_T cbData = 0; LPBYTE pData = NULL; - DWORD cch = 0; BURN_VARIANT value = { }; + DWORD dwValue = 0; + LONGLONG llValue = 0; // format key string hr = VariableFormatString(pVariables, pSearch->RegistrySearch.sczKey, &sczKey, NULL); @@ -988,64 +988,38 @@ static HRESULT RegistrySearchValue( ExitOnFailure(hr, "Failed to open registry key."); // get value - er = ::RegQueryValueExW(hKey, sczValue, NULL, &dwType, NULL, &cbData); - if (ERROR_FILE_NOT_FOUND == er) + hr = RegReadValue(hKey, sczValue, pSearch->RegistrySearch.fExpandEnvironment, &pData, &cbData, &dwType); + if (E_FILENOTFOUND == hr) { // What if there is a hidden variable in sczKey or sczValue? LogStringLine(REPORT_STANDARD, "Registry value not found. Key = '%ls', Value = '%ls'", sczKey, sczValue); ExitFunction1(hr = S_OK); } - ExitOnWin32Error(er, hr, "Failed to query registry key value size."); - - pData = (LPBYTE)MemAlloc(cbData + sizeof(WCHAR), TRUE); // + sizeof(WCHAR) here to ensure that we always have a null terminator for REG_SZ - ExitOnNull(pData, hr, E_OUTOFMEMORY, "Failed to allocate memory registry value."); - - er = ::RegQueryValueExW(hKey, sczValue, NULL, &dwType, pData, &cbData); - ExitOnWin32Error(er, hr, "Failed to query registry key value."); + ExitOnFailure(hr, "Failed to query registry key value."); switch (dwType) { case REG_DWORD: - if (sizeof(LONG) != cbData) + if (memcpy_s(&dwValue, sizeof(DWORD), pData, cbData)) { ExitFunction1(hr = E_UNEXPECTED); } - hr = BVariantSetNumeric(&value, *((LONG*)pData)); + hr = BVariantSetNumeric(&value, dwValue); break; case REG_QWORD: - if (sizeof(LONGLONG) != cbData) + if (memcpy_s(&llValue, sizeof(LONGLONG), pData, cbData)) { ExitFunction1(hr = E_UNEXPECTED); } - hr = BVariantSetNumeric(&value, *((LONGLONG*)pData)); + hr = BVariantSetNumeric(&value, llValue); break; - case REG_EXPAND_SZ: - if (pSearch->RegistrySearch.fExpandEnvironment) - { - hr = StrAlloc(&value.sczValue, cbData); - ExitOnFailure(hr, "Failed to allocate string buffer."); - value.Type = BURN_VARIANT_TYPE_STRING; - - cch = ::ExpandEnvironmentStringsW((LPCWSTR)pData, value.sczValue, cbData); - if (cch > cbData) - { - hr = StrAlloc(&value.sczValue, cch); - ExitOnFailure(hr, "Failed to allocate string buffer."); - - if (cch != ::ExpandEnvironmentStringsW((LPCWSTR)pData, value.sczValue, cch)) - { - ExitWithLastError(hr, "Failed to get expand environment string."); - } - } - break; - } - __fallthrough; + case REG_EXPAND_SZ: __fallthrough; case REG_SZ: hr = BVariantSetString(&value, (LPCWSTR)pData, 0, FALSE); break; default: - ExitOnFailure(hr = E_NOTIMPL, "Unsupported registry key value type. Type = '%u'", dwType); + ExitWithRootFailure(hr, E_NOTIMPL, "Unsupported registry key value type. Type = '%u'", dwType); } ExitOnFailure(hr, "Failed to read registry value."); diff --git a/src/burn/test/BurnUnitTest/TestRegistryFixture.cpp b/src/burn/test/BurnUnitTest/TestRegistryFixture.cpp index 1f3d7680..89704d09 100644 --- a/src/burn/test/BurnUnitTest/TestRegistryFixture.cpp +++ b/src/burn/test/BurnUnitTest/TestRegistryFixture.cpp @@ -186,7 +186,7 @@ namespace WixBuildTools void TestRegistryFixture::SetUp() { // set mock API's - RegFunctionOverride(TestRegistryFixture_RegCreateKeyExW, TestRegistryFixture_RegOpenKeyExW, TestRegistryFixture_RegDeleteKeyExW, NULL, NULL, NULL, NULL, NULL, NULL); + RegFunctionOverride(TestRegistryFixture_RegCreateKeyExW, TestRegistryFixture_RegOpenKeyExW, TestRegistryFixture_RegDeleteKeyExW, NULL, NULL, NULL, NULL, NULL, NULL, NULL); Registry::CurrentUser->CreateSubKey(TEST_REGISTRY_FIXTURE_HKCU_PATH); Registry::CurrentUser->CreateSubKey(TEST_REGISTRY_FIXTURE_HKCU32_PATH); @@ -198,7 +198,7 @@ namespace WixBuildTools { Registry::CurrentUser->DeleteSubKeyTree(this->rootPath, false); - RegFunctionOverride(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + RegFunctionOverride(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); } String^ TestRegistryFixture::GetDirectHkcuPath(REG_KEY_BITNESS bitness, ... array^ paths) diff --git a/src/libs/dutil/WixToolset.DUtil/inc/regutil.h b/src/libs/dutil/WixToolset.DUtil/inc/regutil.h index 76d2d7cb..f5b40291 100644 --- a/src/libs/dutil/WixToolset.DUtil/inc/regutil.h +++ b/src/libs/dutil/WixToolset.DUtil/inc/regutil.h @@ -99,6 +99,15 @@ typedef LSTATUS (APIENTRY *PFN_REGDELETEVALUEW)( __in HKEY hKey, __in_opt LPCWSTR lpValueName ); +typedef LSTATUS(APIENTRY *PFN_REGGETVALUEW)( + __in HKEY hkey, + __in_opt LPCWSTR lpSubKey, + __in_opt LPCWSTR lpValue, + __in DWORD dwFlags, + __out_opt LPDWORD pdwType, + __out_bcount_part_opt(*pcbData, *pcbData) __out_data_source(REGISTRY) PVOID pvData, + __inout_opt LPDWORD pcbData + ); /******************************************************************** RegInitialize - initializes regutil @@ -126,9 +135,17 @@ void DAPI RegFunctionOverride( __in_opt PFN_REGQUERYINFOKEYW pfnRegQueryInfoKeyW, __in_opt PFN_REGQUERYVALUEEXW pfnRegQueryValueExW, __in_opt PFN_REGSETVALUEEXW pfnRegSetValueExW, - __in_opt PFN_REGDELETEVALUEW pfnRegDeleteValueW + __in_opt PFN_REGDELETEVALUEW pfnRegDeleteValueW, + __in_opt PFN_REGGETVALUEW pfnRegGetValueW ); +/******************************************************************** + RegFunctionForceFallback - ignore functions only available in newer versions of Windows. + Typically used for unit testing. + +*********************************************************************/ +void DAPI RegFunctionForceFallback(); + /******************************************************************** RegCreate - creates a registry key. @@ -219,6 +236,20 @@ HRESULT DAPI RegGetType( __out DWORD *pdwType ); +/******************************************************************** + RegReadBinary - reads a registry key value. + If fExpand is TRUE and the value is REG_EXPAND_SZ then it will be expanded. + NOTE: caller is responsible for freeing *ppbBuffer +*********************************************************************/ +HRESULT DAPI RegReadValue( + __in HKEY hk, + __in_z_opt LPCWSTR wzName, + __in BOOL fExpand, + __deref_out_bcount_opt(*pcbBuffer) BYTE** ppbBuffer, + __inout SIZE_T* pcbBuffer, + __out DWORD* pdwType + ); + /******************************************************************** RegReadBinary - reads a registry key binary value. NOTE: caller is responsible for freeing *ppbBuffer @@ -240,6 +271,17 @@ HRESULT DAPI RegReadString( __deref_out_z LPWSTR* psczValue ); +/******************************************************************** + RegReadUnexpandedString - reads a registry key value as a string without expanding it. + +*********************************************************************/ +HRESULT DAPI RegReadUnexpandedString( + __in HKEY hk, + __in_z_opt LPCWSTR wzName, + __inout BOOL* pfNeedsExpansion, + __deref_out_z LPWSTR* psczValue + ); + /******************************************************************** RegReadStringArray - reads a registry key value REG_MULTI_SZ value as a string array. diff --git a/src/libs/dutil/WixToolset.DUtil/regutil.cpp b/src/libs/dutil/WixToolset.DUtil/regutil.cpp index 64224d42..584966ed 100644 --- a/src/libs/dutil/WixToolset.DUtil/regutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/regutil.cpp @@ -9,6 +9,7 @@ #define RegExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_REGUTIL, x, s, __VA_ARGS__) #define RegExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_REGUTIL, x, s, __VA_ARGS__) #define RegExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_REGUTIL, x, s, __VA_ARGS__) +#define RegExitWithRootFailure(x, e, s, ...) ExitWithRootFailureSource(DUTIL_SOURCE_REGUTIL, x, e, s, __VA_ARGS__) #define RegExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_REGUTIL, x, s, __VA_ARGS__) #define RegExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_REGUTIL, p, x, e, s, __VA_ARGS__) #define RegExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_REGUTIL, p, x, s, __VA_ARGS__) @@ -28,10 +29,19 @@ static PFN_REGQUERYINFOKEYW vpfnRegQueryInfoKeyW = ::RegQueryInfoKeyW; static PFN_REGQUERYVALUEEXW vpfnRegQueryValueExW = ::RegQueryValueExW; static PFN_REGSETVALUEEXW vpfnRegSetValueExW = ::RegSetValueExW; static PFN_REGDELETEVALUEW vpfnRegDeleteValueW = ::RegDeleteValueW; +static PFN_REGGETVALUEW vpfnRegGetValueW = NULL; +static PFN_REGGETVALUEW vpfnRegGetValueWFromLibrary = NULL; static HMODULE vhAdvApi32Dll = NULL; static BOOL vfRegInitialized = FALSE; +static HRESULT GetRegValue( + __in HKEY hk, + __in_z_opt LPCWSTR wzName, + __deref_out_bcount_opt(*pcbBuffer) BYTE* pbBuffer, + __inout SIZE_T* pcbBuffer, + __out DWORD* pdwType + ); static HRESULT WriteStringToRegistry( __in HKEY hk, __in_z_opt LPCWSTR wzName, @@ -46,14 +56,22 @@ DAPI_(HRESULT) RegInitialize() hr = LoadSystemLibrary(L"AdvApi32.dll", &vhAdvApi32Dll); RegExitOnFailure(hr, "Failed to load AdvApi32.dll"); - // ignore failures - if this doesn't exist, we'll fall back to RegDeleteKeyW + // Ignore failures - if this doesn't exist, we'll fall back to RegDeleteKeyW. vpfnRegDeleteKeyExWFromLibrary = reinterpret_cast(::GetProcAddress(vhAdvApi32Dll, "RegDeleteKeyExW")); - if (NULL == vpfnRegDeleteKeyExW) + // Ignore failures - if this doesn't exist, we'll fall back to RegQueryValueExW. + vpfnRegGetValueWFromLibrary = reinterpret_cast(::GetProcAddress(vhAdvApi32Dll, "RegGetValueW")); + + if (!vpfnRegDeleteKeyExW) { vpfnRegDeleteKeyExW = vpfnRegDeleteKeyExWFromLibrary; } + if (!vpfnRegGetValueW) + { + vpfnRegGetValueW = vpfnRegGetValueWFromLibrary; + } + vfRegInitialized = TRUE; LExit: @@ -68,7 +86,9 @@ DAPI_(void) RegUninitialize() ::FreeLibrary(vhAdvApi32Dll); vhAdvApi32Dll = NULL; vpfnRegDeleteKeyExWFromLibrary = NULL; + vpfnRegGetValueWFromLibrary = NULL; vpfnRegDeleteKeyExW = NULL; + vpfnRegGetValueW = NULL; } vfRegInitialized = FALSE; @@ -84,7 +104,8 @@ DAPI_(void) RegFunctionOverride( __in_opt PFN_REGQUERYINFOKEYW pfnRegQueryInfoKeyW, __in_opt PFN_REGQUERYVALUEEXW pfnRegQueryValueExW, __in_opt PFN_REGSETVALUEEXW pfnRegSetValueExW, - __in_opt PFN_REGDELETEVALUEW pfnRegDeleteValueW + __in_opt PFN_REGDELETEVALUEW pfnRegDeleteValueW, + __in_opt PFN_REGGETVALUEW pfnRegGetValueW ) { vpfnRegCreateKeyExW = pfnRegCreateKeyExW ? pfnRegCreateKeyExW : ::RegCreateKeyExW; @@ -96,6 +117,14 @@ DAPI_(void) RegFunctionOverride( vpfnRegQueryValueExW = pfnRegQueryValueExW ? pfnRegQueryValueExW : ::RegQueryValueExW; vpfnRegSetValueExW = pfnRegSetValueExW ? pfnRegSetValueExW : ::RegSetValueExW; vpfnRegDeleteValueW = pfnRegDeleteValueW ? pfnRegDeleteValueW : ::RegDeleteValueW; + vpfnRegGetValueW = pfnRegGetValueW ? pfnRegGetValueW : vpfnRegGetValueWFromLibrary; +} + + +DAPI_(void) RegFunctionForceFallback() +{ + vpfnRegDeleteKeyExW = NULL; + vpfnRegGetValueW = NULL; } @@ -358,54 +387,104 @@ LExit: return hr; } -DAPI_(HRESULT) RegReadBinary( +DAPI_(HRESULT) RegReadValue( __in HKEY hk, __in_z_opt LPCWSTR wzName, + __in BOOL fExpand, __deref_out_bcount_opt(*pcbBuffer) BYTE** ppbBuffer, - __out SIZE_T *pcbBuffer - ) + __inout SIZE_T* pcbBuffer, + __out DWORD* pdwType + ) { HRESULT hr = S_OK; - LPBYTE pbBuffer = NULL; - DWORD er = ERROR_SUCCESS; - DWORD cb = 0; - DWORD dwType = 0; - - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, NULL, &cb); - RegExitOnWin32Error(er, hr, "Failed to get size of registry value."); + DWORD dwAttempts = 0; + LPWSTR sczExpand = NULL; - // Zero-length binary values can exist - if (0 < cb) + hr = GetRegValue(hk, wzName, *ppbBuffer, pcbBuffer, pdwType); + if (E_FILENOTFOUND == hr) { - pbBuffer = static_cast(MemAlloc(cb, FALSE)); - RegExitOnNull(pbBuffer, hr, E_OUTOFMEMORY, "Failed to allocate buffer for binary registry value."); + ExitFunction(); + } + else if (E_MOREDATA != hr) + { + RegExitOnFailure(hr, "Failed to get size of raw registry value."); - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, pbBuffer, &cb); - if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er)) + // Zero-length raw values can exist + if (!*ppbBuffer && 0 < *pcbBuffer) { - ExitFunction1(hr = E_FILENOTFOUND); + hr = E_MOREDATA; } - RegExitOnWin32Error(er, hr, "Failed to read registry value."); } - if (REG_BINARY == dwType) + while (E_MOREDATA == hr && dwAttempts < 10) { - *ppbBuffer = pbBuffer; - pbBuffer = NULL; - *pcbBuffer = cb; + ++dwAttempts; + + if (*ppbBuffer) + { + *ppbBuffer = static_cast(MemReAlloc(*ppbBuffer, *pcbBuffer, FALSE)); + } + else + { + *ppbBuffer = static_cast(MemAlloc(*pcbBuffer, FALSE)); + } + RegExitOnNull(*ppbBuffer, hr, E_OUTOFMEMORY, "Failed to allocate buffer for raw registry value."); + + hr = GetRegValue(hk, wzName, *ppbBuffer, pcbBuffer, pdwType); + if (E_FILENOTFOUND == hr) + { + ExitFunction(); + } + else if (E_MOREDATA != hr) + { + RegExitOnFailure(hr, "Failed to read raw registry value."); + } } - else + + if (fExpand && SUCCEEDED(hr) && REG_EXPAND_SZ == *pdwType) { - hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE); - RegExitOnRootFailure(hr, "Error reading binary registry value due to unexpected data type: %u", dwType); + LPWSTR sczValue = reinterpret_cast(*ppbBuffer); + hr = PathExpand(&sczExpand, sczValue, PATH_EXPAND_ENVIRONMENT); + RegExitOnFailure(hr, "Failed to expand registry value: %ls", sczValue); + + *ppbBuffer = reinterpret_cast(sczExpand); + *pcbBuffer = (lstrlenW(sczExpand) + 1) * sizeof(WCHAR); + sczExpand = NULL; + ReleaseMem(sczValue); } LExit: - ReleaseMem(pbBuffer); + ReleaseStr(sczExpand); return hr; } +DAPI_(HRESULT) RegReadBinary( + __in HKEY hk, + __in_z_opt LPCWSTR wzName, + __deref_out_bcount_opt(*pcbBuffer) BYTE** ppbBuffer, + __out SIZE_T* pcbBuffer + ) +{ + HRESULT hr = S_OK; + DWORD dwType = 0; + + hr = RegReadValue(hk, wzName, FALSE, ppbBuffer, pcbBuffer, &dwType); + if (E_FILENOTFOUND == hr) + { + ExitFunction(); + } + RegExitOnFailure(hr, "Failed to read binary registry value."); + + if (REG_BINARY != dwType) + { + RegExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE), "Error reading binary registry value due to unexpected data type: %u", dwType); + } + +LExit: + return hr; +} + DAPI_(HRESULT) RegReadString( __in HKEY hk, @@ -414,65 +493,64 @@ DAPI_(HRESULT) RegReadString( ) { HRESULT hr = S_OK; - DWORD er = ERROR_SUCCESS; SIZE_T cbValue = 0; - DWORD cch = 0; - DWORD cb = 0; DWORD dwType = 0; - LPWSTR sczExpand = NULL; if (psczValue && *psczValue) { - hr = StrMaxLength(*psczValue, &cbValue); - RegExitOnFailure(hr, "Failed to determine length of string."); - - cch = (DWORD)min(DWORD_MAX, cbValue); + hr = MemSizeChecked(*psczValue, &cbValue); + RegExitOnFailure(hr, "Failed to get size of input buffer."); } - if (2 > cch) + hr = RegReadValue(hk, wzName, TRUE, reinterpret_cast(psczValue), &cbValue, &dwType); + if (E_FILENOTFOUND == hr) { - cch = 2; - - hr = StrAlloc(psczValue, cch); - RegExitOnFailure(hr, "Failed to allocate string to minimum size."); + ExitFunction(); } + RegExitOnFailure(hr, "Failed to read string registry value."); - cb = sizeof(WCHAR) * (cch - 1); // subtract one to ensure there will be a space at the end of the string for the null terminator. - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast(*psczValue), &cb); - if (ERROR_MORE_DATA == er) + if (REG_EXPAND_SZ != dwType && REG_SZ != dwType) { - cch = cb / sizeof(WCHAR) + 1; // add one to ensure there will be space at the end for the null terminator - hr = StrAlloc(psczValue, cch); - RegExitOnFailure(hr, "Failed to allocate string bigger for registry value."); - - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast(*psczValue), &cb); + RegExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE), "Error reading string registry value due to unexpected data type: %u", dwType); } - if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er)) + +LExit: + return hr; +} + + +DAPI_(HRESULT) RegReadUnexpandedString( + __in HKEY hk, + __in_z_opt LPCWSTR wzName, + __inout BOOL* pfNeedsExpansion, + __deref_out_z LPWSTR* psczValue + ) +{ + HRESULT hr = S_OK; + SIZE_T cbValue = 0; + DWORD dwType = 0; + LPWSTR sczExpand = NULL; + + if (psczValue && *psczValue) { - ExitFunction1(hr = E_FILENOTFOUND); + hr = MemSizeChecked(*psczValue, &cbValue); + RegExitOnFailure(hr, "Failed to get size of input buffer."); } - RegExitOnWin32Error(er, hr, "Failed to read registry key."); - if (REG_SZ == dwType || REG_EXPAND_SZ == dwType) + hr = RegReadValue(hk, wzName, FALSE, reinterpret_cast(psczValue), &cbValue, &dwType); + if (E_FILENOTFOUND == hr) { - // Always ensure the registry value is null terminated. - (*psczValue)[cch - 1] = L'\0'; - - if (REG_EXPAND_SZ == dwType) - { - hr = StrAllocString(&sczExpand, *psczValue, 0); - RegExitOnFailure(hr, "Failed to copy registry value to expand."); - - hr = PathExpand(psczValue, sczExpand, PATH_EXPAND_ENVIRONMENT); - RegExitOnFailure(hr, "Failed to expand registry value: %ls", *psczValue); - } + ExitFunction(); } - else + RegExitOnFailure(hr, "Failed to read expand string registry value."); + + if (REG_EXPAND_SZ != dwType && REG_SZ != dwType) { - hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE); - RegExitOnRootFailure(hr, "Error reading string registry value due to unexpected data type: %u", dwType); + RegExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE), "Error reading expand string registry value due to unexpected data type: %u", dwType); } + *pfNeedsExpansion = REG_EXPAND_SZ == dwType; + LExit: ReleaseStr(sczExpand); @@ -488,67 +566,57 @@ DAPI_(HRESULT) RegReadStringArray( ) { HRESULT hr = S_OK; - DWORD er = ERROR_SUCCESS; DWORD dwNullCharacters = 0; DWORD dwType = 0; - DWORD cb = 0; - DWORD cch = 0; + SIZE_T cb = 0; + SIZE_T cch = 0; LPCWSTR wzSource = NULL; LPWSTR sczValue = NULL; - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast(sczValue), &cb); - if (0 < cb) - { - cch = cb / sizeof(WCHAR); - hr = StrAlloc(&sczValue, cch); - RegExitOnFailure(hr, "Failed to allocate string for registry value."); - - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast(sczValue), &cb); - } - if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er)) + hr = RegReadValue(hk, wzName, FALSE, reinterpret_cast(&sczValue), &cb, &dwType); + if (E_FILENOTFOUND == hr) { - ExitFunction1(hr = E_FILENOTFOUND); + ExitFunction(); } - RegExitOnWin32Error(er, hr, "Failed to read registry key."); + RegExitOnFailure(hr, "Failed to read string array registry value."); - if (cb / sizeof(WCHAR) != cch) + if (REG_MULTI_SZ != dwType) { - hr = E_UNEXPECTED; - RegExitOnFailure(hr, "The size of registry value %ls unexpected changed between 2 reads", wzName); + RegExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE), "Tried to read string array, but registry value %ls is of an incorrect type", wzName); } - if (REG_MULTI_SZ != dwType) + cch = cb / sizeof(WCHAR); + + for (DWORD i = 0; i < cch; ++i) { - hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE); - RegExitOnRootFailure(hr, "Tried to read string array, but registry value %ls is of an incorrect type", wzName); + if (L'\0' == sczValue[i]) + { + ++dwNullCharacters; + } } // Value exists, but is empty, so no strings to return. - if (2 > cch) + if (!cb || 1 == dwNullCharacters && 1 == cch || 2 == dwNullCharacters && 2 == cch) { *prgsczStrings = NULL; *pcStrings = 0; ExitFunction1(hr = S_OK); } - // The docs specifically say if the value was written without double-null-termination, it'll get read back without it too. - if (L'\0' != sczValue[cch-1] || L'\0' != sczValue[cch-2]) + if (sczValue[cch - 1] != L'\0') { - hr = E_INVALIDARG; - RegExitOnFailure(hr, "Tried to read string array, but registry value %ls is invalid (isn't double-null-terminated)", wzName); + // Count the terminating null character that RegReadValue added past the end. + ++dwNullCharacters; + Assert(!sczValue[cch]); } - - cch = cb / sizeof(WCHAR); - for (DWORD i = 0; i < cch; ++i) + else if (cch > 1 && sczValue[cch - 2] == L'\0') { - if (L'\0' == sczValue[i]) - { - ++dwNullCharacters; - } + // Don't count the extra 1 at the end of the properly double-null terminated string. + --dwNullCharacters; } - // There's one string for every null character encountered (except the extra 1 at the end of the string) - *pcStrings = dwNullCharacters - 1; + // There's one string for every null character encountered. + *pcStrings = dwNullCharacters; hr = MemEnsureArraySize(reinterpret_cast(prgsczStrings), *pcStrings, sizeof(LPWSTR), 0); RegExitOnFailure(hr, "Failed to resize array while reading REG_MULTI_SZ value"); @@ -557,11 +625,13 @@ DAPI_(HRESULT) RegReadStringArray( wzSource = sczValue; for (DWORD i = 0; i < *pcStrings; ++i) { - hr = StrAllocString(&(*prgsczStrings)[i], wzSource, 0); + cch = lstrlenW(wzSource); + + hr = StrAllocString(&(*prgsczStrings)[i], wzSource, cch); RegExitOnFailure(hr, "Failed to allocate copy of string"); // Skip past this string - wzSource += lstrlenW(wzSource) + 1; + wzSource += cch + 1; } #pragma prefast(pop) @@ -579,38 +649,36 @@ DAPI_(HRESULT) RegReadVersion( ) { HRESULT hr = S_OK; - DWORD er = ERROR_SUCCESS; DWORD dwType = 0; - DWORD cb = 0; - LPWSTR sczVersion = NULL; + SIZE_T cb = 0; + LPWSTR sczValue = NULL; - cb = sizeof(DWORD64); - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast(*pdw64Version), &cb); - if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er)) + hr = RegReadValue(hk, wzName, TRUE, reinterpret_cast(&sczValue), &cb, &dwType); + if (E_FILENOTFOUND == hr) { - ExitFunction1(hr = E_FILENOTFOUND); + ExitFunction(); } + RegExitOnFailure(hr, "Failed to read version registry value."); if (REG_SZ == dwType || REG_EXPAND_SZ == dwType) { - hr = RegReadString(hk, wzName, &sczVersion); - RegExitOnFailure(hr, "Failed to read registry version as string."); - - hr = FileVersionFromStringEx(sczVersion, 0, pdw64Version); + hr = FileVersionFromStringEx(sczValue, 0, pdw64Version); RegExitOnFailure(hr, "Failed to convert registry string to version."); } else if (REG_QWORD == dwType) { - RegExitOnWin32Error(er, hr, "Failed to read registry key."); + if (memcpy_s(pdw64Version, sizeof(DWORD64), sczValue, cb)) + { + RegExitWithRootFailure(hr, E_INVALIDARG, "Failed to copy QWORD version value."); + } } else // unexpected data type { - hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE); - RegExitOnRootFailure(hr, "Error reading version registry value due to unexpected data type: %u", dwType); + RegExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE), "Error reading version registry value due to unexpected data type: %u", dwType); } LExit: - ReleaseStr(sczVersion); + ReleaseStr(sczValue); return hr; } @@ -623,37 +691,38 @@ DAPI_(HRESULT) RegReadWixVersion( ) { HRESULT hr = S_OK; - DWORD er = ERROR_SUCCESS; DWORD dwType = 0; - DWORD cb = 0; + SIZE_T cb = 0; DWORD64 dw64Version = 0; - LPWSTR sczVersion = NULL; + LPWSTR sczValue = NULL; VERUTIL_VERSION* pVersion = NULL; - cb = sizeof(DWORD64); - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast(&dw64Version), &cb); - if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er)) + hr = RegReadValue(hk, wzName, TRUE, reinterpret_cast(&sczValue), &cb, &dwType); + if (E_FILENOTFOUND == hr) { - ExitFunction1(hr = E_FILENOTFOUND); + ExitFunction(); } + RegExitOnFailure(hr, "Failed to read wix version registry value."); if (REG_SZ == dwType || REG_EXPAND_SZ == dwType) { - hr = RegReadString(hk, wzName, &sczVersion); - RegExitOnFailure(hr, "Failed to read registry version as string."); - - hr = VerParseVersion(sczVersion, 0, FALSE, &pVersion); - RegExitOnFailure(hr, "Failed to convert registry string to version."); + hr = VerParseVersion(sczValue, 0, FALSE, &pVersion); + RegExitOnFailure(hr, "Failed to convert registry string to wix version."); } else if (REG_QWORD == dwType) { + if (memcpy_s(&dw64Version, sizeof(DWORD64), sczValue, cb)) + { + RegExitWithRootFailure(hr, E_INVALIDARG, "Failed to copy QWORD wix version value."); + } + hr = VerVersionFromQword(dw64Version, &pVersion); - RegExitOnFailure(hr, "Failed to convert registry string to version."); + RegExitOnFailure(hr, "Failed to convert registry string to wix version."); } else // unexpected data type { hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE); - RegExitOnRootFailure(hr, "Error reading version registry value due to unexpected data type: %u", dwType); + RegExitOnRootFailure(hr, "Error reading wix version registry value due to unexpected data type: %u", dwType); } *ppVersion = pVersion; @@ -661,7 +730,7 @@ DAPI_(HRESULT) RegReadWixVersion( LExit: ReleaseVerutilVersion(pVersion); - ReleaseStr(sczVersion); + ReleaseStr(sczValue); return hr; } @@ -673,20 +742,18 @@ DAPI_(HRESULT) RegReadNone( ) { HRESULT hr = S_OK; - DWORD er = ERROR_SUCCESS; DWORD dwType = 0; - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, NULL, NULL); - if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er)) + hr = RegGetType(hk, wzName, &dwType); + if (E_FILENOTFOUND == hr) { - ExitFunction1(hr = E_FILENOTFOUND); + ExitFunction(); } - RegExitOnWin32Error(er, hr, "Failed to query registry key value."); + RegExitOnFailure(hr, "Error reading none registry value type."); if (REG_NONE != dwType) { - hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE); - RegExitOnRootFailure(hr, "Error reading none registry value due to unexpected data type: %u", dwType); + RegExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE), "Error reading none registry value due to unexpected data type: %u", dwType); } LExit: @@ -829,12 +896,9 @@ DAPI_(HRESULT) RegWriteStringArray( DWORD dwTotalStringSize = 0; DWORD cbTotalStringSize = 0; DWORD dwTemp = 0; + DWORD dwRemainingStringSize = 0; - if (0 == cValues) - { - wzWriteValue = L"\0"; - } - else + if (cValues) { // Add space for the null terminator dwTotalStringSize = 1; @@ -850,21 +914,22 @@ DAPI_(HRESULT) RegWriteStringArray( RegExitOnFailure(hr, "Failed to allocate space for string while writing REG_MULTI_SZ"); wzCopyDestination = sczWriteValue; - dwTemp = dwTotalStringSize; + dwRemainingStringSize = dwTotalStringSize; for (DWORD i = 0; i < cValues; ++i) { - hr = ::StringCchCopyW(wzCopyDestination, dwTotalStringSize, rgwzValues[i]); + hr = ::StringCchCopyW(wzCopyDestination, dwRemainingStringSize, rgwzValues[i]); RegExitOnFailure(hr, "failed to copy string: %ls", rgwzValues[i]); - dwTemp -= lstrlenW(rgwzValues[i]) + 1; - wzCopyDestination += lstrlenW(rgwzValues[i]) + 1; + dwTemp = lstrlenW(rgwzValues[i]) + 1; + dwRemainingStringSize -= dwTemp; + wzCopyDestination += dwTemp; } wzWriteValue = sczWriteValue; - } - hr = ::DWordMult(dwTotalStringSize, sizeof(WCHAR), &cbTotalStringSize); - RegExitOnFailure(hr, "Failed to get total string size in bytes"); + hr = ::DWordMult(dwTotalStringSize, sizeof(WCHAR), &cbTotalStringSize); + RegExitOnFailure(hr, "Failed to get total string size in bytes"); + } er = vpfnRegSetValueExW(hk, wzName, 0, REG_MULTI_SZ, reinterpret_cast(wzWriteValue), cbTotalStringSize); RegExitOnWin32Error(er, hr, "Failed to set registry value to array of strings (first string of which is): %ls", wzWriteValue); @@ -1000,6 +1065,48 @@ DAPI_(REGSAM) RegTranslateKeyBitness( } } +static HRESULT GetRegValue( + __in HKEY hk, + __in_z_opt LPCWSTR wzName, + __deref_out_bcount_opt(*pcbBuffer) BYTE* pbBuffer, + __inout SIZE_T* pcbBuffer, + __out DWORD* pdwType + ) +{ + HRESULT hr = S_OK; + DWORD cb = (DWORD)min(DWORD_MAX, *pcbBuffer); + + if (vpfnRegGetValueW) + { + hr = HRESULT_FROM_WIN32(vpfnRegGetValueW(hk, NULL, wzName, RRF_RT_ANY | RRF_NOEXPAND, pdwType, pbBuffer, &cb)); + } + else + { + hr = HRESULT_FROM_WIN32(vpfnRegQueryValueExW(hk, wzName, NULL, pdwType, pbBuffer, &cb)); + + if (REG_SZ == *pdwType || REG_EXPAND_SZ == *pdwType || REG_MULTI_SZ == *pdwType) + { + if (E_MOREDATA == hr || S_OK == hr && (cb + sizeof(WCHAR)) > *pcbBuffer) + { + // Make sure there's room for a null terminator at the end. + HRESULT hrAdd = ::DWordAdd(cb, sizeof(WCHAR), &cb); + + hr = FAILED(hrAdd) ? hrAdd : (pbBuffer ? E_MOREDATA : S_OK); + } + else if (S_OK == hr && pbBuffer) + { + // Always ensure the registry value is null terminated. + WCHAR* pch = reinterpret_cast(pbBuffer + cb); + *pch = L'\0'; + } + } + } + + *pcbBuffer = cb; + + return hr; +} + static HRESULT WriteStringToRegistry( __in HKEY hk, __in_z_opt LPCWSTR wzName, @@ -1013,9 +1120,12 @@ static HRESULT WriteStringToRegistry( if (wzValue) { - hr = ::StringCbLengthW(wzValue, STRSAFE_MAX_CCH * sizeof(TCHAR), &cbValue); + hr = ::StringCbLengthW(wzValue, DWORD_MAX, &cbValue); RegExitOnFailure(hr, "Failed to determine length of registry value: %ls", wzName); + // Need to include the null terminator. + cbValue += sizeof(WCHAR); + er = vpfnRegSetValueExW(hk, wzName, 0, dwType, reinterpret_cast(wzValue), static_cast(cbValue)); RegExitOnWin32Error(er, hr, "Failed to set registry value: %ls", wzName); } diff --git a/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj index c37bdad1..5b40eaf1 100644 --- a/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj +++ b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj @@ -61,6 +61,7 @@ 4564;4691 + diff --git a/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters index 4df7af89..fde49348 100644 --- a/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters +++ b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters @@ -54,6 +54,9 @@ Source Files + + Source Files + Source Files diff --git a/src/libs/dutil/test/DUtilUnitTest/MonUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/MonUtilTest.cpp index 273f2eb6..09d0aeab 100644 --- a/src/libs/dutil/test/DUtilUnitTest/MonUtilTest.cpp +++ b/src/libs/dutil/test/DUtilUnitTest/MonUtilTest.cpp @@ -481,6 +481,8 @@ namespace DutilTests ReleaseMem(pResults->rgDirectories); ReleaseMem(pResults->rgRegKeys); ReleaseMem(pResults); + + RegUninitialize(); } } }; diff --git a/src/libs/dutil/test/DUtilUnitTest/RegUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/RegUtilTest.cpp new file mode 100644 index 00000000..575e3238 --- /dev/null +++ b/src/libs/dutil/test/DUtilUnitTest/RegUtilTest.cpp @@ -0,0 +1,679 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +using namespace System; +using namespace System::Collections::Generic; +using namespace System::Runtime::InteropServices; +using namespace Xunit; +using namespace WixBuildTools::TestSupport; + +LPCWSTR wzBaseRegKey = L"Software\\RegUtilTest\\"; +LPWSTR rgwzMultiValue[2] = { L"First", L"Second" }; +LPWSTR rgwzEmptyMultiValue[2] = { L"", L"" }; + +HKEY hkBase; + +namespace DutilTests +{ + public ref class RegUtil : IDisposable + { + private: + + void CreateBaseKey() + { + HRESULT hr = RegCreate(HKEY_CURRENT_USER, wzBaseRegKey, KEY_ALL_ACCESS, &hkBase); + NativeAssert::Succeeded(hr, "Failed to create base key."); + } + + public: + RegUtil() + { + HRESULT hr = RegInitialize(); + NativeAssert::Succeeded(hr, "RegInitialize failed."); + } + + ~RegUtil() + { + RegFunctionOverride(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + if (hkBase) + { + RegDelete(hkBase, NULL, REG_KEY_DEFAULT, TRUE); + } + + ReleaseRegKey(hkBase); + + RegUninitialize(); + } + + [Fact] + void RegUtilStringValueTest() + { + this->StringValueTest(); + } + + [Fact] + void RegUtilStringValueFallbackTest() + { + RegFunctionForceFallback(); + this->StringValueTest(); + } + + void StringValueTest() + { + HRESULT hr = S_OK; + LPWSTR sczValue = NULL; + LPCWSTR wzValue = L"Value"; + + try + { + this->CreateBaseKey(); + + hr = RegWriteString(hkBase, L"String", wzValue); + NativeAssert::Succeeded(hr, "Failed to write string value."); + + hr = RegReadString(hkBase, L"String", &sczValue); + NativeAssert::Succeeded(hr, "Failed to read string value."); + NativeAssert::StringEqual(wzValue, sczValue); + + ReleaseNullStr(sczValue); + hr = StrAllocString(&sczValue, L"e", 0); + NativeAssert::Succeeded(hr, "Failed to reallocate string value."); + + hr = RegReadString(hkBase, L"String", &sczValue); + NativeAssert::Succeeded(hr, "Failed to read string value."); + NativeAssert::StringEqual(wzValue, sczValue); + } + finally + { + ReleaseStr(sczValue); + } + } + + [Fact] + void RegUtilPartialStringValueTest() + { + this->PartialStringValueTest(); + } + + [Fact] + void RegUtilPartialStringValueFallbackTest() + { + RegFunctionForceFallback(); + this->PartialStringValueTest(); + } + + void PartialStringValueTest() + { + HRESULT hr = S_OK; + LPWSTR sczValue = NULL; + LPCWSTR wzValue = L"Value"; + BOOL fNeedsExpansion = FALSE; + + try + { + this->CreateBaseKey(); + + // Use API directly to write non-null terminated string. + hr = HRESULT_FROM_WIN32(::RegSetValueExW(hkBase, L"PartialString", 0, REG_SZ, reinterpret_cast(wzValue), 4 * sizeof(WCHAR))); + NativeAssert::Succeeded(hr, "Failed to write partial string value."); + + hr = RegReadString(hkBase, L"PartialString", &sczValue); + NativeAssert::Succeeded(hr, "Failed to read partial string value."); + NativeAssert::StringEqual(L"Valu", sczValue); + + hr = RegReadUnexpandedString(hkBase, L"PartialString", &fNeedsExpansion, &sczValue); + NativeAssert::Succeeded(hr, "Failed to read partial unexpanded string value."); + NativeAssert::StringEqual(L"Valu", sczValue); + Assert::False(fNeedsExpansion); + } + finally + { + ReleaseStr(sczValue); + } + } + + [Fact] + void RegUtilEmptyStringValueTest() + { + this->EmptyStringValueTest(); + } + + [Fact] + void RegUtilEmptyStringValueFallbackTest() + { + RegFunctionForceFallback(); + this->EmptyStringValueTest(); + } + + void EmptyStringValueTest() + { + HRESULT hr = S_OK; + LPWSTR sczValue = NULL; + + try + { + this->CreateBaseKey(); + + // Use API directly to write non-null terminated string. + hr = HRESULT_FROM_WIN32(::RegSetValueExW(hkBase, L"EmptyString", 0, REG_SZ, reinterpret_cast(L""), 0)); + NativeAssert::Succeeded(hr, "Failed to write partial string value."); + + hr = RegReadString(hkBase, L"EmptyString", &sczValue); + NativeAssert::Succeeded(hr, "Failed to read partial string value."); + NativeAssert::StringEqual(L"", sczValue); + } + finally + { + ReleaseStr(sczValue); + } + } + + [Fact] + void RegUtilExpandStringValueTest() + { + this->ExpandStringValueTest(); + } + + [Fact] + void RegUtilExpandStringValueFallbackTest() + { + RegFunctionForceFallback(); + this->ExpandStringValueTest(); + } + + void ExpandStringValueTest() + { + HRESULT hr = S_OK; + LPWSTR sczValue = NULL; + LPCWSTR wzValue = L"Value_%USERNAME%"; + String^ expandedValue = Environment::ExpandEnvironmentVariables(gcnew String(wzValue)); + + try + { + this->CreateBaseKey(); + + hr = RegWriteExpandString(hkBase, L"ExpandString", wzValue); + NativeAssert::Succeeded(hr, "Failed to write expand string value."); + + hr = RegReadString(hkBase, L"ExpandString", &sczValue); + NativeAssert::Succeeded(hr, "Failed to read expand string value."); + WixAssert::StringEqual(expandedValue, gcnew String(sczValue), false); + } + finally + { + ReleaseStr(sczValue); + } + } + + [Fact] + void RegUtilNotExpandStringValueTest() + { + this->NotExpandStringValueTest(); + } + + [Fact] + void RegUtilNotExpandStringValueFallbackTest() + { + RegFunctionForceFallback(); + this->NotExpandStringValueTest(); + } + + void NotExpandStringValueTest() + { + HRESULT hr = S_OK; + LPWSTR sczValue = NULL; + BOOL fNeedsExpansion = FALSE; + LPCWSTR wzValue = L"Value_%USERNAME%"; + + try + { + this->CreateBaseKey(); + + hr = RegWriteExpandString(hkBase, L"NotExpandString", wzValue); + NativeAssert::Succeeded(hr, "Failed to write expand string value."); + + hr = RegReadUnexpandedString(hkBase, L"NotExpandString", &fNeedsExpansion, &sczValue); + NativeAssert::Succeeded(hr, "Failed to read expand string value."); + NativeAssert::StringEqual(wzValue, sczValue); + Assert::True(fNeedsExpansion); + } + finally + { + ReleaseStr(sczValue); + } + } + + [Fact] + void RegUtilMultiStringValueTest() + { + this->MultiStringValueTest(); + } + + [Fact] + void RegUtilMultiStringValueFallbackTest() + { + RegFunctionForceFallback(); + this->MultiStringValueTest(); + } + + void MultiStringValueTest() + { + HRESULT hr = S_OK; + LPWSTR* rgsczStrings = NULL; + DWORD cStrings = 0; + + try + { + this->CreateBaseKey(); + + hr = RegWriteStringArray(hkBase, L"MultiString", rgwzMultiValue, 2); + NativeAssert::Succeeded(hr, "Failed to write multi string value."); + + hr = RegReadStringArray(hkBase, L"MultiString", &rgsczStrings, &cStrings); + NativeAssert::Succeeded(hr, "Failed to read multi string value."); + Assert::Equal(2, cStrings); + NativeAssert::StringEqual(L"First", rgsczStrings[0]); + NativeAssert::StringEqual(L"Second", rgsczStrings[1]); + } + finally + { + ReleaseStrArray(rgsczStrings, cStrings); + } + } + + [Fact] + void RegUtilPartialMultiStringValueTest() + { + this->PartialMultiStringValueTest(); + } + + [Fact] + void RegUtilPartialMultiStringValueFallbackTest() + { + RegFunctionForceFallback(); + this->PartialMultiStringValueTest(); + } + + void PartialMultiStringValueTest() + { + HRESULT hr = S_OK; + LPWSTR* rgsczStrings = NULL; + DWORD cStrings = 0; + + try + { + this->CreateBaseKey(); + + // Use API directly to write non-double-null terminated string. + hr = HRESULT_FROM_WIN32(::RegSetValueExW(hkBase, L"PartialMultiString", 0, REG_MULTI_SZ, reinterpret_cast(L"First\0Second"), 13 * sizeof(WCHAR))); + NativeAssert::Succeeded(hr, "Failed to write partial multi string value."); + + hr = RegReadStringArray(hkBase, L"PartialMultiString", &rgsczStrings, &cStrings); + NativeAssert::Succeeded(hr, "Failed to read partial multi string value."); + Assert::Equal(2, cStrings); + NativeAssert::StringEqual(L"First", rgsczStrings[0]); + NativeAssert::StringEqual(L"Second", rgsczStrings[1]); + } + finally + { + ReleaseStrArray(rgsczStrings, cStrings); + } + } + + [Fact] + void RegUtilEmptyMultiStringValueTest() + { + this->EmptyMultiStringValueTest(); + } + + [Fact] + void RegUtilEmptyMultiStringValueFallbackTest() + { + RegFunctionForceFallback(); + this->EmptyMultiStringValueTest(); + } + + void EmptyMultiStringValueTest() + { + HRESULT hr = S_OK; + LPWSTR* rgsczStrings = NULL; + DWORD cStrings = 0; + + try + { + this->CreateBaseKey(); + + hr = RegWriteStringArray(hkBase, L"EmptyMultiString", rgwzMultiValue, 0); + NativeAssert::Succeeded(hr, "Failed to write empty multi string value."); + + hr = RegReadStringArray(hkBase, L"EmptyMultiString", &rgsczStrings, &cStrings); + NativeAssert::Succeeded(hr, "Failed to read empty multi string value."); + Assert::Equal(0, cStrings); + } + finally + { + ReleaseStrArray(rgsczStrings, cStrings); + } + } + + [Fact] + void RegUtilOneEmptyMultiStringValueTest() + { + this->OneEmptyMultiStringValueTest(); + } + + [Fact] + void RegUtilOneEmptyMultiStringValueFallbackTest() + { + RegFunctionForceFallback(); + this->OneEmptyMultiStringValueTest(); + } + + void OneEmptyMultiStringValueTest() + { + HRESULT hr = S_OK; + LPWSTR* rgsczStrings = NULL; + DWORD cStrings = 0; + + try + { + this->CreateBaseKey(); + + hr = RegWriteStringArray(hkBase, L"OneEmptyMultiString", rgwzEmptyMultiValue, 1); + NativeAssert::Succeeded(hr, "Failed to write one empty multi string value."); + + hr = RegReadStringArray(hkBase, L"OneEmptyMultiString", &rgsczStrings, &cStrings); + NativeAssert::Succeeded(hr, "Failed to read one empty multi string value."); + Assert::Equal(0, cStrings); + } + finally + { + ReleaseStrArray(rgsczStrings, cStrings); + } + } + + [Fact] + void RegUtilTwoEmptyMultiStringValueTest() + { + this->TwoEmptyMultiStringValueTest(); + } + + [Fact] + void RegUtilTwoEmptyMultiStringValueFallbackTest() + { + RegFunctionForceFallback(); + this->TwoEmptyMultiStringValueTest(); + } + + void TwoEmptyMultiStringValueTest() + { + HRESULT hr = S_OK; + LPWSTR* rgsczStrings = NULL; + DWORD cStrings = 0; + + try + { + this->CreateBaseKey(); + + hr = RegWriteStringArray(hkBase, L"OneEmptyMultiString", rgwzEmptyMultiValue, 2); + NativeAssert::Succeeded(hr, "Failed to write one empty multi string value."); + + hr = RegReadStringArray(hkBase, L"OneEmptyMultiString", &rgsczStrings, &cStrings); + NativeAssert::Succeeded(hr, "Failed to read one empty multi string value."); + Assert::Equal(2, cStrings); + NativeAssert::StringEqual(L"", rgsczStrings[0]); + NativeAssert::StringEqual(L"", rgsczStrings[1]); + } + finally + { + ReleaseStrArray(rgsczStrings, cStrings); + } + } + + [Fact] + void RegUtilOnePartialEmptyMultiStringValueTest() + { + this->OnePartialEmptyMultiStringValueTest(); + } + + [Fact] + void RegUtilOnePartialEmptyMultiStringValueFallbackTest() + { + RegFunctionForceFallback(); + this->OnePartialEmptyMultiStringValueTest(); + } + + void OnePartialEmptyMultiStringValueTest() + { + HRESULT hr = S_OK; + LPWSTR* rgsczStrings = NULL; + DWORD cStrings = 0; + + try + { + this->CreateBaseKey(); + + // Use API directly to write non-double-null terminated string. + hr = HRESULT_FROM_WIN32(::RegSetValueExW(hkBase, L"OnePartialEmptyMultiString", 0, REG_MULTI_SZ, reinterpret_cast(L""), 1 * sizeof(WCHAR))); + NativeAssert::Succeeded(hr, "Failed to write partial empty multi string value."); + + hr = RegReadStringArray(hkBase, L"OnePartialEmptyMultiString", &rgsczStrings, &cStrings); + NativeAssert::Succeeded(hr, "Failed to read partial empty multi string value."); + Assert::Equal(0, cStrings); + } + finally + { + ReleaseStrArray(rgsczStrings, cStrings); + } + } + + [Fact] + void RegUtilBinaryValueTest() + { + this->BinaryValueTest(); + } + + [Fact] + void RegUtilBinaryValueFallbackTest() + { + RegFunctionForceFallback(); + this->BinaryValueTest(); + } + + void BinaryValueTest() + { + HRESULT hr = S_OK; + BYTE pbSource[4] = { 1, 2, 3, 4 }; + BYTE* pbBuffer = NULL; + SIZE_T cbBuffer = 0; + + try + { + this->CreateBaseKey(); + + hr = RegWriteBinary(hkBase, L"Binary", pbSource, 4); + NativeAssert::Succeeded(hr, "Failed to write binary value."); + + hr = RegReadBinary(hkBase, L"Binary", &pbBuffer, &cbBuffer); + NativeAssert::Succeeded(hr, "Failed to read binary value."); + Assert::Equal(4, cbBuffer); + Assert::Equal(1, pbBuffer[0]); + Assert::Equal(2, pbBuffer[1]); + Assert::Equal(3, pbBuffer[2]); + Assert::Equal(4, pbBuffer[3]); + } + finally + { + ReleaseMem(pbBuffer); + } + } + + [Fact] + void RegUtilEmptyBinaryValueTest() + { + this->EmptyBinaryValueTest(); + } + + [Fact] + void RegUtilEmptyBinaryValueFallbackTest() + { + RegFunctionForceFallback(); + this->EmptyBinaryValueTest(); + } + + void EmptyBinaryValueTest() + { + HRESULT hr = S_OK; + BYTE* pbBuffer = NULL; + SIZE_T cbBuffer = 0; + + try + { + this->CreateBaseKey(); + + hr = RegWriteBinary(hkBase, L"Binary", NULL, 0); + NativeAssert::Succeeded(hr, "Failed to write binary value."); + + hr = RegReadBinary(hkBase, L"Binary", &pbBuffer, &cbBuffer); + NativeAssert::Succeeded(hr, "Failed to read binary value."); + Assert::Equal(0, cbBuffer); + } + finally + { + ReleaseMem(pbBuffer); + } + } + + [Fact] + void RegUtilQwordVersionValueTest() + { + this->QwordVersionValueTest(); + } + + [Fact] + void RegUtilQwordVersionValueFallbackTest() + { + RegFunctionForceFallback(); + this->QwordVersionValueTest(); + } + + void QwordVersionValueTest() + { + HRESULT hr = S_OK; + DWORD64 qwVersion = FILEMAKEVERSION(1, 2, 3, 4); + DWORD64 qwValue = 0; + + this->CreateBaseKey(); + + hr = RegWriteQword(hkBase, L"QwordVersion", qwVersion); + NativeAssert::Succeeded(hr, "Failed to write qword version value."); + + hr = RegReadVersion(hkBase, L"QwordVersion", &qwValue); + NativeAssert::Succeeded(hr, "Failed to read qword version value."); + Assert::Equal(qwVersion, qwValue); + } + + [Fact] + void RegUtilStringVersionValueTest() + { + this->StringVersionValueTest(); + } + + [Fact] + void RegUtilStringVersionValueFallbackTest() + { + RegFunctionForceFallback(); + this->StringVersionValueTest(); + } + + void StringVersionValueTest() + { + HRESULT hr = S_OK; + LPCWSTR wzVersion = L"65535.65535.65535.65535"; + DWORD64 qwValue = 0; + + this->CreateBaseKey(); + + hr = RegWriteString(hkBase, L"StringVersion", wzVersion); + NativeAssert::Succeeded(hr, "Failed to write string version value."); + + hr = RegReadVersion(hkBase, L"StringVersion", &qwValue); + NativeAssert::Succeeded(hr, "Failed to read string version value."); + Assert::Equal(MAXDWORD64, qwValue); + } + + [Fact] + void RegUtilQwordWixVersionValueTest() + { + this->QwordWixVersionValueTest(); + } + + [Fact] + void RegUtilQwordWixVersionValueFallbackTest() + { + RegFunctionForceFallback(); + this->QwordWixVersionValueTest(); + } + + void QwordWixVersionValueTest() + { + HRESULT hr = S_OK; + DWORD64 qwVersion = FILEMAKEVERSION(1, 2, 3, 4); + VERUTIL_VERSION* pVersion = NULL; + + try + { + this->CreateBaseKey(); + + hr = RegWriteQword(hkBase, L"QwordWixVersion", qwVersion); + NativeAssert::Succeeded(hr, "Failed to write qword wix version value."); + + hr = RegReadWixVersion(hkBase, L"QwordWixVersion", &pVersion); + NativeAssert::Succeeded(hr, "Failed to read qword wix version value."); + NativeAssert::StringEqual(L"1.2.3.4", pVersion->sczVersion); + } + finally + { + ReleaseVerutilVersion(pVersion); + } + } + + [Fact] + void RegUtilStringWixVersionValueTest() + { + this->StringWixVersionValueTest(); + } + + [Fact] + void RegUtilStringWixVersionValueFallbackTest() + { + RegFunctionForceFallback(); + this->StringWixVersionValueTest(); + } + + void StringWixVersionValueTest() + { + HRESULT hr = S_OK; + LPCWSTR wzVersion = L"65535.65535.65535.65535-abc+def"; + VERUTIL_VERSION* pVersion = NULL; + + try + { + this->CreateBaseKey(); + + hr = RegWriteString(hkBase, L"StringWixVersion", wzVersion); + NativeAssert::Succeeded(hr, "Failed to write string wix version value."); + + hr = RegReadWixVersion(hkBase, L"StringWixVersion", &pVersion); + NativeAssert::Succeeded(hr, "Failed to read string wix version value."); + NativeAssert::StringEqual(wzVersion, pVersion->sczVersion); + } + finally + { + ReleaseVerutilVersion(pVersion); + } + } + }; +} -- cgit v1.2.3-55-g6feb