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
12 enum {
13 DIRBIT = 1, FILEBIT = 2,
14 PT_COMPUTER_REGITEM = 0x2E,
15 };
16
GetPIDLType(LPCITEMIDLIST pidl)17 static BYTE GetPIDLType(LPCITEMIDLIST pidl)
18 {
19 // Return the type without the 0x80 flag
20 return pidl && pidl->mkid.cb >= 3 ? (pidl->mkid.abID[0] & 0x7F) : 0;
21 }
22
23 struct FS95 // FileSystem item header
24 {
25 WORD cb;
26 BYTE type;
27 BYTE unknown;
28 UINT size;
29 WORD date, time;
30 WORD att;
31 CHAR name[ANYSIZE_ARRAY];
32
IsFSFS9533 static BOOL IsFS(LPCITEMIDLIST p)
34 {
35 return (p && p->mkid.cb > 2) ? (p->mkid.abID[0] & 0x70) == 0x30 : FALSE;
36 }
ValidateFS9537 static FS95* Validate(LPCITEMIDLIST p)
38 {
39 C_ASSERT(FIELD_OFFSET(FS95, name) == 14);
40 return p && p->mkid.cb > FIELD_OFFSET(FS95, name) && IsFS(p) ? (FS95*)p : NULL;
41 }
42 };
43
FileStruct_Att(LPCITEMIDLIST pidl)44 static int FileStruct_Att(LPCITEMIDLIST pidl)
45 {
46 C_ASSERT(FIELD_OFFSET(FS95, att) == 12);
47 FS95 *p = FS95::Validate(pidl);
48 return p ? p->att : (UINT(1) << 31);
49 }
50
51 #define TEST_CLSID(pidl, type, offset, clsid) \
52 do { \
53 ok_long(GetPIDLType(pidl), (type)); \
54 ok_int(*(CLSID*)((&pidl->mkid.abID[(offset) - sizeof(WORD)])) == clsid, TRUE); \
55 } while (0)
56
START_TEST(SHSimpleIDListFromPath)57 START_TEST(SHSimpleIDListFromPath)
58 {
59 HRESULT hr;
60 WCHAR szPath[MAX_PATH];
61 GetWindowsDirectoryW(szPath, _countof(szPath));
62
63 // We compare pidl1 and pidl2
64 CComHeapPtr<ITEMIDLIST> pidl1(SHSimpleIDListFromPath(szPath));
65 CComHeapPtr<ITEMIDLIST> pidl2(ILCreateFromPathW(szPath));
66
67 // Yes, they are equal logically
68 LPITEMIDLIST pidl1Last = ILFindLastID(pidl1), pidl2Last = ILFindLastID(pidl2);
69 ok_int(ILIsEqual(pidl1, pidl2), TRUE);
70 ok_int(ILIsEqual(pidl1Last, pidl2Last), TRUE);
71
72 // Bind to parent
73 CComPtr<IShellFolder> psf1, psf2;
74 hr = SHBindToParent(pidl1, IID_PPV_ARG(IShellFolder, &psf1), NULL);
75 ok_long(hr, S_OK);
76 hr = SHBindToParent(pidl2, IID_PPV_ARG(IShellFolder, &psf2), NULL);
77 ok_long(hr, S_OK);
78
79 // Get attributes
80 DWORD attrs1 = SFGAO_FOLDER, attrs2 = SFGAO_FOLDER;
81 hr = (psf1 ? psf1->GetAttributesOf(1, &pidl1Last, &attrs1) : E_UNEXPECTED);
82 ok_long(hr, S_OK);
83 hr = (psf2 ? psf2->GetAttributesOf(1, &pidl2Last, &attrs2) : E_UNEXPECTED);
84 ok_long(hr, S_OK);
85
86 // There is the difference in attributes because SHSimpleIDListFromPath
87 // cannot create PIDLs to folders, only files and drives:
88 ok_long((attrs1 & SFGAO_FOLDER), 0);
89 ok_long((attrs2 & SFGAO_FOLDER), SFGAO_FOLDER);
90
91
92 // Make sure the internal details match Windows NT5+
93 LPITEMIDLIST item;
94 GetSystemDirectoryW(szPath, _countof(szPath));
95 CComHeapPtr<ITEMIDLIST> pidlSys32(SHSimpleIDListFromPath(szPath));
96 if (szPath[1] != ':' || PathFindFileNameW(szPath) <= szPath)
97 {
98 skip("Not a local directory %ls\n", szPath);
99 }
100 else if (!(LPITEMIDLIST)pidlSys32)
101 {
102 skip("?\n");
103 }
104 else
105 {
106 item = ILFindLastID(pidlSys32);
107 ok_long(item->mkid.abID[0] & 0x73, 0x30 | FILEBIT); // This is actually a file PIDL
108 ok_long(FileStruct_Att(item), 0); // Simple PIDL without attributes
109 ok_int(*(UINT*)(&item->mkid.abID[2]), 0); // No size
110
111 ILRemoveLastID(pidlSys32); // Now we should have "c:\Windows"
112 item = ILFindLastID(pidlSys32);
113 ok_long(item->mkid.abID[0] & 0x73, 0x30 | DIRBIT);
114 ok_int(*(UINT*)(&item->mkid.abID[2]), 0); // No size
115 }
116
117 WCHAR drive[4] = { szPath[0], szPath[1], L'\\', L'\0' };
118 CComHeapPtr<ITEMIDLIST> pidlDrive(SHSimpleIDListFromPath(drive));
119 if (drive[1] != ':')
120 {
121 skip("Not a local drive %ls\n", drive);
122 }
123 else if (!(LPITEMIDLIST)pidlDrive)
124 {
125 skip("?\n");
126 }
127 else
128 {
129 item = ILFindLastID(pidlDrive);
130 ok_long(item->mkid.abID[0] & 0x70, 0x20); // Something in My Computer
131 ok_char(item->mkid.abID[1] | 32, drive[0] | 32);
132 }
133
134 CComHeapPtr<ITEMIDLIST> pidlVirt(SHSimpleIDListFromPath(L"x:\\IDontExist"));
135 if (!(LPITEMIDLIST)pidlVirt)
136 {
137 skip("?\n");
138 }
139 else
140 {
141 item = ILFindLastID(pidlVirt);
142 ok_long(item->mkid.abID[0] & 0x73, 0x30 | FILEBIT); // Yes, a file
143 ok_long(FileStruct_Att(item), 0); // Simple PIDL, no attributes
144 ok_int(*(UINT*)(&item->mkid.abID[2]), 0); // No size
145
146 ILRemoveLastID(pidlVirt); // "x:\"
147 item = ILFindLastID(pidlVirt);
148 ok_long(item->mkid.abID[0] & 0x70, 0x20); // Something in My Computer
149 ok_char(item->mkid.abID[1] | 32, 'x' | 32); // x:
150 }
151
152 LPITEMIDLIST pidl;
153 ok_int((pidl = SHSimpleIDListFromPath(L"c:")) != NULL, TRUE);
154 ILFree(pidl);
155 ok_int((pidl = SHSimpleIDListFromPath(L"c:\\")) != NULL, TRUE);
156 ILFree(pidl);
157 }
158
START_TEST(ILCreateFromPath)159 START_TEST(ILCreateFromPath)
160 {
161 WCHAR szPath[MAX_PATH];
162 LPITEMIDLIST item;
163
164 ok_ptr(ILCreateFromPathW(L"c:\\IDontExist"), NULL);
165
166 GetSystemDirectoryW(szPath, _countof(szPath));
167 CComHeapPtr<ITEMIDLIST> pidlDir(ILCreateFromPathW(szPath));
168 if (szPath[1] != ':' || PathFindFileNameW(szPath) <= szPath)
169 {
170 skip("Not a local directory %ls\n", szPath);
171 }
172 else if (!(LPITEMIDLIST)pidlDir)
173 {
174 skip("?\n");
175 }
176 else
177 {
178 item = ILFindLastID(pidlDir);
179 ok_long(item->mkid.abID[0] & 0x73, 0x30 | DIRBIT);
180 ok_int(*(UINT*)(&item->mkid.abID[2]), 0); // No size
181 }
182 PathAppendW(szPath, L"kernel32.dll");
183 CComHeapPtr<ITEMIDLIST> pidlFile(ILCreateFromPathW(szPath));
184 if (!(LPITEMIDLIST)pidlFile)
185 {
186 skip("?\n");
187 }
188 else
189 {
190 item = ILFindLastID(pidlFile);
191 ok_long(item->mkid.abID[0] & 0x73, 0x30 | FILEBIT);
192 ok_int(*(UINT*)(&item->mkid.abID[2]) > 1024 * 42, TRUE); // At least this large
193 }
194 }
195
START_TEST(PIDL)196 START_TEST(PIDL)
197 {
198 LPITEMIDLIST pidl;
199
200 pidl = SHCloneSpecialIDList(NULL, CSIDL_DRIVES, FALSE);
201 if (pidl)
202 TEST_CLSID(ILFindLastID(pidl), 0x1f, 4, CLSID_MyComputer);
203 else
204 skip("?\n");
205 ILFree(pidl);
206
207 pidl = SHCloneSpecialIDList(NULL, CSIDL_PRINTERS, FALSE);
208 if (pidl)
209 TEST_CLSID(ILFindLastID(pidl), 0x71, 14, CLSID_Printers);
210 else
211 skip("?\n");
212 ILFree(pidl);
213 }
214
START_TEST(ILIsEqual)215 START_TEST(ILIsEqual)
216 {
217 LPITEMIDLIST p1, p2, pidl;
218
219 p1 = p2 = NULL;
220 ok_int(ILIsEqual(p1, p2), TRUE);
221
222 ITEMIDLIST emptyitem = {}, emptyitem2 = {};
223 ok_int(ILIsEqual(&emptyitem, &emptyitem2), TRUE);
224
225 ok_int(ILIsEqual(NULL, &emptyitem), FALSE); // These two are not equal for some reason
226
227 p1 = SHCloneSpecialIDList(NULL, CSIDL_DRIVES, FALSE);
228 p2 = SHCloneSpecialIDList(NULL, CSIDL_DRIVES, FALSE);
229 if (p1 && p2)
230 {
231 ok_int(ILIsEqual(p1, p2), TRUE);
232 p1->mkid.abID[0] = PT_COMPUTER_REGITEM; // RegItem in wrong parent
233 ok_int(ILIsEqual(p1, p2), FALSE);
234 }
235 else
236 {
237 skip("Unable to initialize test\n");
238 }
239 ILFree(p1);
240 ILFree(p2);
241
242 // ILIsParent must compare like ILIsEqual
243 p1 = SHSimpleIDListFromPath(L"c:\\");
244 p2 = SHSimpleIDListFromPath(L"c:\\dir\\file");
245 if (p1 && p2)
246 {
247 ok_int(ILIsParent(NULL, p1, FALSE), FALSE); // NULL is always false
248 ok_int(ILIsParent(p1, NULL, FALSE), FALSE); // NULL is always false
249 ok_int(ILIsParent(NULL, NULL, FALSE), FALSE); // NULL is always false
250 ok_int(ILIsParent(p1, p1, FALSE), TRUE); // I'm my own parent
251 ok_int(ILIsParent(p1, p1, TRUE), FALSE); // Self is not immediate
252 ok_int(ILIsParent(p1, p2, FALSE), TRUE); // Grandchild
253 ok_int(ILIsParent(p1, p2, TRUE), FALSE); // Grandchild is not immediate
254 ok_ptr(ILFindChild(p1, p2), ILGetNext(ILGetNext(p2))); // Child is "dir\\file", skip MyComputer and C:
255 ok_int(ILIsEmpty(pidl = ILFindChild(p1, p1)) && pidl, TRUE); // Self
256 ILRemoveLastID(p2);
257 ok_int(ILIsParent(p1, p2, TRUE), TRUE); // Immediate child
258
259 p1->mkid.abID[0] = PT_COMPUTER_REGITEM; // RegItem in wrong parent
260 ok_int(ILIsParent(p1, p2, FALSE), FALSE);
261 }
262 else
263 {
264 skip("Unable to initialize test\n");
265 }
266 ILFree(p1);
267 ILFree(p2);
268 }
269