xref: /reactos/dll/win32/shlwapi/propbag.cpp (revision a2d8e464)
1 /*
2  * PROJECT:     ReactOS Shell
3  * LICENSE:     LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
4  * PURPOSE:     Implement shell property bags
5  * COPYRIGHT:   Copyright 2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
6  */
7 
8 #define _ATL_NO_EXCEPTIONS
9 #include "precomp.h"
10 #include <shlwapi.h>
11 #include <shlwapi_undoc.h>
12 #include <atlstr.h>         // for CStringW
13 #include <atlsimpcoll.h>    // for CSimpleMap
14 #include <atlcomcli.h>      // for CComVariant
15 #include <atlconv.h>        // for CA2W and CW2A
16 
17 WINE_DEFAULT_DEBUG_CHANNEL(shell);
18 
19 class CBasePropertyBag
20     : public IPropertyBag
21 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
22     , public IPropertyBag2
23 #endif
24 {
25 protected:
26     LONG m_cRefs;   // reference count
27     DWORD m_dwMode; // STGM_* flags
28 
29 public:
30     CBasePropertyBag(DWORD dwMode)
31         : m_cRefs(0)
32         , m_dwMode(dwMode)
33     {
34     }
35 
36     virtual ~CBasePropertyBag() { }
37 
38     // IUnknown interface
39     STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) override
40     {
41 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
42         if (::IsEqualGUID(riid, IID_IPropertyBag2))
43         {
44             AddRef();
45             *ppvObject = static_cast<IPropertyBag2*>(this);
46             return S_OK;
47         }
48 #endif
49         if (::IsEqualGUID(riid, IID_IUnknown) || ::IsEqualGUID(riid, IID_IPropertyBag))
50         {
51             AddRef();
52             *ppvObject = static_cast<IPropertyBag*>(this);
53             return S_OK;
54         }
55 
56         ERR("%p: %s: E_NOTIMPL\n", this, debugstr_guid(&riid));
57         return E_NOTIMPL;
58     }
59     STDMETHODIMP_(ULONG) AddRef() override
60     {
61         return ::InterlockedIncrement(&m_cRefs);
62     }
63     STDMETHODIMP_(ULONG) Release() override
64     {
65         if (::InterlockedDecrement(&m_cRefs) == 0)
66         {
67             delete this;
68             return 0;
69         }
70         return m_cRefs;
71     }
72 
73 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
74     // IPropertyBag2 interface (stubs)
75     STDMETHODIMP Read(
76         _In_ ULONG cProperties,
77         _In_ PROPBAG2 *pPropBag,
78         _In_opt_ IErrorLog *pErrorLog,
79         _Out_ VARIANT *pvarValue,
80         _Out_ HRESULT *phrError) override
81     {
82         return E_NOTIMPL;
83     }
84     STDMETHODIMP Write(
85         _In_ ULONG cProperties,
86         _In_ PROPBAG2 *pPropBag,
87         _In_ VARIANT *pvarValue)
88     {
89         return E_NOTIMPL;
90     }
91     STDMETHODIMP CountProperties(_Out_ ULONG *pcProperties) override
92     {
93         return E_NOTIMPL;
94     }
95     STDMETHODIMP GetPropertyInfo(
96         _In_ ULONG iProperty,
97         _In_ ULONG cProperties,
98         _Out_ PROPBAG2 *pPropBag,
99         _Out_ ULONG *pcProperties) override
100     {
101         return E_NOTIMPL;
102     }
103     STDMETHODIMP LoadObject(
104         _In_z_ LPCWSTR pstrName,
105         _In_ DWORD dwHint,
106         _In_ IUnknown *pUnkObject,
107         _In_opt_ IErrorLog *pErrorLog) override
108     {
109         return E_NOTIMPL;
110     }
111 #endif
112 };
113 
114 struct CPropMapEqual
115 {
116     static bool IsEqualKey(const ATL::CStringW& k1, const ATL::CStringW& k2)
117     {
118         return k1.CompareNoCase(k2) == 0;
119     }
120 
121     static bool IsEqualValue(const ATL::CComVariant& v1, const ATL::CComVariant& v2)
122     {
123         return false;
124     }
125 };
126 
127 class CMemPropertyBag : public CBasePropertyBag
128 {
129 protected:
130     ATL::CSimpleMap<ATL::CStringW, ATL::CComVariant, CPropMapEqual> m_PropMap;
131 
132 public:
133     CMemPropertyBag(DWORD dwFlags) : CBasePropertyBag(dwFlags) { }
134 
135     STDMETHODIMP Read(_In_z_ LPCWSTR pszPropName, _Inout_ VARIANT *pvari,
136                       _Inout_opt_ IErrorLog *pErrorLog) override;
137     STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
138 };
139 
140 STDMETHODIMP
141 CMemPropertyBag::Read(
142     _In_z_ LPCWSTR pszPropName,
143     _Inout_ VARIANT *pvari,
144     _Inout_opt_ IErrorLog *pErrorLog)
145 {
146     UNREFERENCED_PARAMETER(pErrorLog);
147 
148     TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
149 
150     VARTYPE vt = V_VT(pvari);
151 
152     ::VariantInit(pvari);
153 
154 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
155     if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_WRITE)
156     {
157         ERR("%p: 0x%X\n", this, m_dwMode);
158         return E_ACCESSDENIED;
159     }
160 #endif
161 
162     if (!pszPropName || !pvari)
163     {
164         ERR("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
165         return E_INVALIDARG;
166     }
167 
168     INT iItem = m_PropMap.FindKey(pszPropName);
169     if (iItem == -1)
170     {
171         ERR("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
172         return E_FAIL;
173     }
174 
175     HRESULT hr = ::VariantCopy(pvari, &m_PropMap.GetValueAt(iItem));
176     if (FAILED(hr))
177     {
178         ERR("%p: 0x%08X %p\n", this, hr, pvari);
179         return hr;
180     }
181 
182     hr = ::VariantChangeTypeForRead(pvari, vt);
183     if (FAILED(hr))
184     {
185         ERR("%p: 0x%08X %p\n", this, hr, pvari);
186         return hr;
187     }
188 
189     return hr;
190 }
191 
192 STDMETHODIMP
193 CMemPropertyBag::Write(
194     _In_z_ LPCWSTR pszPropName,
195     _In_ VARIANT *pvari)
196 {
197     TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
198 
199 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
200     if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_READ)
201     {
202         ERR("%p: 0x%X\n", this, m_dwMode);
203         return E_ACCESSDENIED;
204     }
205 #endif
206 
207     if (!pszPropName || !pvari)
208     {
209         ERR("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
210         return E_INVALIDARG;
211     }
212 
213     ATL::CComVariant vari;
214     HRESULT hr = vari.Copy(pvari);
215     if (FAILED(hr))
216     {
217         ERR("%p: %s %p: 0x%08X\n", this, debugstr_w(pszPropName), pvari, hr);
218         return hr;
219     }
220 
221     if (!m_PropMap.SetAt(pszPropName, vari))
222     {
223         ERR("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
224         return E_FAIL;
225     }
226 
227     return hr;
228 }
229 
230 /**************************************************************************
231  *  SHCreatePropertyBagOnMemory (SHLWAPI.477)
232  *
233  * Creates a property bag object on memory.
234  *
235  * @param dwMode  Specifies either STGM_READ, STGM_WRITE or STGM_READWRITE. Ignored on Vista+.
236  * @param riid    Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
237  *                Vista+ rejects IID_IPropertyBag2.
238  * @param ppvObj  Receives an IPropertyBag pointer.
239  * @return        An HRESULT value. S_OK on success, non-zero on failure.
240  * @see           http://undoc.airesoft.co.uk/shlwapi.dll/SHCreatePropertyBagOnMemory.php
241  */
242 EXTERN_C HRESULT WINAPI
243 SHCreatePropertyBagOnMemory(_In_ DWORD dwMode, _In_ REFIID riid, _Out_ void **ppvObj)
244 {
245     TRACE("0x%08X, %s, %p\n", dwMode, debugstr_guid(&riid), ppvObj);
246 
247     *ppvObj = NULL;
248 
249     CComPtr<CMemPropertyBag> pMemBag(new CMemPropertyBag(dwMode));
250 
251     return pMemBag->QueryInterface(riid, ppvObj);
252 }
253 
254 class CRegPropertyBag : public CBasePropertyBag
255 {
256 protected:
257     HKEY m_hKey;
258 
259     HRESULT _ReadDword(LPCWSTR pszPropName, VARIANT *pvari);
260     HRESULT _ReadString(LPCWSTR pszPropName, VARIANTARG *pvarg, UINT len);
261     HRESULT _ReadBinary(LPCWSTR pszPropName, VARIANT *pvari, VARTYPE vt, DWORD uBytes);
262     HRESULT _ReadStream(VARIANT *pvari, BYTE *pInit, UINT cbInit);
263     HRESULT _CopyStreamIntoBuff(IStream *pStream, void *pv, ULONG cb);
264     HRESULT _GetStreamSize(IStream *pStream, LPDWORD pcbSize);
265     HRESULT _WriteStream(LPCWSTR pszPropName, IStream *pStream);
266 
267 public:
268     CRegPropertyBag(DWORD dwMode)
269         : CBasePropertyBag(dwMode)
270         , m_hKey(NULL)
271     {
272     }
273 
274     ~CRegPropertyBag() override
275     {
276         if (m_hKey)
277             ::RegCloseKey(m_hKey);
278     }
279 
280     HRESULT Init(HKEY hKey, LPCWSTR lpSubKey);
281 
282     STDMETHODIMP Read(_In_z_ LPCWSTR pszPropName, _Inout_ VARIANT *pvari,
283                       _Inout_opt_ IErrorLog *pErrorLog) override;
284     STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
285 };
286 
287 HRESULT CRegPropertyBag::Init(HKEY hKey, LPCWSTR lpSubKey)
288 {
289     REGSAM nAccess = 0;
290     if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) != STGM_WRITE)
291         nAccess |= KEY_READ;
292     if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) != STGM_READ)
293         nAccess |= KEY_WRITE;
294 
295     LONG error;
296     if (m_dwMode & STGM_CREATE)
297         error = ::RegCreateKeyExW(hKey, lpSubKey, 0, NULL, 0, nAccess, NULL, &m_hKey, NULL);
298     else
299         error = ::RegOpenKeyExW(hKey, lpSubKey, 0, nAccess, &m_hKey);
300 
301     if (error != ERROR_SUCCESS)
302     {
303         ERR("%p %s 0x%08X\n", hKey, debugstr_w(lpSubKey), error);
304         return HRESULT_FROM_WIN32(error);
305     }
306 
307     return S_OK;
308 }
309 
310 HRESULT CRegPropertyBag::_ReadDword(LPCWSTR pszPropName, VARIANT *pvari)
311 {
312     DWORD cbData = sizeof(DWORD);
313     LONG error = SHGetValueW(m_hKey, NULL, pszPropName, NULL, &V_UI4(pvari), &cbData);
314     if (error)
315         return E_FAIL;
316 
317     V_VT(pvari) = VT_UI4;
318     return S_OK;
319 }
320 
321 HRESULT CRegPropertyBag::_ReadString(LPCWSTR pszPropName, VARIANTARG *pvarg, UINT len)
322 {
323     BSTR bstr = ::SysAllocStringByteLen(NULL, len);
324     V_BSTR(pvarg) = bstr;
325     if (!bstr)
326         return E_OUTOFMEMORY;
327 
328     V_VT(pvarg) = VT_BSTR;
329     LONG error = SHGetValueW(m_hKey, NULL, pszPropName, NULL, bstr, (LPDWORD)&len);
330     if (error)
331     {
332         ::VariantClear(pvarg);
333         return E_FAIL;
334     }
335 
336     return S_OK;
337 }
338 
339 HRESULT CRegPropertyBag::_ReadStream(VARIANT *pvari, BYTE *pInit, UINT cbInit)
340 {
341     IStream *pStream = SHCreateMemStream(pInit, cbInit);
342     V_UNKNOWN(pvari) = pStream;
343     if (!pStream)
344         return E_OUTOFMEMORY;
345     V_VT(pvari) = VT_UNKNOWN;
346     return S_OK;
347 }
348 
349 HRESULT
350 CRegPropertyBag::_ReadBinary(
351     LPCWSTR pszPropName,
352     VARIANT *pvari,
353     VARTYPE vt,
354     DWORD uBytes)
355 {
356     HRESULT hr = E_FAIL;
357     if (vt != VT_UNKNOWN || uBytes < sizeof(GUID))
358         return hr;
359 
360     LPBYTE pbData = (LPBYTE)::LocalAlloc(LMEM_ZEROINIT, uBytes);
361     if (!pbData)
362         return hr;
363 
364     if (!SHGetValueW(m_hKey, NULL, pszPropName, NULL, pbData, &uBytes) &&
365         memcmp(&GUID_NULL, pbData, sizeof(GUID)) == 0)
366     {
367         hr = _ReadStream(pvari, pbData + sizeof(GUID), uBytes - sizeof(GUID));
368     }
369 
370     ::LocalFree(pbData);
371 
372     return hr;
373 }
374 
375 HRESULT CRegPropertyBag::_CopyStreamIntoBuff(IStream *pStream, void *pv, ULONG cb)
376 {
377     LARGE_INTEGER li;
378     li.QuadPart = 0;
379     HRESULT hr = pStream->Seek(li, 0, NULL);
380     if (FAILED(hr))
381         return hr;
382     return pStream->Read(pv, cb, NULL);
383 }
384 
385 HRESULT CRegPropertyBag::_GetStreamSize(IStream *pStream, LPDWORD pcbSize)
386 {
387     *pcbSize = 0;
388 
389     ULARGE_INTEGER ui;
390     HRESULT hr = IStream_Size(pStream, &ui);
391     if (FAILED(hr))
392         return hr;
393 
394     if (ui.DUMMYSTRUCTNAME.HighPart)
395         return E_FAIL; /* 64-bit value is not supported */
396 
397     *pcbSize = ui.DUMMYSTRUCTNAME.LowPart;
398     return hr;
399 }
400 
401 STDMETHODIMP
402 CRegPropertyBag::Read(
403     _In_z_ LPCWSTR pszPropName,
404     _Inout_ VARIANT *pvari,
405     _Inout_opt_ IErrorLog *pErrorLog)
406 {
407     UNREFERENCED_PARAMETER(pErrorLog);
408 
409     TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
410 
411     if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_WRITE)
412     {
413         ERR("%p: 0x%X\n", this, m_dwMode);
414         ::VariantInit(pvari);
415         return E_ACCESSDENIED;
416     }
417 
418     VARTYPE vt = V_VT(pvari);
419     VariantInit(pvari);
420 
421     HRESULT hr;
422     DWORD dwType, cbValue;
423     LONG error = SHGetValueW(m_hKey, NULL, pszPropName, &dwType, NULL, &cbValue);
424     if (error != ERROR_SUCCESS)
425         hr = E_FAIL;
426     else if (dwType == REG_SZ)
427         hr = _ReadString(pszPropName, pvari, cbValue);
428     else if (dwType == REG_BINARY)
429         hr = _ReadBinary(pszPropName, pvari, vt, cbValue);
430     else if (dwType == REG_DWORD)
431         hr = _ReadDword(pszPropName, pvari);
432     else
433         hr = E_FAIL;
434 
435     if (FAILED(hr))
436     {
437         ERR("%p: 0x%08X %ld: %s %p\n", this, hr, dwType, debugstr_w(pszPropName), pvari);
438         ::VariantInit(pvari);
439         return hr;
440     }
441 
442     hr = ::VariantChangeTypeForRead(pvari, vt);
443     if (FAILED(hr))
444     {
445         ERR("%p: 0x%08X %ld: %s %p\n", this, hr, dwType, debugstr_w(pszPropName), pvari);
446         ::VariantInit(pvari);
447     }
448 
449     return hr;
450 }
451 
452 HRESULT
453 CRegPropertyBag::_WriteStream(LPCWSTR pszPropName, IStream *pStream)
454 {
455     DWORD cbData;
456     HRESULT hr = _GetStreamSize(pStream, &cbData);
457     if (FAILED(hr) || !cbData)
458         return hr;
459 
460     DWORD cbBinary = cbData + sizeof(GUID);
461     LPBYTE pbBinary = (LPBYTE)::LocalAlloc(LMEM_ZEROINIT, cbBinary);
462     if (!pbBinary)
463         return E_OUTOFMEMORY;
464 
465     hr = _CopyStreamIntoBuff(pStream, pbBinary + sizeof(GUID), cbData);
466     if (SUCCEEDED(hr))
467     {
468         if (SHSetValueW(m_hKey, NULL, pszPropName, REG_BINARY, pbBinary, cbBinary))
469             hr = E_FAIL;
470     }
471 
472     ::LocalFree(pbBinary);
473     return hr;
474 }
475 
476 STDMETHODIMP
477 CRegPropertyBag::Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari)
478 {
479     TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
480 
481     if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_READ)
482     {
483         ERR("%p: 0x%X\n", this, m_dwMode);
484         return E_ACCESSDENIED;
485     }
486 
487     HRESULT hr;
488     LONG error;
489     VARIANTARG vargTemp = { 0 };
490     switch (V_VT(pvari))
491     {
492         case VT_EMPTY:
493             SHDeleteValueW(m_hKey, NULL, pszPropName);
494             hr = S_OK;
495             break;
496 
497         case VT_BOOL:
498         case VT_I1:
499         case VT_I2:
500         case VT_I4:
501         case VT_UI1:
502         case VT_UI2:
503         case VT_UI4:
504         case VT_INT:
505         case VT_UINT:
506         {
507             hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_UI4);
508             if (FAILED(hr))
509                 return hr;
510 
511             error = SHSetValueW(m_hKey, NULL, pszPropName, REG_DWORD, &V_UI4(&vargTemp), sizeof(DWORD));
512             if (error)
513                 hr = E_FAIL;
514 
515             ::VariantClear(&vargTemp);
516             break;
517         }
518 
519         case VT_UNKNOWN:
520         {
521             CComPtr<IStream> pStream;
522             hr = V_UNKNOWN(pvari)->QueryInterface(IID_IStream, (void **)&pStream);
523             if (FAILED(hr))
524                 return hr;
525 
526             hr = _WriteStream(pszPropName, pStream);
527             break;
528         }
529 
530         default:
531         {
532             hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_BSTR);
533             if (FAILED(hr))
534                 return hr;
535 
536             int cch = lstrlenW(V_BSTR(&vargTemp));
537             DWORD cb = (cch + 1) * sizeof(WCHAR);
538             error = SHSetValueW(m_hKey, NULL, pszPropName, REG_SZ, V_BSTR(&vargTemp), cb);
539             if (error)
540                 hr = E_FAIL;
541 
542             ::VariantClear(&vargTemp);
543             break;
544         }
545     }
546 
547     return hr;
548 }
549 
550 /**************************************************************************
551  *  SHCreatePropertyBagOnRegKey (SHLWAPI.471)
552  *
553  * Creates a property bag object on registry key.
554  *
555  * @param hKey       The registry key.
556  * @param pszSubKey  The path of the sub-key.
557  * @param dwMode     The combination of STGM_READ, STGM_WRITE, STGM_READWRITE, and STGM_CREATE.
558  * @param riid       Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
559  * @param ppvObj     Receives an IPropertyBag pointer.
560  * @return           An HRESULT value. S_OK on success, non-zero on failure.
561  * @see              https://source.winehq.org/WineAPI/SHCreatePropertyBagOnRegKey.html
562  */
563 EXTERN_C HRESULT WINAPI
564 SHCreatePropertyBagOnRegKey(
565     _In_ HKEY hKey,
566     _In_z_ LPCWSTR pszSubKey,
567     _In_ DWORD dwMode,
568     _In_ REFIID riid,
569     _Out_ void **ppvObj)
570 {
571     TRACE("%p, %s, 0x%08X, %s, %p\n", hKey, debugstr_w(pszSubKey), dwMode,
572           debugstr_guid(&riid), ppvObj);
573 
574     *ppvObj = NULL;
575 
576     CComPtr<CRegPropertyBag> pRegBag(new CRegPropertyBag(dwMode));
577 
578     HRESULT hr = pRegBag->Init(hKey, pszSubKey);
579     if (FAILED(hr))
580         return hr;
581 
582     return pRegBag->QueryInterface(riid, ppvObj);
583 }
584 
585 /**************************************************************************
586  *  SHGetIniStringW (SHLWAPI.294)
587  *
588  * @see https://source.winehq.org/WineAPI/SHGetIniStringW.html
589  */
590 EXTERN_C DWORD WINAPI
591 SHGetIniStringW(
592     _In_z_ LPCWSTR appName,
593     _In_z_ LPCWSTR keyName,
594     _Out_writes_to_(outLen, return + 1) LPWSTR out,
595     _In_ DWORD outLen,
596     _In_z_ LPCWSTR filename)
597 {
598     TRACE("(%s,%s,%p,%08x,%s)\n", debugstr_w(appName), debugstr_w(keyName),
599           out, outLen, debugstr_w(filename));
600 
601     if (outLen == 0)
602         return 0;
603 
604     // Try ".W"-appended section name. See also SHSetIniStringW
605     CStringW szSection(appName);
606     szSection += L".W";
607     CStringW pszWideBuff;
608     const INT cchWideMax = 4 * MAX_PATH; // UTF-7 needs 4 times length buffer.
609     GetPrivateProfileStringW(szSection, keyName, NULL,
610                              pszWideBuff.GetBuffer(cchWideMax), cchWideMax, filename);
611     pszWideBuff.ReleaseBuffer();
612 
613     if (pszWideBuff.IsEmpty()) // It's empty or not found
614     {
615         // Try the normal section name
616         return GetPrivateProfileStringW(appName, keyName, NULL, out, outLen, filename);
617     }
618 
619     // Okay, now ".W" version is valid. Its value is a UTF-7 string in UTF-16
620     CW2A wide2utf7(pszWideBuff);
621     MultiByteToWideChar(CP_UTF7, 0, wide2utf7, -1, out, outLen);
622     out[outLen - 1] = UNICODE_NULL;
623 
624     return lstrlenW(out);
625 }
626 
627 static BOOL Is7BitClean(LPCWSTR psz)
628 {
629     if (!psz)
630         return TRUE;
631 
632     while (*psz)
633     {
634         if (*psz > 0x7F)
635             return FALSE;
636         ++psz;
637     }
638     return TRUE;
639 }
640 
641 /**************************************************************************
642  *  SHSetIniStringW (SHLWAPI.295)
643  *
644  * @see https://source.winehq.org/WineAPI/SHSetIniStringW.html
645  */
646 EXTERN_C BOOL WINAPI
647 SHSetIniStringW(
648     _In_z_ LPCWSTR appName,
649     _In_z_ LPCWSTR keyName,
650     _In_opt_z_ LPCWSTR str,
651     _In_z_ LPCWSTR filename)
652 {
653     TRACE("(%s, %p, %s, %s)\n", debugstr_w(appName), keyName, debugstr_w(str),
654           debugstr_w(filename));
655 
656     // Write a normal profile string. If str was NULL, then key will be deleted
657     if (!WritePrivateProfileStringW(appName, keyName, str, filename))
658         return FALSE;
659 
660     if (Is7BitClean(str))
661     {
662         // Delete ".A" version
663         CStringW szSection(appName);
664         szSection += L".A";
665         WritePrivateProfileStringW(szSection, keyName, NULL, filename);
666 
667         // Delete ".W" version
668         szSection = appName;
669         szSection += L".W";
670         WritePrivateProfileStringW(szSection, keyName, NULL, filename);
671 
672         return TRUE;
673     }
674 
675     // Now str is not 7-bit clean. It needs UTF-7 encoding in UTF-16.
676     // We write ".A" and ".W"-appended sections
677     CW2A wide2utf7(str, CP_UTF7);
678     CA2W utf72wide(wide2utf7, CP_ACP);
679 
680     BOOL ret = TRUE;
681 
682     // Write ".A" version
683     CStringW szSection(appName);
684     szSection += L".A";
685     if (!WritePrivateProfileStringW(szSection, keyName, str, filename))
686         ret = FALSE;
687 
688     // Write ".W" version
689     szSection = appName;
690     szSection += L".W";
691     if (!WritePrivateProfileStringW(szSection, keyName, utf72wide, filename))
692         ret = FALSE;
693 
694     return ret;
695 }
696 
697 /**************************************************************************
698  *  SHGetIniStringUTF7W (SHLWAPI.473)
699  *
700  * Retrieves a string value from an INI file.
701  *
702  * @param lpAppName         The section name.
703  * @param lpKeyName         The key name.
704  *                          If this string begins from '@', the value will be interpreted as UTF-7.
705  * @param lpReturnedString  Receives a wide string value.
706  * @param nSize             The number of characters in lpReturnedString.
707  * @param lpFileName        The INI file.
708  * @return                  The number of characters copied to the buffer if succeeded.
709  */
710 EXTERN_C DWORD WINAPI
711 SHGetIniStringUTF7W(
712     _In_opt_z_ LPCWSTR lpAppName,
713     _In_z_ LPCWSTR lpKeyName,
714     _Out_writes_to_(nSize, return + 1) _Post_z_ LPWSTR lpReturnedString,
715     _In_ DWORD nSize,
716     _In_z_ LPCWSTR lpFileName)
717 {
718     if (*lpKeyName == L'@') // UTF-7
719         return SHGetIniStringW(lpAppName, lpKeyName + 1, lpReturnedString, nSize, lpFileName);
720 
721     return GetPrivateProfileStringW(lpAppName, lpKeyName, L"", lpReturnedString, nSize, lpFileName);
722 }
723 
724 /**************************************************************************
725  *  SHSetIniStringUTF7W (SHLWAPI.474)
726  *
727  * Sets a string value on an INI file.
728  *
729  * @param lpAppName   The section name.
730  * @param lpKeyName   The key name.
731  *                    If this begins from '@', the value will be stored as UTF-7.
732  * @param lpString    The wide string value to be set.
733  * @param lpFileName  The INI file.
734  * @return            TRUE if successful. FALSE if failed.
735  */
736 EXTERN_C BOOL WINAPI
737 SHSetIniStringUTF7W(
738     _In_z_ LPCWSTR lpAppName,
739     _In_z_ LPCWSTR lpKeyName,
740     _In_opt_z_ LPCWSTR lpString,
741     _In_z_ LPCWSTR lpFileName)
742 {
743     if (*lpKeyName == L'@') // UTF-7
744         return SHSetIniStringW(lpAppName, lpKeyName + 1, lpString, lpFileName);
745 
746     return WritePrivateProfileStringW(lpAppName, lpKeyName, lpString, lpFileName);
747 }
748 
749 class CIniPropertyBag : public CBasePropertyBag
750 {
751 protected:
752     LPWSTR m_pszFileName;
753     LPWSTR m_pszSection;
754     BOOL m_bAlternateStream; // ADS (Alternate Data Stream)
755 
756     static BOOL LooksLikeAnAlternateStream(LPCWSTR pszStart)
757     {
758         LPCWSTR pch = StrRChrW(pszStart, NULL, L'\\');
759         if (!pch)
760             pch = pszStart;
761         return StrChrW(pch, L':') != NULL;
762     }
763 
764     HRESULT
765     _GetSectionAndName(
766         LPCWSTR pszStart,
767         LPWSTR pszSection,
768         UINT cchSectionMax,
769         LPWSTR pszName,
770         UINT cchNameMax);
771 
772 public:
773     CIniPropertyBag(DWORD dwMode)
774         : CBasePropertyBag(dwMode)
775         , m_pszFileName(NULL)
776         , m_pszSection(NULL)
777         , m_bAlternateStream(FALSE)
778     {
779     }
780 
781     ~CIniPropertyBag() override
782     {
783         ::LocalFree(m_pszFileName);
784         ::LocalFree(m_pszSection);
785     }
786 
787     HRESULT Init(LPCWSTR pszIniFile, LPCWSTR pszSection);
788 
789     STDMETHODIMP Read(
790         _In_z_ LPCWSTR pszPropName,
791         _Inout_ VARIANT *pvari,
792         _Inout_opt_ IErrorLog *pErrorLog) override;
793 
794     STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
795 };
796 
797 HRESULT CIniPropertyBag::Init(LPCWSTR pszIniFile, LPCWSTR pszSection)
798 {
799     m_pszFileName = StrDupW(pszIniFile);
800     if (!m_pszFileName)
801         return E_OUTOFMEMORY;
802 
803     // Is it an ADS (Alternate Data Stream) pathname?
804     m_bAlternateStream = LooksLikeAnAlternateStream(m_pszFileName);
805 
806     if (pszSection)
807     {
808         m_pszSection = StrDupW(pszSection);
809         if (!m_pszSection)
810             return E_OUTOFMEMORY;
811     }
812 
813     return S_OK;
814 }
815 
816 HRESULT
817 CIniPropertyBag::_GetSectionAndName(
818     LPCWSTR pszStart,
819     LPWSTR pszSection,
820     UINT cchSectionMax,
821     LPWSTR pszName,
822     UINT cchNameMax)
823 {
824     LPCWSTR pchSep = StrChrW(pszStart, L'\\');
825     if (pchSep)
826     {
827         UINT cchSep = (UINT)(pchSep - pszStart + 1);
828         StrCpyNW(pszSection, pszStart, min(cchSep, cchSectionMax));
829         StrCpyNW(pszName, pchSep + 1, cchNameMax);
830         return S_OK;
831     }
832 
833     if (m_pszSection)
834     {
835         StrCpyNW(pszSection, m_pszSection, cchSectionMax);
836         StrCpyNW(pszName, pszStart, cchNameMax);
837         return S_OK;
838     }
839 
840     ERR("%p: %s\n", this, debugstr_w(pszStart));
841     return E_INVALIDARG;
842 }
843 
844 STDMETHODIMP
845 CIniPropertyBag::Read(
846     _In_z_ LPCWSTR pszPropName,
847     _Inout_ VARIANT *pvari,
848     _Inout_opt_ IErrorLog *pErrorLog)
849 {
850     UNREFERENCED_PARAMETER(pErrorLog);
851 
852     TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
853 
854     VARTYPE vt = V_VT(pvari);
855 
856     ::VariantInit(pvari);
857 
858     if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_WRITE)
859     {
860         ERR("%p: 0x%X\n", this, m_dwMode);
861         return E_ACCESSDENIED;
862     }
863 
864     WCHAR szSection[64], szName[64];
865     HRESULT hr =
866         _GetSectionAndName(pszPropName, szSection, _countof(szSection), szName, _countof(szName));
867     if (FAILED(hr))
868         return hr;
869 
870     const INT cchBuffMax = 4 * MAX_PATH; // UTF-7 needs 4 times length buffer.
871     CComHeapPtr<WCHAR> pszBuff;
872     if (!pszBuff.Allocate(cchBuffMax * sizeof(WCHAR)))
873         return E_OUTOFMEMORY;
874 
875     if (!SHGetIniStringUTF7W(szSection, szName, pszBuff, cchBuffMax, m_pszFileName))
876         return E_FAIL;
877 
878     BSTR bstr = ::SysAllocString(pszBuff);
879     V_BSTR(pvari) = bstr;
880     if (!bstr)
881         return E_OUTOFMEMORY;
882 
883     V_VT(pvari) = VT_BSTR;
884     return ::VariantChangeTypeForRead(pvari, vt);
885 }
886 
887 STDMETHODIMP
888 CIniPropertyBag::Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari)
889 {
890     TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
891 
892     if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_READ)
893     {
894         ERR("%p: 0x%X\n", this, m_dwMode);
895         return E_ACCESSDENIED;
896     }
897 
898     HRESULT hr;
899     BSTR bstr;
900     VARIANTARG vargTemp = { 0 };
901     switch (V_VT(pvari))
902     {
903         case VT_EMPTY:
904             bstr = NULL;
905             break;
906 
907         case VT_BSTR:
908             bstr = V_BSTR(pvari);
909             break;
910 
911         default:
912             hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_BSTR);
913             if (FAILED(hr))
914                 goto Quit;
915 
916             bstr = V_BSTR(&vargTemp);
917             break;
918     }
919 
920     WCHAR szSection[64], szName[64];
921     hr = _GetSectionAndName(pszPropName, szSection, _countof(szSection), szName, _countof(szName));
922     if (SUCCEEDED(hr))
923     {
924         if (SHSetIniStringUTF7W(szSection, szName, bstr, m_pszFileName))
925         {
926             if (!m_bAlternateStream)
927                 SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, m_pszFileName, NULL);
928         }
929         else
930         {
931             hr = E_FAIL;
932         }
933     }
934 
935 Quit:
936     ::VariantClear(&vargTemp);
937     return hr;
938 }
939 
940 /**************************************************************************
941  *  SHCreatePropertyBagOnProfileSection (SHLWAPI.472)
942  *
943  * Creates a property bag object on INI file.
944  *
945  * @param lpFileName  The INI filename.
946  * @param pszSection  The optional section name.
947  * @param dwMode      The combination of STGM_READ, STGM_WRITE, STGM_READWRITE, and STGM_CREATE.
948  * @param riid        Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
949  * @param ppvObj      Receives an IPropertyBag pointer.
950  * @return            An HRESULT value. S_OK on success, non-zero on failure.
951  * @see               https://www.geoffchappell.com/studies/windows/shell/shlwapi/api/propbag/createonprofilesection.htm
952  */
953 EXTERN_C HRESULT WINAPI
954 SHCreatePropertyBagOnProfileSection(
955     _In_z_ LPCWSTR lpFileName,
956     _In_opt_z_ LPCWSTR pszSection,
957     _In_ DWORD dwMode,
958     _In_ REFIID riid,
959     _Out_ void **ppvObj)
960 {
961     HANDLE hFile;
962     PWCHAR pchFileTitle;
963     WCHAR szBuff[MAX_PATH];
964 
965     if (dwMode & STGM_CREATE)
966     {
967         hFile = ::CreateFileW(lpFileName, 0, FILE_SHARE_DELETE, 0, CREATE_NEW,
968                               FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM, NULL);
969         if (hFile != INVALID_HANDLE_VALUE)
970         {
971             pchFileTitle = PathFindFileNameW(lpFileName);
972             if (lstrcmpiW(pchFileTitle, L"desktop.ini") == 0)
973             {
974                 StrCpyNW(szBuff, lpFileName, _countof(szBuff));
975                 if (PathRemoveFileSpecW(szBuff))
976                     PathMakeSystemFolderW(szBuff);
977             }
978             ::CloseHandle(hFile);
979         }
980     }
981 
982     *ppvObj = NULL;
983 
984     if (!PathFileExistsW(lpFileName))
985         return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
986 
987     CComPtr<CIniPropertyBag> pIniPB(new CIniPropertyBag(dwMode));
988 
989     HRESULT hr = pIniPB->Init(lpFileName, pszSection);
990     if (FAILED(hr))
991     {
992         ERR("0x%08X\n", hr);
993         return hr;
994     }
995 
996     return pIniPB->QueryInterface(riid, ppvObj);
997 }
998 
999 class CDesktopUpgradePropertyBag : public CBasePropertyBag
1000 {
1001 protected:
1002     BOOL _AlreadyUpgraded(HKEY hKey);
1003     VOID _MarkAsUpgraded(HKEY hkey);
1004     HRESULT _ReadFlags(VARIANT *pvari);
1005     HRESULT _ReadItemPositions(VARIANT *pvari);
1006     IStream* _GetOldDesktopViewStream();
1007     IStream* _NewStreamFromOld(IStream *pOldStream);
1008 
1009 public:
1010     CDesktopUpgradePropertyBag() : CBasePropertyBag(0) { }
1011 
1012     STDMETHODIMP Read(
1013         _In_z_ LPCWSTR pszPropName,
1014         _Inout_ VARIANT *pvari,
1015         _Inout_opt_ IErrorLog *pErrorLog) override;
1016 
1017     STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override
1018     {
1019         return E_NOTIMPL;
1020     }
1021 };
1022 
1023 VOID CDesktopUpgradePropertyBag::_MarkAsUpgraded(HKEY hkey)
1024 {
1025     DWORD dwValue = TRUE;
1026     SHSetValueW(hkey, NULL, L"Upgrade", REG_DWORD, &dwValue, sizeof(dwValue));
1027 }
1028 
1029 BOOL CDesktopUpgradePropertyBag::_AlreadyUpgraded(HKEY hKey)
1030 {
1031     // Check the existence of the value written in _MarkAsUpgraded.
1032     DWORD dwValue, cbData = sizeof(dwValue);
1033     return SHGetValueW(hKey, NULL, L"Upgrade", NULL, &dwValue, &cbData) == ERROR_SUCCESS;
1034 }
1035 
1036 typedef DWORDLONG DESKVIEW_FLAGS; // 64-bit data
1037 
1038 HRESULT CDesktopUpgradePropertyBag::_ReadFlags(VARIANT *pvari)
1039 {
1040     DESKVIEW_FLAGS Flags;
1041     DWORD cbValue = sizeof(Flags);
1042     if (SHGetValueW(HKEY_CURRENT_USER,
1043                     L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\DeskView",
1044                     L"Settings",
1045                     NULL,
1046                     &Flags,
1047                     &cbValue) != ERROR_SUCCESS || cbValue < sizeof(Flags))
1048     {
1049         return E_FAIL;
1050     }
1051 
1052     V_UINT(pvari) = ((UINT)(Flags >> 32)) | 0x220; // FIXME: Magic number
1053     V_VT(pvari) = VT_UINT;
1054     return S_OK;
1055 }
1056 
1057 typedef struct tagOLD_STREAM_HEADER
1058 {
1059     WORD wMagic;
1060     WORD awUnknown[6];
1061     WORD wSize;
1062 } OLD_STREAM_HEADER, *POLD_STREAM_HEADER;
1063 
1064 IStream* CDesktopUpgradePropertyBag::_NewStreamFromOld(IStream *pOldStream)
1065 {
1066     OLD_STREAM_HEADER Header;
1067     HRESULT hr = pOldStream->Read(&Header, sizeof(Header), NULL);
1068     if (FAILED(hr) || Header.wMagic != 28)
1069         return NULL;
1070 
1071     // Move stream pointer
1072     LARGE_INTEGER li;
1073     li.QuadPart = Header.wSize - sizeof(Header);
1074     hr = pOldStream->Seek(li, STREAM_SEEK_CUR, NULL);
1075     if (FAILED(hr))
1076         return NULL;
1077 
1078     // Get the size
1079     ULARGE_INTEGER uli;
1080     hr = IStream_Size(pOldStream, &uli);
1081     if (FAILED(hr))
1082         return NULL;
1083 
1084     // Create new stream and attach
1085     CComPtr<IStream> pNewStream;
1086     pNewStream.Attach(SHCreateMemStream(NULL, 0));
1087     if (!pNewStream)
1088         return NULL;
1089 
1090     // Subtract Header.wSize from the size
1091     uli.QuadPart -= Header.wSize;
1092 
1093     // Copy to pNewStream
1094     hr = pOldStream->CopyTo(pNewStream, uli, NULL, NULL);
1095     if (FAILED(hr))
1096         return NULL;
1097 
1098     li.QuadPart = 0;
1099     pNewStream->Seek(li, STREAM_SEEK_SET, NULL);
1100 
1101     return pNewStream.Detach();
1102 }
1103 
1104 IStream* CDesktopUpgradePropertyBag::_GetOldDesktopViewStream()
1105 {
1106     HKEY hKey = SHGetShellKey(SHKEY_Root_HKCU, L"Streams\\Desktop", FALSE);
1107     if (!hKey)
1108         return NULL;
1109 
1110     CComPtr<IStream> pOldStream;
1111     if (!_AlreadyUpgraded(hKey))
1112     {
1113         pOldStream.Attach(SHOpenRegStream2W(hKey, NULL, L"ViewView2", 0));
1114         if (pOldStream)
1115         {
1116             ULARGE_INTEGER uli;
1117             HRESULT hr = IStream_Size(pOldStream, &uli);
1118             if (SUCCEEDED(hr) && !uli.QuadPart)
1119                 pOldStream.Release();
1120         }
1121 
1122         if (!pOldStream)
1123             pOldStream.Attach(SHOpenRegStream2W(hKey, NULL, L"ViewView", 0));
1124 
1125         _MarkAsUpgraded(hKey);
1126     }
1127 
1128     ::RegCloseKey(hKey);
1129     return pOldStream.Detach();
1130 }
1131 
1132 HRESULT CDesktopUpgradePropertyBag::_ReadItemPositions(VARIANT *pvari)
1133 {
1134     CComPtr<IStream> pOldStream;
1135     pOldStream.Attach(_GetOldDesktopViewStream());
1136     if (!pOldStream)
1137         return E_FAIL;
1138 
1139     HRESULT hr = E_FAIL;
1140     IStream *pNewStream = _NewStreamFromOld(pOldStream);
1141     if (pNewStream)
1142     {
1143         V_UNKNOWN(pvari) = pNewStream;
1144         V_VT(pvari) = VT_UNKNOWN;
1145         hr = S_OK;
1146     }
1147 
1148     return hr;
1149 }
1150 
1151 STDMETHODIMP
1152 CDesktopUpgradePropertyBag::Read(
1153     _In_z_ LPCWSTR pszPropName,
1154     _Inout_ VARIANT *pvari,
1155     _Inout_opt_ IErrorLog *pErrorLog)
1156 {
1157     UNREFERENCED_PARAMETER(pErrorLog);
1158 
1159     VARTYPE vt = V_VT(pvari);
1160 
1161     HRESULT hr = E_FAIL;
1162     if (StrCmpW(L"FFlags", pszPropName) == 0)
1163         hr = _ReadFlags(pvari);
1164     else if (StrCmpNW(L"ItemPos", pszPropName, 7) == 0)
1165         hr = _ReadItemPositions(pvari);
1166 
1167     if (FAILED(hr))
1168     {
1169         ::VariantInit(pvari);
1170         return hr;
1171     }
1172 
1173     return ::VariantChangeType(pvari, pvari, 0, vt);
1174 }
1175 
1176 /**************************************************************************
1177  *  SHGetDesktopUpgradePropertyBag (Not exported; used in CViewStatePropertyBag)
1178  *
1179  * Creates or gets a property bag object for desktop upgrade
1180  *
1181  * @param riid    Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
1182  * @param ppvObj  Receives an IPropertyBag pointer.
1183  * @return        An HRESULT value. S_OK on success, non-zero on failure.
1184  */
1185 HRESULT SHGetDesktopUpgradePropertyBag(REFIID riid, void **ppvObj)
1186 {
1187     *ppvObj = NULL;
1188     CComPtr<CDesktopUpgradePropertyBag> pPropBag(new CDesktopUpgradePropertyBag());
1189     return pPropBag->QueryInterface(riid, ppvObj);
1190 }
1191