1 /*
2  * PROJECT:         ReactOS api tests
3  * LICENSE:         LGPLv2.1+ - See COPYING.LIB in the top level directory
4  * PURPOSE:         Test for CShellLink
5  * PROGRAMMER:      Andreas Maier
6  */
7 
8 #include "shelltest.h"
9 
10 #define NDEBUG
11 #include <debug.h>
12 #include <stdio.h>
13 #include <shellutils.h>
14 
15 /* Test IShellLink::SetPath with environment-variables, existing, non-existing, ...*/
16 typedef struct
17 {
18     PCWSTR pathIn;
19     HRESULT hrSetPath;
20 
21     /* Test 1 - hrGetPathX = IShellLink::GetPath(pathOutX, ... , flagsX); */
22     PCWSTR pathOut1;
23     DWORD flags1;
24     HRESULT hrGetPath1;
25     BOOL expandPathOut1;
26 
27     /* Test 2 */
28     PCWSTR pathOut2;
29     DWORD flags2;
30     HRESULT hrGetPath2;
31     BOOL expandPathOut2;
32 } TEST_SHELL_LINK_DEF;
33 
34 static TEST_SHELL_LINK_DEF linkTestList[] =
35 {
36     {
37         L"%comspec%",                                 S_OK,
38         L"%comspec%",             SLGP_SHORTPATH,     S_OK, TRUE,
39         L"%comspec%",             SLGP_RAWPATH,       S_OK, FALSE
40     },
41     {
42         L"%anyvar%",                                  E_INVALIDARG,
43         L"",                      SLGP_SHORTPATH,     S_FALSE, FALSE,
44         L"",                      SLGP_RAWPATH,       S_FALSE, FALSE
45     },
46     {
47         L"%anyvar%%comspec%",                         S_OK,
48         L"c:\\%anyvar%%comspec%", SLGP_SHORTPATH,     S_OK, TRUE,
49         L"%anyvar%%comspec%",     SLGP_RAWPATH,       S_OK, FALSE
50     },
51     {
52         L"%temp%",                                    S_OK,
53         L"%temp%",                SLGP_SHORTPATH,     S_OK, TRUE,
54         L"%temp%",                SLGP_RAWPATH,       S_OK, FALSE
55     },
56     {
57         L"%shell%",                                   S_OK,
58         L"%systemroot%\\system32\\%shell%",    SLGP_SHORTPATH,     S_OK, TRUE,
59         L"%shell%",               SLGP_RAWPATH,       S_OK, FALSE
60     },
61     {
62         L"u:\\anypath\\%anyvar%",                     S_OK,
63         L"u:\\anypath\\%anyvar%", SLGP_SHORTPATH,     S_OK, TRUE,
64         L"u:\\anypath\\%anyvar%", SLGP_RAWPATH,       S_OK, FALSE
65     },
66     {
67         L"c:\\temp",                                  S_OK,
68         L"c:\\temp",              SLGP_SHORTPATH,     S_OK, FALSE,
69         L"c:\\temp",              SLGP_RAWPATH,       S_OK, FALSE
70     },
71     {
72         L"cmd.exe",                                  S_OK,
73         L"%comspec%",             SLGP_SHORTPATH,    S_OK,  TRUE,
74         L"%comspec%",             SLGP_RAWPATH,      S_OK,  TRUE
75     },
76     {
77         L"%systemroot%\\non-existent-file", S_OK,
78         L"%systemroot%\\non-existent-file", SLGP_SHORTPATH, S_OK, TRUE,
79         L"%systemroot%\\non-existent-file", SLGP_RAWPATH,   S_OK, FALSE
80     },
81     {
82         L"c:\\non-existent-path\\non-existent-file", S_OK,
83         L"c:\\non-existent-path\\non-existent-file", SLGP_SHORTPATH, S_OK, FALSE,
84         L"c:\\non-existent-path\\non-existent-file", SLGP_RAWPATH,   S_OK, FALSE
85     },
86     {
87         L"non-existent-file",                        E_INVALIDARG,
88         L"",                      SLGP_SHORTPATH,    S_FALSE, FALSE,
89         L"",                      SLGP_RAWPATH,      S_FALSE, FALSE
90     },
91 };
92 
93 static
94 VOID
95 test_checklinkpath(UINT i, TEST_SHELL_LINK_DEF* testDef)
96 {
97 static WCHAR evVar[MAX_PATH];
98 
99     HRESULT hr, expectedHr;
100     WCHAR wPathOut[MAX_PATH];
101     BOOL expandPathOut;
102     PCWSTR expectedPathOut;
103     CComPtr<IShellLinkW> psl;
104     UINT i1;
105     DWORD flags;
106 
107     hr = CoCreateInstance(CLSID_ShellLink,
108                           NULL,
109                           CLSCTX_INPROC_SERVER,
110                           IID_PPV_ARG(IShellLinkW, &psl));
111     ok(hr == S_OK, "CoCreateInstance, hr = 0x%lx\n", hr);
112     if (FAILED(hr))
113     {
114         skip("Could not instantiate CShellLink\n");
115         return;
116     }
117 
118     hr = psl->SetPath(testDef->pathIn);
119     ok(hr == testDef->hrSetPath, "IShellLink::SetPath(%d), got hr = 0x%lx, expected 0x%lx\n", i, hr, testDef->hrSetPath);
120 
121     expectedPathOut = NULL;
122     for (i1 = 0; i1 <= 1; i1++)
123     {
124         if (i1 == 0) /* Usually SLGP_SHORTPATH */
125         {
126             flags = testDef->flags1;
127             expandPathOut = testDef->expandPathOut1;
128             expectedPathOut = testDef->pathOut1;
129             expectedHr = testDef->hrGetPath1;
130         }
131         else // if (i1 == 1) /* Usually SLGP_RAWPATH */
132         {
133             flags = testDef->flags2;
134             expandPathOut = testDef->expandPathOut2;
135             expectedPathOut = testDef->pathOut2;
136             expectedHr = testDef->hrGetPath2;
137         }
138 
139         /* Patch some variables */
140         if (expandPathOut)
141         {
142             ExpandEnvironmentStringsW(expectedPathOut, evVar, _countof(evVar));
143             DPRINT("** %S **\n",evVar);
144             expectedPathOut = evVar;
145         }
146 
147         hr = psl->GetPath(wPathOut, _countof(wPathOut), NULL, flags);
148         ok(hr == expectedHr,
149            "IShellLink::GetPath(%d), flags 0x%lx, got hr = 0x%lx, expected 0x%lx\n",
150             i, flags, hr, expectedHr);
151         ok(_wcsicmp(wPathOut, expectedPathOut) == 0,
152            "IShellLink::GetPath(%d), flags 0x%lx, in %S, got %S, expected %S\n",
153            i, flags, testDef->pathIn, wPathOut, expectedPathOut);
154     }
155 }
156 
157 static
158 VOID
159 TestShellLink(void)
160 {
161     UINT i;
162 
163     /* Needed for test */
164     SetEnvironmentVariableW(L"shell", L"cmd.exe");
165 
166     for (i = 0; i < _countof(linkTestList); ++i)
167     {
168         DPRINT("IShellLink-Test(%d): %S\n", i, linkTestList[i].pathIn);
169         test_checklinkpath(i, &linkTestList[i]);
170     }
171 
172     SetEnvironmentVariableW(L"shell",NULL);
173 }
174 
175 static
176 VOID
177 TestDescription(void)
178 {
179     HRESULT hr;
180     CComPtr<IShellLinkW> psl;
181     WCHAR buffer[64];
182     PCWSTR testDescription = L"This is a test description";
183 
184     /* Test SetDescription */
185     hr = CoCreateInstance(CLSID_ShellLink,
186                           NULL,
187                           CLSCTX_INPROC_SERVER,
188                           IID_PPV_ARG(IShellLinkW, &psl));
189     ok(hr == S_OK, "CoCreateInstance, hr = 0x%lx\n", hr);
190     if (FAILED(hr))
191     {
192         skip("Could not instantiate CShellLink\n");
193         return;
194     }
195 
196     memset(buffer, 0x55, sizeof(buffer));
197     hr = psl->GetDescription(buffer, RTL_NUMBER_OF(buffer));
198     ok(hr == S_OK, "IShellLink::GetDescription returned hr = 0x%lx\n", hr);
199     ok(buffer[0] == 0, "buffer[0] = %x\n", buffer[0]);
200     ok(buffer[1] == 0x5555, "buffer[1] = %x\n", buffer[1]);
201 
202     hr = psl->SetDescription(testDescription);
203     ok(hr == S_OK, "IShellLink::SetDescription returned hr = 0x%lx\n", hr);
204 
205     memset(buffer, 0x55, sizeof(buffer));
206     hr = psl->GetDescription(buffer, RTL_NUMBER_OF(buffer));
207     ok(hr == S_OK, "IShellLink::GetDescription returned hr = 0x%lx\n", hr);
208     ok(buffer[wcslen(testDescription)] == 0, "buffer[n] = %x\n", buffer[wcslen(testDescription)]);
209     ok(buffer[wcslen(testDescription) + 1] == 0x5555, "buffer[n+1] = %x\n", buffer[wcslen(testDescription) + 1]);
210     ok(!wcscmp(buffer, testDescription), "buffer = '%ls'\n", buffer);
211 
212     hr = psl->SetDescription(NULL);
213     ok(hr == S_OK, "IShellLink::SetDescription returned hr = 0x%lx\n", hr);
214 
215     memset(buffer, 0x55, sizeof(buffer));
216     hr = psl->GetDescription(buffer, RTL_NUMBER_OF(buffer));
217     ok(hr == S_OK, "IShellLink::GetDescription returned hr = 0x%lx\n", hr);
218     ok(buffer[0] == 0, "buffer[0] = %x\n", buffer[0]);
219     ok(buffer[1] == 0x5555, "buffer[1] = %x\n", buffer[1]);
220 }
221 
222 
223 /* Test IShellLink::Get/SetIconLocation and IExtractIcon::GetIconLocation */
224 typedef struct
225 {
226     PCWSTR FilePath;
227 
228     /* Expected results */
229     HRESULT hrDefIcon;  // Return value for GIL_DEFAULTICON
230     HRESULT hrForShrt;  // Return value for GIL_FORSHORTCUT
231     /* Return values for GIL_FORSHELL */
232     HRESULT hrForShell;
233     PCWSTR  IconPath;
234     UINT    Flags;
235 } TEST_SHELL_ICON;
236 
237 static TEST_SHELL_ICON ShIconTests[] =
238 {
239     /* Executable with icons */
240     {L"%SystemRoot%\\system32\\cmd.exe", S_FALSE, E_INVALIDARG,
241      S_OK, L"%SystemRoot%\\system32\\cmd.exe", GIL_NOTFILENAME | GIL_PERINSTANCE},
242 
243     /* Executable without icon */
244     {L"%SystemRoot%\\system32\\autochk.exe", S_FALSE, E_INVALIDARG,
245      S_OK, L"%SystemRoot%\\system32\\autochk.exe", GIL_NOTFILENAME | GIL_PERINSTANCE},
246 
247     /* Existing file */
248     {L"%SystemRoot%\\system32\\shell32.dll", S_FALSE, E_INVALIDARG,
249      S_OK, L"*", GIL_NOTFILENAME | GIL_PERCLASS},
250 
251     /* Non-existing files */
252     {L"%SystemRoot%\\non-existent-file.sdf", S_FALSE, E_INVALIDARG,
253      S_OK, L"*", GIL_NOTFILENAME | GIL_PERCLASS},
254     {L"c:\\non-existent-path\\non-existent-file.sdf", S_FALSE, E_INVALIDARG,
255      S_OK, L"*", GIL_NOTFILENAME | GIL_PERCLASS},
256 };
257 
258 static
259 VOID
260 test_iconlocation(UINT i, TEST_SHELL_ICON* testDef)
261 {
262     HRESULT hr;
263     CComPtr<IShellLinkW> psl;
264     CComPtr<IExtractIconW> pei;
265     INT iIcon;
266     UINT wFlags;
267     PCWSTR pszExplorer = L"%SystemRoot%\\explorer.exe";
268     WCHAR szPath[MAX_PATH];
269     WCHAR szPath2[MAX_PATH];
270 
271     hr = CoCreateInstance(CLSID_ShellLink,
272                           NULL,
273                           CLSCTX_INPROC_SERVER,
274                           IID_PPV_ARG(IShellLinkW, &psl));
275     ok(hr == S_OK, "CoCreateInstance, hr = 0x%lx\n", hr);
276     if (FAILED(hr))
277     {
278         skip("Could not instantiate CShellLink\n");
279         return;
280     }
281 
282     /* Set the path to a file */
283     ExpandEnvironmentStringsW(testDef->FilePath, szPath, _countof(szPath));
284     hr = psl->SetPath(szPath);
285     ok(hr == S_OK, "IShellLink::SetPath failed, hr = 0x%lx\n", hr);
286 
287     /*
288      * This test shows that this does not imply that the icon is automatically
289      * set and be retrieved naively by a call to IShellLink::GetIconLocation.
290      */
291     iIcon = 0xdeadbeef;
292     wcscpy(szPath, L"garbage");
293     hr = psl->GetIconLocation(szPath, _countof(szPath), &iIcon);
294     ok(hr == S_OK, "IShellLink::GetIconLocation(%d) failed, hr = 0x%lx\n", i, hr);
295     ok(*szPath == L'\0', "IShellLink::GetIconLocation(%d) returned '%S'\n", i, szPath);
296     ok(iIcon == 0, "IShellLink::GetIconLocation(%d) returned %d, expected %d\n", i, iIcon, 0);
297 
298     /* Try to grab the IExtractIconW interface */
299     hr = psl->QueryInterface(IID_PPV_ARG(IExtractIconW, &pei));
300     ok(hr == S_OK, "IShellLink::QueryInterface(IExtractIconW)(%d) failed, hr = 0x%lx\n", i, hr);
301     if (!pei)
302     {
303         win_skip("No IExtractIconW interface\n");
304         return;
305     }
306 
307     iIcon = wFlags = 0xdeadbeef;
308     wcscpy(szPath, L"garbage");
309     hr = pei->GetIconLocation(GIL_DEFAULTICON, szPath, _countof(szPath), &iIcon, &wFlags);
310     ok(hr == testDef->hrDefIcon, "IExtractIcon::GetIconLocation(%d) returned hr = 0x%lx, expected 0x%lx\n", i, hr, testDef->hrDefIcon);
311     ok(*szPath == L'\0', "IExtractIcon::GetIconLocation(%d) returned '%S'\n", i, szPath);
312     // ok(iIcon == 0, "IExtractIcon::GetIconLocation(%d) returned %d\n", i, iIcon);
313 
314     iIcon = wFlags = 0xdeadbeef;
315     wcscpy(szPath, L"garbage");
316     hr = pei->GetIconLocation(GIL_FORSHORTCUT, szPath, _countof(szPath), &iIcon, &wFlags);
317     ok(hr == testDef->hrForShrt, "IExtractIcon::GetIconLocation(%d) returned hr = 0x%lx, expected 0x%lx\n", i, hr, testDef->hrForShrt);
318     // Here, both szPath and iIcon are untouched...
319 
320     iIcon = wFlags = 0xdeadbeef;
321     wcscpy(szPath, L"garbage");
322     hr = pei->GetIconLocation(GIL_FORSHELL, szPath, _countof(szPath), &iIcon, &wFlags);
323     ok(hr == testDef->hrForShell, "IExtractIcon::GetIconLocation(%d) returned hr = 0x%lx, expected 0x%lx\n", i, hr, testDef->hrForShell);
324     ok(wFlags == testDef->Flags, "IExtractIcon::GetIconLocation(%d) returned wFlags = 0x%x, expected 0x%x\n", i, wFlags, testDef->Flags);
325     /*
326      * Actually, even if wFlags specifies GIL_NOTFILENAME, a correct file name is returned
327      * for executables only (at least...), otherwise we can get an asterix '*'.
328      */
329     ExpandEnvironmentStringsW(testDef->IconPath, szPath2, _countof(szPath2));
330     ok(_wcsicmp(szPath2, szPath) == 0, "IExtractIcon::GetIconLocation(%d) returned '%S', expected '%S'\n", i, szPath, szPath2);
331 
332     // ok(*szPath == L'\0', "IExtractIcon::GetIconLocation returned '%S'\n", szPath);
333     // ok(iIcon == 0, "IExtractIcon::GetIconLocation returned %d\n", iIcon);
334     // ok(FALSE, "hr = 0x%lx, szPath = '%S', iIcon = %d, wFlags = %d\n", hr, szPath, iIcon, wFlags);
335 
336 
337     /*
338      * Now we test what happens when we explicitly set an icon to the shortcut.
339      * Note that actually, SetIconLocation() does not verify whether the file
340      * really exists.
341      */
342     hr = psl->SetIconLocation(pszExplorer, 1);
343     ok(hr == S_OK, "IShellLink::SetIconLocation(%d) failed, hr = 0x%lx\n", i, hr);
344 
345     /*
346      * First, we call IShellLink::GetIconLocation. We retrieve
347      * exactly what we specified with SetIconLocation.
348      */
349     iIcon = 0xdeadbeef;
350     wcscpy(szPath, L"garbage");
351     hr = psl->GetIconLocation(szPath, _countof(szPath), &iIcon);
352     ok(hr == S_OK, "IShellLink::GetIconLocation(%d) failed, hr = 0x%lx\n", i, hr);
353     ok(wcscmp(szPath, pszExplorer) == 0, "IShellLink::GetIconLocation(%d) returned '%S', expected '%S'\n", i, szPath, pszExplorer);
354     ok(iIcon == 1, "IShellLink::GetIconLocation(%d) returned %d, expected %d\n", i, iIcon, 1);
355 
356     /*
357      * Now we test what happens with IExtractIcon::GetIconLocation.
358      * We see that it retrieves the icon of the shortcut's underlying file.
359      */
360     iIcon = wFlags = 0xdeadbeef;
361     wcscpy(szPath, L"garbage");
362     hr = pei->GetIconLocation(GIL_DEFAULTICON, szPath, _countof(szPath), &iIcon, &wFlags);
363     ok(hr == testDef->hrDefIcon, "IExtractIcon::GetIconLocation(%d) returned hr = 0x%lx, expected 0x%lx\n", i, hr, testDef->hrDefIcon);
364     ok(*szPath == L'\0', "IExtractIcon::GetIconLocation(%d) returned '%S'\n", i, szPath);
365     // ok(iIcon == 0, "IExtractIcon::GetIconLocation(%d) returned %d\n", i, iIcon);
366 
367     iIcon = wFlags = 0xdeadbeef;
368     wcscpy(szPath, L"garbage");
369     hr = pei->GetIconLocation(GIL_FORSHORTCUT, szPath, _countof(szPath), &iIcon, &wFlags);
370     ok(hr == testDef->hrForShrt, "IExtractIcon::GetIconLocation(%d) returned hr = 0x%lx, expected 0x%lx\n", i, hr, testDef->hrForShrt);
371     // Here, both szPath and iIcon are untouched...
372 
373     iIcon = wFlags = 0xdeadbeef;
374     wcscpy(szPath, L"garbage");
375     hr = pei->GetIconLocation(GIL_FORSHELL, szPath, _countof(szPath), &iIcon, &wFlags);
376     ok(hr == testDef->hrForShell, "IExtractIcon::GetIconLocation(%d) returned hr = 0x%lx, expected 0x%lx\n", i, hr, testDef->hrForShell);
377     ok(wFlags == testDef->Flags, "IExtractIcon::GetIconLocation(%d) returned wFlags = 0x%x, expected 0x%x\n", i, wFlags, testDef->Flags);
378     /*
379      * Actually, even if wFlags specifies GIL_NOTFILENAME, a correct file name is returned
380      * for executables only (at least...), otherwise we can get an asterix '*'.
381      */
382     ExpandEnvironmentStringsW(testDef->IconPath, szPath2, _countof(szPath2));
383     ok(_wcsicmp(szPath2, szPath) == 0, "IExtractIcon::GetIconLocation(%d) returned '%S', expected '%S'\n", i, szPath, szPath2);
384 
385     // ok(*szPath == L'\0', "IExtractIcon::GetIconLocation returned '%S'\n", szPath);
386     // ok(iIcon == 0, "IExtractIcon::GetIconLocation returned %d\n", iIcon);
387     // ok(FALSE, "hr = 0x%lx, szPath = '%S', iIcon = %d, wFlags = %d\n", hr, szPath, iIcon, wFlags);
388 }
389 
390 static
391 VOID
392 TestIconLocation(void)
393 {
394     UINT i;
395 
396     for (i = 0; i < _countof(ShIconTests); ++i)
397     {
398         test_iconlocation(i, &ShIconTests[i]);
399     }
400 }
401 
402 
403 START_TEST(CShellLink)
404 {
405     CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
406 
407     TestShellLink();
408     TestDescription();
409     TestIconLocation();
410 
411     CoUninitialize();
412 }
413