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