Unicodify wineesd.
[wine] / dlls / shell32 / tests / shlexec.c
1 /*
2  * Unit test of the ShellExecute function.
3  *
4  * Copyright 2005 Francois Gouget for CodeWeavers
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 /* TODO:
22  * - test the default verb selection
23  * - test selection of an alternate class
24  * - try running executables in more ways
25  * - try passing arguments to executables
26  * - ShellExecute("foo.shlexec") with no path should work if foo.shlexec is
27  *   in the PATH
28  * - test associations that use %l, %L or "%1" instead of %1
29  * - we may want to test ShellExecuteEx() instead of ShellExecute()
30  *   and then we could also check its return value
31  * - ShellExecuteEx() also calls SetLastError() with meaningful values which
32  *   we could check
33  */
34
35 #include <stdio.h>
36 #include <assert.h>
37
38 #include "wtypes.h"
39 #include "winbase.h"
40 #include "windef.h"
41 #include "shellapi.h"
42 #include "shlwapi.h"
43 #include "wine/test.h"
44
45 #include "shell32_test.h"
46
47
48 static char argv0[MAX_PATH];
49 static int myARGC;
50 static char** myARGV;
51 static char tmpdir[MAX_PATH];
52
53 static const char* testfiles[]=
54 {
55     "%s\\test file.shlexec",
56     "%s\\test file.noassoc",
57     "%s\\test file.noassoc.shlexec",
58     "%s\\test file.shlexec.noassoc",
59     "%s\\test_shortcut_shlexec.lnk",
60     NULL
61 };
62
63
64 static void strcat_param(char* str, const char* param)
65 {
66     if (param!=NULL)
67     {
68         strcat(str, "\"");
69         strcat(str, param);
70         strcat(str, "\"");
71     }
72     else
73     {
74         strcat(str, "null");
75     }
76 }
77
78 static char shell_call[2048]="";
79 static int shell_execute(LPCSTR operation, LPCSTR file, LPCSTR parameters, LPCSTR directory)
80 {
81     strcpy(shell_call, "ShellExecute(");
82     strcat_param(shell_call, operation);
83     strcat(shell_call, ", ");
84     strcat_param(shell_call, file);
85     strcat(shell_call, ", ");
86     strcat_param(shell_call, parameters);
87     strcat(shell_call, ", ");
88     strcat_param(shell_call, directory);
89     strcat(shell_call, ")");
90     if (winetest_debug > 1)
91         trace("%s\n", shell_call);
92
93     SetLastError(0xcafebabe);
94     /* FIXME: We cannot use ShellExecuteEx() here because if there is no
95      * association it displays the 'Open With' dialog and I could not find
96      * a flag to prevent this.
97      */
98     return (int)ShellExecute(NULL, operation, file, parameters, directory,
99                              SW_SHOWNORMAL);
100 }
101
102 static int shell_execute_ex(DWORD mask, LPCSTR operation, LPCSTR file,
103                             LPCSTR parameters, LPCSTR directory)
104 {
105     SHELLEXECUTEINFO sei;
106     BOOL success;
107     int rc;
108
109     strcpy(shell_call, "ShellExecuteEx(");
110     strcat_param(shell_call, operation);
111     strcat(shell_call, ", ");
112     strcat_param(shell_call, file);
113     strcat(shell_call, ", ");
114     strcat_param(shell_call, parameters);
115     strcat(shell_call, ", ");
116     strcat_param(shell_call, directory);
117     strcat(shell_call, ")");
118     if (winetest_debug > 1)
119         trace("%s\n", shell_call);
120
121     sei.cbSize=sizeof(sei);
122     sei.fMask=mask;
123     sei.hwnd=NULL;
124     sei.lpVerb=operation;
125     sei.lpFile=file;
126     sei.lpParameters=parameters;
127     sei.lpDirectory=directory;
128     sei.nShow=SW_SHOWNORMAL;
129     sei.hInstApp=NULL; /* Out */
130     sei.lpIDList=NULL;
131     sei.lpClass=NULL;
132     sei.hkeyClass=NULL;
133     sei.dwHotKey=0;
134     U(sei).hIcon=NULL;
135
136     SetLastError(0xcafebabe);
137     success=ShellExecuteEx(&sei);
138     rc=(int)sei.hInstApp;
139     ok((success && rc >= 32) || (!success && rc < 32),
140        "%s rc=%d and hInstApp=%d is not allowed\n", shell_call, success, rc);
141     return rc;
142 }
143
144 static void create_test_association(const char* extension)
145 {
146     HKEY hkey, hkey_shell;
147     char class[MAX_PATH];
148     LONG rc;
149
150     sprintf(class, "shlexec%s", extension);
151     rc=RegCreateKeyEx(HKEY_CLASSES_ROOT, extension, 0, NULL, 0, KEY_SET_VALUE,
152                       NULL, &hkey, NULL);
153     assert(rc==ERROR_SUCCESS);
154     rc=RegSetValueEx(hkey, NULL, 0, REG_SZ, class, strlen(class)+1);
155     assert(rc==ERROR_SUCCESS);
156     CloseHandle(hkey);
157
158     rc=RegCreateKeyEx(HKEY_CLASSES_ROOT, class, 0, NULL, 0,
159                       KEY_CREATE_SUB_KEY | KEY_ENUMERATE_SUB_KEYS, NULL, &hkey, NULL);
160     assert(rc==ERROR_SUCCESS);
161     rc=RegCreateKeyEx(hkey, "shell", 0, NULL, 0,
162                       KEY_CREATE_SUB_KEY, NULL, &hkey_shell, NULL);
163     assert(rc==ERROR_SUCCESS);
164     CloseHandle(hkey);
165     CloseHandle(hkey_shell);
166 }
167
168 static void delete_test_association(const char* extension)
169 {
170     char class[MAX_PATH];
171
172     sprintf(class, "shlexec%s", extension);
173     SHDeleteKey(HKEY_CLASSES_ROOT, class);
174     SHDeleteKey(HKEY_CLASSES_ROOT, extension);
175 }
176
177 static void create_test_verb(const char* extension, const char* verb)
178 {
179     HKEY hkey_shell, hkey_verb, hkey_cmd;
180     char shell[MAX_PATH];
181     char* cmd;
182     LONG rc;
183
184     sprintf(shell, "shlexec%s\\shell", extension);
185     rc=RegOpenKeyEx(HKEY_CLASSES_ROOT, shell, 0,
186                     KEY_CREATE_SUB_KEY, &hkey_shell);
187     assert(rc==ERROR_SUCCESS);
188     rc=RegCreateKeyEx(hkey_shell, verb, 0, NULL, 0, KEY_CREATE_SUB_KEY,
189                       NULL, &hkey_verb, NULL);
190     assert(rc==ERROR_SUCCESS);
191     rc=RegCreateKeyEx(hkey_verb, "command", 0, NULL, 0, KEY_SET_VALUE,
192                       NULL, &hkey_cmd, NULL);
193     assert(rc==ERROR_SUCCESS);
194
195     cmd=malloc(strlen(argv0)+13+1);
196     sprintf(cmd,"%s shlexec \"%%1\"", argv0);
197     rc=RegSetValueEx(hkey_cmd, NULL, 0, REG_SZ, cmd, strlen(cmd)+1);
198     assert(rc==ERROR_SUCCESS);
199
200     free(cmd);
201     CloseHandle(hkey_shell);
202     CloseHandle(hkey_verb);
203     CloseHandle(hkey_cmd);
204 }
205
206
207 typedef struct
208 {
209     char* basename;
210     int rc;
211     int todo;
212 } filename_tests_t;
213
214 static filename_tests_t filename_tests[]=
215 {
216     /* Test bad / nonexistent filenames */
217     {"%s\\nonexistent.shlexec", ERROR_FILE_NOT_FOUND, 1},
218     {"%s\\nonexistent.noassoc", ERROR_FILE_NOT_FOUND, 1},
219
220     /* Standard tests */
221     {"%s\\test file.shlexec",   0, 0},
222     {"%s\\test file.shlexec.",  0, 0},
223     {"%s/test file.shlexec",    0, 0},
224
225     /* Test filenames with no association */
226     {"%s\\test file.noassoc",   SE_ERR_NOASSOC, 0},
227
228     /* Test double extensions */
229     {"%s\\test file.noassoc.shlexec", 0, 0},
230     {"%s\\test file.shlexec.noassoc", SE_ERR_NOASSOC, 0},
231
232     /* Test shortcuts */
233     {"%s\\test_shortcut_shlexec.lnk", 0, 0},
234
235     {NULL, 0, 0}
236 };
237
238 static void test_filename()
239 {
240     char filename[MAX_PATH];
241     const filename_tests_t* test;
242     HMODULE hdll;
243     DLLVERSIONINFO dllver;
244     HRESULT (WINAPI *pDllGetVersion)(DLLVERSIONINFO*);
245     char* c;
246     int rc;
247
248     test=filename_tests;
249     while (test->basename)
250     {
251         sprintf(filename, test->basename, tmpdir);
252         if (strchr(filename, '/'))
253         {
254             c=filename;
255             while (*c)
256             {
257                 if (*c=='\\')
258                     *c='/';
259                 c++;
260             }
261         }
262         rc=shell_execute(NULL, filename, NULL, NULL);
263         if (test->rc==0)
264         {
265             if (test->todo)
266             {
267                 todo_wine
268                 {
269                     ok(rc>=32, "%s failed: rc=%d err=%ld\n", shell_call,
270                        rc, GetLastError());
271                 }
272             }
273             else
274             {
275                 ok(rc>=32, "%s failed: rc=%d err=%ld\n", shell_call,
276                    rc, GetLastError());
277             }
278         }
279         else
280         {
281             if (test->todo)
282             {
283                 todo_wine
284                 {
285                     ok(rc==test->rc, "%s returned %d\n", shell_call, rc);
286                 }
287             }
288             else
289             {
290                 ok(rc==test->rc, "%s returned %d\n", shell_call, rc);
291             }
292         }
293         test++;
294     }
295
296     hdll=GetModuleHandleA("shell32.dll");
297     pDllGetVersion=(void*)GetProcAddress(hdll, "DllGetVersion");
298     if (pDllGetVersion)
299     {
300         dllver.cbSize=sizeof(dllver);
301         pDllGetVersion(&dllver);
302         trace("major=%ld minor=%ld build=%ld platform=%ld\n",
303               dllver.dwMajorVersion, dllver.dwMinorVersion,
304               dllver.dwBuildNumber, dllver.dwPlatformID);
305
306         /* The more recent versions of shell32.dll accept quoted filenames
307          * while older ones (e.g. 4.00) don't. Still we want to test this
308          * because IE 6 depends on the new behavior.
309          * One day we may need to check the exact version of the dll but for
310          * now making sure DllGetVersion() is present is sufficient.
311          */
312         sprintf(filename, "\"%s\\test file.shlexec\"", tmpdir);
313         rc=shell_execute(NULL, filename, NULL, NULL);
314         ok(rc>=32, "%s failed: rc=%d err=%ld\n", shell_call, rc,
315            GetLastError());
316
317         if (dllver.dwMajorVersion>=6)
318         {
319             /* Recent versions of shell32.dll accept '/'s in shortcut paths.
320              * Older versions don't or are quite buggy in this regard.
321              */
322             sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir);
323             c=filename;
324             while (*c)
325             {
326                 if (*c=='\\')
327                     *c='/';
328                 c++;
329             }
330             rc=shell_execute(NULL, filename, NULL, NULL);
331             todo_wine {
332             ok(rc>=32, "%s failed: rc=%d err=%ld\n", shell_call, rc,
333                GetLastError());
334             }
335         }
336     }
337 }
338
339
340 static void test_exes()
341 {
342     char filename[MAX_PATH];
343     int rc;
344
345     /* We need NOZONECHECKS on Win2003 to block a dialog */
346     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, "shlexec -nop",
347                         NULL);
348     ok(rc>=32, "%s returned %d\n", shell_call, rc);
349
350     sprintf(filename, "%s\\test file.noassoc", tmpdir);
351     if (CopyFile(argv0, filename, FALSE))
352     {
353         rc=shell_execute(NULL, filename, "shlexec -nop", NULL);
354         todo_wine {
355         ok(rc==SE_ERR_NOASSOC, "%s succeeded: rc=%d\n", shell_call, rc);
356         }
357     }
358 }
359
360
361 static void init_test()
362 {
363     char filename[MAX_PATH];
364     WCHAR lnkfile[MAX_PATH];
365     const char* const * testfile;
366     lnk_desc_t desc;
367     DWORD rc;
368     HRESULT r;
369
370     r = CoInitialize(NULL);
371     ok(SUCCEEDED(r), "CoInitialize failed (0x%08lx)\n", r);
372     if (!SUCCEEDED(r))
373         exit(1);
374
375     rc=GetModuleFileName(NULL, argv0, sizeof(argv0));
376     assert(rc!=0 && rc<sizeof(argv0));
377     if (GetFileAttributes(argv0)==INVALID_FILE_ATTRIBUTES)
378     {
379         strcat(argv0, ".so");
380         ok(GetFileAttributes(argv0)!=INVALID_FILE_ATTRIBUTES,
381            "unable to find argv0!\n");
382     }
383
384     GetTempPathA(sizeof(tmpdir)/sizeof(*tmpdir), tmpdir);
385
386     /* Set up the test files */
387     testfile=testfiles;
388     while (*testfile)
389     {
390         HANDLE hfile;
391
392         sprintf(filename, *testfile, tmpdir);
393         hfile=CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
394                      FILE_ATTRIBUTE_NORMAL, NULL);
395         if (hfile==INVALID_HANDLE_VALUE)
396         {
397             trace("unable to create '%s': err=%ld\n", filename, GetLastError());
398             assert(0);
399         }
400         CloseHandle(hfile);
401         testfile++;
402     }
403
404     /* Setup the test shortcuts */
405     sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir);
406     MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, sizeof(lnkfile)/sizeof(*lnkfile));
407     desc.description=NULL;
408     desc.workdir=NULL;
409     sprintf(filename, "%s\\test file.shlexec", tmpdir);
410     desc.path=filename;
411     desc.pidl=NULL;
412     desc.arguments="";
413     desc.showcmd=0;
414     desc.icon=NULL;
415     desc.icon_id=0;
416     desc.hotkey=0;
417     create_lnk(lnkfile, &desc, 0);
418
419     /* Create a basic association suitable for most tests */
420     create_test_association(".shlexec");
421     create_test_verb(".shlexec", "Open");
422 }
423
424 static void cleanup_test()
425 {
426     char filename[MAX_PATH];
427     const char* const * testfile;
428
429     /* Delete the test files */
430     testfile=testfiles;
431     while (*testfile)
432     {
433         sprintf(filename, *testfile, tmpdir);
434         DeleteFile(filename);
435         testfile++;
436     }
437
438     /* Delete the test association */
439     delete_test_association(".shlexec");
440
441     CoUninitialize();
442 }
443
444 START_TEST(shlexec)
445 {
446
447     myARGC = winetest_get_mainargs(&myARGV);
448     if (myARGC>=3)
449     {
450         /* FIXME: We should dump the parameters we got
451          *        and have the parent verify them
452          */
453         exit(0);
454     }
455
456     init_test();
457
458     test_filename();
459     test_exes();
460
461     cleanup_test();
462 }