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 SHGetAttributesFromDataObject
5  * COPYRIGHT:   Copyright 2021 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 
15 static CLIPFORMAT g_DataObjectAttributes = 0;
16 static const DWORD dwDefaultAttributeMask = SFGAO_CANCOPY | SFGAO_CANMOVE | SFGAO_STORAGE | SFGAO_CANRENAME | SFGAO_CANDELETE |
17                                      SFGAO_READONLY | SFGAO_STREAM | SFGAO_FOLDER;
18 static_assert(dwDefaultAttributeMask == 0x2044003B, "Unexpected default attribute mask");
19 
20 
21 struct TmpFile
22 {
23     WCHAR Buffer[MAX_PATH] = {};
24 
25     void Create(LPCWSTR Folder)
26     {
27         GetTempFileNameW(Folder, L"SHG", 0, Buffer);
28     }
29 
30     ~TmpFile()
31     {
32         if (Buffer[0])
33         {
34             SetFileAttributesW(Buffer, FILE_ATTRIBUTE_NORMAL);
35             DeleteFileW(Buffer);
36         }
37     }
38 };
39 
40 
41 CComPtr<IShellFolder> _BindToObject(PCUIDLIST_ABSOLUTE pidl)
42 {
43     CComPtr<IShellFolder> spDesktop, spResult;
44     HRESULT hr = SHGetDesktopFolder(&spDesktop);
45     if (FAILED_UNEXPECTEDLY(hr))
46         return spResult;
47 
48     if (FAILED_UNEXPECTEDLY(spDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &spResult))))
49     {
50         spResult.Release();
51     }
52     return spResult;
53 }
54 
55 
56 static void ok_attributes_(IDataObject* pDataObject, HRESULT expect_hr, DWORD expect_mask, DWORD expect_attr, UINT expect_items)
57 {
58     FORMATETC fmt = { g_DataObjectAttributes, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
59     STGMEDIUM medium = {};
60 
61     HRESULT hr = pDataObject->GetData(&fmt, &medium);
62     winetest_ok(hr == expect_hr, "Unexpected result from GetData, got 0x%lx, expected 0x%lx\n", hr, expect_hr);
63 
64     if (hr == expect_hr && expect_hr == S_OK)
65     {
66         LPVOID blob = GlobalLock(medium.hGlobal);
67         winetest_ok(blob != nullptr, "Failed to lock hGlobal\n");
68         if (blob)
69         {
70             SIZE_T size = GlobalSize(medium.hGlobal);
71             winetest_ok(size == 0xc, "Unexpected size, got %lu, expected 12\n", size);
72             if (size == 0xc)
73             {
74                 PDWORD data = (PDWORD)blob;
75                 winetest_ok(data[0] == expect_mask, "Unexpected mask, got 0x%lx, expected 0x%lx\n", data[0], expect_mask);
76                 winetest_ok(data[1] == expect_attr, "Unexpected attr, got 0x%lx, expected 0x%lx\n", data[1], expect_attr);
77                 winetest_ok(data[2] == expect_items, "Unexpected item count, got %lu, expected %u\n", data[2], expect_items);
78             }
79             GlobalUnlock(medium.hGlobal);
80         }
81     }
82 
83     if (SUCCEEDED(hr))
84         ReleaseStgMedium(&medium);
85 }
86 
87 
88 #define ok_attributes           (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : ok_attributes_
89 #define ok_hr_ret(x, y)         ok_hr(x, y); if (x != y) return
90 
91 static void test_SpecialCases()
92 {
93     DWORD dwAttributeMask = 0, dwAttributes = 123;
94     UINT cItems = 123;
95 
96     HRESULT hr = SHGetAttributesFromDataObject(nullptr, dwAttributeMask, &dwAttributes, &cItems);
97     ok_hr(hr, S_OK);
98     ok_int(dwAttributes, 0);
99     ok_int(cItems, 0);
100 
101     cItems = 123;
102     hr = SHGetAttributesFromDataObject(nullptr, dwAttributeMask, nullptr, &cItems);
103     ok_hr(hr, S_OK);
104     ok_int(cItems, 0);
105 
106     dwAttributes = 123;
107     hr = SHGetAttributesFromDataObject(nullptr, dwAttributeMask, &dwAttributes, nullptr);
108     ok_hr(hr, S_OK);
109     ok_int(dwAttributes, 0);
110 }
111 
112 
113 static void test_AttributesRegistration()
114 {
115     WCHAR Buffer[MAX_PATH] = {};
116 
117     GetModuleFileNameW(NULL, Buffer, _countof(Buffer));
118     CComHeapPtr<ITEMIDLIST_ABSOLUTE> spPath(ILCreateFromPathW(Buffer));
119 
120     ok(spPath != nullptr, "Unable to create pidl from %S\n", Buffer);
121     if (spPath == nullptr)
122         return;
123 
124     SFGAOF attributes = dwDefaultAttributeMask;
125     HRESULT hr;
126     {
127         CComPtr<IShellFolder> spFolder;
128         PCUITEMID_CHILD child;
129         hr = SHBindToParent(spPath, IID_PPV_ARG(IShellFolder, &spFolder), &child);
130         ok_hr_ret(hr, S_OK);
131 
132         hr = spFolder->GetAttributesOf(1, &child, &attributes);
133         ok_hr_ret(hr, S_OK);
134 
135         attributes &= dwDefaultAttributeMask;
136     }
137 
138     CComHeapPtr<ITEMIDLIST> parent(ILClone(spPath));
139     PCIDLIST_RELATIVE child = ILFindLastID(spPath);
140     ILRemoveLastID(parent);
141 
142     CComPtr<IDataObject> spDataObject;
143     hr = CIDLData_CreateFromIDArray(parent, 1, &child, &spDataObject);
144     ok_hr_ret(hr, S_OK);
145 
146     /* Not registered yet */
147     ok_attributes(spDataObject, DV_E_FORMATETC, 0, 0, 0);
148 
149     /* Ask for attributes, without specifying any */
150     DWORD dwAttributeMask = 0, dwAttributes = 0;
151     UINT cItems = 0;
152     hr = SHGetAttributesFromDataObject(spDataObject, dwAttributeMask, &dwAttributes, &cItems);
153     ok_hr(hr, S_OK);
154 
155     /* Now there are attributes registered */
156     ok_attributes(spDataObject, S_OK, dwDefaultAttributeMask, attributes, 1);
157 
158     // Now add an additional mask value (our exe should have a propsheet!)
159     dwAttributeMask = SFGAO_HASPROPSHEET;
160     dwAttributes = 0;
161     cItems = 0;
162     hr = SHGetAttributesFromDataObject(spDataObject, dwAttributeMask, &dwAttributes, &cItems);
163     ok_hr(hr, S_OK);
164 
165     // Observe that this is now also cached
166     ok_attributes(spDataObject, S_OK, dwDefaultAttributeMask | SFGAO_HASPROPSHEET, attributes | SFGAO_HASPROPSHEET, 1);
167 }
168 
169 static void test_MultipleFiles()
170 {
171     TmpFile TmpFile1, TmpFile2, TmpFile3;
172 
173     CComHeapPtr<ITEMIDLIST> pidl_tmpfolder;
174     CComHeapPtr<ITEMIDLIST> pidl1, pidl2, pidl3;
175 
176     ITEMIDLIST* items[3] = {};
177     SFGAOF attributes_first = dwDefaultAttributeMask;
178     SFGAOF attributes2 = dwDefaultAttributeMask;
179     SFGAOF attributes3 = dwDefaultAttributeMask;
180     SFGAOF attributes_last = dwDefaultAttributeMask;
181 
182     HRESULT hr;
183     {
184         WCHAR TempFolder[MAX_PATH] = {};
185         GetTempPathW(_countof(TempFolder), TempFolder);
186 
187         // Create temp files
188         TmpFile1.Create(TempFolder);
189         TmpFile2.Create(TempFolder);
190         TmpFile3.Create(TempFolder);
191 
192         // Last file is read-only
193         SetFileAttributesW(TmpFile3.Buffer, FILE_ATTRIBUTE_READONLY);
194 
195         hr = SHParseDisplayName(TempFolder, NULL, &pidl_tmpfolder, NULL, NULL);
196         ok_hr_ret(hr, S_OK);
197 
198         CComPtr<IShellFolder> spFolder = _BindToObject(pidl_tmpfolder);
199         ok(!!spFolder, "Unable to bind to tmp folder\n");
200         if (!spFolder)
201             return;
202 
203         hr = spFolder->ParseDisplayName(NULL, 0, PathFindFileNameW(TmpFile1.Buffer), NULL, &pidl1, NULL);
204         ok_hr_ret(hr, S_OK);
205 
206         hr = spFolder->ParseDisplayName(NULL, 0, PathFindFileNameW(TmpFile2.Buffer), NULL, &pidl2, NULL);
207         ok_hr_ret(hr, S_OK);
208 
209         hr = spFolder->ParseDisplayName(NULL, 0, PathFindFileNameW(TmpFile3.Buffer), NULL, &pidl3, NULL);
210         ok_hr_ret(hr, S_OK);
211 
212         items[0] = pidl1;
213         items[1] = pidl2;
214         items[2] = pidl3;
215 
216         // Query file attributes
217         hr = spFolder->GetAttributesOf(1, items, &attributes_first);
218         ok_hr(hr, S_OK);
219 
220         hr = spFolder->GetAttributesOf(2, items, &attributes2);
221         ok_hr(hr, S_OK);
222 
223         hr = spFolder->GetAttributesOf(3, items, &attributes3);
224         ok_hr(hr, S_OK);
225 
226         hr = spFolder->GetAttributesOf(1, items + 2, &attributes_last);
227         ok_hr(hr, S_OK);
228 
229         // Ignore any non-default attributes
230         attributes_first &= dwDefaultAttributeMask;
231         attributes2 &= dwDefaultAttributeMask;
232         attributes3 &= dwDefaultAttributeMask;
233         attributes_last &= dwDefaultAttributeMask;
234     }
235 
236     // Only 'single' files have the stream attribute set
237     ok(attributes_first & SFGAO_STREAM, "Expected SFGAO_STREAM on attributes_first (0x%lx)\n", attributes_first);
238     ok(!(attributes2 & SFGAO_STREAM), "Expected no SFGAO_STREAM on attributes2 (0x%lx)\n", attributes2);
239     ok(!(attributes3 & SFGAO_STREAM), "Expected no SFGAO_STREAM on attributes3 (0x%lx)\n", attributes3);
240     ok(attributes_last & SFGAO_STREAM, "Expected SFGAO_STREAM on attributes_last (0x%lx)\n", attributes_last);
241 
242     // Only attributes common on all are returned, so only the last has the readonly bit set!
243     ok(!(attributes_first & SFGAO_READONLY), "Expected no SFGAO_READONLY on attributes_first (0x%lx)\n", attributes_first);
244     ok(!(attributes2 & SFGAO_READONLY), "Expected no SFGAO_READONLY on attributes2 (0x%lx)\n", attributes2);
245     ok(!(attributes3 & SFGAO_READONLY), "Expected no SFGAO_READONLY on attributes3 (0x%lx)\n", attributes3);
246     ok(attributes_last & SFGAO_READONLY, "Expected SFGAO_READONLY on attributes_last (0x%lx)\n", attributes_last);
247 
248     // The actual tests
249     {
250         // Just the first file
251         CComPtr<IDataObject> spDataObject;
252         hr = CIDLData_CreateFromIDArray(pidl_tmpfolder, 1, items, &spDataObject);
253         ok_hr_ret(hr, S_OK);
254 
255         DWORD dwAttributeMask = 0, dwAttributes = 123;
256         UINT cItems = 123;
257         hr = SHGetAttributesFromDataObject(spDataObject, dwAttributeMask, &dwAttributes, &cItems);
258         ok_hr(hr, S_OK);
259         ok_attributes(spDataObject, S_OK, dwDefaultAttributeMask, attributes_first, 1);
260     }
261 
262     {
263         // First 2 files
264         CComPtr<IDataObject> spDataObject;
265         hr = CIDLData_CreateFromIDArray(pidl_tmpfolder, 2, items, &spDataObject);
266         ok_hr_ret(hr, S_OK);
267 
268         DWORD dwAttributeMask = 0, dwAttributes = 123;
269         UINT cItems = 123;
270         hr = SHGetAttributesFromDataObject(spDataObject, dwAttributeMask, &dwAttributes, &cItems);
271         ok_hr(hr, S_OK);
272         ok_attributes(spDataObject, S_OK, dwDefaultAttributeMask, attributes2, 2);
273     }
274 
275     {
276         // All 3 files
277         CComPtr<IDataObject> spDataObject;
278         hr = CIDLData_CreateFromIDArray(pidl_tmpfolder, 3, items, &spDataObject);
279         ok_hr_ret(hr, S_OK);
280 
281         DWORD dwAttributeMask = 0, dwAttributes = 123;
282         UINT cItems = 123;
283         hr = SHGetAttributesFromDataObject(spDataObject, dwAttributeMask, &dwAttributes, &cItems);
284         ok_hr(hr, S_OK);
285         ok_attributes(spDataObject, S_OK, dwDefaultAttributeMask, attributes3, 3);
286     }
287 
288     {
289         // Only the last file
290         CComPtr<IDataObject> spDataObject;
291         hr = CIDLData_CreateFromIDArray(pidl_tmpfolder, 1, items + 2, &spDataObject);
292         ok_hr_ret(hr, S_OK);
293 
294         DWORD dwAttributeMask = 0, dwAttributes = 123;
295         UINT cItems = 123;
296         hr = SHGetAttributesFromDataObject(spDataObject, dwAttributeMask, &dwAttributes, &cItems);
297         ok_hr(hr, S_OK);
298         ok_attributes(spDataObject, S_OK, dwDefaultAttributeMask, attributes_last, 1);
299     }
300 }
301 
302 START_TEST(SHGetAttributesFromDataObject)
303 {
304     HRESULT hr;
305 
306     hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
307     ok_hr(hr, S_OK);
308     if (!SUCCEEDED(hr))
309         return;
310 
311     g_DataObjectAttributes = (CLIPFORMAT)RegisterClipboardFormatW(L"DataObjectAttributes");
312     ok(g_DataObjectAttributes != 0, "Unable to register DataObjectAttributes\n");
313 
314     test_SpecialCases();
315     test_AttributesRegistration();
316     test_MultipleFiles();
317 
318     CoUninitialize();
319 }
320