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 
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 
33     static BOOL IsFS(LPCITEMIDLIST p)
34     {
35         return (p && p->mkid.cb > 2) ? (p->mkid.abID[0] & 0x70) == 0x30 : FALSE;
36     }
37     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 
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 
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 
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 
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 
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