1 /*
2  * Unit test of the ShellExecute function.
3  *
4  * Copyright 2005, 2016 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  * - ShellExecuteEx() also calls SetLastError() with meaningful values which
30  *   we could check
31  */
32 
33 /* Needed to get SEE_MASK_NOZONECHECKS with the PSDK */
34 #ifndef __REACTOS__
35 #define NTDDI_WINXPSP1 0x05010100
36 #define NTDDI_VERSION NTDDI_WINXPSP1
37 #define _WIN32_WINNT 0x0501
38 #endif
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 "ddeml.h"
49 
50 #include "wine/heap.h"
51 #include "wine/test.h"
52 
53 #include "shell32_test.h"
54 
55 
56 static char argv0[MAX_PATH];
57 static int myARGC;
58 static char** myARGV;
59 static char tmpdir[MAX_PATH];
60 static char child_file[MAX_PATH];
61 static DLLVERSIONINFO dllver;
62 static BOOL skip_shlexec_tests = FALSE;
63 static BOOL skip_noassoc_tests = FALSE;
64 static HANDLE dde_ready_event;
65 
66 
67 /***
68  *
69  * Helpers to read from / write to the child process results file.
70  * (borrowed from dlls/kernel32/tests/process.c)
71  *
72  ***/
73 
74 static const char* encodeA(const char* str)
75 {
76     static char encoded[2*1024+1];
77     char*       ptr;
78     size_t      len,i;
79 
80     if (!str) return "";
81     len = strlen(str) + 1;
82     if (len >= sizeof(encoded)/2)
83     {
84         fprintf(stderr, "string is too long!\n");
85         assert(0);
86     }
87     ptr = encoded;
88     for (i = 0; i < len; i++)
89         sprintf(&ptr[i * 2], "%02x", (unsigned char)str[i]);
90     ptr[2 * len] = '\0';
91     return ptr;
92 }
93 
94 static unsigned decode_char(char c)
95 {
96     if (c >= '0' && c <= '9') return c - '0';
97     if (c >= 'a' && c <= 'f') return c - 'a' + 10;
98     assert(c >= 'A' && c <= 'F');
99     return c - 'A' + 10;
100 }
101 
102 static char* decodeA(const char* str)
103 {
104     static char decoded[1024];
105     char*       ptr;
106     size_t      len,i;
107 
108     len = strlen(str) / 2;
109     if (!len--) return NULL;
110     if (len >= sizeof(decoded))
111     {
112         fprintf(stderr, "string is too long!\n");
113         assert(0);
114     }
115     ptr = decoded;
116     for (i = 0; i < len; i++)
117         ptr[i] = (decode_char(str[2 * i]) << 4) | decode_char(str[2 * i + 1]);
118     ptr[len] = '\0';
119     return ptr;
120 }
121 
122 static void WINETEST_PRINTF_ATTR(2,3) childPrintf(HANDLE h, const char* fmt, ...)
123 {
124     va_list     valist;
125     char        buffer[1024];
126     DWORD       w;
127 
128     va_start(valist, fmt);
129     vsprintf(buffer, fmt, valist);
130     va_end(valist);
131     WriteFile(h, buffer, strlen(buffer), &w, NULL);
132 }
133 
134 static char* getChildString(const char* sect, const char* key)
135 {
136     char        buf[1024];
137     char*       ret;
138 
139     GetPrivateProfileStringA(sect, key, "-", buf, sizeof(buf), child_file);
140     if (buf[0] == '\0' || (buf[0] == '-' && buf[1] == '\0')) return NULL;
141     assert(!(strlen(buf) & 1));
142     ret = decodeA(buf);
143     return ret;
144 }
145 
146 
147 /***
148  *
149  * Child code
150  *
151  ***/
152 
153 #define CHILD_DDE_TIMEOUT 2500
154 static DWORD ddeInst;
155 static HSZ hszTopic;
156 static char ddeExec[MAX_PATH], ddeApplication[MAX_PATH];
157 static BOOL post_quit_on_execute;
158 
159 /* Handle DDE for doChild() and test_dde_default_app() */
160 static HDDEDATA CALLBACK ddeCb(UINT uType, UINT uFmt, HCONV hConv,
161                                HSZ hsz1, HSZ hsz2, HDDEDATA hData,
162                                ULONG_PTR dwData1, ULONG_PTR dwData2)
163 {
164     DWORD size = 0;
165 
166     if (winetest_debug > 2)
167         trace("dde_cb: %04x, %04x, %p, %p, %p, %p, %08lx, %08lx\n",
168               uType, uFmt, hConv, hsz1, hsz2, hData, dwData1, dwData2);
169 
170     switch (uType)
171     {
172         case XTYP_CONNECT:
173             if (!DdeCmpStringHandles(hsz1, hszTopic))
174             {
175                 size = DdeQueryStringA(ddeInst, hsz2, ddeApplication, MAX_PATH, CP_WINANSI);
176                 ok(size < MAX_PATH, "got size %d\n", size);
177                 assert(size < MAX_PATH);
178                 return (HDDEDATA)TRUE;
179             }
180             return (HDDEDATA)FALSE;
181 
182         case XTYP_EXECUTE:
183             size = DdeGetData(hData, (LPBYTE)ddeExec, MAX_PATH, 0);
184             ok(size < MAX_PATH, "got size %d\n", size);
185             assert(size < MAX_PATH);
186             DdeFreeDataHandle(hData);
187             if (post_quit_on_execute)
188                 PostQuitMessage(0);
189             return (HDDEDATA)DDE_FACK;
190 
191         default:
192             return NULL;
193     }
194 }
195 
196 static HANDLE hEvent;
197 static void init_event(const char* child_file)
198 {
199     char* event_name;
200     event_name=strrchr(child_file, '\\')+1;
201     hEvent=CreateEventA(NULL, FALSE, FALSE, event_name);
202 }
203 
204 /*
205  * This is just to make sure the child won't run forever stuck in a
206  * GetMessage() loop when DDE fails for some reason.
207  */
208 static void CALLBACK childTimeout(HWND wnd, UINT msg, UINT_PTR timer, DWORD time)
209 {
210     trace("childTimeout called\n");
211 
212     PostQuitMessage(0);
213 }
214 
215 static void doChild(int argc, char** argv)
216 {
217     char *filename, buffer[MAX_PATH];
218     HANDLE hFile, map;
219     int i;
220     UINT_PTR timer;
221 
222     filename=argv[2];
223     hFile=CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0);
224     if (hFile == INVALID_HANDLE_VALUE)
225         return;
226 
227     /* Arguments */
228     childPrintf(hFile, "[Child]\r\n");
229     if (winetest_debug > 2)
230     {
231         trace("cmdlineA='%s'\n", GetCommandLineA());
232         trace("argcA=%d\n", argc);
233     }
234     childPrintf(hFile, "cmdlineA=%s\r\n", encodeA(GetCommandLineA()));
235     childPrintf(hFile, "argcA=%d\r\n", argc);
236     for (i = 0; i < argc; i++)
237     {
238         if (winetest_debug > 2)
239             trace("argvA%d='%s'\n", i, argv[i]);
240         childPrintf(hFile, "argvA%d=%s\r\n", i, encodeA(argv[i]));
241     }
242     GetModuleFileNameA(GetModuleHandleA(NULL), buffer, sizeof(buffer));
243     childPrintf(hFile, "longPath=%s\r\n", encodeA(buffer));
244 
245     /* Check environment variable inheritance */
246     *buffer = '\0';
247     SetLastError(0);
248     GetEnvironmentVariableA("ShlexecVar", buffer, sizeof(buffer));
249     childPrintf(hFile, "ShlexecVarLE=%d\r\n", GetLastError());
250     childPrintf(hFile, "ShlexecVar=%s\r\n", encodeA(buffer));
251 
252     map = OpenFileMappingA(FILE_MAP_READ, FALSE, "winetest_shlexec_dde_map");
253     if (map != NULL)
254     {
255         HANDLE dde_ready;
256         char *shared_block = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 4096);
257         CloseHandle(map);
258         if (shared_block[0] != '\0' || shared_block[1] != '\0')
259         {
260             HDDEDATA hdde;
261             HSZ hszApplication;
262             MSG msg;
263             UINT rc;
264 
265             post_quit_on_execute = TRUE;
266             ddeInst = 0;
267             rc = DdeInitializeA(&ddeInst, ddeCb, CBF_SKIP_ALLNOTIFICATIONS | CBF_FAIL_ADVISES |
268                                 CBF_FAIL_POKES | CBF_FAIL_REQUESTS, 0);
269             ok(rc == DMLERR_NO_ERROR, "DdeInitializeA() returned %d\n", rc);
270             hszApplication = DdeCreateStringHandleA(ddeInst, shared_block, CP_WINANSI);
271             ok(hszApplication != NULL, "DdeCreateStringHandleA(%s) = NULL\n", shared_block);
272             shared_block += strlen(shared_block) + 1;
273             hszTopic = DdeCreateStringHandleA(ddeInst, shared_block, CP_WINANSI);
274             ok(hszTopic != NULL, "DdeCreateStringHandleA(%s) = NULL\n", shared_block);
275             hdde = DdeNameService(ddeInst, hszApplication, 0, DNS_REGISTER | DNS_FILTEROFF);
276             ok(hdde != NULL, "DdeNameService() failed le=%u\n", GetLastError());
277 
278             timer = SetTimer(NULL, 0, CHILD_DDE_TIMEOUT, childTimeout);
279 
280             dde_ready = OpenEventA(EVENT_MODIFY_STATE, FALSE, "winetest_shlexec_dde_ready");
281             SetEvent(dde_ready);
282             CloseHandle(dde_ready);
283 
284             while (GetMessageA(&msg, NULL, 0, 0))
285             {
286                 if (winetest_debug > 2)
287                     trace("msg %d lParam=%ld wParam=%lu\n", msg.message, msg.lParam, msg.wParam);
288                 DispatchMessageA(&msg);
289             }
290 
291             Sleep(500);
292             KillTimer(NULL, timer);
293             hdde = DdeNameService(ddeInst, hszApplication, 0, DNS_UNREGISTER);
294             ok(hdde != NULL, "DdeNameService() failed le=%u\n", GetLastError());
295             ok(DdeFreeStringHandle(ddeInst, hszTopic), "DdeFreeStringHandle(topic)\n");
296             ok(DdeFreeStringHandle(ddeInst, hszApplication), "DdeFreeStringHandle(application)\n");
297             ok(DdeUninitialize(ddeInst), "DdeUninitialize() failed\n");
298         }
299         else
300         {
301             dde_ready = OpenEventA(EVENT_MODIFY_STATE, FALSE, "winetest_shlexec_dde_ready");
302             SetEvent(dde_ready);
303             CloseHandle(dde_ready);
304         }
305 
306         UnmapViewOfFile(shared_block);
307 
308         childPrintf(hFile, "ddeExec=%s\r\n", encodeA(ddeExec));
309     }
310 
311     childPrintf(hFile, "Failures=%d\r\n", winetest_get_failures());
312     CloseHandle(hFile);
313 
314     init_event(filename);
315     SetEvent(hEvent);
316     CloseHandle(hEvent);
317 }
318 
319 static void dump_child_(const char* file, int line)
320 {
321     if (winetest_debug > 1)
322     {
323         char key[18];
324         char* str;
325         int i, c;
326 
327         str=getChildString("Child", "cmdlineA");
328         trace_(file, line)("cmdlineA='%s'\n", str);
329         c=GetPrivateProfileIntA("Child", "argcA", -1, child_file);
330         trace_(file, line)("argcA=%d\n",c);
331         for (i=0;i<c;i++)
332         {
333             sprintf(key, "argvA%d", i);
334             str=getChildString("Child", key);
335             trace_(file, line)("%s='%s'\n", key, str);
336         }
337 
338         c=GetPrivateProfileIntA("Child", "ShlexecVarLE", -1, child_file);
339         trace_(file, line)("ShlexecVarLE=%d\n", c);
340         str=getChildString("Child", "ShlexecVar");
341         trace_(file, line)("ShlexecVar='%s'\n", str);
342 
343         c=GetPrivateProfileIntA("Child", "Failures", -1, child_file);
344         trace_(file, line)("Failures=%d\n", c);
345     }
346 }
347 
348 
349 /***
350  *
351  * Helpers to check the ShellExecute() / child process results.
352  *
353  ***/
354 
355 static char shell_call[2048];
356 static void WINETEST_PRINTF_ATTR(2,3) _okShell(int condition, const char *msg, ...)
357 {
358     va_list valist;
359     char buffer[2048];
360 
361     strcpy(buffer, shell_call);
362     strcat(buffer, " ");
363     va_start(valist, msg);
364     vsprintf(buffer+strlen(buffer), msg, valist);
365     va_end(valist);
366     winetest_ok(condition, "%s", buffer);
367 }
368 #define okShell_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : _okShell
369 #define okShell okShell_(__FILE__, __LINE__)
370 
371 static char assoc_desc[2048];
372 static void reset_association_description(void)
373 {
374     *assoc_desc = '\0';
375 }
376 
377 static void okChildString_(const char* file, int line, const char* key, const char* expected, const char* bad)
378 {
379     char* result;
380     result=getChildString("Child", key);
381     if (!result)
382     {
383         okShell_(file, line)(FALSE, "%s expected '%s', but key not found or empty\n", key, expected);
384         return;
385     }
386     okShell_(file, line)(lstrcmpiA(result, expected) == 0 ||
387                          broken(lstrcmpiA(result, bad) == 0),
388                          "%s expected '%s', got '%s'\n", key, expected, result);
389 }
390 #define okChildString(key, expected) okChildString_(__FILE__, __LINE__, (key), (expected), (expected))
391 #define okChildStringBroken(key, expected, broken) okChildString_(__FILE__, __LINE__, (key), (expected), (broken))
392 
393 static int StrCmpPath(const char* s1, const char* s2)
394 {
395     if (!s1 && !s2) return 0;
396     if (!s2) return 1;
397     if (!s1) return -1;
398     while (*s1)
399     {
400         if (!*s2)
401         {
402             if (*s1=='.')
403                 s1++;
404             return (*s1-*s2);
405         }
406         if ((*s1=='/' || *s1=='\\') && (*s2=='/' || *s2=='\\'))
407         {
408             while (*s1=='/' || *s1=='\\')
409                 s1++;
410             while (*s2=='/' || *s2=='\\')
411                 s2++;
412         }
413         else if (toupper(*s1)==toupper(*s2))
414         {
415             s1++;
416             s2++;
417         }
418         else
419         {
420             return (*s1-*s2);
421         }
422     }
423     if (*s2=='.')
424         s2++;
425     if (*s2)
426         return -1;
427     return 0;
428 }
429 
430 static void okChildPath_(const char* file, int line, const char* key, const char* expected)
431 {
432     char* result;
433     int equal, shortequal;
434     result=getChildString("Child", key);
435     if (!result)
436     {
437         okShell_(file,line)(FALSE, "%s expected '%s', but key not found or empty\n", key, expected);
438         return;
439     }
440     shortequal = FALSE;
441     equal = (StrCmpPath(result, expected) == 0);
442     if (!equal)
443     {
444         char altpath[MAX_PATH];
445         DWORD rc = GetLongPathNameA(expected, altpath, sizeof(altpath));
446         if (0 < rc && rc < sizeof(altpath))
447             equal = (StrCmpPath(result, altpath) == 0);
448         if (!equal)
449         {
450             rc = GetShortPathNameA(expected, altpath, sizeof(altpath));
451             if (0 < rc && rc < sizeof(altpath))
452                 shortequal = (StrCmpPath(result, altpath) == 0);
453         }
454     }
455     okShell_(file,line)(equal || broken(shortequal) /* XP SP1 */,
456                         "%s expected '%s', got '%s'\n", key, expected, result);
457 }
458 #define okChildPath(key, expected) okChildPath_(__FILE__, __LINE__, (key), (expected))
459 
460 static void okChildInt_(const char* file, int line, const char* key, int expected)
461 {
462     INT result;
463     result=GetPrivateProfileIntA("Child", key, expected, child_file);
464     okShell_(file,line)(result == expected,
465                         "%s expected %d, but got %d\n", key, expected, result);
466 }
467 #define okChildInt(key, expected) okChildInt_(__FILE__, __LINE__, (key), (expected))
468 
469 static void okChildIntBroken_(const char* file, int line, const char* key, int expected)
470 {
471     INT result;
472     result=GetPrivateProfileIntA("Child", key, expected, child_file);
473     okShell_(file,line)(result == expected || broken(result != expected),
474                         "%s expected %d, but got %d\n", key, expected, result);
475 }
476 #define okChildIntBroken(key, expected) okChildIntBroken_(__FILE__, __LINE__, (key), (expected))
477 
478 
479 /***
480  *
481  * ShellExecute wrappers
482  *
483  ***/
484 
485 static void strcat_param(char* str, const char* name, const char* param)
486 {
487     if (param)
488     {
489         if (str[strlen(str)-1] == '"')
490             strcat(str, ", ");
491         strcat(str, name);
492         strcat(str, "=\"");
493         strcat(str, param);
494         strcat(str, "\"");
495     }
496 }
497 
498 static int _todo_wait = 0;
499 #define todo_wait for (_todo_wait = 1; _todo_wait; _todo_wait = 0)
500 
501 static int bad_shellexecute = 0;
502 
503 static INT_PTR shell_execute_(const char* file, int line, LPCSTR verb, LPCSTR filename, LPCSTR parameters, LPCSTR directory)
504 {
505     INT_PTR rc, rcEmpty = 0;
506 
507     if(!verb)
508         rcEmpty = shell_execute_(file, line, "", filename, parameters, directory);
509 
510     strcpy(shell_call, "ShellExecute(");
511     strcat_param(shell_call, "verb", verb);
512     strcat_param(shell_call, "file", filename);
513     strcat_param(shell_call, "params", parameters);
514     strcat_param(shell_call, "dir", directory);
515     strcat(shell_call, ")");
516     strcat(shell_call, assoc_desc);
517     if (winetest_debug > 1)
518         trace_(file, line)("Called %s\n", shell_call);
519 
520     DeleteFileA(child_file);
521     SetLastError(0xcafebabe);
522 
523     /* FIXME: We cannot use ShellExecuteEx() here because if there is no
524      * association it displays the 'Open With' dialog and I could not find
525      * a flag to prevent this.
526      */
527     rc=(INT_PTR)ShellExecuteA(NULL, verb, filename, parameters, directory, SW_HIDE);
528 
529     if (rc > 32)
530     {
531         int wait_rc;
532         wait_rc=WaitForSingleObject(hEvent, 5000);
533         if (wait_rc == WAIT_TIMEOUT)
534         {
535             HWND wnd = FindWindowA("#32770", "Windows");
536             if (!wnd)
537                 wnd = FindWindowA("Shell_Flyout", "");
538             if (wnd != NULL)
539             {
540                 SendMessageA(wnd, WM_CLOSE, 0, 0);
541                 win_skip("Skipping shellexecute of file with unassociated extension\n");
542                 skip_noassoc_tests = TRUE;
543                 rc = SE_ERR_NOASSOC;
544             }
545         }
546         todo_wine_if(_todo_wait)
547             okShell_(file, line)(wait_rc==WAIT_OBJECT_0 || rc <= 32,
548                                  "WaitForSingleObject returned %d\n", wait_rc);
549     }
550     /* The child process may have changed the result file, so let profile
551      * functions know about it
552      */
553     WritePrivateProfileStringA(NULL, NULL, NULL, child_file);
554     if (GetFileAttributesA(child_file) != INVALID_FILE_ATTRIBUTES)
555     {
556         int c;
557         dump_child_(file, line);
558         c = GetPrivateProfileIntA("Child", "Failures", -1, child_file);
559         if (c > 0)
560             winetest_add_failures(c);
561         okChildInt_(file, line, "ShlexecVarLE", 0);
562         okChildString_(file, line, "ShlexecVar", "Present", "Present");
563     }
564 
565     if(!verb)
566     {
567         if (rc != rcEmpty && rcEmpty == SE_ERR_NOASSOC) /* NT4 */
568             bad_shellexecute = 1;
569         okShell_(file, line)(rc == rcEmpty ||
570                              broken(rc != rcEmpty && rcEmpty == SE_ERR_NOASSOC) /* NT4 */,
571                              "Got different return value with empty string: %lu %lu\n", rc, rcEmpty);
572     }
573 
574     return rc;
575 }
576 #define shell_execute(verb, filename, parameters, directory) \
577     shell_execute_(__FILE__, __LINE__, verb, filename, parameters, directory)
578 
579 static INT_PTR shell_execute_ex_(const char* file, int line,
580                                  DWORD mask, LPCSTR verb, LPCSTR filename,
581                                  LPCSTR parameters, LPCSTR directory,
582                                  LPCSTR class)
583 {
584     char smask[11];
585     SHELLEXECUTEINFOA sei;
586     BOOL success;
587     INT_PTR rc;
588 
589     /* Add some flags so we can wait for the child process */
590     mask |= SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
591 
592     strcpy(shell_call, "ShellExecuteEx(");
593     sprintf(smask, "0x%x", mask);
594     strcat_param(shell_call, "mask", smask);
595     strcat_param(shell_call, "verb", verb);
596     strcat_param(shell_call, "file", filename);
597     strcat_param(shell_call, "params", parameters);
598     strcat_param(shell_call, "dir", directory);
599     strcat_param(shell_call, "class", class);
600     strcat(shell_call, ")");
601     strcat(shell_call, assoc_desc);
602     if (winetest_debug > 1)
603         trace_(file, line)("Called %s\n", shell_call);
604 
605     sei.cbSize=sizeof(sei);
606     sei.fMask=mask;
607     sei.hwnd=NULL;
608     sei.lpVerb=verb;
609     sei.lpFile=filename;
610     sei.lpParameters=parameters;
611     sei.lpDirectory=directory;
612     sei.nShow=SW_SHOWNORMAL;
613     sei.hInstApp=NULL; /* Out */
614     sei.lpIDList=NULL;
615     sei.lpClass=class;
616     sei.hkeyClass=NULL;
617     sei.dwHotKey=0;
618     U(sei).hIcon=NULL;
619     sei.hProcess=(HANDLE)0xdeadbeef; /* Out */
620 
621     DeleteFileA(child_file);
622     SetLastError(0xcafebabe);
623     success=ShellExecuteExA(&sei);
624     rc=(INT_PTR)sei.hInstApp;
625     okShell_(file, line)((success && rc > 32) || (!success && rc <= 32),
626                          "rc=%d and hInstApp=%ld is not allowed\n",
627                          success, rc);
628 
629     if (rc > 32)
630     {
631         DWORD wait_rc, rc;
632         if (sei.hProcess!=NULL)
633         {
634             wait_rc=WaitForSingleObject(sei.hProcess, 5000);
635             okShell_(file, line)(wait_rc==WAIT_OBJECT_0,
636                                  "WaitForSingleObject(hProcess) returned %d\n",
637                                  wait_rc);
638             wait_rc = GetExitCodeProcess(sei.hProcess, &rc);
639             okShell_(file, line)(wait_rc, "GetExitCodeProcess() failed le=%u\n", GetLastError());
640             todo_wine_if(_todo_wait)
641                 okShell_(file, line)(rc == 0, "child returned %u\n", rc);
642             CloseHandle(sei.hProcess);
643         }
644         wait_rc=WaitForSingleObject(hEvent, 5000);
645         todo_wine_if(_todo_wait)
646             okShell_(file, line)(wait_rc==WAIT_OBJECT_0,
647                                  "WaitForSingleObject returned %d\n", wait_rc);
648     }
649     else
650         okShell_(file, line)(sei.hProcess==NULL,
651                              "returned a process handle %p\n", sei.hProcess);
652 
653     /* The child process may have changed the result file, so let profile
654      * functions know about it
655      */
656     WritePrivateProfileStringA(NULL, NULL, NULL, child_file);
657     if (rc > 32 && GetFileAttributesA(child_file) != INVALID_FILE_ATTRIBUTES)
658     {
659         int c;
660         dump_child_(file, line);
661         c = GetPrivateProfileIntA("Child", "Failures", -1, child_file);
662         if (c > 0)
663             winetest_add_failures(c);
664         /* When NOZONECHECKS is specified the environment variables are not
665          * inherited if the process does not have elevated privileges.
666          */
667         if ((mask & SEE_MASK_NOZONECHECKS) && skip_shlexec_tests)
668         {
669             okChildInt_(file, line, "ShlexecVarLE", 203);
670             okChildString_(file, line, "ShlexecVar", "", "");
671         }
672         else
673         {
674             okChildInt_(file, line, "ShlexecVarLE", 0);
675             okChildString_(file, line, "ShlexecVar", "Present", "Present");
676         }
677     }
678 
679     return rc;
680 }
681 #define shell_execute_ex(mask, verb, filename, parameters, directory, class) \
682     shell_execute_ex_(__FILE__, __LINE__, mask, verb, filename, parameters, directory, class)
683 
684 
685 /***
686  *
687  * Functions to create / delete associations wrappers
688  *
689  ***/
690 
691 static BOOL create_test_class(const char* class, BOOL protocol)
692 {
693     HKEY hkey, hkey_shell;
694     LONG rc;
695 
696     rc = RegCreateKeyExA(HKEY_CLASSES_ROOT, class, 0, NULL, 0,
697                          KEY_CREATE_SUB_KEY | KEY_SET_VALUE, NULL,
698                          &hkey, NULL);
699     ok(rc == ERROR_SUCCESS || rc == ERROR_ACCESS_DENIED,
700        "could not create class %s (rc=%d)\n", class, rc);
701     if (rc != ERROR_SUCCESS)
702         return FALSE;
703 
704     if (protocol)
705     {
706         rc = RegSetValueExA(hkey, "URL Protocol", 0, REG_SZ, (LPBYTE)"", 1);
707         ok(rc == ERROR_SUCCESS, "RegSetValueEx '%s' failed, expected ERROR_SUCCESS, got %d\n", class, rc);
708     }
709 
710     rc = RegCreateKeyExA(hkey, "shell", 0, NULL, 0,
711                          KEY_CREATE_SUB_KEY, NULL, &hkey_shell, NULL);
712     ok(rc == ERROR_SUCCESS, "RegCreateKeyEx 'shell' failed, expected ERROR_SUCCESS, got %d\n", rc);
713 
714     CloseHandle(hkey);
715     CloseHandle(hkey_shell);
716     return TRUE;
717 }
718 
719 static BOOL create_test_association(const char* extension)
720 {
721     HKEY hkey;
722     char class[MAX_PATH];
723     LONG rc;
724 
725     sprintf(class, "shlexec%s", extension);
726     rc=RegCreateKeyExA(HKEY_CLASSES_ROOT, extension, 0, NULL, 0, KEY_SET_VALUE,
727                        NULL, &hkey, NULL);
728     ok(rc == ERROR_SUCCESS || rc == ERROR_ACCESS_DENIED,
729        "could not create association %s (rc=%d)\n", class, rc);
730     if (rc != ERROR_SUCCESS)
731         return FALSE;
732 
733     rc=RegSetValueExA(hkey, NULL, 0, REG_SZ, (LPBYTE) class, strlen(class)+1);
734     ok(rc==ERROR_SUCCESS, "RegSetValueEx '%s' failed, expected ERROR_SUCCESS, got %d\n", class, rc);
735     CloseHandle(hkey);
736 
737     return create_test_class(class, FALSE);
738 }
739 
740 /* Based on RegDeleteTreeW from dlls/advapi32/registry.c */
741 static LSTATUS myRegDeleteTreeA(HKEY hKey, LPCSTR lpszSubKey)
742 {
743     LONG ret;
744     DWORD dwMaxSubkeyLen, dwMaxValueLen;
745     DWORD dwMaxLen, dwSize;
746     CHAR szNameBuf[MAX_PATH], *lpszName = szNameBuf;
747     HKEY hSubKey = hKey;
748 
749     if(lpszSubKey)
750     {
751         ret = RegOpenKeyExA(hKey, lpszSubKey, 0, KEY_READ, &hSubKey);
752         if (ret) return ret;
753     }
754 
755     /* Get highest length for keys, values */
756     ret = RegQueryInfoKeyA(hSubKey, NULL, NULL, NULL, NULL,
757             &dwMaxSubkeyLen, NULL, NULL, &dwMaxValueLen, NULL, NULL, NULL);
758     if (ret) goto cleanup;
759 
760     dwMaxSubkeyLen++;
761     dwMaxValueLen++;
762     dwMaxLen = max(dwMaxSubkeyLen, dwMaxValueLen);
763     if (dwMaxLen > sizeof(szNameBuf)/sizeof(CHAR))
764     {
765         /* Name too big: alloc a buffer for it */
766         if (!(lpszName = heap_alloc(dwMaxLen*sizeof(CHAR))))
767         {
768             ret = ERROR_NOT_ENOUGH_MEMORY;
769             goto cleanup;
770         }
771     }
772 
773 
774     /* Recursively delete all the subkeys */
775     while (TRUE)
776     {
777         dwSize = dwMaxLen;
778         if (RegEnumKeyExA(hSubKey, 0, lpszName, &dwSize, NULL,
779                           NULL, NULL, NULL)) break;
780 
781         ret = myRegDeleteTreeA(hSubKey, lpszName);
782         if (ret) goto cleanup;
783     }
784 
785     if (lpszSubKey)
786         ret = RegDeleteKeyA(hKey, lpszSubKey);
787     else
788         while (TRUE)
789         {
790             dwSize = dwMaxLen;
791             if (RegEnumValueA(hKey, 0, lpszName, &dwSize,
792                   NULL, NULL, NULL, NULL)) break;
793 
794             ret = RegDeleteValueA(hKey, lpszName);
795             if (ret) goto cleanup;
796         }
797 
798 cleanup:
799     /* Free buffer if allocated */
800     if (lpszName != szNameBuf)
801         heap_free(lpszName);
802     if(lpszSubKey)
803         RegCloseKey(hSubKey);
804     return ret;
805 }
806 
807 static void delete_test_class(const char* classname)
808 {
809     myRegDeleteTreeA(HKEY_CLASSES_ROOT, classname);
810 }
811 
812 static void delete_test_association(const char* extension)
813 {
814     char classname[MAX_PATH];
815 
816     sprintf(classname, "shlexec%s", extension);
817     delete_test_class(classname);
818     myRegDeleteTreeA(HKEY_CLASSES_ROOT, extension);
819 }
820 
821 static void create_test_verb_dde(const char* classname, const char* verb,
822                                  int rawcmd, const char* cmdtail, const char *ddeexec,
823                                  const char *application, const char *topic,
824                                  const char *ifexec)
825 {
826     HKEY hkey_shell, hkey_verb, hkey_cmd;
827     char shell[MAX_PATH];
828     char* cmd;
829     LONG rc;
830 
831     strcpy(assoc_desc, " Assoc ");
832     strcat_param(assoc_desc, "class", classname);
833     strcat_param(assoc_desc, "verb", verb);
834     sprintf(shell, "%d", rawcmd);
835     strcat_param(assoc_desc, "rawcmd", shell);
836     strcat_param(assoc_desc, "cmdtail", cmdtail);
837     strcat_param(assoc_desc, "ddeexec", ddeexec);
838     strcat_param(assoc_desc, "app", application);
839     strcat_param(assoc_desc, "topic", topic);
840     strcat_param(assoc_desc, "ifexec", ifexec);
841 
842     sprintf(shell, "%s\\shell", classname);
843     rc=RegOpenKeyExA(HKEY_CLASSES_ROOT, shell, 0,
844                      KEY_CREATE_SUB_KEY, &hkey_shell);
845     ok(rc == ERROR_SUCCESS, "%s key creation failed with %d\n", shell, rc);
846 
847     rc=RegCreateKeyExA(hkey_shell, verb, 0, NULL, 0, KEY_CREATE_SUB_KEY,
848                        NULL, &hkey_verb, NULL);
849     ok(rc == ERROR_SUCCESS, "%s verb key creation failed with %d\n", verb, rc);
850 
851     rc=RegCreateKeyExA(hkey_verb, "command", 0, NULL, 0, KEY_SET_VALUE,
852                        NULL, &hkey_cmd, NULL);
853     ok(rc == ERROR_SUCCESS, "\'command\' key creation failed with %d\n", rc);
854 
855     if (rawcmd)
856     {
857         rc=RegSetValueExA(hkey_cmd, NULL, 0, REG_SZ, (LPBYTE)cmdtail, strlen(cmdtail)+1);
858     }
859     else
860     {
861         cmd = heap_alloc(strlen(argv0) + 10 + strlen(child_file) + 2 + strlen(cmdtail) + 1);
862         sprintf(cmd,"%s shlexec \"%s\" %s", argv0, child_file, cmdtail);
863         rc=RegSetValueExA(hkey_cmd, NULL, 0, REG_SZ, (LPBYTE)cmd, strlen(cmd)+1);
864         ok(rc == ERROR_SUCCESS, "setting command failed with %d\n", rc);
865         heap_free(cmd);
866     }
867 
868     if (ddeexec)
869     {
870         HKEY hkey_ddeexec, hkey_application, hkey_topic, hkey_ifexec;
871 
872         rc=RegCreateKeyExA(hkey_verb, "ddeexec", 0, NULL, 0, KEY_SET_VALUE |
873                            KEY_CREATE_SUB_KEY, NULL, &hkey_ddeexec, NULL);
874         ok(rc == ERROR_SUCCESS, "\'ddeexec\' key creation failed with %d\n", rc);
875         rc=RegSetValueExA(hkey_ddeexec, NULL, 0, REG_SZ, (LPBYTE)ddeexec,
876                           strlen(ddeexec)+1);
877         ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);
878 
879         if (application)
880         {
881             rc=RegCreateKeyExA(hkey_ddeexec, "application", 0, NULL, 0, KEY_SET_VALUE,
882                                NULL, &hkey_application, NULL);
883             ok(rc == ERROR_SUCCESS, "\'application\' key creation failed with %d\n", rc);
884 
885             rc=RegSetValueExA(hkey_application, NULL, 0, REG_SZ, (LPBYTE)application,
886                               strlen(application)+1);
887             ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);
888             CloseHandle(hkey_application);
889         }
890         if (topic)
891         {
892             rc=RegCreateKeyExA(hkey_ddeexec, "topic", 0, NULL, 0, KEY_SET_VALUE,
893                                NULL, &hkey_topic, NULL);
894             ok(rc == ERROR_SUCCESS, "\'topic\' key creation failed with %d\n", rc);
895             rc=RegSetValueExA(hkey_topic, NULL, 0, REG_SZ, (LPBYTE)topic,
896                               strlen(topic)+1);
897             ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);
898             CloseHandle(hkey_topic);
899         }
900         if (ifexec)
901         {
902             rc=RegCreateKeyExA(hkey_ddeexec, "ifexec", 0, NULL, 0, KEY_SET_VALUE,
903                                NULL, &hkey_ifexec, NULL);
904             ok(rc == ERROR_SUCCESS, "\'ifexec\' key creation failed with %d\n", rc);
905             rc=RegSetValueExA(hkey_ifexec, NULL, 0, REG_SZ, (LPBYTE)ifexec,
906                               strlen(ifexec)+1);
907             ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);
908             CloseHandle(hkey_ifexec);
909         }
910         CloseHandle(hkey_ddeexec);
911     }
912 
913     CloseHandle(hkey_shell);
914     CloseHandle(hkey_verb);
915     CloseHandle(hkey_cmd);
916 }
917 
918 /* Creates a class' non-DDE test verb.
919  * This function is meant to be used to create long term test verbs and thus
920  * does not trace them.
921  */
922 static void create_test_verb(const char* classname, const char* verb,
923                              int rawcmd, const char* cmdtail)
924 {
925     create_test_verb_dde(classname, verb, rawcmd, cmdtail, NULL, NULL,
926                          NULL, NULL);
927     reset_association_description();
928 }
929 
930 
931 /***
932  *
933  * GetLongPathNameA equivalent that supports Win95 and WinNT
934  *
935  ***/
936 
937 static DWORD get_long_path_name(const char* shortpath, char* longpath, DWORD longlen)
938 {
939     char tmplongpath[MAX_PATH];
940     const char* p;
941     DWORD sp = 0, lp = 0;
942     DWORD tmplen;
943     WIN32_FIND_DATAA wfd;
944     HANDLE goit;
945 
946     if (!shortpath || !shortpath[0])
947         return 0;
948 
949     if (shortpath[1] == ':')
950     {
951         tmplongpath[0] = shortpath[0];
952         tmplongpath[1] = ':';
953         lp = sp = 2;
954     }
955 
956     while (shortpath[sp])
957     {
958         /* check for path delimiters and reproduce them */
959         if (shortpath[sp] == '\\' || shortpath[sp] == '/')
960         {
961             if (!lp || tmplongpath[lp-1] != '\\')
962             {
963                 /* strip double "\\" */
964                 tmplongpath[lp++] = '\\';
965             }
966             tmplongpath[lp] = 0; /* terminate string */
967             sp++;
968             continue;
969         }
970 
971         p = shortpath + sp;
972         if (sp == 0 && p[0] == '.' && (p[1] == '/' || p[1] == '\\'))
973         {
974             tmplongpath[lp++] = *p++;
975             tmplongpath[lp++] = *p++;
976         }
977         for (; *p && *p != '/' && *p != '\\'; p++);
978         tmplen = p - (shortpath + sp);
979         lstrcpynA(tmplongpath + lp, shortpath + sp, tmplen + 1);
980         /* Check if the file exists and use the existing file name */
981         goit = FindFirstFileA(tmplongpath, &wfd);
982         if (goit == INVALID_HANDLE_VALUE)
983             return 0;
984         FindClose(goit);
985         strcpy(tmplongpath + lp, wfd.cFileName);
986         lp += strlen(tmplongpath + lp);
987         sp += tmplen;
988     }
989     tmplen = strlen(shortpath) - 1;
990     if ((shortpath[tmplen] == '/' || shortpath[tmplen] == '\\') &&
991         (tmplongpath[lp - 1] != '/' && tmplongpath[lp - 1] != '\\'))
992         tmplongpath[lp++] = shortpath[tmplen];
993     tmplongpath[lp] = 0;
994 
995     tmplen = strlen(tmplongpath) + 1;
996     if (tmplen <= longlen)
997     {
998         strcpy(longpath, tmplongpath);
999         tmplen--; /* length without 0 */
1000     }
1001 
1002     return tmplen;
1003 }
1004 
1005 
1006 /***
1007  *
1008  * Tests
1009  *
1010  ***/
1011 
1012 static const char* testfiles[]=
1013 {
1014     "%s\\test file.shlexec",
1015     "%s\\%%nasty%% $file.shlexec",
1016     "%s\\test file.noassoc",
1017     "%s\\test file.noassoc.shlexec",
1018     "%s\\test file.shlexec.noassoc",
1019     "%s\\test_shortcut_shlexec.lnk",
1020     "%s\\test_shortcut_exe.lnk",
1021     "%s\\test file.shl",
1022     "%s\\test file.shlfoo",
1023     "%s\\test file.sha",
1024     "%s\\test file.sfe",
1025     "%s\\test file.shlproto",
1026     "%s\\masked file.shlexec",
1027     "%s\\masked",
1028     "%s\\test file.sde",
1029     "%s\\test file.exe",
1030     "%s\\test2.exe",
1031     "%s\\simple.shlexec",
1032     "%s\\drawback_file.noassoc",
1033     "%s\\drawback_file.noassoc foo.shlexec",
1034     "%s\\drawback_nonexist.noassoc foo.shlexec",
1035     NULL
1036 };
1037 
1038 typedef struct
1039 {
1040     const char* verb;
1041     const char* basename;
1042     int todo;
1043     INT_PTR rc;
1044 } filename_tests_t;
1045 
1046 static filename_tests_t filename_tests[]=
1047 {
1048     /* Test bad / nonexistent filenames */
1049     {NULL,           "%s\\nonexistent.shlexec", 0x0, SE_ERR_FNF},
1050     {NULL,           "%s\\nonexistent.noassoc", 0x0, SE_ERR_FNF},
1051 
1052     /* Standard tests */
1053     {NULL,           "%s\\test file.shlexec",   0x0, 33},
1054     {NULL,           "%s\\test file.shlexec.",  0x0, 33},
1055     {NULL,           "%s\\%%nasty%% $file.shlexec", 0x0, 33},
1056     {NULL,           "%s/test file.shlexec",    0x0, 33},
1057 
1058     /* Test filenames with no association */
1059     {NULL,           "%s\\test file.noassoc",   0x0,  SE_ERR_NOASSOC},
1060 
1061     /* Test double extensions */
1062     {NULL,           "%s\\test file.noassoc.shlexec", 0x0, 33},
1063     {NULL,           "%s\\test file.shlexec.noassoc", 0x0, SE_ERR_NOASSOC},
1064 
1065     /* Test alternate verbs */
1066     {"LowerL",       "%s\\nonexistent.shlexec", 0x0, SE_ERR_FNF},
1067     {"LowerL",       "%s\\test file.noassoc",   0x0,  SE_ERR_NOASSOC},
1068 
1069     {"QuotedLowerL", "%s\\test file.shlexec",   0x0, 33},
1070     {"QuotedUpperL", "%s\\test file.shlexec",   0x0, 33},
1071 
1072     {"notaverb",     "%s\\test file.shlexec",   0x10, SE_ERR_NOASSOC},
1073 
1074     {"averb",        "%s\\test file.sha",       0x10, 33},
1075 
1076     /* Test file masked due to space */
1077     {NULL,           "%s\\masked file.shlexec",   0x0, 33},
1078     /* Test if quoting prevents the masking */
1079     {NULL,           "%s\\masked file.shlexec",   0x40, 33},
1080     /* Test with incorrect quote */
1081     {NULL,           "\"%s\\masked file.shlexec",   0x0, SE_ERR_FNF},
1082 
1083     /* Test extension / URI protocol collision */
1084     {NULL,           "%s\\test file.shlproto",   0x0, SE_ERR_NOASSOC},
1085 
1086     {NULL, NULL, 0}
1087 };
1088 
1089 static filename_tests_t noquotes_tests[]=
1090 {
1091     /* Test unquoted '%1' thingies */
1092     {"NoQuotes",     "%s\\test file.shlexec",   0xa, 33},
1093     {"LowerL",       "%s\\test file.shlexec",   0xa, 33},
1094     {"UpperL",       "%s\\test file.shlexec",   0xa, 33},
1095 
1096     {NULL, NULL, 0}
1097 };
1098 
1099 static void test_lpFile_parsed(void)
1100 {
1101     char fileA[MAX_PATH];
1102     INT_PTR rc;
1103 
1104     if (skip_shlexec_tests)
1105     {
1106         skip("No filename parsing tests due to lack of .shlexec association\n");
1107         return;
1108     }
1109 
1110     /* existing "drawback_file.noassoc" prevents finding "drawback_file.noassoc foo.shlexec" on wine */
1111     sprintf(fileA, "%s\\drawback_file.noassoc foo.shlexec", tmpdir);
1112     rc=shell_execute(NULL, fileA, NULL, NULL);
1113     okShell(rc > 32, "failed: rc=%lu\n", rc);
1114 
1115     /* if quoted, existing "drawback_file.noassoc" not prevents finding "drawback_file.noassoc foo.shlexec" on wine */
1116     sprintf(fileA, "\"%s\\drawback_file.noassoc foo.shlexec\"", tmpdir);
1117     rc=shell_execute(NULL, fileA, NULL, NULL);
1118     okShell(rc > 32 || broken(rc == SE_ERR_FNF) /* Win95/NT4 */,
1119             "failed: rc=%lu\n", rc);
1120 
1121     /* error should be SE_ERR_FNF, not SE_ERR_NOASSOC */
1122     sprintf(fileA, "\"%s\\drawback_file.noassoc\" foo.shlexec", tmpdir);
1123     rc=shell_execute(NULL, fileA, NULL, NULL);
1124     okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
1125 
1126     /* ""command"" not works on wine (and real win9x and w2k) */
1127     sprintf(fileA, "\"\"%s\\simple.shlexec\"\"", tmpdir);
1128     rc=shell_execute(NULL, fileA, NULL, NULL);
1129     todo_wine okShell(rc > 32 || broken(rc == SE_ERR_FNF) /* Win9x/2000 */,
1130                       "failed: rc=%lu\n", rc);
1131 
1132     /* nonexisting "drawback_nonexist.noassoc" not prevents finding "drawback_nonexist.noassoc foo.shlexec" on wine */
1133     sprintf(fileA, "%s\\drawback_nonexist.noassoc foo.shlexec", tmpdir);
1134     rc=shell_execute(NULL, fileA, NULL, NULL);
1135     okShell(rc > 32, "failed: rc=%lu\n", rc);
1136 
1137     /* is SEE_MASK_DOENVSUBST default flag? Should only be when XP emulates 9x (XP bug or real 95 or ME behavior ?) */
1138     rc=shell_execute(NULL, "%TMPDIR%\\simple.shlexec", NULL, NULL);
1139     todo_wine okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
1140 
1141     /* quoted */
1142     rc=shell_execute(NULL, "\"%TMPDIR%\\simple.shlexec\"", NULL, NULL);
1143     todo_wine okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
1144 
1145     /* test SEE_MASK_DOENVSUBST works */
1146     rc=shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI,
1147                         NULL, "%TMPDIR%\\simple.shlexec", NULL, NULL, NULL);
1148     okShell(rc > 32, "failed: rc=%lu\n", rc);
1149 
1150     /* quoted lpFile does not work on real win95 and nt4 */
1151     rc=shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI,
1152                         NULL, "\"%TMPDIR%\\simple.shlexec\"", NULL, NULL, NULL);
1153     okShell(rc > 32 || broken(rc == SE_ERR_FNF) /* Win95/NT4 */,
1154             "failed: rc=%lu\n", rc);
1155 }
1156 
1157 typedef struct
1158 {
1159     const char* cmd;
1160     const char* args[11];
1161     int todo;
1162 } cmdline_tests_t;
1163 
1164 static const cmdline_tests_t cmdline_tests[] =
1165 {
1166     {"exe",
1167      {"exe", NULL}, 0},
1168 
1169     {"exe arg1 arg2 \"arg three\" 'four five` six\\ $even)",
1170      {"exe", "arg1", "arg2", "arg three", "'four", "five`", "six\\", "$even)", NULL}, 0},
1171 
1172     {"exe arg=1 arg-2 three\tfour\rfour\nfour ",
1173      {"exe", "arg=1", "arg-2", "three", "four\rfour\nfour", NULL}, 0},
1174 
1175     {"exe arg\"one\" \"second\"arg thirdarg ",
1176      {"exe", "argone", "secondarg", "thirdarg", NULL}, 0},
1177 
1178     /* Don't lose unclosed quoted arguments */
1179     {"exe arg1 \"unclosed",
1180      {"exe", "arg1", "unclosed", NULL}, 0},
1181 
1182     {"exe arg1 \"",
1183      {"exe", "arg1", "", NULL}, 0},
1184 
1185     /* cmd's metacharacters have no special meaning */
1186     {"exe \"one^\" \"arg\"&two three|four",
1187      {"exe", "one^", "arg&two", "three|four", NULL}, 0},
1188 
1189     /* Environment variables are not interpreted either */
1190     {"exe %TMPDIR% %2",
1191      {"exe", "%TMPDIR%", "%2", NULL}, 0},
1192 
1193     /* If not followed by a quote, backslashes go through as is */
1194     {"exe o\\ne t\\\\wo t\\\\\\ree f\\\\\\\\our ",
1195      {"exe", "o\\ne", "t\\\\wo", "t\\\\\\ree", "f\\\\\\\\our", NULL}, 0},
1196 
1197     {"exe \"o\\ne\" \"t\\\\wo\" \"t\\\\\\ree\" \"f\\\\\\\\our\" ",
1198      {"exe", "o\\ne", "t\\\\wo", "t\\\\\\ree", "f\\\\\\\\our", NULL}, 0},
1199 
1200     /* When followed by a quote their number is halved and the remainder
1201      * escapes the quote
1202      */
1203     {"exe \\\"one \\\\\"two\" \\\\\\\"three \\\\\\\\\"four\" end",
1204      {"exe", "\"one", "\\two", "\\\"three", "\\\\four", "end", NULL}, 0},
1205 
1206     {"exe \"one\\\" still\" \"two\\\\\" \"three\\\\\\\" still\" \"four\\\\\\\\\" end",
1207      {"exe", "one\" still", "two\\", "three\\\" still", "four\\\\", "end", NULL}, 0},
1208 
1209     /* One can put a quote in an unquoted string by tripling it, that is in
1210      * effect quoting it like so """ -> ". The general rule is as follows:
1211      * 3n   quotes -> n quotes
1212      * 3n+1 quotes -> n quotes plus start of a quoted string
1213      * 3n+2 quotes -> n quotes (plus an empty string from the remaining pair)
1214      * Nicely, when n is 0 we get the standard rules back.
1215      */
1216     {"exe two\"\"quotes next",
1217      {"exe", "twoquotes", "next", NULL}, 0},
1218 
1219     {"exe three\"\"\"quotes next",
1220      {"exe", "three\"quotes", "next", NULL}, 0},
1221 
1222     {"exe four\"\"\"\" quotes\" next 4%3=1",
1223      {"exe", "four\" quotes", "next", "4%3=1", NULL}, 0},
1224 
1225     {"exe five\"\"\"\"\"quotes next",
1226      {"exe", "five\"quotes", "next", NULL}, 0},
1227 
1228     {"exe six\"\"\"\"\"\"quotes next",
1229      {"exe", "six\"\"quotes", "next", NULL}, 0},
1230 
1231     {"exe seven\"\"\"\"\"\"\" quotes\" next 7%3=1",
1232      {"exe", "seven\"\" quotes", "next", "7%3=1", NULL}, 0},
1233 
1234     {"exe twelve\"\"\"\"\"\"\"\"\"\"\"\"quotes next",
1235      {"exe", "twelve\"\"\"\"quotes", "next", NULL}, 0},
1236 
1237     {"exe thirteen\"\"\"\"\"\"\"\"\"\"\"\"\" quotes\" next 13%3=1",
1238      {"exe", "thirteen\"\"\"\" quotes", "next", "13%3=1", NULL}, 0},
1239 
1240     /* Inside a quoted string the opening quote is added to the set of
1241      * consecutive quotes to get the effective quotes count. This gives:
1242      * 1+3n   quotes -> n quotes
1243      * 1+3n+1 quotes -> n quotes plus closes the quoted string
1244      * 1+3n+2 quotes -> n+1 quotes plus closes the quoted string
1245      */
1246     {"exe \"two\"\"quotes next",
1247      {"exe", "two\"quotes", "next", NULL}, 0},
1248 
1249     {"exe \"two\"\" next",
1250      {"exe", "two\"", "next", NULL}, 0},
1251 
1252     {"exe \"three\"\"\" quotes\" next 4%3=1",
1253      {"exe", "three\" quotes", "next", "4%3=1", NULL}, 0},
1254 
1255     {"exe \"four\"\"\"\"quotes next",
1256      {"exe", "four\"quotes", "next", NULL}, 0},
1257 
1258     {"exe \"five\"\"\"\"\"quotes next",
1259      {"exe", "five\"\"quotes", "next", NULL}, 0},
1260 
1261     {"exe \"six\"\"\"\"\"\" quotes\" next 7%3=1",
1262      {"exe", "six\"\" quotes", "next", "7%3=1", NULL}, 0},
1263 
1264     {"exe \"eleven\"\"\"\"\"\"\"\"\"\"\"quotes next",
1265      {"exe", "eleven\"\"\"\"quotes", "next", NULL}, 0},
1266 
1267     {"exe \"twelve\"\"\"\"\"\"\"\"\"\"\"\" quotes\" next 13%3=1",
1268      {"exe", "twelve\"\"\"\" quotes", "next", "13%3=1", NULL}, 0},
1269 
1270     /* Escaped consecutive quotes are fun */
1271     {"exe \"the crazy \\\\\"\"\"\\\\\" quotes",
1272      {"exe", "the crazy \\\"\\", "quotes", NULL}, 0},
1273 
1274     /* The executable path has its own rules!!!
1275      * - Backslashes have no special meaning.
1276      * - If the first character is a quote, then the second quote ends the
1277      *   executable path.
1278      * - The previous rule holds even if the next character is not a space!
1279      * - If the first character is not a quote, then quotes have no special
1280      *   meaning either and the executable path stops at the first space.
1281      * - The consecutive quotes rules don't apply either.
1282      * - Even if there is no space between the executable path and the first
1283      *   argument, the latter is parsed using the regular rules.
1284      */
1285     {"exe\"file\"path arg1",
1286      {"exe\"file\"path", "arg1", NULL}, 0},
1287 
1288     {"exe\"file\"path\targ1",
1289      {"exe\"file\"path", "arg1", NULL}, 0},
1290 
1291     {"exe\"path\\ arg1",
1292      {"exe\"path\\", "arg1", NULL}, 0},
1293 
1294     {"\\\"exe \"arg one\"",
1295      {"\\\"exe", "arg one", NULL}, 0},
1296 
1297     {"\"spaced exe\" \"next arg\"",
1298      {"spaced exe", "next arg", NULL}, 0},
1299 
1300     {"\"spaced exe\"\t\"next arg\"",
1301      {"spaced exe", "next arg", NULL}, 0},
1302 
1303     {"\"exe\"arg\" one\" argtwo",
1304      {"exe", "arg one", "argtwo", NULL}, 0},
1305 
1306     {"\"spaced exe\\\"arg1 arg2",
1307      {"spaced exe\\", "arg1", "arg2", NULL}, 0},
1308 
1309     {"\"two\"\" arg1 ",
1310      {"two", " arg1 ", NULL}, 0},
1311 
1312     {"\"three\"\"\" arg2",
1313      {"three", "", "arg2", NULL}, 0},
1314 
1315     {"\"four\"\"\"\"arg1",
1316      {"four", "\"arg1", NULL}, 0},
1317 
1318     /* If the first character is a space then the executable path is empty */
1319     {" \"arg\"one argtwo",
1320      {"", "argone", "argtwo", NULL}, 0},
1321 
1322     {NULL, {NULL}, 0}
1323 };
1324 
1325 static BOOL test_one_cmdline(const cmdline_tests_t* test)
1326 {
1327     WCHAR cmdW[MAX_PATH], argW[MAX_PATH];
1328     LPWSTR *cl2a;
1329     int cl2a_count;
1330     LPWSTR *argsW;
1331     int i, count;
1332 
1333     /* trace("----- cmd='%s'\n", test->cmd); */
1334     MultiByteToWideChar(CP_ACP, 0, test->cmd, -1, cmdW, sizeof(cmdW)/sizeof(*cmdW));
1335     argsW = cl2a = CommandLineToArgvW(cmdW, &cl2a_count);
1336     if (argsW == NULL && cl2a_count == -1)
1337     {
1338         win_skip("CommandLineToArgvW not implemented, skipping\n");
1339         return FALSE;
1340     }
1341     ok(!argsW[cl2a_count] || broken(argsW[cl2a_count] != NULL) /* before Vista */,
1342        "expected NULL-terminated list of commandline arguments\n");
1343 
1344     count = 0;
1345     while (test->args[count])
1346         count++;
1347     todo_wine_if(test->todo & 0x1)
1348         ok(cl2a_count == count, "%s: expected %d arguments, but got %d\n", test->cmd, count, cl2a_count);
1349 
1350     for (i = 0; i < cl2a_count; i++)
1351     {
1352         if (i < count)
1353         {
1354             MultiByteToWideChar(CP_ACP, 0, test->args[i], -1, argW, sizeof(argW)/sizeof(*argW));
1355             todo_wine_if(test->todo & (1 << (i+4)))
1356                 ok(!lstrcmpW(*argsW, argW), "%s: arg[%d] expected %s but got %s\n", test->cmd, i, wine_dbgstr_w(argW), wine_dbgstr_w(*argsW));
1357         }
1358         else todo_wine_if(test->todo & 0x1)
1359             ok(0, "%s: got extra arg[%d]=%s\n", test->cmd, i, wine_dbgstr_w(*argsW));
1360         argsW++;
1361     }
1362     LocalFree(cl2a);
1363     return TRUE;
1364 }
1365 
1366 static void test_commandline2argv(void)
1367 {
1368     static const WCHAR exeW[] = {'e','x','e',0};
1369     const cmdline_tests_t* test;
1370     WCHAR strW[MAX_PATH];
1371     LPWSTR *args;
1372     int numargs;
1373     DWORD le;
1374 
1375     test = cmdline_tests;
1376     while (test->cmd)
1377     {
1378         if (!test_one_cmdline(test))
1379             return;
1380         test++;
1381     }
1382 
1383     SetLastError(0xdeadbeef);
1384     args = CommandLineToArgvW(exeW, NULL);
1385     le = GetLastError();
1386     ok(args == NULL && le == ERROR_INVALID_PARAMETER, "expected NULL with ERROR_INVALID_PARAMETER got %p with %u\n", args, le);
1387 
1388     SetLastError(0xdeadbeef);
1389     args = CommandLineToArgvW(NULL, NULL);
1390     le = GetLastError();
1391     ok(args == NULL && le == ERROR_INVALID_PARAMETER, "expected NULL with ERROR_INVALID_PARAMETER got %p with %u\n", args, le);
1392 
1393     *strW = 0;
1394     args = CommandLineToArgvW(strW, &numargs);
1395     ok(numargs == 1 || broken(numargs > 1), "expected 1 args, got %d\n", numargs);
1396     ok(!args || (!args[numargs] || broken(args[numargs] != NULL) /* before Vista */),
1397        "expected NULL-terminated list of commandline arguments\n");
1398     if (numargs == 1)
1399     {
1400         GetModuleFileNameW(NULL, strW, sizeof(strW)/sizeof(*strW));
1401         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));
1402     }
1403     if (args) LocalFree(args);
1404 }
1405 
1406 /* The goal here is to analyze how ShellExecute() builds the command that
1407  * will be run. The tricky part is that there are three transformation
1408  * steps between the 'parameters' string we pass to ShellExecute() and the
1409  * argument list we observe in the child process:
1410  * - The parsing of 'parameters' string into individual arguments. The tests
1411  *   show this is done differently from both CreateProcess() and
1412  *   CommandLineToArgv()!
1413  * - The way the command 'formatting directives' such as %1, %2, etc are
1414  *   handled.
1415  * - And the way the resulting command line is then parsed to yield the
1416  *   argument list we check.
1417  */
1418 typedef struct
1419 {
1420     const char* verb;
1421     const char* params;
1422     int todo;
1423     cmdline_tests_t cmd;
1424     cmdline_tests_t broken;
1425 } argify_tests_t;
1426 
1427 static const argify_tests_t argify_tests[] =
1428 {
1429     /* Start with three simple parameters. Notice that one can reorder and
1430      * duplicate the parameters. Also notice how %* take the raw input
1431      * parameters string, including the trailing spaces, no matter what
1432      * arguments have already been used.
1433      */
1434     {"Params232S", "p2 p3 p4 ", 0xc2,
1435      {" p2 p3 \"p2\" \"p2 p3 p4 \"",
1436       {"", "p2", "p3", "p2", "p2 p3 p4 ", NULL}, 0}},
1437 
1438     /* Unquoted argument references like %2 don't automatically quote their
1439      * argument. Similarly, when they are quoted they don't escape the quotes
1440      * that their argument may contain.
1441      */
1442     {"Params232S", "\"p two\" p3 p4  ", 0x3f3,
1443      {" p two p3 \"p two\" \"\"p two\" p3 p4  \"",
1444       {"", "p", "two", "p3", "p two", "p", "two p3 p4  ", NULL}, 0}},
1445 
1446     /* Only single digits are supported so only %1 to %9. Shown here with %20
1447      * because %10 is a pain.
1448      */
1449     {"Params20", "p", 0,
1450      {" \"p0\"",
1451       {"", "p0", NULL}, 0}},
1452 
1453     /* Only (double-)quotes have a special meaning. */
1454     {"Params23456", "'p2 p3` p4\\ $even", 0x40,
1455      {" \"'p2\" \"p3`\" \"p4\\\" \"$even\" \"\"",
1456       {"", "'p2", "p3`", "p4\" $even \"", NULL}, 0}},
1457 
1458     {"Params23456", "p=2 p-3 p4\tp4\rp4\np4", 0x1c2,
1459      {" \"p=2\" \"p-3\" \"p4\tp4\rp4\np4\" \"\" \"\"",
1460       {"", "p=2", "p-3", "p4\tp4\rp4\np4", "", "", NULL}, 0}},
1461 
1462     /* In unquoted strings, quotes are treated are a parameter separator just
1463      * like spaces! However they can be doubled to get a literal quote.
1464      * Specifically:
1465      * 2n   quotes -> n quotes
1466      * 2n+1 quotes -> n quotes and a parameter separator
1467      */
1468     {"Params23456789", "one\"quote \"p four\" one\"quote p7", 0xff3,
1469      {" \"one\" \"quote\" \"p four\" \"one\" \"quote\" \"p7\" \"\" \"\"",
1470       {"", "one", "quote", "p four", "one", "quote", "p7", "", "", NULL}, 0}},
1471 
1472     {"Params23456789", "two\"\"quotes \"p three\" two\"\"quotes p5", 0xf2,
1473      {" \"two\"quotes\" \"p three\" \"two\"quotes\" \"p5\" \"\" \"\" \"\" \"\"",
1474       {"", "twoquotes p", "three twoquotes", "p5", "", "", "", "", NULL}, 0}},
1475 
1476     {"Params23456789", "three\"\"\"quotes \"p four\" three\"\"\"quotes p6", 0xff3,
1477      {" \"three\"\" \"quotes\" \"p four\" \"three\"\" \"quotes\" \"p6\" \"\" \"\"",
1478       {"", "three\"", "quotes", "p four", "three\"", "quotes", "p6", "", "", NULL}, 0}},
1479 
1480     {"Params23456789", "four\"\"\"\"quotes \"p three\" four\"\"\"\"quotes p5", 0xf3,
1481      {" \"four\"\"quotes\" \"p three\" \"four\"\"quotes\" \"p5\" \"\" \"\" \"\" \"\"",
1482       {"", "four\"quotes p", "three fourquotes p5 \"", "", "", "", NULL}, 0}},
1483 
1484     /* Quoted strings cannot be continued by tacking on a non space character
1485      * either.
1486      */
1487     {"Params23456", "\"p two\"p3 \"p four\"p5 p6", 0x1f3,
1488      {" \"p two\" \"p3\" \"p four\" \"p5\" \"p6\"",
1489       {"", "p two", "p3", "p four", "p5", "p6", NULL}, 0}},
1490 
1491     /* In quoted strings, the quotes are halved and an odd number closes the
1492      * string. Specifically:
1493      * 2n   quotes -> n quotes
1494      * 2n+1 quotes -> n quotes and closes the string and hence the parameter
1495      */
1496     {"Params23456789", "\"one q\"uote \"p four\" \"one q\"uote p7", 0xff3,
1497      {" \"one q\" \"uote\" \"p four\" \"one q\" \"uote\" \"p7\" \"\" \"\"",
1498       {"", "one q", "uote", "p four", "one q", "uote", "p7", "", "", NULL}, 0}},
1499 
1500     {"Params23456789", "\"two \"\" quotes\" \"p three\" \"two \"\" quotes\" p5", 0x1ff3,
1501      {" \"two \" quotes\" \"p three\" \"two \" quotes\" \"p5\" \"\" \"\" \"\" \"\"",
1502       {"", "two ", "quotes p", "three two", " quotes", "p5", "", "", "", "", NULL}, 0}},
1503 
1504     {"Params23456789", "\"three q\"\"\"uotes \"p four\" \"three q\"\"\"uotes p7", 0xff3,
1505      {" \"three q\"\" \"uotes\" \"p four\" \"three q\"\" \"uotes\" \"p7\" \"\" \"\"",
1506       {"", "three q\"", "uotes", "p four", "three q\"", "uotes", "p7", "", "", NULL}, 0}},
1507 
1508     {"Params23456789", "\"four \"\"\"\" quotes\" \"p three\" \"four \"\"\"\" quotes\" p5", 0xff3,
1509      {" \"four \"\" quotes\" \"p three\" \"four \"\" quotes\" \"p5\" \"\" \"\" \"\" \"\"",
1510       {"", "four \"", "quotes p", "three four", "", "quotes p5 \"", "", "", "", NULL}, 0}},
1511 
1512     /* The quoted string rules also apply to consecutive quotes at the start
1513      * of a parameter but don't count the opening quote!
1514      */
1515     {"Params23456789", "\"\"twoquotes \"p four\" \"\"twoquotes p7", 0xbf3,
1516      {" \"\" \"twoquotes\" \"p four\" \"\" \"twoquotes\" \"p7\" \"\" \"\"",
1517       {"", "", "twoquotes", "p four", "", "twoquotes", "p7", "", "", NULL}, 0}},
1518 
1519     {"Params23456789", "\"\"\"three quotes\" \"p three\" \"\"\"three quotes\" p5", 0x6f3,
1520      {" \"\"three quotes\" \"p three\" \"\"three quotes\" \"p5\" \"\" \"\" \"\" \"\"",
1521       {"", "three", "quotes p", "three \"three", "quotes p5 \"", "", "", "", NULL}, 0}},
1522 
1523     {"Params23456789", "\"\"\"\"fourquotes \"p four\" \"\"\"\"fourquotes p7", 0xbf3,
1524      {" \"\"\" \"fourquotes\" \"p four\" \"\"\" \"fourquotes\" \"p7\" \"\" \"\"",
1525       {"", "\"", "fourquotes", "p four", "\"", "fourquotes", "p7", "", "", NULL}, 0}},
1526 
1527     /* An unclosed quoted string gets lost! */
1528     {"Params23456", "p2 \"p3\" \"p4 is lost", 0x1c3,
1529      {" \"p2\" \"p3\" \"\" \"\" \"\"",
1530       {"", "p2", "p3", "", "", "", NULL}, 0},
1531      {" \"p2\" \"p3\" \"p3\" \"\" \"\"",
1532        {"", "p2", "p3", "p3", "", "", NULL}, 0}},
1533 
1534     /* Backslashes have no special meaning even when preceding quotes. All
1535      * they do is start an unquoted string.
1536      */
1537     {"Params23456", "\\\"p\\three \"pfour\\\" pfive", 0x73,
1538      {" \"\\\" \"p\\three\" \"pfour\\\" \"pfive\" \"\"",
1539       {"", "\" p\\three pfour\"", "pfive", "", NULL}, 0}},
1540 
1541     /* Environment variables are left untouched. */
1542     {"Params23456", "%TMPDIR% %t %c", 0,
1543      {" \"%TMPDIR%\" \"%t\" \"%c\" \"\" \"\"",
1544       {"", "%TMPDIR%", "%t", "%c", "", "", NULL}, 0}},
1545 
1546     /* %~2 is equivalent to %*. However %~3 and higher include the spaces
1547      * before the parameter!
1548      * (but not the previous parameter's closing quote fortunately)
1549      */
1550     {"Params2345Etc", "p2  p3 \"p4\"  p5 p6 ", 0x3f3,
1551      {" ~2=\"p2  p3 \"p4\"  p5 p6 \" ~3=\"  p3 \"p4\"  p5 p6 \" ~4=\" \"p4\"  p5 p6 \" ~5=  p5 p6 ",
1552       {"", "~2=p2  p3 p4  p5 p6 ", "~3=  p3 p4  p5 p6 ", "~4= p4  p5 p6 ", "~5=", "p5", "p6", NULL}, 0}},
1553 
1554     /* %~n works even if there is no nth parameter. */
1555     {"Params9Etc", "p2 p3 p4 p5 p6 p7 p8   ", 0x12,
1556      {" ~9=\"   \"",
1557       {"", "~9=   ", NULL}, 0}},
1558 
1559     {"Params9Etc", "p2 p3 p4 p5 p6 p7   ", 0x12,
1560      {" ~9=\"\"",
1561       {"", "~9=", NULL}, 0}},
1562 
1563     /* The %~n directives also transmit the tenth parameter and beyond. */
1564     {"Params9Etc", "p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 and beyond!", 0x12,
1565      {" ~9=\" p9 p10 p11 and beyond!\"",
1566       {"", "~9= p9 p10 p11 and beyond!", NULL}, 0}},
1567 
1568     /* Bad formatting directives lose their % sign, except those followed by
1569      * a tilde! Environment variables are not expanded but lose their % sign.
1570      */
1571     {"ParamsBad", "p2 p3 p4 p5", 0x12,
1572      {" \"% - %~ %~0 %~1 %~a %~* a b c TMPDIR\"",
1573       {"", "% - %~ %~0 %~1 %~a %~* a b c TMPDIR", NULL}, 0}},
1574 
1575     {NULL, NULL, 0, {NULL, {NULL}, 0}}
1576 };
1577 
1578 static void test_argify(void)
1579 {
1580     BOOL has_cl2a = TRUE;
1581     char fileA[MAX_PATH], params[2*MAX_PATH+12];
1582     INT_PTR rc;
1583     const argify_tests_t* test;
1584     const cmdline_tests_t *bad;
1585     const char* cmd;
1586     unsigned i, count;
1587 
1588     /* Test with a long parameter */
1589     for (rc = 0; rc < MAX_PATH; rc++)
1590         fileA[rc] = 'a' + rc % 26;
1591     fileA[MAX_PATH-1] = '\0';
1592     sprintf(params, "shlexec \"%s\" %s", child_file, fileA);
1593 
1594     /* We need NOZONECHECKS on Win2003 to block a dialog */
1595     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, params, NULL, NULL);
1596     okShell(rc > 32, "failed: rc=%lu\n", rc);
1597     okChildInt("argcA", 4);
1598     okChildPath("argvA3", fileA);
1599 
1600     if (skip_shlexec_tests)
1601     {
1602         skip("No argify tests due to lack of .shlexec association\n");
1603         return;
1604     }
1605 
1606     create_test_verb("shlexec.shlexec", "Params232S", 0, "Params232S %2 %3 \"%2\" \"%*\"");
1607     create_test_verb("shlexec.shlexec", "Params23456", 0, "Params23456 \"%2\" \"%3\" \"%4\" \"%5\" \"%6\"");
1608     create_test_verb("shlexec.shlexec", "Params23456789", 0, "Params23456789 \"%2\" \"%3\" \"%4\" \"%5\" \"%6\" \"%7\" \"%8\" \"%9\"");
1609     create_test_verb("shlexec.shlexec", "Params2345Etc", 0, "Params2345Etc ~2=\"%~2\" ~3=\"%~3\" ~4=\"%~4\" ~5=%~5");
1610     create_test_verb("shlexec.shlexec", "Params9Etc", 0, "Params9Etc ~9=\"%~9\"");
1611     create_test_verb("shlexec.shlexec", "Params20", 0, "Params20 \"%20\"");
1612     create_test_verb("shlexec.shlexec", "ParamsBad", 0, "ParamsBad \"%% %- %~ %~0 %~1 %~a %~* %a %b %c %TMPDIR%\"");
1613 
1614     sprintf(fileA, "%s\\test file.shlexec", tmpdir);
1615 
1616     test = argify_tests;
1617     while (test->params)
1618     {
1619         bad = test->broken.cmd ? &test->broken : &test->cmd;
1620 
1621         /* trace("***** verb='%s' params='%s'\n", test->verb, test->params); */
1622         rc = shell_execute_ex(SEE_MASK_DOENVSUBST, test->verb, fileA, test->params, NULL, NULL);
1623         okShell(rc > 32, "failed: rc=%lu\n", rc);
1624 
1625         count = 0;
1626         while (test->cmd.args[count])
1627             count++;
1628         /* +4 for the shlexec arguments, -1 because of the added ""
1629          * argument for the CommandLineToArgvW() tests.
1630          */
1631         todo_wine_if(test->todo & 0x1)
1632             okChildInt("argcA", 4 + count - 1);
1633 
1634         cmd = getChildString("Child", "cmdlineA");
1635         /* Our commands are such that the verb immediately precedes the
1636          * part we are interested in.
1637          */
1638         if (cmd) cmd = strstr(cmd, test->verb);
1639         if (cmd) cmd += strlen(test->verb);
1640         if (!cmd) cmd = "(null)";
1641         todo_wine_if(test->todo & 0x2)
1642             okShell(!strcmp(cmd, test->cmd.cmd) || broken(!strcmp(cmd, bad->cmd)),
1643                     "the cmdline is '%s' instead of '%s'\n", cmd, test->cmd.cmd);
1644 
1645         for (i = 0; i < count - 1; i++)
1646         {
1647             char argname[18];
1648             sprintf(argname, "argvA%d", 4 + i);
1649             todo_wine_if(test->todo & (1 << (i+4)))
1650                 okChildStringBroken(argname, test->cmd.args[i+1], bad->args[i+1]);
1651         }
1652 
1653         if (has_cl2a)
1654             has_cl2a = test_one_cmdline(&(test->cmd));
1655         test++;
1656     }
1657 }
1658 
1659 static void test_filename(void)
1660 {
1661     char filename[MAX_PATH];
1662     const filename_tests_t* test;
1663     char* c;
1664     INT_PTR rc;
1665 
1666     if (skip_shlexec_tests)
1667     {
1668         skip("No ShellExecute/filename tests due to lack of .shlexec association\n");
1669         return;
1670     }
1671 
1672     test=filename_tests;
1673     while (test->basename)
1674     {
1675         BOOL quotedfile = FALSE;
1676 
1677         if (skip_noassoc_tests && test->rc == SE_ERR_NOASSOC)
1678         {
1679             win_skip("Skipping shellexecute of file with unassociated extension\n");
1680             test++;
1681             continue;
1682         }
1683 
1684         sprintf(filename, test->basename, tmpdir);
1685         if (strchr(filename, '/'))
1686         {
1687             c=filename;
1688             while (*c)
1689             {
1690                 if (*c=='\\')
1691                     *c='/';
1692                 c++;
1693             }
1694         }
1695         if ((test->todo & 0x40)==0)
1696         {
1697             rc=shell_execute(test->verb, filename, NULL, NULL);
1698         }
1699         else
1700         {
1701             char quoted[MAX_PATH + 2];
1702 
1703             quotedfile = TRUE;
1704             sprintf(quoted, "\"%s\"", filename);
1705             rc=shell_execute(test->verb, quoted, NULL, NULL);
1706         }
1707         if (rc > 32)
1708             rc=33;
1709         okShell(rc==test->rc ||
1710                 broken(quotedfile && rc == SE_ERR_FNF), /* NT4 */
1711                 "failed: rc=%ld err=%u\n", rc, GetLastError());
1712         if (rc == 33)
1713         {
1714             const char* verb;
1715             todo_wine_if(test->todo & 0x2)
1716                 okChildInt("argcA", 5);
1717             verb=(test->verb ? test->verb : "Open");
1718             todo_wine_if(test->todo & 0x4)
1719                 okChildString("argvA3", verb);
1720             todo_wine_if(test->todo & 0x8)
1721                 okChildPath("argvA4", filename);
1722         }
1723         test++;
1724     }
1725 
1726     test=noquotes_tests;
1727     while (test->basename)
1728     {
1729         sprintf(filename, test->basename, tmpdir);
1730         rc=shell_execute(test->verb, filename, NULL, NULL);
1731         if (rc > 32)
1732             rc=33;
1733         todo_wine_if(test->todo & 0x1)
1734             okShell(rc==test->rc, "failed: rc=%ld err=%u\n", rc, GetLastError());
1735         if (rc==0)
1736         {
1737             int count;
1738             const char* verb;
1739             char* str;
1740 
1741             verb=(test->verb ? test->verb : "Open");
1742             todo_wine_if(test->todo & 0x4)
1743                 okChildString("argvA3", verb);
1744 
1745             count=4;
1746             str=filename;
1747             while (1)
1748             {
1749                 char attrib[18];
1750                 char* space;
1751                 space=strchr(str, ' ');
1752                 if (space)
1753                     *space='\0';
1754                 sprintf(attrib, "argvA%d", count);
1755                 todo_wine_if(test->todo & 0x8)
1756                     okChildPath(attrib, str);
1757                 count++;
1758                 if (!space)
1759                     break;
1760                 str=space+1;
1761             }
1762             todo_wine_if(test->todo & 0x2)
1763                 okChildInt("argcA", count);
1764         }
1765         test++;
1766     }
1767 
1768     if (dllver.dwMajorVersion != 0)
1769     {
1770         /* The more recent versions of shell32.dll accept quoted filenames
1771          * while older ones (e.g. 4.00) don't. Still we want to test this
1772          * because IE 6 depends on the new behavior.
1773          * One day we may need to check the exact version of the dll but for
1774          * now making sure DllGetVersion() is present is sufficient.
1775          */
1776         sprintf(filename, "\"%s\\test file.shlexec\"", tmpdir);
1777         rc=shell_execute(NULL, filename, NULL, NULL);
1778         okShell(rc > 32, "failed: rc=%ld err=%u\n", rc, GetLastError());
1779         okChildInt("argcA", 5);
1780         okChildString("argvA3", "Open");
1781         sprintf(filename, "%s\\test file.shlexec", tmpdir);
1782         okChildPath("argvA4", filename);
1783     }
1784 
1785     sprintf(filename, "\"%s\\test file.sha\"", tmpdir);
1786     rc=shell_execute(NULL, filename, NULL, NULL);
1787     todo_wine okShell(rc > 32, "failed: rc=%ld err=%u\n", rc, GetLastError());
1788     okChildInt("argcA", 5);
1789     todo_wine okChildString("argvA3", "averb");
1790     sprintf(filename, "%s\\test file.sha", tmpdir);
1791     todo_wine okChildPath("argvA4", filename);
1792 }
1793 
1794 typedef struct
1795 {
1796     const char* urlprefix;
1797     const char* basename;
1798     int flags;
1799     int todo;
1800 } fileurl_tests_t;
1801 
1802 #define URL_SUCCESS  0x1
1803 #define USE_COLON    0x2
1804 #define USE_BSLASH   0x4
1805 
1806 static fileurl_tests_t fileurl_tests[]=
1807 {
1808     /* How many slashes does it take... */
1809     {"file:", "%s\\test file.shlexec", URL_SUCCESS, 0},
1810     {"file:/", "%s\\test file.shlexec", URL_SUCCESS, 0},
1811     {"file://", "%s\\test file.shlexec", URL_SUCCESS, 0},
1812     {"file:///", "%s\\test file.shlexec", URL_SUCCESS, 0},
1813     {"File:///", "%s\\test file.shlexec", URL_SUCCESS, 0},
1814     {"file:////", "%s\\test file.shlexec", URL_SUCCESS, 0},
1815     {"file://///", "%s\\test file.shlexec", 0, 0},
1816 
1817     /* Test with Windows-style paths */
1818     {"file:///", "%s\\test file.shlexec", URL_SUCCESS | USE_COLON, 0},
1819     {"file:///", "%s\\test file.shlexec", URL_SUCCESS | USE_BSLASH, 0},
1820 
1821     /* Check handling of hostnames */
1822     {"file://localhost/", "%s\\test file.shlexec", URL_SUCCESS, 0},
1823     {"file://localhost:80/", "%s\\test file.shlexec", 0, 0},
1824     {"file://LocalHost/", "%s\\test file.shlexec", URL_SUCCESS, 0},
1825     {"file://127.0.0.1/", "%s\\test file.shlexec", 0, 0},
1826     {"file://::1/", "%s\\test file.shlexec", 0, 0},
1827     {"file://notahost/", "%s\\test file.shlexec", 0, 0},
1828 
1829     /* Environment variables are not expanded in URLs */
1830     {"%urlprefix%", "%s\\test file.shlexec", 0, 0x1},
1831     {"file:///", "%%TMPDIR%%\\test file.shlexec", 0, 0},
1832 
1833     /* Test shortcuts vs. URLs */
1834     {"file://///", "%s\\test_shortcut_shlexec.lnk", 0, 0x1c},
1835 
1836     /* Confuse things by mixing protocols */
1837     {"file://", "shlproto://foo/bar", USE_COLON, 0},
1838 
1839     {NULL, NULL, 0, 0}
1840 };
1841 
1842 static void test_fileurls(void)
1843 {
1844     char filename[MAX_PATH], fileurl[MAX_PATH], longtmpdir[MAX_PATH];
1845     char command[MAX_PATH];
1846     const fileurl_tests_t* test;
1847     char *s;
1848     INT_PTR rc;
1849 
1850     if (skip_shlexec_tests)
1851     {
1852         skip("No file URL tests due to lack of .shlexec association\n");
1853         return;
1854     }
1855 
1856     rc = shell_execute_ex(SEE_MASK_FLAG_NO_UI, NULL,
1857                           "file:///nosuchfile.shlexec", NULL, NULL, NULL);
1858     if (rc > 32)
1859     {
1860         win_skip("shell32 is too old (likely < 4.72). Skipping the file URL tests\n");
1861         return;
1862     }
1863 
1864     get_long_path_name(tmpdir, longtmpdir, sizeof(longtmpdir)/sizeof(*longtmpdir));
1865     SetEnvironmentVariableA("urlprefix", "file:///");
1866 
1867     test=fileurl_tests;
1868     while (test->basename)
1869     {
1870         /* Build the file URL */
1871         sprintf(filename, test->basename, longtmpdir);
1872         strcpy(fileurl, test->urlprefix);
1873         strcat(fileurl, filename);
1874         s = fileurl + strlen(test->urlprefix);
1875         while (*s)
1876         {
1877             if (!(test->flags & USE_COLON) && *s == ':')
1878                 *s = '|';
1879             else if (!(test->flags & USE_BSLASH) && *s == '\\')
1880                 *s = '/';
1881             s++;
1882         }
1883 
1884         /* Test it first with FindExecutable() */
1885         rc = (INT_PTR)FindExecutableA(fileurl, NULL, command);
1886         ok(rc == SE_ERR_FNF, "FindExecutable(%s) failed: bad rc=%lu\n", fileurl, rc);
1887 
1888         /* Then ShellExecute() */
1889         if ((test->todo & 0x10) == 0)
1890             rc = shell_execute(NULL, fileurl, NULL, NULL);
1891         else todo_wait
1892             rc = shell_execute(NULL, fileurl, NULL, NULL);
1893         if (bad_shellexecute)
1894         {
1895             win_skip("shell32 is too old (likely 4.72). Skipping the file URL tests\n");
1896             break;
1897         }
1898         if (test->flags & URL_SUCCESS)
1899         {
1900             todo_wine_if(test->todo & 0x1)
1901                 okShell(rc > 32, "failed: bad rc=%lu\n", rc);
1902         }
1903         else
1904         {
1905             todo_wine_if(test->todo & 0x1)
1906                 okShell(rc == SE_ERR_FNF || rc == SE_ERR_PNF ||
1907                         broken(rc == SE_ERR_ACCESSDENIED) /* win2000 */,
1908                         "failed: bad rc=%lu\n", rc);
1909         }
1910         if (rc == 33)
1911         {
1912             todo_wine_if(test->todo & 0x2)
1913                 okChildInt("argcA", 5);
1914             todo_wine_if(test->todo & 0x4)
1915                 okChildString("argvA3", "Open");
1916             todo_wine_if(test->todo & 0x8)
1917                 okChildPath("argvA4", filename);
1918         }
1919         test++;
1920     }
1921 
1922     SetEnvironmentVariableA("urlprefix", NULL);
1923 }
1924 
1925 static void test_urls(void)
1926 {
1927     char url[MAX_PATH];
1928     INT_PTR rc;
1929 
1930     if (!create_test_class("fakeproto", FALSE))
1931     {
1932         skip("Unable to create 'fakeproto' class for URL tests\n");
1933         return;
1934     }
1935     create_test_verb("fakeproto", "open", 0, "URL %1");
1936 
1937     create_test_class("shlpaverb", TRUE);
1938     create_test_verb("shlpaverb", "averb", 0, "PAVerb \"%1\"");
1939 
1940     /* Protocols must be properly declared */
1941     rc = shell_execute(NULL, "notaproto://foo", NULL, NULL);
1942     ok(rc == SE_ERR_NOASSOC || broken(rc == SE_ERR_ACCESSDENIED),
1943        "%s returned %lu\n", shell_call, rc);
1944 
1945     rc = shell_execute(NULL, "fakeproto://foo/bar", NULL, NULL);
1946     todo_wine ok(rc == SE_ERR_NOASSOC || broken(rc == SE_ERR_ACCESSDENIED),
1947                  "%s returned %lu\n", shell_call, rc);
1948 
1949     /* Here's a real live one */
1950     rc = shell_execute(NULL, "shlproto://foo/bar", NULL, NULL);
1951     ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
1952     okChildInt("argcA", 5);
1953     okChildString("argvA3", "URL");
1954     okChildString("argvA4", "shlproto://foo/bar");
1955 
1956     /* Check default verb detection */
1957     rc = shell_execute(NULL, "shlpaverb://foo/bar", NULL, NULL);
1958     todo_wine ok(rc > 32 || /* XP+IE7 - Win10 */
1959                  broken(rc == SE_ERR_NOASSOC), /* XP+IE6 */
1960                  "%s failed: rc=%lu\n", shell_call, rc);
1961     if (rc > 32)
1962     {
1963         okChildInt("argcA", 5);
1964         todo_wine okChildString("argvA3", "PAVerb");
1965         todo_wine okChildString("argvA4", "shlpaverb://foo/bar");
1966     }
1967 
1968     /* But alternative verbs are a recent feature! */
1969     rc = shell_execute("averb", "shlproto://foo/bar", NULL, NULL);
1970     ok(rc > 32 || /* Win8 - Win10 */
1971        broken(rc == SE_ERR_ACCESSDENIED), /* XP - Win7 */
1972        "%s failed: rc=%lu\n", shell_call, rc);
1973     if (rc > 32)
1974     {
1975         okChildString("argvA3", "AVerb");
1976         okChildString("argvA4", "shlproto://foo/bar");
1977     }
1978 
1979     /* A .lnk ending does not turn a URL into a shortcut */
1980     rc = shell_execute(NULL, "shlproto://foo/bar.lnk", NULL, NULL);
1981     ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
1982     okChildInt("argcA", 5);
1983     okChildString("argvA3", "URL");
1984     okChildString("argvA4", "shlproto://foo/bar.lnk");
1985 
1986     /* Neither does a .exe extension */
1987     rc = shell_execute(NULL, "shlproto://foo/bar.exe", NULL, NULL);
1988     ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
1989     okChildInt("argcA", 5);
1990     okChildString("argvA3", "URL");
1991     okChildString("argvA4", "shlproto://foo/bar.exe");
1992 
1993     /* But a class name overrides it */
1994     rc = shell_execute(NULL, "shlproto://foo/bar", "shlexec.shlexec", NULL);
1995     ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
1996     okChildInt("argcA", 5);
1997     okChildString("argvA3", "URL");
1998     okChildString("argvA4", "shlproto://foo/bar");
1999 
2000     /* Environment variables are expanded in URLs (but not in file URLs!) */
2001     rc = shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI,
2002                           NULL, "shlproto://%TMPDIR%/bar", NULL, NULL, NULL);
2003     okShell(rc > 32, "failed: rc=%lu\n", rc);
2004     okChildInt("argcA", 5);
2005     sprintf(url, "shlproto://%s/bar", tmpdir);
2006     okChildString("argvA3", "URL");
2007     okChildStringBroken("argvA4", url, "shlproto://%TMPDIR%/bar");
2008 
2009     /* But only after the path has been identified as a URL */
2010     SetEnvironmentVariableA("urlprefix", "shlproto:///");
2011     rc = shell_execute(NULL, "%urlprefix%foo", NULL, NULL);
2012     todo_wine ok(rc == SE_ERR_FNF, "%s returned %lu\n", shell_call, rc);
2013     SetEnvironmentVariableA("urlprefix", NULL);
2014 
2015     delete_test_class("fakeproto");
2016     delete_test_class("shlpaverb");
2017 }
2018 
2019 static void test_find_executable(void)
2020 {
2021     char notepad_path[MAX_PATH];
2022     char filename[MAX_PATH];
2023     char command[MAX_PATH];
2024     const filename_tests_t* test;
2025     INT_PTR rc;
2026 
2027     if (!create_test_association(".sfe"))
2028     {
2029         skip("Unable to create association for '.sfe'\n");
2030         return;
2031     }
2032     create_test_verb("shlexec.sfe", "Open", 1, "%1");
2033 
2034     /* Don't test FindExecutable(..., NULL), it always crashes */
2035 
2036     strcpy(command, "your word");
2037     if (0) /* Can crash on Vista! */
2038     {
2039     rc=(INT_PTR)FindExecutableA(NULL, NULL, command);
2040     ok(rc == SE_ERR_FNF || rc > 32 /* nt4 */, "FindExecutable(NULL) returned %ld\n", rc);
2041     ok(strcmp(command, "your word") != 0, "FindExecutable(NULL) returned command=[%s]\n", command);
2042     }
2043 
2044     GetSystemDirectoryA( notepad_path, MAX_PATH );
2045     strcat( notepad_path, "\\notepad.exe" );
2046 
2047     /* Search for something that should be in the system-wide search path (no default directory) */
2048     strcpy(command, "your word");
2049     rc=(INT_PTR)FindExecutableA("notepad.exe", NULL, command);
2050     ok(rc > 32, "FindExecutable(%s) returned %ld\n", "notepad.exe", rc);
2051     ok(strcasecmp(command, notepad_path) == 0, "FindExecutable(%s) returned command=[%s]\n", "notepad.exe", command);
2052 
2053     /* Search for something that should be in the system-wide search path (with default directory) */
2054     strcpy(command, "your word");
2055     rc=(INT_PTR)FindExecutableA("notepad.exe", tmpdir, command);
2056     ok(rc > 32, "FindExecutable(%s) returned %ld\n", "notepad.exe", rc);
2057     ok(strcasecmp(command, notepad_path) == 0, "FindExecutable(%s) returned command=[%s]\n", "notepad.exe", command);
2058 
2059     strcpy(command, "your word");
2060     rc=(INT_PTR)FindExecutableA(tmpdir, NULL, command);
2061     ok(rc == SE_ERR_NOASSOC /* >= win2000 */ || rc > 32 /* win98, nt4 */, "FindExecutable(NULL) returned %ld\n", rc);
2062     ok(strcmp(command, "your word") != 0, "FindExecutable(NULL) returned command=[%s]\n", command);
2063 
2064     sprintf(filename, "%s\\test file.sfe", tmpdir);
2065     rc=(INT_PTR)FindExecutableA(filename, NULL, command);
2066     ok(rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc);
2067     /* Depending on the platform, command could be '%1' or 'test file.sfe' */
2068 
2069     rc=(INT_PTR)FindExecutableA("test file.sfe", tmpdir, command);
2070     ok(rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc);
2071 
2072     rc=(INT_PTR)FindExecutableA("test file.sfe", NULL, command);
2073     ok(rc == SE_ERR_FNF, "FindExecutable(%s) returned %ld\n", filename, rc);
2074 
2075     delete_test_association(".sfe");
2076 
2077     if (!create_test_association(".shl"))
2078     {
2079         skip("Unable to create association for '.shl'\n");
2080         return;
2081     }
2082     create_test_verb("shlexec.shl", "Open", 0, "Open");
2083 
2084     sprintf(filename, "%s\\test file.shl", tmpdir);
2085     rc=(INT_PTR)FindExecutableA(filename, NULL, command);
2086     ok(rc == SE_ERR_FNF /* NT4 */ || rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc);
2087 
2088     sprintf(filename, "%s\\test file.shlfoo", tmpdir);
2089     rc=(INT_PTR)FindExecutableA(filename, NULL, command);
2090 
2091     delete_test_association(".shl");
2092 
2093     if (rc > 32)
2094     {
2095         /* On Windows XP and 2003 FindExecutable() is completely broken.
2096          * Probably what it does is convert the filename to 8.3 format,
2097          * which as a side effect converts the '.shlfoo' extension to '.shl',
2098          * and then tries to find an association for '.shl'. This means it
2099          * will normally fail on most extensions with more than 3 characters,
2100          * like '.mpeg', etc.
2101          * Also it means we cannot do any other test.
2102          */
2103         win_skip("FindExecutable() is broken -> not running 4+ character extension tests\n");
2104         return;
2105     }
2106 
2107     if (skip_shlexec_tests)
2108     {
2109         skip("No FindExecutable/filename tests due to lack of .shlexec association\n");
2110         return;
2111     }
2112 
2113     test=filename_tests;
2114     while (test->basename)
2115     {
2116         sprintf(filename, test->basename, tmpdir);
2117         if (strchr(filename, '/'))
2118         {
2119             char* c;
2120             c=filename;
2121             while (*c)
2122             {
2123                 if (*c=='\\')
2124                     *c='/';
2125                 c++;
2126             }
2127         }
2128         /* Win98 does not '\0'-terminate command! */
2129         memset(command, '\0', sizeof(command));
2130         rc=(INT_PTR)FindExecutableA(filename, NULL, command);
2131         if (rc > 32)
2132             rc=33;
2133         todo_wine_if(test->todo & 0x10)
2134             ok(rc==test->rc, "FindExecutable(%s) failed: rc=%ld\n", filename, rc);
2135         if (rc > 32)
2136         {
2137             BOOL equal;
2138             equal=strcmp(command, argv0) == 0 ||
2139                 /* NT4 returns an extra 0x8 character! */
2140                 (strlen(command) == strlen(argv0)+1 && strncmp(command, argv0, strlen(argv0)) == 0);
2141             todo_wine_if(test->todo & 0x20)
2142                 ok(equal, "FindExecutable(%s) returned command='%s' instead of '%s'\n",
2143                    filename, command, argv0);
2144         }
2145         test++;
2146     }
2147 }
2148 
2149 
2150 static filename_tests_t lnk_tests[]=
2151 {
2152     /* Pass bad / nonexistent filenames as a parameter */
2153     {NULL, "%s\\nonexistent.shlexec",    0xa, 33},
2154     {NULL, "%s\\nonexistent.noassoc",    0xa, 33},
2155 
2156     /* Pass regular paths as a parameter */
2157     {NULL, "%s\\test file.shlexec",      0xa, 33},
2158     {NULL, "%s/%%nasty%% $file.shlexec", 0xa, 33},
2159 
2160     /* Pass filenames with no association as a parameter */
2161     {NULL, "%s\\test file.noassoc",      0xa, 33},
2162 
2163     {NULL, NULL, 0}
2164 };
2165 
2166 static void test_lnks(void)
2167 {
2168     char filename[MAX_PATH];
2169     char params[MAX_PATH];
2170     const filename_tests_t* test;
2171     INT_PTR rc;
2172 
2173     if (skip_shlexec_tests)
2174         skip("No FindExecutable/filename tests due to lack of .shlexec association\n");
2175     else
2176     {
2177         /* Should open through our association */
2178         sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir);
2179         rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, NULL);
2180         okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2181         okChildInt("argcA", 5);
2182         okChildString("argvA3", "Open");
2183         sprintf(params, "%s\\test file.shlexec", tmpdir);
2184         get_long_path_name(params, filename, sizeof(filename));
2185         okChildPath("argvA4", filename);
2186 
2187         rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_DOENVSUBST, NULL, "%TMPDIR%\\test_shortcut_shlexec.lnk", NULL, NULL, NULL);
2188         okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2189         okChildInt("argcA", 5);
2190         okChildString("argvA3", "Open");
2191         sprintf(params, "%s\\test file.shlexec", tmpdir);
2192         get_long_path_name(params, filename, sizeof(filename));
2193         okChildPath("argvA4", filename);
2194     }
2195 
2196     /* Should just run our executable */
2197     sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
2198     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, NULL);
2199     okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2200     okChildInt("argcA", 4);
2201     okChildString("argvA3", "Lnk");
2202 
2203     if (!skip_shlexec_tests)
2204     {
2205         /* An explicit class overrides lnk's ContextMenuHandler */
2206         rc=shell_execute_ex(SEE_MASK_CLASSNAME | SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, "shlexec.shlexec");
2207         okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2208         okChildInt("argcA", 5);
2209         okChildString("argvA3", "Open");
2210         okChildPath("argvA4", filename);
2211     }
2212 
2213     if (dllver.dwMajorVersion>=6)
2214     {
2215         char* c;
2216        /* Recent versions of shell32.dll accept '/'s in shortcut paths.
2217          * Older versions don't or are quite buggy in this regard.
2218          */
2219         sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
2220         c=filename;
2221         while (*c)
2222         {
2223             if (*c=='\\')
2224                 *c='/';
2225             c++;
2226         }
2227         rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, NULL);
2228         okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2229         okChildInt("argcA", 4);
2230         okChildString("argvA3", "Lnk");
2231     }
2232 
2233     sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
2234     test=lnk_tests;
2235     while (test->basename)
2236     {
2237         params[0]='\"';
2238         sprintf(params+1, test->basename, tmpdir);
2239         strcat(params,"\"");
2240         rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, params,
2241                             NULL, NULL);
2242         if (rc > 32)
2243             rc=33;
2244         todo_wine_if(test->todo & 0x1)
2245             okShell(rc==test->rc, "failed: rc=%lu err=%u\n", rc, GetLastError());
2246         if (rc==0)
2247         {
2248             todo_wine_if(test->todo & 0x2)
2249                 okChildInt("argcA", 5);
2250             todo_wine_if(test->todo & 0x4)
2251                 okChildString("argvA3", "Lnk");
2252             sprintf(params, test->basename, tmpdir);
2253             okChildPath("argvA4", params);
2254         }
2255         test++;
2256     }
2257 }
2258 
2259 
2260 static void test_exes(void)
2261 {
2262     char filename[MAX_PATH];
2263     char params[1024];
2264     INT_PTR rc;
2265 
2266     sprintf(params, "shlexec \"%s\" Exec", child_file);
2267 
2268     /* We need NOZONECHECKS on Win2003 to block a dialog */
2269     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, params,
2270                         NULL, NULL);
2271     okShell(rc > 32, "returned %lu\n", rc);
2272     okChildInt("argcA", 4);
2273     okChildString("argvA3", "Exec");
2274 
2275     if (! skip_noassoc_tests)
2276     {
2277         sprintf(filename, "%s\\test file.noassoc", tmpdir);
2278         if (CopyFileA(argv0, filename, FALSE))
2279         {
2280             rc=shell_execute(NULL, filename, params, NULL);
2281             todo_wine {
2282                 okShell(rc==SE_ERR_NOASSOC, "returned %lu\n", rc);
2283             }
2284         }
2285     }
2286     else
2287     {
2288         win_skip("Skipping shellexecute of file with unassociated extension\n");
2289     }
2290 
2291     /* test combining executable and parameters */
2292     sprintf(filename, "%s shlexec \"%s\" Exec", argv0, child_file);
2293     rc = shell_execute(NULL, filename, NULL, NULL);
2294     okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2295 
2296     sprintf(filename, "\"%s\" shlexec \"%s\" Exec", argv0, child_file);
2297     rc = shell_execute(NULL, filename, NULL, NULL);
2298     okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2299 
2300     /* A verb, even if invalid, overrides the normal handling of executables */
2301     todo_wait rc = shell_execute_ex(SEE_MASK_FLAG_NO_UI,
2302                                     "notaverb", argv0, NULL, NULL, NULL);
2303     todo_wine okShell(rc == SE_ERR_NOASSOC, "returned %lu\n", rc);
2304 
2305     if (!skip_shlexec_tests)
2306     {
2307         /* A class overrides the normal handling of executables too */
2308         /* FIXME SEE_MASK_FLAG_NO_UI is only needed due to Wine's bug */
2309         rc = shell_execute_ex(SEE_MASK_CLASSNAME | SEE_MASK_FLAG_NO_UI,
2310                               NULL, argv0, NULL, NULL, ".shlexec");
2311         todo_wine okShell(rc > 32, "returned %lu\n", rc);
2312         okChildInt("argcA", 5);
2313         todo_wine okChildString("argvA3", "Open");
2314         todo_wine okChildPath("argvA4", argv0);
2315     }
2316 }
2317 
2318 typedef struct
2319 {
2320     const char* command;
2321     const char* ddeexec;
2322     const char* application;
2323     const char* topic;
2324     const char* ifexec;
2325     int expectedArgs;
2326     const char* expectedDdeExec;
2327     BOOL broken;
2328 } dde_tests_t;
2329 
2330 static dde_tests_t dde_tests[] =
2331 {
2332     /* Test passing and not passing command-line
2333      * argument, no DDE */
2334     {"", NULL, NULL, NULL, NULL, 0, ""},
2335     {"\"%1\"", NULL, NULL, NULL, NULL, 1, ""},
2336 
2337     /* Test passing and not passing command-line
2338      * argument, with DDE */
2339     {"", "[open(\"%1\")]", "shlexec", "dde", NULL, 0, "[open(\"%s\")]"},
2340     {"\"%1\"", "[open(\"%1\")]", "shlexec", "dde", NULL, 1, "[open(\"%s\")]"},
2341 
2342     /* Test unquoted %1 in command and ddeexec
2343      * (test filename has space) */
2344     {"%1", "[open(%1)]", "shlexec", "dde", NULL, 2, "[open(%s)]", TRUE /* before vista */},
2345 
2346     /* Test ifexec precedence over ddeexec */
2347     {"", "[open(\"%1\")]", "shlexec", "dde", "[ifexec(\"%1\")]", 0, "[ifexec(\"%s\")]"},
2348 
2349     /* Test default DDE topic */
2350     {"", "[open(\"%1\")]", "shlexec", NULL, NULL, 0, "[open(\"%s\")]"},
2351 
2352     /* Test default DDE application */
2353     {"", "[open(\"%1\")]", NULL, "dde", NULL, 0, "[open(\"%s\")]"},
2354 
2355     {NULL}
2356 };
2357 
2358 static int waitforinputidle_count;
2359 static DWORD WINAPI hooked_WaitForInputIdle(HANDLE process, DWORD timeout)
2360 {
2361     waitforinputidle_count++;
2362     if (winetest_debug > 1)
2363         trace("WaitForInputIdle() waiting for dde event timeout=min(%u,5s)\n", timeout);
2364     timeout = timeout < 5000 ? timeout : 5000;
2365     return WaitForSingleObject(dde_ready_event, timeout);
2366 }
2367 
2368 /*
2369  * WaitForInputIdle() will normally return immediately for console apps. That's
2370  * a problem for us because ShellExecute will assume that an app is ready to
2371  * receive DDE messages after it has called WaitForInputIdle() on that app.
2372  * To work around that we install our own version of WaitForInputIdle() that
2373  * will wait for the child to explicitly tell us that it is ready. We do that
2374  * by changing the entry for WaitForInputIdle() in the shell32 import address
2375  * table.
2376  */
2377 static void hook_WaitForInputIdle(DWORD (WINAPI *new_func)(HANDLE, DWORD))
2378 {
2379     char *base;
2380     PIMAGE_NT_HEADERS nt_headers;
2381     DWORD import_directory_rva;
2382     PIMAGE_IMPORT_DESCRIPTOR import_descriptor;
2383     int hook_count = 0;
2384 
2385     base = (char *) GetModuleHandleA("shell32.dll");
2386     nt_headers = (PIMAGE_NT_HEADERS)(base + ((PIMAGE_DOS_HEADER) base)->e_lfanew);
2387     import_directory_rva = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
2388 
2389     /* Search for the correct imported module by walking the import descriptors */
2390     import_descriptor = (PIMAGE_IMPORT_DESCRIPTOR)(base + import_directory_rva);
2391     while (U(*import_descriptor).OriginalFirstThunk != 0)
2392     {
2393         char *import_module_name;
2394 
2395         import_module_name = base + import_descriptor->Name;
2396         if (lstrcmpiA(import_module_name, "user32.dll") == 0 ||
2397             lstrcmpiA(import_module_name, "user32") == 0)
2398         {
2399             PIMAGE_THUNK_DATA int_entry;
2400             PIMAGE_THUNK_DATA iat_entry;
2401 
2402             /* The import name table and import address table are two parallel
2403              * arrays. We need the import name table to find the imported
2404              * routine and the import address table to patch the address, so
2405              * walk them side by side */
2406             int_entry = (PIMAGE_THUNK_DATA)(base + U(*import_descriptor).OriginalFirstThunk);
2407             iat_entry = (PIMAGE_THUNK_DATA)(base + import_descriptor->FirstThunk);
2408             while (int_entry->u1.Ordinal != 0)
2409             {
2410                 if (! IMAGE_SNAP_BY_ORDINAL(int_entry->u1.Ordinal))
2411                 {
2412                     PIMAGE_IMPORT_BY_NAME import_by_name;
2413                     import_by_name = (PIMAGE_IMPORT_BY_NAME)(base + int_entry->u1.AddressOfData);
2414                     if (lstrcmpA((char *) import_by_name->Name, "WaitForInputIdle") == 0)
2415                     {
2416                         /* Found the correct routine in the correct imported module. Patch it. */
2417                         DWORD old_prot;
2418                         VirtualProtect(&iat_entry->u1.Function, sizeof(ULONG_PTR), PAGE_READWRITE, &old_prot);
2419                         iat_entry->u1.Function = (ULONG_PTR) new_func;
2420                         VirtualProtect(&iat_entry->u1.Function, sizeof(ULONG_PTR), old_prot, &old_prot);
2421                         if (winetest_debug > 1)
2422                             trace("Hooked %s.WaitForInputIdle\n", import_module_name);
2423                         hook_count++;
2424                         break;
2425                     }
2426                 }
2427                 int_entry++;
2428                 iat_entry++;
2429             }
2430             break;
2431         }
2432 
2433         import_descriptor++;
2434     }
2435     ok(hook_count, "Could not hook WaitForInputIdle()\n");
2436 }
2437 
2438 static void test_dde(void)
2439 {
2440     char filename[MAX_PATH], defApplication[MAX_PATH];
2441     const dde_tests_t* test;
2442     char params[1024];
2443     INT_PTR rc;
2444     HANDLE map;
2445     char *shared_block;
2446     DWORD ddeflags;
2447 
2448     hook_WaitForInputIdle(hooked_WaitForInputIdle);
2449 
2450     sprintf(filename, "%s\\test file.sde", tmpdir);
2451 
2452     /* Default service is application name minus path and extension */
2453     strcpy(defApplication, strrchr(argv0, '\\')+1);
2454     *strchr(defApplication, '.') = 0;
2455 
2456     map = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
2457                              4096, "winetest_shlexec_dde_map");
2458     shared_block = MapViewOfFile(map, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 4096);
2459 
2460     ddeflags = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI;
2461     test = dde_tests;
2462     while (test->command)
2463     {
2464         if (!create_test_association(".sde"))
2465         {
2466             skip("Unable to create association for '.sde'\n");
2467             return;
2468         }
2469         create_test_verb_dde("shlexec.sde", "Open", 0, test->command, test->ddeexec,
2470                              test->application, test->topic, test->ifexec);
2471 
2472         if (test->application != NULL || test->topic != NULL)
2473         {
2474             strcpy(shared_block, test->application ? test->application : defApplication);
2475             strcpy(shared_block + strlen(shared_block) + 1, test->topic ? test->topic : SZDDESYS_TOPIC);
2476         }
2477         else
2478         {
2479             shared_block[0] = '\0';
2480             shared_block[1] = '\0';
2481         }
2482         ddeExec[0] = 0;
2483 
2484         waitforinputidle_count = 0;
2485         dde_ready_event = CreateEventA(NULL, TRUE, FALSE, "winetest_shlexec_dde_ready");
2486         rc = shell_execute_ex(ddeflags, NULL, filename, NULL, NULL, NULL);
2487         CloseHandle(dde_ready_event);
2488         if (!(ddeflags & SEE_MASK_WAITFORINPUTIDLE) && rc == SE_ERR_DDEFAIL &&
2489             GetLastError() == ERROR_FILE_NOT_FOUND &&
2490             strcmp(winetest_platform, "windows") == 0)
2491         {
2492             /* Windows 10 does not call WaitForInputIdle() for DDE which creates
2493              * a race condition as the DDE server may not have time to start up.
2494              * When that happens the test fails with the above results and we
2495              * compensate by forcing the WaitForInputIdle() call.
2496              */
2497             trace("Adding SEE_MASK_WAITFORINPUTIDLE for Windows 10\n");
2498             ddeflags |= SEE_MASK_WAITFORINPUTIDLE;
2499             delete_test_association(".sde");
2500             Sleep(CHILD_DDE_TIMEOUT);
2501             continue;
2502         }
2503         okShell(32 < rc, "failed: rc=%lu err=%u\n", rc, GetLastError());
2504         if (test->ddeexec)
2505             okShell(waitforinputidle_count == 1 ||
2506                     broken(waitforinputidle_count == 0) /* Win10 race */,
2507                     "WaitForInputIdle() was called %u times\n",
2508                     waitforinputidle_count);
2509         else
2510             okShell(waitforinputidle_count == 0, "WaitForInputIdle() was called %u times for a non-DDE case\n", waitforinputidle_count);
2511 
2512         if (32 < rc)
2513         {
2514             if (test->broken)
2515                 okChildIntBroken("argcA", test->expectedArgs + 3);
2516             else
2517                 okChildInt("argcA", test->expectedArgs + 3);
2518 
2519             if (test->expectedArgs == 1) okChildPath("argvA3", filename);
2520 
2521             sprintf(params, test->expectedDdeExec, filename);
2522             okChildPath("ddeExec", params);
2523         }
2524         reset_association_description();
2525 
2526         delete_test_association(".sde");
2527         test++;
2528     }
2529 
2530     UnmapViewOfFile(shared_block);
2531     CloseHandle(map);
2532     hook_WaitForInputIdle((void *) WaitForInputIdle);
2533 }
2534 
2535 #define DDE_DEFAULT_APP_VARIANTS 3
2536 typedef struct
2537 {
2538     const char* command;
2539     const char* expectedDdeApplication[DDE_DEFAULT_APP_VARIANTS];
2540     int todo;
2541     int rc[DDE_DEFAULT_APP_VARIANTS];
2542 } dde_default_app_tests_t;
2543 
2544 static dde_default_app_tests_t dde_default_app_tests[] =
2545 {
2546     /* There are three possible sets of results: Windows <= 2000, XP SP1 and
2547      * >= XP SP2. Use the first two tests to determine which results to expect.
2548      */
2549 
2550     /* Test unquoted existing filename with a space */
2551     {"%s\\test file.exe", {"test file", "test file", "test"}, 0x0, {33, 33, 33}},
2552     {"%s\\test2 file.exe", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
2553 
2554     /* Test unquoted existing filename with a space */
2555     {"%s\\test file.exe param", {"test file", "test file", "test"}, 0x0, {33, 33, 33}},
2556 
2557     /* Test quoted existing filename with a space */
2558     {"\"%s\\test file.exe\"", {"test file", "test file", "test file"}, 0x0, {33, 33, 33}},
2559     {"\"%s\\test file.exe\" param", {"test file", "test file", "test file"}, 0x0, {33, 33, 33}},
2560 
2561     /* Test unquoted filename with a space that doesn't exist, but
2562      * test2.exe does */
2563     {"%s\\test2 file.exe param", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
2564 
2565     /* Test quoted filename with a space that does not exist */
2566     {"\"%s\\test2 file.exe\"", {"", "", "test2 file"}, 0x0, {5, 2, 33}},
2567     {"\"%s\\test2 file.exe\" param", {"", "", "test2 file"}, 0x0, {5, 2, 33}},
2568 
2569     /* Test filename supplied without the extension */
2570     {"%s\\test2", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
2571     {"%s\\test2 param", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
2572 
2573     /* Test an unquoted nonexistent filename */
2574     {"%s\\notexist.exe", {"", "", "notexist"}, 0x0, {5, 2, 33}},
2575     {"%s\\notexist.exe param", {"", "", "notexist"}, 0x0, {5, 2, 33}},
2576 
2577     /* Test an application that will be found on the path */
2578     {"cmd", {"cmd", "cmd", "cmd"}, 0x0, {33, 33, 33}},
2579     {"cmd param", {"cmd", "cmd", "cmd"}, 0x0, {33, 33, 33}},
2580 
2581     /* Test an application that will not be found on the path */
2582     {"xyzwxyzwxyz", {"", "", "xyzwxyzwxyz"}, 0x0, {5, 2, 33}},
2583     {"xyzwxyzwxyz param", {"", "", "xyzwxyzwxyz"}, 0x0, {5, 2, 33}},
2584 
2585     {NULL, {NULL}, 0, {0}}
2586 };
2587 
2588 typedef struct
2589 {
2590     char *filename;
2591     DWORD threadIdParent;
2592 } dde_thread_info_t;
2593 
2594 static DWORD CALLBACK ddeThread(LPVOID arg)
2595 {
2596     dde_thread_info_t *info = arg;
2597     assert(info && info->filename);
2598     PostThreadMessageA(info->threadIdParent,
2599                        WM_QUIT,
2600                        shell_execute_ex(SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI, NULL, info->filename, NULL, NULL, NULL),
2601                        0);
2602     ExitThread(0);
2603 }
2604 
2605 static void test_dde_default_app(void)
2606 {
2607     char filename[MAX_PATH];
2608     HSZ hszApplication;
2609     dde_thread_info_t info = { filename, GetCurrentThreadId() };
2610     const dde_default_app_tests_t* test;
2611     char params[1024];
2612     DWORD threadId;
2613     MSG msg;
2614     INT_PTR rc;
2615     int which = 0;
2616     HDDEDATA ret;
2617     BOOL b;
2618 
2619     post_quit_on_execute = FALSE;
2620     ddeInst = 0;
2621     rc = DdeInitializeA(&ddeInst, ddeCb, CBF_SKIP_ALLNOTIFICATIONS | CBF_FAIL_ADVISES |
2622                         CBF_FAIL_POKES | CBF_FAIL_REQUESTS, 0);
2623     ok(rc == DMLERR_NO_ERROR, "got %lx\n", rc);
2624 
2625     sprintf(filename, "%s\\test file.sde", tmpdir);
2626 
2627     /* It is strictly not necessary to register an application name here, but wine's
2628      * DdeNameService implementation complains if 0 is passed instead of
2629      * hszApplication with DNS_FILTEROFF */
2630     hszApplication = DdeCreateStringHandleA(ddeInst, "shlexec", CP_WINANSI);
2631     hszTopic = DdeCreateStringHandleA(ddeInst, "shlexec", CP_WINANSI);
2632     ok(hszApplication && hszTopic, "got %p and %p\n", hszApplication, hszTopic);
2633     ret = DdeNameService(ddeInst, hszApplication, 0, DNS_REGISTER | DNS_FILTEROFF);
2634     ok(ret != 0, "got %p\n", ret);
2635 
2636     test = dde_default_app_tests;
2637     while (test->command)
2638     {
2639         HANDLE thread;
2640 
2641         if (!create_test_association(".sde"))
2642         {
2643             skip("Unable to create association for '.sde'\n");
2644             return;
2645         }
2646         sprintf(params, test->command, tmpdir);
2647         create_test_verb_dde("shlexec.sde", "Open", 1, params, "[test]", NULL,
2648                              "shlexec", NULL);
2649         ddeApplication[0] = 0;
2650 
2651         /* No application will be run as we will respond to the first DDE event,
2652          * so don't wait for it */
2653         SetEvent(hEvent);
2654 
2655         thread = CreateThread(NULL, 0, ddeThread, &info, 0, &threadId);
2656         ok(thread != NULL, "got %p\n", thread);
2657         while (GetMessageA(&msg, NULL, 0, 0)) DispatchMessageA(&msg);
2658         rc = msg.wParam > 32 ? 33 : msg.wParam;
2659 
2660         /* The first two tests determine which set of results to expect.
2661          * First check the platform as only the first set of results is
2662          * acceptable for Wine.
2663          */
2664         if (strcmp(winetest_platform, "wine"))
2665         {
2666             if (test == dde_default_app_tests)
2667             {
2668                 if (strcmp(ddeApplication, test->expectedDdeApplication[0]))
2669                     which = 2;
2670             }
2671             else if (test == dde_default_app_tests + 1)
2672             {
2673                 if (which == 0 && rc == test->rc[1])
2674                     which = 1;
2675                 trace("DDE result variant %d\n", which);
2676             }
2677         }
2678 
2679         todo_wine_if(test->todo & 0x1)
2680             okShell(rc==test->rc[which], "failed: rc=%lu err=%u\n",
2681                     rc, GetLastError());
2682         if (rc == 33)
2683         {
2684             todo_wine_if(test->todo & 0x2)
2685                 ok(!strcmp(ddeApplication, test->expectedDdeApplication[which]),
2686                    "Expected application '%s', got '%s'\n",
2687                    test->expectedDdeApplication[which], ddeApplication);
2688         }
2689         reset_association_description();
2690 
2691         delete_test_association(".sde");
2692         test++;
2693     }
2694 
2695     ret = DdeNameService(ddeInst, hszApplication, 0, DNS_UNREGISTER);
2696     ok(ret != 0, "got %p\n", ret);
2697     b = DdeFreeStringHandle(ddeInst, hszTopic);
2698     ok(b, "got %d\n", b);
2699     b = DdeFreeStringHandle(ddeInst, hszApplication);
2700     ok(b, "got %d\n", b);
2701     b = DdeUninitialize(ddeInst);
2702     ok(b, "got %d\n", b);
2703 }
2704 
2705 static void init_test(void)
2706 {
2707     HMODULE hdll;
2708     HRESULT (WINAPI *pDllGetVersion)(DLLVERSIONINFO*);
2709     char filename[MAX_PATH];
2710     WCHAR lnkfile[MAX_PATH];
2711     char params[1024];
2712     const char* const * testfile;
2713     lnk_desc_t desc;
2714     DWORD rc;
2715     HRESULT r;
2716 
2717     hdll=GetModuleHandleA("shell32.dll");
2718     pDllGetVersion=(void*)GetProcAddress(hdll, "DllGetVersion");
2719     if (pDllGetVersion)
2720     {
2721         dllver.cbSize=sizeof(dllver);
2722         pDllGetVersion(&dllver);
2723         trace("major=%d minor=%d build=%d platform=%d\n",
2724               dllver.dwMajorVersion, dllver.dwMinorVersion,
2725               dllver.dwBuildNumber, dllver.dwPlatformID);
2726     }
2727     else
2728     {
2729         memset(&dllver, 0, sizeof(dllver));
2730     }
2731 
2732     r = CoInitialize(NULL);
2733     ok(r == S_OK, "CoInitialize failed (0x%08x)\n", r);
2734     if (FAILED(r))
2735         exit(1);
2736 
2737     rc=GetModuleFileNameA(NULL, argv0, sizeof(argv0));
2738     ok(rc != 0 && rc < sizeof(argv0), "got %d\n", rc);
2739     if (GetFileAttributesA(argv0)==INVALID_FILE_ATTRIBUTES)
2740     {
2741         strcat(argv0, ".so");
2742         ok(GetFileAttributesA(argv0)!=INVALID_FILE_ATTRIBUTES,
2743            "unable to find argv0!\n");
2744     }
2745 
2746     /* Older versions (win 2k) fail tests if there is a space in
2747        the path. */
2748     if (dllver.dwMajorVersion <= 5)
2749         strcpy(filename, "c:\\");
2750     else
2751         GetTempPathA(sizeof(filename), filename);
2752     GetTempFileNameA(filename, "wt", 0, tmpdir);
2753     GetLongPathNameA(tmpdir, tmpdir, sizeof(tmpdir));
2754     DeleteFileA( tmpdir );
2755     rc = CreateDirectoryA( tmpdir, NULL );
2756     ok( rc, "failed to create %s err %u\n", tmpdir, GetLastError() );
2757     /* Set %TMPDIR% for the tests */
2758     SetEnvironmentVariableA("TMPDIR", tmpdir);
2759 
2760     rc = GetTempFileNameA(tmpdir, "wt", 0, child_file);
2761     ok(rc != 0, "got %d\n", rc);
2762     init_event(child_file);
2763 
2764     /* Set up the test files */
2765     testfile=testfiles;
2766     while (*testfile)
2767     {
2768         HANDLE hfile;
2769 
2770         sprintf(filename, *testfile, tmpdir);
2771         hfile=CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
2772                      FILE_ATTRIBUTE_NORMAL, NULL);
2773         if (hfile==INVALID_HANDLE_VALUE)
2774         {
2775             trace("unable to create '%s': err=%u\n", filename, GetLastError());
2776             assert(0);
2777         }
2778         CloseHandle(hfile);
2779         testfile++;
2780     }
2781 
2782     /* Setup the test shortcuts */
2783     sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir);
2784     MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, sizeof(lnkfile)/sizeof(*lnkfile));
2785     desc.description=NULL;
2786     desc.workdir=NULL;
2787     sprintf(filename, "%s\\test file.shlexec", tmpdir);
2788     desc.path=filename;
2789     desc.pidl=NULL;
2790     desc.arguments="ignored";
2791     desc.showcmd=0;
2792     desc.icon=NULL;
2793     desc.icon_id=0;
2794     desc.hotkey=0;
2795     create_lnk(lnkfile, &desc, 0);
2796 
2797     sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
2798     MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, sizeof(lnkfile)/sizeof(*lnkfile));
2799     desc.description=NULL;
2800     desc.workdir=NULL;
2801     desc.path=argv0;
2802     desc.pidl=NULL;
2803     sprintf(params, "shlexec \"%s\" Lnk", child_file);
2804     desc.arguments=params;
2805     desc.showcmd=0;
2806     desc.icon=NULL;
2807     desc.icon_id=0;
2808     desc.hotkey=0;
2809     create_lnk(lnkfile, &desc, 0);
2810 
2811     /* Create a basic association suitable for most tests */
2812     if (!create_test_association(".shlexec"))
2813     {
2814         skip_shlexec_tests = TRUE;
2815         skip("Unable to create association for '.shlexec'\n");
2816         return;
2817     }
2818     create_test_verb("shlexec.shlexec", "Open", 0, "Open \"%1\"");
2819     create_test_verb("shlexec.shlexec", "NoQuotes", 0, "NoQuotes %1");
2820     create_test_verb("shlexec.shlexec", "LowerL", 0, "LowerL %l");
2821     create_test_verb("shlexec.shlexec", "QuotedLowerL", 0, "QuotedLowerL \"%l\"");
2822     create_test_verb("shlexec.shlexec", "UpperL", 0, "UpperL %L");
2823     create_test_verb("shlexec.shlexec", "QuotedUpperL", 0, "QuotedUpperL \"%L\"");
2824 
2825     create_test_association(".sha");
2826     create_test_verb("shlexec.sha", "averb", 0, "AVerb \"%1\"");
2827 
2828     create_test_class("shlproto", TRUE);
2829     create_test_verb("shlproto", "open", 0, "URL \"%1\"");
2830     create_test_verb("shlproto", "averb", 0, "AVerb \"%1\"");
2831 
2832     /* Set an environment variable to see if it is inherited */
2833     SetEnvironmentVariableA("ShlexecVar", "Present");
2834 }
2835 
2836 static void cleanup_test(void)
2837 {
2838     char filename[MAX_PATH];
2839     const char* const * testfile;
2840 
2841     /* Delete the test files */
2842     testfile=testfiles;
2843     while (*testfile)
2844     {
2845         sprintf(filename, *testfile, tmpdir);
2846         /* Make sure we can delete the files ('test file.noassoc' is read-only now) */
2847         SetFileAttributesA(filename, FILE_ATTRIBUTE_NORMAL);
2848         DeleteFileA(filename);
2849         testfile++;
2850     }
2851     DeleteFileA(child_file);
2852     RemoveDirectoryA(tmpdir);
2853 
2854     /* Delete the test association */
2855     delete_test_association(".shlexec");
2856     delete_test_association(".sha");
2857     delete_test_class("shlproto");
2858 
2859     CloseHandle(hEvent);
2860 
2861     CoUninitialize();
2862 }
2863 
2864 static void test_directory(void)
2865 {
2866     char path[MAX_PATH], curdir[MAX_PATH];
2867     char params[1024], dirpath[1024];
2868     INT_PTR rc;
2869 
2870     sprintf(path, "%s\\test2.exe", tmpdir);
2871     CopyFileA(argv0, path, FALSE);
2872 
2873     sprintf(params, "shlexec \"%s\" Exec", child_file);
2874 
2875     /* Test with the current directory */
2876     GetCurrentDirectoryA(sizeof(curdir), curdir);
2877     SetCurrentDirectoryA(tmpdir);
2878     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2879                         NULL, "test2.exe", params, NULL, NULL);
2880     okShell(rc > 32, "returned %lu\n", rc);
2881     okChildInt("argcA", 4);
2882     okChildString("argvA3", "Exec");
2883     todo_wine okChildPath("longPath", path);
2884     SetCurrentDirectoryA(curdir);
2885 
2886     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2887                         NULL, "test2.exe", params, NULL, NULL);
2888     okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2889 
2890     /* Explicitly specify the directory to use */
2891     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2892                         NULL, "test2.exe", params, tmpdir, NULL);
2893     okShell(rc > 32, "returned %lu\n", rc);
2894     okChildInt("argcA", 4);
2895     okChildString("argvA3", "Exec");
2896     todo_wine okChildPath("longPath", path);
2897 
2898     /* Specify it through an environment variable */
2899     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2900                         NULL, "test2.exe", params, "%TMPDIR%", NULL);
2901     todo_wine okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2902 
2903     rc=shell_execute_ex(SEE_MASK_DOENVSUBST|SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2904                         NULL, "test2.exe", params, "%TMPDIR%", NULL);
2905     okShell(rc > 32, "returned %lu\n", rc);
2906     okChildInt("argcA", 4);
2907     okChildString("argvA3", "Exec");
2908     todo_wine okChildPath("longPath", path);
2909 
2910     /* Not a colon-separated directory list */
2911     sprintf(dirpath, "%s:%s", curdir, tmpdir);
2912     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2913                         NULL, "test2.exe", params, dirpath, NULL);
2914     okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2915 }
2916 
2917 START_TEST(shlexec)
2918 {
2919 
2920     myARGC = winetest_get_mainargs(&myARGV);
2921     if (myARGC >= 3)
2922     {
2923         doChild(myARGC, myARGV);
2924         /* Skip the tests/failures trace for child processes */
2925         ExitProcess(winetest_get_failures());
2926     }
2927 
2928     init_test();
2929 
2930     test_commandline2argv();
2931     test_argify();
2932     test_lpFile_parsed();
2933     test_filename();
2934     test_fileurls();
2935     test_urls();
2936     test_find_executable();
2937     test_lnks();
2938     test_exes();
2939     test_dde();
2940     test_dde_default_app();
2941     test_directory();
2942 
2943     cleanup_test();
2944 }
2945