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