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