1 /*
2  * PROJECT:     ReactOS api tests
3  * LICENSE:     LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
4  * PURPOSE:     Tests for SHPropertyBag Read/Write
5  * COPYRIGHT:   Copyright 2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
6  */
7 
8 #include <apitest.h>
9 #include <shlwapi.h>
10 #include <shlobj.h>
11 #include <shlwapi_undoc.h>
12 #include <versionhelpers.h>
13 
14 #include <pseh/pseh2.h>
15 
16 static LPCWSTR s_pszPropNames[4] = { NULL, NULL, NULL, NULL };
17 static VARTYPE s_vt;
18 static INT s_cRead = 0;
19 static INT s_cWrite = 0;
20 
21 static void ResetTest(VARTYPE vt,
22                   LPCWSTR pszName0 = NULL, LPCWSTR pszName1 = NULL,
23                   LPCWSTR pszName2 = NULL, LPCWSTR pszName3 = NULL)
24 {
25     s_vt = vt;
26     s_cRead = s_cWrite = 0;
27     s_pszPropNames[0] = pszName0;
28     s_pszPropNames[1] = pszName1;
29     s_pszPropNames[2] = pszName2;
30     s_pszPropNames[3] = pszName3;
31 }
32 
33 static SAFEARRAY* CreateByteArray(LPCVOID pvSrc, DWORD cbSize)
34 {
35     SAFEARRAYBOUND Bound;
36     Bound.lLbound = 0;
37     Bound.cElements = cbSize;
38 
39     SAFEARRAY* pArray = SafeArrayCreate(VT_UI1, 1, &Bound);
40     if (!pArray)
41         return NULL;
42 
43     void HUGEP *pvData;
44     HRESULT hr = SafeArrayAccessData(pArray, &pvData);
45     if (FAILED(hr))
46     {
47         SafeArrayDestroy(pArray);
48         return NULL;
49     }
50 
51     CopyMemory(pvData, pvSrc, cbSize);
52     SafeArrayUnaccessData(pArray);
53 
54     return pArray;
55 }
56 
57 class CDummyPropertyBag : public IPropertyBag
58 {
59 public:
60     CDummyPropertyBag()
61     {
62     }
63 
64     // IUnknown
65     STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) override
66     {
67         ok(FALSE, "Unexpected call\n");
68         return S_OK;
69     }
70     STDMETHODIMP_(ULONG) AddRef() override
71     {
72         ok(FALSE, "Unexpected call\n");
73         return S_OK;
74     }
75     STDMETHODIMP_(ULONG) Release() override
76     {
77         ok(FALSE, "Unexpected call\n");
78         return S_OK;
79     }
80 
81     // IPropertyBag
82     STDMETHODIMP Read(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog) override
83     {
84         ++s_cRead;
85         ok_int(s_vt, V_VT(pvari));
86         for (size_t i = 0; i < _countof(s_pszPropNames); ++i)
87         {
88             if (s_pszPropNames[i])
89             {
90                 ok_wstr(pszPropName, s_pszPropNames[i]);
91                 s_pszPropNames[i] = NULL;
92 
93                 if (lstrcmpiW(pszPropName, L"RECTL2.top") == 0)
94                     return E_FAIL;
95 
96                 if (lstrcmpiW(pszPropName, L"GUID1") == 0)
97                 {
98                     V_VT(pvari) = (VT_UI1 | VT_ARRAY);
99                     V_ARRAY(pvari) = CreateByteArray(&IID_IShellLink, sizeof(IID));
100                     return S_OK;
101                 }
102 
103                 if (lstrcmpiW(pszPropName, L"GUID2") == 0)
104                 {
105                     WCHAR szText[50];
106                     StringFromGUID2(IID_IUnknown, szText, _countof(szText));
107 
108                     V_VT(pvari) = VT_BSTR;
109                     V_BSTR(pvari) = SysAllocString(szText);
110                     return S_OK;
111                 }
112 
113                 if (lstrcmpiW(pszPropName, L"GUID3") == 0)
114                 {
115                     V_VT(pvari) = VT_EMPTY;
116                     V_UI4(pvari) = 0xDEADFACE;
117                     return S_OK;
118                 }
119 
120                 goto Skip1;
121             }
122         }
123         ok(FALSE, "Unexpected call\n");
124 Skip1:
125         return S_OK;
126     }
127 
128     STDMETHODIMP Write(LPCWSTR pszPropName, VARIANT *pvari) override
129     {
130         ++s_cWrite;
131         ok_int(s_vt, V_VT(pvari));
132         for (size_t i = 0; i < _countof(s_pszPropNames); ++i)
133         {
134             if (s_pszPropNames[i])
135             {
136                 ok_wstr(pszPropName, s_pszPropNames[i]);
137                 s_pszPropNames[i] = NULL;
138                 if (lstrcmpiW(pszPropName, L"RECTL2.bottom") == 0)
139                 {
140                     s_vt = VT_EMPTY;
141                     ZeroMemory(&s_pszPropNames, sizeof(s_pszPropNames));
142                     s_pszPropNames[0] = L"RECTL2.right";
143                     return E_FAIL;
144                 }
145                 goto Skip2;
146             }
147         }
148         ok(FALSE, "Unexpected call\n");
149 Skip2:
150         return S_OK;
151     }
152 };
153 
154 static void SHPropertyBag_ReadTest(void)
155 {
156     HRESULT hr;
157     CDummyPropertyBag dummy;
158     BOOL bValue = 0xDEADFACE;
159     SHORT sValue = 0xDEAD;
160     LONG lValue = 0xDEADDEAD;
161     DWORD dwValue = 0xFEEDF00D;
162     BSTR bstr = NULL;
163     POINTL ptl = { 0xEEEE, 0xDDDD };
164     POINTS pts = { 0x2222, 0x3333 };
165     RECTL rcl = { 123, 456, 789, 101112 };
166     GUID guid = { 0 };
167 
168     ResetTest(VT_BOOL, L"BOOL1");
169     hr = SHPropertyBag_ReadBOOL(&dummy, s_pszPropNames[0], &bValue);
170     ok_long(hr, S_OK);
171     ok_int(s_cRead, 1);
172     ok_int(s_cWrite, 0);
173 
174     ResetTest(VT_UI2, L"SHORT1");
175     hr = SHPropertyBag_ReadSHORT(&dummy, s_pszPropNames[0], &sValue);
176     ok_long(hr, S_OK);
177     ok_int(s_cRead, 1);
178     ok_int(s_cWrite, 0);
179 
180     ResetTest(VT_I4, L"LONG1");
181     hr = SHPropertyBag_ReadLONG(&dummy, s_pszPropNames[0], &lValue);
182     ok_long(hr, S_OK);
183     ok_int(s_cRead, 1);
184     ok_int(s_cWrite, 0);
185 
186     ResetTest(VT_UI4, L"DWORD1");
187     hr = SHPropertyBag_ReadDWORD(&dummy, s_pszPropNames[0], &dwValue);
188     ok_long(hr, S_OK);
189     ok_int(s_cRead, 1);
190     ok_int(s_cWrite, 0);
191 
192     ResetTest(VT_BSTR, L"Str1");
193     hr = SHPropertyBag_ReadBSTR(&dummy, s_pszPropNames[0], &bstr);
194     ok_long(hr, S_OK);
195     ok_int(s_cRead, 1);
196     ok_int(s_cWrite, 0);
197     SysFreeString(bstr);
198 
199     ResetTest(VT_I4, L"POINTL1.x", L"POINTL1.y");
200     hr = SHPropertyBag_ReadPOINTL(&dummy, L"POINTL1", &ptl);
201     ok_long(hr, S_OK);
202     ok_int(s_cRead, 2);
203     ok_int(s_cWrite, 0);
204 
205     ResetTest(VT_I4, L"POINTS1.x", L"POINTS1.y");
206     hr = SHPropertyBag_ReadPOINTS(&dummy, L"POINTS1", &pts);
207     ok_long(hr, S_OK);
208     ok_int(s_cRead, 2);
209     ok_int(s_cWrite, 0);
210 
211     ResetTest(VT_I4, L"RECTL1.left", L"RECTL1.top", L"RECTL1.right", L"RECTL1.bottom");
212     hr = SHPropertyBag_ReadRECTL(&dummy, L"RECTL1", &rcl);
213     ok_long(hr, S_OK);
214     ok_int(s_cRead, 4);
215     ok_int(s_cWrite, 0);
216 
217     ResetTest(VT_I4, L"RECTL2.left", L"RECTL2.top", L"RECTL2.right", L"RECTL2.bottom");
218     hr = SHPropertyBag_ReadRECTL(&dummy, L"RECTL2", &rcl);
219     ok_long(hr, E_FAIL);
220     ok_int(s_cRead, 2);
221     ok_int(s_cWrite, 0);
222 
223     ResetTest(VT_EMPTY, L"GUID1");
224     hr = SHPropertyBag_ReadGUID(&dummy, L"GUID1", &guid);
225     ok_long(hr, S_OK);
226     ok_int(s_cRead, 1);
227     ok_int(s_cWrite, 0);
228     ok_int(IsEqualGUID(guid, IID_IShellLink), TRUE);
229 
230     ResetTest(VT_EMPTY, L"GUID2");
231     hr = SHPropertyBag_ReadGUID(&dummy, L"GUID2", &guid);
232     ok_long(hr, S_OK);
233     ok_int(s_cRead, 1);
234     ok_int(s_cWrite, 0);
235     ok_int(IsEqualGUID(guid, IID_IUnknown), TRUE);
236 
237     ResetTest(VT_EMPTY, L"GUID3");
238     guid = IID_IExtractIcon;
239     hr = SHPropertyBag_ReadGUID(&dummy, L"GUID3", &guid);
240 
241     if (IsWindowsVistaOrGreater())
242         ok_long(hr, E_INVALIDARG);
243     else
244         ok_long(hr, S_OK);
245 
246     ok_int(s_cRead, 1);
247     ok_int(s_cWrite, 0);
248     ok_int(IsEqualGUID(guid, IID_IExtractIcon), TRUE);
249 }
250 
251 static void SHPropertyBag_WriteTest(void)
252 {
253     HRESULT hr;
254     CDummyPropertyBag dummy;
255 
256     ResetTest(VT_EMPTY, L"EMPTY1");
257     hr = SHPropertyBag_Delete(&dummy, s_pszPropNames[0]);
258     ok_long(hr, S_OK);
259     ok_int(s_cRead, 0);
260     ok_int(s_cWrite, 1);
261 
262     ResetTest(VT_BOOL, L"BOOL1");
263     hr = SHPropertyBag_WriteBOOL(&dummy, s_pszPropNames[0], TRUE);
264     ok_long(hr, S_OK);
265     ok_int(s_cRead, 0);
266     ok_int(s_cWrite, 1);
267 
268     ResetTest(VT_UI2, L"SHORT1");
269     hr = SHPropertyBag_WriteSHORT(&dummy, s_pszPropNames[0], 1);
270     ok_long(hr, S_OK);
271     ok_int(s_cRead, 0);
272     ok_int(s_cWrite, 1);
273 
274     ResetTest(VT_I4, L"LONG1");
275     hr = SHPropertyBag_WriteLONG(&dummy, s_pszPropNames[0], 1);
276     ok_long(hr, S_OK);
277     ok_int(s_cRead, 0);
278     ok_int(s_cWrite, 1);
279 
280     ResetTest(VT_UI4, L"DWORD1");
281     hr = SHPropertyBag_WriteDWORD(&dummy, s_pszPropNames[0], 1);
282     ok_long(hr, S_OK);
283     ok_int(s_cRead, 0);
284     ok_int(s_cWrite, 1);
285 
286     ResetTest(VT_BSTR, L"Str1");
287     hr = SHPropertyBag_WriteStr(&dummy, s_pszPropNames[0], L"1");
288     ok_long(hr, S_OK);
289     ok_int(s_cRead, 0);
290     ok_int(s_cWrite, 1);
291 
292     ResetTest(VT_I4, L"POINTL1.x", L"POINTL1.y");
293     POINTL ptl = { 0xEEEE, 0xDDDD };
294     hr = SHPropertyBag_WritePOINTL(&dummy, L"POINTL1", &ptl);
295     ok_long(hr, S_OK);
296     ok_int(s_cRead, 0);
297     ok_int(s_cWrite, 2);
298 
299     ResetTest(VT_I4, L"POINTS1.x", L"POINTS1.y");
300     POINTS pts = { 0x2222, 0x3333 };
301     hr = SHPropertyBag_WritePOINTS(&dummy, L"POINTS1", &pts);
302     ok_long(hr, S_OK);
303     ok_int(s_cRead, 0);
304     ok_int(s_cWrite, 2);
305 
306     ResetTest(VT_I4, L"RECTL1.left", L"RECTL1.top", L"RECTL1.right", L"RECTL1.bottom");
307     RECTL rcl = { 123, 456, 789, 101112 };
308     hr = SHPropertyBag_WriteRECTL(&dummy, L"RECTL1", &rcl);
309     ok_long(hr, S_OK);
310     ok_int(s_cRead, 0);
311     ok_int(s_cWrite, 4);
312 
313     ResetTest(VT_I4, L"RECTL2.left", L"RECTL2.top", L"RECTL2.right", L"RECTL2.bottom");
314     hr = SHPropertyBag_WriteRECTL(&dummy, L"RECTL2", &rcl);
315     ok_long(hr, S_OK);
316     ok_int(s_cRead, 0);
317     ok_int(s_cWrite, 5);
318 
319     GUID guid;
320     ZeroMemory(&guid, sizeof(guid));
321     ResetTest(VT_BSTR, L"GUID1");
322     hr = SHPropertyBag_WriteGUID(&dummy, L"GUID1", &guid);
323     ok_long(hr, S_OK);
324     ok_int(s_cRead, 0);
325     ok_int(s_cWrite, 1);
326 }
327 
328 START_TEST(SHPropertyBag)
329 {
330     SHPropertyBag_ReadTest();
331     SHPropertyBag_WriteTest();
332 }
333