xref: /reactos/dll/win32/shlwapi/propbag.cpp (revision 802dc971)
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 <shlobj_undoc.h>
13 #include <shlguid_undoc.h>
14 #include <atlstr.h>         // for CStringW
15 #include <atlsimpcoll.h>    // for CSimpleMap
16 #include <atlcomcli.h>      // for CComVariant
17 #include <atlconv.h>        // for CA2W and CW2A
18 #include <strsafe.h>        // for StringC... functions
19 
20 WINE_DEFAULT_DEBUG_CHANNEL(shell);
21 
22 #define MODE_CAN_READ(dwMode) \
23     (((dwMode) & (STGM_READ | STGM_WRITE | STGM_READWRITE)) != STGM_WRITE)
24 #define MODE_CAN_WRITE(dwMode) \
25     (((dwMode) & (STGM_READ | STGM_WRITE | STGM_READWRITE)) != STGM_READ)
26 
27 class CBasePropertyBag
28     : public IPropertyBag
29 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
30     , public IPropertyBag2
31 #endif
32 {
33 protected:
34     LONG m_cRefs;   // reference count
35     DWORD m_dwMode; // STGM_* flags
36 
37 public:
CBasePropertyBag(DWORD dwMode)38     CBasePropertyBag(DWORD dwMode)
39         : m_cRefs(0)
40         , m_dwMode(dwMode)
41     {
42     }
43 
~CBasePropertyBag()44     virtual ~CBasePropertyBag() { }
45 
46     // IUnknown interface
QueryInterface(REFIID riid,void ** ppvObject)47     STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) override
48     {
49         if (!ppvObject)
50             return E_POINTER;
51 
52 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
53         if (::IsEqualGUID(riid, IID_IPropertyBag2))
54         {
55             AddRef();
56             *ppvObject = static_cast<IPropertyBag2*>(this);
57             return S_OK;
58         }
59 #endif
60         if (::IsEqualGUID(riid, IID_IUnknown) || ::IsEqualGUID(riid, IID_IPropertyBag))
61         {
62             AddRef();
63             *ppvObject = static_cast<IPropertyBag*>(this);
64             return S_OK;
65         }
66 
67         ERR("%p: %s: E_NOINTERFACE\n", this, debugstr_guid(&riid));
68         return E_NOINTERFACE;
69     }
AddRef()70     STDMETHODIMP_(ULONG) AddRef() override
71     {
72         return ::InterlockedIncrement(&m_cRefs);
73     }
Release()74     STDMETHODIMP_(ULONG) Release() override
75     {
76         if (::InterlockedDecrement(&m_cRefs) == 0)
77         {
78             delete this;
79             return 0;
80         }
81         return m_cRefs;
82     }
83 
84 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
85     // IPropertyBag2 interface (stubs)
Read(_In_ ULONG cProperties,_In_ PROPBAG2 * pPropBag,_In_opt_ IErrorLog * pErrorLog,_Out_ VARIANT * pvarValue,_Out_ HRESULT * phrError)86     STDMETHODIMP Read(
87         _In_ ULONG cProperties,
88         _In_ PROPBAG2 *pPropBag,
89         _In_opt_ IErrorLog *pErrorLog,
90         _Out_ VARIANT *pvarValue,
91         _Out_ HRESULT *phrError) override
92     {
93         return E_NOTIMPL;
94     }
Write(_In_ ULONG cProperties,_In_ PROPBAG2 * pPropBag,_In_ VARIANT * pvarValue)95     STDMETHODIMP Write(
96         _In_ ULONG cProperties,
97         _In_ PROPBAG2 *pPropBag,
98         _In_ VARIANT *pvarValue)
99     {
100         return E_NOTIMPL;
101     }
CountProperties(_Out_ ULONG * pcProperties)102     STDMETHODIMP CountProperties(_Out_ ULONG *pcProperties) override
103     {
104         return E_NOTIMPL;
105     }
GetPropertyInfo(_In_ ULONG iProperty,_In_ ULONG cProperties,_Out_ PROPBAG2 * pPropBag,_Out_ ULONG * pcProperties)106     STDMETHODIMP GetPropertyInfo(
107         _In_ ULONG iProperty,
108         _In_ ULONG cProperties,
109         _Out_ PROPBAG2 *pPropBag,
110         _Out_ ULONG *pcProperties) override
111     {
112         return E_NOTIMPL;
113     }
LoadObject(_In_z_ LPCWSTR pstrName,_In_ DWORD dwHint,_In_ IUnknown * pUnkObject,_In_opt_ IErrorLog * pErrorLog)114     STDMETHODIMP LoadObject(
115         _In_z_ LPCWSTR pstrName,
116         _In_ DWORD dwHint,
117         _In_ IUnknown *pUnkObject,
118         _In_opt_ IErrorLog *pErrorLog) override
119     {
120         return E_NOTIMPL;
121     }
122 #endif
123 };
124 
125 struct CPropMapEqual
126 {
IsEqualKeyCPropMapEqual127     static bool IsEqualKey(const ATL::CStringW& k1, const ATL::CStringW& k2)
128     {
129         return k1.CompareNoCase(k2) == 0;
130     }
131 
IsEqualValueCPropMapEqual132     static bool IsEqualValue(const ATL::CComVariant& v1, const ATL::CComVariant& v2)
133     {
134         return false;
135     }
136 };
137 
138 class CMemPropertyBag : public CBasePropertyBag
139 {
140 protected:
141     ATL::CSimpleMap<ATL::CStringW, ATL::CComVariant, CPropMapEqual> m_PropMap;
142 
143 public:
CMemPropertyBag(DWORD dwMode)144     CMemPropertyBag(DWORD dwMode) : CBasePropertyBag(dwMode) { }
145 
146     STDMETHODIMP Read(_In_z_ LPCWSTR pszPropName, _Inout_ VARIANT *pvari,
147                       _Inout_opt_ IErrorLog *pErrorLog) override;
148     STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
149 };
150 
151 STDMETHODIMP
Read(_In_z_ LPCWSTR pszPropName,_Inout_ VARIANT * pvari,_Inout_opt_ IErrorLog * pErrorLog)152 CMemPropertyBag::Read(
153     _In_z_ LPCWSTR pszPropName,
154     _Inout_ VARIANT *pvari,
155     _Inout_opt_ IErrorLog *pErrorLog)
156 {
157     UNREFERENCED_PARAMETER(pErrorLog);
158 
159     TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
160 
161     VARTYPE vt = V_VT(pvari);
162 
163     ::VariantInit(pvari);
164 
165 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
166     if (!MODE_CAN_READ(m_dwMode))
167     {
168         ERR("%p: 0x%X\n", this, m_dwMode);
169         return E_ACCESSDENIED;
170     }
171 #endif
172 
173     if (!pszPropName || !pvari)
174     {
175         ERR("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
176         return E_INVALIDARG;
177     }
178 
179     INT iItem = m_PropMap.FindKey(pszPropName);
180     if (iItem == -1)
181     {
182         ERR("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
183         return E_FAIL;
184     }
185 
186     HRESULT hr = ::VariantCopy(pvari, &m_PropMap.GetValueAt(iItem));
187     if (FAILED(hr))
188     {
189         ERR("%p: 0x%08X %p\n", this, hr, pvari);
190         return hr;
191     }
192 
193     hr = ::VariantChangeTypeForRead(pvari, vt);
194     if (FAILED(hr))
195     {
196         ERR("%p: 0x%08X %p\n", this, hr, pvari);
197         return hr;
198     }
199 
200     return hr;
201 }
202 
203 STDMETHODIMP
Write(_In_z_ LPCWSTR pszPropName,_In_ VARIANT * pvari)204 CMemPropertyBag::Write(
205     _In_z_ LPCWSTR pszPropName,
206     _In_ VARIANT *pvari)
207 {
208     TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
209 
210 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
211     if (!MODE_CAN_WRITE(m_dwMode))
212     {
213         ERR("%p: 0x%X\n", this, m_dwMode);
214         return E_ACCESSDENIED;
215     }
216 #endif
217 
218     if (!pszPropName || !pvari)
219     {
220         ERR("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
221         return E_INVALIDARG;
222     }
223 
224     ATL::CComVariant vari;
225     HRESULT hr = vari.Copy(pvari);
226     if (FAILED(hr))
227     {
228         ERR("%p: %s %p: 0x%08X\n", this, debugstr_w(pszPropName), pvari, hr);
229         return hr;
230     }
231 
232     if (!m_PropMap.SetAt(pszPropName, vari))
233     {
234         ERR("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
235         return E_FAIL;
236     }
237 
238     return hr;
239 }
240 
241 /**************************************************************************
242  *  SHCreatePropertyBagOnMemory (SHLWAPI.477)
243  *
244  * Creates a property bag object on memory.
245  *
246  * @param dwMode  Specifies either STGM_READ, STGM_WRITE or STGM_READWRITE. Ignored on Vista+.
247  * @param riid    Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
248  *                Vista+ rejects IID_IPropertyBag2.
249  * @param ppvObj  Receives an IPropertyBag pointer.
250  * @return        An HRESULT value. S_OK on success, non-zero on failure.
251  * @see           http://undoc.airesoft.co.uk/shlwapi.dll/SHCreatePropertyBagOnMemory.php
252  */
253 EXTERN_C HRESULT WINAPI
SHCreatePropertyBagOnMemory(_In_ DWORD dwMode,_In_ REFIID riid,_Out_ void ** ppvObj)254 SHCreatePropertyBagOnMemory(_In_ DWORD dwMode, _In_ REFIID riid, _Out_ void **ppvObj)
255 {
256     TRACE("0x%08X, %s, %p\n", dwMode, debugstr_guid(&riid), ppvObj);
257 
258     *ppvObj = NULL;
259 
260     CComPtr<CMemPropertyBag> pMemBag(new CMemPropertyBag(dwMode));
261 
262     return pMemBag->QueryInterface(riid, ppvObj);
263 }
264 
265 class CRegPropertyBag : public CBasePropertyBag
266 {
267 protected:
268     HKEY m_hKey;
269 
270     HRESULT _ReadDword(LPCWSTR pszPropName, VARIANT *pvari);
271     HRESULT _ReadString(LPCWSTR pszPropName, VARIANTARG *pvarg, UINT len);
272     HRESULT _ReadBinary(LPCWSTR pszPropName, VARIANT *pvari, VARTYPE vt, DWORD uBytes);
273     HRESULT _ReadStream(VARIANT *pvari, BYTE *pInit, UINT cbInit);
274     HRESULT _CopyStreamIntoBuff(IStream *pStream, void *pv, ULONG cb);
275     HRESULT _GetStreamSize(IStream *pStream, LPDWORD pcbSize);
276     HRESULT _WriteStream(LPCWSTR pszPropName, IStream *pStream);
277 
278 public:
CRegPropertyBag(DWORD dwMode)279     CRegPropertyBag(DWORD dwMode)
280         : CBasePropertyBag(dwMode)
281         , m_hKey(NULL)
282     {
283     }
284 
~CRegPropertyBag()285     ~CRegPropertyBag() override
286     {
287         if (m_hKey)
288             ::RegCloseKey(m_hKey);
289     }
290 
291     HRESULT Init(HKEY hKey, LPCWSTR lpSubKey);
292 
293     STDMETHODIMP Read(_In_z_ LPCWSTR pszPropName, _Inout_ VARIANT *pvari,
294                       _Inout_opt_ IErrorLog *pErrorLog) override;
295     STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
296 };
297 
Init(HKEY hKey,LPCWSTR lpSubKey)298 HRESULT CRegPropertyBag::Init(HKEY hKey, LPCWSTR lpSubKey)
299 {
300     REGSAM nAccess = 0;
301     if (MODE_CAN_READ(m_dwMode))
302         nAccess |= KEY_READ;
303     if (MODE_CAN_WRITE(m_dwMode))
304         nAccess |= KEY_WRITE;
305 
306     LONG error;
307     if (m_dwMode & STGM_CREATE)
308         error = ::RegCreateKeyExW(hKey, lpSubKey, 0, NULL, 0, nAccess, NULL, &m_hKey, NULL);
309     else
310         error = ::RegOpenKeyExW(hKey, lpSubKey, 0, nAccess, &m_hKey);
311 
312     if (error != ERROR_SUCCESS)
313     {
314         ERR("%p %s 0x%08X\n", hKey, debugstr_w(lpSubKey), error);
315         return HRESULT_FROM_WIN32(error);
316     }
317 
318     return S_OK;
319 }
320 
_ReadDword(LPCWSTR pszPropName,VARIANT * pvari)321 HRESULT CRegPropertyBag::_ReadDword(LPCWSTR pszPropName, VARIANT *pvari)
322 {
323     DWORD cbData = sizeof(DWORD);
324     LONG error = SHGetValueW(m_hKey, NULL, pszPropName, NULL, &V_UI4(pvari), &cbData);
325     if (error)
326         return E_FAIL;
327 
328     V_VT(pvari) = VT_UI4;
329     return S_OK;
330 }
331 
_ReadString(LPCWSTR pszPropName,VARIANTARG * pvarg,UINT len)332 HRESULT CRegPropertyBag::_ReadString(LPCWSTR pszPropName, VARIANTARG *pvarg, UINT len)
333 {
334     BSTR bstr = ::SysAllocStringByteLen(NULL, len);
335     V_BSTR(pvarg) = bstr;
336     if (!bstr)
337         return E_OUTOFMEMORY;
338 
339     V_VT(pvarg) = VT_BSTR;
340     LONG error = SHGetValueW(m_hKey, NULL, pszPropName, NULL, bstr, (LPDWORD)&len);
341     if (error)
342     {
343         ::VariantClear(pvarg);
344         return E_FAIL;
345     }
346 
347     return S_OK;
348 }
349 
_ReadStream(VARIANT * pvari,BYTE * pInit,UINT cbInit)350 HRESULT CRegPropertyBag::_ReadStream(VARIANT *pvari, BYTE *pInit, UINT cbInit)
351 {
352     IStream *pStream = SHCreateMemStream(pInit, cbInit);
353     V_UNKNOWN(pvari) = pStream;
354     if (!pStream)
355         return E_OUTOFMEMORY;
356     V_VT(pvari) = VT_UNKNOWN;
357     return S_OK;
358 }
359 
360 HRESULT
_ReadBinary(LPCWSTR pszPropName,VARIANT * pvari,VARTYPE vt,DWORD uBytes)361 CRegPropertyBag::_ReadBinary(
362     LPCWSTR pszPropName,
363     VARIANT *pvari,
364     VARTYPE vt,
365     DWORD uBytes)
366 {
367     HRESULT hr = E_FAIL;
368     if (vt != VT_UNKNOWN || uBytes < sizeof(GUID))
369         return hr;
370 
371     LPBYTE pbData = (LPBYTE)::LocalAlloc(LMEM_ZEROINIT, uBytes);
372     if (!pbData)
373         return hr;
374 
375     if (!SHGetValueW(m_hKey, NULL, pszPropName, NULL, pbData, &uBytes) &&
376         memcmp(&GUID_NULL, pbData, sizeof(GUID)) == 0)
377     {
378         hr = _ReadStream(pvari, pbData + sizeof(GUID), uBytes - sizeof(GUID));
379     }
380 
381     ::LocalFree(pbData);
382 
383     return hr;
384 }
385 
_CopyStreamIntoBuff(IStream * pStream,void * pv,ULONG cb)386 HRESULT CRegPropertyBag::_CopyStreamIntoBuff(IStream *pStream, void *pv, ULONG cb)
387 {
388     LARGE_INTEGER li;
389     li.QuadPart = 0;
390     HRESULT hr = pStream->Seek(li, 0, NULL);
391     if (FAILED(hr))
392         return hr;
393     return pStream->Read(pv, cb, NULL);
394 }
395 
_GetStreamSize(IStream * pStream,LPDWORD pcbSize)396 HRESULT CRegPropertyBag::_GetStreamSize(IStream *pStream, LPDWORD pcbSize)
397 {
398     *pcbSize = 0;
399 
400     ULARGE_INTEGER ui;
401     HRESULT hr = IStream_Size(pStream, &ui);
402     if (FAILED(hr))
403         return hr;
404 
405     if (ui.DUMMYSTRUCTNAME.HighPart)
406         return E_FAIL; /* 64-bit value is not supported */
407 
408     *pcbSize = ui.DUMMYSTRUCTNAME.LowPart;
409     return hr;
410 }
411 
412 STDMETHODIMP
Read(_In_z_ LPCWSTR pszPropName,_Inout_ VARIANT * pvari,_Inout_opt_ IErrorLog * pErrorLog)413 CRegPropertyBag::Read(
414     _In_z_ LPCWSTR pszPropName,
415     _Inout_ VARIANT *pvari,
416     _Inout_opt_ IErrorLog *pErrorLog)
417 {
418     UNREFERENCED_PARAMETER(pErrorLog);
419 
420     TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
421 
422     if (!MODE_CAN_READ(m_dwMode))
423     {
424         ERR("%p: 0x%X\n", this, m_dwMode);
425         ::VariantInit(pvari);
426         return E_ACCESSDENIED;
427     }
428 
429     VARTYPE vt = V_VT(pvari);
430     VariantInit(pvari);
431 
432     HRESULT hr;
433     DWORD dwType, cbValue;
434     LONG error = SHGetValueW(m_hKey, NULL, pszPropName, &dwType, NULL, &cbValue);
435     if (error != ERROR_SUCCESS)
436         hr = E_FAIL;
437     else if (dwType == REG_SZ)
438         hr = _ReadString(pszPropName, pvari, cbValue);
439     else if (dwType == REG_BINARY)
440         hr = _ReadBinary(pszPropName, pvari, vt, cbValue);
441     else if (dwType == REG_DWORD)
442         hr = _ReadDword(pszPropName, pvari);
443     else
444         hr = E_FAIL;
445 
446     if (FAILED(hr))
447     {
448         ERR("%p: 0x%08X %ld: %s %p\n", this, hr, dwType, debugstr_w(pszPropName), pvari);
449         ::VariantInit(pvari);
450         return hr;
451     }
452 
453     hr = ::VariantChangeTypeForRead(pvari, vt);
454     if (FAILED(hr))
455     {
456         ERR("%p: 0x%08X %ld: %s %p\n", this, hr, dwType, debugstr_w(pszPropName), pvari);
457         ::VariantInit(pvari);
458     }
459 
460     return hr;
461 }
462 
463 HRESULT
_WriteStream(LPCWSTR pszPropName,IStream * pStream)464 CRegPropertyBag::_WriteStream(LPCWSTR pszPropName, IStream *pStream)
465 {
466     DWORD cbData;
467     HRESULT hr = _GetStreamSize(pStream, &cbData);
468     if (FAILED(hr) || !cbData)
469         return hr;
470 
471     DWORD cbBinary = cbData + sizeof(GUID);
472     LPBYTE pbBinary = (LPBYTE)::LocalAlloc(LMEM_ZEROINIT, cbBinary);
473     if (!pbBinary)
474         return E_OUTOFMEMORY;
475 
476     hr = _CopyStreamIntoBuff(pStream, pbBinary + sizeof(GUID), cbData);
477     if (SUCCEEDED(hr))
478     {
479         if (SHSetValueW(m_hKey, NULL, pszPropName, REG_BINARY, pbBinary, cbBinary))
480             hr = E_FAIL;
481     }
482 
483     ::LocalFree(pbBinary);
484     return hr;
485 }
486 
487 STDMETHODIMP
Write(_In_z_ LPCWSTR pszPropName,_In_ VARIANT * pvari)488 CRegPropertyBag::Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari)
489 {
490     TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
491 
492     if (!MODE_CAN_WRITE(m_dwMode))
493     {
494         ERR("%p: 0x%X\n", this, m_dwMode);
495         return E_ACCESSDENIED;
496     }
497 
498     HRESULT hr;
499     LONG error;
500     VARIANTARG vargTemp = { 0 };
501     switch (V_VT(pvari))
502     {
503         case VT_EMPTY:
504             SHDeleteValueW(m_hKey, NULL, pszPropName);
505             hr = S_OK;
506             break;
507 
508         case VT_BOOL:
509         case VT_I1:
510         case VT_I2:
511         case VT_I4:
512         case VT_UI1:
513         case VT_UI2:
514         case VT_UI4:
515         case VT_INT:
516         case VT_UINT:
517         {
518             hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_UI4);
519             if (FAILED(hr))
520                 return hr;
521 
522             error = SHSetValueW(m_hKey, NULL, pszPropName, REG_DWORD, &V_UI4(&vargTemp), sizeof(DWORD));
523             if (error)
524                 hr = E_FAIL;
525 
526             ::VariantClear(&vargTemp);
527             break;
528         }
529 
530         case VT_UNKNOWN:
531         {
532             CComPtr<IStream> pStream;
533             hr = V_UNKNOWN(pvari)->QueryInterface(IID_IStream, (void **)&pStream);
534             if (FAILED(hr))
535                 return hr;
536 
537             hr = _WriteStream(pszPropName, pStream);
538             break;
539         }
540 
541         default:
542         {
543             hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_BSTR);
544             if (FAILED(hr))
545                 return hr;
546 
547             int cch = lstrlenW(V_BSTR(&vargTemp));
548             DWORD cb = (cch + 1) * sizeof(WCHAR);
549             error = SHSetValueW(m_hKey, NULL, pszPropName, REG_SZ, V_BSTR(&vargTemp), cb);
550             if (error)
551                 hr = E_FAIL;
552 
553             ::VariantClear(&vargTemp);
554             break;
555         }
556     }
557 
558     return hr;
559 }
560 
561 /**************************************************************************
562  *  SHCreatePropertyBagOnRegKey (SHLWAPI.471)
563  *
564  * Creates a property bag object on registry key.
565  *
566  * @param hKey       The registry key.
567  * @param pszSubKey  The path of the sub-key.
568  * @param dwMode     The combination of STGM_READ, STGM_WRITE, STGM_READWRITE, and STGM_CREATE.
569  * @param riid       Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
570  * @param ppvObj     Receives an IPropertyBag pointer.
571  * @return           An HRESULT value. S_OK on success, non-zero on failure.
572  * @see              https://source.winehq.org/WineAPI/SHCreatePropertyBagOnRegKey.html
573  */
574 EXTERN_C HRESULT WINAPI
SHCreatePropertyBagOnRegKey(_In_ HKEY hKey,_In_z_ LPCWSTR pszSubKey,_In_ DWORD dwMode,_In_ REFIID riid,_Out_ void ** ppvObj)575 SHCreatePropertyBagOnRegKey(
576     _In_ HKEY hKey,
577     _In_z_ LPCWSTR pszSubKey,
578     _In_ DWORD dwMode,
579     _In_ REFIID riid,
580     _Out_ void **ppvObj)
581 {
582     TRACE("%p, %s, 0x%08X, %s, %p\n", hKey, debugstr_w(pszSubKey), dwMode,
583           debugstr_guid(&riid), ppvObj);
584 
585     *ppvObj = NULL;
586 
587     CComPtr<CRegPropertyBag> pRegBag(new CRegPropertyBag(dwMode));
588 
589     HRESULT hr = pRegBag->Init(hKey, pszSubKey);
590     if (FAILED(hr))
591         return hr;
592 
593     return pRegBag->QueryInterface(riid, ppvObj);
594 }
595 
596 /**************************************************************************
597  *  SHGetIniStringW (SHLWAPI.294)
598  *
599  * @see https://source.winehq.org/WineAPI/SHGetIniStringW.html
600  */
601 EXTERN_C DWORD WINAPI
602 SHGetIniStringW(
603     _In_z_ LPCWSTR appName,
604     _In_z_ LPCWSTR keyName,
605     _Out_writes_to_(outLen, return + 1) LPWSTR out,
606     _In_ DWORD outLen,
607     _In_z_ LPCWSTR filename)
608 {
609     TRACE("(%s,%s,%p,%08x,%s)\n", debugstr_w(appName), debugstr_w(keyName),
610           out, outLen, debugstr_w(filename));
611 
612     if (outLen == 0)
613         return 0;
614 
615     // Try ".W"-appended section name. See also SHSetIniStringW
616     CStringW szSection(appName);
617     szSection += L".W";
618     CStringW pszWideBuff;
619     const INT cchWideMax = 4 * MAX_PATH; // UTF-7 needs 4 times length buffer.
620     GetPrivateProfileStringW(szSection, keyName, NULL,
621                              pszWideBuff.GetBuffer(cchWideMax), cchWideMax, filename);
622     pszWideBuff.ReleaseBuffer();
623 
624     if (pszWideBuff.IsEmpty()) // It's empty or not found
625     {
626         // Try the normal section name
627         return GetPrivateProfileStringW(appName, keyName, NULL, out, outLen, filename);
628     }
629 
630     // Okay, now ".W" version is valid. Its value is a UTF-7 string in UTF-16
631     CW2A wide2utf7(pszWideBuff);
632     MultiByteToWideChar(CP_UTF7, 0, wide2utf7, -1, out, outLen);
633     out[outLen - 1] = UNICODE_NULL;
634 
635     return lstrlenW(out);
636 }
637 
Is7BitClean(LPCWSTR psz)638 static BOOL Is7BitClean(LPCWSTR psz)
639 {
640     if (!psz)
641         return TRUE;
642 
643     while (*psz)
644     {
645         if (*psz > 0x7F)
646             return FALSE;
647         ++psz;
648     }
649     return TRUE;
650 }
651 
652 /**************************************************************************
653  *  SHSetIniStringW (SHLWAPI.295)
654  *
655  * @see https://source.winehq.org/WineAPI/SHSetIniStringW.html
656  */
657 EXTERN_C BOOL WINAPI
SHSetIniStringW(_In_z_ LPCWSTR appName,_In_z_ LPCWSTR keyName,_In_opt_z_ LPCWSTR str,_In_z_ LPCWSTR filename)658 SHSetIniStringW(
659     _In_z_ LPCWSTR appName,
660     _In_z_ LPCWSTR keyName,
661     _In_opt_z_ LPCWSTR str,
662     _In_z_ LPCWSTR filename)
663 {
664     TRACE("(%s, %p, %s, %s)\n", debugstr_w(appName), keyName, debugstr_w(str),
665           debugstr_w(filename));
666 
667     // Write a normal profile string. If str was NULL, then key will be deleted
668     if (!WritePrivateProfileStringW(appName, keyName, str, filename))
669         return FALSE;
670 
671     if (Is7BitClean(str))
672     {
673         // Delete ".A" version
674         CStringW szSection(appName);
675         szSection += L".A";
676         WritePrivateProfileStringW(szSection, keyName, NULL, filename);
677 
678         // Delete ".W" version
679         szSection = appName;
680         szSection += L".W";
681         WritePrivateProfileStringW(szSection, keyName, NULL, filename);
682 
683         return TRUE;
684     }
685 
686     // Now str is not 7-bit clean. It needs UTF-7 encoding in UTF-16.
687     // We write ".A" and ".W"-appended sections
688     CW2A wide2utf7(str, CP_UTF7);
689     CA2W utf72wide(wide2utf7, CP_ACP);
690 
691     BOOL ret = TRUE;
692 
693     // Write ".A" version
694     CStringW szSection(appName);
695     szSection += L".A";
696     if (!WritePrivateProfileStringW(szSection, keyName, str, filename))
697         ret = FALSE;
698 
699     // Write ".W" version
700     szSection = appName;
701     szSection += L".W";
702     if (!WritePrivateProfileStringW(szSection, keyName, utf72wide, filename))
703         ret = FALSE;
704 
705     return ret;
706 }
707 
708 /**************************************************************************
709  *  SHGetIniStringUTF7W (SHLWAPI.473)
710  *
711  * Retrieves a string value from an INI file.
712  *
713  * @param lpAppName         The section name.
714  * @param lpKeyName         The key name.
715  *                          If this string begins from '@', the value will be interpreted as UTF-7.
716  * @param lpReturnedString  Receives a wide string value.
717  * @param nSize             The number of characters in lpReturnedString.
718  * @param lpFileName        The INI file.
719  * @return                  The number of characters copied to the buffer if succeeded.
720  */
721 EXTERN_C DWORD WINAPI
722 SHGetIniStringUTF7W(
723     _In_opt_z_ LPCWSTR lpAppName,
724     _In_z_ LPCWSTR lpKeyName,
725     _Out_writes_to_(nSize, return + 1) _Post_z_ LPWSTR lpReturnedString,
726     _In_ DWORD nSize,
727     _In_z_ LPCWSTR lpFileName)
728 {
729     if (*lpKeyName == L'@') // UTF-7
730         return SHGetIniStringW(lpAppName, lpKeyName + 1, lpReturnedString, nSize, lpFileName);
731 
732     return GetPrivateProfileStringW(lpAppName, lpKeyName, L"", lpReturnedString, nSize, lpFileName);
733 }
734 
735 /**************************************************************************
736  *  SHSetIniStringUTF7W (SHLWAPI.474)
737  *
738  * Sets a string value on an INI file.
739  *
740  * @param lpAppName   The section name.
741  * @param lpKeyName   The key name.
742  *                    If this begins from '@', the value will be stored as UTF-7.
743  * @param lpString    The wide string value to be set.
744  * @param lpFileName  The INI file.
745  * @return            TRUE if successful. FALSE if failed.
746  */
747 EXTERN_C BOOL WINAPI
SHSetIniStringUTF7W(_In_z_ LPCWSTR lpAppName,_In_z_ LPCWSTR lpKeyName,_In_opt_z_ LPCWSTR lpString,_In_z_ LPCWSTR lpFileName)748 SHSetIniStringUTF7W(
749     _In_z_ LPCWSTR lpAppName,
750     _In_z_ LPCWSTR lpKeyName,
751     _In_opt_z_ LPCWSTR lpString,
752     _In_z_ LPCWSTR lpFileName)
753 {
754     if (*lpKeyName == L'@') // UTF-7
755         return SHSetIniStringW(lpAppName, lpKeyName + 1, lpString, lpFileName);
756 
757     return WritePrivateProfileStringW(lpAppName, lpKeyName, lpString, lpFileName);
758 }
759 
760 class CIniPropertyBag : public CBasePropertyBag
761 {
762 protected:
763     LPWSTR m_pszFileName;
764     LPWSTR m_pszSection;
765     BOOL m_bAlternateStream; // ADS (Alternate Data Stream)
766 
LooksLikeAnAlternateStream(LPCWSTR pszStart)767     static BOOL LooksLikeAnAlternateStream(LPCWSTR pszStart)
768     {
769         LPCWSTR pch = StrRChrW(pszStart, NULL, L'\\');
770         if (!pch)
771             pch = pszStart;
772         return StrChrW(pch, L':') != NULL;
773     }
774 
775     HRESULT
776     _GetSectionAndName(
777         LPCWSTR pszStart,
778         LPWSTR pszSection,
779         UINT cchSectionMax,
780         LPWSTR pszName,
781         UINT cchNameMax);
782 
783 public:
CIniPropertyBag(DWORD dwMode)784     CIniPropertyBag(DWORD dwMode)
785         : CBasePropertyBag(dwMode)
786         , m_pszFileName(NULL)
787         , m_pszSection(NULL)
788         , m_bAlternateStream(FALSE)
789     {
790     }
791 
~CIniPropertyBag()792     ~CIniPropertyBag() override
793     {
794         ::LocalFree(m_pszFileName);
795         ::LocalFree(m_pszSection);
796     }
797 
798     HRESULT Init(LPCWSTR pszIniFile, LPCWSTR pszSection);
799 
800     STDMETHODIMP Read(
801         _In_z_ LPCWSTR pszPropName,
802         _Inout_ VARIANT *pvari,
803         _Inout_opt_ IErrorLog *pErrorLog) override;
804 
805     STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
806 };
807 
Init(LPCWSTR pszIniFile,LPCWSTR pszSection)808 HRESULT CIniPropertyBag::Init(LPCWSTR pszIniFile, LPCWSTR pszSection)
809 {
810     m_pszFileName = StrDupW(pszIniFile);
811     if (!m_pszFileName)
812         return E_OUTOFMEMORY;
813 
814     // Is it an ADS (Alternate Data Stream) pathname?
815     m_bAlternateStream = LooksLikeAnAlternateStream(m_pszFileName);
816 
817     if (pszSection)
818     {
819         m_pszSection = StrDupW(pszSection);
820         if (!m_pszSection)
821             return E_OUTOFMEMORY;
822     }
823 
824     return S_OK;
825 }
826 
827 HRESULT
_GetSectionAndName(LPCWSTR pszStart,LPWSTR pszSection,UINT cchSectionMax,LPWSTR pszName,UINT cchNameMax)828 CIniPropertyBag::_GetSectionAndName(
829     LPCWSTR pszStart,
830     LPWSTR pszSection,
831     UINT cchSectionMax,
832     LPWSTR pszName,
833     UINT cchNameMax)
834 {
835     LPCWSTR pchSep = StrChrW(pszStart, L'\\');
836     if (pchSep)
837     {
838         UINT cchSep = (UINT)(pchSep - pszStart + 1);
839         StrCpyNW(pszSection, pszStart, min(cchSep, cchSectionMax));
840         StrCpyNW(pszName, pchSep + 1, cchNameMax);
841         return S_OK;
842     }
843 
844     if (m_pszSection)
845     {
846         StrCpyNW(pszSection, m_pszSection, cchSectionMax);
847         StrCpyNW(pszName, pszStart, cchNameMax);
848         return S_OK;
849     }
850 
851     ERR("%p: %s\n", this, debugstr_w(pszStart));
852     return E_INVALIDARG;
853 }
854 
855 STDMETHODIMP
Read(_In_z_ LPCWSTR pszPropName,_Inout_ VARIANT * pvari,_Inout_opt_ IErrorLog * pErrorLog)856 CIniPropertyBag::Read(
857     _In_z_ LPCWSTR pszPropName,
858     _Inout_ VARIANT *pvari,
859     _Inout_opt_ IErrorLog *pErrorLog)
860 {
861     UNREFERENCED_PARAMETER(pErrorLog);
862 
863     TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
864 
865     VARTYPE vt = V_VT(pvari);
866 
867     ::VariantInit(pvari);
868 
869     if (!MODE_CAN_READ(m_dwMode))
870     {
871         ERR("%p: 0x%X\n", this, m_dwMode);
872         return E_ACCESSDENIED;
873     }
874 
875     WCHAR szSection[64], szName[64];
876     HRESULT hr =
877         _GetSectionAndName(pszPropName, szSection, _countof(szSection), szName, _countof(szName));
878     if (FAILED(hr))
879         return hr;
880 
881     const INT cchBuffMax = 4 * MAX_PATH; // UTF-7 needs 4 times length buffer.
882     CComHeapPtr<WCHAR> pszBuff;
883     if (!pszBuff.Allocate(cchBuffMax * sizeof(WCHAR)))
884         return E_OUTOFMEMORY;
885 
886     if (!SHGetIniStringUTF7W(szSection, szName, pszBuff, cchBuffMax, m_pszFileName))
887         return E_FAIL;
888 
889     BSTR bstr = ::SysAllocString(pszBuff);
890     V_BSTR(pvari) = bstr;
891     if (!bstr)
892         return E_OUTOFMEMORY;
893 
894     V_VT(pvari) = VT_BSTR;
895     return ::VariantChangeTypeForRead(pvari, vt);
896 }
897 
898 STDMETHODIMP
Write(_In_z_ LPCWSTR pszPropName,_In_ VARIANT * pvari)899 CIniPropertyBag::Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari)
900 {
901     TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
902 
903     if (!MODE_CAN_WRITE(m_dwMode))
904     {
905         ERR("%p: 0x%X\n", this, m_dwMode);
906         return E_ACCESSDENIED;
907     }
908 
909     HRESULT hr;
910     BSTR bstr;
911     VARIANTARG vargTemp = { 0 };
912     switch (V_VT(pvari))
913     {
914         case VT_EMPTY:
915             bstr = NULL;
916             break;
917 
918         case VT_BSTR:
919             bstr = V_BSTR(pvari);
920             break;
921 
922         default:
923             hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_BSTR);
924             if (FAILED(hr))
925                 goto Quit;
926 
927             bstr = V_BSTR(&vargTemp);
928             break;
929     }
930 
931     WCHAR szSection[64], szName[64];
932     hr = _GetSectionAndName(pszPropName, szSection, _countof(szSection), szName, _countof(szName));
933     if (SUCCEEDED(hr))
934     {
935         if (SHSetIniStringUTF7W(szSection, szName, bstr, m_pszFileName))
936         {
937             if (!m_bAlternateStream)
938                 SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, m_pszFileName, NULL);
939         }
940         else
941         {
942             hr = E_FAIL;
943         }
944     }
945 
946 Quit:
947     ::VariantClear(&vargTemp);
948     return hr;
949 }
950 
951 /**************************************************************************
952  *  SHCreatePropertyBagOnProfileSection (SHLWAPI.472)
953  *
954  * Creates a property bag object on INI file.
955  *
956  * @param lpFileName  The INI filename.
957  * @param pszSection  The optional section name.
958  * @param dwMode      The combination of STGM_READ, STGM_WRITE, STGM_READWRITE, and STGM_CREATE.
959  * @param riid        Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
960  * @param ppvObj      Receives an IPropertyBag pointer.
961  * @return            An HRESULT value. S_OK on success, non-zero on failure.
962  * @see               https://www.geoffchappell.com/studies/windows/shell/shlwapi/api/propbag/createonprofilesection.htm
963  */
964 EXTERN_C HRESULT WINAPI
SHCreatePropertyBagOnProfileSection(_In_z_ LPCWSTR lpFileName,_In_opt_z_ LPCWSTR pszSection,_In_ DWORD dwMode,_In_ REFIID riid,_Out_ void ** ppvObj)965 SHCreatePropertyBagOnProfileSection(
966     _In_z_ LPCWSTR lpFileName,
967     _In_opt_z_ LPCWSTR pszSection,
968     _In_ DWORD dwMode,
969     _In_ REFIID riid,
970     _Out_ void **ppvObj)
971 {
972     HANDLE hFile;
973     PWCHAR pchFileTitle;
974     WCHAR szBuff[MAX_PATH];
975 
976     if (dwMode & STGM_CREATE)
977     {
978         hFile = ::CreateFileW(lpFileName, 0, FILE_SHARE_DELETE, 0, CREATE_NEW,
979                               FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM, NULL);
980         if (hFile != INVALID_HANDLE_VALUE)
981         {
982             pchFileTitle = PathFindFileNameW(lpFileName);
983             if (lstrcmpiW(pchFileTitle, L"desktop.ini") == 0)
984             {
985                 StrCpyNW(szBuff, lpFileName, _countof(szBuff));
986                 if (PathRemoveFileSpecW(szBuff))
987                     PathMakeSystemFolderW(szBuff);
988             }
989             ::CloseHandle(hFile);
990         }
991     }
992 
993     *ppvObj = NULL;
994 
995     if (!PathFileExistsW(lpFileName))
996         return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
997 
998     CComPtr<CIniPropertyBag> pIniPB(new CIniPropertyBag(dwMode));
999 
1000     HRESULT hr = pIniPB->Init(lpFileName, pszSection);
1001     if (FAILED(hr))
1002     {
1003         ERR("0x%08X\n", hr);
1004         return hr;
1005     }
1006 
1007     return pIniPB->QueryInterface(riid, ppvObj);
1008 }
1009 
1010 class CDesktopUpgradePropertyBag : public CBasePropertyBag
1011 {
1012 protected:
1013     BOOL _AlreadyUpgraded(HKEY hKey);
1014     VOID _MarkAsUpgraded(HKEY hkey);
1015     HRESULT _ReadFlags(VARIANT *pvari);
1016     HRESULT _ReadItemPositions(VARIANT *pvari);
1017     IStream* _GetOldDesktopViewStream();
1018     IStream* _NewStreamFromOld(IStream *pOldStream);
1019 
1020 public:
CDesktopUpgradePropertyBag()1021     CDesktopUpgradePropertyBag() : CBasePropertyBag(STGM_READ) { }
1022 
1023     STDMETHODIMP Read(
1024         _In_z_ LPCWSTR pszPropName,
1025         _Inout_ VARIANT *pvari,
1026         _Inout_opt_ IErrorLog *pErrorLog) override;
1027 
Write(_In_z_ LPCWSTR pszPropName,_In_ VARIANT * pvari)1028     STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override
1029     {
1030         ERR("%p: %s: Read-only\n", this, debugstr_w(pszPropName));
1031         return E_NOTIMPL;
1032     }
1033 };
1034 
_MarkAsUpgraded(HKEY hkey)1035 VOID CDesktopUpgradePropertyBag::_MarkAsUpgraded(HKEY hkey)
1036 {
1037     DWORD dwValue = TRUE;
1038     SHSetValueW(hkey, NULL, L"Upgrade", REG_DWORD, &dwValue, sizeof(dwValue));
1039 }
1040 
_AlreadyUpgraded(HKEY hKey)1041 BOOL CDesktopUpgradePropertyBag::_AlreadyUpgraded(HKEY hKey)
1042 {
1043     // Check the existence of the value written in _MarkAsUpgraded.
1044     DWORD dwValue, cbData = sizeof(dwValue);
1045     return SHGetValueW(hKey, NULL, L"Upgrade", NULL, &dwValue, &cbData) == ERROR_SUCCESS;
1046 }
1047 
1048 typedef DWORDLONG DESKVIEW_FLAGS; // 64-bit data
1049 
_ReadFlags(VARIANT * pvari)1050 HRESULT CDesktopUpgradePropertyBag::_ReadFlags(VARIANT *pvari)
1051 {
1052     DESKVIEW_FLAGS Flags;
1053     DWORD cbValue = sizeof(Flags);
1054     if (SHGetValueW(HKEY_CURRENT_USER,
1055                     L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\DeskView",
1056                     L"Settings",
1057                     NULL,
1058                     &Flags,
1059                     &cbValue) != ERROR_SUCCESS || cbValue < sizeof(Flags))
1060     {
1061         return E_FAIL;
1062     }
1063 
1064     V_UINT(pvari) = ((UINT)(Flags >> 32)) | 0x220; // FIXME: Magic number
1065     V_VT(pvari) = VT_UINT;
1066     return S_OK;
1067 }
1068 
1069 typedef struct tagOLD_STREAM_HEADER
1070 {
1071     WORD wMagic;
1072     WORD awUnknown[6];
1073     WORD wSize;
1074 } OLD_STREAM_HEADER, *POLD_STREAM_HEADER;
1075 
_NewStreamFromOld(IStream * pOldStream)1076 IStream* CDesktopUpgradePropertyBag::_NewStreamFromOld(IStream *pOldStream)
1077 {
1078     OLD_STREAM_HEADER Header;
1079     HRESULT hr = pOldStream->Read(&Header, sizeof(Header), NULL);
1080     if (FAILED(hr) || Header.wMagic != 28)
1081         return NULL;
1082 
1083     // Move stream pointer
1084     LARGE_INTEGER li;
1085     li.QuadPart = Header.wSize - sizeof(Header);
1086     hr = pOldStream->Seek(li, STREAM_SEEK_CUR, NULL);
1087     if (FAILED(hr))
1088         return NULL;
1089 
1090     // Get the size
1091     ULARGE_INTEGER uli;
1092     hr = IStream_Size(pOldStream, &uli);
1093     if (FAILED(hr))
1094         return NULL;
1095 
1096     // Create new stream and attach
1097     CComPtr<IStream> pNewStream;
1098     pNewStream.Attach(SHCreateMemStream(NULL, 0));
1099     if (!pNewStream)
1100         return NULL;
1101 
1102     // Subtract Header.wSize from the size
1103     uli.QuadPart -= Header.wSize;
1104 
1105     // Copy to pNewStream
1106     hr = pOldStream->CopyTo(pNewStream, uli, NULL, NULL);
1107     if (FAILED(hr))
1108         return NULL;
1109 
1110     li.QuadPart = 0;
1111     pNewStream->Seek(li, STREAM_SEEK_SET, NULL);
1112 
1113     return pNewStream.Detach();
1114 }
1115 
_GetOldDesktopViewStream()1116 IStream* CDesktopUpgradePropertyBag::_GetOldDesktopViewStream()
1117 {
1118     HKEY hKey = SHGetShellKey(SHKEY_Root_HKCU, L"Streams\\Desktop", FALSE);
1119     if (!hKey)
1120         return NULL;
1121 
1122     CComPtr<IStream> pOldStream;
1123     if (!_AlreadyUpgraded(hKey))
1124     {
1125         pOldStream.Attach(SHOpenRegStream2W(hKey, NULL, L"ViewView2", 0));
1126         if (pOldStream)
1127         {
1128             ULARGE_INTEGER uli;
1129             HRESULT hr = IStream_Size(pOldStream, &uli);
1130             if (SUCCEEDED(hr) && !uli.QuadPart)
1131                 pOldStream.Release();
1132         }
1133 
1134         if (!pOldStream)
1135             pOldStream.Attach(SHOpenRegStream2W(hKey, NULL, L"ViewView", 0));
1136 
1137         _MarkAsUpgraded(hKey);
1138     }
1139 
1140     ::RegCloseKey(hKey);
1141     return pOldStream.Detach();
1142 }
1143 
_ReadItemPositions(VARIANT * pvari)1144 HRESULT CDesktopUpgradePropertyBag::_ReadItemPositions(VARIANT *pvari)
1145 {
1146     CComPtr<IStream> pOldStream;
1147     pOldStream.Attach(_GetOldDesktopViewStream());
1148     if (!pOldStream)
1149         return E_FAIL;
1150 
1151     HRESULT hr = E_FAIL;
1152     IStream *pNewStream = _NewStreamFromOld(pOldStream);
1153     if (pNewStream)
1154     {
1155         V_UNKNOWN(pvari) = pNewStream;
1156         V_VT(pvari) = VT_UNKNOWN;
1157         hr = S_OK;
1158     }
1159 
1160     return hr;
1161 }
1162 
1163 STDMETHODIMP
Read(_In_z_ LPCWSTR pszPropName,_Inout_ VARIANT * pvari,_Inout_opt_ IErrorLog * pErrorLog)1164 CDesktopUpgradePropertyBag::Read(
1165     _In_z_ LPCWSTR pszPropName,
1166     _Inout_ VARIANT *pvari,
1167     _Inout_opt_ IErrorLog *pErrorLog)
1168 {
1169     UNREFERENCED_PARAMETER(pErrorLog);
1170 
1171     VARTYPE vt = V_VT(pvari);
1172 
1173     HRESULT hr = E_FAIL;
1174     if (StrCmpW(L"FFlags", pszPropName) == 0)
1175         hr = _ReadFlags(pvari);
1176     else if (StrCmpNW(L"ItemPos", pszPropName, 7) == 0)
1177         hr = _ReadItemPositions(pvari);
1178 
1179     if (FAILED(hr))
1180     {
1181         ::VariantInit(pvari);
1182         return hr;
1183     }
1184 
1185     return ::VariantChangeType(pvari, pvari, 0, vt);
1186 }
1187 
1188 /**************************************************************************
1189  *  SHGetDesktopUpgradePropertyBag (Internal)
1190  *
1191  * Creates or gets a property bag object for desktop upgrade
1192  *
1193  * @param riid    Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
1194  * @param ppvObj  Receives an IPropertyBag pointer.
1195  * @return        An HRESULT value. S_OK on success, non-zero on failure.
1196  */
SHGetDesktopUpgradePropertyBag(REFIID riid,void ** ppvObj)1197 HRESULT SHGetDesktopUpgradePropertyBag(REFIID riid, void **ppvObj)
1198 {
1199     *ppvObj = NULL;
1200     CComPtr<CDesktopUpgradePropertyBag> pPropBag(new CDesktopUpgradePropertyBag());
1201     return pPropBag->QueryInterface(riid, ppvObj);
1202 }
1203 
1204 class CViewStatePropertyBag : public CBasePropertyBag
1205 {
1206 protected:
1207     LPITEMIDLIST m_pidl = NULL;
1208     LPWSTR m_pszPath = NULL;
1209     DWORD m_dwVspbFlags = 0; // SHGVSPB_... flags
1210     CComPtr<IPropertyBag> m_pPidlBag;
1211     CComPtr<IPropertyBag> m_pUpgradeBag;
1212     CComPtr<IPropertyBag> m_pInheritBag;
1213     CComPtr<IPropertyBag> m_pUserDefaultsBag;
1214     CComPtr<IPropertyBag> m_pFolderDefaultsBag;
1215     CComPtr<IPropertyBag> m_pGlobalDefaultsBag;
1216     CComPtr<IPropertyBag> m_pReadBag;
1217     CComPtr<IPropertyBag> m_pWriteBag;
1218     BOOL m_bPidlBag = FALSE;
1219     BOOL m_bUpgradeBag = FALSE;
1220     BOOL m_bInheritBag = FALSE;
1221     BOOL m_bUserDefaultsBag = FALSE;
1222     BOOL m_bFolderDefaultsBag = FALSE;
1223     BOOL m_bGlobalDefaultsBag = FALSE;
1224     BOOL m_bReadBag = FALSE;
1225     BOOL m_bWriteBag = FALSE;
1226 
1227     BOOL _IsSamePidl(LPCITEMIDLIST pidlOther) const;
1228     BOOL _IsSystemFolder() const;
1229     BOOL _CanAccessPidlBag() const;
1230     BOOL _CanAccessUserDefaultsBag() const;
1231     BOOL _CanAccessFolderDefaultsBag() const;
1232     BOOL _CanAccessGlobalDefaultsBag() const;
1233     BOOL _CanAccessInheritBag() const;
1234     BOOL _CanAccessUpgradeBag() const;
1235 
1236     HKEY _GetHKey(DWORD dwVspbFlags);
1237 
1238     UINT _GetMRUSize(HKEY hKey);
1239 
1240     HRESULT _GetMRUSlots(
1241         LPCITEMIDLIST pidl,
1242         DWORD dwMode,
1243         HKEY hKey,
1244         UINT *puSlots,
1245         UINT cSlots,
1246         UINT *pcSlots);
1247 
1248     HRESULT _GetMRUSlot(LPCITEMIDLIST pidl, DWORD dwMode, HKEY hKey, UINT *pSlot);
1249 
1250     HRESULT _GetRegKey(
1251         LPCITEMIDLIST pidl,
1252         LPCWSTR pszBagName,
1253         DWORD dwFlags,
1254         DWORD dwMode,
1255         HKEY hKey,
1256         LPWSTR pszDest,
1257         INT cchDest);
1258 
1259     HRESULT _CreateBag(
1260         LPITEMIDLIST pidl,
1261         LPCWSTR pszPath,
1262         DWORD dwVspbFlags,
1263         DWORD dwMode,
1264         REFIID riid,
1265         IPropertyBag **pppb);
1266 
1267     HRESULT _FindNearestInheritBag(REFIID riid, IPropertyBag **pppb);
1268 
1269     void _ResetTryAgainFlag();
1270 
1271     BOOL _EnsureReadBag(DWORD dwMode, REFIID riid);
1272     BOOL _EnsurePidlBag(DWORD dwMode, REFIID riid);
1273     BOOL _EnsureInheritBag(DWORD dwMode, REFIID riid);
1274     BOOL _EnsureUpgradeBag(DWORD dwMode, REFIID riid);
1275     BOOL _EnsureUserDefaultsBag(DWORD dwMode, REFIID riid);
1276     BOOL _EnsureFolderDefaultsBag(DWORD dwMode, REFIID riid);
1277     BOOL _EnsureGlobalDefaultsBag(DWORD dwMode, REFIID riid);
1278     BOOL _EnsureWriteBag(DWORD dwMode, REFIID riid);
1279     HRESULT _ReadPidlBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
1280     HRESULT _ReadInheritBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
1281     HRESULT _ReadUpgradeBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
1282     HRESULT _ReadUserDefaultsBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
1283     HRESULT _ReadFolderDefaultsBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
1284     HRESULT _ReadGlobalDefaultsBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
1285     void _PruneMRUTree();
1286 
1287 public:
CViewStatePropertyBag()1288     CViewStatePropertyBag() : CBasePropertyBag(STGM_READ) { }
1289 
~CViewStatePropertyBag()1290     ~CViewStatePropertyBag() override
1291     {
1292         ::ILFree(m_pidl);
1293         ::LocalFree(m_pszPath);
1294     }
1295 
1296     HRESULT Init(_In_opt_ LPCITEMIDLIST pidl, _In_opt_ LPCWSTR pszPath, _In_ DWORD dwVspbFlags);
1297     BOOL IsSameBag(LPCITEMIDLIST pidl, LPCWSTR pszPath, DWORD dwVspbFlags) const;
1298 
1299     STDMETHODIMP Read(
1300         _In_z_ LPCWSTR pszPropName,
1301         _Inout_ VARIANT *pvari,
1302         _Inout_opt_ IErrorLog *pErrorLog) override;
1303 
1304     STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
1305 };
1306 
1307 // CViewStatePropertyBag is cached
1308 CComPtr<CViewStatePropertyBag> g_pCachedBag;
1309 extern "C"
1310 {
1311     CRITICAL_SECTION g_csBagCacheLock;
1312 }
1313 
1314 HRESULT
Init(_In_opt_ LPCITEMIDLIST pidl,_In_opt_ LPCWSTR pszPath,_In_ DWORD dwVspbFlags)1315 CViewStatePropertyBag::Init(
1316     _In_opt_ LPCITEMIDLIST pidl,
1317     _In_opt_ LPCWSTR pszPath,
1318     _In_ DWORD dwVspbFlags)
1319 {
1320     if (pidl)
1321     {
1322         m_pidl = ILClone(pidl);
1323         if (!m_pidl)
1324             return E_OUTOFMEMORY;
1325     }
1326 
1327     if (pszPath)
1328     {
1329         m_pszPath = StrDupW(pszPath);
1330         if (!m_pszPath)
1331             return E_OUTOFMEMORY;
1332 
1333         m_dwVspbFlags = dwVspbFlags;
1334     }
1335 
1336     return S_OK;
1337 }
1338 
_IsSamePidl(LPCITEMIDLIST pidlOther) const1339 BOOL CViewStatePropertyBag::_IsSamePidl(LPCITEMIDLIST pidlOther) const
1340 {
1341     if (!pidlOther && !m_pidl)
1342         return TRUE;
1343 
1344     return (pidlOther && m_pidl && ILIsEqual(pidlOther, m_pidl));
1345 }
1346 
IsSameBag(LPCITEMIDLIST pidl,LPCWSTR pszPath,DWORD dwVspbFlags) const1347 BOOL CViewStatePropertyBag::IsSameBag(LPCITEMIDLIST pidl, LPCWSTR pszPath, DWORD dwVspbFlags) const
1348 {
1349     return (dwVspbFlags == m_dwVspbFlags && StrCmpW(pszPath, m_pszPath) == 0 && _IsSamePidl(pidl));
1350 }
1351 
_IsSystemFolder() const1352 BOOL CViewStatePropertyBag::_IsSystemFolder() const
1353 {
1354     LPCITEMIDLIST ppidlLast;
1355     CComPtr<IShellFolder> psf;
1356 
1357     HRESULT hr = SHBindToParent(m_pidl, IID_IShellFolder, (void **)&psf, &ppidlLast);
1358     if (FAILED(hr))
1359         return FALSE;
1360 
1361     WIN32_FIND_DATAW FindData;
1362     hr = SHGetDataFromIDListW(psf, ppidlLast, SHGDFIL_FINDDATA, &FindData, sizeof(FindData));
1363     if (FAILED(hr))
1364         return FALSE;
1365 
1366     return PathIsSystemFolderW(NULL, FindData.dwFileAttributes);
1367 }
1368 
_CanAccessPidlBag() const1369 BOOL CViewStatePropertyBag::_CanAccessPidlBag() const
1370 {
1371     return ((m_dwVspbFlags & SHGVSPB_FOLDER) == SHGVSPB_FOLDER);
1372 }
1373 
_CanAccessUserDefaultsBag() const1374 BOOL CViewStatePropertyBag::_CanAccessUserDefaultsBag() const
1375 {
1376     if (_CanAccessPidlBag())
1377         return TRUE;
1378 
1379     return ((m_dwVspbFlags & SHGVSPB_USERDEFAULTS) == SHGVSPB_USERDEFAULTS);
1380 }
1381 
_CanAccessFolderDefaultsBag() const1382 BOOL CViewStatePropertyBag::_CanAccessFolderDefaultsBag() const
1383 {
1384     if (_CanAccessUserDefaultsBag())
1385         return TRUE;
1386 
1387     return ((m_dwVspbFlags & SHGVSPB_ALLUSERS) && (m_dwVspbFlags & SHGVSPB_PERFOLDER));
1388 }
1389 
_CanAccessGlobalDefaultsBag() const1390 BOOL CViewStatePropertyBag::_CanAccessGlobalDefaultsBag() const
1391 {
1392     if (_CanAccessFolderDefaultsBag())
1393         return TRUE;
1394 
1395     return ((m_dwVspbFlags & SHGVSPB_GLOBALDEAFAULTS) == SHGVSPB_GLOBALDEAFAULTS);
1396 }
1397 
_CanAccessInheritBag() const1398 BOOL CViewStatePropertyBag::_CanAccessInheritBag() const
1399 {
1400     return (_CanAccessPidlBag() || (m_dwVspbFlags & SHGVSPB_INHERIT));
1401 }
1402 
_CanAccessUpgradeBag() const1403 BOOL CViewStatePropertyBag::_CanAccessUpgradeBag() const
1404 {
1405     return StrCmpW(m_pszPath, L"Desktop") == 0;
1406 }
1407 
_ResetTryAgainFlag()1408 void CViewStatePropertyBag::_ResetTryAgainFlag()
1409 {
1410     if (m_dwVspbFlags & SHGVSPB_NOAUTODEFAULTS)
1411         m_bReadBag = FALSE;
1412     else if ((m_dwVspbFlags & SHGVSPB_FOLDER) == SHGVSPB_FOLDER)
1413         m_bPidlBag = FALSE;
1414     else if (m_dwVspbFlags & SHGVSPB_INHERIT)
1415         m_bInheritBag = FALSE;
1416     else if ((m_dwVspbFlags & SHGVSPB_USERDEFAULTS) == SHGVSPB_USERDEFAULTS)
1417         m_bUserDefaultsBag = FALSE;
1418     else if ((m_dwVspbFlags & SHGVSPB_ALLUSERS) && (m_dwVspbFlags & SHGVSPB_PERFOLDER))
1419         m_bFolderDefaultsBag = FALSE;
1420     else if ((m_dwVspbFlags & SHGVSPB_GLOBALDEAFAULTS) == SHGVSPB_GLOBALDEAFAULTS)
1421         m_bGlobalDefaultsBag = FALSE;
1422 }
1423 
_GetHKey(DWORD dwVspbFlags)1424 HKEY CViewStatePropertyBag::_GetHKey(DWORD dwVspbFlags)
1425 {
1426     if (!(dwVspbFlags & (SHGVSPB_INHERIT | SHGVSPB_PERUSER)))
1427         return SHGetShellKey((SHKEY_Key_Shell | SHKEY_Root_HKLM), NULL, TRUE);
1428 
1429     if ((m_dwVspbFlags & SHGVSPB_ROAM) && (dwVspbFlags & SHGVSPB_PERFOLDER))
1430         return SHGetShellKey((SHKEY_Key_Shell | SHKEY_Root_HKCU), NULL, TRUE);
1431 
1432     return SHGetShellKey(SHKEY_Key_ShellNoRoam | SHKEY_Root_HKCU, NULL, TRUE);
1433 }
1434 
_GetMRUSize(HKEY hKey)1435 UINT CViewStatePropertyBag::_GetMRUSize(HKEY hKey)
1436 {
1437     DWORD dwValue, cbValue = sizeof(dwValue);
1438 
1439     if (SHGetValueW(hKey, NULL, L"BagMRU Size", NULL, &dwValue, &cbValue) != ERROR_SUCCESS)
1440         return 400; // The default size of the MRU (most recently used) list
1441 
1442     return (UINT)dwValue;
1443 }
1444 
1445 HRESULT
_GetMRUSlots(LPCITEMIDLIST pidl,DWORD dwMode,HKEY hKey,UINT * puSlots,UINT cSlots,UINT * pcSlots)1446 CViewStatePropertyBag::_GetMRUSlots(
1447     LPCITEMIDLIST pidl,
1448     DWORD dwMode,
1449     HKEY hKey,
1450     UINT *puSlots,
1451     UINT cSlots,
1452     UINT *pcSlots)
1453 {
1454     CComPtr<IMruPidlList> pMruList;
1455     HRESULT hr = ::CoCreateInstance(CLSID_MruPidlList, NULL, CLSCTX_INPROC_SERVER,
1456                                     IID_IMruPidlList, (void**)&pMruList);
1457     if (FAILED(hr))
1458         return hr;
1459 
1460     UINT cMRUSize = _GetMRUSize(hKey);
1461     hr = pMruList->InitList(cMRUSize, hKey, L"BagMRU");
1462     if (FAILED(hr))
1463         return hr;
1464 
1465     hr = pMruList->QueryPidl(pidl, cSlots, puSlots, pcSlots);
1466     if (hr == S_OK || MODE_CAN_WRITE(dwMode)) // FIXME: HACK! (Without this, a new pidl can never be saved)
1467         hr = pMruList->UsePidl(pidl, puSlots);
1468     else if (cSlots == 1)
1469         hr = E_FAIL;
1470 
1471     return hr;
1472 }
1473 
1474 HRESULT
_GetMRUSlot(LPCITEMIDLIST pidl,DWORD dwMode,HKEY hKey,UINT * pSlot)1475 CViewStatePropertyBag::_GetMRUSlot(LPCITEMIDLIST pidl, DWORD dwMode, HKEY hKey, UINT *pSlot)
1476 {
1477     UINT cSlots;
1478     return _GetMRUSlots(pidl, dwMode, hKey, pSlot, 1, &cSlots);
1479 }
1480 
1481 HRESULT
_GetRegKey(LPCITEMIDLIST pidl,LPCWSTR pszBagName,DWORD dwFlags,DWORD dwMode,HKEY hKey,LPWSTR pszDest,INT cchDest)1482 CViewStatePropertyBag::_GetRegKey(
1483     LPCITEMIDLIST pidl,
1484     LPCWSTR pszBagName,
1485     DWORD dwFlags,
1486     DWORD dwMode,
1487     HKEY hKey,
1488     LPWSTR pszDest,
1489     INT cchDest)
1490 {
1491     HRESULT hr = S_OK;
1492     UINT nSlot;
1493 
1494     if (dwFlags & (SHGVSPB_INHERIT | SHGVSPB_PERFOLDER))
1495     {
1496         hr = _GetMRUSlot(pidl, dwMode, hKey, &nSlot);
1497         if (SUCCEEDED(hr))
1498         {
1499             if (dwFlags & SHGVSPB_INHERIT)
1500                 StringCchPrintfW(pszDest, cchDest, L"Bags\\%d\\%s\\Inherit", nSlot, pszBagName);
1501             else
1502                 StringCchPrintfW(pszDest, cchDest, L"Bags\\%d\\%s", nSlot, pszBagName);
1503         }
1504     }
1505     else
1506     {
1507         StringCchPrintfW(pszDest, cchDest, L"Bags\\AllFolders\\%s", pszBagName);
1508     }
1509 
1510     return hr;
1511 }
1512 
BindCtx_CreateWithMode(DWORD dwMode,IBindCtx ** ppbc)1513 static HRESULT BindCtx_CreateWithMode(DWORD dwMode, IBindCtx **ppbc)
1514 {
1515     HRESULT hr = ::CreateBindCtx(0, ppbc);
1516     if (FAILED(hr))
1517         return hr;
1518 
1519     IBindCtx *pbc = *ppbc;
1520 
1521     BIND_OPTS opts = { sizeof(opts) };
1522     opts.grfMode = dwMode;
1523     hr = pbc->SetBindOptions(&opts);
1524     if (FAILED(hr))
1525     {
1526         pbc->Release();
1527         *ppbc = NULL;
1528     }
1529 
1530     return hr;
1531 }
1532 
1533 HRESULT
_CreateBag(LPITEMIDLIST pidl,LPCWSTR pszPath,DWORD dwVspbFlags,DWORD dwMode,REFIID riid,IPropertyBag ** pppb)1534 CViewStatePropertyBag::_CreateBag(
1535     LPITEMIDLIST pidl,
1536     LPCWSTR pszPath,
1537     DWORD dwVspbFlags,
1538     DWORD dwMode,
1539     REFIID riid,
1540     IPropertyBag **pppb)
1541 {
1542     HRESULT hr;
1543     HKEY hKey;
1544     CComPtr<IBindCtx> pBC;
1545     CComPtr<IShellFolder> psf;
1546     WCHAR szBuff[64];
1547 
1548     if (MODE_CAN_WRITE(dwMode))
1549         dwMode |= STGM_CREATE;
1550 
1551     if ((dwVspbFlags & SHGVSPB_ALLUSERS) && (dwVspbFlags & SHGVSPB_PERFOLDER))
1552     {
1553         hr = BindCtx_CreateWithMode(dwMode, &pBC);
1554         if (SUCCEEDED(hr))
1555         {
1556             hr = SHGetDesktopFolder(&psf);
1557             if (SUCCEEDED(hr))
1558             {
1559                 hr = psf->BindToObject(m_pidl, pBC, riid, (void **)pppb);
1560                 if (SUCCEEDED(hr) && !*pppb)
1561                     hr = E_FAIL;
1562             }
1563         }
1564     }
1565     else
1566     {
1567         hKey = _GetHKey(dwVspbFlags);
1568         if (!hKey)
1569             return E_FAIL;
1570 
1571         hr = _GetRegKey(pidl, pszPath, dwVspbFlags, dwMode, hKey, szBuff, _countof(szBuff));
1572         if (SUCCEEDED(hr))
1573             hr = SHCreatePropertyBagOnRegKey(hKey, szBuff, dwMode, riid, (void**)pppb);
1574 
1575         ::RegCloseKey(hKey);
1576     }
1577 
1578     return hr;
1579 }
1580 
1581 HRESULT
_FindNearestInheritBag(REFIID riid,IPropertyBag ** pppb)1582 CViewStatePropertyBag::_FindNearestInheritBag(REFIID riid, IPropertyBag **pppb)
1583 {
1584     *pppb = NULL;
1585 
1586     HKEY hKey = _GetHKey(SHGVSPB_INHERIT);
1587     if (!hKey)
1588         return E_FAIL;
1589 
1590     UINT cSlots, anSlots[64];
1591     if (FAILED(_GetMRUSlots(m_pidl, 0, hKey, anSlots, _countof(anSlots), &cSlots)) || !cSlots)
1592     {
1593         ::RegCloseKey(hKey);
1594         return E_FAIL;
1595     }
1596 
1597     HRESULT hr = E_FAIL;
1598     WCHAR szBuff[64];
1599     for (UINT iSlot = 0; iSlot < cSlots; ++iSlot)
1600     {
1601         StringCchPrintfW(szBuff, _countof(szBuff), L"Bags\\%d\\%s\\Inherit", anSlots[iSlot],
1602                          m_pszPath);
1603         hr = SHCreatePropertyBagOnRegKey(hKey, szBuff, STGM_READ, riid, (void**)pppb);
1604         if (SUCCEEDED(hr))
1605             break;
1606     }
1607 
1608     ::RegCloseKey(hKey);
1609     return hr;
1610 }
1611 
_EnsureReadBag(DWORD dwMode,REFIID riid)1612 BOOL CViewStatePropertyBag::_EnsureReadBag(DWORD dwMode, REFIID riid)
1613 {
1614     if (!m_pReadBag && !m_bReadBag)
1615     {
1616         m_bReadBag = TRUE;
1617         _CreateBag(m_pidl, m_pszPath, m_dwVspbFlags, dwMode, riid, &m_pReadBag);
1618     }
1619     return (m_pReadBag != NULL);
1620 }
1621 
_EnsurePidlBag(DWORD dwMode,REFIID riid)1622 BOOL CViewStatePropertyBag::_EnsurePidlBag(DWORD dwMode, REFIID riid)
1623 {
1624     if (!m_pPidlBag && !m_bPidlBag && _CanAccessPidlBag())
1625     {
1626         m_bPidlBag = TRUE;
1627         _CreateBag(m_pidl, m_pszPath, SHGVSPB_FOLDER, dwMode, riid, &m_pPidlBag);
1628     }
1629     return (m_pPidlBag != NULL);
1630 }
1631 
_EnsureInheritBag(DWORD dwMode,REFIID riid)1632 BOOL CViewStatePropertyBag::_EnsureInheritBag(DWORD dwMode, REFIID riid)
1633 {
1634     if (!m_pInheritBag && !m_bInheritBag && _CanAccessInheritBag())
1635     {
1636         m_bInheritBag = TRUE;
1637         _FindNearestInheritBag(riid, &m_pInheritBag);
1638     }
1639     return (m_pInheritBag != NULL);
1640 }
1641 
_EnsureUpgradeBag(DWORD dwMode,REFIID riid)1642 BOOL CViewStatePropertyBag::_EnsureUpgradeBag(DWORD dwMode, REFIID riid)
1643 {
1644     if (!m_pUpgradeBag && !m_bUpgradeBag && _CanAccessUpgradeBag())
1645     {
1646         m_bUpgradeBag = TRUE;
1647         SHGetDesktopUpgradePropertyBag(riid, (void**)&m_pUpgradeBag);
1648     }
1649     return (m_pUpgradeBag != NULL);
1650 }
1651 
_EnsureUserDefaultsBag(DWORD dwMode,REFIID riid)1652 BOOL CViewStatePropertyBag::_EnsureUserDefaultsBag(DWORD dwMode, REFIID riid)
1653 {
1654     if (!m_pUserDefaultsBag && !m_bUserDefaultsBag && _CanAccessUserDefaultsBag())
1655     {
1656         m_bUserDefaultsBag = TRUE;
1657         _CreateBag(NULL, m_pszPath, SHGVSPB_USERDEFAULTS, dwMode, riid, &m_pUserDefaultsBag);
1658     }
1659     return (m_pUserDefaultsBag != NULL);
1660 }
1661 
_EnsureFolderDefaultsBag(DWORD dwMode,REFIID riid)1662 BOOL CViewStatePropertyBag::_EnsureFolderDefaultsBag(DWORD dwMode, REFIID riid)
1663 {
1664     if (!m_pFolderDefaultsBag && !m_bFolderDefaultsBag && _CanAccessFolderDefaultsBag())
1665     {
1666         m_bFolderDefaultsBag = TRUE;
1667         if (_IsSystemFolder())
1668         {
1669             _CreateBag(m_pidl, m_pszPath, SHGVSPB_PERFOLDER | SHGVSPB_ALLUSERS,
1670                        dwMode, riid, &m_pFolderDefaultsBag);
1671         }
1672     }
1673     return (m_pFolderDefaultsBag != NULL);
1674 }
1675 
_EnsureGlobalDefaultsBag(DWORD dwMode,REFIID riid)1676 BOOL CViewStatePropertyBag::_EnsureGlobalDefaultsBag(DWORD dwMode, REFIID riid)
1677 {
1678     if (!m_pGlobalDefaultsBag && !m_bGlobalDefaultsBag && _CanAccessGlobalDefaultsBag())
1679     {
1680         m_bGlobalDefaultsBag = TRUE;
1681         _CreateBag(NULL, m_pszPath, SHGVSPB_GLOBALDEAFAULTS, dwMode, riid, &m_pGlobalDefaultsBag);
1682     }
1683     return (m_pGlobalDefaultsBag != NULL);
1684 }
1685 
1686 HRESULT
_ReadPidlBag(LPCWSTR pszPropName,VARIANT * pvari,IErrorLog * pErrorLog)1687 CViewStatePropertyBag::_ReadPidlBag(
1688     LPCWSTR pszPropName,
1689     VARIANT *pvari,
1690     IErrorLog *pErrorLog)
1691 {
1692     if (!_EnsurePidlBag(STGM_READ, IID_IPropertyBag))
1693         return E_FAIL;
1694 
1695     return m_pPidlBag->Read(pszPropName, pvari, pErrorLog);
1696 }
1697 
1698 HRESULT
_ReadInheritBag(LPCWSTR pszPropName,VARIANT * pvari,IErrorLog * pErrorLog)1699 CViewStatePropertyBag::_ReadInheritBag(
1700     LPCWSTR pszPropName,
1701     VARIANT *pvari,
1702     IErrorLog *pErrorLog)
1703 {
1704     if (!_EnsureInheritBag(STGM_READ, IID_IPropertyBag))
1705         return E_FAIL;
1706 
1707     return m_pInheritBag->Read(pszPropName, pvari, pErrorLog);
1708 }
1709 
1710 HRESULT
_ReadUpgradeBag(LPCWSTR pszPropName,VARIANT * pvari,IErrorLog * pErrorLog)1711 CViewStatePropertyBag::_ReadUpgradeBag(
1712     LPCWSTR pszPropName,
1713     VARIANT *pvari,
1714     IErrorLog *pErrorLog)
1715 {
1716     if (!_EnsureUpgradeBag(STGM_READ, IID_IPropertyBag))
1717         return E_FAIL;
1718 
1719     return m_pUpgradeBag->Read(pszPropName, pvari, pErrorLog);
1720 }
1721 
1722 HRESULT
_ReadUserDefaultsBag(LPCWSTR pszPropName,VARIANT * pvari,IErrorLog * pErrorLog)1723 CViewStatePropertyBag::_ReadUserDefaultsBag(
1724     LPCWSTR pszPropName,
1725     VARIANT *pvari,
1726     IErrorLog *pErrorLog)
1727 {
1728     if (!_EnsureUserDefaultsBag(STGM_READ, IID_IPropertyBag))
1729         return E_FAIL;
1730 
1731     return m_pUserDefaultsBag->Read(pszPropName, pvari, pErrorLog);
1732 }
1733 
1734 HRESULT
_ReadFolderDefaultsBag(LPCWSTR pszPropName,VARIANT * pvari,IErrorLog * pErrorLog)1735 CViewStatePropertyBag::_ReadFolderDefaultsBag(
1736     LPCWSTR pszPropName,
1737     VARIANT *pvari,
1738     IErrorLog *pErrorLog)
1739 {
1740     if (!_EnsureFolderDefaultsBag(STGM_READ, IID_IPropertyBag))
1741         return E_FAIL;
1742 
1743     return m_pFolderDefaultsBag->Read(pszPropName, pvari, pErrorLog);
1744 }
1745 
1746 HRESULT
_ReadGlobalDefaultsBag(LPCWSTR pszPropName,VARIANT * pvari,IErrorLog * pErrorLog)1747 CViewStatePropertyBag::_ReadGlobalDefaultsBag(
1748     LPCWSTR pszPropName,
1749     VARIANT *pvari,
1750     IErrorLog *pErrorLog)
1751 {
1752     if (!_EnsureGlobalDefaultsBag(STGM_READ, IID_IPropertyBag))
1753         return E_FAIL;
1754 
1755     return m_pGlobalDefaultsBag->Read(pszPropName, pvari, pErrorLog);
1756 }
1757 
1758 STDMETHODIMP
Read(_In_z_ LPCWSTR pszPropName,_Inout_ VARIANT * pvari,_Inout_opt_ IErrorLog * pErrorLog)1759 CViewStatePropertyBag::Read(
1760     _In_z_ LPCWSTR pszPropName,
1761     _Inout_ VARIANT *pvari,
1762     _Inout_opt_ IErrorLog *pErrorLog)
1763 {
1764     if ((m_dwVspbFlags & SHGVSPB_NOAUTODEFAULTS) || (m_dwVspbFlags & SHGVSPB_INHERIT))
1765     {
1766         if (!_EnsureReadBag(STGM_READ, IID_IPropertyBag))
1767             return E_FAIL;
1768 
1769         return m_pReadBag->Read(pszPropName, pvari, pErrorLog);
1770     }
1771 
1772     HRESULT hr = _ReadPidlBag(pszPropName, pvari, pErrorLog);
1773     if (SUCCEEDED(hr))
1774         return hr;
1775 
1776     hr = _ReadInheritBag(pszPropName, pvari, pErrorLog);
1777     if (SUCCEEDED(hr))
1778         return hr;
1779 
1780     hr = _ReadUpgradeBag(pszPropName, pvari, pErrorLog);
1781     if (SUCCEEDED(hr))
1782         return hr;
1783 
1784     hr = _ReadUserDefaultsBag(pszPropName, pvari, pErrorLog);
1785     if (SUCCEEDED(hr))
1786         return hr;
1787 
1788     hr = _ReadFolderDefaultsBag(pszPropName, pvari, pErrorLog);
1789     if (SUCCEEDED(hr))
1790         return hr;
1791 
1792     return _ReadGlobalDefaultsBag(pszPropName, pvari, pErrorLog);
1793 }
1794 
_PruneMRUTree()1795 void CViewStatePropertyBag::_PruneMRUTree()
1796 {
1797     HKEY hKey = _GetHKey(SHGVSPB_INHERIT);
1798     if (!hKey)
1799         return;
1800 
1801     CComPtr<IMruPidlList> pMruList;
1802     HRESULT hr = ::CoCreateInstance(CLSID_MruPidlList, NULL, CLSCTX_INPROC_SERVER,
1803                                     IID_IMruPidlList, (void**)&pMruList);
1804     if (SUCCEEDED(hr))
1805     {
1806         hr = pMruList->InitList(200, hKey, L"BagMRU");
1807         if (SUCCEEDED(hr))
1808             pMruList->PruneKids(m_pidl);
1809     }
1810 
1811     ::RegCloseKey(hKey);
1812 }
1813 
_EnsureWriteBag(DWORD dwMode,REFIID riid)1814 BOOL CViewStatePropertyBag::_EnsureWriteBag(DWORD dwMode, REFIID riid)
1815 {
1816     if (!m_pWriteBag && !m_bWriteBag)
1817     {
1818         m_bWriteBag = TRUE;
1819         _CreateBag(m_pidl, m_pszPath, m_dwVspbFlags, dwMode, riid, &m_pWriteBag);
1820         if (m_pWriteBag)
1821         {
1822             _ResetTryAgainFlag();
1823             if (m_dwVspbFlags & SHGVSPB_INHERIT)
1824                 _PruneMRUTree();
1825         }
1826     }
1827     return (m_pWriteBag != NULL);
1828 }
1829 
Write(_In_z_ LPCWSTR pszPropName,_In_ VARIANT * pvari)1830 STDMETHODIMP CViewStatePropertyBag::Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari)
1831 {
1832     if (!_EnsureWriteBag(STGM_WRITE, IID_IPropertyBag))
1833         return E_FAIL;
1834 
1835     return m_pWriteBag->Write(pszPropName, pvari);
1836 }
1837 
SHIsRemovableDrive(LPCITEMIDLIST pidl)1838 static BOOL SHIsRemovableDrive(LPCITEMIDLIST pidl)
1839 {
1840     STRRET strret;
1841     CComPtr<IShellFolder> psf;
1842     WCHAR szBuff[MAX_PATH];
1843     LPCITEMIDLIST ppidlLast;
1844     INT iDrive, nType;
1845     HRESULT hr;
1846 
1847     hr = SHBindToParent(pidl, IID_IShellFolder, (void **)&psf, &ppidlLast);
1848     if (FAILED(hr))
1849         return FALSE;
1850 
1851     hr = psf->GetDisplayNameOf(ppidlLast, SHGDN_FORPARSING, &strret);
1852     if (FAILED(hr))
1853         return FALSE;
1854 
1855     hr = StrRetToBufW(&strret, ppidlLast, szBuff, _countof(szBuff));
1856     if (FAILED(hr))
1857         return FALSE;
1858 
1859     iDrive = PathGetDriveNumberW(szBuff);
1860     if (iDrive < 0)
1861         return FALSE;
1862 
1863     nType = RealDriveType(iDrive, FALSE);
1864     return (nType == DRIVE_REMOVABLE || nType == DRIVE_CDROM);
1865 }
1866 
1867 /**************************************************************************
1868  *  SHGetViewStatePropertyBag (SHLWAPI.515)
1869  *
1870  * Retrieves a property bag in which the view state information of a folder
1871  * can be stored.
1872  *
1873  * @param pidl      PIDL of the folder requested
1874  * @param bag_name  Name of the property bag requested
1875  * @param flags     Optional SHGVSPB_... flags
1876  * @param riid      IID of requested property bag interface
1877  * @param ppv       Address to receive pointer to the new interface
1878  * @return          An HRESULT value. S_OK on success, non-zero on failure.
1879  * @see https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-shgetviewstatepropertybag
1880  */
1881 EXTERN_C HRESULT WINAPI
SHGetViewStatePropertyBag(_In_opt_ PCIDLIST_ABSOLUTE pidl,_In_opt_ LPCWSTR bag_name,_In_ DWORD flags,_In_ REFIID riid,_Outptr_ void ** ppv)1882 SHGetViewStatePropertyBag(
1883     _In_opt_ PCIDLIST_ABSOLUTE pidl,
1884     _In_opt_ LPCWSTR bag_name,
1885     _In_ DWORD flags,
1886     _In_ REFIID riid,
1887     _Outptr_ void **ppv)
1888 {
1889     HRESULT hr;
1890 
1891     TRACE("%p %s 0x%X %p %p\n", pidl, debugstr_w(bag_name), flags, &riid, ppv);
1892 
1893     *ppv = NULL;
1894 
1895     ::EnterCriticalSection(&g_csBagCacheLock);
1896 
1897     if (g_pCachedBag && g_pCachedBag->IsSameBag(pidl, bag_name, flags))
1898     {
1899         hr = g_pCachedBag->QueryInterface(riid, ppv);
1900         ::LeaveCriticalSection(&g_csBagCacheLock);
1901         return hr;
1902     }
1903 
1904     if (SHIsRemovableDrive(pidl))
1905     {
1906         TRACE("pidl %p is removable\n", pidl);
1907         ::LeaveCriticalSection(&g_csBagCacheLock);
1908         return E_FAIL;
1909     }
1910 
1911     CComPtr<CViewStatePropertyBag> pBag(new CViewStatePropertyBag());
1912 
1913     hr = pBag->Init(pidl, bag_name, flags);
1914     if (FAILED(hr))
1915     {
1916         ERR("0x%08X\n", hr);
1917         ::LeaveCriticalSection(&g_csBagCacheLock);
1918         return hr;
1919     }
1920     g_pCachedBag = pBag;
1921     ::LeaveCriticalSection(&g_csBagCacheLock);
1922     return pBag->QueryInterface(riid, ppv);
1923 }
1924 
FreeViewStatePropertyBagCache(VOID)1925 EXTERN_C VOID FreeViewStatePropertyBagCache(VOID)
1926 {
1927     ::EnterCriticalSection(&g_csBagCacheLock);
1928     g_pCachedBag.Release();
1929     ::LeaveCriticalSection(&g_csBagCacheLock);
1930 }
1931 
1932 /**************************************************************************
1933  *  SHGetPerScreenResName (SHLWAPI.533)
1934  *
1935  * @see https://www.geoffchappell.com/studies/windows/shell/shlwapi/api/propbag/getperscreenresname.htm
1936  */
1937 EXTERN_C INT WINAPI
SHGetPerScreenResName(_Out_writes_ (cchBuffer)LPWSTR pszBuffer,_In_ INT cchBuffer,_In_ DWORD dwReserved)1938 SHGetPerScreenResName(
1939     _Out_writes_(cchBuffer) LPWSTR pszBuffer,
1940     _In_ INT cchBuffer,
1941     _In_ DWORD dwReserved)
1942 {
1943     if (dwReserved)
1944         return 0;
1945 
1946     HDC hDC = ::GetDC(NULL);
1947     INT cxWidth = ::GetDeviceCaps(hDC, HORZRES);
1948     INT cyHeight = ::GetDeviceCaps(hDC, VERTRES);
1949     INT cMonitors = ::GetSystemMetrics(SM_CMONITORS);
1950     ::ReleaseDC(NULL, hDC);
1951 
1952     StringCchPrintfW(pszBuffer, cchBuffer, L"%dx%d(%d)", cxWidth, cyHeight, cMonitors);
1953     return lstrlenW(pszBuffer);
1954 }
1955 
1956 /**************************************************************************
1957  *  IUnknown_QueryServicePropertyBag (SHLWAPI.536)
1958  *
1959  * @param punk      An IUnknown interface.
1960  * @param flags     The SHGVSPB_... flags of SHGetViewStatePropertyBag.
1961  * @param riid      IID of requested property bag interface.
1962  * @param ppvObj    Address to receive pointer to the new interface.
1963  * @return          An HRESULT value. S_OK on success, non-zero on failure.
1964  * @see https://geoffchappell.com/studies/windows/shell/shlwapi/api/util/iunknown/queryservicepropertybag.htm
1965  */
1966 EXTERN_C HRESULT WINAPI
IUnknown_QueryServicePropertyBag(_In_ IUnknown * punk,_In_ long flags,_In_ REFIID riid,_Outptr_ void ** ppvObj)1967 IUnknown_QueryServicePropertyBag(
1968     _In_ IUnknown *punk,
1969     _In_ long flags,
1970     _In_ REFIID riid,
1971     _Outptr_ void **ppvObj)
1972 {
1973     TRACE("%p 0x%x %p %p\n", punk, flags, &riid, ppvObj);
1974 
1975     CComPtr<IShellBrowserService> pService;
1976     HRESULT hr = IUnknown_QueryService(punk, SID_STopLevelBrowser, IID_IShellBrowserService,
1977                                        (void **)&pService);
1978     if (FAILED(hr))
1979     {
1980         ERR("0x%X\n", hr);
1981         return hr;
1982     }
1983 
1984     return pService->GetPropertyBag(flags, riid, ppvObj);
1985 }
1986