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