https://git.reactos.org/?p=reactos.git;a=commitdiff;h=56d95154eeaf2e96d5f3c6cd47baa03e50435235

commit 56d95154eeaf2e96d5f3c6cd47baa03e50435235
Author:     Katayama Hirofumi MZ <katayama.hirofumi...@gmail.com>
AuthorDate: Sat Aug 19 11:22:55 2023 +0900
Commit:     GitHub <nore...@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/createonprofilesection.htm
+ */
+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(

Reply via email to