1c3ec1b9aSWhindmar Saksit /*
2c3ec1b9aSWhindmar Saksit  * PROJECT:     ReactOS API tests
3c3ec1b9aSWhindmar Saksit  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4c3ec1b9aSWhindmar Saksit  * PURPOSE:     Test for SHSimpleIDListFromPath
5c3ec1b9aSWhindmar Saksit  * COPYRIGHT:   Copyright 2024 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
6c3ec1b9aSWhindmar Saksit  *              Copyright 2024 Whindmar Saksit <whindsaks@proton.me>
7c3ec1b9aSWhindmar Saksit  */
8c3ec1b9aSWhindmar Saksit 
9c3ec1b9aSWhindmar Saksit #include "shelltest.h"
10c3ec1b9aSWhindmar Saksit #include <shellutils.h>
11c3ec1b9aSWhindmar Saksit 
12654c59a5SWhindmar Saksit enum {
13654c59a5SWhindmar Saksit     DIRBIT = 1, FILEBIT = 2,
14654c59a5SWhindmar Saksit     PT_COMPUTER_REGITEM = 0x2E,
15654c59a5SWhindmar Saksit };
16c3ec1b9aSWhindmar Saksit 
GetPIDLType(LPCITEMIDLIST pidl)17c3ec1b9aSWhindmar Saksit static BYTE GetPIDLType(LPCITEMIDLIST pidl)
18c3ec1b9aSWhindmar Saksit {
19c3ec1b9aSWhindmar Saksit     // Return the type without the 0x80 flag
20c3ec1b9aSWhindmar Saksit     return pidl && pidl->mkid.cb >= 3 ? (pidl->mkid.abID[0] & 0x7F) : 0;
21c3ec1b9aSWhindmar Saksit }
22c3ec1b9aSWhindmar Saksit 
23c3ec1b9aSWhindmar Saksit struct FS95 // FileSystem item header
24c3ec1b9aSWhindmar Saksit {
25c3ec1b9aSWhindmar Saksit     WORD cb;
26c3ec1b9aSWhindmar Saksit     BYTE type;
27c3ec1b9aSWhindmar Saksit     BYTE unknown;
28c3ec1b9aSWhindmar Saksit     UINT size;
29c3ec1b9aSWhindmar Saksit     WORD date, time;
30c3ec1b9aSWhindmar Saksit     WORD att;
31c3ec1b9aSWhindmar Saksit     CHAR name[ANYSIZE_ARRAY];
32c3ec1b9aSWhindmar Saksit 
IsFSFS9533c3ec1b9aSWhindmar Saksit     static BOOL IsFS(LPCITEMIDLIST p)
34c3ec1b9aSWhindmar Saksit     {
35c3ec1b9aSWhindmar Saksit         return (p && p->mkid.cb > 2) ? (p->mkid.abID[0] & 0x70) == 0x30 : FALSE;
36c3ec1b9aSWhindmar Saksit     }
ValidateFS9537c3ec1b9aSWhindmar Saksit     static FS95* Validate(LPCITEMIDLIST p)
38c3ec1b9aSWhindmar Saksit     {
39c3ec1b9aSWhindmar Saksit         C_ASSERT(FIELD_OFFSET(FS95, name) == 14);
40c3ec1b9aSWhindmar Saksit         return p && p->mkid.cb > FIELD_OFFSET(FS95, name) && IsFS(p) ? (FS95*)p : NULL;
41c3ec1b9aSWhindmar Saksit     }
42c3ec1b9aSWhindmar Saksit };
43c3ec1b9aSWhindmar Saksit 
FileStruct_Att(LPCITEMIDLIST pidl)44c3ec1b9aSWhindmar Saksit static int FileStruct_Att(LPCITEMIDLIST pidl)
45c3ec1b9aSWhindmar Saksit {
46c3ec1b9aSWhindmar Saksit     C_ASSERT(FIELD_OFFSET(FS95, att) == 12);
47c3ec1b9aSWhindmar Saksit     FS95 *p = FS95::Validate(pidl);
48c3ec1b9aSWhindmar Saksit     return p ? p->att : (UINT(1) << 31);
49c3ec1b9aSWhindmar Saksit }
50c3ec1b9aSWhindmar Saksit 
51c3ec1b9aSWhindmar Saksit #define TEST_CLSID(pidl, type, offset, clsid) \
52c3ec1b9aSWhindmar Saksit     do { \
53c3ec1b9aSWhindmar Saksit         ok_long(GetPIDLType(pidl), (type)); \
54c3ec1b9aSWhindmar Saksit         ok_int(*(CLSID*)((&pidl->mkid.abID[(offset) - sizeof(WORD)])) == clsid, TRUE); \
55c3ec1b9aSWhindmar Saksit     } while (0)
56c3ec1b9aSWhindmar Saksit 
START_TEST(SHSimpleIDListFromPath)57c3ec1b9aSWhindmar Saksit START_TEST(SHSimpleIDListFromPath)
58c3ec1b9aSWhindmar Saksit {
59c3ec1b9aSWhindmar Saksit     HRESULT hr;
60c3ec1b9aSWhindmar Saksit     WCHAR szPath[MAX_PATH];
61c3ec1b9aSWhindmar Saksit     GetWindowsDirectoryW(szPath, _countof(szPath));
62c3ec1b9aSWhindmar Saksit 
63c3ec1b9aSWhindmar Saksit     // We compare pidl1 and pidl2
64c3ec1b9aSWhindmar Saksit     CComHeapPtr<ITEMIDLIST> pidl1(SHSimpleIDListFromPath(szPath));
65c3ec1b9aSWhindmar Saksit     CComHeapPtr<ITEMIDLIST> pidl2(ILCreateFromPathW(szPath));
66c3ec1b9aSWhindmar Saksit 
67c3ec1b9aSWhindmar Saksit     // Yes, they are equal logically
68c3ec1b9aSWhindmar Saksit     LPITEMIDLIST pidl1Last = ILFindLastID(pidl1), pidl2Last = ILFindLastID(pidl2);
69c3ec1b9aSWhindmar Saksit     ok_int(ILIsEqual(pidl1, pidl2), TRUE);
70c3ec1b9aSWhindmar Saksit     ok_int(ILIsEqual(pidl1Last, pidl2Last), TRUE);
71c3ec1b9aSWhindmar Saksit 
72c3ec1b9aSWhindmar Saksit     // Bind to parent
73c3ec1b9aSWhindmar Saksit     CComPtr<IShellFolder> psf1, psf2;
74c3ec1b9aSWhindmar Saksit     hr = SHBindToParent(pidl1, IID_PPV_ARG(IShellFolder, &psf1), NULL);
75c3ec1b9aSWhindmar Saksit     ok_long(hr, S_OK);
76c3ec1b9aSWhindmar Saksit     hr = SHBindToParent(pidl2, IID_PPV_ARG(IShellFolder, &psf2), NULL);
77c3ec1b9aSWhindmar Saksit     ok_long(hr, S_OK);
78c3ec1b9aSWhindmar Saksit 
79c3ec1b9aSWhindmar Saksit     // Get attributes
80c3ec1b9aSWhindmar Saksit     DWORD attrs1 = SFGAO_FOLDER, attrs2 = SFGAO_FOLDER;
81c3ec1b9aSWhindmar Saksit     hr = (psf1 ? psf1->GetAttributesOf(1, &pidl1Last, &attrs1) : E_UNEXPECTED);
82c3ec1b9aSWhindmar Saksit     ok_long(hr, S_OK);
83c3ec1b9aSWhindmar Saksit     hr = (psf2 ? psf2->GetAttributesOf(1, &pidl2Last, &attrs2) : E_UNEXPECTED);
84c3ec1b9aSWhindmar Saksit     ok_long(hr, S_OK);
85c3ec1b9aSWhindmar Saksit 
86c3ec1b9aSWhindmar Saksit     // There is the difference in attributes because SHSimpleIDListFromPath
87c3ec1b9aSWhindmar Saksit     // cannot create PIDLs to folders, only files and drives:
88c3ec1b9aSWhindmar Saksit     ok_long((attrs1 & SFGAO_FOLDER), 0);
89c3ec1b9aSWhindmar Saksit     ok_long((attrs2 & SFGAO_FOLDER), SFGAO_FOLDER);
90c3ec1b9aSWhindmar Saksit 
91c3ec1b9aSWhindmar Saksit 
92c3ec1b9aSWhindmar Saksit     // Make sure the internal details match Windows NT5+
93c3ec1b9aSWhindmar Saksit     LPITEMIDLIST item;
94c3ec1b9aSWhindmar Saksit     GetSystemDirectoryW(szPath, _countof(szPath));
95c3ec1b9aSWhindmar Saksit     CComHeapPtr<ITEMIDLIST> pidlSys32(SHSimpleIDListFromPath(szPath));
96c3ec1b9aSWhindmar Saksit     if (szPath[1] != ':' || PathFindFileNameW(szPath) <= szPath)
97c3ec1b9aSWhindmar Saksit     {
98c3ec1b9aSWhindmar Saksit         skip("Not a local directory %ls\n", szPath);
99c3ec1b9aSWhindmar Saksit     }
100c3ec1b9aSWhindmar Saksit     else if (!(LPITEMIDLIST)pidlSys32)
101c3ec1b9aSWhindmar Saksit     {
102c3ec1b9aSWhindmar Saksit         skip("?\n");
103c3ec1b9aSWhindmar Saksit     }
104c3ec1b9aSWhindmar Saksit     else
105c3ec1b9aSWhindmar Saksit     {
106c3ec1b9aSWhindmar Saksit         item = ILFindLastID(pidlSys32);
107c3ec1b9aSWhindmar Saksit         ok_long(item->mkid.abID[0] & 0x73, 0x30 | FILEBIT); // This is actually a file PIDL
108c3ec1b9aSWhindmar Saksit         ok_long(FileStruct_Att(item), 0); // Simple PIDL without attributes
109c3ec1b9aSWhindmar Saksit         ok_int(*(UINT*)(&item->mkid.abID[2]), 0); // No size
110c3ec1b9aSWhindmar Saksit 
111c3ec1b9aSWhindmar Saksit         ILRemoveLastID(pidlSys32); // Now we should have "c:\Windows"
112c3ec1b9aSWhindmar Saksit         item = ILFindLastID(pidlSys32);
113c3ec1b9aSWhindmar Saksit         ok_long(item->mkid.abID[0] & 0x73, 0x30 | DIRBIT);
114c3ec1b9aSWhindmar Saksit         ok_int(*(UINT*)(&item->mkid.abID[2]), 0); // No size
115c3ec1b9aSWhindmar Saksit     }
116c3ec1b9aSWhindmar Saksit 
117c3ec1b9aSWhindmar Saksit     WCHAR drive[4] = { szPath[0], szPath[1], L'\\', L'\0' };
118c3ec1b9aSWhindmar Saksit     CComHeapPtr<ITEMIDLIST> pidlDrive(SHSimpleIDListFromPath(drive));
119c3ec1b9aSWhindmar Saksit     if (drive[1] != ':')
120c3ec1b9aSWhindmar Saksit     {
121c3ec1b9aSWhindmar Saksit         skip("Not a local drive %ls\n", drive);
122c3ec1b9aSWhindmar Saksit     }
123c3ec1b9aSWhindmar Saksit     else if (!(LPITEMIDLIST)pidlDrive)
124c3ec1b9aSWhindmar Saksit     {
125c3ec1b9aSWhindmar Saksit         skip("?\n");
126c3ec1b9aSWhindmar Saksit     }
127c3ec1b9aSWhindmar Saksit     else
128c3ec1b9aSWhindmar Saksit     {
129c3ec1b9aSWhindmar Saksit         item = ILFindLastID(pidlDrive);
130c3ec1b9aSWhindmar Saksit         ok_long(item->mkid.abID[0] & 0x70, 0x20); // Something in My Computer
131c3ec1b9aSWhindmar Saksit         ok_char(item->mkid.abID[1] | 32, drive[0] | 32);
132c3ec1b9aSWhindmar Saksit     }
133c3ec1b9aSWhindmar Saksit 
134c3ec1b9aSWhindmar Saksit     CComHeapPtr<ITEMIDLIST> pidlVirt(SHSimpleIDListFromPath(L"x:\\IDontExist"));
135c3ec1b9aSWhindmar Saksit     if (!(LPITEMIDLIST)pidlVirt)
136c3ec1b9aSWhindmar Saksit     {
137c3ec1b9aSWhindmar Saksit         skip("?\n");
138c3ec1b9aSWhindmar Saksit     }
139c3ec1b9aSWhindmar Saksit     else
140c3ec1b9aSWhindmar Saksit     {
141c3ec1b9aSWhindmar Saksit         item = ILFindLastID(pidlVirt);
142c3ec1b9aSWhindmar Saksit         ok_long(item->mkid.abID[0] & 0x73, 0x30 | FILEBIT); // Yes, a file
143c3ec1b9aSWhindmar Saksit         ok_long(FileStruct_Att(item), 0); // Simple PIDL, no attributes
144c3ec1b9aSWhindmar Saksit         ok_int(*(UINT*)(&item->mkid.abID[2]), 0); // No size
145c3ec1b9aSWhindmar Saksit 
146c3ec1b9aSWhindmar Saksit         ILRemoveLastID(pidlVirt); // "x:\"
147c3ec1b9aSWhindmar Saksit         item = ILFindLastID(pidlVirt);
148c3ec1b9aSWhindmar Saksit         ok_long(item->mkid.abID[0] & 0x70, 0x20); // Something in My Computer
149c3ec1b9aSWhindmar Saksit         ok_char(item->mkid.abID[1] | 32, 'x' | 32); // x:
150c3ec1b9aSWhindmar Saksit     }
151*8107ff86SWhindmar Saksit 
152*8107ff86SWhindmar Saksit     LPITEMIDLIST pidl;
153*8107ff86SWhindmar Saksit     ok_int((pidl = SHSimpleIDListFromPath(L"c:")) != NULL, TRUE);
154*8107ff86SWhindmar Saksit     ILFree(pidl);
155*8107ff86SWhindmar Saksit     ok_int((pidl = SHSimpleIDListFromPath(L"c:\\")) != NULL, TRUE);
156*8107ff86SWhindmar Saksit     ILFree(pidl);
157c3ec1b9aSWhindmar Saksit }
158c3ec1b9aSWhindmar Saksit 
START_TEST(ILCreateFromPath)159c3ec1b9aSWhindmar Saksit START_TEST(ILCreateFromPath)
160c3ec1b9aSWhindmar Saksit {
161c3ec1b9aSWhindmar Saksit     WCHAR szPath[MAX_PATH];
162c3ec1b9aSWhindmar Saksit     LPITEMIDLIST item;
163c3ec1b9aSWhindmar Saksit 
164c3ec1b9aSWhindmar Saksit     ok_ptr(ILCreateFromPathW(L"c:\\IDontExist"), NULL);
165c3ec1b9aSWhindmar Saksit 
166c3ec1b9aSWhindmar Saksit     GetSystemDirectoryW(szPath, _countof(szPath));
167c3ec1b9aSWhindmar Saksit     CComHeapPtr<ITEMIDLIST> pidlDir(ILCreateFromPathW(szPath));
168c3ec1b9aSWhindmar Saksit     if (szPath[1] != ':' || PathFindFileNameW(szPath) <= szPath)
169c3ec1b9aSWhindmar Saksit     {
170c3ec1b9aSWhindmar Saksit         skip("Not a local directory %ls\n", szPath);
171c3ec1b9aSWhindmar Saksit     }
172c3ec1b9aSWhindmar Saksit     else if (!(LPITEMIDLIST)pidlDir)
173c3ec1b9aSWhindmar Saksit     {
174c3ec1b9aSWhindmar Saksit         skip("?\n");
175c3ec1b9aSWhindmar Saksit     }
176c3ec1b9aSWhindmar Saksit     else
177c3ec1b9aSWhindmar Saksit     {
178c3ec1b9aSWhindmar Saksit         item = ILFindLastID(pidlDir);
179c3ec1b9aSWhindmar Saksit         ok_long(item->mkid.abID[0] & 0x73, 0x30 | DIRBIT);
180c3ec1b9aSWhindmar Saksit         ok_int(*(UINT*)(&item->mkid.abID[2]), 0); // No size
181c3ec1b9aSWhindmar Saksit     }
182c3ec1b9aSWhindmar Saksit     PathAppendW(szPath, L"kernel32.dll");
183c3ec1b9aSWhindmar Saksit     CComHeapPtr<ITEMIDLIST> pidlFile(ILCreateFromPathW(szPath));
184c3ec1b9aSWhindmar Saksit     if (!(LPITEMIDLIST)pidlFile)
185c3ec1b9aSWhindmar Saksit     {
186c3ec1b9aSWhindmar Saksit         skip("?\n");
187c3ec1b9aSWhindmar Saksit     }
188c3ec1b9aSWhindmar Saksit     else
189c3ec1b9aSWhindmar Saksit     {
190c3ec1b9aSWhindmar Saksit         item = ILFindLastID(pidlFile);
191c3ec1b9aSWhindmar Saksit         ok_long(item->mkid.abID[0] & 0x73, 0x30 | FILEBIT);
192c3ec1b9aSWhindmar Saksit         ok_int(*(UINT*)(&item->mkid.abID[2]) > 1024 * 42, TRUE); // At least this large
193c3ec1b9aSWhindmar Saksit     }
194c3ec1b9aSWhindmar Saksit }
195c3ec1b9aSWhindmar Saksit 
START_TEST(PIDL)196c3ec1b9aSWhindmar Saksit START_TEST(PIDL)
197c3ec1b9aSWhindmar Saksit {
198c3ec1b9aSWhindmar Saksit     LPITEMIDLIST pidl;
199c3ec1b9aSWhindmar Saksit 
200c3ec1b9aSWhindmar Saksit     pidl = SHCloneSpecialIDList(NULL, CSIDL_DRIVES, FALSE);
201c3ec1b9aSWhindmar Saksit     if (pidl)
202c3ec1b9aSWhindmar Saksit         TEST_CLSID(ILFindLastID(pidl), 0x1f, 4, CLSID_MyComputer);
203c3ec1b9aSWhindmar Saksit     else
204c3ec1b9aSWhindmar Saksit         skip("?\n");
205c3ec1b9aSWhindmar Saksit     ILFree(pidl);
206c3ec1b9aSWhindmar Saksit 
207c3ec1b9aSWhindmar Saksit     pidl = SHCloneSpecialIDList(NULL, CSIDL_PRINTERS, FALSE);
208c3ec1b9aSWhindmar Saksit     if (pidl)
209c3ec1b9aSWhindmar Saksit         TEST_CLSID(ILFindLastID(pidl), 0x71, 14, CLSID_Printers);
210c3ec1b9aSWhindmar Saksit     else
211c3ec1b9aSWhindmar Saksit         skip("?\n");
212c3ec1b9aSWhindmar Saksit     ILFree(pidl);
213c3ec1b9aSWhindmar Saksit }
214654c59a5SWhindmar Saksit 
START_TEST(ILIsEqual)215654c59a5SWhindmar Saksit START_TEST(ILIsEqual)
216654c59a5SWhindmar Saksit {
217654c59a5SWhindmar Saksit     LPITEMIDLIST p1, p2, pidl;
218654c59a5SWhindmar Saksit 
219654c59a5SWhindmar Saksit     p1 = p2 = NULL;
220654c59a5SWhindmar Saksit     ok_int(ILIsEqual(p1, p2), TRUE);
221654c59a5SWhindmar Saksit 
222654c59a5SWhindmar Saksit     ITEMIDLIST emptyitem = {}, emptyitem2 = {};
223654c59a5SWhindmar Saksit     ok_int(ILIsEqual(&emptyitem, &emptyitem2), TRUE);
224654c59a5SWhindmar Saksit 
225654c59a5SWhindmar Saksit     ok_int(ILIsEqual(NULL, &emptyitem), FALSE); // These two are not equal for some reason
226654c59a5SWhindmar Saksit 
227654c59a5SWhindmar Saksit     p1 = SHCloneSpecialIDList(NULL, CSIDL_DRIVES, FALSE);
228654c59a5SWhindmar Saksit     p2 = SHCloneSpecialIDList(NULL, CSIDL_DRIVES, FALSE);
229654c59a5SWhindmar Saksit     if (p1 && p2)
230654c59a5SWhindmar Saksit     {
231654c59a5SWhindmar Saksit         ok_int(ILIsEqual(p1, p2), TRUE);
232654c59a5SWhindmar Saksit         p1->mkid.abID[0] = PT_COMPUTER_REGITEM; // RegItem in wrong parent
233654c59a5SWhindmar Saksit         ok_int(ILIsEqual(p1, p2), FALSE);
234654c59a5SWhindmar Saksit     }
235654c59a5SWhindmar Saksit     else
236654c59a5SWhindmar Saksit     {
237654c59a5SWhindmar Saksit         skip("Unable to initialize test\n");
238654c59a5SWhindmar Saksit     }
239654c59a5SWhindmar Saksit     ILFree(p1);
240654c59a5SWhindmar Saksit     ILFree(p2);
241654c59a5SWhindmar Saksit 
242654c59a5SWhindmar Saksit     // ILIsParent must compare like ILIsEqual
243654c59a5SWhindmar Saksit     p1 = SHSimpleIDListFromPath(L"c:\\");
244654c59a5SWhindmar Saksit     p2 = SHSimpleIDListFromPath(L"c:\\dir\\file");
245654c59a5SWhindmar Saksit     if (p1 && p2)
246654c59a5SWhindmar Saksit     {
247654c59a5SWhindmar Saksit         ok_int(ILIsParent(NULL, p1, FALSE), FALSE); // NULL is always false
248654c59a5SWhindmar Saksit         ok_int(ILIsParent(p1, NULL, FALSE), FALSE); // NULL is always false
249654c59a5SWhindmar Saksit         ok_int(ILIsParent(NULL, NULL, FALSE), FALSE); // NULL is always false
250654c59a5SWhindmar Saksit         ok_int(ILIsParent(p1, p1, FALSE), TRUE); // I'm my own parent
251654c59a5SWhindmar Saksit         ok_int(ILIsParent(p1, p1, TRUE), FALSE); // Self is not immediate
252654c59a5SWhindmar Saksit         ok_int(ILIsParent(p1, p2, FALSE), TRUE); // Grandchild
253654c59a5SWhindmar Saksit         ok_int(ILIsParent(p1, p2, TRUE), FALSE); // Grandchild is not immediate
254654c59a5SWhindmar Saksit         ok_ptr(ILFindChild(p1, p2), ILGetNext(ILGetNext(p2))); // Child is "dir\\file", skip MyComputer and C:
255654c59a5SWhindmar Saksit         ok_int(ILIsEmpty(pidl = ILFindChild(p1, p1)) && pidl, TRUE); // Self
256654c59a5SWhindmar Saksit         ILRemoveLastID(p2);
257654c59a5SWhindmar Saksit         ok_int(ILIsParent(p1, p2, TRUE), TRUE); // Immediate child
258654c59a5SWhindmar Saksit 
259654c59a5SWhindmar Saksit         p1->mkid.abID[0] = PT_COMPUTER_REGITEM; // RegItem in wrong parent
260654c59a5SWhindmar Saksit         ok_int(ILIsParent(p1, p2, FALSE), FALSE);
261654c59a5SWhindmar Saksit     }
262654c59a5SWhindmar Saksit     else
263654c59a5SWhindmar Saksit     {
264654c59a5SWhindmar Saksit         skip("Unable to initialize test\n");
265654c59a5SWhindmar Saksit     }
266654c59a5SWhindmar Saksit     ILFree(p1);
267654c59a5SWhindmar Saksit     ILFree(p2);
268654c59a5SWhindmar Saksit }
269