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