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