Reactos
at master 342 lines 11 kB view raw
1/* 2 * PROJECT: ReactOS API tests 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * PURPOSE: Test for SHSimpleIDListFromPath 5 * COPYRIGHT: Copyright 2024 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) 6 * Copyright 2024 Whindmar Saksit <whindsaks@proton.me> 7 */ 8 9#include "shelltest.h" 10#include <shellutils.h> 11 12enum { 13 DIRBIT = 1, FILEBIT = 2, 14 PT_COMPUTER_REGITEM = 0x2E, 15 PT_INTERNET_URL = 0x61, 16 PT_CONTROLS_OLDREGITEM = 0x70, 17 PT_CONTROLS_NEWREGITEM = 0x71, 18}; 19 20static BYTE GetPIDLType(LPCITEMIDLIST pidl) 21{ 22 // Return the type without the 0x80 flag 23 return pidl && pidl->mkid.cb >= 3 ? (pidl->mkid.abID[0] & 0x7F) : 0; 24} 25 26struct FS95 // FileSystem item header 27{ 28 WORD cb; 29 BYTE type; 30 BYTE unknown; 31 UINT size; 32 WORD date, time; 33 WORD att; 34 CHAR name[ANYSIZE_ARRAY]; 35 36 static BOOL IsFS(LPCITEMIDLIST p) 37 { 38 return (p && p->mkid.cb > 2) ? (p->mkid.abID[0] & 0x70) == 0x30 : FALSE; 39 } 40 static FS95* Validate(LPCITEMIDLIST p) 41 { 42 C_ASSERT(FIELD_OFFSET(FS95, name) == 14); 43 return p && p->mkid.cb > FIELD_OFFSET(FS95, name) && IsFS(p) ? (FS95*)p : NULL; 44 } 45}; 46 47static int FileStruct_Att(LPCITEMIDLIST pidl) 48{ 49 C_ASSERT(FIELD_OFFSET(FS95, att) == 12); 50 FS95 *p = FS95::Validate(pidl); 51 return p ? p->att : (UINT(1) << 31); 52} 53 54static HRESULT ParseDisplayName(PWSTR pszPath, LPITEMIDLIST *ppidl, IBindCtx *pBC = NULL) 55{ 56 CComPtr<IShellFolder> pSF; 57 HRESULT hr = SHGetDesktopFolder(&pSF); 58 return FAILED(hr) ? hr : pSF->ParseDisplayName(NULL, pBC, pszPath, NULL, ppidl, NULL); 59} 60 61static HRESULT GetDisplayNameOf(IShellFolder *pSF, LPCITEMIDLIST pidl, UINT Flags, PWSTR Buf, UINT Cap) 62{ 63 STRRET sr; 64 HRESULT hr = pSF->GetDisplayNameOf(pidl, Flags, &sr); 65 if (SUCCEEDED(hr)) 66 hr = StrRetToBufW(&sr, pidl, Buf, Cap); 67 return hr; 68} 69 70static HRESULT GetDisplayNameOf(PCIDLIST_ABSOLUTE pidl, UINT Flags, PWSTR Buf, UINT Cap) 71{ 72 CComPtr<IShellFolder> pSF; 73 PCUITEMID_CHILD pidlChild; 74 HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &pSF), &pidlChild); 75 return FAILED(hr) ? hr : GetDisplayNameOf(pSF, pidlChild, Flags, Buf, Cap); 76} 77 78#define TEST_CLSID(pidl, type, offset, clsid) \ 79 do { \ 80 ok_long(GetPIDLType(pidl), (type)); \ 81 ok_int(*(CLSID*)((&pidl->mkid.abID[(offset) - sizeof(WORD)])) == clsid, TRUE); \ 82 } while (0) 83 84START_TEST(SHSimpleIDListFromPath) 85{ 86 HRESULT hr; 87 WCHAR szPath[MAX_PATH]; 88 GetWindowsDirectoryW(szPath, _countof(szPath)); 89 90 // We compare pidl1 and pidl2 91 CComHeapPtr<ITEMIDLIST> pidl1(SHSimpleIDListFromPath(szPath)); 92 CComHeapPtr<ITEMIDLIST> pidl2(ILCreateFromPathW(szPath)); 93 94 // Yes, they are equal logically 95 LPITEMIDLIST pidl1Last = ILFindLastID(pidl1), pidl2Last = ILFindLastID(pidl2); 96 ok_int(ILIsEqual(pidl1, pidl2), TRUE); 97 ok_int(ILIsEqual(pidl1Last, pidl2Last), TRUE); 98 99 // Bind to parent 100 CComPtr<IShellFolder> psf1, psf2; 101 hr = SHBindToParent(pidl1, IID_PPV_ARG(IShellFolder, &psf1), NULL); 102 ok_long(hr, S_OK); 103 hr = SHBindToParent(pidl2, IID_PPV_ARG(IShellFolder, &psf2), NULL); 104 ok_long(hr, S_OK); 105 106 // Get attributes 107 DWORD attrs1 = SFGAO_FOLDER, attrs2 = SFGAO_FOLDER; 108 hr = (psf1 ? psf1->GetAttributesOf(1, &pidl1Last, &attrs1) : E_UNEXPECTED); 109 ok_long(hr, S_OK); 110 hr = (psf2 ? psf2->GetAttributesOf(1, &pidl2Last, &attrs2) : E_UNEXPECTED); 111 ok_long(hr, S_OK); 112 113 // There is the difference in attributes because SHSimpleIDListFromPath 114 // cannot create PIDLs to folders, only files and drives: 115 ok_long((attrs1 & SFGAO_FOLDER), 0); 116 ok_long((attrs2 & SFGAO_FOLDER), SFGAO_FOLDER); 117 118 119 // Make sure the internal details match Windows NT5+ 120 LPITEMIDLIST item; 121 GetSystemDirectoryW(szPath, _countof(szPath)); 122 CComHeapPtr<ITEMIDLIST> pidlSys32(SHSimpleIDListFromPath(szPath)); 123 if (szPath[1] != ':' || PathFindFileNameW(szPath) <= szPath) 124 { 125 skip("Not a local directory %ls\n", szPath); 126 } 127 else if (!(LPITEMIDLIST)pidlSys32) 128 { 129 skip("?\n"); 130 } 131 else 132 { 133 item = ILFindLastID(pidlSys32); 134 ok_long(item->mkid.abID[0] & 0x73, 0x30 | FILEBIT); // This is actually a file PIDL 135 ok_long(FileStruct_Att(item), 0); // Simple PIDL without attributes 136 ok_int(*(UINT*)(&item->mkid.abID[2]), 0); // No size 137 138 ILRemoveLastID(pidlSys32); // Now we should have "c:\Windows" 139 item = ILFindLastID(pidlSys32); 140 ok_long(item->mkid.abID[0] & 0x73, 0x30 | DIRBIT); 141 ok_int(*(UINT*)(&item->mkid.abID[2]), 0); // No size 142 } 143 144 WCHAR drive[4] = { szPath[0], szPath[1], L'\\', L'\0' }; 145 CComHeapPtr<ITEMIDLIST> pidlDrive(SHSimpleIDListFromPath(drive)); 146 if (drive[1] != ':') 147 { 148 skip("Not a local drive %ls\n", drive); 149 } 150 else if (!(LPITEMIDLIST)pidlDrive) 151 { 152 skip("?\n"); 153 } 154 else 155 { 156 item = ILFindLastID(pidlDrive); 157 ok_long(item->mkid.abID[0] & 0x70, 0x20); // Something in My Computer 158 ok_char(item->mkid.abID[1] | 32, drive[0] | 32); 159 } 160 161 CComHeapPtr<ITEMIDLIST> pidlVirt(SHSimpleIDListFromPath(L"x:\\IDontExist")); 162 if (!(LPITEMIDLIST)pidlVirt) 163 { 164 skip("?\n"); 165 } 166 else 167 { 168 item = ILFindLastID(pidlVirt); 169 ok_long(item->mkid.abID[0] & 0x73, 0x30 | FILEBIT); // Yes, a file 170 ok_long(FileStruct_Att(item), 0); // Simple PIDL, no attributes 171 ok_int(*(UINT*)(&item->mkid.abID[2]), 0); // No size 172 173 ILRemoveLastID(pidlVirt); // "x:\" 174 item = ILFindLastID(pidlVirt); 175 ok_long(item->mkid.abID[0] & 0x70, 0x20); // Something in My Computer 176 ok_char(item->mkid.abID[1] | 32, 'x' | 32); // x: 177 } 178 179 LPITEMIDLIST pidl; 180 ok_int((pidl = SHSimpleIDListFromPath(L"c:")) != NULL, LOBYTE(GetVersion()) >= 6); 181 ILFree(pidl); 182 ok_int((pidl = SHSimpleIDListFromPath(L"c:\\")) != NULL, TRUE); 183 ILFree(pidl); 184} 185 186START_TEST(ILCreateFromPath) 187{ 188 WCHAR szPath[MAX_PATH]; 189 LPITEMIDLIST item; 190 191 ok_ptr(ILCreateFromPathW(L"c:\\IDontExist"), NULL); 192 193 GetSystemDirectoryW(szPath, _countof(szPath)); 194 CComHeapPtr<ITEMIDLIST> pidlDir(ILCreateFromPathW(szPath)); 195 if (szPath[1] != ':' || PathFindFileNameW(szPath) <= szPath) 196 { 197 skip("Not a local directory %ls\n", szPath); 198 } 199 else if (!(LPITEMIDLIST)pidlDir) 200 { 201 skip("?\n"); 202 } 203 else 204 { 205 item = ILFindLastID(pidlDir); 206 ok_long(item->mkid.abID[0] & 0x73, 0x30 | DIRBIT); 207 ok_int(*(UINT*)(&item->mkid.abID[2]), 0); // No size 208 } 209 PathAppendW(szPath, L"kernel32.dll"); 210 CComHeapPtr<ITEMIDLIST> pidlFile(ILCreateFromPathW(szPath)); 211 if (!(LPITEMIDLIST)pidlFile) 212 { 213 skip("?\n"); 214 } 215 else 216 { 217 item = ILFindLastID(pidlFile); 218 ok_long(item->mkid.abID[0] & 0x73, 0x30 | FILEBIT); 219 ok_int(*(UINT*)(&item->mkid.abID[2]) > 1024 * 42, TRUE); // At least this large 220 } 221} 222 223START_TEST(PIDL) 224{ 225 CCoInit ComInit; 226 LPITEMIDLIST pidl; 227 228 pidl = SHCloneSpecialIDList(NULL, CSIDL_DRIVES, FALSE); 229 if (pidl) 230 TEST_CLSID(ILFindLastID(pidl), 0x1f, 4, CLSID_MyComputer); 231 else 232 skip("?\n"); 233 ILFree(pidl); 234 235 pidl = SHCloneSpecialIDList(NULL, CSIDL_PRINTERS, FALSE); 236 if (pidl) 237 { 238 // Accept both the old and new format from the special folder API (NT5 vs NT6) 239 LPITEMIDLIST pidlLeaf = ILFindLastID(pidl); 240 if (LOBYTE(GetVersion()) < 6) 241 TEST_CLSID(pidlLeaf, PT_CONTROLS_OLDREGITEM, 4, CLSID_Printers); 242 else 243 TEST_CLSID(pidlLeaf, PT_CONTROLS_NEWREGITEM, 14, CLSID_Printers); 244 245 // The Control Panel should always return the new format when parsing 246 LPITEMIDLIST pidl2; 247 WCHAR szParse[MAX_PATH]; 248 if (SUCCEEDED(GetDisplayNameOf(pidl, SHGDN_FORPARSING, szParse, _countof(szParse))) && 249 SUCCEEDED(ParseDisplayName(szParse, &pidl2))) 250 { 251 TEST_CLSID(ILFindLastID(pidl2), PT_CONTROLS_NEWREGITEM, 14, CLSID_Printers); 252 ILFree(pidl2); 253 } 254 else 255 { 256 skip("Failed to parse in Control Panel\n"); 257 } 258 } 259 else 260 { 261 skip("?\n"); 262 } 263 ILFree(pidl); 264 265 266 CComPtr<IShellFolder> pInternet; 267 HRESULT hr = SHCoCreateInstance(NULL, &CLSID_Internet, NULL, IID_PPV_ARG(IShellFolder, &pInternet)); 268 if (SUCCEEDED(hr)) 269 { 270 PCWSTR pszUrl = L"http://example.com/page?query&foo=bar"; 271 PIDLIST_RELATIVE pidlUrl; 272 hr = pInternet->ParseDisplayName(NULL, NULL, const_cast<PWSTR>(pszUrl), NULL, &pidlUrl, NULL); 273 ok_long(hr, S_OK); 274 if (hr == S_OK) 275 { 276 ok_int(pidlUrl->mkid.abID[0], PT_INTERNET_URL); 277 WCHAR buf[MAX_PATH]; 278 hr = GetDisplayNameOf(pInternet, pidlUrl, SHGDN_FORPARSING, buf, _countof(buf)); 279 ok_long(hr, S_OK); 280 if (SUCCEEDED(hr)) 281 { 282 ok(!lstrcmpiW(buf, pszUrl), "FORPARSING must match URL\n"); 283 } 284 ILFree(pidlUrl); 285 } 286 } 287} 288 289START_TEST(ILIsEqual) 290{ 291 LPITEMIDLIST p1, p2, pidl; 292 293 p1 = p2 = NULL; 294 ok_int(ILIsEqual(p1, p2), TRUE); 295 296 ITEMIDLIST emptyitem = {}, emptyitem2 = {}; 297 ok_int(ILIsEqual(&emptyitem, &emptyitem2), TRUE); 298 299 ok_int(ILIsEqual(NULL, &emptyitem), FALSE); // These two are not equal for some reason 300 301 p1 = SHCloneSpecialIDList(NULL, CSIDL_DRIVES, FALSE); 302 p2 = SHCloneSpecialIDList(NULL, CSIDL_DRIVES, FALSE); 303 if (p1 && p2) 304 { 305 ok_int(ILIsEqual(p1, p2), TRUE); 306 p1->mkid.abID[0] = PT_COMPUTER_REGITEM; // RegItem in wrong parent 307 ok_int(ILIsEqual(p1, p2), FALSE); 308 } 309 else 310 { 311 skip("Unable to initialize test\n"); 312 } 313 ILFree(p1); 314 ILFree(p2); 315 316 // ILIsParent must compare like ILIsEqual 317 p1 = SHSimpleIDListFromPath(L"c:\\"); 318 p2 = SHSimpleIDListFromPath(L"c:\\dir\\file"); 319 if (p1 && p2) 320 { 321 ok_int(ILIsParent(NULL, p1, FALSE), FALSE); // NULL is always false 322 ok_int(ILIsParent(p1, NULL, FALSE), FALSE); // NULL is always false 323 ok_int(ILIsParent(NULL, NULL, FALSE), FALSE); // NULL is always false 324 ok_int(ILIsParent(p1, p1, FALSE), TRUE); // I'm my own parent 325 ok_int(ILIsParent(p1, p1, TRUE), FALSE); // Self is not immediate 326 ok_int(ILIsParent(p1, p2, FALSE), TRUE); // Grandchild 327 ok_int(ILIsParent(p1, p2, TRUE), FALSE); // Grandchild is not immediate 328 ok_ptr(ILFindChild(p1, p2), ILGetNext(ILGetNext(p2))); // Child is "dir\\file", skip MyComputer and C: 329 ok_int(ILIsEmpty(pidl = ILFindChild(p1, p1)) && pidl, TRUE); // Self 330 ILRemoveLastID(p2); 331 ok_int(ILIsParent(p1, p2, TRUE), TRUE); // Immediate child 332 333 p1->mkid.abID[0] = PT_COMPUTER_REGITEM; // RegItem in wrong parent 334 ok_int(ILIsParent(p1, p2, FALSE), FALSE); 335 } 336 else 337 { 338 skip("Unable to initialize test\n"); 339 } 340 ILFree(p1); 341 ILFree(p2); 342}