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