https://git.reactos.org/?p=reactos.git;a=commitdiff;h=56d95154eeaf2e96d5f3c…
commit 56d95154eeaf2e96d5f3c6cd47baa03e50435235
Author: Katayama Hirofumi MZ <katayama.hirofumi.mz(a)gmail.com>
AuthorDate: Sat Aug 19 11:22:55 2023 +0900
Commit: GitHub <noreply(a)github.com>
CommitDate: Sat Aug 19 11:22:55 2023 +0900
[SHLWAPI][SHLWAPI_APITEST][SDK] INI file property bag (#5546)
- Add SHGetIniStringUTF7W and SHSetIniStringUTF7W functions.
- Add CIniPropertyBag class.
- Implement SHCreatePropertyBagOnProfileSection function.
CORE-9283
---
dll/win32/shlwapi/propbag.cpp | 302 +++++++++++++++++++++
dll/win32/shlwapi/shlwapi.spec | 6 +-
.../rostests/apitests/shlwapi/SHPropertyBag.cpp | 137 ++++++++++
sdk/include/reactos/shlwapi_undoc.h | 23 ++
4 files changed, 465 insertions(+), 3 deletions(-)
diff --git a/dll/win32/shlwapi/propbag.cpp b/dll/win32/shlwapi/propbag.cpp
index 28e22736d5d..ce65aa6c6a0 100644
--- a/dll/win32/shlwapi/propbag.cpp
+++ b/dll/win32/shlwapi/propbag.cpp
@@ -693,3 +693,305 @@ SHSetIniStringW(
return ret;
}
+
+/**************************************************************************
+ * SHGetIniStringUTF7W (SHLWAPI.473)
+ *
+ * Retrieves a string value from an INI file.
+ *
+ * @param lpAppName The section name.
+ * @param lpKeyName The key name.
+ * If this string begins from '@', the value will be
interpreted as UTF-7.
+ * @param lpReturnedString Receives a wide string value.
+ * @param nSize The number of characters in lpReturnedString.
+ * @param lpFileName The INI file.
+ * @return The number of characters copied to the buffer if succeeded.
+ */
+EXTERN_C DWORD WINAPI
+SHGetIniStringUTF7W(
+ _In_opt_z_ LPCWSTR lpAppName,
+ _In_z_ LPCWSTR lpKeyName,
+ _Out_writes_to_(nSize, return + 1) _Post_z_ LPWSTR lpReturnedString,
+ _In_ DWORD nSize,
+ _In_z_ LPCWSTR lpFileName)
+{
+ if (*lpKeyName == L'@') // UTF-7
+ return SHGetIniStringW(lpAppName, lpKeyName + 1, lpReturnedString, nSize,
lpFileName);
+
+ return GetPrivateProfileStringW(lpAppName, lpKeyName, L"",
lpReturnedString, nSize, lpFileName);
+}
+
+/**************************************************************************
+ * SHSetIniStringUTF7W (SHLWAPI.474)
+ *
+ * Sets a string value on an INI file.
+ *
+ * @param lpAppName The section name.
+ * @param lpKeyName The key name.
+ * If this begins from '@', the value will be stored as
UTF-7.
+ * @param lpString The wide string value to be set.
+ * @param lpFileName The INI file.
+ * @return TRUE if successful. FALSE if failed.
+ */
+EXTERN_C BOOL WINAPI
+SHSetIniStringUTF7W(
+ _In_z_ LPCWSTR lpAppName,
+ _In_z_ LPCWSTR lpKeyName,
+ _In_opt_z_ LPCWSTR lpString,
+ _In_z_ LPCWSTR lpFileName)
+{
+ if (*lpKeyName == L'@') // UTF-7
+ return SHSetIniStringW(lpAppName, lpKeyName + 1, lpString, lpFileName);
+
+ return WritePrivateProfileStringW(lpAppName, lpKeyName, lpString, lpFileName);
+}
+
+class CIniPropertyBag : public CBasePropertyBag
+{
+protected:
+ LPWSTR m_pszFileName;
+ LPWSTR m_pszSection;
+ BOOL m_bAlternateStream; // ADS (Alternate Data Stream)
+
+ static BOOL LooksLikeAnAlternateStream(LPCWSTR pszStart)
+ {
+ LPCWSTR pch = StrRChrW(pszStart, NULL, L'\\');
+ if (!pch)
+ pch = pszStart;
+ return StrChrW(pch, L':') != NULL;
+ }
+
+ HRESULT
+ _GetSectionAndName(
+ LPCWSTR pszStart,
+ LPWSTR pszSection,
+ UINT cchSectionMax,
+ LPWSTR pszName,
+ UINT cchNameMax);
+
+public:
+ CIniPropertyBag(DWORD dwMode)
+ : CBasePropertyBag(dwMode)
+ , m_pszFileName(NULL)
+ , m_pszSection(NULL)
+ , m_bAlternateStream(FALSE)
+ {
+ }
+
+ ~CIniPropertyBag() override
+ {
+ ::LocalFree(m_pszFileName);
+ ::LocalFree(m_pszSection);
+ }
+
+ HRESULT Init(LPCWSTR pszIniFile, LPCWSTR pszSection);
+
+ STDMETHODIMP Read(
+ _In_z_ LPCWSTR pszPropName,
+ _Inout_ VARIANT *pvari,
+ _Inout_opt_ IErrorLog *pErrorLog) override;
+
+ STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
+};
+
+HRESULT CIniPropertyBag::Init(LPCWSTR pszIniFile, LPCWSTR pszSection)
+{
+ m_pszFileName = StrDupW(pszIniFile);
+ if (!m_pszFileName)
+ return E_OUTOFMEMORY;
+
+ // Is it an ADS (Alternate Data Stream) pathname?
+ m_bAlternateStream = LooksLikeAnAlternateStream(m_pszFileName);
+
+ if (pszSection)
+ {
+ m_pszSection = StrDupW(pszSection);
+ if (!m_pszSection)
+ return E_OUTOFMEMORY;
+ }
+
+ return S_OK;
+}
+
+HRESULT
+CIniPropertyBag::_GetSectionAndName(
+ LPCWSTR pszStart,
+ LPWSTR pszSection,
+ UINT cchSectionMax,
+ LPWSTR pszName,
+ UINT cchNameMax)
+{
+ LPCWSTR pchSep = StrChrW(pszStart, L'\\');
+ if (pchSep)
+ {
+ UINT cchSep = (UINT)(pchSep - pszStart + 1);
+ StrCpyNW(pszSection, pszStart, min(cchSep, cchSectionMax));
+ StrCpyNW(pszName, pchSep + 1, cchNameMax);
+ return S_OK;
+ }
+
+ if (m_pszSection)
+ {
+ StrCpyNW(pszSection, m_pszSection, cchSectionMax);
+ StrCpyNW(pszName, pszStart, cchNameMax);
+ return S_OK;
+ }
+
+ ERR("%p: %s\n", this, debugstr_w(pszStart));
+ return E_INVALIDARG;
+}
+
+STDMETHODIMP
+CIniPropertyBag::Read(
+ _In_z_ LPCWSTR pszPropName,
+ _Inout_ VARIANT *pvari,
+ _Inout_opt_ IErrorLog *pErrorLog)
+{
+ UNREFERENCED_PARAMETER(pErrorLog);
+
+ TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
+
+ VARTYPE vt = V_VT(pvari);
+
+ ::VariantInit(pvari);
+
+ if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_WRITE)
+ {
+ ERR("%p: 0x%X\n", this, m_dwMode);
+ return E_ACCESSDENIED;
+ }
+
+ WCHAR szSection[64], szName[64];
+ HRESULT hr =
+ _GetSectionAndName(pszPropName, szSection, _countof(szSection), szName,
_countof(szName));
+ if (FAILED(hr))
+ return hr;
+
+ const INT cchBuffMax = 4 * MAX_PATH; // UTF-7 needs 4 times length buffer.
+ CComHeapPtr<WCHAR> pszBuff;
+ if (!pszBuff.Allocate(cchBuffMax * sizeof(WCHAR)))
+ return E_OUTOFMEMORY;
+
+ if (!SHGetIniStringUTF7W(szSection, szName, pszBuff, cchBuffMax, m_pszFileName))
+ return E_FAIL;
+
+ BSTR bstr = ::SysAllocString(pszBuff);
+ V_BSTR(pvari) = bstr;
+ if (!bstr)
+ return E_OUTOFMEMORY;
+
+ V_VT(pvari) = VT_BSTR;
+ return ::VariantChangeTypeForRead(pvari, vt);
+}
+
+STDMETHODIMP
+CIniPropertyBag::Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari)
+{
+ TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
+
+ if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_READ)
+ {
+ ERR("%p: 0x%X\n", this, m_dwMode);
+ return E_ACCESSDENIED;
+ }
+
+ HRESULT hr;
+ BSTR bstr;
+ VARIANTARG vargTemp = { 0 };
+ switch (V_VT(pvari))
+ {
+ case VT_EMPTY:
+ bstr = NULL;
+ break;
+
+ case VT_BSTR:
+ bstr = V_BSTR(pvari);
+ break;
+
+ default:
+ hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_BSTR);
+ if (FAILED(hr))
+ goto Quit;
+
+ bstr = V_BSTR(&vargTemp);
+ break;
+ }
+
+ WCHAR szSection[64], szName[64];
+ hr = _GetSectionAndName(pszPropName, szSection, _countof(szSection), szName,
_countof(szName));
+ if (SUCCEEDED(hr))
+ {
+ if (SHSetIniStringUTF7W(szSection, szName, bstr, m_pszFileName))
+ {
+ if (!m_bAlternateStream)
+ SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, m_pszFileName, NULL);
+ }
+ else
+ {
+ hr = E_FAIL;
+ }
+ }
+
+Quit:
+ ::VariantClear(&vargTemp);
+ return hr;
+}
+
+/**************************************************************************
+ * SHCreatePropertyBagOnProfileSection (SHLWAPI.472)
+ *
+ * Creates a property bag object on INI file.
+ *
+ * @param lpFileName The INI filename.
+ * @param pszSection The optional section name.
+ * @param dwMode The combination of STGM_READ, STGM_WRITE, STGM_READWRITE, and
STGM_CREATE.
+ * @param riid Specifies either IID_IUnknown, IID_IPropertyBag or
IID_IPropertyBag2.
+ * @param ppvObj Receives an IPropertyBag pointer.
+ * @return An HRESULT value. S_OK on success, non-zero on failure.
+ * @see
https://www.geoffchappell.com/studies/windows/shell/shlwapi/api/propbag/cre…
+ */
+EXTERN_C HRESULT WINAPI
+SHCreatePropertyBagOnProfileSection(
+ _In_z_ LPCWSTR lpFileName,
+ _In_opt_z_ LPCWSTR pszSection,
+ _In_ DWORD dwMode,
+ _In_ REFIID riid,
+ _Out_ void **ppvObj)
+{
+ HANDLE hFile;
+ PWCHAR pchFileTitle;
+ WCHAR szBuff[MAX_PATH];
+
+ if (dwMode & STGM_CREATE)
+ {
+ hFile = ::CreateFileW(lpFileName, 0, FILE_SHARE_DELETE, 0, CREATE_NEW,
+ FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM, NULL);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ pchFileTitle = PathFindFileNameW(lpFileName);
+ if (lstrcmpiW(pchFileTitle, L"desktop.ini") == 0)
+ {
+ StrCpyNW(szBuff, lpFileName, _countof(szBuff));
+ if (PathRemoveFileSpecW(szBuff))
+ PathMakeSystemFolderW(szBuff);
+ }
+ ::CloseHandle(hFile);
+ }
+ }
+
+ *ppvObj = NULL;
+
+ if (!PathFileExistsW(lpFileName))
+ return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
+
+ CComPtr<CIniPropertyBag> pIniPB(new CIniPropertyBag(dwMode));
+
+ HRESULT hr = pIniPB->Init(lpFileName, pszSection);
+ if (FAILED(hr))
+ {
+ ERR("0x%08X\n", hr);
+ return hr;
+ }
+
+ return pIniPB->QueryInterface(riid, ppvObj);
+}
diff --git a/dll/win32/shlwapi/shlwapi.spec b/dll/win32/shlwapi/shlwapi.spec
index 13cac9b8c7a..434fb246046 100644
--- a/dll/win32/shlwapi/shlwapi.spec
+++ b/dll/win32/shlwapi/shlwapi.spec
@@ -469,9 +469,9 @@
469 stub -noname RunRegCommand
470 stub -noname IUnknown_ProfferServiceOld
471 stdcall -noname SHCreatePropertyBagOnRegKey(ptr wstr long ptr ptr)
-472 stub -noname SHCreatePropertyBagOnProfileSelection
-473 stub -noname SHGetIniStringUTF7W
-474 stub -noname SHSetIniStringUTF7W
+472 stdcall -noname SHCreatePropertyBagOnProfileSection(wstr wstr long ptr ptr)
+473 stdcall -noname SHGetIniStringUTF7W(wstr wstr ptr long wstr)
+474 stdcall -noname SHSetIniStringUTF7W(wstr wstr wstr wstr)
475 stdcall -noname GetShellSecurityDescriptor(ptr long)
476 stdcall -noname SHGetObjectCompatFlags(ptr ptr)
477 stdcall -noname SHCreatePropertyBagOnMemory(long ptr ptr)
diff --git a/modules/rostests/apitests/shlwapi/SHPropertyBag.cpp
b/modules/rostests/apitests/shlwapi/SHPropertyBag.cpp
index 2593cf66c6d..57ec8881006 100644
--- a/modules/rostests/apitests/shlwapi/SHPropertyBag.cpp
+++ b/modules/rostests/apitests/shlwapi/SHPropertyBag.cpp
@@ -8,6 +8,7 @@
#include <apitest.h>
#include <shlwapi.h>
#include <shlobj.h>
+#include <stdio.h>
#include <shlwapi_undoc.h>
#include <versionhelpers.h>
@@ -687,6 +688,141 @@ static void SHPropertyBag_SHSetIniStringW(void)
DeleteFileW(szIniFile);
}
+void SHPropertyBag_OnIniFile(void)
+{
+ WCHAR szIniFile[MAX_PATH], szValue[MAX_PATH];
+ HRESULT hr;
+ IPropertyBag *pPropBag;
+ VARIANT vari;
+ DWORD dwRet;
+
+ ExpandEnvironmentStringsW(L"%TEMP%\\SHPropertyBag.ini", szIniFile,
_countof(szIniFile));
+
+ DeleteFileW(szIniFile);
+ fclose(_wfopen(szIniFile, L"w"));
+
+ trace("%ls\n", szIniFile);
+
+ // read-write
+ hr = SHCreatePropertyBagOnProfileSection(
+ szIniFile,
+ L"TestSection",
+ STGM_READWRITE,
+ IID_IPropertyBag,
+ (void**)&pPropBag);
+ ok_long(hr, S_OK);
+ ok_int(PathFileExistsW(szIniFile), TRUE);
+
+ // Write UI4
+ VariantInit(&vari);
+ V_VT(&vari) = VT_UI4;
+ V_UI4(&vari) = 0xDEADFACE;
+ hr = pPropBag->Write(L"Name1", &vari);
+ ok_long(hr, S_OK);
+ VariantClear(&vari);
+
+ // Write BSTR
+ VariantInit(&vari);
+ V_VT(&vari) = VT_BSTR;
+ V_BSTR(&vari) = SysAllocString(L"StrValue");
+ hr = pPropBag->Write(L"Name2", &vari);
+ ok_long(hr, S_OK);
+ VariantClear(&vari);
+
+ // Write BSTR (dirty UTF-7)
+ VariantInit(&vari);
+ V_VT(&vari) = VT_BSTR;
+ V_BSTR(&vari) = SysAllocString(L"ABC\x3042\x3044\x3046\x2665");
+ hr = pPropBag->Write(L"@Name3", &vari);
+ ok_long(hr, S_OK);
+ VariantClear(&vari);
+
+ // Write BSTR (clean UTF-7)
+ VariantInit(&vari);
+ V_VT(&vari) = VT_BSTR;
+ V_BSTR(&vari) = SysAllocString(L"1234abc");
+ hr = pPropBag->Write(L"@Name4", &vari);
+ ok_long(hr, S_OK);
+ VariantClear(&vari);
+
+ pPropBag->Release();
+
+ // Flush
+ WritePrivateProfileStringW(NULL, NULL, NULL, szIniFile);
+
+ // Check INI file
+ dwRet = GetPrivateProfileStringW(L"TestSection", L"Name1",
L"BAD", szValue, _countof(szValue), szIniFile);
+ ok_long(dwRet, 10);
+ ok_wstr(szValue, L"3735943886");
+
+ dwRet = GetPrivateProfileStringW(L"TestSection", L"Name2",
L"BAD", szValue, _countof(szValue), szIniFile);
+ ok_long(dwRet, 8);
+ ok_wstr(szValue, L"StrValue");
+
+ GetPrivateProfileStringW(L"TestSection", L"Name3",
L"NotFound", szValue, _countof(szValue), szIniFile);
+ ok_int(memcmp(szValue, L"ABC", 3 * sizeof(WCHAR)), 0);
+
+ GetPrivateProfileStringW(L"TestSection.A", L"Name3",
L"NotFound", szValue, _countof(szValue), szIniFile);
+ ok_int(memcmp(szValue, L"ABC", 3 * sizeof(WCHAR)), 0);
+
+ GetPrivateProfileStringW(L"TestSection.W", L"Name3",
L"NotFound", szValue, _countof(szValue), szIniFile);
+ ok_wstr(szValue, L"ABC+MEIwRDBGJmU-"); // UTF-7
+
+ GetPrivateProfileStringW(L"TestSection", L"Name4",
L"NotFound", szValue, _countof(szValue), szIniFile);
+ ok_wstr(szValue, L"1234abc");
+
+ GetPrivateProfileStringW(L"TestSection.A", L"Name4",
L"NotFound", szValue, _countof(szValue), szIniFile);
+ ok_wstr(szValue, L"NotFound");
+
+ GetPrivateProfileStringW(L"TestSection.W", L"Name4",
L"NotFound", szValue, _countof(szValue), szIniFile);
+ ok_wstr(szValue, L"NotFound");
+
+ // read-only
+ hr = SHCreatePropertyBagOnProfileSection(
+ szIniFile,
+ NULL,
+ STGM_READ,
+ IID_IPropertyBag,
+ (void**)&pPropBag);
+ ok_long(hr, S_OK);
+
+ // Read UI4
+ VariantInit(&vari);
+ V_VT(&vari) = VT_UI4;
+ hr = pPropBag->Read(L"TestSection\\Name1", &vari, NULL);
+ ok_long(hr, S_OK);
+ ok_long(V_UI4(&vari), 0xDEADFACE);
+ VariantClear(&vari);
+
+ // Read BSTR
+ VariantInit(&vari);
+ V_VT(&vari) = VT_BSTR;
+ hr = pPropBag->Read(L"TestSection\\Name2", &vari, NULL);
+ ok_long(hr, S_OK);
+ ok_wstr(V_BSTR(&vari), L"StrValue");
+ VariantClear(&vari);
+
+ // Read BSTR (dirty UTF-7)
+ VariantInit(&vari);
+ V_VT(&vari) = VT_BSTR;
+ hr = pPropBag->Read(L"TestSection\\@Name3", &vari, NULL);
+ ok_long(hr, S_OK);
+ ok_wstr(V_BSTR(&vari), L"ABC\x3042\x3044\x3046\x2665");
+ VariantClear(&vari);
+
+ // Read BSTR (clean UTF-7)
+ VariantInit(&vari);
+ V_VT(&vari) = VT_BSTR;
+ hr = pPropBag->Read(L"TestSection\\@Name4", &vari, NULL);
+ ok_long(hr, S_OK);
+ ok_wstr(V_BSTR(&vari), L"1234abc");
+ VariantClear(&vari);
+
+ pPropBag->Release();
+
+ DeleteFileW(szIniFile);
+}
+
START_TEST(SHPropertyBag)
{
SHPropertyBag_ReadTest();
@@ -694,4 +830,5 @@ START_TEST(SHPropertyBag)
SHPropertyBag_OnMemory();
SHPropertyBag_OnRegKey();
SHPropertyBag_SHSetIniStringW();
+ SHPropertyBag_OnIniFile();
}
diff --git a/sdk/include/reactos/shlwapi_undoc.h b/sdk/include/reactos/shlwapi_undoc.h
index 4e01e7c633f..270bc344291 100644
--- a/sdk/include/reactos/shlwapi_undoc.h
+++ b/sdk/include/reactos/shlwapi_undoc.h
@@ -135,6 +135,14 @@ SHCreatePropertyBagOnRegKey(
_In_ REFIID riid,
_Out_ void **ppvObj);
+HRESULT WINAPI
+SHCreatePropertyBagOnProfileSection(
+ _In_z_ LPCWSTR lpFileName,
+ _In_opt_z_ LPCWSTR pszSection,
+ _In_ DWORD dwMode,
+ _In_ REFIID riid,
+ _Out_ void **ppvObj);
+
HWND WINAPI SHCreateWorkerWindowA(WNDPROC wndProc, HWND hWndParent, DWORD dwExStyle,
DWORD dwStyle, HMENU hMenu, LONG_PTR wnd_extra);
@@ -194,6 +202,21 @@ SHSetIniStringW(
_In_opt_z_ LPCWSTR str,
_In_z_ LPCWSTR filename);
+DWORD WINAPI
+SHGetIniStringUTF7W(
+ _In_opt_z_ LPCWSTR lpAppName,
+ _In_z_ LPCWSTR lpKeyName,
+ _Out_writes_to_(nSize, return + 1) _Post_z_ LPWSTR lpReturnedString,
+ _In_ DWORD nSize,
+ _In_z_ LPCWSTR lpFileName);
+
+BOOL WINAPI
+SHSetIniStringUTF7W(
+ _In_z_ LPCWSTR lpAppName,
+ _In_z_ LPCWSTR lpKeyName,
+ _In_opt_z_ LPCWSTR lpString,
+ _In_z_ LPCWSTR lpFileName);
+
int
WINAPIV
ShellMessageBoxWrapW(