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