winex11: Get rid of some unused prototypes.
[wine] / dlls / shell32 / tests / shelllink.c
1 /*
2  * Unit tests for shelllinks
3  *
4  * Copyright 2004 Mike McCormack
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  * This is a test program for the SHGet{Special}Folder{Path|Location} functions
20  * of shell32, that get either a filesystem path or a LPITEMIDLIST (shell
21  * namespace) path for a given folder (CSIDL value).
22  *
23  */
24
25 #define COBJMACROS
26
27 #include "initguid.h"
28 #include "windows.h"
29 #include "shlguid.h"
30 #include "shobjidl.h"
31 #include "shlobj.h"
32 #include "wine/test.h"
33
34 #include "shell32_test.h"
35
36 #ifndef SLDF_HAS_LOGO3ID
37 #  define SLDF_HAS_LOGO3ID 0x00000800 /* not available in the Vista SDK */
38 #endif
39
40 static void (WINAPI *pILFree)(LPITEMIDLIST);
41 static BOOL (WINAPI *pILIsEqual)(LPCITEMIDLIST, LPCITEMIDLIST);
42 static HRESULT (WINAPI *pSHILCreateFromPath)(LPCWSTR, LPITEMIDLIST *,DWORD*);
43 static HRESULT (WINAPI *pSHDefExtractIconA)(LPCSTR, int, UINT, HICON*, HICON*, UINT);
44
45 static DWORD (WINAPI *pGetLongPathNameA)(LPCSTR, LPSTR, DWORD);
46 static DWORD (WINAPI *pGetShortPathNameA)(LPCSTR, LPSTR, DWORD);
47
48 static const GUID _IID_IShellLinkDataList = {
49     0x45e2b4ae, 0xb1c3, 0x11d0,
50     { 0xb9, 0x2f, 0x00, 0xa0, 0xc9, 0x03, 0x12, 0xe1 }
51 };
52
53 static const WCHAR notafile[]= { 'C',':','\\','n','o','n','e','x','i','s','t','e','n','t','\\','f','i','l','e',0 };
54
55
56 /* For some reason SHILCreateFromPath does not work on Win98 and
57  * SHSimpleIDListFromPathA does not work on NT4. But if we call both we
58  * get what we want on all platforms.
59  */
60 static LPITEMIDLIST (WINAPI *pSHSimpleIDListFromPathAW)(LPCVOID);
61
62 static LPITEMIDLIST path_to_pidl(const char* path)
63 {
64     LPITEMIDLIST pidl;
65
66     if (!pSHSimpleIDListFromPathAW)
67     {
68         HMODULE hdll=GetModuleHandleA("shell32.dll");
69         pSHSimpleIDListFromPathAW=(void*)GetProcAddress(hdll, (char*)162);
70         if (!pSHSimpleIDListFromPathAW)
71             win_skip("SHSimpleIDListFromPathAW not found in shell32.dll\n");
72     }
73
74     pidl=NULL;
75     /* pSHSimpleIDListFromPathAW maps to A on non NT platforms */
76     if (pSHSimpleIDListFromPathAW && (GetVersion() & 0x80000000))
77         pidl=pSHSimpleIDListFromPathAW(path);
78
79     if (!pidl)
80     {
81         WCHAR* pathW;
82         HRESULT r;
83         int len;
84
85         len=MultiByteToWideChar(CP_ACP, 0, path, -1, NULL, 0);
86         pathW=HeapAlloc(GetProcessHeap(), 0, len*sizeof(WCHAR));
87         MultiByteToWideChar(CP_ACP, 0, path, -1, pathW, len);
88
89         r=pSHILCreateFromPath(pathW, &pidl, NULL);
90         ok(r == S_OK, "SHILCreateFromPath failed (0x%08x)\n", r);
91         HeapFree(GetProcessHeap(), 0, pathW);
92     }
93     return pidl;
94 }
95
96
97 /*
98  * Test manipulation of an IShellLink's properties.
99  */
100
101 static void test_get_set(void)
102 {
103     HRESULT r;
104     IShellLinkA *sl;
105     IShellLinkW *slW = NULL;
106     char mypath[MAX_PATH];
107     char buffer[INFOTIPSIZE];
108     LPITEMIDLIST pidl, tmp_pidl;
109     const char * str;
110     int i;
111     WORD w;
112
113     r = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
114                          &IID_IShellLinkA, (LPVOID*)&sl);
115     ok(r == S_OK, "no IID_IShellLinkA (0x%08x)\n", r);
116     if (r != S_OK)
117         return;
118
119     /* Test Getting / Setting the description */
120     strcpy(buffer,"garbage");
121     r = IShellLinkA_GetDescription(sl, buffer, sizeof(buffer));
122     ok(r == S_OK, "GetDescription failed (0x%08x)\n", r);
123     ok(*buffer=='\0', "GetDescription returned '%s'\n", buffer);
124
125     str="Some description";
126     r = IShellLinkA_SetDescription(sl, str);
127     ok(r == S_OK, "SetDescription failed (0x%08x)\n", r);
128
129     strcpy(buffer,"garbage");
130     r = IShellLinkA_GetDescription(sl, buffer, sizeof(buffer));
131     ok(r == S_OK, "GetDescription failed (0x%08x)\n", r);
132     ok(lstrcmp(buffer,str)==0, "GetDescription returned '%s'\n", buffer);
133
134     r = IShellLinkA_SetDescription(sl, NULL);
135     ok(r == S_OK, "SetDescription failed (0x%08x)\n", r);
136
137     strcpy(buffer,"garbage");
138     r = IShellLinkA_GetDescription(sl, buffer, sizeof(buffer));
139     ok(r == S_OK, "GetDescription failed (0x%08x)\n", r);
140     ok(*buffer=='\0' || broken(lstrcmp(buffer,str)==0), "GetDescription returned '%s'\n", buffer); /* NT4 */
141
142
143     /* Test Getting / Setting the work directory */
144     strcpy(buffer,"garbage");
145     r = IShellLinkA_GetWorkingDirectory(sl, buffer, sizeof(buffer));
146     ok(r == S_OK, "GetWorkingDirectory failed (0x%08x)\n", r);
147     ok(*buffer=='\0', "GetWorkingDirectory returned '%s'\n", buffer);
148
149     str="c:\\nonexistent\\directory";
150     r = IShellLinkA_SetWorkingDirectory(sl, str);
151     ok(r == S_OK, "SetWorkingDirectory failed (0x%08x)\n", r);
152
153     strcpy(buffer,"garbage");
154     r = IShellLinkA_GetWorkingDirectory(sl, buffer, sizeof(buffer));
155     ok(r == S_OK, "GetWorkingDirectory failed (0x%08x)\n", r);
156     ok(lstrcmpi(buffer,str)==0, "GetWorkingDirectory returned '%s'\n", buffer);
157
158     /* Test Getting / Setting the path */
159     strcpy(buffer,"garbage");
160     r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH);
161     todo_wine ok(r == S_FALSE || broken(r == S_OK) /* NT4/W2K */, "GetPath failed (0x%08x)\n", r);
162     ok(*buffer=='\0', "GetPath returned '%s'\n", buffer);
163
164     CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
165                      &IID_IShellLinkW, (LPVOID*)&slW);
166     if (!slW /* Win9x */ || !pGetLongPathNameA /* NT4 */)
167         skip("SetPath with NULL parameter crashes on Win9x and some NT4\n");
168     else
169     {
170         IShellLinkW_Release(slW);
171         r = IShellLinkA_SetPath(sl, NULL);
172         ok(r==E_INVALIDARG ||
173            broken(r==S_OK), /* Some Win95 and NT4 */
174            "SetPath returned wrong error (0x%08x)\n", r);
175     }
176
177     r = IShellLinkA_SetPath(sl, "");
178     ok(r==S_OK, "SetPath failed (0x%08x)\n", r);
179
180     strcpy(buffer,"garbage");
181     r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH);
182     todo_wine ok(r == S_FALSE, "GetPath failed (0x%08x)\n", r);
183     ok(*buffer=='\0', "GetPath returned '%s'\n", buffer);
184
185     /* Win98 returns S_FALSE, but WinXP returns S_OK */
186     str="c:\\nonexistent\\file";
187     r = IShellLinkA_SetPath(sl, str);
188     ok(r==S_FALSE || r==S_OK, "SetPath failed (0x%08x)\n", r);
189
190     strcpy(buffer,"garbage");
191     r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH);
192     ok(r == S_OK, "GetPath failed (0x%08x)\n", r);
193     ok(lstrcmpi(buffer,str)==0, "GetPath returned '%s'\n", buffer);
194
195     /* Get some real path to play with */
196     GetWindowsDirectoryA( mypath, sizeof(mypath)-12 );
197     strcat(mypath, "\\regedit.exe");
198
199     /* Test the interaction of SetPath and SetIDList */
200     tmp_pidl=NULL;
201     r = IShellLinkA_GetIDList(sl, &tmp_pidl);
202     ok(r == S_OK, "GetIDList failed (0x%08x)\n", r);
203     if (r == S_OK)
204     {
205         BOOL ret;
206
207         strcpy(buffer,"garbage");
208         ret = SHGetPathFromIDListA(tmp_pidl, buffer);
209         ok(ret, "SHGetPathFromIDListA failed\n");
210         if (ret)
211             ok(lstrcmpi(buffer,str)==0, "GetIDList returned '%s'\n", buffer);
212         pILFree(tmp_pidl);
213     }
214
215     pidl=path_to_pidl(mypath);
216     ok(pidl!=NULL, "path_to_pidl returned a NULL pidl\n");
217
218     if (pidl)
219     {
220         LPITEMIDLIST second_pidl;
221
222         r = IShellLinkA_SetIDList(sl, pidl);
223         ok(r == S_OK, "SetIDList failed (0x%08x)\n", r);
224
225         tmp_pidl=NULL;
226         r = IShellLinkA_GetIDList(sl, &tmp_pidl);
227         ok(r == S_OK, "GetIDList failed (0x%08x)\n", r);
228         ok(tmp_pidl && pILIsEqual(pidl, tmp_pidl),
229            "GetIDList returned an incorrect pidl\n");
230
231         r = IShellLinkA_GetIDList(sl, &second_pidl);
232         ok(r == S_OK, "GetIDList failed (0x%08x)\n", r);
233         ok(second_pidl && pILIsEqual(pidl, second_pidl),
234            "GetIDList returned an incorrect pidl\n");
235         ok(second_pidl != tmp_pidl, "pidls are the same\n");
236
237         pILFree(second_pidl);
238         pILFree(tmp_pidl);
239         pILFree(pidl);
240
241         strcpy(buffer,"garbage");
242         r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH);
243         ok(r == S_OK, "GetPath failed (0x%08x)\n", r);
244         todo_wine
245         ok(lstrcmpi(buffer, mypath)==0, "GetPath returned '%s'\n", buffer);
246
247     }
248
249     /* test path with quotes (IShellLinkA_SetPath returns S_FALSE on W2K and below and S_OK on XP and above */
250     r = IShellLinkA_SetPath(sl, "\"c:\\nonexistent\\file\"");
251     ok(r==S_FALSE || r == S_OK, "SetPath failed (0x%08x)\n", r);
252
253     strcpy(buffer,"garbage");
254     r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH);
255     ok(r==S_OK, "GetPath failed (0x%08x)\n", r);
256     ok(!lstrcmp(buffer, "C:\\nonexistent\\file") ||
257        broken(!lstrcmp(buffer, "C:\\\"c:\\nonexistent\\file\"")), /* NT4 */
258        "case doesn't match\n");
259
260     r = IShellLinkA_SetPath(sl, "\"c:\\foo");
261     ok(r==S_FALSE || r == S_OK || r == E_INVALIDARG /* Vista */, "SetPath failed (0x%08x)\n", r);
262
263     r = IShellLinkA_SetPath(sl, "\"\"c:\\foo");
264     ok(r==S_FALSE || r == S_OK || r == E_INVALIDARG /* Vista */, "SetPath failed (0x%08x)\n", r);
265
266     r = IShellLinkA_SetPath(sl, "c:\\foo\"");
267     ok(r==S_FALSE || r == S_OK || r == E_INVALIDARG /* Vista */, "SetPath failed (0x%08x)\n", r);
268
269     r = IShellLinkA_SetPath(sl, "\"\"c:\\foo\"");
270     ok(r==S_FALSE || r == S_OK || r == E_INVALIDARG /* Vista */, "SetPath failed (0x%08x)\n", r);
271
272     r = IShellLinkA_SetPath(sl, "\"\"c:\\foo\"\"");
273     ok(r==S_FALSE || r == S_OK || r == E_INVALIDARG /* Vista */, "SetPath failed (0x%08x)\n", r);
274
275     /* Test Getting / Setting the arguments */
276     strcpy(buffer,"garbage");
277     r = IShellLinkA_GetArguments(sl, buffer, sizeof(buffer));
278     ok(r == S_OK, "GetArguments failed (0x%08x)\n", r);
279     ok(*buffer=='\0', "GetArguments returned '%s'\n", buffer);
280
281     str="param1 \"spaced param2\"";
282     r = IShellLinkA_SetArguments(sl, str);
283     ok(r == S_OK, "SetArguments failed (0x%08x)\n", r);
284
285     strcpy(buffer,"garbage");
286     r = IShellLinkA_GetArguments(sl, buffer, sizeof(buffer));
287     ok(r == S_OK, "GetArguments failed (0x%08x)\n", r);
288     ok(lstrcmp(buffer,str)==0, "GetArguments returned '%s'\n", buffer);
289
290     strcpy(buffer,"garbage");
291     r = IShellLinkA_SetArguments(sl, NULL);
292     ok(r == S_OK, "SetArguments failed (0x%08x)\n", r);
293     r = IShellLinkA_GetArguments(sl, buffer, sizeof(buffer));
294     ok(r == S_OK, "GetArguments failed (0x%08x)\n", r);
295     ok(!buffer[0] || lstrcmp(buffer,str)==0, "GetArguments returned '%s'\n", buffer);
296
297     strcpy(buffer,"garbage");
298     r = IShellLinkA_SetArguments(sl, "");
299     ok(r == S_OK, "SetArguments failed (0x%08x)\n", r);
300     r = IShellLinkA_GetArguments(sl, buffer, sizeof(buffer));
301     ok(r == S_OK, "GetArguments failed (0x%08x)\n", r);
302     ok(!buffer[0], "GetArguments returned '%s'\n", buffer);
303
304     /* Test Getting / Setting showcmd */
305     i=0xdeadbeef;
306     r = IShellLinkA_GetShowCmd(sl, &i);
307     ok(r == S_OK, "GetShowCmd failed (0x%08x)\n", r);
308     ok(i==SW_SHOWNORMAL, "GetShowCmd returned %d\n", i);
309
310     r = IShellLinkA_SetShowCmd(sl, SW_SHOWMAXIMIZED);
311     ok(r == S_OK, "SetShowCmd failed (0x%08x)\n", r);
312
313     i=0xdeadbeef;
314     r = IShellLinkA_GetShowCmd(sl, &i);
315     ok(r == S_OK, "GetShowCmd failed (0x%08x)\n", r);
316     ok(i==SW_SHOWMAXIMIZED, "GetShowCmd returned %d'\n", i);
317
318     /* Test Getting / Setting the icon */
319     i=0xdeadbeef;
320     strcpy(buffer,"garbage");
321     r = IShellLinkA_GetIconLocation(sl, buffer, sizeof(buffer), &i);
322     ok(r == S_OK, "GetIconLocation failed (0x%08x)\n", r);
323     ok(*buffer=='\0', "GetIconLocation returned '%s'\n", buffer);
324     ok(i==0, "GetIconLocation returned %d\n", i);
325
326     str="c:\\nonexistent\\file";
327     r = IShellLinkA_SetIconLocation(sl, str, 0xbabecafe);
328     ok(r == S_OK, "SetIconLocation failed (0x%08x)\n", r);
329
330     i=0xdeadbeef;
331     r = IShellLinkA_GetIconLocation(sl, buffer, sizeof(buffer), &i);
332     ok(r == S_OK, "GetIconLocation failed (0x%08x)\n", r);
333     ok(lstrcmpi(buffer,str)==0, "GetIconLocation returned '%s'\n", buffer);
334     ok(i==0xbabecafe, "GetIconLocation returned %d'\n", i);
335
336     /* Test Getting / Setting the hot key */
337     w=0xbeef;
338     r = IShellLinkA_GetHotkey(sl, &w);
339     ok(r == S_OK, "GetHotkey failed (0x%08x)\n", r);
340     ok(w==0, "GetHotkey returned %d\n", w);
341
342     r = IShellLinkA_SetHotkey(sl, 0x5678);
343     ok(r == S_OK, "SetHotkey failed (0x%08x)\n", r);
344
345     w=0xbeef;
346     r = IShellLinkA_GetHotkey(sl, &w);
347     ok(r == S_OK, "GetHotkey failed (0x%08x)\n", r);
348     ok(w==0x5678, "GetHotkey returned %d'\n", w);
349
350     IShellLinkA_Release(sl);
351 }
352
353
354 /*
355  * Test saving and loading .lnk files
356  */
357
358 #define lok                   ok_(__FILE__, line)
359 #define lok_todo_4(todo_flag,a,b,c,d) \
360     if ((todo & todo_flag) == 0) lok((a), (b), (c), (d)); \
361     else todo_wine lok((a), (b), (c), (d));
362 #define lok_todo_2(todo_flag,a,b) \
363     if ((todo & todo_flag) == 0) lok((a), (b)); \
364     else todo_wine lok((a), (b));
365 #define check_lnk(a,b,c)        check_lnk_(__LINE__, (a), (b), (c))
366
367 void create_lnk_(int line, const WCHAR* path, lnk_desc_t* desc, int save_fails)
368 {
369     HRESULT r;
370     IShellLinkA *sl;
371     IPersistFile *pf;
372
373     r = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
374                          &IID_IShellLinkA, (LPVOID*)&sl);
375     lok(r == S_OK, "no IID_IShellLinkA (0x%08x)\n", r);
376     if (r != S_OK)
377         return;
378
379     if (desc->description)
380     {
381         r = IShellLinkA_SetDescription(sl, desc->description);
382         lok(r == S_OK, "SetDescription failed (0x%08x)\n", r);
383     }
384     if (desc->workdir)
385     {
386         r = IShellLinkA_SetWorkingDirectory(sl, desc->workdir);
387         lok(r == S_OK, "SetWorkingDirectory failed (0x%08x)\n", r);
388     }
389     if (desc->path)
390     {
391         r = IShellLinkA_SetPath(sl, desc->path);
392         lok(SUCCEEDED(r), "SetPath failed (0x%08x)\n", r);
393     }
394     if (desc->pidl)
395     {
396         r = IShellLinkA_SetIDList(sl, desc->pidl);
397         lok(r == S_OK, "SetIDList failed (0x%08x)\n", r);
398     }
399     if (desc->arguments)
400     {
401         r = IShellLinkA_SetArguments(sl, desc->arguments);
402         lok(r == S_OK, "SetArguments failed (0x%08x)\n", r);
403     }
404     if (desc->showcmd)
405     {
406         r = IShellLinkA_SetShowCmd(sl, desc->showcmd);
407         lok(r == S_OK, "SetShowCmd failed (0x%08x)\n", r);
408     }
409     if (desc->icon)
410     {
411         r = IShellLinkA_SetIconLocation(sl, desc->icon, desc->icon_id);
412         lok(r == S_OK, "SetIconLocation failed (0x%08x)\n", r);
413     }
414     if (desc->hotkey)
415     {
416         r = IShellLinkA_SetHotkey(sl, desc->hotkey);
417         lok(r == S_OK, "SetHotkey failed (0x%08x)\n", r);
418     }
419
420     r = IShellLinkW_QueryInterface(sl, &IID_IPersistFile, (LPVOID*)&pf);
421     lok(r == S_OK, "no IID_IPersistFile (0x%08x)\n", r);
422     if (r == S_OK)
423     {
424         LPOLESTR str;
425
426     if (0)
427     {
428         /* crashes on XP */
429         IPersistFile_GetCurFile(pf, NULL);
430     }
431
432         /* test GetCurFile before ::Save */
433         str = (LPWSTR)0xdeadbeef;
434         r = IPersistFile_GetCurFile(pf, &str);
435         lok(r == S_FALSE ||
436             broken(r == S_OK), /* shell32 < 5.0 */
437             "got 0x%08x\n", r);
438         lok(str == NULL, "got %p\n", str);
439
440         r = IPersistFile_Save(pf, path, TRUE);
441         if (save_fails)
442         {
443             todo_wine {
444             lok(r == S_OK, "save failed (0x%08x)\n", r);
445             }
446         }
447         else
448         {
449             lok(r == S_OK, "save failed (0x%08x)\n", r);
450         }
451
452         /* test GetCurFile after ::Save */
453         r = IPersistFile_GetCurFile(pf, &str);
454         lok(r == S_OK, "got 0x%08x\n", r);
455         lok(str != NULL ||
456             broken(str == NULL), /* shell32 < 5.0 */
457             "Didn't expect NULL\n");
458         if (str != NULL)
459         {
460             IMalloc *pmalloc;
461
462             lok(!winetest_strcmpW(path, str), "Expected %s, got %s\n",
463                 wine_dbgstr_w(path), wine_dbgstr_w(str));
464
465             SHGetMalloc(&pmalloc);
466             IMalloc_Free(pmalloc, str);
467         }
468         else
469             win_skip("GetCurFile fails on shell32 < 5.0\n");
470
471         IPersistFile_Release(pf);
472     }
473
474     IShellLinkA_Release(sl);
475 }
476
477 static void check_lnk_(int line, const WCHAR* path, lnk_desc_t* desc, int todo)
478 {
479     HRESULT r;
480     IShellLinkA *sl;
481     IPersistFile *pf;
482     char buffer[INFOTIPSIZE];
483     LPOLESTR str;
484
485     r = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
486                          &IID_IShellLinkA, (LPVOID*)&sl);
487     lok(r == S_OK, "no IID_IShellLinkA (0x%08x)\n", r);
488     if (r != S_OK)
489         return;
490
491     r = IShellLinkA_QueryInterface(sl, &IID_IPersistFile, (LPVOID*)&pf);
492     lok(r == S_OK, "no IID_IPersistFile (0x%08x)\n", r);
493     if (r != S_OK)
494     {
495         IShellLinkA_Release(sl);
496         return;
497     }
498
499     /* test GetCurFile before ::Load */
500     str = (LPWSTR)0xdeadbeef;
501     r = IPersistFile_GetCurFile(pf, &str);
502     lok(r == S_FALSE ||
503         broken(r == S_OK), /* shell32 < 5.0 */
504         "got 0x%08x\n", r);
505     lok(str == NULL, "got %p\n", str);
506
507     r = IPersistFile_Load(pf, path, STGM_READ);
508     lok(r == S_OK, "load failed (0x%08x)\n", r);
509
510     /* test GetCurFile after ::Save */
511     r = IPersistFile_GetCurFile(pf, &str);
512     lok(r == S_OK, "got 0x%08x\n", r);
513     lok(str != NULL ||
514         broken(str == NULL), /* shell32 < 5.0 */
515         "Didn't expect NULL\n");
516     if (str != NULL)
517     {
518         IMalloc *pmalloc;
519
520         lok(!winetest_strcmpW(path, str), "Expected %s, got %s\n",
521             wine_dbgstr_w(path), wine_dbgstr_w(str));
522
523         SHGetMalloc(&pmalloc);
524         IMalloc_Free(pmalloc, str);
525     }
526     else
527         win_skip("GetCurFile fails on shell32 < 5.0\n");
528
529     IPersistFile_Release(pf);
530     if (r != S_OK)
531     {
532         IShellLinkA_Release(sl);
533         return;
534     }
535
536     if (desc->description)
537     {
538         strcpy(buffer,"garbage");
539         r = IShellLinkA_GetDescription(sl, buffer, sizeof(buffer));
540         lok(r == S_OK, "GetDescription failed (0x%08x)\n", r);
541         lok_todo_4(0x1, lstrcmp(buffer, desc->description)==0,
542            "GetDescription returned '%s' instead of '%s'\n",
543            buffer, desc->description);
544     }
545     if (desc->workdir)
546     {
547         strcpy(buffer,"garbage");
548         r = IShellLinkA_GetWorkingDirectory(sl, buffer, sizeof(buffer));
549         lok(r == S_OK, "GetWorkingDirectory failed (0x%08x)\n", r);
550         lok_todo_4(0x2, lstrcmpi(buffer, desc->workdir)==0,
551            "GetWorkingDirectory returned '%s' instead of '%s'\n",
552            buffer, desc->workdir);
553     }
554     if (desc->path)
555     {
556         strcpy(buffer,"garbage");
557         r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH);
558         lok(SUCCEEDED(r), "GetPath failed (0x%08x)\n", r);
559         lok_todo_4(0x4, lstrcmpi(buffer, desc->path)==0,
560            "GetPath returned '%s' instead of '%s'\n",
561            buffer, desc->path);
562     }
563     if (desc->pidl)
564     {
565         LPITEMIDLIST pidl=NULL;
566         r = IShellLinkA_GetIDList(sl, &pidl);
567         lok(r == S_OK, "GetIDList failed (0x%08x)\n", r);
568         lok_todo_2(0x8, pILIsEqual(pidl, desc->pidl),
569            "GetIDList returned an incorrect pidl\n");
570     }
571     if (desc->showcmd)
572     {
573         int i=0xdeadbeef;
574         r = IShellLinkA_GetShowCmd(sl, &i);
575         lok(r == S_OK, "GetShowCmd failed (0x%08x)\n", r);
576         lok_todo_4(0x10, i==desc->showcmd,
577            "GetShowCmd returned 0x%0x instead of 0x%0x\n",
578            i, desc->showcmd);
579     }
580     if (desc->icon)
581     {
582         int i=0xdeadbeef;
583         strcpy(buffer,"garbage");
584         r = IShellLinkA_GetIconLocation(sl, buffer, sizeof(buffer), &i);
585         lok(r == S_OK, "GetIconLocation failed (0x%08x)\n", r);
586         lok_todo_4(0x20, lstrcmpi(buffer, desc->icon)==0,
587            "GetIconLocation returned '%s' instead of '%s'\n",
588            buffer, desc->icon);
589         lok_todo_4(0x20, i==desc->icon_id,
590            "GetIconLocation returned 0x%0x instead of 0x%0x\n",
591            i, desc->icon_id);
592     }
593     if (desc->hotkey)
594     {
595         WORD i=0xbeef;
596         r = IShellLinkA_GetHotkey(sl, &i);
597         lok(r == S_OK, "GetHotkey failed (0x%08x)\n", r);
598         lok_todo_4(0x40, i==desc->hotkey,
599            "GetHotkey returned 0x%04x instead of 0x%04x\n",
600            i, desc->hotkey);
601     }
602
603     IShellLinkA_Release(sl);
604 }
605
606 static void test_load_save(void)
607 {
608     WCHAR lnkfile[MAX_PATH];
609     char lnkfileA[MAX_PATH];
610     static const char lnkfileA_name[] = "\\test.lnk";
611
612     lnk_desc_t desc;
613     char mypath[MAX_PATH];
614     char mydir[MAX_PATH];
615     char realpath[MAX_PATH];
616     char* p;
617     HANDLE hf;
618     DWORD r;
619
620     if (!pGetLongPathNameA)
621     {
622         win_skip("GetLongPathNameA is not available\n");
623         return;
624     }
625
626     /* Don't used a fixed path for the test.lnk file */
627     GetTempPathA(MAX_PATH, lnkfileA);
628     lstrcatA(lnkfileA, lnkfileA_name);
629     MultiByteToWideChar(CP_ACP, 0, lnkfileA, -1, lnkfile, MAX_PATH);
630
631     /* Save an empty .lnk file */
632     memset(&desc, 0, sizeof(desc));
633     create_lnk(lnkfile, &desc, 0);
634
635     /* It should come back as a bunch of empty strings */
636     desc.description="";
637     desc.workdir="";
638     desc.path="";
639     desc.arguments="";
640     desc.icon="";
641     check_lnk(lnkfile, &desc, 0x0);
642
643     /* Point a .lnk file to nonexistent files */
644     desc.description="";
645     desc.workdir="c:\\Nonexitent\\work\\directory";
646     desc.path="c:\\nonexistent\\path";
647     desc.pidl=NULL;
648     desc.arguments="";
649     desc.showcmd=0;
650     desc.icon="c:\\nonexistent\\icon\\file";
651     desc.icon_id=1234;
652     desc.hotkey=0;
653     create_lnk(lnkfile, &desc, 0);
654     check_lnk(lnkfile, &desc, 0x0);
655
656     r=GetModuleFileName(NULL, mypath, sizeof(mypath));
657     ok(r<sizeof(mypath), "GetModuleFileName failed (%d)\n", r);
658     strcpy(mydir, mypath);
659     p=strrchr(mydir, '\\');
660     if (p)
661         *p='\0';
662
663     /* IShellLink returns path in long form */
664     if (!pGetLongPathNameA(mypath, realpath, MAX_PATH)) strcpy( realpath, mypath );
665
666     /* Overwrite the existing lnk file and point it to existing files */
667     desc.description="test 2";
668     desc.workdir=mydir;
669     desc.path=realpath;
670     desc.pidl=NULL;
671     desc.arguments="/option1 /option2 \"Some string\"";
672     desc.showcmd=SW_SHOWNORMAL;
673     desc.icon=mypath;
674     desc.icon_id=0;
675     desc.hotkey=0x1234;
676     create_lnk(lnkfile, &desc, 0);
677     check_lnk(lnkfile, &desc, 0x0);
678
679     /* Test omitting .exe from an absolute path */
680     p=strrchr(realpath, '.');
681     if (p)
682         *p='\0';
683
684     desc.description="absolute path without .exe";
685     desc.workdir=mydir;
686     desc.path=realpath;
687     desc.pidl=NULL;
688     desc.arguments="/option1 /option2 \"Some string\"";
689     desc.showcmd=SW_SHOWNORMAL;
690     desc.icon=mypath;
691     desc.icon_id=0;
692     desc.hotkey=0x1234;
693     create_lnk(lnkfile, &desc, 0);
694     strcat(realpath, ".exe");
695     check_lnk(lnkfile, &desc, 0x4);
696
697     /* Overwrite the existing lnk file and test link to a command on the path */
698     desc.description="command on path";
699     desc.workdir=mypath;
700     desc.path="rundll32.exe";
701     desc.pidl=NULL;
702     desc.arguments="/option1 /option2 \"Some string\"";
703     desc.showcmd=SW_SHOWNORMAL;
704     desc.icon=mypath;
705     desc.icon_id=0;
706     desc.hotkey=0x1234;
707     create_lnk(lnkfile, &desc, 0);
708     /* Check that link is created to proper location */
709     SearchPathA( NULL, desc.path, NULL, MAX_PATH, realpath, NULL);
710     desc.path=realpath;
711     check_lnk(lnkfile, &desc, 0x0);
712
713     /* Test omitting .exe from a command on the path */
714     desc.description="command on path without .exe";
715     desc.workdir=mypath;
716     desc.path="rundll32";
717     desc.pidl=NULL;
718     desc.arguments="/option1 /option2 \"Some string\"";
719     desc.showcmd=SW_SHOWNORMAL;
720     desc.icon=mypath;
721     desc.icon_id=0;
722     desc.hotkey=0x1234;
723     create_lnk(lnkfile, &desc, 0);
724     /* Check that link is created to proper location */
725     SearchPathA( NULL, "rundll32", NULL, MAX_PATH, realpath, NULL);
726     desc.path=realpath;
727     check_lnk(lnkfile, &desc, 0x4);
728
729     /* Create a temporary non-executable file */
730     r=GetTempPath(sizeof(mypath), mypath);
731     ok(r<sizeof(mypath), "GetTempPath failed (%d), err %d\n", r, GetLastError());
732     r=pGetLongPathNameA(mypath, mydir, sizeof(mydir));
733     ok(r<sizeof(mydir), "GetLongPathName failed (%d), err %d\n", r, GetLastError());
734     p=strrchr(mydir, '\\');
735     if (p)
736         *p='\0';
737
738     strcpy(mypath, mydir);
739     strcat(mypath, "\\test.txt");
740     hf = CreateFile(mypath, GENERIC_WRITE, 0, NULL,
741                     CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
742     CloseHandle(hf);
743
744     /* Overwrite the existing lnk file and test link to an existing non-executable file */
745     desc.description="non-executable file";
746     desc.workdir=mydir;
747     desc.path=mypath;
748     desc.pidl=NULL;
749     desc.arguments="";
750     desc.showcmd=SW_SHOWNORMAL;
751     desc.icon=mypath;
752     desc.icon_id=0;
753     desc.hotkey=0x1234;
754     create_lnk(lnkfile, &desc, 0);
755     check_lnk(lnkfile, &desc, 0x0);
756
757     r=pGetShortPathNameA(mydir, mypath, sizeof(mypath));
758     ok(r<sizeof(mypath), "GetShortPathName failed (%d), err %d\n", r, GetLastError());
759
760     strcpy(realpath, mypath);
761     strcat(realpath, "\\test.txt");
762     strcat(mypath, "\\\\test.txt");
763
764     /* Overwrite the existing lnk file and test link to a short path with double backslashes */
765     desc.description="non-executable file";
766     desc.workdir=mydir;
767     desc.path=mypath;
768     desc.pidl=NULL;
769     desc.arguments="";
770     desc.showcmd=SW_SHOWNORMAL;
771     desc.icon=mypath;
772     desc.icon_id=0;
773     desc.hotkey=0x1234;
774     create_lnk(lnkfile, &desc, 0);
775     desc.path=realpath;
776     check_lnk(lnkfile, &desc, 0x0);
777
778     r = DeleteFileA(mypath);
779     ok(r, "failed to delete file %s (%d)\n", mypath, GetLastError());
780
781     /* Create a temporary .bat file */
782     strcpy(mypath, mydir);
783     strcat(mypath, "\\test.bat");
784     hf = CreateFile(mypath, GENERIC_WRITE, 0, NULL,
785                     CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
786     CloseHandle(hf);
787
788     strcpy(realpath, mypath);
789
790     p=strrchr(mypath, '.');
791     if (p)
792         *p='\0';
793
794     /* Try linking to the .bat file without the extension */
795     desc.description="batch file";
796     desc.workdir=mydir;
797     desc.path=mypath;
798     desc.pidl=NULL;
799     desc.arguments="";
800     desc.showcmd=SW_SHOWNORMAL;
801     desc.icon=mypath;
802     desc.icon_id=0;
803     desc.hotkey=0x1234;
804     create_lnk(lnkfile, &desc, 0);
805     desc.path = realpath;
806     check_lnk(lnkfile, &desc, 0x4);
807
808     r = DeleteFileA(realpath);
809     ok(r, "failed to delete file %s (%d)\n", realpath, GetLastError());
810
811     /* FIXME: Also test saving a .lnk pointing to a pidl that cannot be
812      * represented as a path.
813      */
814
815     /* DeleteFileW is not implemented on Win9x */
816     r=DeleteFileA(lnkfileA);
817     ok(r, "failed to delete link '%s' (%d)\n", lnkfileA, GetLastError());
818 }
819
820 static void test_datalink(void)
821 {
822     static const WCHAR lnk[] = {
823       ':',':','{','9','d','b','1','1','8','6','e','-','4','0','d','f','-','1',
824       '1','d','1','-','a','a','8','c','-','0','0','c','0','4','f','b','6','7',
825       '8','6','3','}',':','2','6',',','!','!','g','x','s','f','(','N','g',']',
826       'q','F','`','H','{','L','s','A','C','C','E','S','S','F','i','l','e','s',
827       '>','p','l','T',']','j','I','{','j','f','(','=','1','&','L','[','-','8',
828       '1','-',']',':',':',0 };
829     static const WCHAR comp[] = {
830       '2','6',',','!','!','g','x','s','f','(','N','g',']','q','F','`','H','{',
831       'L','s','A','C','C','E','S','S','F','i','l','e','s','>','p','l','T',']',
832       'j','I','{','j','f','(','=','1','&','L','[','-','8','1','-',']',0 };
833     IShellLinkDataList *dl = NULL;
834     IShellLinkW *sl = NULL;
835     HRESULT r;
836     DWORD flags = 0;
837     EXP_DARWIN_LINK *dar;
838
839     r = CoCreateInstance( &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
840                             &IID_IShellLinkW, (LPVOID*)&sl );
841     ok( r == S_OK ||
842         broken(r == E_NOINTERFACE), /* Win9x */
843         "CoCreateInstance failed (0x%08x)\n", r);
844     if (!sl)
845     {
846         win_skip("no shelllink\n");
847         return;
848     }
849
850     r = IShellLinkW_QueryInterface( sl, &_IID_IShellLinkDataList, (LPVOID*) &dl );
851     ok( r == S_OK ||
852         broken(r == E_NOINTERFACE), /* NT4 */
853         "IShellLinkW_QueryInterface failed (0x%08x)\n", r);
854
855     if (!dl)
856     {
857         win_skip("no datalink interface\n");
858         IShellLinkW_Release( sl );
859         return;
860     }
861
862     flags = 0;
863     r = IShellLinkDataList_GetFlags( dl, &flags );
864     ok( r == S_OK, "GetFlags failed\n");
865     ok( flags == 0, "GetFlags returned wrong flags\n");
866
867     dar = (void*)-1;
868     r = IShellLinkDataList_CopyDataBlock( dl, EXP_DARWIN_ID_SIG, (LPVOID*) &dar );
869     ok( r == E_FAIL, "CopyDataBlock failed\n");
870     ok( dar == NULL, "should be null\n");
871
872     if (!pGetLongPathNameA /* NT4 */)
873         skip("SetPath with NULL parameter crashes on NT4\n");
874     else
875     {
876         r = IShellLinkW_SetPath(sl, NULL);
877         ok(r == E_INVALIDARG, "SetPath returned wrong error (0x%08x)\n", r);
878     }
879
880     r = IShellLinkW_SetPath(sl, lnk);
881     ok(r == S_OK, "SetPath failed\n");
882
883 if (0)
884 {
885     /* the following crashes */
886     IShellLinkDataList_GetFlags( dl, NULL );
887 }
888
889     flags = 0;
890     r = IShellLinkDataList_GetFlags( dl, &flags );
891     ok( r == S_OK, "GetFlags failed\n");
892     /* SLDF_HAS_LOGO3ID is no longer supported on Vista+, filter it out */
893     ok( (flags & (~ SLDF_HAS_LOGO3ID)) == SLDF_HAS_DARWINID,
894         "GetFlags returned wrong flags\n");
895
896     dar = NULL;
897     r = IShellLinkDataList_CopyDataBlock( dl, EXP_DARWIN_ID_SIG, (LPVOID*) &dar );
898     ok( r == S_OK, "CopyDataBlock failed\n");
899
900     ok( dar && ((DATABLOCK_HEADER*)dar)->dwSignature == EXP_DARWIN_ID_SIG, "signature wrong\n");
901     ok( dar && 0==lstrcmpW(dar->szwDarwinID, comp ), "signature wrong\n");
902
903     LocalFree( dar );
904
905     IUnknown_Release( dl );
906     IShellLinkW_Release( sl );
907 }
908
909 static void test_shdefextracticon(void)
910 {
911     HICON hiconlarge=NULL, hiconsmall=NULL;
912     HRESULT res;
913
914     if (!pSHDefExtractIconA)
915     {
916         win_skip("SHDefExtractIconA is unavailable\n");
917         return;
918     }
919
920     res = pSHDefExtractIconA("shell32.dll", 0, 0, &hiconlarge, &hiconsmall, MAKELONG(16,24));
921     ok(SUCCEEDED(res), "SHDefExtractIconA failed, res=%x\n", res);
922     ok(hiconlarge != NULL, "got null hiconlarge\n");
923     ok(hiconsmall != NULL, "got null hiconsmall\n");
924     DestroyIcon(hiconlarge);
925     DestroyIcon(hiconsmall);
926
927     hiconsmall = NULL;
928     res = pSHDefExtractIconA("shell32.dll", 0, 0, NULL, &hiconsmall, MAKELONG(16,24));
929     ok(SUCCEEDED(res), "SHDefExtractIconA failed, res=%x\n", res);
930     ok(hiconsmall != NULL, "got null hiconsmall\n");
931     DestroyIcon(hiconsmall);
932
933     res = pSHDefExtractIconA("shell32.dll", 0, 0, NULL, NULL, MAKELONG(16,24));
934     ok(SUCCEEDED(res), "SHDefExtractIconA failed, res=%x\n", res);
935 }
936
937 static void test_GetIconLocation(void)
938 {
939     IShellLinkA *sl;
940     const char *str;
941     char buffer[INFOTIPSIZE], mypath[MAX_PATH];
942     int i;
943     HRESULT r;
944     LPITEMIDLIST pidl;
945
946     r = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
947             &IID_IShellLinkA, (LPVOID*)&sl);
948     ok(r == S_OK, "no IID_IShellLinkA (0x%08x)\n", r);
949     if(r != S_OK)
950         return;
951
952     i = 0xdeadbeef;
953     strcpy(buffer, "garbage");
954     r = IShellLinkA_GetIconLocation(sl, buffer, sizeof(buffer), &i);
955     ok(r == S_OK, "GetIconLocation failed (0x%08x)\n", r);
956     ok(*buffer == '\0', "GetIconLocation returned '%s'\n", buffer);
957     ok(i == 0, "GetIconLocation returned %d\n", i);
958
959     str = "c:\\some\\path";
960     r = IShellLinkA_SetPath(sl, str);
961     ok(r == S_FALSE || r == S_OK, "SetPath failed (0x%08x)\n", r);
962
963     i = 0xdeadbeef;
964     strcpy(buffer, "garbage");
965     r = IShellLinkA_GetIconLocation(sl, buffer, sizeof(buffer), &i);
966     ok(r == S_OK, "GetIconLocation failed (0x%08x)\n", r);
967     ok(*buffer == '\0', "GetIconLocation returned '%s'\n", buffer);
968     ok(i == 0, "GetIconLocation returned %d\n", i);
969
970     GetWindowsDirectoryA(mypath, sizeof(mypath) - 12);
971     strcat(mypath, "\\regedit.exe");
972     pidl = path_to_pidl(mypath);
973     r = IShellLinkA_SetIDList(sl, pidl);
974     ok(r == S_OK, "SetPath failed (0x%08x)\n", r);
975     pILFree(pidl);
976
977     i = 0xdeadbeef;
978     strcpy(buffer, "garbage");
979     r = IShellLinkA_GetIconLocation(sl, buffer, sizeof(buffer), &i);
980     ok(r == S_OK, "GetIconLocation failed (0x%08x)\n", r);
981     ok(*buffer == '\0', "GetIconLocation returned '%s'\n", buffer);
982     ok(i == 0, "GetIconLocation returned %d\n", i);
983
984     str = "c:\\nonexistent\\file";
985     r = IShellLinkA_SetIconLocation(sl, str, 0xbabecafe);
986     ok(r == S_OK, "SetIconLocation failed (0x%08x)\n", r);
987
988     i = 0xdeadbeef;
989     r = IShellLinkA_GetIconLocation(sl, buffer, sizeof(buffer), &i);
990     ok(r == S_OK, "GetIconLocation failed (0x%08x)\n", r);
991     ok(lstrcmpi(buffer,str) == 0, "GetIconLocation returned '%s'\n", buffer);
992     ok(i == 0xbabecafe, "GetIconLocation returned %d'\n", i);
993
994     IShellLinkA_Release(sl);
995 }
996
997 START_TEST(shelllink)
998 {
999     HRESULT r;
1000     HMODULE hmod = GetModuleHandleA("shell32.dll");
1001     HMODULE hkernel32 = GetModuleHandleA("kernel32.dll");
1002
1003     pILFree = (void *)GetProcAddress(hmod, (LPSTR)155);
1004     pILIsEqual = (void *)GetProcAddress(hmod, (LPSTR)21);
1005     pSHILCreateFromPath = (void *)GetProcAddress(hmod, (LPSTR)28);
1006     pSHDefExtractIconA = (void *)GetProcAddress(hmod, "SHDefExtractIconA");
1007
1008     pGetLongPathNameA = (void *)GetProcAddress(hkernel32, "GetLongPathNameA");
1009     pGetShortPathNameA = (void *)GetProcAddress(hkernel32, "GetShortPathNameA");
1010
1011     r = CoInitialize(NULL);
1012     ok(r == S_OK, "CoInitialize failed (0x%08x)\n", r);
1013     if (r != S_OK)
1014         return;
1015
1016     test_get_set();
1017     test_load_save();
1018     test_datalink();
1019     test_shdefextracticon();
1020     test_GetIconLocation();
1021
1022     CoUninitialize();
1023 }