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(