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