1 /*
2  * PROJECT:     ReactOS api tests
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Test for CIDLDataObj
5  * COPYRIGHT:   Copyright 2019 Mark Jansen (mark.jansen@reactos.org)
6  */
7 
8 #include "shelltest.h"
9 #include <ndk/rtlfuncs.h>
10 #include <stdio.h>
11 #include <shellutils.h>
12 #include <shlwapi.h>
13 
14 static DWORD g_WinVersion;
15 
16 
17 static void TestAdviseAndCanonical(PCIDLIST_ABSOLUTE pidlFolder, UINT cidl, PCUIDLIST_RELATIVE_ARRAY apidl)
18 {
19     CComPtr<IDataObject> spDataObj;
20     HRESULT hr = CIDLData_CreateFromIDArray(pidlFolder, cidl, apidl, &spDataObj);
21 
22     ok_hex(hr, S_OK);
23     if (!SUCCEEDED(hr))
24         return;
25 
26     hr = spDataObj->DAdvise(NULL, 0, NULL, NULL);
27     ok_hex(hr, OLE_E_ADVISENOTSUPPORTED);
28 
29     hr = spDataObj->DUnadvise(0);
30     ok_hex(hr, OLE_E_ADVISENOTSUPPORTED);
31 
32     hr = spDataObj->EnumDAdvise(NULL);
33     ok_hex(hr, OLE_E_ADVISENOTSUPPORTED);
34 
35 
36     FORMATETC in = {1, (DVTARGETDEVICE*)2, 3, 4, 5};
37     FORMATETC out = {6, (DVTARGETDEVICE*)7, 8, 9, 10};
38 
39     hr = spDataObj->GetCanonicalFormatEtc(&in, &out);
40     ok_hex(hr, DATA_S_SAMEFORMATETC);
41 
42     if (g_WinVersion < _WIN32_WINNT_VISTA)
43     {
44         ok_int(out.cfFormat, 6);
45         ok_ptr(out.ptd, (void*)7);
46         ok_int(out.dwAspect, 8);
47         ok_int(out.lindex, 9);
48         ok_int(out.tymed, 10);
49         trace("out unmodified\n");
50     }
51     else
52     {
53         ok_int(out.cfFormat, in.cfFormat);
54         ok_ptr(out.ptd, NULL);
55         ok_int(out.dwAspect, (int)in.dwAspect);
56         ok_int(out.lindex, in.lindex);
57         ok_int(out.tymed, (int)in.tymed);
58         trace("in copied to out\n");
59     }
60 }
61 
62 
63 static inline PCUIDLIST_ABSOLUTE HIDA_GetPIDLFolder(CIDA const* pida)
64 {
65     return (PCUIDLIST_ABSOLUTE)(((LPBYTE)pida) + (pida)->aoffset[0]);
66 }
67 
68 static inline PCUIDLIST_RELATIVE HIDA_GetPIDLItem(CIDA const* pida, SIZE_T i)
69 {
70     return (PCUIDLIST_RELATIVE)(((LPBYTE)pida) + (pida)->aoffset[i + 1]);
71 }
72 
73 #define ok_wstri(x, y) \
74     ok(wcsicmp(x, y) == 0, "Wrong string. Expected '%S', got '%S'\n", y, x)
75 
76 static void TestHIDA(PVOID pData, SIZE_T Size, LPCWSTR ExpectRoot, LPCWSTR ExpectPath1, LPCWSTR ExpectPath2)
77 {
78     LPIDA pida = (LPIDA)pData;
79 
80     ok_int(pida->cidl, 2);
81     if (pida->cidl != 2)
82         return;
83 
84     WCHAR FolderPath[MAX_PATH], Item1[MAX_PATH], Item2[MAX_PATH];
85     BOOL bRet = SHGetPathFromIDListW(HIDA_GetPIDLFolder(pida), FolderPath);
86     ok_int(bRet, TRUE);
87     if (!bRet)
88         return;
89     ok_wstri(FolderPath, ExpectRoot);
90 
91     CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidl1(ILCombine(HIDA_GetPIDLFolder(pida), HIDA_GetPIDLItem(pida, 0)));
92     CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidl2(ILCombine(HIDA_GetPIDLFolder(pida), HIDA_GetPIDLItem(pida, 1)));
93 
94     bRet = SHGetPathFromIDListW(pidl1, Item1);
95     ok_int(bRet, TRUE);
96     if (!bRet)
97         return;
98     ok_wstri(Item1, ExpectPath1);
99 
100     bRet = SHGetPathFromIDListW(pidl2, Item2);
101     ok_int(bRet, TRUE);
102     if (!bRet)
103         return;
104     ok_wstri(Item2, ExpectPath2);
105 }
106 
107 static void TestHDROP(PVOID pData, SIZE_T Size, LPCWSTR ExpectRoot, LPCWSTR ExpectPath1, LPCWSTR ExpectPath2)
108 {
109     DROPFILES* pDropFiles = (DROPFILES*)pData;
110     ok_int(pDropFiles->fWide, TRUE);
111 
112     LPCWSTR Expected[2] = { ExpectPath1, ExpectPath2 };
113 
114     DWORD offset = pDropFiles->pFiles;
115     UINT Count = 0;
116     for (;;Count++)
117     {
118         LPCWSTR ptr = (LPCWSTR)(((BYTE*)pDropFiles) + offset);
119         if (!*ptr)
120             break;
121 
122         if (Count < _countof(Expected))
123             ok_wstri(Expected[Count], ptr);
124 
125         offset += (wcslen(ptr) + 1) * sizeof(WCHAR);
126     }
127     ok_int(Count, 2);
128 }
129 
130 static void TestFilenameA(PVOID pData, SIZE_T Size, LPCWSTR ExpectRoot, LPCWSTR ExpectPath1, LPCWSTR ExpectPath2)
131 {
132     LPCSTR FirstFile = (LPCSTR)pData;
133     LPWSTR FirstFileW;
134 
135     HRESULT hr = SHStrDupA(FirstFile, &FirstFileW);
136     ok_hex(hr, S_OK);
137     if (!SUCCEEDED(hr))
138         return;
139 
140     ok_wstri(ExpectPath1, FirstFileW);
141     CoTaskMemFree(FirstFileW);
142 }
143 
144 static void TestFilenameW(PVOID pData, SIZE_T Size, LPCWSTR ExpectRoot, LPCWSTR ExpectPath1, LPCWSTR ExpectPath2)
145 {
146     LPCWSTR FirstFile = (LPCWSTR)pData;
147     ok_wstri(ExpectPath1, FirstFile);
148 }
149 
150 
151 static void TestDefaultFormat(PCIDLIST_ABSOLUTE pidlFolder, UINT cidl, PCUIDLIST_RELATIVE_ARRAY apidl)
152 {
153     CComPtr<IDataObject> spDataObj;
154     HRESULT hr = CIDLData_CreateFromIDArray(pidlFolder, cidl, apidl, &spDataObj);
155 
156     ok_hex(hr, S_OK);
157     if (!SUCCEEDED(hr))
158         return;
159 
160     CComPtr<IEnumFORMATETC> pEnumFmt;
161     hr = spDataObj->EnumFormatEtc(DATADIR_GET, &pEnumFmt);
162 
163     ok_hex(hr, S_OK);
164     if (!SUCCEEDED(hr))
165         return;
166 
167     UINT Expected[4] = {
168         RegisterClipboardFormatA(CFSTR_SHELLIDLISTA),
169         CF_HDROP,
170         RegisterClipboardFormatA(CFSTR_FILENAMEA),
171         RegisterClipboardFormatA("FileNameW"),
172     };
173 
174     UINT Count = 0;
175     FORMATETC fmt;
176     while (S_OK == (hr=pEnumFmt->Next(1, &fmt, NULL)))
177     {
178         char szGot[512], szExpected[512];
179         GetClipboardFormatNameA(fmt.cfFormat, szGot, sizeof(szGot));
180         ok(Count < _countof(Expected), "%u\n", Count);
181         if (Count < _countof(Expected))
182         {
183             GetClipboardFormatNameA(Expected[Count], szExpected, sizeof(szExpected));
184             ok(fmt.cfFormat == Expected[Count], "Got 0x%x(%s), expected 0x%x(%s) for %u\n",
185                fmt.cfFormat, szGot, Expected[Count], szExpected, Count);
186         }
187 
188         ok(fmt.ptd == NULL, "Got 0x%p, expected 0x%p for [%u].ptd\n", fmt.ptd, (void*)NULL, Count);
189         ok(fmt.dwAspect == DVASPECT_CONTENT, "Got 0x%lu, expected 0x%d for [%u].dwAspect\n", fmt.dwAspect, DVASPECT_CONTENT, Count);
190         ok(fmt.lindex == -1, "Got 0x%lx, expected 0x%x for [%u].lindex\n", fmt.lindex, -1, Count);
191         ok(fmt.tymed == TYMED_HGLOBAL, "Got 0x%lu, expected 0x%d for [%u].tymed\n", fmt.tymed, TYMED_HGLOBAL, Count);
192 
193         Count++;
194     }
195     trace("Got %u formats\n", Count);
196     ULONG ExpectedCount = (g_WinVersion < _WIN32_WINNT_WIN8) ? 1 : 4;
197     ok_int(Count, (int)ExpectedCount);
198     ok_hex(hr, S_FALSE);
199 
200     typedef void (*TestFunction)(PVOID pData, SIZE_T Size, LPCWSTR ExpectRoot, LPCWSTR ExpectPath1, LPCWSTR ExpectPath2);
201     TestFunction TestFormats[] = {
202         TestHIDA,
203         TestHDROP,
204         TestFilenameA,
205         TestFilenameW,
206     };
207 
208     WCHAR ExpectRoot[MAX_PATH], ExpectItem1[MAX_PATH], ExpectItem2[MAX_PATH];
209 
210     hr = SHGetFolderPathW(NULL, CSIDL_WINDOWS, NULL, 0, ExpectRoot);
211     ok_hex(hr, S_OK);
212     if (!SUCCEEDED(hr))
213         return;
214 
215     hr = SHGetFolderPathW(NULL, CSIDL_SYSTEM, NULL, 0, ExpectItem1);
216     ok_hex(hr, S_OK);
217     if (!SUCCEEDED(hr))
218         return;
219 
220     hr = SHGetFolderPathW(NULL, CSIDL_RESOURCES, NULL, 0, ExpectItem2);
221     ok_hex(hr, S_OK);
222     if (!SUCCEEDED(hr))
223         return;
224 
225 
226     /* The formats are not synthesized on request */
227     for (Count = 0; Count < _countof(Expected); ++Count)
228     {
229         STGMEDIUM medium = {0};
230         FORMATETC etc = { (CLIPFORMAT)Expected[Count], NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
231         char szExpected[512];
232 
233         GetClipboardFormatNameA(etc.cfFormat, szExpected, sizeof(szExpected));
234         hr = spDataObj->GetData(&etc, &medium);
235         HRESULT hr2 = spDataObj->QueryGetData(&etc);
236         ok_hex(hr2, SUCCEEDED(hr) ? S_OK : S_FALSE);
237 
238         if (Count < ExpectedCount)
239         {
240             ok(hr == S_OK, "0x%x (0x%x(%s))\n", (unsigned int)hr, Expected[Count], szExpected);
241             ok(medium.tymed == TYMED_HGLOBAL, "0x%lx (0x%x(%s))\n", medium.tymed, Expected[Count], szExpected);
242             if (hr == S_OK && medium.tymed == TYMED_HGLOBAL)
243             {
244                 PVOID pData = GlobalLock(medium.hGlobal);
245                 SIZE_T Size = GlobalSize(medium.hGlobal);
246                 TestFormats[Count](pData, Size, ExpectRoot, ExpectItem1, ExpectItem2);
247                 GlobalUnlock(medium.hGlobal);
248             }
249         }
250         else
251         {
252             if (g_WinVersion < _WIN32_WINNT_VISTA)
253                 ok(hr == E_INVALIDARG, "0x%x (0x%x(%s))\n", (unsigned int)hr, Expected[Count], szExpected);
254             else
255                 ok(hr == DV_E_FORMATETC, "0x%x (0x%x(%s))\n", (unsigned int)hr, Expected[Count], szExpected);
256         }
257 
258         if (SUCCEEDED(hr))
259             ReleaseStgMedium(&medium);
260     }
261 
262     // Not registered
263     CLIPFORMAT Format = RegisterClipboardFormatW(CFSTR_PREFERREDDROPEFFECTW);
264     FORMATETC formatetc = { Format, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
265     STGMEDIUM medium;
266 
267     hr = spDataObj->GetData(&formatetc, &medium);
268     if (g_WinVersion < _WIN32_WINNT_VISTA)
269         ok_hex(hr, E_INVALIDARG);
270     else
271         ok_hex(hr, DV_E_FORMATETC);
272 }
273 
274 
275 static void TestSetAndGetExtraFormat(PCIDLIST_ABSOLUTE pidlFolder, UINT cidl, PCUIDLIST_RELATIVE_ARRAY apidl)
276 {
277     CComPtr<IDataObject> spDataObj;
278     HRESULT hr = CIDLData_CreateFromIDArray(pidlFolder, cidl, apidl, &spDataObj);
279 
280     ok_hex(hr, S_OK);
281     if (!SUCCEEDED(hr))
282         return;
283 
284     STGMEDIUM medium = {0};
285     medium.tymed = TYMED_HGLOBAL;
286     medium.hGlobal = GlobalAlloc(GHND, sizeof(DWORD));
287     ok(medium.hGlobal != NULL, "Download more ram\n");
288     PDWORD data = (PDWORD)GlobalLock(medium.hGlobal);
289     *data = 12345;
290     GlobalUnlock(medium.hGlobal);
291 
292     UINT flags = GlobalFlags(medium.hGlobal);
293     SIZE_T size = GlobalSize(medium.hGlobal);
294     ok_hex(flags, 0);
295     ok_size_t(size, sizeof(DWORD));
296 
297     FORMATETC etc = { (CLIPFORMAT)RegisterClipboardFormatA(CFSTR_INDRAGLOOPA), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
298     FORMATETC etc2 = etc;
299 
300     /* Not supported! */
301     hr = spDataObj->SetData(&etc, &medium, FALSE);
302     if (g_WinVersion < _WIN32_WINNT_WIN8)
303         ok_hex(hr, E_INVALIDARG);
304     else
305         ok_hex(hr, E_NOTIMPL);
306 
307     /* Object takes ownership! */
308     hr = spDataObj->SetData(&etc, &medium, TRUE);
309     ok_hex(hr, S_OK);
310     if (!SUCCEEDED(hr))
311         return;
312 
313     /* Does not touch the hGlobal! */
314     flags = GlobalFlags(medium.hGlobal);
315     size = GlobalSize(medium.hGlobal);
316     ok_hex(flags, 0);
317     ok_size_t(size, sizeof(DWORD));
318 
319     STGMEDIUM medium2 = {0};
320 
321     /* No conversion */
322     etc2.dwAspect = DVASPECT_DOCPRINT;
323     hr = spDataObj->GetData(&etc2, &medium2);
324     HRESULT hr2 = spDataObj->QueryGetData(&etc2);
325     ok_hex(hr2, SUCCEEDED(hr) ? S_OK : S_FALSE);
326     if (g_WinVersion < _WIN32_WINNT_VISTA)
327         ok_hex(hr, E_INVALIDARG);
328     else
329         ok_hex(hr, DV_E_FORMATETC);
330 
331     etc2.dwAspect = DVASPECT_CONTENT;
332     etc2.tymed = TYMED_NULL;
333     hr = spDataObj->GetData(&etc2, &medium2);
334     hr2 = spDataObj->QueryGetData(&etc2);
335     ok_hex(hr2, SUCCEEDED(hr) ? S_OK : S_FALSE);
336     if (g_WinVersion < _WIN32_WINNT_VISTA)
337         ok_hex(hr, E_INVALIDARG);
338     else
339         ok_hex(hr, DV_E_FORMATETC);
340     etc2.tymed = TYMED_HGLOBAL;
341 
342     ok_ptr(medium2.pUnkForRelease, NULL);
343     hr = spDataObj->GetData(&etc2, &medium2);
344     hr2 = spDataObj->QueryGetData(&etc2);
345     ok_hex(hr2, SUCCEEDED(hr) ? S_OK : S_FALSE);
346     ok_hex(hr, S_OK);
347     if (hr == S_OK)
348     {
349         ok_hex(medium2.tymed, TYMED_HGLOBAL);
350         if (g_WinVersion < _WIN32_WINNT_VISTA)
351         {
352             /* The IDataObject is set as pUnkForRelease */
353             ok(medium2.pUnkForRelease == (IUnknown*)spDataObj, "Expected the data object (0x%p), got 0x%p\n",
354                 (IUnknown*)spDataObj, medium2.pUnkForRelease);
355             ok(medium.hGlobal == medium2.hGlobal, "Pointers are not the same!, got 0x%p and 0x%p\n", medium.hGlobal, medium2.hGlobal);
356         }
357         else
358         {
359             ok_ptr(medium2.pUnkForRelease, NULL);
360             ok(medium.hGlobal != medium2.hGlobal, "Pointers are the same!\n");
361         }
362 
363         flags = GlobalFlags(medium2.hGlobal);
364         size = GlobalSize(medium2.hGlobal);
365         ok_hex(flags, 0);
366         ok_size_t(size, sizeof(DWORD));
367 
368         data = (PDWORD)GlobalLock(medium2.hGlobal);
369         if (data)
370             ok_int(*data, 12345);
371         else
372             ok(0, "GlobalLock: %lu\n", GetLastError());
373         GlobalUnlock(medium2.hGlobal);
374 
375         HGLOBAL backup = medium2.hGlobal;
376         ReleaseStgMedium(&medium2);
377 
378         flags = GlobalFlags(backup);
379         size = GlobalSize(backup);
380         if (g_WinVersion < _WIN32_WINNT_VISTA)
381         {
382             /* Same object! just the pUnkForRelease was set, so original hGlobal is still valid */
383             ok_hex(flags, 0);
384             ok_size_t(size, sizeof(DWORD));
385         }
386         else
387         {
388             ok_hex(flags, GMEM_INVALID_HANDLE);
389             ok_size_t(size, 0);
390         }
391 
392         /* Original is still intact (but no longer ours!) */
393         flags = GlobalFlags(medium.hGlobal);
394         size = GlobalSize(medium.hGlobal);
395         ok_hex(flags, 0);
396         ok_size_t(size, sizeof(DWORD));
397     }
398 
399     HGLOBAL backup = medium.hGlobal;
400     spDataObj.Release();
401 
402     /* Now our hGlobal is deleted */
403     flags = GlobalFlags(backup);
404     size = GlobalSize(backup);
405     ok_hex(flags, GMEM_INVALID_HANDLE);
406     ok_size_t(size, 0);
407 }
408 
409 START_TEST(CIDLData)
410 {
411     HRESULT hr;
412 
413     CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
414 
415     RTL_OSVERSIONINFOEXW rtlinfo = {0};
416 
417     rtlinfo.dwOSVersionInfoSize = sizeof(rtlinfo);
418     RtlGetVersion((PRTL_OSVERSIONINFOW)&rtlinfo);
419     g_WinVersion = (rtlinfo.dwMajorVersion << 8) | rtlinfo.dwMinorVersion;
420 
421     CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlWindows;
422     CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlSystem32;
423     CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlResources;
424 
425     hr = SHGetFolderLocation(NULL, CSIDL_WINDOWS, NULL, 0, &pidlWindows);
426     ok_hex(hr, S_OK);
427     if (!SUCCEEDED(hr))
428         return;
429 
430     hr = SHGetFolderLocation(NULL, CSIDL_SYSTEM, NULL, 0, &pidlSystem32);
431     ok_hex(hr, S_OK);
432     if (!SUCCEEDED(hr))
433         return;
434 
435     hr = SHGetFolderLocation(NULL, CSIDL_RESOURCES, NULL, 0, &pidlResources);
436     ok_hex(hr, S_OK);
437     if (!SUCCEEDED(hr))
438         return;
439 
440     CComPtr<IShellFolder> shellFolder;
441     PCUITEMID_CHILD child1;
442     hr = SHBindToParent(pidlSystem32, IID_PPV_ARG(IShellFolder, &shellFolder), &child1);
443     ok_hex(hr, S_OK);
444     if (!SUCCEEDED(hr))
445         return;
446 
447     PCUITEMID_CHILD child2 = ILFindLastID(pidlResources);
448 
449     UINT cidl = 2;
450     PCUIDLIST_RELATIVE apidl[2] = {
451         child1, child2
452     };
453 
454     TestAdviseAndCanonical(pidlWindows, cidl, apidl);
455     TestDefaultFormat(pidlWindows, cidl, apidl);
456     TestSetAndGetExtraFormat(pidlWindows, cidl, apidl);
457 }
458