shell32/tests: Add a few more ShellExecute() file URL tests.
[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, 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 /* Needed to get SEE_MASK_NOZONECHECKS with the PSDK */
36 #define NTDDI_WINXPSP1 0x05010100
37 #define NTDDI_VERSION NTDDI_WINXPSP1
38 #define _WIN32_WINNT 0x0501
39
40 #include <stdio.h>
41 #include <assert.h>
42
43 #include "wtypes.h"
44 #include "winbase.h"
45 #include "windef.h"
46 #include "shellapi.h"
47 #include "shlwapi.h"
48 #include "wine/test.h"
49
50 #include "shell32_test.h"
51
52
53 static char argv0[MAX_PATH];
54 static int myARGC;
55 static char** myARGV;
56 static char tmpdir[MAX_PATH];
57 static char child_file[MAX_PATH];
58 static DLLVERSIONINFO dllver;
59 static BOOL skip_noassoc_tests = FALSE;
60 static HANDLE dde_ready_event;
61
62
63 /***
64  *
65  * ShellExecute wrappers
66  *
67  ***/
68 static void dump_child(void);
69
70 static HANDLE hEvent;
71 static void init_event(const char* child_file)
72 {
73     char* event_name;
74     event_name=strrchr(child_file, '\\')+1;
75     hEvent=CreateEvent(NULL, FALSE, FALSE, event_name);
76 }
77
78 static void strcat_param(char* str, const char* param)
79 {
80     if (param!=NULL)
81     {
82         strcat(str, "\"");
83         strcat(str, param);
84         strcat(str, "\"");
85     }
86     else
87     {
88         strcat(str, "null");
89     }
90 }
91
92 static char shell_call[2048]="";
93 static int bad_shellexecute = 0;
94 static INT_PTR shell_execute(LPCSTR operation, LPCSTR file, LPCSTR parameters, LPCSTR directory)
95 {
96     INT_PTR rc, rcEmpty = 0;
97
98     if(!operation)
99         rcEmpty = shell_execute("", file, parameters, directory);
100
101     strcpy(shell_call, "ShellExecute(");
102     strcat_param(shell_call, operation);
103     strcat(shell_call, ", ");
104     strcat_param(shell_call, file);
105     strcat(shell_call, ", ");
106     strcat_param(shell_call, parameters);
107     strcat(shell_call, ", ");
108     strcat_param(shell_call, directory);
109     strcat(shell_call, ")");
110     if (winetest_debug > 1)
111         trace("%s\n", shell_call);
112
113     DeleteFile(child_file);
114     SetLastError(0xcafebabe);
115
116     /* FIXME: We cannot use ShellExecuteEx() here because if there is no
117      * association it displays the 'Open With' dialog and I could not find
118      * a flag to prevent this.
119      */
120     rc=(INT_PTR)ShellExecute(NULL, operation, file, parameters, directory, SW_SHOWNORMAL);
121
122     if (rc > 32)
123     {
124         int wait_rc;
125         wait_rc=WaitForSingleObject(hEvent, 5000);
126         if (wait_rc == WAIT_TIMEOUT)
127         {
128             HWND wnd = FindWindowA("#32770", "Windows");
129             if (wnd != NULL)
130             {
131                 SendMessage(wnd, WM_CLOSE, 0, 0);
132                 win_skip("Skipping shellexecute of file with unassociated extension\n");
133                 skip_noassoc_tests = TRUE;
134                 rc = SE_ERR_NOASSOC;
135             }
136         }
137         ok(wait_rc==WAIT_OBJECT_0 || rc <= 32, "%s WaitForSingleObject returned %d\n", shell_call, wait_rc);
138     }
139     /* The child process may have changed the result file, so let profile
140      * functions know about it
141      */
142     WritePrivateProfileStringA(NULL, NULL, NULL, child_file);
143     if (rc > 32)
144         dump_child();
145
146     if(!operation)
147     {
148         if (rc != rcEmpty && rcEmpty == SE_ERR_NOASSOC) /* NT4 */
149             bad_shellexecute = 1;
150         ok(rc == rcEmpty || broken(rc != rcEmpty && rcEmpty == SE_ERR_NOASSOC) /* NT4 */,
151            "%s Got different return value with empty string: %lu %lu\n", shell_call, rc, rcEmpty);
152     }
153
154     return rc;
155 }
156
157 static INT_PTR shell_execute_ex(DWORD mask, LPCSTR operation, LPCSTR file,
158                                 LPCSTR parameters, LPCSTR directory)
159 {
160     SHELLEXECUTEINFO sei;
161     BOOL success;
162     INT_PTR rc;
163
164     strcpy(shell_call, "ShellExecuteEx(");
165     strcat_param(shell_call, operation);
166     strcat(shell_call, ", ");
167     strcat_param(shell_call, file);
168     strcat(shell_call, ", ");
169     strcat_param(shell_call, parameters);
170     strcat(shell_call, ", ");
171     strcat_param(shell_call, directory);
172     strcat(shell_call, ")");
173     if (winetest_debug > 1)
174         trace("%s\n", shell_call);
175
176     sei.cbSize=sizeof(sei);
177     sei.fMask=SEE_MASK_NOCLOSEPROCESS | mask;
178     sei.hwnd=NULL;
179     sei.lpVerb=operation;
180     sei.lpFile=file;
181     sei.lpParameters=parameters;
182     sei.lpDirectory=directory;
183     sei.nShow=SW_SHOWNORMAL;
184     sei.hInstApp=NULL; /* Out */
185     sei.lpIDList=NULL;
186     sei.lpClass=NULL;
187     sei.hkeyClass=NULL;
188     sei.dwHotKey=0;
189     U(sei).hIcon=NULL;
190     sei.hProcess=NULL; /* Out */
191
192     DeleteFile(child_file);
193     SetLastError(0xcafebabe);
194     success=ShellExecuteEx(&sei);
195     rc=(INT_PTR)sei.hInstApp;
196     ok((success && rc > 32) || (!success && rc <= 32),
197        "%s rc=%d and hInstApp=%ld is not allowed\n", shell_call, success, rc);
198
199     if (rc > 32)
200     {
201         int wait_rc;
202         if (sei.hProcess!=NULL)
203         {
204             wait_rc=WaitForSingleObject(sei.hProcess, 5000);
205             ok(wait_rc==WAIT_OBJECT_0, "WaitForSingleObject(hProcess) returned %d\n", wait_rc);
206         }
207         wait_rc=WaitForSingleObject(hEvent, 5000);
208         ok(wait_rc==WAIT_OBJECT_0, "WaitForSingleObject returned %d\n", wait_rc);
209     }
210     /* The child process may have changed the result file, so let profile
211      * functions know about it
212      */
213     WritePrivateProfileStringA(NULL, NULL, NULL, child_file);
214     if (rc > 32)
215         dump_child();
216
217     return rc;
218 }
219
220
221
222 /***
223  *
224  * Functions to create / delete associations wrappers
225  *
226  ***/
227
228 static BOOL create_test_association(const char* extension)
229 {
230     HKEY hkey, hkey_shell;
231     char class[MAX_PATH];
232     LONG rc;
233
234     sprintf(class, "shlexec%s", extension);
235     rc=RegCreateKeyEx(HKEY_CLASSES_ROOT, extension, 0, NULL, 0, KEY_SET_VALUE,
236                       NULL, &hkey, NULL);
237     if (rc != ERROR_SUCCESS)
238         return FALSE;
239
240     rc=RegSetValueEx(hkey, NULL, 0, REG_SZ, (LPBYTE) class, strlen(class)+1);
241     ok(rc==ERROR_SUCCESS, "RegSetValueEx '%s' failed, expected ERROR_SUCCESS, got %d\n", class, rc);
242     CloseHandle(hkey);
243
244     rc=RegCreateKeyEx(HKEY_CLASSES_ROOT, class, 0, NULL, 0,
245                       KEY_CREATE_SUB_KEY | KEY_ENUMERATE_SUB_KEYS, NULL, &hkey, NULL);
246     ok(rc==ERROR_SUCCESS, "RegCreateKeyEx '%s' failed, expected ERROR_SUCCESS, got %d\n", class, rc);
247
248     rc=RegCreateKeyEx(hkey, "shell", 0, NULL, 0,
249                       KEY_CREATE_SUB_KEY, NULL, &hkey_shell, NULL);
250     ok(rc==ERROR_SUCCESS, "RegCreateKeyEx 'shell' failed, expected ERROR_SUCCESS, got %d\n", rc);
251
252     CloseHandle(hkey);
253     CloseHandle(hkey_shell);
254
255     return TRUE;
256 }
257
258 /* Based on RegDeleteTreeW from dlls/advapi32/registry.c */
259 static LSTATUS myRegDeleteTreeA(HKEY hKey, LPCSTR lpszSubKey)
260 {
261     LONG ret;
262     DWORD dwMaxSubkeyLen, dwMaxValueLen;
263     DWORD dwMaxLen, dwSize;
264     CHAR szNameBuf[MAX_PATH], *lpszName = szNameBuf;
265     HKEY hSubKey = hKey;
266
267     if(lpszSubKey)
268     {
269         ret = RegOpenKeyExA(hKey, lpszSubKey, 0, KEY_READ, &hSubKey);
270         if (ret) return ret;
271     }
272
273     /* Get highest length for keys, values */
274     ret = RegQueryInfoKeyA(hSubKey, NULL, NULL, NULL, NULL,
275             &dwMaxSubkeyLen, NULL, NULL, &dwMaxValueLen, NULL, NULL, NULL);
276     if (ret) goto cleanup;
277
278     dwMaxSubkeyLen++;
279     dwMaxValueLen++;
280     dwMaxLen = max(dwMaxSubkeyLen, dwMaxValueLen);
281     if (dwMaxLen > sizeof(szNameBuf)/sizeof(CHAR))
282     {
283         /* Name too big: alloc a buffer for it */
284         if (!(lpszName = HeapAlloc( GetProcessHeap(), 0, dwMaxLen*sizeof(CHAR))))
285         {
286             ret = ERROR_NOT_ENOUGH_MEMORY;
287             goto cleanup;
288         }
289     }
290
291
292     /* Recursively delete all the subkeys */
293     while (TRUE)
294     {
295         dwSize = dwMaxLen;
296         if (RegEnumKeyExA(hSubKey, 0, lpszName, &dwSize, NULL,
297                           NULL, NULL, NULL)) break;
298
299         ret = myRegDeleteTreeA(hSubKey, lpszName);
300         if (ret) goto cleanup;
301     }
302
303     if (lpszSubKey)
304         ret = RegDeleteKeyA(hKey, lpszSubKey);
305     else
306         while (TRUE)
307         {
308             dwSize = dwMaxLen;
309             if (RegEnumValueA(hKey, 0, lpszName, &dwSize,
310                   NULL, NULL, NULL, NULL)) break;
311
312             ret = RegDeleteValueA(hKey, lpszName);
313             if (ret) goto cleanup;
314         }
315
316 cleanup:
317     /* Free buffer if allocated */
318     if (lpszName != szNameBuf)
319         HeapFree( GetProcessHeap(), 0, lpszName);
320     if(lpszSubKey)
321         RegCloseKey(hSubKey);
322     return ret;
323 }
324
325 static void delete_test_association(const char* extension)
326 {
327     char class[MAX_PATH];
328
329     sprintf(class, "shlexec%s", extension);
330     myRegDeleteTreeA(HKEY_CLASSES_ROOT, class);
331     myRegDeleteTreeA(HKEY_CLASSES_ROOT, extension);
332 }
333
334 static void create_test_verb_dde(const char* extension, const char* verb,
335                                  int rawcmd, const char* cmdtail, const char *ddeexec,
336                                  const char *application, const char *topic,
337                                  const char *ifexec)
338 {
339     HKEY hkey_shell, hkey_verb, hkey_cmd;
340     char shell[MAX_PATH];
341     char* cmd;
342     LONG rc;
343
344     sprintf(shell, "shlexec%s\\shell", extension);
345     rc=RegOpenKeyEx(HKEY_CLASSES_ROOT, shell, 0,
346                     KEY_CREATE_SUB_KEY, &hkey_shell);
347     assert(rc==ERROR_SUCCESS);
348     rc=RegCreateKeyEx(hkey_shell, verb, 0, NULL, 0, KEY_CREATE_SUB_KEY,
349                       NULL, &hkey_verb, NULL);
350     assert(rc==ERROR_SUCCESS);
351     rc=RegCreateKeyEx(hkey_verb, "command", 0, NULL, 0, KEY_SET_VALUE,
352                       NULL, &hkey_cmd, NULL);
353     assert(rc==ERROR_SUCCESS);
354
355     if (rawcmd)
356     {
357         rc=RegSetValueEx(hkey_cmd, NULL, 0, REG_SZ, (LPBYTE)cmdtail, strlen(cmdtail)+1);
358     }
359     else
360     {
361         cmd=HeapAlloc(GetProcessHeap(), 0, strlen(argv0)+10+strlen(child_file)+2+strlen(cmdtail)+1);
362         sprintf(cmd,"%s shlexec \"%s\" %s", argv0, child_file, cmdtail);
363         rc=RegSetValueEx(hkey_cmd, NULL, 0, REG_SZ, (LPBYTE)cmd, strlen(cmd)+1);
364         assert(rc==ERROR_SUCCESS);
365         HeapFree(GetProcessHeap(), 0, cmd);
366     }
367
368     if (ddeexec)
369     {
370         HKEY hkey_ddeexec, hkey_application, hkey_topic, hkey_ifexec;
371
372         rc=RegCreateKeyEx(hkey_verb, "ddeexec", 0, NULL, 0, KEY_SET_VALUE |
373                           KEY_CREATE_SUB_KEY, NULL, &hkey_ddeexec, NULL);
374         assert(rc==ERROR_SUCCESS);
375         rc=RegSetValueEx(hkey_ddeexec, NULL, 0, REG_SZ, (LPBYTE)ddeexec,
376                          strlen(ddeexec)+1);
377         assert(rc==ERROR_SUCCESS);
378         if (application)
379         {
380             rc=RegCreateKeyEx(hkey_ddeexec, "application", 0, NULL, 0, KEY_SET_VALUE,
381                               NULL, &hkey_application, NULL);
382             assert(rc==ERROR_SUCCESS);
383             rc=RegSetValueEx(hkey_application, NULL, 0, REG_SZ, (LPBYTE)application,
384                              strlen(application)+1);
385             assert(rc==ERROR_SUCCESS);
386             CloseHandle(hkey_application);
387         }
388         if (topic)
389         {
390             rc=RegCreateKeyEx(hkey_ddeexec, "topic", 0, NULL, 0, KEY_SET_VALUE,
391                               NULL, &hkey_topic, NULL);
392             assert(rc==ERROR_SUCCESS);
393             rc=RegSetValueEx(hkey_topic, NULL, 0, REG_SZ, (LPBYTE)topic,
394                              strlen(topic)+1);
395             assert(rc==ERROR_SUCCESS);
396             CloseHandle(hkey_topic);
397         }
398         if (ifexec)
399         {
400             rc=RegCreateKeyEx(hkey_ddeexec, "ifexec", 0, NULL, 0, KEY_SET_VALUE,
401                               NULL, &hkey_ifexec, NULL);
402             assert(rc==ERROR_SUCCESS);
403             rc=RegSetValueEx(hkey_ifexec, NULL, 0, REG_SZ, (LPBYTE)ifexec,
404                              strlen(ifexec)+1);
405             assert(rc==ERROR_SUCCESS);
406             CloseHandle(hkey_ifexec);
407         }
408         CloseHandle(hkey_ddeexec);
409     }
410
411     CloseHandle(hkey_shell);
412     CloseHandle(hkey_verb);
413     CloseHandle(hkey_cmd);
414 }
415
416 static void create_test_verb(const char* extension, const char* verb,
417                              int rawcmd, const char* cmdtail)
418 {
419     create_test_verb_dde(extension, verb, rawcmd, cmdtail, NULL, NULL,
420                          NULL, NULL);
421 }
422
423 /***
424  *
425  * Functions to check that the child process was started just right
426  * (borrowed from dlls/kernel32/tests/process.c)
427  *
428  ***/
429
430 static const char* encodeA(const char* str)
431 {
432     static char encoded[2*1024+1];
433     char*       ptr;
434     size_t      len,i;
435
436     if (!str) return "";
437     len = strlen(str) + 1;
438     if (len >= sizeof(encoded)/2)
439     {
440         fprintf(stderr, "string is too long!\n");
441         assert(0);
442     }
443     ptr = encoded;
444     for (i = 0; i < len; i++)
445         sprintf(&ptr[i * 2], "%02x", (unsigned char)str[i]);
446     ptr[2 * len] = '\0';
447     return ptr;
448 }
449
450 static unsigned decode_char(char c)
451 {
452     if (c >= '0' && c <= '9') return c - '0';
453     if (c >= 'a' && c <= 'f') return c - 'a' + 10;
454     assert(c >= 'A' && c <= 'F');
455     return c - 'A' + 10;
456 }
457
458 static char* decodeA(const char* str)
459 {
460     static char decoded[1024];
461     char*       ptr;
462     size_t      len,i;
463
464     len = strlen(str) / 2;
465     if (!len--) return NULL;
466     if (len >= sizeof(decoded))
467     {
468         fprintf(stderr, "string is too long!\n");
469         assert(0);
470     }
471     ptr = decoded;
472     for (i = 0; i < len; i++)
473         ptr[i] = (decode_char(str[2 * i]) << 4) | decode_char(str[2 * i + 1]);
474     ptr[len] = '\0';
475     return ptr;
476 }
477
478 static void     childPrintf(HANDLE h, const char* fmt, ...)
479 {
480     va_list     valist;
481     char        buffer[1024];
482     DWORD       w;
483
484     va_start(valist, fmt);
485     vsprintf(buffer, fmt, valist);
486     va_end(valist);
487     WriteFile(h, buffer, strlen(buffer), &w, NULL);
488 }
489
490 static DWORD ddeInst;
491 static HSZ hszTopic;
492 static char ddeExec[MAX_PATH], ddeApplication[MAX_PATH];
493 static BOOL post_quit_on_execute;
494
495 static HDDEDATA CALLBACK ddeCb(UINT uType, UINT uFmt, HCONV hConv,
496                                HSZ hsz1, HSZ hsz2, HDDEDATA hData,
497                                ULONG_PTR dwData1, ULONG_PTR dwData2)
498 {
499     DWORD size = 0;
500
501     if (winetest_debug > 2)
502         trace("dde_cb: %04x, %04x, %p, %p, %p, %p, %08lx, %08lx\n",
503               uType, uFmt, hConv, hsz1, hsz2, hData, dwData1, dwData2);
504
505     switch (uType)
506     {
507         case XTYP_CONNECT:
508             if (!DdeCmpStringHandles(hsz1, hszTopic))
509             {
510                 size = DdeQueryString(ddeInst, hsz2, ddeApplication, MAX_PATH, CP_WINANSI);
511                 assert(size < MAX_PATH);
512                 return (HDDEDATA)TRUE;
513             }
514             return (HDDEDATA)FALSE;
515
516         case XTYP_EXECUTE:
517             size = DdeGetData(hData, (LPBYTE)ddeExec, MAX_PATH, 0L);
518             assert(size < MAX_PATH);
519             DdeFreeDataHandle(hData);
520             if (post_quit_on_execute)
521                 PostQuitMessage(0);
522             return (HDDEDATA)DDE_FACK;
523
524         default:
525             return NULL;
526     }
527 }
528
529 /*
530  * This is just to make sure the child won't run forever stuck in a GetMessage()
531  * loop when DDE fails for some reason.
532  */
533 static void CALLBACK childTimeout(HWND wnd, UINT msg, UINT_PTR timer, DWORD time)
534 {
535     trace("childTimeout called\n");
536
537     PostQuitMessage(0);
538 }
539
540 static void doChild(int argc, char** argv)
541 {
542     char *filename, longpath[MAX_PATH] = "";
543     HANDLE hFile, map;
544     int i;
545     int rc;
546     HSZ hszApplication;
547     UINT_PTR timer;
548     HANDLE dde_ready;
549     MSG msg;
550     char *shared_block;
551
552     filename=argv[2];
553     hFile=CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0);
554     if (hFile == INVALID_HANDLE_VALUE)
555         return;
556
557     /* Arguments */
558     childPrintf(hFile, "[Arguments]\r\n");
559     if (winetest_debug > 2)
560         trace("argcA=%d\n", argc);
561     childPrintf(hFile, "argcA=%d\r\n", argc);
562     for (i = 0; i < argc; i++)
563     {
564         if (winetest_debug > 2)
565             trace("argvA%d=%s\n", i, argv[i]);
566         childPrintf(hFile, "argvA%d=%s\r\n", i, encodeA(argv[i]));
567     }
568     GetModuleFileNameA(GetModuleHandleA(NULL), longpath, MAX_PATH);
569     childPrintf(hFile, "longPath=%s\r\n", encodeA(longpath));
570
571     map = OpenFileMappingA(FILE_MAP_READ, FALSE, "winetest_shlexec_dde_map");
572     if (map != NULL)
573     {
574         shared_block = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 4096);
575         CloseHandle(map);
576         if (shared_block[0] != '\0' || shared_block[1] != '\0')
577         {
578             post_quit_on_execute = TRUE;
579             ddeInst = 0;
580             rc = DdeInitializeA(&ddeInst, ddeCb, CBF_SKIP_ALLNOTIFICATIONS | CBF_FAIL_ADVISES |
581                                 CBF_FAIL_POKES | CBF_FAIL_REQUESTS, 0L);
582             assert(rc == DMLERR_NO_ERROR);
583             hszApplication = DdeCreateStringHandleA(ddeInst, shared_block, CP_WINANSI);
584             hszTopic = DdeCreateStringHandleA(ddeInst, shared_block + strlen(shared_block) + 1, CP_WINANSI);
585             assert(hszApplication && hszTopic);
586             assert(DdeNameService(ddeInst, hszApplication, 0L, DNS_REGISTER | DNS_FILTEROFF));
587
588             timer = SetTimer(NULL, 0, 2500, childTimeout);
589
590             dde_ready = OpenEvent(EVENT_MODIFY_STATE, FALSE, "winetest_shlexec_dde_ready");
591             SetEvent(dde_ready);
592             CloseHandle(dde_ready);
593
594             while (GetMessage(&msg, NULL, 0, 0))
595                 DispatchMessage(&msg);
596
597             Sleep(500);
598             KillTimer(NULL, timer);
599             assert(DdeNameService(ddeInst, hszApplication, 0L, DNS_UNREGISTER));
600             assert(DdeFreeStringHandle(ddeInst, hszTopic));
601             assert(DdeFreeStringHandle(ddeInst, hszApplication));
602             assert(DdeUninitialize(ddeInst));
603         }
604         else
605         {
606             dde_ready = OpenEvent(EVENT_MODIFY_STATE, FALSE, "winetest_shlexec_dde_ready");
607             SetEvent(dde_ready);
608             CloseHandle(dde_ready);
609         }
610
611         UnmapViewOfFile(shared_block);
612
613         childPrintf(hFile, "ddeExec=%s\r\n", encodeA(ddeExec));
614     }
615
616     CloseHandle(hFile);
617
618     init_event(filename);
619     SetEvent(hEvent);
620     CloseHandle(hEvent);
621 }
622
623 static char* getChildString(const char* sect, const char* key)
624 {
625     char        buf[1024];
626     char*       ret;
627
628     GetPrivateProfileStringA(sect, key, "-", buf, sizeof(buf), child_file);
629     if (buf[0] == '\0' || (buf[0] == '-' && buf[1] == '\0')) return NULL;
630     assert(!(strlen(buf) & 1));
631     ret = decodeA(buf);
632     return ret;
633 }
634
635 static void dump_child(void)
636 {
637     if (winetest_debug > 1)
638     {
639         char key[18];
640         char* str;
641         int i, c;
642
643         c=GetPrivateProfileIntA("Arguments", "argcA", -1, child_file);
644         trace("argcA=%d\n",c);
645         for (i=0;i<c;i++)
646         {
647             sprintf(key, "argvA%d", i);
648             str=getChildString("Arguments", key);
649             trace("%s=%s\n", key, str);
650         }
651     }
652 }
653
654 static int StrCmpPath(const char* s1, const char* s2)
655 {
656     if (!s1 && !s2) return 0;
657     if (!s2) return 1;
658     if (!s1) return -1;
659     while (*s1)
660     {
661         if (!*s2)
662         {
663             if (*s1=='.')
664                 s1++;
665             return (*s1-*s2);
666         }
667         if ((*s1=='/' || *s1=='\\') && (*s2=='/' || *s2=='\\'))
668         {
669             while (*s1=='/' || *s1=='\\')
670                 s1++;
671             while (*s2=='/' || *s2=='\\')
672                 s2++;
673         }
674         else if (toupper(*s1)==toupper(*s2))
675         {
676             s1++;
677             s2++;
678         }
679         else
680         {
681             return (*s1-*s2);
682         }
683     }
684     if (*s2=='.')
685         s2++;
686     if (*s2)
687         return -1;
688     return 0;
689 }
690
691 static void _okChildString(const char* file, int line, const char* key, const char* expected)
692 {
693     char* result;
694     result=getChildString("Arguments", key);
695     if (!result)
696     {
697         ok_(file, line)(FALSE, "%s expected '%s', but key not found or empty\n", key, expected);
698         return;
699     }
700     ok_(file, line)(lstrcmpiA(result, expected) == 0,
701                     "%s expected '%s', got '%s'\n", key, expected, result);
702 }
703
704 static void _okChildPath(const char* file, int line, const char* key, const char* expected)
705 {
706     char* result;
707     result=getChildString("Arguments", key);
708     if (!result)
709     {
710         ok_(file, line)(FALSE, "%s expected '%s', but key not found or empty\n", key, expected);
711         return;
712     }
713     ok_(file, line)(StrCmpPath(result, expected) == 0,
714                     "%s expected '%s', got '%s'\n", key, expected, result);
715 }
716
717 static void _okChildInt(const char* file, int line, const char* key, int expected)
718 {
719     INT result;
720     result=GetPrivateProfileIntA("Arguments", key, expected, child_file);
721     ok_(file, line)(result == expected,
722                     "%s expected %d, but got %d\n", key, expected, result);
723 }
724
725 #define okChildString(key, expected) _okChildString(__FILE__, __LINE__, (key), (expected))
726 #define okChildPath(key, expected) _okChildPath(__FILE__, __LINE__, (key), (expected))
727 #define okChildInt(key, expected)    _okChildInt(__FILE__, __LINE__, (key), (expected))
728
729 /***
730  *
731  * GetLongPathNameA equivalent that supports Win95 and WinNT
732  *
733  ***/
734
735 static DWORD get_long_path_name(const char* shortpath, char* longpath, DWORD longlen)
736 {
737     char tmplongpath[MAX_PATH];
738     const char* p;
739     DWORD sp = 0, lp = 0;
740     DWORD tmplen;
741     WIN32_FIND_DATAA wfd;
742     HANDLE goit;
743
744     if (!shortpath || !shortpath[0])
745         return 0;
746
747     if (shortpath[1] == ':')
748     {
749         tmplongpath[0] = shortpath[0];
750         tmplongpath[1] = ':';
751         lp = sp = 2;
752     }
753
754     while (shortpath[sp])
755     {
756         /* check for path delimiters and reproduce them */
757         if (shortpath[sp] == '\\' || shortpath[sp] == '/')
758         {
759             if (!lp || tmplongpath[lp-1] != '\\')
760             {
761                 /* strip double "\\" */
762                 tmplongpath[lp++] = '\\';
763             }
764             tmplongpath[lp] = 0; /* terminate string */
765             sp++;
766             continue;
767         }
768
769         p = shortpath + sp;
770         if (sp == 0 && p[0] == '.' && (p[1] == '/' || p[1] == '\\'))
771         {
772             tmplongpath[lp++] = *p++;
773             tmplongpath[lp++] = *p++;
774         }
775         for (; *p && *p != '/' && *p != '\\'; p++);
776         tmplen = p - (shortpath + sp);
777         lstrcpyn(tmplongpath + lp, shortpath + sp, tmplen + 1);
778         /* Check if the file exists and use the existing file name */
779         goit = FindFirstFileA(tmplongpath, &wfd);
780         if (goit == INVALID_HANDLE_VALUE)
781             return 0;
782         FindClose(goit);
783         strcpy(tmplongpath + lp, wfd.cFileName);
784         lp += strlen(tmplongpath + lp);
785         sp += tmplen;
786     }
787     tmplen = strlen(shortpath) - 1;
788     if ((shortpath[tmplen] == '/' || shortpath[tmplen] == '\\') &&
789         (tmplongpath[lp - 1] != '/' && tmplongpath[lp - 1] != '\\'))
790         tmplongpath[lp++] = shortpath[tmplen];
791     tmplongpath[lp] = 0;
792
793     tmplen = strlen(tmplongpath) + 1;
794     if (tmplen <= longlen)
795     {
796         strcpy(longpath, tmplongpath);
797         tmplen--; /* length without 0 */
798     }
799
800     return tmplen;
801 }
802
803 /***
804  *
805  * PathFindFileNameA equivalent that supports WinNT
806  *
807  ***/
808
809 static LPSTR path_find_file_name(LPCSTR lpszPath)
810 {
811   LPCSTR lastSlash = lpszPath;
812
813   while (lpszPath && *lpszPath)
814   {
815     if ((*lpszPath == '\\' || *lpszPath == '/' || *lpszPath == ':') &&
816         lpszPath[1] && lpszPath[1] != '\\' && lpszPath[1] != '/')
817       lastSlash = lpszPath + 1;
818     lpszPath = CharNext(lpszPath);
819   }
820   return (LPSTR)lastSlash;
821 }
822
823 /***
824  *
825  * Tests
826  *
827  ***/
828
829 static const char* testfiles[]=
830 {
831     "%s\\test file.shlexec",
832     "%s\\%%nasty%% $file.shlexec",
833     "%s\\test file.noassoc",
834     "%s\\test file.noassoc.shlexec",
835     "%s\\test file.shlexec.noassoc",
836     "%s\\test_shortcut_shlexec.lnk",
837     "%s\\test_shortcut_exe.lnk",
838     "%s\\test file.shl",
839     "%s\\test file.shlfoo",
840     "%s\\test file.sfe",
841     "%s\\masked file.shlexec",
842     "%s\\masked",
843     "%s\\test file.sde",
844     "%s\\test file.exe",
845     "%s\\test2.exe",
846     "%s\\simple.shlexec",
847     "%s\\drawback_file.noassoc",
848     "%s\\drawback_file.noassoc foo.shlexec",
849     "%s\\drawback_nonexist.noassoc foo.shlexec",
850     NULL
851 };
852
853 typedef struct
854 {
855     const char* verb;
856     const char* basename;
857     int todo;
858     int rc;
859 } filename_tests_t;
860
861 static filename_tests_t filename_tests[]=
862 {
863     /* Test bad / nonexistent filenames */
864     {NULL,           "%s\\nonexistent.shlexec", 0x0, SE_ERR_FNF},
865     {NULL,           "%s\\nonexistent.noassoc", 0x0, SE_ERR_FNF},
866
867     /* Standard tests */
868     {NULL,           "%s\\test file.shlexec",   0x0, 33},
869     {NULL,           "%s\\test file.shlexec.",  0x0, 33},
870     {NULL,           "%s\\%%nasty%% $file.shlexec", 0x0, 33},
871     {NULL,           "%s/test file.shlexec",    0x0, 33},
872
873     /* Test filenames with no association */
874     {NULL,           "%s\\test file.noassoc",   0x0,  SE_ERR_NOASSOC},
875
876     /* Test double extensions */
877     {NULL,           "%s\\test file.noassoc.shlexec", 0x0, 33},
878     {NULL,           "%s\\test file.shlexec.noassoc", 0x0, SE_ERR_NOASSOC},
879
880     /* Test alternate verbs */
881     {"LowerL",       "%s\\nonexistent.shlexec", 0x0, SE_ERR_FNF},
882     {"LowerL",       "%s\\test file.noassoc",   0x0,  SE_ERR_NOASSOC},
883
884     {"QuotedLowerL", "%s\\test file.shlexec",   0x0, 33},
885     {"QuotedUpperL", "%s\\test file.shlexec",   0x0, 33},
886
887     /* Test file masked due to space */
888     {NULL,           "%s\\masked file.shlexec",   0x1, 33},
889     /* Test if quoting prevents the masking */
890     {NULL,           "%s\\masked file.shlexec",   0x40, 33},
891
892     {NULL, NULL, 0}
893 };
894
895 static filename_tests_t noquotes_tests[]=
896 {
897     /* Test unquoted '%1' thingies */
898     {"NoQuotes",     "%s\\test file.shlexec",   0xa, 33},
899     {"LowerL",       "%s\\test file.shlexec",   0xa, 33},
900     {"UpperL",       "%s\\test file.shlexec",   0xa, 33},
901
902     {NULL, NULL, 0}
903 };
904
905 static void test_lpFile_parsed(void)
906 {
907     /* basename tmpdir */
908     const char* shorttmpdir;
909     const char *testfile;
910     char fileA[MAX_PATH];
911     INT_PTR rc;
912
913     GetTempPathA(sizeof(fileA), fileA);
914     shorttmpdir = tmpdir + strlen(fileA);
915
916     /* ensure tmpdir is in %TEMP%: GetTempPath() can succeed even if TEMP is undefined */
917     SetEnvironmentVariableA("TEMP", fileA);
918
919     /* existing "drawback_file.noassoc" prevents finding "drawback_file.noassoc foo.shlexec" on wine */
920     testfile = "%s\\drawback_file.noassoc foo.shlexec";
921     sprintf(fileA, testfile, tmpdir);
922     rc=shell_execute(NULL, fileA, NULL, NULL);
923     todo_wine {
924         ok(rc>32,
925             "expected success (33), got %s (%lu), lpFile: %s\n",
926             rc > 32 ? "success" : "failure", rc, fileA
927             );
928     }
929
930     /* if quoted, existing "drawback_file.noassoc" not prevents finding "drawback_file.noassoc foo.shlexec" on wine */
931     testfile = "\"%s\\drawback_file.noassoc foo.shlexec\"";
932     sprintf(fileA, testfile, tmpdir);
933     rc=shell_execute(NULL, fileA, NULL, NULL);
934     ok(rc>32 || broken(rc == 2) /* Win95/NT4 */,
935         "expected success (33), got %s (%lu), lpFile: %s\n",
936         rc > 32 ? "success" : "failure", rc, fileA
937         );
938
939     /* error should be 2, not 31 */
940     testfile = "\"%s\\drawback_file.noassoc\" foo.shlexec";
941     sprintf(fileA, testfile, tmpdir);
942     rc=shell_execute(NULL, fileA, NULL, NULL);
943     ok(rc==2,
944         "expected failure (2), got %s (%lu), lpFile: %s\n",
945         rc > 32 ? "success" : "failure", rc, fileA
946         );
947
948     /* ""command"" not works on wine (and real win9x and w2k) */
949     testfile = "\"\"%s\\simple.shlexec\"\"";
950     sprintf(fileA, testfile, tmpdir);
951     rc=shell_execute(NULL, fileA, NULL, NULL);
952     todo_wine {
953         ok(rc>32 || broken(rc == 2) /* Win9x/2000 */,
954             "expected success (33), got %s (%lu), lpFile: %s\n",
955             rc > 32 ? "success" : "failure", rc, fileA
956             );
957     }
958
959     /* nonexisting "drawback_nonexist.noassoc" not prevents finding "drawback_nonexist.noassoc foo.shlexec" on wine */
960     testfile = "%s\\drawback_nonexist.noassoc foo.shlexec";
961     sprintf(fileA, testfile, tmpdir);
962     rc=shell_execute(NULL, fileA, NULL, NULL);
963     ok(rc>32,
964         "expected success (33), got %s (%lu), lpFile: %s\n",
965         rc > 32 ? "success" : "failure", rc, fileA
966         );
967
968     /* is SEE_MASK_DOENVSUBST default flag? Should only be when XP emulates 9x (XP bug or real 95 or ME behavior ?) */
969     testfile = "%%TEMP%%\\%s\\simple.shlexec";
970     sprintf(fileA, testfile, shorttmpdir);
971     rc=shell_execute(NULL, fileA, NULL, NULL);
972     todo_wine {
973         ok(rc==2,
974             "expected failure (2), got %s (%lu), lpFile: %s\n",
975             rc > 32 ? "success" : "failure", rc, fileA
976             );
977     }
978
979     /* quoted */
980     testfile = "\"%%TEMP%%\\%s\\simple.shlexec\"";
981     sprintf(fileA, testfile, shorttmpdir);
982     rc=shell_execute(NULL, fileA, NULL, NULL);
983     todo_wine {
984         ok(rc==2,
985             "expected failure (2), got %s (%lu), lpFile: %s\n",
986             rc > 32 ? "success" : "failure", rc, fileA
987             );
988     }
989
990     /* test SEE_MASK_DOENVSUBST works */
991     testfile = "%%TEMP%%\\%s\\simple.shlexec";
992     sprintf(fileA, testfile, shorttmpdir);
993     rc=shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI, NULL, fileA, NULL, NULL);
994     ok(rc>32,
995         "expected success (33), got %s (%lu), lpFile: %s\n",
996         rc > 32 ? "success" : "failure", rc, fileA
997         );
998
999     /* quoted lpFile not works only on real win95 and nt4 */
1000     testfile = "\"%%TEMP%%\\%s\\simple.shlexec\"";
1001     sprintf(fileA, testfile, shorttmpdir);
1002     rc=shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI, NULL, fileA, NULL, NULL);
1003     ok(rc>32 || broken(rc == 2) /* Win95/NT4 */,
1004         "expected success (33), got %s (%lu), lpFile: %s\n",
1005         rc > 32 ? "success" : "failure", rc, fileA
1006         );
1007
1008 }
1009
1010 static void test_argify(void)
1011 {
1012     char fileA[MAX_PATH];
1013     INT_PTR rc;
1014
1015     sprintf(fileA, "%s\\test file.shlexec", tmpdir);
1016
1017     /* %2 */
1018     rc=shell_execute("NoQuotesParam2", fileA, "a b", NULL);
1019     ok(rc>32,
1020         "expected success (33), got %s (%lu), lpFile: %s\n",
1021         rc > 32 ? "success" : "failure", rc, fileA
1022         );
1023     if (rc>32)
1024     {
1025         okChildInt("argcA", 5);
1026         okChildString("argvA4", "a");
1027     }
1028
1029     /* %2 */
1030     /* '"a"""'   -> 'a"' */
1031     rc=shell_execute("NoQuotesParam2", fileA, "\"a:\"\"some string\"\"\"", NULL);
1032     ok(rc>32,
1033         "expected success (33), got %s (%lu), lpFile: %s\n",
1034         rc > 32 ? "success" : "failure", rc, fileA
1035         );
1036     if (rc>32)
1037     {
1038         okChildInt("argcA", 5);
1039         todo_wine {
1040             okChildString("argvA4", "a:some string");
1041         }
1042     }
1043
1044     /* %2 */
1045     /* backslash isn't escape char
1046      * '"a\""'   -> '"a\""' */
1047     rc=shell_execute("NoQuotesParam2", fileA, "\"a:\\\"some string\\\"\"", NULL);
1048     ok(rc>32,
1049         "expected success (33), got %s (%lu), lpFile: %s\n",
1050         rc > 32 ? "success" : "failure", rc, fileA
1051         );
1052     if (rc>32)
1053     {
1054         okChildInt("argcA", 5);
1055         todo_wine {
1056             okChildString("argvA4", "a:\\");
1057         }
1058     }
1059
1060     /* "%2" */
1061     /* \t isn't whitespace */
1062     rc=shell_execute("QuotedParam2", fileA, "a\tb c", NULL);
1063     ok(rc>32,
1064         "expected success (33), got %s (%lu), lpFile: %s\n",
1065         rc > 32 ? "success" : "failure", rc, fileA
1066         );
1067     if (rc>32)
1068     {
1069         okChildInt("argcA", 5);
1070         todo_wine {
1071             okChildString("argvA4", "a\tb");
1072         }
1073     }
1074
1075     /* %* */
1076     rc=shell_execute("NoQuotesAllParams", fileA, "a b c d e f g h", NULL);
1077     ok(rc>32,
1078         "expected success (33), got %s (%lu), lpFile: %s\n",
1079         rc > 32 ? "success" : "failure", rc, fileA
1080         );
1081     if (rc>32)
1082     {
1083         todo_wine {
1084             okChildInt("argcA", 12);
1085             okChildString("argvA4", "a");
1086             okChildString("argvA11", "h");
1087         }
1088     }
1089
1090     /* %* can sometimes contain only whitespaces and no args */
1091     rc=shell_execute("QuotedAllParams", fileA, "   ", NULL);
1092     ok(rc>32,
1093         "expected success (33), got %s (%lu), lpFile: %s\n",
1094         rc > 32 ? "success" : "failure", rc, fileA
1095         );
1096     if (rc>32)
1097     {
1098         todo_wine {
1099             okChildInt("argcA", 5);
1100             okChildString("argvA4", "   ");
1101         }
1102     }
1103
1104     /* %~3 */
1105     rc=shell_execute("NoQuotesParams345etc", fileA, "a b c d e f g h", NULL);
1106     ok(rc>32,
1107         "expected success (33), got %s (%lu), lpFile: %s\n",
1108         rc > 32 ? "success" : "failure", rc, fileA
1109         );
1110     if (rc>32)
1111     {
1112         todo_wine {
1113             okChildInt("argcA", 11);
1114             okChildString("argvA4", "b");
1115             okChildString("argvA10", "h");
1116         }
1117     }
1118
1119     /* %~3 is rest of command line starting with whitespaces after 2nd arg */
1120     rc=shell_execute("QuotedParams345etc", fileA, "a    ", NULL);
1121     ok(rc>32,
1122         "expected success (33), got %s (%lu), lpFile: %s\n",
1123         rc > 32 ? "success" : "failure", rc, fileA
1124         );
1125     if (rc>32)
1126     {
1127         okChildInt("argcA", 5);
1128         todo_wine {
1129             okChildString("argvA4", "    ");
1130         }
1131     }
1132
1133 }
1134
1135 static void test_filename(void)
1136 {
1137     char filename[MAX_PATH];
1138     const filename_tests_t* test;
1139     char* c;
1140     int rc;
1141
1142     test=filename_tests;
1143     while (test->basename)
1144     {
1145         BOOL quotedfile = FALSE;
1146
1147         if (skip_noassoc_tests && test->rc == SE_ERR_NOASSOC)
1148         {
1149             win_skip("Skipping shellexecute of file with unassociated extension\n");
1150             test++;
1151             continue;
1152         }
1153
1154         sprintf(filename, test->basename, tmpdir);
1155         if (strchr(filename, '/'))
1156         {
1157             c=filename;
1158             while (*c)
1159             {
1160                 if (*c=='\\')
1161                     *c='/';
1162                 c++;
1163             }
1164         }
1165         if ((test->todo & 0x40)==0)
1166         {
1167             rc=shell_execute(test->verb, filename, NULL, NULL);
1168         }
1169         else
1170         {
1171             char quoted[MAX_PATH + 2];
1172
1173             quotedfile = TRUE;
1174             sprintf(quoted, "\"%s\"", filename);
1175             rc=shell_execute(test->verb, quoted, NULL, NULL);
1176         }
1177         if (rc > 32)
1178             rc=33;
1179         if ((test->todo & 0x1)==0)
1180         {
1181             ok(rc==test->rc ||
1182                broken(quotedfile && rc == 2), /* NT4 */
1183                "%s failed: rc=%d err=%u\n", shell_call,
1184                rc, GetLastError());
1185         }
1186         else todo_wine
1187         {
1188             ok(rc==test->rc, "%s failed: rc=%d err=%u\n", shell_call,
1189                rc, GetLastError());
1190         }
1191         if (rc == 33)
1192         {
1193             const char* verb;
1194             if ((test->todo & 0x2)==0)
1195             {
1196                 okChildInt("argcA", 5);
1197             }
1198             else todo_wine
1199             {
1200                 okChildInt("argcA", 5);
1201             }
1202             verb=(test->verb ? test->verb : "Open");
1203             if ((test->todo & 0x4)==0)
1204             {
1205                 okChildString("argvA3", verb);
1206             }
1207             else todo_wine
1208             {
1209                 okChildString("argvA3", verb);
1210             }
1211             if ((test->todo & 0x8)==0)
1212             {
1213                 okChildPath("argvA4", filename);
1214             }
1215             else todo_wine
1216             {
1217                 okChildPath("argvA4", filename);
1218             }
1219         }
1220         test++;
1221     }
1222
1223     test=noquotes_tests;
1224     while (test->basename)
1225     {
1226         sprintf(filename, test->basename, tmpdir);
1227         rc=shell_execute(test->verb, filename, NULL, NULL);
1228         if (rc > 32)
1229             rc=33;
1230         if ((test->todo & 0x1)==0)
1231         {
1232             ok(rc==test->rc, "%s failed: rc=%d err=%u\n", shell_call,
1233                rc, GetLastError());
1234         }
1235         else todo_wine
1236         {
1237             ok(rc==test->rc, "%s failed: rc=%d err=%u\n", shell_call,
1238                rc, GetLastError());
1239         }
1240         if (rc==0)
1241         {
1242             int count;
1243             const char* verb;
1244             char* str;
1245
1246             verb=(test->verb ? test->verb : "Open");
1247             if ((test->todo & 0x4)==0)
1248             {
1249                 okChildString("argvA3", verb);
1250             }
1251             else todo_wine
1252             {
1253                 okChildString("argvA3", verb);
1254             }
1255
1256             count=4;
1257             str=filename;
1258             while (1)
1259             {
1260                 char attrib[18];
1261                 char* space;
1262                 space=strchr(str, ' ');
1263                 if (space)
1264                     *space='\0';
1265                 sprintf(attrib, "argvA%d", count);
1266                 if ((test->todo & 0x8)==0)
1267                 {
1268                     okChildPath(attrib, str);
1269                 }
1270                 else todo_wine
1271                 {
1272                     okChildPath(attrib, str);
1273                 }
1274                 count++;
1275                 if (!space)
1276                     break;
1277                 str=space+1;
1278             }
1279             if ((test->todo & 0x2)==0)
1280             {
1281                 okChildInt("argcA", count);
1282             }
1283             else todo_wine
1284             {
1285                 okChildInt("argcA", count);
1286             }
1287         }
1288         test++;
1289     }
1290
1291     if (dllver.dwMajorVersion != 0)
1292     {
1293         /* The more recent versions of shell32.dll accept quoted filenames
1294          * while older ones (e.g. 4.00) don't. Still we want to test this
1295          * because IE 6 depends on the new behavior.
1296          * One day we may need to check the exact version of the dll but for
1297          * now making sure DllGetVersion() is present is sufficient.
1298          */
1299         sprintf(filename, "\"%s\\test file.shlexec\"", tmpdir);
1300         rc=shell_execute(NULL, filename, NULL, NULL);
1301         ok(rc > 32, "%s failed: rc=%d err=%u\n", shell_call, rc,
1302            GetLastError());
1303         okChildInt("argcA", 5);
1304         okChildString("argvA3", "Open");
1305         sprintf(filename, "%s\\test file.shlexec", tmpdir);
1306         okChildPath("argvA4", filename);
1307     }
1308 }
1309
1310 typedef struct
1311 {
1312     const char* urlprefix;
1313     const char* basename;
1314     int flags;
1315     int todo;
1316 } fileurl_tests_t;
1317
1318 #define URL_SUCCESS  0x1
1319 #define USE_COLON    0x2
1320 #define USE_BSLASH   0x4
1321
1322 static fileurl_tests_t fileurl_tests[]=
1323 {
1324     /* How many slashes does it take... */
1325     {"file:", "%s\\test file.shlexec", URL_SUCCESS, 0x1},
1326     {"file:/", "%s\\test file.shlexec", URL_SUCCESS, 0x1},
1327     {"file://", "%s\\test file.shlexec", URL_SUCCESS, 0x1},
1328     {"file:///", "%s\\test file.shlexec", URL_SUCCESS, 0x1},
1329     {"File:///", "%s\\test file.shlexec", URL_SUCCESS, 0x1},
1330     {"file:////", "%s\\test file.shlexec", URL_SUCCESS, 0x1},
1331     {"file://///", "%s\\test file.shlexec", 0, 0x1},
1332
1333     /* Test with Windows-style paths */
1334     {"file:///", "%s\\test file.shlexec", URL_SUCCESS | USE_COLON, 0x1},
1335     {"file:///", "%s\\test file.shlexec", URL_SUCCESS | USE_BSLASH, 0x1},
1336
1337     /* Check handling of hostnames */
1338     {"file://localhost/", "%s\\test file.shlexec", URL_SUCCESS, 0x1},
1339     {"file://localhost:80/", "%s\\test file.shlexec", 0, 0x1},
1340     {"file://LocalHost/", "%s\\test file.shlexec", URL_SUCCESS, 0x1},
1341     {"file://127.0.0.1/", "%s\\test file.shlexec", 0, 0x1},
1342     {"file://::1/", "%s\\test file.shlexec", 0, 0x1},
1343     {"file://notahost/", "%s\\test file.shlexec", 0, 0x1},
1344
1345     /* Environment variables are not expanded in URLs */
1346     {"%urlprefix%", "%s\\test file.shlexec", 0, 0x1},
1347     {"file:///", "%s\\%%urlenvvar%% file.shlexec", 0, 0x1},
1348
1349     {NULL, NULL, 0, 0}
1350 };
1351
1352 static void test_fileurl(void)
1353 {
1354     char filename[MAX_PATH], fileurl[MAX_PATH], longtmpdir[MAX_PATH];
1355     char command[MAX_PATH];
1356     const fileurl_tests_t* test;
1357     char *s;
1358     INT_PTR rc;
1359
1360     rc = (INT_PTR)ShellExecute(NULL, NULL, "file:///nosuchfile.shlexec", NULL, NULL, SW_SHOWNORMAL);
1361     if (rc > 32)
1362     {
1363         win_skip("shell32 is too old (likely < 4.72). Skipping the file URL tests\n");
1364         return;
1365     }
1366
1367     get_long_path_name(tmpdir, longtmpdir, sizeof(longtmpdir)/sizeof(*longtmpdir));
1368     SetEnvironmentVariable("urlprefix", "file:///");
1369     SetEnvironmentVariable("urlenvvar", "test");
1370
1371     test=fileurl_tests;
1372     while (test->basename)
1373     {
1374         /* Build the file URL */
1375         sprintf(filename, test->basename, longtmpdir);
1376         strcpy(fileurl, test->urlprefix);
1377         strcat(fileurl, filename);
1378         s = fileurl + strlen(test->urlprefix);
1379         while (*s)
1380         {
1381             if (!(test->flags & USE_COLON) && *s == ':')
1382                 *s = '|';
1383             else if (!(test->flags & USE_BSLASH) && *s == '\\')
1384                 *s = '/';
1385             s++;
1386         }
1387
1388         /* Test it first with FindExecutable() */
1389         rc = (INT_PTR)FindExecutableA(fileurl, NULL, command);
1390         ok(rc == SE_ERR_FNF, "FindExecutable(%s) failed: bad rc=%lu\n", fileurl, rc);
1391
1392         /* Then ShellExecute() */
1393         rc = shell_execute(NULL, fileurl, NULL, NULL);
1394         if (bad_shellexecute)
1395         {
1396             win_skip("shell32 is too old (likely 4.72). Skipping the file URL tests\n");
1397             break;
1398         }
1399         if (test->flags & URL_SUCCESS)
1400         {
1401             if ((test->todo & 0x1) == 0)
1402                 ok(rc > 32, "%s failed: bad rc=%lu\n", shell_call, rc);
1403             else todo_wine
1404                 ok(rc > 32, "%s failed: bad rc=%lu\n", shell_call, rc);
1405         }
1406         else
1407         {
1408             if ((test->todo & 0x1) == 0)
1409                 ok(rc == SE_ERR_FNF || rc == SE_ERR_PNF ||
1410                    broken(rc == SE_ERR_ACCESSDENIED) /* win2000 */,
1411                    "%s failed: bad rc=%lu\n", shell_call, rc);
1412             else todo_wine
1413                 ok(rc == SE_ERR_FNF || rc == SE_ERR_PNF ||
1414                    broken(rc == SE_ERR_ACCESSDENIED) /* win2000 */,
1415                    "%s failed: bad rc=%lu\n", shell_call, rc);
1416         }
1417         if (rc == 33)
1418         {
1419             if ((test->todo & 0x2) == 0)
1420                 okChildInt("argcA", 5);
1421             else todo_wine
1422                 okChildInt("argcA", 5);
1423
1424             if ((test->todo & 0x4) == 0)
1425                 okChildString("argvA3", "Open");
1426             else todo_wine
1427                 okChildString("argvA3", "Open");
1428
1429             if ((test->todo & 0x8) == 0)
1430                 okChildPath("argvA4", filename);
1431             else todo_wine
1432                 okChildPath("argvA4", filename);
1433         }
1434         test++;
1435     }
1436
1437     SetEnvironmentVariable("urlprefix", NULL);
1438     SetEnvironmentVariable("urlenvvar", NULL);
1439 }
1440
1441 static void test_find_executable(void)
1442 {
1443     char notepad_path[MAX_PATH];
1444     char filename[MAX_PATH];
1445     char command[MAX_PATH];
1446     const filename_tests_t* test;
1447     INT_PTR rc;
1448
1449     if (!create_test_association(".sfe"))
1450     {
1451         skip("Unable to create association for '.sfe'\n");
1452         return;
1453     }
1454     create_test_verb(".sfe", "Open", 1, "%1");
1455
1456     /* Don't test FindExecutable(..., NULL), it always crashes */
1457
1458     strcpy(command, "your word");
1459     if (0) /* Can crash on Vista! */
1460     {
1461     rc=(INT_PTR)FindExecutableA(NULL, NULL, command);
1462     ok(rc == SE_ERR_FNF || rc > 32 /* nt4 */, "FindExecutable(NULL) returned %ld\n", rc);
1463     ok(strcmp(command, "your word") != 0, "FindExecutable(NULL) returned command=[%s]\n", command);
1464     }
1465
1466     GetSystemDirectoryA( notepad_path, MAX_PATH );
1467     strcat( notepad_path, "\\notepad.exe" );
1468
1469     /* Search for something that should be in the system-wide search path (no default directory) */
1470     strcpy(command, "your word");
1471     rc=(INT_PTR)FindExecutableA("notepad.exe", NULL, command);
1472     ok(rc > 32, "FindExecutable(%s) returned %ld\n", "notepad.exe", rc);
1473     ok(strcasecmp(command, notepad_path) == 0, "FindExecutable(%s) returned command=[%s]\n", "notepad.exe", command);
1474
1475     /* Search for something that should be in the system-wide search path (with default directory) */
1476     strcpy(command, "your word");
1477     rc=(INT_PTR)FindExecutableA("notepad.exe", tmpdir, command);
1478     ok(rc > 32, "FindExecutable(%s) returned %ld\n", "notepad.exe", rc);
1479     ok(strcasecmp(command, notepad_path) == 0, "FindExecutable(%s) returned command=[%s]\n", "notepad.exe", command);
1480
1481     strcpy(command, "your word");
1482     rc=(INT_PTR)FindExecutableA(tmpdir, NULL, command);
1483     ok(rc == SE_ERR_NOASSOC /* >= win2000 */ || rc > 32 /* win98, nt4 */, "FindExecutable(NULL) returned %ld\n", rc);
1484     ok(strcmp(command, "your word") != 0, "FindExecutable(NULL) returned command=[%s]\n", command);
1485
1486     sprintf(filename, "%s\\test file.sfe", tmpdir);
1487     rc=(INT_PTR)FindExecutableA(filename, NULL, command);
1488     ok(rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc);
1489     /* Depending on the platform, command could be '%1' or 'test file.sfe' */
1490
1491     rc=(INT_PTR)FindExecutableA("test file.sfe", tmpdir, command);
1492     ok(rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc);
1493
1494     rc=(INT_PTR)FindExecutableA("test file.sfe", NULL, command);
1495     ok(rc == SE_ERR_FNF, "FindExecutable(%s) returned %ld\n", filename, rc);
1496
1497     delete_test_association(".sfe");
1498
1499     if (!create_test_association(".shl"))
1500     {
1501         skip("Unable to create association for '.shl'\n");
1502         return;
1503     }
1504     create_test_verb(".shl", "Open", 0, "Open");
1505
1506     sprintf(filename, "%s\\test file.shl", tmpdir);
1507     rc=(INT_PTR)FindExecutableA(filename, NULL, command);
1508     ok(rc == SE_ERR_FNF /* NT4 */ || rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc);
1509
1510     sprintf(filename, "%s\\test file.shlfoo", tmpdir);
1511     rc=(INT_PTR)FindExecutableA(filename, NULL, command);
1512
1513     delete_test_association(".shl");
1514
1515     if (rc > 32)
1516     {
1517         /* On Windows XP and 2003 FindExecutable() is completely broken.
1518          * Probably what it does is convert the filename to 8.3 format,
1519          * which as a side effect converts the '.shlfoo' extension to '.shl',
1520          * and then tries to find an association for '.shl'. This means it
1521          * will normally fail on most extensions with more than 3 characters,
1522          * like '.mpeg', etc.
1523          * Also it means we cannot do any other test.
1524          */
1525         win_skip("FindExecutable() is broken -> not running 4+ character extension tests\n");
1526         return;
1527     }
1528
1529     test=filename_tests;
1530     while (test->basename)
1531     {
1532         sprintf(filename, test->basename, tmpdir);
1533         if (strchr(filename, '/'))
1534         {
1535             char* c;
1536             c=filename;
1537             while (*c)
1538             {
1539                 if (*c=='\\')
1540                     *c='/';
1541                 c++;
1542             }
1543         }
1544         /* Win98 does not '\0'-terminate command! */
1545         memset(command, '\0', sizeof(command));
1546         rc=(INT_PTR)FindExecutableA(filename, NULL, command);
1547         if (rc > 32)
1548             rc=33;
1549         if ((test->todo & 0x10)==0)
1550         {
1551             ok(rc==test->rc, "FindExecutable(%s) failed: rc=%ld\n", filename, rc);
1552         }
1553         else todo_wine
1554         {
1555             ok(rc==test->rc, "FindExecutable(%s) failed: rc=%ld\n", filename, rc);
1556         }
1557         if (rc > 32)
1558         {
1559             int equal;
1560             equal=strcmp(command, argv0) == 0 ||
1561                 /* NT4 returns an extra 0x8 character! */
1562                 (strlen(command) == strlen(argv0)+1 && strncmp(command, argv0, strlen(argv0)) == 0);
1563             if ((test->todo & 0x20)==0)
1564             {
1565                 ok(equal, "FindExecutable(%s) returned command='%s' instead of '%s'\n",
1566                    filename, command, argv0);
1567             }
1568             else todo_wine
1569             {
1570                 ok(equal, "FindExecutable(%s) returned command='%s' instead of '%s'\n",
1571                    filename, command, argv0);
1572             }
1573         }
1574         test++;
1575     }
1576 }
1577
1578
1579 static filename_tests_t lnk_tests[]=
1580 {
1581     /* Pass bad / nonexistent filenames as a parameter */
1582     {NULL, "%s\\nonexistent.shlexec",    0xa, 33},
1583     {NULL, "%s\\nonexistent.noassoc",    0xa, 33},
1584
1585     /* Pass regular paths as a parameter */
1586     {NULL, "%s\\test file.shlexec",      0xa, 33},
1587     {NULL, "%s/%%nasty%% $file.shlexec", 0xa, 33},
1588
1589     /* Pass filenames with no association as a parameter */
1590     {NULL, "%s\\test file.noassoc",      0xa, 33},
1591
1592     {NULL, NULL, 0}
1593 };
1594
1595 static void test_lnks(void)
1596 {
1597     char filename[MAX_PATH];
1598     char params[MAX_PATH];
1599     const filename_tests_t* test;
1600     INT_PTR rc;
1601
1602     sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir);
1603     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL);
1604     ok(rc > 32, "%s failed: rc=%lu err=%u\n", shell_call, rc,
1605        GetLastError());
1606     okChildInt("argcA", 5);
1607     okChildString("argvA3", "Open");
1608     sprintf(params, "%s\\test file.shlexec", tmpdir);
1609     get_long_path_name(params, filename, sizeof(filename));
1610     okChildPath("argvA4", filename);
1611
1612     sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
1613     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL);
1614     ok(rc > 32, "%s failed: rc=%lu err=%u\n", shell_call, rc,
1615        GetLastError());
1616     okChildInt("argcA", 4);
1617     okChildString("argvA3", "Lnk");
1618
1619     if (dllver.dwMajorVersion>=6)
1620     {
1621         char* c;
1622        /* Recent versions of shell32.dll accept '/'s in shortcut paths.
1623          * Older versions don't or are quite buggy in this regard.
1624          */
1625         sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
1626         c=filename;
1627         while (*c)
1628         {
1629             if (*c=='\\')
1630                 *c='/';
1631             c++;
1632         }
1633         rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL);
1634         ok(rc > 32, "%s failed: rc=%lu err=%u\n", shell_call, rc,
1635            GetLastError());
1636         okChildInt("argcA", 4);
1637         okChildString("argvA3", "Lnk");
1638     }
1639
1640     sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
1641     test=lnk_tests;
1642     while (test->basename)
1643     {
1644         params[0]='\"';
1645         sprintf(params+1, test->basename, tmpdir);
1646         strcat(params,"\"");
1647         rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, params,
1648                             NULL);
1649         if (rc > 32)
1650             rc=33;
1651         if ((test->todo & 0x1)==0)
1652         {
1653             ok(rc==test->rc, "%s failed: rc=%lu err=%u\n", shell_call,
1654                rc, GetLastError());
1655         }
1656         else todo_wine
1657         {
1658             ok(rc==test->rc, "%s failed: rc=%lu err=%u\n", shell_call,
1659                rc, GetLastError());
1660         }
1661         if (rc==0)
1662         {
1663             if ((test->todo & 0x2)==0)
1664             {
1665                 okChildInt("argcA", 5);
1666             }
1667             else
1668             {
1669                 okChildInt("argcA", 5);
1670             }
1671             if ((test->todo & 0x4)==0)
1672             {
1673                 okChildString("argvA3", "Lnk");
1674             }
1675             else todo_wine
1676             {
1677                 okChildString("argvA3", "Lnk");
1678             }
1679             sprintf(params, test->basename, tmpdir);
1680             if ((test->todo & 0x8)==0)
1681             {
1682                 okChildPath("argvA4", params);
1683             }
1684             else
1685             {
1686                 okChildPath("argvA4", params);
1687             }
1688         }
1689         test++;
1690     }
1691 }
1692
1693
1694 static void test_exes(void)
1695 {
1696     char filename[MAX_PATH];
1697     char params[1024];
1698     INT_PTR rc;
1699
1700     sprintf(params, "shlexec \"%s\" Exec", child_file);
1701
1702     /* We need NOZONECHECKS on Win2003 to block a dialog */
1703     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, params,
1704                         NULL);
1705     ok(rc > 32, "%s returned %lu\n", shell_call, rc);
1706     okChildInt("argcA", 4);
1707     okChildString("argvA3", "Exec");
1708
1709     if (! skip_noassoc_tests)
1710     {
1711         sprintf(filename, "%s\\test file.noassoc", tmpdir);
1712         if (CopyFile(argv0, filename, FALSE))
1713         {
1714             rc=shell_execute(NULL, filename, params, NULL);
1715             todo_wine {
1716                 ok(rc==SE_ERR_NOASSOC, "%s succeeded: rc=%lu\n", shell_call, rc);
1717             }
1718         }
1719     }
1720     else
1721     {
1722         win_skip("Skipping shellexecute of file with unassociated extension\n");
1723     }
1724 }
1725
1726 static void test_exes_long(void)
1727 {
1728     char filename[MAX_PATH];
1729     char params[2024];
1730     char longparam[MAX_PATH];
1731     INT_PTR rc;
1732
1733     for (rc = 0; rc < MAX_PATH; rc++)
1734         longparam[rc]='a'+rc%26;
1735     longparam[MAX_PATH-1]=0;
1736
1737
1738     sprintf(params, "shlexec \"%s\" %s", child_file,longparam);
1739
1740     /* We need NOZONECHECKS on Win2003 to block a dialog */
1741     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, params,
1742                         NULL);
1743     ok(rc > 32, "%s returned %lu\n", shell_call, rc);
1744     okChildInt("argcA", 4);
1745     okChildString("argvA3", longparam);
1746
1747     if (! skip_noassoc_tests)
1748     {
1749         sprintf(filename, "%s\\test file.noassoc", tmpdir);
1750         if (CopyFile(argv0, filename, FALSE))
1751         {
1752             rc=shell_execute(NULL, filename, params, NULL);
1753             todo_wine {
1754                 ok(rc==SE_ERR_NOASSOC, "%s succeeded: rc=%lu\n", shell_call, rc);
1755             }
1756         }
1757     }
1758     else
1759     {
1760         win_skip("Skipping shellexecute of file with unassociated extension\n");
1761     }
1762 }
1763
1764 typedef struct
1765 {
1766     const char* command;
1767     const char* ddeexec;
1768     const char* application;
1769     const char* topic;
1770     const char* ifexec;
1771     int expectedArgs;
1772     const char* expectedDdeExec;
1773     int todo;
1774 } dde_tests_t;
1775
1776 static dde_tests_t dde_tests[] =
1777 {
1778     /* Test passing and not passing command-line
1779      * argument, no DDE */
1780     {"", NULL, NULL, NULL, NULL, FALSE, "", 0x0},
1781     {"\"%1\"", NULL, NULL, NULL, NULL, TRUE, "", 0x0},
1782
1783     /* Test passing and not passing command-line
1784      * argument, with DDE */
1785     {"", "[open(\"%1\")]", "shlexec", "dde", NULL, FALSE, "[open(\"%s\")]", 0x0},
1786     {"\"%1\"", "[open(\"%1\")]", "shlexec", "dde", NULL, TRUE, "[open(\"%s\")]", 0x0},
1787
1788     /* Test unquoted %1 in command and ddeexec
1789      * (test filename has space) */
1790     {"%1", "[open(%1)]", "shlexec", "dde", NULL, 2, "[open(%s)]", 0x0},
1791
1792     /* Test ifexec precedence over ddeexec */
1793     {"", "[open(\"%1\")]", "shlexec", "dde", "[ifexec(\"%1\")]", FALSE, "[ifexec(\"%s\")]", 0x0},
1794
1795     /* Test default DDE topic */
1796     {"", "[open(\"%1\")]", "shlexec", NULL, NULL, FALSE, "[open(\"%s\")]", 0x0},
1797
1798     /* Test default DDE application */
1799     {"", "[open(\"%1\")]", NULL, "dde", NULL, FALSE, "[open(\"%s\")]", 0x0},
1800
1801     {NULL, NULL, NULL, NULL, NULL, 0, 0x0}
1802 };
1803
1804 static DWORD WINAPI hooked_WaitForInputIdle(HANDLE process, DWORD timeout)
1805 {
1806     return WaitForSingleObject(dde_ready_event, timeout);
1807 }
1808
1809 /*
1810  * WaitForInputIdle() will normally return immediately for console apps. That's
1811  * a problem for us because ShellExecute will assume that an app is ready to
1812  * receive DDE messages after it has called WaitForInputIdle() on that app.
1813  * To work around that we install our own version of WaitForInputIdle() that
1814  * will wait for the child to explicitly tell us that it is ready. We do that
1815  * by changing the entry for WaitForInputIdle() in the shell32 import address
1816  * table.
1817  */
1818 static void hook_WaitForInputIdle(DWORD (WINAPI *new_func)(HANDLE, DWORD))
1819 {
1820     char *base;
1821     PIMAGE_NT_HEADERS nt_headers;
1822     DWORD import_directory_rva;
1823     PIMAGE_IMPORT_DESCRIPTOR import_descriptor;
1824
1825     base = (char *) GetModuleHandleA("shell32.dll");
1826     nt_headers = (PIMAGE_NT_HEADERS)(base + ((PIMAGE_DOS_HEADER) base)->e_lfanew);
1827     import_directory_rva = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
1828
1829     /* Search for the correct imported module by walking the import descriptors */
1830     import_descriptor = (PIMAGE_IMPORT_DESCRIPTOR)(base + import_directory_rva);
1831     while (U(*import_descriptor).OriginalFirstThunk != 0)
1832     {
1833         char *import_module_name;
1834
1835         import_module_name = base + import_descriptor->Name;
1836         if (lstrcmpiA(import_module_name, "user32.dll") == 0 ||
1837             lstrcmpiA(import_module_name, "user32") == 0)
1838         {
1839             PIMAGE_THUNK_DATA int_entry;
1840             PIMAGE_THUNK_DATA iat_entry;
1841
1842             /* The import name table and import address table are two parallel
1843              * arrays. We need the import name table to find the imported
1844              * routine and the import address table to patch the address, so
1845              * walk them side by side */
1846             int_entry = (PIMAGE_THUNK_DATA)(base + U(*import_descriptor).OriginalFirstThunk);
1847             iat_entry = (PIMAGE_THUNK_DATA)(base + import_descriptor->FirstThunk);
1848             while (int_entry->u1.Ordinal != 0)
1849             {
1850                 if (! IMAGE_SNAP_BY_ORDINAL(int_entry->u1.Ordinal))
1851                 {
1852                     PIMAGE_IMPORT_BY_NAME import_by_name;
1853                     import_by_name = (PIMAGE_IMPORT_BY_NAME)(base + int_entry->u1.AddressOfData);
1854                     if (lstrcmpA((char *) import_by_name->Name, "WaitForInputIdle") == 0)
1855                     {
1856                         /* Found the correct routine in the correct imported module. Patch it. */
1857                         DWORD old_prot;
1858                         VirtualProtect(&iat_entry->u1.Function, sizeof(ULONG_PTR), PAGE_READWRITE, &old_prot);
1859                         iat_entry->u1.Function = (ULONG_PTR) new_func;
1860                         VirtualProtect(&iat_entry->u1.Function, sizeof(ULONG_PTR), old_prot, &old_prot);
1861                         break;
1862                     }
1863                 }
1864                 int_entry++;
1865                 iat_entry++;
1866             }
1867             break;
1868         }
1869
1870         import_descriptor++;
1871     }
1872 }
1873
1874 static void test_dde(void)
1875 {
1876     char filename[MAX_PATH], defApplication[MAX_PATH];
1877     const dde_tests_t* test;
1878     char params[1024];
1879     INT_PTR rc;
1880     HANDLE map;
1881     char *shared_block;
1882
1883     hook_WaitForInputIdle(hooked_WaitForInputIdle);
1884
1885     sprintf(filename, "%s\\test file.sde", tmpdir);
1886
1887     /* Default service is application name minus path and extension */
1888     strcpy(defApplication, strrchr(argv0, '\\')+1);
1889     *strchr(defApplication, '.') = 0;
1890
1891     map = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
1892                              4096, "winetest_shlexec_dde_map");
1893     shared_block = MapViewOfFile(map, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 4096);
1894
1895     test = dde_tests;
1896     while (test->command)
1897     {
1898         if (!create_test_association(".sde"))
1899         {
1900             skip("Unable to create association for '.sde'\n");
1901             return;
1902         }
1903         create_test_verb_dde(".sde", "Open", 0, test->command, test->ddeexec,
1904                              test->application, test->topic, test->ifexec);
1905
1906         if (test->application != NULL || test->topic != NULL)
1907         {
1908             strcpy(shared_block, test->application ? test->application : defApplication);
1909             strcpy(shared_block + strlen(shared_block) + 1, test->topic ? test->topic : SZDDESYS_TOPIC);
1910         }
1911         else
1912         {
1913             shared_block[0] = '\0';
1914             shared_block[1] = '\0';
1915         }
1916         ddeExec[0] = 0;
1917
1918         dde_ready_event = CreateEventA(NULL, FALSE, FALSE, "winetest_shlexec_dde_ready");
1919         rc = shell_execute_ex(SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI, NULL, filename, NULL, NULL);
1920         CloseHandle(dde_ready_event);
1921         if ((test->todo & 0x1)==0)
1922         {
1923             ok(32 < rc, "%s failed: rc=%lu err=%u\n", shell_call,
1924                rc, GetLastError());
1925         }
1926         else todo_wine
1927         {
1928             ok(32 < rc, "%s failed: rc=%lu err=%u\n", shell_call,
1929                rc, GetLastError());
1930         }
1931         if (32 < rc)
1932         {
1933             if ((test->todo & 0x2)==0)
1934             {
1935                 okChildInt("argcA", test->expectedArgs + 3);
1936             }
1937             else todo_wine
1938             {
1939                 okChildInt("argcA", test->expectedArgs + 3);
1940             }
1941             if (test->expectedArgs == 1)
1942             {
1943                 if ((test->todo & 0x4) == 0)
1944                 {
1945                     okChildPath("argvA3", filename);
1946                 }
1947                 else todo_wine
1948                 {
1949                     okChildPath("argvA3", filename);
1950                 }
1951             }
1952             if ((test->todo & 0x8) == 0)
1953             {
1954                 sprintf(params, test->expectedDdeExec, filename);
1955                 okChildPath("ddeExec", params);
1956             }
1957             else todo_wine
1958             {
1959                 sprintf(params, test->expectedDdeExec, filename);
1960                 okChildPath("ddeExec", params);
1961             }
1962         }
1963
1964         delete_test_association(".sde");
1965         test++;
1966     }
1967
1968     UnmapViewOfFile(shared_block);
1969     CloseHandle(map);
1970     hook_WaitForInputIdle((void *) WaitForInputIdle);
1971 }
1972
1973 #define DDE_DEFAULT_APP_VARIANTS 2
1974 typedef struct
1975 {
1976     const char* command;
1977     const char* expectedDdeApplication[DDE_DEFAULT_APP_VARIANTS];
1978     int todo;
1979     int rc[DDE_DEFAULT_APP_VARIANTS];
1980 } dde_default_app_tests_t;
1981
1982 static dde_default_app_tests_t dde_default_app_tests[] =
1983 {
1984     /* Windows XP and 98 handle default DDE app names in different ways.
1985      * The application name we see in the first test determines the pattern
1986      * of application names and return codes we will look for. */
1987
1988     /* Test unquoted existing filename with a space */
1989     {"%s\\test file.exe", {"test file", "test"}, 0x0, {33, 33}},
1990     {"%s\\test file.exe param", {"test file", "test"}, 0x0, {33, 33}},
1991
1992     /* Test quoted existing filename with a space */
1993     {"\"%s\\test file.exe\"", {"test file", "test file"}, 0x0, {33, 33}},
1994     {"\"%s\\test file.exe\" param", {"test file", "test file"}, 0x0, {33, 33}},
1995
1996     /* Test unquoted filename with a space that doesn't exist, but
1997      * test2.exe does */
1998     {"%s\\test2 file.exe", {"test2", "test2"}, 0x0, {33, 33}},
1999     {"%s\\test2 file.exe param", {"test2", "test2"}, 0x0, {33, 33}},
2000
2001     /* Test quoted filename with a space that does not exist */
2002     {"\"%s\\test2 file.exe\"", {"", "test2 file"}, 0x0, {5, 33}},
2003     {"\"%s\\test2 file.exe\" param", {"", "test2 file"}, 0x0, {5, 33}},
2004
2005     /* Test filename supplied without the extension */
2006     {"%s\\test2", {"test2", "test2"}, 0x0, {33, 33}},
2007     {"%s\\test2 param", {"test2", "test2"}, 0x0, {33, 33}},
2008
2009     /* Test an unquoted nonexistent filename */
2010     {"%s\\notexist.exe", {"", "notexist"}, 0x0, {5, 33}},
2011     {"%s\\notexist.exe param", {"", "notexist"}, 0x0, {5, 33}},
2012
2013     /* Test an application that will be found on the path */
2014     {"cmd", {"cmd", "cmd"}, 0x0, {33, 33}},
2015     {"cmd param", {"cmd", "cmd"}, 0x0, {33, 33}},
2016
2017     /* Test an application that will not be found on the path */
2018     {"xyzwxyzwxyz", {"", "xyzwxyzwxyz"}, 0x0, {5, 33}},
2019     {"xyzwxyzwxyz param", {"", "xyzwxyzwxyz"}, 0x0, {5, 33}},
2020
2021     {NULL, {NULL}, 0, {0}}
2022 };
2023
2024 typedef struct
2025 {
2026     char *filename;
2027     DWORD threadIdParent;
2028 } dde_thread_info_t;
2029
2030 static DWORD CALLBACK ddeThread(LPVOID arg)
2031 {
2032     dde_thread_info_t *info = arg;
2033     assert(info && info->filename);
2034     PostThreadMessage(info->threadIdParent,
2035                       WM_QUIT,
2036                       shell_execute_ex(SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI, NULL, info->filename, NULL, NULL),
2037                       0L);
2038     ExitThread(0);
2039 }
2040
2041 static void test_dde_default_app(void)
2042 {
2043     char filename[MAX_PATH];
2044     HSZ hszApplication;
2045     dde_thread_info_t info = { filename, GetCurrentThreadId() };
2046     const dde_default_app_tests_t* test;
2047     char params[1024];
2048     DWORD threadId;
2049     MSG msg;
2050     INT_PTR rc;
2051     int which = 0;
2052
2053     post_quit_on_execute = FALSE;
2054     ddeInst = 0;
2055     rc = DdeInitializeA(&ddeInst, ddeCb, CBF_SKIP_ALLNOTIFICATIONS | CBF_FAIL_ADVISES |
2056                         CBF_FAIL_POKES | CBF_FAIL_REQUESTS, 0L);
2057     assert(rc == DMLERR_NO_ERROR);
2058
2059     sprintf(filename, "%s\\test file.sde", tmpdir);
2060
2061     /* It is strictly not necessary to register an application name here, but wine's
2062      * DdeNameService implementation complains if 0L is passed instead of
2063      * hszApplication with DNS_FILTEROFF */
2064     hszApplication = DdeCreateStringHandleA(ddeInst, "shlexec", CP_WINANSI);
2065     hszTopic = DdeCreateStringHandleA(ddeInst, "shlexec", CP_WINANSI);
2066     assert(hszApplication && hszTopic);
2067     assert(DdeNameService(ddeInst, hszApplication, 0L, DNS_REGISTER | DNS_FILTEROFF));
2068
2069     test = dde_default_app_tests;
2070     while (test->command)
2071     {
2072         if (!create_test_association(".sde"))
2073         {
2074             skip("Unable to create association for '.sde'\n");
2075             return;
2076         }
2077         sprintf(params, test->command, tmpdir);
2078         create_test_verb_dde(".sde", "Open", 1, params, "[test]", NULL,
2079                              "shlexec", NULL);
2080         ddeApplication[0] = 0;
2081
2082         /* No application will be run as we will respond to the first DDE event,
2083          * so don't wait for it */
2084         SetEvent(hEvent);
2085
2086         assert(CreateThread(NULL, 0, ddeThread, &info, 0, &threadId));
2087         while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg);
2088         rc = msg.wParam > 32 ? 33 : msg.wParam;
2089
2090         /* First test, find which set of test data we expect to see */
2091         if (test == dde_default_app_tests)
2092         {
2093             int i;
2094             for (i=0; i<DDE_DEFAULT_APP_VARIANTS; i++)
2095             {
2096                 if (!strcmp(ddeApplication, test->expectedDdeApplication[i]))
2097                 {
2098                     which = i;
2099                     break;
2100                 }
2101             }
2102             if (i == DDE_DEFAULT_APP_VARIANTS)
2103                 skip("Default DDE application test does not match any available results, using first expected data set.\n");
2104         }
2105
2106         if ((test->todo & 0x1)==0)
2107         {
2108             ok(rc==test->rc[which], "%s failed: rc=%lu err=%u\n", shell_call,
2109                rc, GetLastError());
2110         }
2111         else todo_wine
2112         {
2113             ok(rc==test->rc[which], "%s failed: rc=%lu err=%u\n", shell_call,
2114                rc, GetLastError());
2115         }
2116         if (rc == 33)
2117         {
2118             if ((test->todo & 0x2)==0)
2119             {
2120                 ok(!strcmp(ddeApplication, test->expectedDdeApplication[which]),
2121                    "Expected application '%s', got '%s'\n",
2122                    test->expectedDdeApplication[which], ddeApplication);
2123             }
2124             else todo_wine
2125             {
2126                 ok(!strcmp(ddeApplication, test->expectedDdeApplication[which]),
2127                    "Expected application '%s', got '%s'\n",
2128                    test->expectedDdeApplication[which], ddeApplication);
2129             }
2130         }
2131
2132         delete_test_association(".sde");
2133         test++;
2134     }
2135
2136     assert(DdeNameService(ddeInst, hszApplication, 0L, DNS_UNREGISTER));
2137     assert(DdeFreeStringHandle(ddeInst, hszTopic));
2138     assert(DdeFreeStringHandle(ddeInst, hszApplication));
2139     assert(DdeUninitialize(ddeInst));
2140 }
2141
2142 static void init_test(void)
2143 {
2144     HMODULE hdll;
2145     HRESULT (WINAPI *pDllGetVersion)(DLLVERSIONINFO*);
2146     char filename[MAX_PATH];
2147     WCHAR lnkfile[MAX_PATH];
2148     char params[1024];
2149     const char* const * testfile;
2150     lnk_desc_t desc;
2151     DWORD rc;
2152     HRESULT r;
2153
2154     hdll=GetModuleHandleA("shell32.dll");
2155     pDllGetVersion=(void*)GetProcAddress(hdll, "DllGetVersion");
2156     if (pDllGetVersion)
2157     {
2158         dllver.cbSize=sizeof(dllver);
2159         pDllGetVersion(&dllver);
2160         trace("major=%d minor=%d build=%d platform=%d\n",
2161               dllver.dwMajorVersion, dllver.dwMinorVersion,
2162               dllver.dwBuildNumber, dllver.dwPlatformID);
2163     }
2164     else
2165     {
2166         memset(&dllver, 0, sizeof(dllver));
2167     }
2168
2169     r = CoInitialize(NULL);
2170     ok(r == S_OK, "CoInitialize failed (0x%08x)\n", r);
2171     if (FAILED(r))
2172         exit(1);
2173
2174     rc=GetModuleFileName(NULL, argv0, sizeof(argv0));
2175     assert(rc!=0 && rc<sizeof(argv0));
2176     if (GetFileAttributes(argv0)==INVALID_FILE_ATTRIBUTES)
2177     {
2178         strcat(argv0, ".so");
2179         ok(GetFileAttributes(argv0)!=INVALID_FILE_ATTRIBUTES,
2180            "unable to find argv0!\n");
2181     }
2182
2183     GetTempPathA(sizeof(filename), filename);
2184     GetTempFileNameA(filename, "wt", 0, tmpdir);
2185     DeleteFileA( tmpdir );
2186     rc = CreateDirectoryA( tmpdir, NULL );
2187     ok( rc, "failed to create %s err %u\n", tmpdir, GetLastError() );
2188     rc = GetTempFileNameA(tmpdir, "wt", 0, child_file);
2189     assert(rc != 0);
2190     init_event(child_file);
2191
2192     /* Set up the test files */
2193     testfile=testfiles;
2194     while (*testfile)
2195     {
2196         HANDLE hfile;
2197
2198         sprintf(filename, *testfile, tmpdir);
2199         hfile=CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
2200                      FILE_ATTRIBUTE_NORMAL, NULL);
2201         if (hfile==INVALID_HANDLE_VALUE)
2202         {
2203             trace("unable to create '%s': err=%u\n", filename, GetLastError());
2204             assert(0);
2205         }
2206         CloseHandle(hfile);
2207         testfile++;
2208     }
2209
2210     /* Setup the test shortcuts */
2211     sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir);
2212     MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, sizeof(lnkfile)/sizeof(*lnkfile));
2213     desc.description=NULL;
2214     desc.workdir=NULL;
2215     sprintf(filename, "%s\\test file.shlexec", tmpdir);
2216     desc.path=filename;
2217     desc.pidl=NULL;
2218     desc.arguments="ignored";
2219     desc.showcmd=0;
2220     desc.icon=NULL;
2221     desc.icon_id=0;
2222     desc.hotkey=0;
2223     create_lnk(lnkfile, &desc, 0);
2224
2225     sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
2226     MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, sizeof(lnkfile)/sizeof(*lnkfile));
2227     desc.description=NULL;
2228     desc.workdir=NULL;
2229     desc.path=argv0;
2230     desc.pidl=NULL;
2231     sprintf(params, "shlexec \"%s\" Lnk", child_file);
2232     desc.arguments=params;
2233     desc.showcmd=0;
2234     desc.icon=NULL;
2235     desc.icon_id=0;
2236     desc.hotkey=0;
2237     create_lnk(lnkfile, &desc, 0);
2238
2239     /* Create a basic association suitable for most tests */
2240     if (!create_test_association(".shlexec"))
2241     {
2242         skip("Unable to create association for '.shlexec'\n");
2243         return;
2244     }
2245     create_test_verb(".shlexec", "Open", 0, "Open \"%1\"");
2246     create_test_verb(".shlexec", "NoQuotes", 0, "NoQuotes %1");
2247     create_test_verb(".shlexec", "LowerL", 0, "LowerL %l");
2248     create_test_verb(".shlexec", "QuotedLowerL", 0, "QuotedLowerL \"%l\"");
2249     create_test_verb(".shlexec", "UpperL", 0, "UpperL %L");
2250     create_test_verb(".shlexec", "QuotedUpperL", 0, "QuotedUpperL \"%L\"");
2251
2252     create_test_verb(".shlexec", "NoQuotesParam2", 0, "NoQuotesParam2 %2");
2253     create_test_verb(".shlexec", "QuotedParam2", 0, "QuotedParam2 \"%2\"");
2254
2255     create_test_verb(".shlexec", "NoQuotesAllParams", 0, "NoQuotesAllParams %*");
2256     create_test_verb(".shlexec", "QuotedAllParams", 0, "QuotedAllParams \"%*\"");
2257
2258     create_test_verb(".shlexec", "NoQuotesParams345etc", 0, "NoQuotesParams345etc %~3");
2259     create_test_verb(".shlexec", "QuotedParams345etc", 0, "QuotedParams345etc \"%~3\"");
2260 }
2261
2262 static void cleanup_test(void)
2263 {
2264     char filename[MAX_PATH];
2265     const char* const * testfile;
2266
2267     /* Delete the test files */
2268     testfile=testfiles;
2269     while (*testfile)
2270     {
2271         sprintf(filename, *testfile, tmpdir);
2272         /* Make sure we can delete the files ('test file.noassoc' is read-only now) */
2273         SetFileAttributes(filename, FILE_ATTRIBUTE_NORMAL);
2274         DeleteFile(filename);
2275         testfile++;
2276     }
2277     DeleteFile(child_file);
2278     RemoveDirectoryA(tmpdir);
2279
2280     /* Delete the test association */
2281     delete_test_association(".shlexec");
2282
2283     CloseHandle(hEvent);
2284
2285     CoUninitialize();
2286 }
2287
2288 static void test_commandline(void)
2289 {
2290     static const WCHAR one[] = {'o','n','e',0};
2291     static const WCHAR two[] = {'t','w','o',0};
2292     static const WCHAR three[] = {'t','h','r','e','e',0};
2293     static const WCHAR four[] = {'f','o','u','r',0};
2294
2295     static const WCHAR fmt1[] = {'%','s',' ','%','s',' ','%','s',' ','%','s',0};
2296     static const WCHAR fmt2[] = {' ','%','s',' ','%','s',' ','%','s',' ','%','s',0};
2297     static const WCHAR fmt3[] = {'%','s','=','%','s',' ','%','s','=','\"','%','s','\"',0};
2298     static const WCHAR fmt4[] = {'\"','%','s','\"',' ','\"','%','s',' ','%','s','\"',' ','%','s',0};
2299     static const WCHAR fmt5[] = {'\\','\"','%','s','\"',' ','%','s','=','\"','%','s','\\','\"',' ','\"','%','s','\\','\"',0};
2300     static const WCHAR fmt6[] = {0};
2301
2302     static const WCHAR chkfmt1[] = {'%','s','=','%','s',0};
2303     static const WCHAR chkfmt2[] = {'%','s',' ','%','s',0};
2304     static const WCHAR chkfmt3[] = {'\\','\"','%','s','\"',0};
2305     static const WCHAR chkfmt4[] = {'%','s','=','%','s','\"',' ','%','s','\"',0};
2306     WCHAR cmdline[255];
2307     LPWSTR *args = (LPWSTR*)0xdeadcafe, pbuf;
2308     INT numargs = -1;
2309     size_t buflen;
2310     DWORD lerror;
2311
2312     wsprintfW(cmdline,fmt1,one,two,three,four);
2313     args=CommandLineToArgvW(cmdline,&numargs);
2314     if (args == NULL && numargs == -1)
2315     {
2316         win_skip("CommandLineToArgvW not implemented, skipping\n");
2317         return;
2318     }
2319     ok(numargs == 4, "expected 4 args, got %i\n",numargs);
2320     ok(lstrcmpW(args[0],one)==0,"arg0 is not as expected\n");
2321     ok(lstrcmpW(args[1],two)==0,"arg1 is not as expected\n");
2322     ok(lstrcmpW(args[2],three)==0,"arg2 is not as expected\n");
2323     ok(lstrcmpW(args[3],four)==0,"arg3 is not as expected\n");
2324
2325     SetLastError(0xdeadbeef);
2326     args=CommandLineToArgvW(cmdline,NULL);
2327     lerror=GetLastError();
2328     ok(args == NULL && lerror == ERROR_INVALID_PARAMETER, "expected NULL with ERROR_INVALID_PARAMETER got %p with %u\n",args,lerror);
2329     SetLastError(0xdeadbeef);
2330     args=CommandLineToArgvW(NULL,NULL);
2331     lerror=GetLastError();
2332     ok(args == NULL && lerror == ERROR_INVALID_PARAMETER, "expected NULL with ERROR_INVALID_PARAMETER got %p with %u\n",args,lerror);
2333
2334     wsprintfW(cmdline,fmt2,one,two,three,four);
2335     args=CommandLineToArgvW(cmdline,&numargs);
2336     ok(numargs == 5, "expected 5 args, got %i\n",numargs);
2337     ok(args[0][0]==0,"arg0 is not as expected\n");
2338     ok(lstrcmpW(args[1],one)==0,"arg1 is not as expected\n");
2339     ok(lstrcmpW(args[2],two)==0,"arg2 is not as expected\n");
2340     ok(lstrcmpW(args[3],three)==0,"arg3 is not as expected\n");
2341     ok(lstrcmpW(args[4],four)==0,"arg4 is not as expected\n");
2342
2343     wsprintfW(cmdline,fmt3,one,two,three,four);
2344     args=CommandLineToArgvW(cmdline,&numargs);
2345     ok(numargs == 2, "expected 2 args, got %i\n",numargs);
2346     wsprintfW(cmdline,chkfmt1,one,two);
2347     ok(lstrcmpW(args[0],cmdline)==0,"arg0 is not as expected\n");
2348     wsprintfW(cmdline,chkfmt1,three,four);
2349     ok(lstrcmpW(args[1],cmdline)==0,"arg1 is not as expected\n");
2350
2351     wsprintfW(cmdline,fmt4,one,two,three,four);
2352     args=CommandLineToArgvW(cmdline,&numargs);
2353     ok(numargs == 3, "expected 3 args, got %i\n",numargs);
2354     ok(lstrcmpW(args[0],one)==0,"arg0 is not as expected\n");
2355     wsprintfW(cmdline,chkfmt2,two,three);
2356     ok(lstrcmpW(args[1],cmdline)==0,"arg1 is not as expected\n");
2357     ok(lstrcmpW(args[2],four)==0,"arg2 is not as expected\n");
2358
2359     wsprintfW(cmdline,fmt5,one,two,three,four);
2360     args=CommandLineToArgvW(cmdline,&numargs);
2361     ok(numargs == 2, "expected 2 args, got %i\n",numargs);
2362     wsprintfW(cmdline,chkfmt3,one);
2363     todo_wine ok(lstrcmpW(args[0],cmdline)==0,"arg0 is not as expected\n");
2364     wsprintfW(cmdline,chkfmt4,two,three,four);
2365     todo_wine ok(lstrcmpW(args[1],cmdline)==0,"arg1 is not as expected\n");
2366
2367     wsprintfW(cmdline,fmt6);
2368     args=CommandLineToArgvW(cmdline,&numargs);
2369     ok(numargs == 1, "expected 1 args, got %i\n",numargs);
2370     if (numargs == 1) {
2371         buflen = max(lstrlenW(args[0])+1,256);
2372         pbuf = HeapAlloc(GetProcessHeap(), 0, buflen*sizeof(pbuf[0]));
2373         GetModuleFileNameW(NULL, pbuf, buflen);
2374         pbuf[buflen-1] = 0;
2375         /* check args[0] is module file name */
2376         ok(lstrcmpW(args[0],pbuf)==0, "wrong path to the current executable\n");
2377         HeapFree(GetProcessHeap(), 0, pbuf);
2378     }
2379 }
2380
2381 static void test_directory(void)
2382 {
2383     char path[MAX_PATH], newdir[MAX_PATH];
2384     char params[1024];
2385     INT_PTR rc;
2386
2387     /* copy this executable to a new folder and cd to it */
2388     sprintf(newdir, "%s\\newfolder", tmpdir);
2389     rc = CreateDirectoryA( newdir, NULL );
2390     ok( rc, "failed to create %s err %u\n", newdir, GetLastError() );
2391     sprintf(path, "%s\\%s", newdir, path_find_file_name(argv0));
2392     CopyFileA(argv0, path, FALSE);
2393     SetCurrentDirectory(tmpdir);
2394
2395     sprintf(params, "shlexec \"%s\" Exec", child_file);
2396
2397     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2398                         NULL, path_find_file_name(argv0), params, NULL);
2399     todo_wine ok(rc == SE_ERR_FNF, "%s returned %lu\n", shell_call, rc);
2400
2401     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2402                         NULL, path_find_file_name(argv0), params, newdir);
2403     ok(rc > 32, "%s returned %lu\n", shell_call, rc);
2404     okChildInt("argcA", 4);
2405     okChildString("argvA3", "Exec");
2406     todo_wine okChildPath("longPath", path);
2407
2408     DeleteFile(path);
2409     RemoveDirectoryA(newdir);
2410 }
2411
2412 START_TEST(shlexec)
2413 {
2414
2415     myARGC = winetest_get_mainargs(&myARGV);
2416     if (myARGC >= 3)
2417     {
2418         doChild(myARGC, myARGV);
2419         exit(0);
2420     }
2421
2422     init_test();
2423
2424     test_argify();
2425     test_lpFile_parsed();
2426     test_filename();
2427     test_fileurl();
2428     test_find_executable();
2429     test_lnks();
2430     test_exes();
2431     test_exes_long();
2432     test_dde();
2433     test_dde_default_app();
2434     test_commandline();
2435     test_directory();
2436
2437     cleanup_test();
2438 }