server: Use server_get_file_info for all info classes not implemented on client side.
[wine.git] / dlls / shell32 / tests / shlexec.c
blob9dff9f81e6b0268bea4e755ed0086363280b6f54
1 /*
2 * Unit test of the ShellExecute function.
4 * Copyright 2005, 2016 Francois Gouget for CodeWeavers
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.
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.
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
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
33 /* Needed to get SEE_MASK_NOZONECHECKS with the PSDK */
34 #define NTDDI_WINXPSP1 0x05010100
35 #define NTDDI_VERSION NTDDI_WINXPSP1
36 #define _WIN32_WINNT 0x0501
38 #include <stdio.h>
39 #include <assert.h>
41 #include "wtypes.h"
42 #include "winbase.h"
43 #include "windef.h"
44 #include "shellapi.h"
45 #include "shlwapi.h"
46 #include "ddeml.h"
48 #include "wine/heap.h"
49 #include "wine/test.h"
51 #include "shell32_test.h"
54 static char argv0[MAX_PATH];
55 static int myARGC;
56 static char** myARGV;
57 static char tmpdir[MAX_PATH];
58 static char child_file[MAX_PATH];
59 static DLLVERSIONINFO dllver;
60 static BOOL skip_shlexec_tests = FALSE;
61 static BOOL skip_noassoc_tests = FALSE;
62 static HANDLE dde_ready_event;
65 /***
67 * Helpers to read from / write to the child process results file.
68 * (borrowed from dlls/kernel32/tests/process.c)
70 ***/
72 static const char* encodeA(const char* str)
74 static char encoded[2*1024+1];
75 char* ptr;
76 size_t len,i;
78 if (!str) return "";
79 len = strlen(str) + 1;
80 if (len >= sizeof(encoded)/2)
82 fprintf(stderr, "string is too long!\n");
83 assert(0);
85 ptr = encoded;
86 for (i = 0; i < len; i++)
87 sprintf(&ptr[i * 2], "%02x", (unsigned char)str[i]);
88 ptr[2 * len] = '\0';
89 return ptr;
92 static unsigned decode_char(char c)
94 if (c >= '0' && c <= '9') return c - '0';
95 if (c >= 'a' && c <= 'f') return c - 'a' + 10;
96 assert(c >= 'A' && c <= 'F');
97 return c - 'A' + 10;
100 static char* decodeA(const char* str)
102 static char decoded[1024];
103 char* ptr;
104 size_t len,i;
106 len = strlen(str) / 2;
107 if (!len--) return NULL;
108 if (len >= sizeof(decoded))
110 fprintf(stderr, "string is too long!\n");
111 assert(0);
113 ptr = decoded;
114 for (i = 0; i < len; i++)
115 ptr[i] = (decode_char(str[2 * i]) << 4) | decode_char(str[2 * i + 1]);
116 ptr[len] = '\0';
117 return ptr;
120 static void WINETEST_PRINTF_ATTR(2,3) childPrintf(HANDLE h, const char* fmt, ...)
122 va_list valist;
123 char buffer[1024];
124 DWORD w;
126 va_start(valist, fmt);
127 vsprintf(buffer, fmt, valist);
128 va_end(valist);
129 WriteFile(h, buffer, strlen(buffer), &w, NULL);
132 static char* getChildString(const char* sect, const char* key)
134 char buf[1024];
135 char* ret;
137 GetPrivateProfileStringA(sect, key, "-", buf, sizeof(buf), child_file);
138 if (buf[0] == '\0' || (buf[0] == '-' && buf[1] == '\0')) return NULL;
139 assert(!(strlen(buf) & 1));
140 ret = decodeA(buf);
141 return ret;
145 /***
147 * Child code
149 ***/
151 #define CHILD_DDE_TIMEOUT 2500
152 static DWORD ddeInst;
153 static HSZ hszTopic;
154 static char ddeExec[MAX_PATH], ddeApplication[MAX_PATH];
155 static BOOL post_quit_on_execute;
157 /* Handle DDE for doChild() and test_dde_default_app() */
158 static HDDEDATA CALLBACK ddeCb(UINT uType, UINT uFmt, HCONV hConv,
159 HSZ hsz1, HSZ hsz2, HDDEDATA hData,
160 ULONG_PTR dwData1, ULONG_PTR dwData2)
162 DWORD size = 0;
164 if (winetest_debug > 2)
165 trace("dde_cb: %04x, %04x, %p, %p, %p, %p, %08lx, %08lx\n",
166 uType, uFmt, hConv, hsz1, hsz2, hData, dwData1, dwData2);
168 switch (uType)
170 case XTYP_CONNECT:
171 if (!DdeCmpStringHandles(hsz1, hszTopic))
173 size = DdeQueryStringA(ddeInst, hsz2, ddeApplication, MAX_PATH, CP_WINANSI);
174 ok(size < MAX_PATH, "got size %d\n", size);
175 assert(size < MAX_PATH);
176 return (HDDEDATA)TRUE;
178 return (HDDEDATA)FALSE;
180 case XTYP_EXECUTE:
181 size = DdeGetData(hData, (LPBYTE)ddeExec, MAX_PATH, 0);
182 ok(size < MAX_PATH, "got size %d\n", size);
183 assert(size < MAX_PATH);
184 DdeFreeDataHandle(hData);
185 if (post_quit_on_execute)
186 PostQuitMessage(0);
187 return (HDDEDATA)DDE_FACK;
189 default:
190 return NULL;
194 static HANDLE hEvent;
195 static void init_event(const char* child_file)
197 char* event_name;
198 event_name=strrchr(child_file, '\\')+1;
199 hEvent=CreateEventA(NULL, FALSE, FALSE, event_name);
203 * This is just to make sure the child won't run forever stuck in a
204 * GetMessage() loop when DDE fails for some reason.
206 static void CALLBACK childTimeout(HWND wnd, UINT msg, UINT_PTR timer, DWORD time)
208 trace("childTimeout called\n");
210 PostQuitMessage(0);
213 static void doChild(int argc, char** argv)
215 char *filename, buffer[MAX_PATH];
216 HANDLE hFile, map;
217 int i;
218 UINT_PTR timer;
220 filename=argv[2];
221 hFile=CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0);
222 if (hFile == INVALID_HANDLE_VALUE)
223 return;
225 /* Arguments */
226 childPrintf(hFile, "[Child]\r\n");
227 if (winetest_debug > 2)
229 trace("cmdlineA='%s'\n", GetCommandLineA());
230 trace("argcA=%d\n", argc);
232 childPrintf(hFile, "cmdlineA=%s\r\n", encodeA(GetCommandLineA()));
233 childPrintf(hFile, "argcA=%d\r\n", argc);
234 for (i = 0; i < argc; i++)
236 if (winetest_debug > 2)
237 trace("argvA%d='%s'\n", i, argv[i]);
238 childPrintf(hFile, "argvA%d=%s\r\n", i, encodeA(argv[i]));
240 GetModuleFileNameA(GetModuleHandleA(NULL), buffer, sizeof(buffer));
241 childPrintf(hFile, "longPath=%s\r\n", encodeA(buffer));
243 /* Check environment variable inheritance */
244 *buffer = '\0';
245 SetLastError(0);
246 GetEnvironmentVariableA("ShlexecVar", buffer, sizeof(buffer));
247 childPrintf(hFile, "ShlexecVarLE=%d\r\n", GetLastError());
248 childPrintf(hFile, "ShlexecVar=%s\r\n", encodeA(buffer));
250 map = OpenFileMappingA(FILE_MAP_READ, FALSE, "winetest_shlexec_dde_map");
251 if (map != NULL)
253 HANDLE dde_ready;
254 char *shared_block = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 4096);
255 CloseHandle(map);
256 if (shared_block[0] != '\0' || shared_block[1] != '\0')
258 HDDEDATA hdde;
259 HSZ hszApplication;
260 MSG msg;
261 UINT rc;
263 post_quit_on_execute = TRUE;
264 ddeInst = 0;
265 rc = DdeInitializeA(&ddeInst, ddeCb, CBF_SKIP_ALLNOTIFICATIONS | CBF_FAIL_ADVISES |
266 CBF_FAIL_POKES | CBF_FAIL_REQUESTS, 0);
267 ok(rc == DMLERR_NO_ERROR, "DdeInitializeA() returned %d\n", rc);
268 hszApplication = DdeCreateStringHandleA(ddeInst, shared_block, CP_WINANSI);
269 ok(hszApplication != NULL, "DdeCreateStringHandleA(%s) = NULL\n", shared_block);
270 shared_block += strlen(shared_block) + 1;
271 hszTopic = DdeCreateStringHandleA(ddeInst, shared_block, CP_WINANSI);
272 ok(hszTopic != NULL, "DdeCreateStringHandleA(%s) = NULL\n", shared_block);
273 hdde = DdeNameService(ddeInst, hszApplication, 0, DNS_REGISTER | DNS_FILTEROFF);
274 ok(hdde != NULL, "DdeNameService() failed le=%u\n", GetLastError());
276 timer = SetTimer(NULL, 0, CHILD_DDE_TIMEOUT, childTimeout);
278 dde_ready = OpenEventA(EVENT_MODIFY_STATE, FALSE, "winetest_shlexec_dde_ready");
279 SetEvent(dde_ready);
280 CloseHandle(dde_ready);
282 while (GetMessageA(&msg, NULL, 0, 0))
284 if (winetest_debug > 2)
285 trace("msg %d lParam=%ld wParam=%lu\n", msg.message, msg.lParam, msg.wParam);
286 DispatchMessageA(&msg);
289 Sleep(500);
290 KillTimer(NULL, timer);
291 hdde = DdeNameService(ddeInst, hszApplication, 0, DNS_UNREGISTER);
292 ok(hdde != NULL, "DdeNameService() failed le=%u\n", GetLastError());
293 ok(DdeFreeStringHandle(ddeInst, hszTopic), "DdeFreeStringHandle(topic)\n");
294 ok(DdeFreeStringHandle(ddeInst, hszApplication), "DdeFreeStringHandle(application)\n");
295 ok(DdeUninitialize(ddeInst), "DdeUninitialize() failed\n");
297 else
299 dde_ready = OpenEventA(EVENT_MODIFY_STATE, FALSE, "winetest_shlexec_dde_ready");
300 SetEvent(dde_ready);
301 CloseHandle(dde_ready);
304 UnmapViewOfFile(shared_block);
306 childPrintf(hFile, "ddeExec=%s\r\n", encodeA(ddeExec));
309 childPrintf(hFile, "Failures=%d\r\n", winetest_get_failures());
310 CloseHandle(hFile);
312 init_event(filename);
313 SetEvent(hEvent);
314 CloseHandle(hEvent);
317 static void dump_child_(const char* file, int line)
319 if (winetest_debug > 1)
321 char key[18];
322 char* str;
323 int i, c;
325 str=getChildString("Child", "cmdlineA");
326 trace_(file, line)("cmdlineA='%s'\n", str);
327 c=GetPrivateProfileIntA("Child", "argcA", -1, child_file);
328 trace_(file, line)("argcA=%d\n",c);
329 for (i=0;i<c;i++)
331 sprintf(key, "argvA%d", i);
332 str=getChildString("Child", key);
333 trace_(file, line)("%s='%s'\n", key, str);
336 c=GetPrivateProfileIntA("Child", "ShlexecVarLE", -1, child_file);
337 trace_(file, line)("ShlexecVarLE=%d\n", c);
338 str=getChildString("Child", "ShlexecVar");
339 trace_(file, line)("ShlexecVar='%s'\n", str);
341 c=GetPrivateProfileIntA("Child", "Failures", -1, child_file);
342 trace_(file, line)("Failures=%d\n", c);
347 /***
349 * Helpers to check the ShellExecute() / child process results.
351 ***/
353 static char shell_call[2048];
354 static void WINETEST_PRINTF_ATTR(2,3) _okShell(int condition, const char *msg, ...)
356 va_list valist;
357 char buffer[2048];
359 strcpy(buffer, shell_call);
360 strcat(buffer, " ");
361 va_start(valist, msg);
362 vsprintf(buffer+strlen(buffer), msg, valist);
363 va_end(valist);
364 winetest_ok(condition, "%s", buffer);
366 #define okShell_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : _okShell
367 #define okShell okShell_(__FILE__, __LINE__)
369 static char assoc_desc[2048];
370 static void reset_association_description(void)
372 *assoc_desc = '\0';
375 static void okChildString_(const char* file, int line, const char* key, const char* expected, const char* bad)
377 char* result;
378 result=getChildString("Child", key);
379 if (!result)
381 okShell_(file, line)(FALSE, "%s expected '%s', but key not found or empty\n", key, expected);
382 return;
384 okShell_(file, line)(lstrcmpiA(result, expected) == 0 ||
385 broken(lstrcmpiA(result, bad) == 0),
386 "%s expected '%s', got '%s'\n", key, expected, result);
388 #define okChildString(key, expected) okChildString_(__FILE__, __LINE__, (key), (expected), (expected))
389 #define okChildStringBroken(key, expected, broken) okChildString_(__FILE__, __LINE__, (key), (expected), (broken))
391 static int StrCmpPath(const char* s1, const char* s2)
393 if (!s1 && !s2) return 0;
394 if (!s2) return 1;
395 if (!s1) return -1;
396 while (*s1)
398 if (!*s2)
400 if (*s1=='.')
401 s1++;
402 return (*s1-*s2);
404 if ((*s1=='/' || *s1=='\\') && (*s2=='/' || *s2=='\\'))
406 while (*s1=='/' || *s1=='\\')
407 s1++;
408 while (*s2=='/' || *s2=='\\')
409 s2++;
411 else if (toupper(*s1)==toupper(*s2))
413 s1++;
414 s2++;
416 else
418 return (*s1-*s2);
421 if (*s2=='.')
422 s2++;
423 if (*s2)
424 return -1;
425 return 0;
428 static void okChildPath_(const char* file, int line, const char* key, const char* expected)
430 char* result;
431 int equal, shortequal;
432 result=getChildString("Child", key);
433 if (!result)
435 okShell_(file,line)(FALSE, "%s expected '%s', but key not found or empty\n", key, expected);
436 return;
438 shortequal = FALSE;
439 equal = (StrCmpPath(result, expected) == 0);
440 if (!equal)
442 char altpath[MAX_PATH];
443 DWORD rc = GetLongPathNameA(expected, altpath, sizeof(altpath));
444 if (0 < rc && rc < sizeof(altpath))
445 equal = (StrCmpPath(result, altpath) == 0);
446 if (!equal)
448 rc = GetShortPathNameA(expected, altpath, sizeof(altpath));
449 if (0 < rc && rc < sizeof(altpath))
450 shortequal = (StrCmpPath(result, altpath) == 0);
453 okShell_(file,line)(equal || broken(shortequal) /* XP SP1 */,
454 "%s expected '%s', got '%s'\n", key, expected, result);
456 #define okChildPath(key, expected) okChildPath_(__FILE__, __LINE__, (key), (expected))
458 static void okChildInt_(const char* file, int line, const char* key, int expected)
460 INT result;
461 result=GetPrivateProfileIntA("Child", key, expected, child_file);
462 okShell_(file,line)(result == expected,
463 "%s expected %d, but got %d\n", key, expected, result);
465 #define okChildInt(key, expected) okChildInt_(__FILE__, __LINE__, (key), (expected))
467 static void okChildIntBroken_(const char* file, int line, const char* key, int expected)
469 INT result;
470 result=GetPrivateProfileIntA("Child", key, expected, child_file);
471 okShell_(file,line)(result == expected || broken(result != expected),
472 "%s expected %d, but got %d\n", key, expected, result);
474 #define okChildIntBroken(key, expected) okChildIntBroken_(__FILE__, __LINE__, (key), (expected))
477 /***
479 * ShellExecute wrappers
481 ***/
483 static void strcat_param(char* str, const char* name, const char* param)
485 if (param)
487 if (str[strlen(str)-1] == '"')
488 strcat(str, ", ");
489 strcat(str, name);
490 strcat(str, "=\"");
491 strcat(str, param);
492 strcat(str, "\"");
496 static int _todo_wait = 0;
497 #define todo_wait for (_todo_wait = 1; _todo_wait; _todo_wait = 0)
499 static int bad_shellexecute = 0;
501 static INT_PTR shell_execute_(const char* file, int line, LPCSTR verb, LPCSTR filename, LPCSTR parameters, LPCSTR directory)
503 INT_PTR rc, rcEmpty = 0;
505 if(!verb)
506 rcEmpty = shell_execute_(file, line, "", filename, parameters, directory);
508 strcpy(shell_call, "ShellExecute(");
509 strcat_param(shell_call, "verb", verb);
510 strcat_param(shell_call, "file", filename);
511 strcat_param(shell_call, "params", parameters);
512 strcat_param(shell_call, "dir", directory);
513 strcat(shell_call, ")");
514 strcat(shell_call, assoc_desc);
515 if (winetest_debug > 1)
516 trace_(file, line)("Called %s\n", shell_call);
518 DeleteFileA(child_file);
519 SetLastError(0xcafebabe);
521 /* FIXME: We cannot use ShellExecuteEx() here because if there is no
522 * association it displays the 'Open With' dialog and I could not find
523 * a flag to prevent this.
525 rc=(INT_PTR)ShellExecuteA(NULL, verb, filename, parameters, directory, SW_HIDE);
527 if (rc > 32)
529 int wait_rc;
530 wait_rc=WaitForSingleObject(hEvent, 5000);
531 if (wait_rc == WAIT_TIMEOUT)
533 HWND wnd = FindWindowA("#32770", "Windows");
534 if (!wnd)
535 wnd = FindWindowA("Shell_Flyout", "");
536 if (wnd != NULL)
538 SendMessageA(wnd, WM_CLOSE, 0, 0);
539 win_skip("Skipping shellexecute of file with unassociated extension\n");
540 skip_noassoc_tests = TRUE;
541 rc = SE_ERR_NOASSOC;
544 todo_wine_if(_todo_wait)
545 okShell_(file, line)(wait_rc==WAIT_OBJECT_0 || rc <= 32,
546 "WaitForSingleObject returned %d\n", wait_rc);
548 /* The child process may have changed the result file, so let profile
549 * functions know about it
551 WritePrivateProfileStringA(NULL, NULL, NULL, child_file);
552 if (GetFileAttributesA(child_file) != INVALID_FILE_ATTRIBUTES)
554 int c;
555 dump_child_(file, line);
556 c = GetPrivateProfileIntA("Child", "Failures", -1, child_file);
557 if (c > 0)
558 winetest_add_failures(c);
559 okChildInt_(file, line, "ShlexecVarLE", 0);
560 okChildString_(file, line, "ShlexecVar", "Present", "Present");
563 if(!verb)
565 if (rc != rcEmpty && rcEmpty == SE_ERR_NOASSOC) /* NT4 */
566 bad_shellexecute = 1;
567 okShell_(file, line)(rc == rcEmpty ||
568 broken(rc != rcEmpty && rcEmpty == SE_ERR_NOASSOC) /* NT4 */,
569 "Got different return value with empty string: %lu %lu\n", rc, rcEmpty);
572 return rc;
574 #define shell_execute(verb, filename, parameters, directory) \
575 shell_execute_(__FILE__, __LINE__, verb, filename, parameters, directory)
577 static INT_PTR shell_execute_ex_(const char* file, int line,
578 DWORD mask, LPCSTR verb, LPCSTR filename,
579 LPCSTR parameters, LPCSTR directory,
580 LPCSTR class)
582 char smask[11];
583 SHELLEXECUTEINFOA sei;
584 BOOL success;
585 INT_PTR rc;
587 /* Add some flags so we can wait for the child process */
588 mask |= SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
590 strcpy(shell_call, "ShellExecuteEx(");
591 sprintf(smask, "0x%x", mask);
592 strcat_param(shell_call, "mask", smask);
593 strcat_param(shell_call, "verb", verb);
594 strcat_param(shell_call, "file", filename);
595 strcat_param(shell_call, "params", parameters);
596 strcat_param(shell_call, "dir", directory);
597 strcat_param(shell_call, "class", class);
598 strcat(shell_call, ")");
599 strcat(shell_call, assoc_desc);
600 if (winetest_debug > 1)
601 trace_(file, line)("Called %s\n", shell_call);
603 sei.cbSize=sizeof(sei);
604 sei.fMask=mask;
605 sei.hwnd=NULL;
606 sei.lpVerb=verb;
607 sei.lpFile=filename;
608 sei.lpParameters=parameters;
609 sei.lpDirectory=directory;
610 sei.nShow=SW_SHOWNORMAL;
611 sei.hInstApp=NULL; /* Out */
612 sei.lpIDList=NULL;
613 sei.lpClass=class;
614 sei.hkeyClass=NULL;
615 sei.dwHotKey=0;
616 U(sei).hIcon=NULL;
617 sei.hProcess=(HANDLE)0xdeadbeef; /* Out */
619 DeleteFileA(child_file);
620 SetLastError(0xcafebabe);
621 success=ShellExecuteExA(&sei);
622 rc=(INT_PTR)sei.hInstApp;
623 okShell_(file, line)((success && rc > 32) || (!success && rc <= 32),
624 "rc=%d and hInstApp=%ld is not allowed\n",
625 success, rc);
627 if (rc > 32)
629 DWORD wait_rc, rc;
630 if (sei.hProcess!=NULL)
632 wait_rc=WaitForSingleObject(sei.hProcess, 5000);
633 okShell_(file, line)(wait_rc==WAIT_OBJECT_0,
634 "WaitForSingleObject(hProcess) returned %d\n",
635 wait_rc);
636 wait_rc = GetExitCodeProcess(sei.hProcess, &rc);
637 okShell_(file, line)(wait_rc, "GetExitCodeProcess() failed le=%u\n", GetLastError());
638 todo_wine_if(_todo_wait)
639 okShell_(file, line)(rc == 0, "child returned %u\n", rc);
640 CloseHandle(sei.hProcess);
642 wait_rc=WaitForSingleObject(hEvent, 5000);
643 todo_wine_if(_todo_wait)
644 okShell_(file, line)(wait_rc==WAIT_OBJECT_0,
645 "WaitForSingleObject returned %d\n", wait_rc);
647 else
648 okShell_(file, line)(sei.hProcess==NULL,
649 "returned a process handle %p\n", sei.hProcess);
651 /* The child process may have changed the result file, so let profile
652 * functions know about it
654 WritePrivateProfileStringA(NULL, NULL, NULL, child_file);
655 if (rc > 32 && GetFileAttributesA(child_file) != INVALID_FILE_ATTRIBUTES)
657 int c;
658 dump_child_(file, line);
659 c = GetPrivateProfileIntA("Child", "Failures", -1, child_file);
660 if (c > 0)
661 winetest_add_failures(c);
662 /* When NOZONECHECKS is specified the environment variables are not
663 * inherited if the process does not have elevated privileges.
665 if ((mask & SEE_MASK_NOZONECHECKS) && skip_shlexec_tests)
667 okChildInt_(file, line, "ShlexecVarLE", 203);
668 okChildString_(file, line, "ShlexecVar", "", "");
670 else
672 okChildInt_(file, line, "ShlexecVarLE", 0);
673 okChildString_(file, line, "ShlexecVar", "Present", "Present");
677 return rc;
679 #define shell_execute_ex(mask, verb, filename, parameters, directory, class) \
680 shell_execute_ex_(__FILE__, __LINE__, mask, verb, filename, parameters, directory, class)
683 /***
685 * Functions to create / delete associations wrappers
687 ***/
689 static BOOL create_test_class(const char* class, BOOL protocol)
691 HKEY hkey, hkey_shell;
692 LONG rc;
694 rc = RegCreateKeyExA(HKEY_CLASSES_ROOT, class, 0, NULL, 0,
695 KEY_CREATE_SUB_KEY | KEY_SET_VALUE, NULL,
696 &hkey, NULL);
697 ok(rc == ERROR_SUCCESS || rc == ERROR_ACCESS_DENIED,
698 "could not create class %s (rc=%d)\n", class, rc);
699 if (rc != ERROR_SUCCESS)
700 return FALSE;
702 if (protocol)
704 rc = RegSetValueExA(hkey, "URL Protocol", 0, REG_SZ, (LPBYTE)"", 1);
705 ok(rc == ERROR_SUCCESS, "RegSetValueEx '%s' failed, expected ERROR_SUCCESS, got %d\n", class, rc);
708 rc = RegCreateKeyExA(hkey, "shell", 0, NULL, 0,
709 KEY_CREATE_SUB_KEY, NULL, &hkey_shell, NULL);
710 ok(rc == ERROR_SUCCESS, "RegCreateKeyEx 'shell' failed, expected ERROR_SUCCESS, got %d\n", rc);
712 CloseHandle(hkey);
713 CloseHandle(hkey_shell);
714 return TRUE;
717 static BOOL create_test_association(const char* extension)
719 HKEY hkey;
720 char class[MAX_PATH];
721 LONG rc;
723 sprintf(class, "shlexec%s", extension);
724 rc=RegCreateKeyExA(HKEY_CLASSES_ROOT, extension, 0, NULL, 0, KEY_SET_VALUE,
725 NULL, &hkey, NULL);
726 ok(rc == ERROR_SUCCESS || rc == ERROR_ACCESS_DENIED,
727 "could not create association %s (rc=%d)\n", class, rc);
728 if (rc != ERROR_SUCCESS)
729 return FALSE;
731 rc=RegSetValueExA(hkey, NULL, 0, REG_SZ, (LPBYTE) class, strlen(class)+1);
732 ok(rc==ERROR_SUCCESS, "RegSetValueEx '%s' failed, expected ERROR_SUCCESS, got %d\n", class, rc);
733 CloseHandle(hkey);
735 return create_test_class(class, FALSE);
738 /* Based on RegDeleteTreeW from dlls/advapi32/registry.c */
739 static LSTATUS myRegDeleteTreeA(HKEY hKey, LPCSTR lpszSubKey)
741 LONG ret;
742 DWORD dwMaxSubkeyLen, dwMaxValueLen;
743 DWORD dwMaxLen, dwSize;
744 CHAR szNameBuf[MAX_PATH], *lpszName = szNameBuf;
745 HKEY hSubKey = hKey;
747 if(lpszSubKey)
749 ret = RegOpenKeyExA(hKey, lpszSubKey, 0, KEY_READ, &hSubKey);
750 if (ret) return ret;
753 /* Get highest length for keys, values */
754 ret = RegQueryInfoKeyA(hSubKey, NULL, NULL, NULL, NULL,
755 &dwMaxSubkeyLen, NULL, NULL, &dwMaxValueLen, NULL, NULL, NULL);
756 if (ret) goto cleanup;
758 dwMaxSubkeyLen++;
759 dwMaxValueLen++;
760 dwMaxLen = max(dwMaxSubkeyLen, dwMaxValueLen);
761 if (dwMaxLen > ARRAY_SIZE(szNameBuf))
763 /* Name too big: alloc a buffer for it */
764 if (!(lpszName = heap_alloc(dwMaxLen*sizeof(CHAR))))
766 ret = ERROR_NOT_ENOUGH_MEMORY;
767 goto cleanup;
772 /* Recursively delete all the subkeys */
773 while (TRUE)
775 dwSize = dwMaxLen;
776 if (RegEnumKeyExA(hSubKey, 0, lpszName, &dwSize, NULL,
777 NULL, NULL, NULL)) break;
779 ret = myRegDeleteTreeA(hSubKey, lpszName);
780 if (ret) goto cleanup;
783 if (lpszSubKey)
784 ret = RegDeleteKeyA(hKey, lpszSubKey);
785 else
786 while (TRUE)
788 dwSize = dwMaxLen;
789 if (RegEnumValueA(hKey, 0, lpszName, &dwSize,
790 NULL, NULL, NULL, NULL)) break;
792 ret = RegDeleteValueA(hKey, lpszName);
793 if (ret) goto cleanup;
796 cleanup:
797 /* Free buffer if allocated */
798 if (lpszName != szNameBuf)
799 heap_free(lpszName);
800 if(lpszSubKey)
801 RegCloseKey(hSubKey);
802 return ret;
805 static void delete_test_class(const char* classname)
807 myRegDeleteTreeA(HKEY_CLASSES_ROOT, classname);
810 static void delete_test_association(const char* extension)
812 char classname[MAX_PATH];
814 sprintf(classname, "shlexec%s", extension);
815 delete_test_class(classname);
816 myRegDeleteTreeA(HKEY_CLASSES_ROOT, extension);
819 static void create_test_verb_dde(const char* classname, const char* verb,
820 int rawcmd, const char* cmdtail, const char *ddeexec,
821 const char *application, const char *topic,
822 const char *ifexec)
824 HKEY hkey_shell, hkey_verb, hkey_cmd;
825 char shell[MAX_PATH];
826 char* cmd;
827 LONG rc;
829 strcpy(assoc_desc, " Assoc ");
830 strcat_param(assoc_desc, "class", classname);
831 strcat_param(assoc_desc, "verb", verb);
832 sprintf(shell, "%d", rawcmd);
833 strcat_param(assoc_desc, "rawcmd", shell);
834 strcat_param(assoc_desc, "cmdtail", cmdtail);
835 strcat_param(assoc_desc, "ddeexec", ddeexec);
836 strcat_param(assoc_desc, "app", application);
837 strcat_param(assoc_desc, "topic", topic);
838 strcat_param(assoc_desc, "ifexec", ifexec);
840 sprintf(shell, "%s\\shell", classname);
841 rc=RegOpenKeyExA(HKEY_CLASSES_ROOT, shell, 0,
842 KEY_CREATE_SUB_KEY, &hkey_shell);
843 ok(rc == ERROR_SUCCESS, "%s key creation failed with %d\n", shell, rc);
845 rc=RegCreateKeyExA(hkey_shell, verb, 0, NULL, 0, KEY_CREATE_SUB_KEY,
846 NULL, &hkey_verb, NULL);
847 ok(rc == ERROR_SUCCESS, "%s verb key creation failed with %d\n", verb, rc);
849 rc=RegCreateKeyExA(hkey_verb, "command", 0, NULL, 0, KEY_SET_VALUE,
850 NULL, &hkey_cmd, NULL);
851 ok(rc == ERROR_SUCCESS, "\'command\' key creation failed with %d\n", rc);
853 if (rawcmd)
855 rc=RegSetValueExA(hkey_cmd, NULL, 0, REG_SZ, (LPBYTE)cmdtail, strlen(cmdtail)+1);
857 else
859 cmd = heap_alloc(strlen(argv0) + 10 + strlen(child_file) + 2 + strlen(cmdtail) + 1);
860 sprintf(cmd,"%s shlexec \"%s\" %s", argv0, child_file, cmdtail);
861 rc=RegSetValueExA(hkey_cmd, NULL, 0, REG_SZ, (LPBYTE)cmd, strlen(cmd)+1);
862 ok(rc == ERROR_SUCCESS, "setting command failed with %d\n", rc);
863 heap_free(cmd);
866 if (ddeexec)
868 HKEY hkey_ddeexec, hkey_application, hkey_topic, hkey_ifexec;
870 rc=RegCreateKeyExA(hkey_verb, "ddeexec", 0, NULL, 0, KEY_SET_VALUE |
871 KEY_CREATE_SUB_KEY, NULL, &hkey_ddeexec, NULL);
872 ok(rc == ERROR_SUCCESS, "\'ddeexec\' key creation failed with %d\n", rc);
873 rc=RegSetValueExA(hkey_ddeexec, NULL, 0, REG_SZ, (LPBYTE)ddeexec,
874 strlen(ddeexec)+1);
875 ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);
877 if (application)
879 rc=RegCreateKeyExA(hkey_ddeexec, "application", 0, NULL, 0, KEY_SET_VALUE,
880 NULL, &hkey_application, NULL);
881 ok(rc == ERROR_SUCCESS, "\'application\' key creation failed with %d\n", rc);
883 rc=RegSetValueExA(hkey_application, NULL, 0, REG_SZ, (LPBYTE)application,
884 strlen(application)+1);
885 ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);
886 CloseHandle(hkey_application);
888 if (topic)
890 rc=RegCreateKeyExA(hkey_ddeexec, "topic", 0, NULL, 0, KEY_SET_VALUE,
891 NULL, &hkey_topic, NULL);
892 ok(rc == ERROR_SUCCESS, "\'topic\' key creation failed with %d\n", rc);
893 rc=RegSetValueExA(hkey_topic, NULL, 0, REG_SZ, (LPBYTE)topic,
894 strlen(topic)+1);
895 ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);
896 CloseHandle(hkey_topic);
898 if (ifexec)
900 rc=RegCreateKeyExA(hkey_ddeexec, "ifexec", 0, NULL, 0, KEY_SET_VALUE,
901 NULL, &hkey_ifexec, NULL);
902 ok(rc == ERROR_SUCCESS, "\'ifexec\' key creation failed with %d\n", rc);
903 rc=RegSetValueExA(hkey_ifexec, NULL, 0, REG_SZ, (LPBYTE)ifexec,
904 strlen(ifexec)+1);
905 ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);
906 CloseHandle(hkey_ifexec);
908 CloseHandle(hkey_ddeexec);
911 CloseHandle(hkey_shell);
912 CloseHandle(hkey_verb);
913 CloseHandle(hkey_cmd);
916 /* Creates a class' non-DDE test verb.
917 * This function is meant to be used to create long term test verbs and thus
918 * does not trace them.
920 static void create_test_verb(const char* classname, const char* verb,
921 int rawcmd, const char* cmdtail)
923 create_test_verb_dde(classname, verb, rawcmd, cmdtail, NULL, NULL,
924 NULL, NULL);
925 reset_association_description();
929 /***
931 * GetLongPathNameA equivalent that supports Win95 and WinNT
933 ***/
935 static DWORD get_long_path_name(const char* shortpath, char* longpath, DWORD longlen)
937 char tmplongpath[MAX_PATH];
938 const char* p;
939 DWORD sp = 0, lp = 0;
940 DWORD tmplen;
941 WIN32_FIND_DATAA wfd;
942 HANDLE goit;
944 if (!shortpath || !shortpath[0])
945 return 0;
947 if (shortpath[1] == ':')
949 tmplongpath[0] = shortpath[0];
950 tmplongpath[1] = ':';
951 lp = sp = 2;
954 while (shortpath[sp])
956 /* check for path delimiters and reproduce them */
957 if (shortpath[sp] == '\\' || shortpath[sp] == '/')
959 if (!lp || tmplongpath[lp-1] != '\\')
961 /* strip double "\\" */
962 tmplongpath[lp++] = '\\';
964 tmplongpath[lp] = 0; /* terminate string */
965 sp++;
966 continue;
969 p = shortpath + sp;
970 if (sp == 0 && p[0] == '.' && (p[1] == '/' || p[1] == '\\'))
972 tmplongpath[lp++] = *p++;
973 tmplongpath[lp++] = *p++;
975 for (; *p && *p != '/' && *p != '\\'; p++);
976 tmplen = p - (shortpath + sp);
977 lstrcpynA(tmplongpath + lp, shortpath + sp, tmplen + 1);
978 /* Check if the file exists and use the existing file name */
979 goit = FindFirstFileA(tmplongpath, &wfd);
980 if (goit == INVALID_HANDLE_VALUE)
981 return 0;
982 FindClose(goit);
983 strcpy(tmplongpath + lp, wfd.cFileName);
984 lp += strlen(tmplongpath + lp);
985 sp += tmplen;
987 tmplen = strlen(shortpath) - 1;
988 if ((shortpath[tmplen] == '/' || shortpath[tmplen] == '\\') &&
989 (tmplongpath[lp - 1] != '/' && tmplongpath[lp - 1] != '\\'))
990 tmplongpath[lp++] = shortpath[tmplen];
991 tmplongpath[lp] = 0;
993 tmplen = strlen(tmplongpath) + 1;
994 if (tmplen <= longlen)
996 strcpy(longpath, tmplongpath);
997 tmplen--; /* length without 0 */
1000 return tmplen;
1004 /***
1006 * Tests
1008 ***/
1010 static const char* testfiles[]=
1012 "%s\\test file.shlexec",
1013 "%s\\%%nasty%% $file.shlexec",
1014 "%s\\test file.noassoc",
1015 "%s\\test file.noassoc.shlexec",
1016 "%s\\test file.shlexec.noassoc",
1017 "%s\\test_shortcut_shlexec.lnk",
1018 "%s\\test_shortcut_exe.lnk",
1019 "%s\\test file.shl",
1020 "%s\\test file.shlfoo",
1021 "%s\\test file.sha",
1022 "%s\\test file.sfe",
1023 "%s\\test file.shlproto",
1024 "%s\\masked file.shlexec",
1025 "%s\\masked",
1026 "%s\\test file.sde",
1027 "%s\\test file.exe",
1028 "%s\\test2.exe",
1029 "%s\\simple.shlexec",
1030 "%s\\drawback_file.noassoc",
1031 "%s\\drawback_file.noassoc foo.shlexec",
1032 "%s\\drawback_nonexist.noassoc foo.shlexec",
1033 NULL
1036 typedef struct
1038 const char* verb;
1039 const char* basename;
1040 int todo;
1041 INT_PTR rc;
1042 } filename_tests_t;
1044 static filename_tests_t filename_tests[]=
1046 /* Test bad / nonexistent filenames */
1047 {NULL, "%s\\nonexistent.shlexec", 0x0, SE_ERR_FNF},
1048 {NULL, "%s\\nonexistent.noassoc", 0x0, SE_ERR_FNF},
1050 /* Standard tests */
1051 {NULL, "%s\\test file.shlexec", 0x0, 33},
1052 {NULL, "%s\\test file.shlexec.", 0x0, 33},
1053 {NULL, "%s\\%%nasty%% $file.shlexec", 0x0, 33},
1054 {NULL, "%s/test file.shlexec", 0x0, 33},
1056 /* Test filenames with no association */
1057 {NULL, "%s\\test file.noassoc", 0x0, SE_ERR_NOASSOC},
1059 /* Test double extensions */
1060 {NULL, "%s\\test file.noassoc.shlexec", 0x0, 33},
1061 {NULL, "%s\\test file.shlexec.noassoc", 0x0, SE_ERR_NOASSOC},
1063 /* Test alternate verbs */
1064 {"LowerL", "%s\\nonexistent.shlexec", 0x0, SE_ERR_FNF},
1065 {"LowerL", "%s\\test file.noassoc", 0x0, SE_ERR_NOASSOC},
1067 {"QuotedLowerL", "%s\\test file.shlexec", 0x0, 33},
1068 {"QuotedUpperL", "%s\\test file.shlexec", 0x0, 33},
1070 {"notaverb", "%s\\test file.shlexec", 0x10, SE_ERR_NOASSOC},
1072 {"averb", "%s\\test file.sha", 0x10, 33},
1074 /* Test file masked due to space */
1075 {NULL, "%s\\masked file.shlexec", 0x0, 33},
1076 /* Test if quoting prevents the masking */
1077 {NULL, "%s\\masked file.shlexec", 0x40, 33},
1078 /* Test with incorrect quote */
1079 {NULL, "\"%s\\masked file.shlexec", 0x0, SE_ERR_FNF},
1081 /* Test extension / URI protocol collision */
1082 {NULL, "%s\\test file.shlproto", 0x0, SE_ERR_NOASSOC},
1084 {NULL, NULL, 0}
1087 static filename_tests_t noquotes_tests[]=
1089 /* Test unquoted '%1' thingies */
1090 {"NoQuotes", "%s\\test file.shlexec", 0xa, 33},
1091 {"LowerL", "%s\\test file.shlexec", 0xa, 33},
1092 {"UpperL", "%s\\test file.shlexec", 0xa, 33},
1094 {NULL, NULL, 0}
1097 static void test_lpFile_parsed(void)
1099 char fileA[MAX_PATH];
1100 INT_PTR rc;
1102 if (skip_shlexec_tests)
1104 skip("No filename parsing tests due to lack of .shlexec association\n");
1105 return;
1108 /* existing "drawback_file.noassoc" 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, "failed: rc=%lu\n", rc);
1113 /* if quoted, existing "drawback_file.noassoc" not prevents finding "drawback_file.noassoc foo.shlexec" on wine */
1114 sprintf(fileA, "\"%s\\drawback_file.noassoc foo.shlexec\"", tmpdir);
1115 rc=shell_execute(NULL, fileA, NULL, NULL);
1116 okShell(rc > 32 || broken(rc == SE_ERR_FNF) /* Win95/NT4 */,
1117 "failed: rc=%lu\n", rc);
1119 /* error should be SE_ERR_FNF, not SE_ERR_NOASSOC */
1120 sprintf(fileA, "\"%s\\drawback_file.noassoc\" foo.shlexec", tmpdir);
1121 rc=shell_execute(NULL, fileA, NULL, NULL);
1122 okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
1124 /* ""command"" not works on wine (and real win9x and w2k) */
1125 sprintf(fileA, "\"\"%s\\simple.shlexec\"\"", tmpdir);
1126 rc=shell_execute(NULL, fileA, NULL, NULL);
1127 todo_wine okShell(rc > 32 || broken(rc == SE_ERR_FNF) /* Win9x/2000 */,
1128 "failed: rc=%lu\n", rc);
1130 /* nonexisting "drawback_nonexist.noassoc" not prevents finding "drawback_nonexist.noassoc foo.shlexec" on wine */
1131 sprintf(fileA, "%s\\drawback_nonexist.noassoc foo.shlexec", tmpdir);
1132 rc=shell_execute(NULL, fileA, NULL, NULL);
1133 okShell(rc > 32, "failed: rc=%lu\n", rc);
1135 /* is SEE_MASK_DOENVSUBST default flag? Should only be when XP emulates 9x (XP bug or real 95 or ME behavior ?) */
1136 rc=shell_execute(NULL, "%TMPDIR%\\simple.shlexec", NULL, NULL);
1137 todo_wine okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
1139 /* quoted */
1140 rc=shell_execute(NULL, "\"%TMPDIR%\\simple.shlexec\"", NULL, NULL);
1141 todo_wine okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
1143 /* test SEE_MASK_DOENVSUBST works */
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, "failed: rc=%lu\n", rc);
1148 /* quoted lpFile does not work on real win95 and nt4 */
1149 rc=shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI,
1150 NULL, "\"%TMPDIR%\\simple.shlexec\"", NULL, NULL, NULL);
1151 okShell(rc > 32 || broken(rc == SE_ERR_FNF) /* Win95/NT4 */,
1152 "failed: rc=%lu\n", rc);
1155 typedef struct
1157 const char* cmd;
1158 const char* args[11];
1159 int todo;
1160 } cmdline_tests_t;
1162 static const cmdline_tests_t cmdline_tests[] =
1164 {"exe",
1165 {"exe", NULL}, 0},
1167 {"exe arg1 arg2 \"arg three\" 'four five` six\\ $even)",
1168 {"exe", "arg1", "arg2", "arg three", "'four", "five`", "six\\", "$even)", NULL}, 0},
1170 {"exe arg=1 arg-2 three\tfour\rfour\nfour ",
1171 {"exe", "arg=1", "arg-2", "three", "four\rfour\nfour", NULL}, 0},
1173 {"exe arg\"one\" \"second\"arg thirdarg ",
1174 {"exe", "argone", "secondarg", "thirdarg", NULL}, 0},
1176 /* Don't lose unclosed quoted arguments */
1177 {"exe arg1 \"unclosed",
1178 {"exe", "arg1", "unclosed", NULL}, 0},
1180 {"exe arg1 \"",
1181 {"exe", "arg1", "", NULL}, 0},
1183 /* cmd's metacharacters have no special meaning */
1184 {"exe \"one^\" \"arg\"&two three|four",
1185 {"exe", "one^", "arg&two", "three|four", NULL}, 0},
1187 /* Environment variables are not interpreted either */
1188 {"exe %TMPDIR% %2",
1189 {"exe", "%TMPDIR%", "%2", NULL}, 0},
1191 /* If not followed by a quote, backslashes go through as is */
1192 {"exe o\\ne t\\\\wo t\\\\\\ree f\\\\\\\\our ",
1193 {"exe", "o\\ne", "t\\\\wo", "t\\\\\\ree", "f\\\\\\\\our", NULL}, 0},
1195 {"exe \"o\\ne\" \"t\\\\wo\" \"t\\\\\\ree\" \"f\\\\\\\\our\" ",
1196 {"exe", "o\\ne", "t\\\\wo", "t\\\\\\ree", "f\\\\\\\\our", NULL}, 0},
1198 /* When followed by a quote their number is halved and the remainder
1199 * escapes the quote
1201 {"exe \\\"one \\\\\"two\" \\\\\\\"three \\\\\\\\\"four\" end",
1202 {"exe", "\"one", "\\two", "\\\"three", "\\\\four", "end", NULL}, 0},
1204 {"exe \"one\\\" still\" \"two\\\\\" \"three\\\\\\\" still\" \"four\\\\\\\\\" end",
1205 {"exe", "one\" still", "two\\", "three\\\" still", "four\\\\", "end", NULL}, 0},
1207 /* One can put a quote in an unquoted string by tripling it, that is in
1208 * effect quoting it like so """ -> ". The general rule is as follows:
1209 * 3n quotes -> n quotes
1210 * 3n+1 quotes -> n quotes plus start of a quoted string
1211 * 3n+2 quotes -> n quotes (plus an empty string from the remaining pair)
1212 * Nicely, when n is 0 we get the standard rules back.
1214 {"exe two\"\"quotes next",
1215 {"exe", "twoquotes", "next", NULL}, 0},
1217 {"exe three\"\"\"quotes next",
1218 {"exe", "three\"quotes", "next", NULL}, 0},
1220 {"exe four\"\"\"\" quotes\" next 4%3=1",
1221 {"exe", "four\" quotes", "next", "4%3=1", NULL}, 0},
1223 {"exe five\"\"\"\"\"quotes next",
1224 {"exe", "five\"quotes", "next", NULL}, 0},
1226 {"exe six\"\"\"\"\"\"quotes next",
1227 {"exe", "six\"\"quotes", "next", NULL}, 0},
1229 {"exe seven\"\"\"\"\"\"\" quotes\" next 7%3=1",
1230 {"exe", "seven\"\" quotes", "next", "7%3=1", NULL}, 0},
1232 {"exe twelve\"\"\"\"\"\"\"\"\"\"\"\"quotes next",
1233 {"exe", "twelve\"\"\"\"quotes", "next", NULL}, 0},
1235 {"exe thirteen\"\"\"\"\"\"\"\"\"\"\"\"\" quotes\" next 13%3=1",
1236 {"exe", "thirteen\"\"\"\" quotes", "next", "13%3=1", NULL}, 0},
1238 /* Inside a quoted string the opening quote is added to the set of
1239 * consecutive quotes to get the effective quotes count. This gives:
1240 * 1+3n quotes -> n quotes
1241 * 1+3n+1 quotes -> n quotes plus closes the quoted string
1242 * 1+3n+2 quotes -> n+1 quotes plus closes the quoted string
1244 {"exe \"two\"\"quotes next",
1245 {"exe", "two\"quotes", "next", NULL}, 0},
1247 {"exe \"two\"\" next",
1248 {"exe", "two\"", "next", NULL}, 0},
1250 {"exe \"three\"\"\" quotes\" next 4%3=1",
1251 {"exe", "three\" quotes", "next", "4%3=1", NULL}, 0},
1253 {"exe \"four\"\"\"\"quotes next",
1254 {"exe", "four\"quotes", "next", NULL}, 0},
1256 {"exe \"five\"\"\"\"\"quotes next",
1257 {"exe", "five\"\"quotes", "next", NULL}, 0},
1259 {"exe \"six\"\"\"\"\"\" quotes\" next 7%3=1",
1260 {"exe", "six\"\" quotes", "next", "7%3=1", NULL}, 0},
1262 {"exe \"eleven\"\"\"\"\"\"\"\"\"\"\"quotes next",
1263 {"exe", "eleven\"\"\"\"quotes", "next", NULL}, 0},
1265 {"exe \"twelve\"\"\"\"\"\"\"\"\"\"\"\" quotes\" next 13%3=1",
1266 {"exe", "twelve\"\"\"\" quotes", "next", "13%3=1", NULL}, 0},
1268 /* Escaped consecutive quotes are fun */
1269 {"exe \"the crazy \\\\\"\"\"\\\\\" quotes",
1270 {"exe", "the crazy \\\"\\", "quotes", NULL}, 0},
1272 /* The executable path has its own rules!!!
1273 * - Backslashes have no special meaning.
1274 * - If the first character is a quote, then the second quote ends the
1275 * executable path.
1276 * - The previous rule holds even if the next character is not a space!
1277 * - If the first character is not a quote, then quotes have no special
1278 * meaning either and the executable path stops at the first space.
1279 * - The consecutive quotes rules don't apply either.
1280 * - Even if there is no space between the executable path and the first
1281 * argument, the latter is parsed using the regular rules.
1283 {"exe\"file\"path arg1",
1284 {"exe\"file\"path", "arg1", NULL}, 0},
1286 {"exe\"file\"path\targ1",
1287 {"exe\"file\"path", "arg1", NULL}, 0},
1289 {"exe\"path\\ arg1",
1290 {"exe\"path\\", "arg1", NULL}, 0},
1292 {"\\\"exe \"arg one\"",
1293 {"\\\"exe", "arg one", NULL}, 0},
1295 {"\"spaced exe\" \"next arg\"",
1296 {"spaced exe", "next arg", NULL}, 0},
1298 {"\"spaced exe\"\t\"next arg\"",
1299 {"spaced exe", "next arg", NULL}, 0},
1301 {"\"exe\"arg\" one\" argtwo",
1302 {"exe", "arg one", "argtwo", NULL}, 0},
1304 {"\"spaced exe\\\"arg1 arg2",
1305 {"spaced exe\\", "arg1", "arg2", NULL}, 0},
1307 {"\"two\"\" arg1 ",
1308 {"two", " arg1 ", NULL}, 0},
1310 {"\"three\"\"\" arg2",
1311 {"three", "", "arg2", NULL}, 0},
1313 {"\"four\"\"\"\"arg1",
1314 {"four", "\"arg1", NULL}, 0},
1316 /* If the first character is a space then the executable path is empty */
1317 {" \"arg\"one argtwo",
1318 {"", "argone", "argtwo", NULL}, 0},
1320 {NULL, {NULL}, 0}
1323 static BOOL test_one_cmdline(const cmdline_tests_t* test)
1325 WCHAR cmdW[MAX_PATH], argW[MAX_PATH];
1326 LPWSTR *cl2a;
1327 int cl2a_count;
1328 LPWSTR *argsW;
1329 int i, count;
1331 /* trace("----- cmd='%s'\n", test->cmd); */
1332 MultiByteToWideChar(CP_ACP, 0, test->cmd, -1, cmdW, ARRAY_SIZE(cmdW));
1333 argsW = cl2a = CommandLineToArgvW(cmdW, &cl2a_count);
1334 if (argsW == NULL && cl2a_count == -1)
1336 win_skip("CommandLineToArgvW not implemented, skipping\n");
1337 return FALSE;
1339 ok(!argsW[cl2a_count] || broken(argsW[cl2a_count] != NULL) /* before Vista */,
1340 "expected NULL-terminated list of commandline arguments\n");
1342 count = 0;
1343 while (test->args[count])
1344 count++;
1345 todo_wine_if(test->todo & 0x1)
1346 ok(cl2a_count == count, "%s: expected %d arguments, but got %d\n", test->cmd, count, cl2a_count);
1348 for (i = 0; i < cl2a_count; i++)
1350 if (i < count)
1352 MultiByteToWideChar(CP_ACP, 0, test->args[i], -1, argW, ARRAY_SIZE(argW));
1353 todo_wine_if(test->todo & (1 << (i+4)))
1354 ok(!lstrcmpW(*argsW, argW), "%s: arg[%d] expected %s but got %s\n", test->cmd, i, wine_dbgstr_w(argW), wine_dbgstr_w(*argsW));
1356 else todo_wine_if(test->todo & 0x1)
1357 ok(0, "%s: got extra arg[%d]=%s\n", test->cmd, i, wine_dbgstr_w(*argsW));
1358 argsW++;
1360 LocalFree(cl2a);
1361 return TRUE;
1364 static void test_commandline2argv(void)
1366 static const WCHAR exeW[] = {'e','x','e',0};
1367 const cmdline_tests_t* test;
1368 WCHAR strW[MAX_PATH];
1369 LPWSTR *args;
1370 int numargs;
1371 DWORD le;
1373 test = cmdline_tests;
1374 while (test->cmd)
1376 if (!test_one_cmdline(test))
1377 return;
1378 test++;
1381 SetLastError(0xdeadbeef);
1382 args = CommandLineToArgvW(exeW, 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);
1386 SetLastError(0xdeadbeef);
1387 args = CommandLineToArgvW(NULL, NULL);
1388 le = GetLastError();
1389 ok(args == NULL && le == ERROR_INVALID_PARAMETER, "expected NULL with ERROR_INVALID_PARAMETER got %p with %u\n", args, le);
1391 *strW = 0;
1392 args = CommandLineToArgvW(strW, &numargs);
1393 ok(numargs == 1 || broken(numargs > 1), "expected 1 args, got %d\n", numargs);
1394 ok(!args || (!args[numargs] || broken(args[numargs] != NULL) /* before Vista */),
1395 "expected NULL-terminated list of commandline arguments\n");
1396 if (numargs == 1)
1398 GetModuleFileNameW(NULL, strW, ARRAY_SIZE(strW));
1399 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));
1401 if (args) LocalFree(args);
1404 /* The goal here is to analyze how ShellExecute() builds the command that
1405 * will be run. The tricky part is that there are three transformation
1406 * steps between the 'parameters' string we pass to ShellExecute() and the
1407 * argument list we observe in the child process:
1408 * - The parsing of 'parameters' string into individual arguments. The tests
1409 * show this is done differently from both CreateProcess() and
1410 * CommandLineToArgv()!
1411 * - The way the command 'formatting directives' such as %1, %2, etc are
1412 * handled.
1413 * - And the way the resulting command line is then parsed to yield the
1414 * argument list we check.
1416 typedef struct
1418 const char* verb;
1419 const char* params;
1420 int todo;
1421 const char *cmd;
1422 const char *broken;
1423 } argify_tests_t;
1425 static const argify_tests_t argify_tests[] =
1427 /* Start with three simple parameters. Notice that one can reorder and
1428 * duplicate the parameters. Also notice how %* take the raw input
1429 * parameters string, including the trailing spaces, no matter what
1430 * arguments have already been used.
1432 {"Params232S", "p2 p3 p4 ", TRUE,
1433 " p2 p3 \"p2\" \"p2 p3 p4 \""},
1435 /* Unquoted argument references like %2 don't automatically quote their
1436 * argument. Similarly, when they are quoted they don't escape the quotes
1437 * that their argument may contain.
1439 {"Params232S", "\"p two\" p3 p4 ", TRUE,
1440 " p two p3 \"p two\" \"\"p two\" p3 p4 \""},
1442 /* Only single digits are supported so only %1 to %9. Shown here with %20
1443 * because %10 is a pain.
1445 {"Params20", "p", FALSE,
1446 " \"p0\""},
1448 /* Only (double-)quotes have a special meaning. */
1449 {"Params23456", "'p2 p3` p4\\ $even", FALSE,
1450 " \"'p2\" \"p3`\" \"p4\\\" \"$even\" \"\""},
1452 {"Params23456", "p=2 p-3 p4\tp4\rp4\np4", TRUE,
1453 " \"p=2\" \"p-3\" \"p4\tp4\rp4\np4\" \"\" \"\""},
1455 /* In unquoted strings, quotes are treated are a parameter separator just
1456 * like spaces! However they can be doubled to get a literal quote.
1457 * Specifically:
1458 * 2n quotes -> n quotes
1459 * 2n+1 quotes -> n quotes and a parameter separator
1461 {"Params23456789", "one\"quote \"p four\" one\"quote p7", TRUE,
1462 " \"one\" \"quote\" \"p four\" \"one\" \"quote\" \"p7\" \"\" \"\""},
1464 {"Params23456789", "two\"\"quotes \"p three\" two\"\"quotes p5", TRUE,
1465 " \"two\"quotes\" \"p three\" \"two\"quotes\" \"p5\" \"\" \"\" \"\" \"\""},
1467 {"Params23456789", "three\"\"\"quotes \"p four\" three\"\"\"quotes p6", TRUE,
1468 " \"three\"\" \"quotes\" \"p four\" \"three\"\" \"quotes\" \"p6\" \"\" \"\""},
1470 {"Params23456789", "four\"\"\"\"quotes \"p three\" four\"\"\"\"quotes p5", TRUE,
1471 " \"four\"\"quotes\" \"p three\" \"four\"\"quotes\" \"p5\" \"\" \"\" \"\" \"\""},
1473 /* Quoted strings cannot be continued by tacking on a non space character
1474 * either.
1476 {"Params23456", "\"p two\"p3 \"p four\"p5 p6", TRUE,
1477 " \"p two\" \"p3\" \"p four\" \"p5\" \"p6\""},
1479 /* In quoted strings, the quotes are halved and an odd number closes the
1480 * string. Specifically:
1481 * 2n quotes -> n quotes
1482 * 2n+1 quotes -> n quotes and closes the string and hence the parameter
1484 {"Params23456789", "\"one q\"uote \"p four\" \"one q\"uote p7", TRUE,
1485 " \"one q\" \"uote\" \"p four\" \"one q\" \"uote\" \"p7\" \"\" \"\""},
1487 {"Params23456789", "\"two \"\" quotes\" \"p three\" \"two \"\" quotes\" p5", TRUE,
1488 " \"two \" quotes\" \"p three\" \"two \" quotes\" \"p5\" \"\" \"\" \"\" \"\""},
1490 {"Params23456789", "\"three q\"\"\"uotes \"p four\" \"three q\"\"\"uotes p7", TRUE,
1491 " \"three q\"\" \"uotes\" \"p four\" \"three q\"\" \"uotes\" \"p7\" \"\" \"\""},
1493 {"Params23456789", "\"four \"\"\"\" quotes\" \"p three\" \"four \"\"\"\" quotes\" p5", TRUE,
1494 " \"four \"\" quotes\" \"p three\" \"four \"\" quotes\" \"p5\" \"\" \"\" \"\" \"\""},
1496 /* The quoted string rules also apply to consecutive quotes at the start
1497 * of a parameter but don't count the opening quote!
1499 {"Params23456789", "\"\"twoquotes \"p four\" \"\"twoquotes p7", TRUE,
1500 " \"\" \"twoquotes\" \"p four\" \"\" \"twoquotes\" \"p7\" \"\" \"\""},
1502 {"Params23456789", "\"\"\"three quotes\" \"p three\" \"\"\"three quotes\" p5", TRUE,
1503 " \"\"three quotes\" \"p three\" \"\"three quotes\" \"p5\" \"\" \"\" \"\" \"\""},
1505 {"Params23456789", "\"\"\"\"fourquotes \"p four\" \"\"\"\"fourquotes p7", TRUE,
1506 " \"\"\" \"fourquotes\" \"p four\" \"\"\" \"fourquotes\" \"p7\" \"\" \"\""},
1508 /* An unclosed quoted string gets lost! */
1509 {"Params23456", "p2 \"p3\" \"p4 is lost", TRUE,
1510 " \"p2\" \"p3\" \"\" \"\" \"\"",
1511 " \"p2\" \"p3\" \"p3\" \"\" \"\""}, /* NT4/2k */
1513 /* Backslashes have no special meaning even when preceding quotes. All
1514 * they do is start an unquoted string.
1516 {"Params23456", "\\\"p\\three \"pfour\\\" pfive", TRUE,
1517 " \"\\\" \"p\\three\" \"pfour\\\" \"pfive\" \"\""},
1519 /* Environment variables are left untouched. */
1520 {"Params23456", "%TMPDIR% %t %c", FALSE,
1521 " \"%TMPDIR%\" \"%t\" \"%c\" \"\" \"\""},
1523 /* %~2 is equivalent to %*. However %~3 and higher include the spaces
1524 * before the parameter!
1525 * (but not the previous parameter's closing quote fortunately)
1527 {"Params2345Etc", "p2 p3 \"p4\" p5 p6 ", TRUE,
1528 " ~2=\"p2 p3 \"p4\" p5 p6 \" ~3=\" p3 \"p4\" p5 p6 \" ~4=\" \"p4\" p5 p6 \" ~5= p5 p6 "},
1530 /* %~n works even if there is no nth parameter. */
1531 {"Params9Etc", "p2 p3 p4 p5 p6 p7 p8 ", TRUE,
1532 " ~9=\" \""},
1534 {"Params9Etc", "p2 p3 p4 p5 p6 p7 ", TRUE,
1535 " ~9=\"\""},
1537 /* The %~n directives also transmit the tenth parameter and beyond. */
1538 {"Params9Etc", "p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 and beyond!", TRUE,
1539 " ~9=\" p9 p10 p11 and beyond!\""},
1541 /* Bad formatting directives lose their % sign, except those followed by
1542 * a tilde! Environment variables are not expanded but lose their % sign.
1544 {"ParamsBad", "p2 p3 p4 p5", TRUE,
1545 " \"% - %~ %~0 %~1 %~a %~* a b c TMPDIR\""},
1550 static void test_argify(void)
1552 char fileA[MAX_PATH], params[2*MAX_PATH+12];
1553 INT_PTR rc;
1554 const argify_tests_t* test;
1555 const char *bad;
1556 const char* cmd;
1558 /* Test with a long parameter */
1559 for (rc = 0; rc < MAX_PATH; rc++)
1560 fileA[rc] = 'a' + rc % 26;
1561 fileA[MAX_PATH-1] = '\0';
1562 sprintf(params, "shlexec \"%s\" %s", child_file, fileA);
1564 /* We need NOZONECHECKS on Win2003 to block a dialog */
1565 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, params, NULL, NULL);
1566 okShell(rc > 32, "failed: rc=%lu\n", rc);
1567 okChildInt("argcA", 4);
1568 okChildPath("argvA3", fileA);
1570 if (skip_shlexec_tests)
1572 skip("No argify tests due to lack of .shlexec association\n");
1573 return;
1576 create_test_verb("shlexec.shlexec", "Params232S", 0, "Params232S %2 %3 \"%2\" \"%*\"");
1577 create_test_verb("shlexec.shlexec", "Params23456", 0, "Params23456 \"%2\" \"%3\" \"%4\" \"%5\" \"%6\"");
1578 create_test_verb("shlexec.shlexec", "Params23456789", 0, "Params23456789 \"%2\" \"%3\" \"%4\" \"%5\" \"%6\" \"%7\" \"%8\" \"%9\"");
1579 create_test_verb("shlexec.shlexec", "Params2345Etc", 0, "Params2345Etc ~2=\"%~2\" ~3=\"%~3\" ~4=\"%~4\" ~5=%~5");
1580 create_test_verb("shlexec.shlexec", "Params9Etc", 0, "Params9Etc ~9=\"%~9\"");
1581 create_test_verb("shlexec.shlexec", "Params20", 0, "Params20 \"%20\"");
1582 create_test_verb("shlexec.shlexec", "ParamsBad", 0, "ParamsBad \"%% %- %~ %~0 %~1 %~a %~* %a %b %c %TMPDIR%\"");
1584 sprintf(fileA, "%s\\test file.shlexec", tmpdir);
1586 test = argify_tests;
1587 while (test->params)
1589 bad = test->broken ? test->broken : test->cmd;
1591 rc = shell_execute_ex(SEE_MASK_DOENVSUBST, test->verb, fileA, test->params, NULL, NULL);
1592 okShell(rc > 32, "failed: rc=%lu\n", rc);
1594 cmd = getChildString("Child", "cmdlineA");
1595 /* Our commands are such that the verb immediately precedes the
1596 * part we are interested in.
1598 if (cmd) cmd = strstr(cmd, test->verb);
1599 if (cmd) cmd += strlen(test->verb);
1600 if (!cmd) cmd = "(null)";
1601 todo_wine_if(test->todo)
1602 okShell(!strcmp(cmd, test->cmd) || broken(!strcmp(cmd, bad)),
1603 "expected '%s', got '%s'\n", cmd, test->cmd);
1604 test++;
1608 static void test_filename(void)
1610 char filename[MAX_PATH];
1611 const filename_tests_t* test;
1612 char* c;
1613 INT_PTR rc;
1615 if (skip_shlexec_tests)
1617 skip("No ShellExecute/filename tests due to lack of .shlexec association\n");
1618 return;
1621 test=filename_tests;
1622 while (test->basename)
1624 BOOL quotedfile = FALSE;
1626 if (skip_noassoc_tests && test->rc == SE_ERR_NOASSOC)
1628 win_skip("Skipping shellexecute of file with unassociated extension\n");
1629 test++;
1630 continue;
1633 sprintf(filename, test->basename, tmpdir);
1634 if (strchr(filename, '/'))
1636 c=filename;
1637 while (*c)
1639 if (*c=='\\')
1640 *c='/';
1641 c++;
1644 if ((test->todo & 0x40)==0)
1646 rc=shell_execute(test->verb, filename, NULL, NULL);
1648 else
1650 char quoted[MAX_PATH + 2];
1652 quotedfile = TRUE;
1653 sprintf(quoted, "\"%s\"", filename);
1654 rc=shell_execute(test->verb, quoted, NULL, NULL);
1656 if (rc > 32)
1657 rc=33;
1658 okShell(rc==test->rc ||
1659 broken(quotedfile && rc == SE_ERR_FNF), /* NT4 */
1660 "failed: rc=%ld err=%u\n", rc, GetLastError());
1661 if (rc == 33)
1663 const char* verb;
1664 todo_wine_if(test->todo & 0x2)
1665 okChildInt("argcA", 5);
1666 verb=(test->verb ? test->verb : "Open");
1667 todo_wine_if(test->todo & 0x4)
1668 okChildString("argvA3", verb);
1669 todo_wine_if(test->todo & 0x8)
1670 okChildPath("argvA4", filename);
1672 test++;
1675 test=noquotes_tests;
1676 while (test->basename)
1678 sprintf(filename, test->basename, tmpdir);
1679 rc=shell_execute(test->verb, filename, NULL, NULL);
1680 if (rc > 32)
1681 rc=33;
1682 todo_wine_if(test->todo & 0x1)
1683 okShell(rc==test->rc, "failed: rc=%ld err=%u\n", rc, GetLastError());
1684 if (rc==0)
1686 int count;
1687 const char* verb;
1688 char* str;
1690 verb=(test->verb ? test->verb : "Open");
1691 todo_wine_if(test->todo & 0x4)
1692 okChildString("argvA3", verb);
1694 count=4;
1695 str=filename;
1696 while (1)
1698 char attrib[18];
1699 char* space;
1700 space=strchr(str, ' ');
1701 if (space)
1702 *space='\0';
1703 sprintf(attrib, "argvA%d", count);
1704 todo_wine_if(test->todo & 0x8)
1705 okChildPath(attrib, str);
1706 count++;
1707 if (!space)
1708 break;
1709 str=space+1;
1711 todo_wine_if(test->todo & 0x2)
1712 okChildInt("argcA", count);
1714 test++;
1717 if (dllver.dwMajorVersion != 0)
1719 /* The more recent versions of shell32.dll accept quoted filenames
1720 * while older ones (e.g. 4.00) don't. Still we want to test this
1721 * because IE 6 depends on the new behavior.
1722 * One day we may need to check the exact version of the dll but for
1723 * now making sure DllGetVersion() is present is sufficient.
1725 sprintf(filename, "\"%s\\test file.shlexec\"", tmpdir);
1726 rc=shell_execute(NULL, filename, NULL, NULL);
1727 okShell(rc > 32, "failed: rc=%ld err=%u\n", rc, GetLastError());
1728 okChildInt("argcA", 5);
1729 okChildString("argvA3", "Open");
1730 sprintf(filename, "%s\\test file.shlexec", tmpdir);
1731 okChildPath("argvA4", filename);
1734 sprintf(filename, "\"%s\\test file.sha\"", tmpdir);
1735 rc=shell_execute(NULL, filename, NULL, NULL);
1736 todo_wine okShell(rc > 32, "failed: rc=%ld err=%u\n", rc, GetLastError());
1737 okChildInt("argcA", 5);
1738 todo_wine okChildString("argvA3", "averb");
1739 sprintf(filename, "%s\\test file.sha", tmpdir);
1740 todo_wine okChildPath("argvA4", filename);
1743 typedef struct
1745 const char* urlprefix;
1746 const char* basename;
1747 int flags;
1748 int todo;
1749 } fileurl_tests_t;
1751 #define URL_SUCCESS 0x1
1752 #define USE_COLON 0x2
1753 #define USE_BSLASH 0x4
1755 static fileurl_tests_t fileurl_tests[]=
1757 /* How many slashes does it take... */
1758 {"file:", "%s\\test file.shlexec", URL_SUCCESS, 0},
1759 {"file:/", "%s\\test file.shlexec", URL_SUCCESS, 0},
1760 {"file://", "%s\\test file.shlexec", URL_SUCCESS, 0},
1761 {"file:///", "%s\\test file.shlexec", URL_SUCCESS, 0},
1762 {"File:///", "%s\\test file.shlexec", URL_SUCCESS, 0},
1763 {"file:////", "%s\\test file.shlexec", URL_SUCCESS, 0},
1764 {"file://///", "%s\\test file.shlexec", 0, 0},
1766 /* Test with Windows-style paths */
1767 {"file:///", "%s\\test file.shlexec", URL_SUCCESS | USE_COLON, 0},
1768 {"file:///", "%s\\test file.shlexec", URL_SUCCESS | USE_BSLASH, 0},
1770 /* Check handling of hostnames */
1771 {"file://localhost/", "%s\\test file.shlexec", URL_SUCCESS, 0},
1772 {"file://localhost:80/", "%s\\test file.shlexec", 0, 0},
1773 {"file://LocalHost/", "%s\\test file.shlexec", URL_SUCCESS, 0},
1774 {"file://127.0.0.1/", "%s\\test file.shlexec", 0, 0},
1775 {"file://::1/", "%s\\test file.shlexec", 0, 0},
1776 {"file://notahost/", "%s\\test file.shlexec", 0, 0},
1778 /* Environment variables are not expanded in URLs */
1779 {"%urlprefix%", "%s\\test file.shlexec", 0, 0x1},
1780 {"file:///", "%%TMPDIR%%\\test file.shlexec", 0, 0},
1782 /* Test shortcuts vs. URLs */
1783 {"file://///", "%s\\test_shortcut_shlexec.lnk", 0, 0x1c},
1785 /* Confuse things by mixing protocols */
1786 {"file://", "shlproto://foo/bar", USE_COLON, 0},
1788 {NULL, NULL, 0, 0}
1791 static void test_fileurls(void)
1793 char filename[MAX_PATH], fileurl[MAX_PATH], longtmpdir[MAX_PATH];
1794 char command[MAX_PATH];
1795 const fileurl_tests_t* test;
1796 char *s;
1797 INT_PTR rc;
1799 if (skip_shlexec_tests)
1801 skip("No file URL tests due to lack of .shlexec association\n");
1802 return;
1805 rc = shell_execute_ex(SEE_MASK_FLAG_NO_UI, NULL,
1806 "file:///nosuchfile.shlexec", NULL, NULL, NULL);
1807 if (rc > 32)
1809 win_skip("shell32 is too old (likely < 4.72). Skipping the file URL tests\n");
1810 return;
1813 get_long_path_name(tmpdir, longtmpdir, ARRAY_SIZE(longtmpdir));
1814 SetEnvironmentVariableA("urlprefix", "file:///");
1816 test=fileurl_tests;
1817 while (test->basename)
1819 /* Build the file URL */
1820 sprintf(filename, test->basename, longtmpdir);
1821 strcpy(fileurl, test->urlprefix);
1822 strcat(fileurl, filename);
1823 s = fileurl + strlen(test->urlprefix);
1824 while (*s)
1826 if (!(test->flags & USE_COLON) && *s == ':')
1827 *s = '|';
1828 else if (!(test->flags & USE_BSLASH) && *s == '\\')
1829 *s = '/';
1830 s++;
1833 /* Test it first with FindExecutable() */
1834 rc = (INT_PTR)FindExecutableA(fileurl, NULL, command);
1835 ok(rc == SE_ERR_FNF, "FindExecutable(%s) failed: bad rc=%lu\n", fileurl, rc);
1837 /* Then ShellExecute() */
1838 if ((test->todo & 0x10) == 0)
1839 rc = shell_execute(NULL, fileurl, NULL, NULL);
1840 else todo_wait
1841 rc = shell_execute(NULL, fileurl, NULL, NULL);
1842 if (bad_shellexecute)
1844 win_skip("shell32 is too old (likely 4.72). Skipping the file URL tests\n");
1845 break;
1847 if (test->flags & URL_SUCCESS)
1849 todo_wine_if(test->todo & 0x1)
1850 okShell(rc > 32, "failed: bad rc=%lu\n", rc);
1852 else
1854 todo_wine_if(test->todo & 0x1)
1855 okShell(rc == SE_ERR_FNF || rc == SE_ERR_PNF ||
1856 broken(rc == SE_ERR_ACCESSDENIED) /* win2000 */,
1857 "failed: bad rc=%lu\n", rc);
1859 if (rc == 33)
1861 todo_wine_if(test->todo & 0x2)
1862 okChildInt("argcA", 5);
1863 todo_wine_if(test->todo & 0x4)
1864 okChildString("argvA3", "Open");
1865 todo_wine_if(test->todo & 0x8)
1866 okChildPath("argvA4", filename);
1868 test++;
1871 SetEnvironmentVariableA("urlprefix", NULL);
1874 static void test_urls(void)
1876 char url[MAX_PATH];
1877 INT_PTR rc;
1879 if (!create_test_class("fakeproto", FALSE))
1881 skip("Unable to create 'fakeproto' class for URL tests\n");
1882 return;
1884 create_test_verb("fakeproto", "open", 0, "URL %1");
1886 create_test_class("shlpaverb", TRUE);
1887 create_test_verb("shlpaverb", "averb", 0, "PAVerb \"%1\"");
1889 /* Protocols must be properly declared */
1890 rc = shell_execute(NULL, "notaproto://foo", NULL, NULL);
1891 ok(rc == SE_ERR_NOASSOC || broken(rc == SE_ERR_ACCESSDENIED),
1892 "%s returned %lu\n", shell_call, rc);
1894 rc = shell_execute(NULL, "fakeproto://foo/bar", NULL, NULL);
1895 todo_wine ok(rc == SE_ERR_NOASSOC || broken(rc == SE_ERR_ACCESSDENIED),
1896 "%s returned %lu\n", shell_call, rc);
1898 /* Here's a real live one */
1899 rc = shell_execute(NULL, "shlproto://foo/bar", NULL, NULL);
1900 ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
1901 okChildInt("argcA", 5);
1902 okChildString("argvA3", "URL");
1903 okChildString("argvA4", "shlproto://foo/bar");
1905 /* Check default verb detection */
1906 rc = shell_execute(NULL, "shlpaverb://foo/bar", NULL, NULL);
1907 todo_wine ok(rc > 32 || /* XP+IE7 - Win10 */
1908 broken(rc == SE_ERR_NOASSOC), /* XP+IE6 */
1909 "%s failed: rc=%lu\n", shell_call, rc);
1910 if (rc > 32)
1912 okChildInt("argcA", 5);
1913 todo_wine okChildString("argvA3", "PAVerb");
1914 todo_wine okChildString("argvA4", "shlpaverb://foo/bar");
1917 /* But alternative verbs are a recent feature! */
1918 rc = shell_execute("averb", "shlproto://foo/bar", NULL, NULL);
1919 ok(rc > 32 || /* Win8 - Win10 */
1920 broken(rc == SE_ERR_ACCESSDENIED), /* XP - Win7 */
1921 "%s failed: rc=%lu\n", shell_call, rc);
1922 if (rc > 32)
1924 okChildString("argvA3", "AVerb");
1925 okChildString("argvA4", "shlproto://foo/bar");
1928 /* A .lnk ending does not turn a URL into a shortcut */
1929 rc = shell_execute(NULL, "shlproto://foo/bar.lnk", NULL, NULL);
1930 ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
1931 okChildInt("argcA", 5);
1932 okChildString("argvA3", "URL");
1933 okChildString("argvA4", "shlproto://foo/bar.lnk");
1935 /* Neither does a .exe extension */
1936 rc = shell_execute(NULL, "shlproto://foo/bar.exe", NULL, NULL);
1937 ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
1938 okChildInt("argcA", 5);
1939 okChildString("argvA3", "URL");
1940 okChildString("argvA4", "shlproto://foo/bar.exe");
1942 /* But a class name overrides it */
1943 rc = shell_execute(NULL, "shlproto://foo/bar", "shlexec.shlexec", NULL);
1944 ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
1945 okChildInt("argcA", 5);
1946 okChildString("argvA3", "URL");
1947 okChildString("argvA4", "shlproto://foo/bar");
1949 /* Environment variables are expanded in URLs (but not in file URLs!) */
1950 rc = shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI,
1951 NULL, "shlproto://%TMPDIR%/bar", NULL, NULL, NULL);
1952 okShell(rc > 32, "failed: rc=%lu\n", rc);
1953 okChildInt("argcA", 5);
1954 sprintf(url, "shlproto://%s/bar", tmpdir);
1955 okChildString("argvA3", "URL");
1956 okChildStringBroken("argvA4", url, "shlproto://%TMPDIR%/bar");
1958 /* But only after the path has been identified as a URL */
1959 SetEnvironmentVariableA("urlprefix", "shlproto:///");
1960 rc = shell_execute(NULL, "%urlprefix%foo", NULL, NULL);
1961 todo_wine ok(rc == SE_ERR_FNF, "%s returned %lu\n", shell_call, rc);
1962 SetEnvironmentVariableA("urlprefix", NULL);
1964 delete_test_class("fakeproto");
1965 delete_test_class("shlpaverb");
1968 static void test_find_executable(void)
1970 char notepad_path[MAX_PATH];
1971 char filename[MAX_PATH];
1972 char command[MAX_PATH];
1973 const filename_tests_t* test;
1974 INT_PTR rc;
1976 if (!create_test_association(".sfe"))
1978 skip("Unable to create association for '.sfe'\n");
1979 return;
1981 create_test_verb("shlexec.sfe", "Open", 1, "%1");
1983 /* Don't test FindExecutable(..., NULL), it always crashes */
1985 strcpy(command, "your word");
1986 if (0) /* Can crash on Vista! */
1988 rc=(INT_PTR)FindExecutableA(NULL, NULL, command);
1989 ok(rc == SE_ERR_FNF || rc > 32 /* nt4 */, "FindExecutable(NULL) returned %ld\n", rc);
1990 ok(strcmp(command, "your word") != 0, "FindExecutable(NULL) returned command=[%s]\n", command);
1993 GetSystemDirectoryA( notepad_path, MAX_PATH );
1994 strcat( notepad_path, "\\notepad.exe" );
1996 /* Search for something that should be in the system-wide search path (no default directory) */
1997 strcpy(command, "your word");
1998 rc=(INT_PTR)FindExecutableA("notepad.exe", NULL, command);
1999 ok(rc > 32, "FindExecutable(%s) returned %ld\n", "notepad.exe", rc);
2000 ok(strcasecmp(command, notepad_path) == 0, "FindExecutable(%s) returned command=[%s]\n", "notepad.exe", command);
2002 /* Search for something that should be in the system-wide search path (with default directory) */
2003 strcpy(command, "your word");
2004 rc=(INT_PTR)FindExecutableA("notepad.exe", tmpdir, command);
2005 ok(rc > 32, "FindExecutable(%s) returned %ld\n", "notepad.exe", rc);
2006 ok(strcasecmp(command, notepad_path) == 0, "FindExecutable(%s) returned command=[%s]\n", "notepad.exe", command);
2008 strcpy(command, "your word");
2009 rc=(INT_PTR)FindExecutableA(tmpdir, NULL, command);
2010 ok(rc == SE_ERR_NOASSOC /* >= win2000 */ || rc > 32 /* win98, nt4 */, "FindExecutable(NULL) returned %ld\n", rc);
2011 ok(strcmp(command, "your word") != 0, "FindExecutable(NULL) returned command=[%s]\n", command);
2013 sprintf(filename, "%s\\test file.sfe", tmpdir);
2014 rc=(INT_PTR)FindExecutableA(filename, NULL, command);
2015 ok(rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc);
2016 /* Depending on the platform, command could be '%1' or 'test file.sfe' */
2018 rc=(INT_PTR)FindExecutableA("test file.sfe", tmpdir, command);
2019 ok(rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc);
2021 rc=(INT_PTR)FindExecutableA("test file.sfe", NULL, command);
2022 ok(rc == SE_ERR_FNF, "FindExecutable(%s) returned %ld\n", filename, rc);
2024 delete_test_association(".sfe");
2026 if (!create_test_association(".shl"))
2028 skip("Unable to create association for '.shl'\n");
2029 return;
2031 create_test_verb("shlexec.shl", "Open", 0, "Open");
2033 sprintf(filename, "%s\\test file.shl", tmpdir);
2034 rc=(INT_PTR)FindExecutableA(filename, NULL, command);
2035 ok(rc == SE_ERR_FNF /* NT4 */ || rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc);
2037 sprintf(filename, "%s\\test file.shlfoo", tmpdir);
2038 rc=(INT_PTR)FindExecutableA(filename, NULL, command);
2040 delete_test_association(".shl");
2042 if (rc > 32)
2044 /* On Windows XP and 2003 FindExecutable() is completely broken.
2045 * Probably what it does is convert the filename to 8.3 format,
2046 * which as a side effect converts the '.shlfoo' extension to '.shl',
2047 * and then tries to find an association for '.shl'. This means it
2048 * will normally fail on most extensions with more than 3 characters,
2049 * like '.mpeg', etc.
2050 * Also it means we cannot do any other test.
2052 win_skip("FindExecutable() is broken -> not running 4+ character extension tests\n");
2053 return;
2056 if (skip_shlexec_tests)
2058 skip("No FindExecutable/filename tests due to lack of .shlexec association\n");
2059 return;
2062 test=filename_tests;
2063 while (test->basename)
2065 sprintf(filename, test->basename, tmpdir);
2066 if (strchr(filename, '/'))
2068 char* c;
2069 c=filename;
2070 while (*c)
2072 if (*c=='\\')
2073 *c='/';
2074 c++;
2077 /* Win98 does not '\0'-terminate command! */
2078 memset(command, '\0', sizeof(command));
2079 rc=(INT_PTR)FindExecutableA(filename, NULL, command);
2080 if (rc > 32)
2081 rc=33;
2082 todo_wine_if(test->todo & 0x10)
2083 ok(rc==test->rc, "FindExecutable(%s) failed: rc=%ld\n", filename, rc);
2084 if (rc > 32)
2086 BOOL equal;
2087 equal=strcmp(command, argv0) == 0 ||
2088 /* NT4 returns an extra 0x8 character! */
2089 (strlen(command) == strlen(argv0)+1 && strncmp(command, argv0, strlen(argv0)) == 0);
2090 todo_wine_if(test->todo & 0x20)
2091 ok(equal, "FindExecutable(%s) returned command='%s' instead of '%s'\n",
2092 filename, command, argv0);
2094 test++;
2099 static filename_tests_t lnk_tests[]=
2101 /* Pass bad / nonexistent filenames as a parameter */
2102 {NULL, "%s\\nonexistent.shlexec", 0xa, 33},
2103 {NULL, "%s\\nonexistent.noassoc", 0xa, 33},
2105 /* Pass regular paths as a parameter */
2106 {NULL, "%s\\test file.shlexec", 0xa, 33},
2107 {NULL, "%s/%%nasty%% $file.shlexec", 0xa, 33},
2109 /* Pass filenames with no association as a parameter */
2110 {NULL, "%s\\test file.noassoc", 0xa, 33},
2112 {NULL, NULL, 0}
2115 static void test_lnks(void)
2117 char filename[MAX_PATH];
2118 char params[MAX_PATH];
2119 const filename_tests_t* test;
2120 INT_PTR rc;
2122 if (skip_shlexec_tests)
2123 skip("No FindExecutable/filename tests due to lack of .shlexec association\n");
2124 else
2126 /* Should open through our association */
2127 sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir);
2128 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, NULL);
2129 okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2130 okChildInt("argcA", 5);
2131 okChildString("argvA3", "Open");
2132 sprintf(params, "%s\\test file.shlexec", tmpdir);
2133 get_long_path_name(params, filename, sizeof(filename));
2134 okChildPath("argvA4", filename);
2136 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_DOENVSUBST, NULL, "%TMPDIR%\\test_shortcut_shlexec.lnk", NULL, NULL, NULL);
2137 okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2138 okChildInt("argcA", 5);
2139 okChildString("argvA3", "Open");
2140 sprintf(params, "%s\\test file.shlexec", tmpdir);
2141 get_long_path_name(params, filename, sizeof(filename));
2142 okChildPath("argvA4", filename);
2145 /* Should just run our executable */
2146 sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
2147 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, NULL);
2148 okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2149 okChildInt("argcA", 4);
2150 okChildString("argvA3", "Lnk");
2152 if (!skip_shlexec_tests)
2154 /* An explicit class overrides lnk's ContextMenuHandler */
2155 rc=shell_execute_ex(SEE_MASK_CLASSNAME | SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, "shlexec.shlexec");
2156 okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2157 okChildInt("argcA", 5);
2158 okChildString("argvA3", "Open");
2159 okChildPath("argvA4", filename);
2162 if (dllver.dwMajorVersion>=6)
2164 char* c;
2165 /* Recent versions of shell32.dll accept '/'s in shortcut paths.
2166 * Older versions don't or are quite buggy in this regard.
2168 sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
2169 c=filename;
2170 while (*c)
2172 if (*c=='\\')
2173 *c='/';
2174 c++;
2176 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, NULL);
2177 okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2178 okChildInt("argcA", 4);
2179 okChildString("argvA3", "Lnk");
2182 sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
2183 test=lnk_tests;
2184 while (test->basename)
2186 params[0]='\"';
2187 sprintf(params+1, test->basename, tmpdir);
2188 strcat(params,"\"");
2189 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, params,
2190 NULL, NULL);
2191 if (rc > 32)
2192 rc=33;
2193 todo_wine_if(test->todo & 0x1)
2194 okShell(rc==test->rc, "failed: rc=%lu err=%u\n", rc, GetLastError());
2195 if (rc==0)
2197 todo_wine_if(test->todo & 0x2)
2198 okChildInt("argcA", 5);
2199 todo_wine_if(test->todo & 0x4)
2200 okChildString("argvA3", "Lnk");
2201 sprintf(params, test->basename, tmpdir);
2202 okChildPath("argvA4", params);
2204 test++;
2209 static void test_exes(void)
2211 char filename[MAX_PATH];
2212 char params[1024];
2213 INT_PTR rc;
2215 sprintf(params, "shlexec \"%s\" Exec", child_file);
2217 /* We need NOZONECHECKS on Win2003 to block a dialog */
2218 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, params,
2219 NULL, NULL);
2220 okShell(rc > 32, "returned %lu\n", rc);
2221 okChildInt("argcA", 4);
2222 okChildString("argvA3", "Exec");
2224 if (! skip_noassoc_tests)
2226 sprintf(filename, "%s\\test file.noassoc", tmpdir);
2227 if (CopyFileA(argv0, filename, FALSE))
2229 rc=shell_execute(NULL, filename, params, NULL);
2230 todo_wine {
2231 okShell(rc==SE_ERR_NOASSOC, "returned %lu\n", rc);
2235 else
2237 win_skip("Skipping shellexecute of file with unassociated extension\n");
2240 /* test combining executable and parameters */
2241 sprintf(filename, "%s shlexec \"%s\" Exec", argv0, child_file);
2242 rc = shell_execute(NULL, filename, NULL, NULL);
2243 okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2245 sprintf(filename, "\"%s\" shlexec \"%s\" Exec", argv0, child_file);
2246 rc = shell_execute(NULL, filename, NULL, NULL);
2247 okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2249 /* A verb, even if invalid, overrides the normal handling of executables */
2250 todo_wait rc = shell_execute_ex(SEE_MASK_FLAG_NO_UI,
2251 "notaverb", argv0, NULL, NULL, NULL);
2252 todo_wine okShell(rc == SE_ERR_NOASSOC, "returned %lu\n", rc);
2254 if (!skip_shlexec_tests)
2256 /* A class overrides the normal handling of executables too */
2257 /* FIXME SEE_MASK_FLAG_NO_UI is only needed due to Wine's bug */
2258 rc = shell_execute_ex(SEE_MASK_CLASSNAME | SEE_MASK_FLAG_NO_UI,
2259 NULL, argv0, NULL, NULL, ".shlexec");
2260 todo_wine okShell(rc > 32, "returned %lu\n", rc);
2261 okChildInt("argcA", 5);
2262 todo_wine okChildString("argvA3", "Open");
2263 todo_wine okChildPath("argvA4", argv0);
2267 typedef struct
2269 const char* command;
2270 const char* ddeexec;
2271 const char* application;
2272 const char* topic;
2273 const char* ifexec;
2274 int expectedArgs;
2275 const char* expectedDdeExec;
2276 BOOL broken;
2277 } dde_tests_t;
2279 static dde_tests_t dde_tests[] =
2281 /* Test passing and not passing command-line
2282 * argument, no DDE */
2283 {"", NULL, NULL, NULL, NULL, 0, ""},
2284 {"\"%1\"", NULL, NULL, NULL, NULL, 1, ""},
2286 /* Test passing and not passing command-line
2287 * argument, with DDE */
2288 {"", "[open(\"%1\")]", "shlexec", "dde", NULL, 0, "[open(\"%s\")]"},
2289 {"\"%1\"", "[open(\"%1\")]", "shlexec", "dde", NULL, 1, "[open(\"%s\")]"},
2291 /* Test unquoted %1 in command and ddeexec
2292 * (test filename has space) */
2293 {"%1", "[open(%1)]", "shlexec", "dde", NULL, 2, "[open(%s)]", TRUE /* before vista */},
2295 /* Test ifexec precedence over ddeexec */
2296 {"", "[open(\"%1\")]", "shlexec", "dde", "[ifexec(\"%1\")]", 0, "[ifexec(\"%s\")]"},
2298 /* Test default DDE topic */
2299 {"", "[open(\"%1\")]", "shlexec", NULL, NULL, 0, "[open(\"%s\")]"},
2301 /* Test default DDE application */
2302 {"", "[open(\"%1\")]", NULL, "dde", NULL, 0, "[open(\"%s\")]"},
2304 {NULL}
2307 static int waitforinputidle_count;
2308 static DWORD WINAPI hooked_WaitForInputIdle(HANDLE process, DWORD timeout)
2310 waitforinputidle_count++;
2311 if (winetest_debug > 1)
2312 trace("WaitForInputIdle() waiting for dde event timeout=min(%u,5s)\n", timeout);
2313 timeout = timeout < 5000 ? timeout : 5000;
2314 return WaitForSingleObject(dde_ready_event, timeout);
2318 * WaitForInputIdle() will normally return immediately for console apps. That's
2319 * a problem for us because ShellExecute will assume that an app is ready to
2320 * receive DDE messages after it has called WaitForInputIdle() on that app.
2321 * To work around that we install our own version of WaitForInputIdle() that
2322 * will wait for the child to explicitly tell us that it is ready. We do that
2323 * by changing the entry for WaitForInputIdle() in the shell32 import address
2324 * table.
2326 static void hook_WaitForInputIdle(DWORD (WINAPI *new_func)(HANDLE, DWORD))
2328 char *base;
2329 PIMAGE_NT_HEADERS nt_headers;
2330 DWORD import_directory_rva;
2331 PIMAGE_IMPORT_DESCRIPTOR import_descriptor;
2332 int hook_count = 0;
2334 base = (char *) GetModuleHandleA("shell32.dll");
2335 nt_headers = (PIMAGE_NT_HEADERS)(base + ((PIMAGE_DOS_HEADER) base)->e_lfanew);
2336 import_directory_rva = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
2338 /* Search for the correct imported module by walking the import descriptors */
2339 import_descriptor = (PIMAGE_IMPORT_DESCRIPTOR)(base + import_directory_rva);
2340 while (U(*import_descriptor).OriginalFirstThunk != 0)
2342 char *import_module_name;
2344 import_module_name = base + import_descriptor->Name;
2345 if (lstrcmpiA(import_module_name, "user32.dll") == 0 ||
2346 lstrcmpiA(import_module_name, "user32") == 0)
2348 PIMAGE_THUNK_DATA int_entry;
2349 PIMAGE_THUNK_DATA iat_entry;
2351 /* The import name table and import address table are two parallel
2352 * arrays. We need the import name table to find the imported
2353 * routine and the import address table to patch the address, so
2354 * walk them side by side */
2355 int_entry = (PIMAGE_THUNK_DATA)(base + U(*import_descriptor).OriginalFirstThunk);
2356 iat_entry = (PIMAGE_THUNK_DATA)(base + import_descriptor->FirstThunk);
2357 while (int_entry->u1.Ordinal != 0)
2359 if (! IMAGE_SNAP_BY_ORDINAL(int_entry->u1.Ordinal))
2361 PIMAGE_IMPORT_BY_NAME import_by_name;
2362 import_by_name = (PIMAGE_IMPORT_BY_NAME)(base + int_entry->u1.AddressOfData);
2363 if (lstrcmpA((char *) import_by_name->Name, "WaitForInputIdle") == 0)
2365 /* Found the correct routine in the correct imported module. Patch it. */
2366 DWORD old_prot;
2367 VirtualProtect(&iat_entry->u1.Function, sizeof(ULONG_PTR), PAGE_READWRITE, &old_prot);
2368 iat_entry->u1.Function = (ULONG_PTR) new_func;
2369 VirtualProtect(&iat_entry->u1.Function, sizeof(ULONG_PTR), old_prot, &old_prot);
2370 if (winetest_debug > 1)
2371 trace("Hooked %s.WaitForInputIdle\n", import_module_name);
2372 hook_count++;
2373 break;
2376 int_entry++;
2377 iat_entry++;
2379 break;
2382 import_descriptor++;
2384 ok(hook_count, "Could not hook WaitForInputIdle()\n");
2387 static void test_dde(void)
2389 char filename[MAX_PATH], defApplication[MAX_PATH];
2390 const dde_tests_t* test;
2391 char params[1024];
2392 INT_PTR rc;
2393 HANDLE map;
2394 char *shared_block;
2395 DWORD ddeflags;
2397 hook_WaitForInputIdle(hooked_WaitForInputIdle);
2399 sprintf(filename, "%s\\test file.sde", tmpdir);
2401 /* Default service is application name minus path and extension */
2402 strcpy(defApplication, strrchr(argv0, '\\')+1);
2403 *strchr(defApplication, '.') = 0;
2405 map = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
2406 4096, "winetest_shlexec_dde_map");
2407 shared_block = MapViewOfFile(map, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 4096);
2409 ddeflags = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI;
2410 test = dde_tests;
2411 while (test->command)
2413 if (!create_test_association(".sde"))
2415 skip("Unable to create association for '.sde'\n");
2416 return;
2418 create_test_verb_dde("shlexec.sde", "Open", 0, test->command, test->ddeexec,
2419 test->application, test->topic, test->ifexec);
2421 if (test->application != NULL || test->topic != NULL)
2423 strcpy(shared_block, test->application ? test->application : defApplication);
2424 strcpy(shared_block + strlen(shared_block) + 1, test->topic ? test->topic : SZDDESYS_TOPIC);
2426 else
2428 shared_block[0] = '\0';
2429 shared_block[1] = '\0';
2431 ddeExec[0] = 0;
2433 waitforinputidle_count = 0;
2434 dde_ready_event = CreateEventA(NULL, TRUE, FALSE, "winetest_shlexec_dde_ready");
2435 rc = shell_execute_ex(ddeflags, NULL, filename, NULL, NULL, NULL);
2436 CloseHandle(dde_ready_event);
2437 if (!(ddeflags & SEE_MASK_WAITFORINPUTIDLE) && rc == SE_ERR_DDEFAIL &&
2438 GetLastError() == ERROR_FILE_NOT_FOUND &&
2439 strcmp(winetest_platform, "windows") == 0)
2441 /* Windows 10 does not call WaitForInputIdle() for DDE which creates
2442 * a race condition as the DDE server may not have time to start up.
2443 * When that happens the test fails with the above results and we
2444 * compensate by forcing the WaitForInputIdle() call.
2446 trace("Adding SEE_MASK_WAITFORINPUTIDLE for Windows 10\n");
2447 ddeflags |= SEE_MASK_WAITFORINPUTIDLE;
2448 delete_test_association(".sde");
2449 Sleep(CHILD_DDE_TIMEOUT);
2450 continue;
2452 okShell(32 < rc, "failed: rc=%lu err=%u\n", rc, GetLastError());
2453 if (test->ddeexec)
2454 okShell(waitforinputidle_count == 1 ||
2455 broken(waitforinputidle_count == 0) /* Win10 race */,
2456 "WaitForInputIdle() was called %u times\n",
2457 waitforinputidle_count);
2458 else
2459 okShell(waitforinputidle_count == 0, "WaitForInputIdle() was called %u times for a non-DDE case\n", waitforinputidle_count);
2461 if (32 < rc)
2463 if (test->broken)
2464 okChildIntBroken("argcA", test->expectedArgs + 3);
2465 else
2466 okChildInt("argcA", test->expectedArgs + 3);
2468 if (test->expectedArgs == 1) okChildPath("argvA3", filename);
2470 sprintf(params, test->expectedDdeExec, filename);
2471 okChildPath("ddeExec", params);
2473 reset_association_description();
2475 delete_test_association(".sde");
2476 test++;
2479 UnmapViewOfFile(shared_block);
2480 CloseHandle(map);
2481 hook_WaitForInputIdle((void *) WaitForInputIdle);
2484 #define DDE_DEFAULT_APP_VARIANTS 3
2485 typedef struct
2487 const char* command;
2488 const char* expectedDdeApplication[DDE_DEFAULT_APP_VARIANTS];
2489 int todo;
2490 int rc[DDE_DEFAULT_APP_VARIANTS];
2491 } dde_default_app_tests_t;
2493 static dde_default_app_tests_t dde_default_app_tests[] =
2495 /* There are three possible sets of results: Windows <= 2000, XP SP1 and
2496 * >= XP SP2. Use the first two tests to determine which results to expect.
2499 /* Test unquoted existing filename with a space */
2500 {"%s\\test file.exe", {"test file", "test file", "test"}, 0x0, {33, 33, 33}},
2501 {"%s\\test2 file.exe", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
2503 /* Test unquoted existing filename with a space */
2504 {"%s\\test file.exe param", {"test file", "test file", "test"}, 0x0, {33, 33, 33}},
2506 /* Test quoted existing filename with a space */
2507 {"\"%s\\test file.exe\"", {"test file", "test file", "test file"}, 0x0, {33, 33, 33}},
2508 {"\"%s\\test file.exe\" param", {"test file", "test file", "test file"}, 0x0, {33, 33, 33}},
2510 /* Test unquoted filename with a space that doesn't exist, but
2511 * test2.exe does */
2512 {"%s\\test2 file.exe param", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
2514 /* Test quoted filename with a space that does not exist */
2515 {"\"%s\\test2 file.exe\"", {"", "", "test2 file"}, 0x0, {5, 2, 33}},
2516 {"\"%s\\test2 file.exe\" param", {"", "", "test2 file"}, 0x0, {5, 2, 33}},
2518 /* Test filename supplied without the extension */
2519 {"%s\\test2", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
2520 {"%s\\test2 param", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
2522 /* Test an unquoted nonexistent filename */
2523 {"%s\\notexist.exe", {"", "", "notexist"}, 0x0, {5, 2, 33}},
2524 {"%s\\notexist.exe param", {"", "", "notexist"}, 0x0, {5, 2, 33}},
2526 /* Test an application that will be found on the path */
2527 {"cmd", {"cmd", "cmd", "cmd"}, 0x0, {33, 33, 33}},
2528 {"cmd param", {"cmd", "cmd", "cmd"}, 0x0, {33, 33, 33}},
2530 /* Test an application that will not be found on the path */
2531 {"xyzwxyzwxyz", {"", "", "xyzwxyzwxyz"}, 0x0, {5, 2, 33}},
2532 {"xyzwxyzwxyz param", {"", "", "xyzwxyzwxyz"}, 0x0, {5, 2, 33}},
2534 {NULL, {NULL}, 0, {0}}
2537 typedef struct
2539 char *filename;
2540 DWORD threadIdParent;
2541 } dde_thread_info_t;
2543 static DWORD CALLBACK ddeThread(LPVOID arg)
2545 dde_thread_info_t *info = arg;
2546 assert(info && info->filename);
2547 PostThreadMessageA(info->threadIdParent,
2548 WM_QUIT,
2549 shell_execute_ex(SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI, NULL, info->filename, NULL, NULL, NULL),
2551 ExitThread(0);
2554 static void test_dde_default_app(void)
2556 char filename[MAX_PATH];
2557 HSZ hszApplication;
2558 dde_thread_info_t info = { filename, GetCurrentThreadId() };
2559 const dde_default_app_tests_t* test;
2560 char params[1024];
2561 DWORD threadId;
2562 MSG msg;
2563 INT_PTR rc;
2564 int which = 0;
2565 HDDEDATA ret;
2566 BOOL b;
2568 post_quit_on_execute = FALSE;
2569 ddeInst = 0;
2570 rc = DdeInitializeA(&ddeInst, ddeCb, CBF_SKIP_ALLNOTIFICATIONS | CBF_FAIL_ADVISES |
2571 CBF_FAIL_POKES | CBF_FAIL_REQUESTS, 0);
2572 ok(rc == DMLERR_NO_ERROR, "got %lx\n", rc);
2574 sprintf(filename, "%s\\test file.sde", tmpdir);
2576 /* It is strictly not necessary to register an application name here, but wine's
2577 * DdeNameService implementation complains if 0 is passed instead of
2578 * hszApplication with DNS_FILTEROFF */
2579 hszApplication = DdeCreateStringHandleA(ddeInst, "shlexec", CP_WINANSI);
2580 hszTopic = DdeCreateStringHandleA(ddeInst, "shlexec", CP_WINANSI);
2581 ok(hszApplication && hszTopic, "got %p and %p\n", hszApplication, hszTopic);
2582 ret = DdeNameService(ddeInst, hszApplication, 0, DNS_REGISTER | DNS_FILTEROFF);
2583 ok(ret != 0, "got %p\n", ret);
2585 test = dde_default_app_tests;
2586 while (test->command)
2588 HANDLE thread;
2590 if (!create_test_association(".sde"))
2592 skip("Unable to create association for '.sde'\n");
2593 return;
2595 sprintf(params, test->command, tmpdir);
2596 create_test_verb_dde("shlexec.sde", "Open", 1, params, "[test]", NULL,
2597 "shlexec", NULL);
2598 ddeApplication[0] = 0;
2600 /* No application will be run as we will respond to the first DDE event,
2601 * so don't wait for it */
2602 SetEvent(hEvent);
2604 thread = CreateThread(NULL, 0, ddeThread, &info, 0, &threadId);
2605 ok(thread != NULL, "got %p\n", thread);
2606 while (GetMessageA(&msg, NULL, 0, 0)) DispatchMessageA(&msg);
2607 rc = msg.wParam > 32 ? 33 : msg.wParam;
2609 /* The first two tests determine which set of results to expect.
2610 * First check the platform as only the first set of results is
2611 * acceptable for Wine.
2613 if (strcmp(winetest_platform, "wine"))
2615 if (test == dde_default_app_tests)
2617 if (strcmp(ddeApplication, test->expectedDdeApplication[0]))
2618 which = 2;
2620 else if (test == dde_default_app_tests + 1)
2622 if (which == 0 && rc == test->rc[1])
2623 which = 1;
2624 trace("DDE result variant %d\n", which);
2628 todo_wine_if(test->todo & 0x1)
2629 okShell(rc==test->rc[which], "failed: rc=%lu err=%u\n",
2630 rc, GetLastError());
2631 if (rc == 33)
2633 todo_wine_if(test->todo & 0x2)
2634 ok(!strcmp(ddeApplication, test->expectedDdeApplication[which]),
2635 "Expected application '%s', got '%s'\n",
2636 test->expectedDdeApplication[which], ddeApplication);
2638 reset_association_description();
2640 delete_test_association(".sde");
2641 test++;
2644 ret = DdeNameService(ddeInst, hszApplication, 0, DNS_UNREGISTER);
2645 ok(ret != 0, "got %p\n", ret);
2646 b = DdeFreeStringHandle(ddeInst, hszTopic);
2647 ok(b, "got %d\n", b);
2648 b = DdeFreeStringHandle(ddeInst, hszApplication);
2649 ok(b, "got %d\n", b);
2650 b = DdeUninitialize(ddeInst);
2651 ok(b, "got %d\n", b);
2654 static void init_test(void)
2656 HMODULE hdll;
2657 HRESULT (WINAPI *pDllGetVersion)(DLLVERSIONINFO*);
2658 char filename[MAX_PATH];
2659 WCHAR lnkfile[MAX_PATH];
2660 char params[1024];
2661 const char* const * testfile;
2662 lnk_desc_t desc;
2663 DWORD rc;
2664 HRESULT r;
2666 hdll=GetModuleHandleA("shell32.dll");
2667 pDllGetVersion=(void*)GetProcAddress(hdll, "DllGetVersion");
2668 if (pDllGetVersion)
2670 dllver.cbSize=sizeof(dllver);
2671 pDllGetVersion(&dllver);
2672 trace("major=%d minor=%d build=%d platform=%d\n",
2673 dllver.dwMajorVersion, dllver.dwMinorVersion,
2674 dllver.dwBuildNumber, dllver.dwPlatformID);
2676 else
2678 memset(&dllver, 0, sizeof(dllver));
2681 r = CoInitialize(NULL);
2682 ok(r == S_OK, "CoInitialize failed (0x%08x)\n", r);
2683 if (FAILED(r))
2684 exit(1);
2686 rc=GetModuleFileNameA(NULL, argv0, sizeof(argv0));
2687 ok(rc != 0 && rc < sizeof(argv0), "got %d\n", rc);
2688 if (GetFileAttributesA(argv0)==INVALID_FILE_ATTRIBUTES)
2690 strcat(argv0, ".so");
2691 ok(GetFileAttributesA(argv0)!=INVALID_FILE_ATTRIBUTES,
2692 "unable to find argv0!\n");
2695 /* Older versions (win 2k) fail tests if there is a space in
2696 the path. */
2697 if (dllver.dwMajorVersion <= 5)
2698 strcpy(filename, "c:\\");
2699 else
2700 GetTempPathA(sizeof(filename), filename);
2701 GetTempFileNameA(filename, "wt", 0, tmpdir);
2702 GetLongPathNameA(tmpdir, tmpdir, sizeof(tmpdir));
2703 DeleteFileA( tmpdir );
2704 rc = CreateDirectoryA( tmpdir, NULL );
2705 ok( rc, "failed to create %s err %u\n", tmpdir, GetLastError() );
2706 /* Set %TMPDIR% for the tests */
2707 SetEnvironmentVariableA("TMPDIR", tmpdir);
2709 rc = GetTempFileNameA(tmpdir, "wt", 0, child_file);
2710 ok(rc != 0, "got %d\n", rc);
2711 init_event(child_file);
2713 /* Set up the test files */
2714 testfile=testfiles;
2715 while (*testfile)
2717 HANDLE hfile;
2719 sprintf(filename, *testfile, tmpdir);
2720 hfile=CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
2721 FILE_ATTRIBUTE_NORMAL, NULL);
2722 if (hfile==INVALID_HANDLE_VALUE)
2724 trace("unable to create '%s': err=%u\n", filename, GetLastError());
2725 assert(0);
2727 CloseHandle(hfile);
2728 testfile++;
2731 /* Setup the test shortcuts */
2732 sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir);
2733 MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, ARRAY_SIZE(lnkfile));
2734 desc.description=NULL;
2735 desc.workdir=NULL;
2736 sprintf(filename, "%s\\test file.shlexec", tmpdir);
2737 desc.path=filename;
2738 desc.pidl=NULL;
2739 desc.arguments="ignored";
2740 desc.showcmd=0;
2741 desc.icon=NULL;
2742 desc.icon_id=0;
2743 desc.hotkey=0;
2744 create_lnk(lnkfile, &desc, 0);
2746 sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
2747 MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, ARRAY_SIZE(lnkfile));
2748 desc.description=NULL;
2749 desc.workdir=NULL;
2750 desc.path=argv0;
2751 desc.pidl=NULL;
2752 sprintf(params, "shlexec \"%s\" Lnk", child_file);
2753 desc.arguments=params;
2754 desc.showcmd=0;
2755 desc.icon=NULL;
2756 desc.icon_id=0;
2757 desc.hotkey=0;
2758 create_lnk(lnkfile, &desc, 0);
2760 /* Create a basic association suitable for most tests */
2761 if (!create_test_association(".shlexec"))
2763 skip_shlexec_tests = TRUE;
2764 skip("Unable to create association for '.shlexec'\n");
2765 return;
2767 create_test_verb("shlexec.shlexec", "Open", 0, "Open \"%1\"");
2768 create_test_verb("shlexec.shlexec", "NoQuotes", 0, "NoQuotes %1");
2769 create_test_verb("shlexec.shlexec", "LowerL", 0, "LowerL %l");
2770 create_test_verb("shlexec.shlexec", "QuotedLowerL", 0, "QuotedLowerL \"%l\"");
2771 create_test_verb("shlexec.shlexec", "UpperL", 0, "UpperL %L");
2772 create_test_verb("shlexec.shlexec", "QuotedUpperL", 0, "QuotedUpperL \"%L\"");
2774 create_test_association(".sha");
2775 create_test_verb("shlexec.sha", "averb", 0, "AVerb \"%1\"");
2777 create_test_class("shlproto", TRUE);
2778 create_test_verb("shlproto", "open", 0, "URL \"%1\"");
2779 create_test_verb("shlproto", "averb", 0, "AVerb \"%1\"");
2781 /* Set an environment variable to see if it is inherited */
2782 SetEnvironmentVariableA("ShlexecVar", "Present");
2785 static void cleanup_test(void)
2787 char filename[MAX_PATH];
2788 const char* const * testfile;
2790 /* Delete the test files */
2791 testfile=testfiles;
2792 while (*testfile)
2794 sprintf(filename, *testfile, tmpdir);
2795 /* Make sure we can delete the files ('test file.noassoc' is read-only now) */
2796 SetFileAttributesA(filename, FILE_ATTRIBUTE_NORMAL);
2797 DeleteFileA(filename);
2798 testfile++;
2800 DeleteFileA(child_file);
2801 RemoveDirectoryA(tmpdir);
2803 /* Delete the test association */
2804 delete_test_association(".shlexec");
2805 delete_test_association(".sha");
2806 delete_test_class("shlproto");
2808 CloseHandle(hEvent);
2810 CoUninitialize();
2813 static void test_directory(void)
2815 char path[MAX_PATH], curdir[MAX_PATH];
2816 char params[1024], dirpath[1024];
2817 INT_PTR rc;
2819 sprintf(path, "%s\\test2.exe", tmpdir);
2820 CopyFileA(argv0, path, FALSE);
2822 sprintf(params, "shlexec \"%s\" Exec", child_file);
2824 /* Test with the current directory */
2825 GetCurrentDirectoryA(sizeof(curdir), curdir);
2826 SetCurrentDirectoryA(tmpdir);
2827 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2828 NULL, "test2.exe", params, NULL, NULL);
2829 okShell(rc > 32, "returned %lu\n", rc);
2830 okChildInt("argcA", 4);
2831 okChildString("argvA3", "Exec");
2832 todo_wine okChildPath("longPath", path);
2833 SetCurrentDirectoryA(curdir);
2835 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2836 NULL, "test2.exe", params, NULL, NULL);
2837 okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2839 /* Explicitly specify the directory to use */
2840 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2841 NULL, "test2.exe", params, tmpdir, NULL);
2842 okShell(rc > 32, "returned %lu\n", rc);
2843 okChildInt("argcA", 4);
2844 okChildString("argvA3", "Exec");
2845 todo_wine okChildPath("longPath", path);
2847 /* Specify it through an environment variable */
2848 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2849 NULL, "test2.exe", params, "%TMPDIR%", NULL);
2850 todo_wine okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2852 rc=shell_execute_ex(SEE_MASK_DOENVSUBST|SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2853 NULL, "test2.exe", params, "%TMPDIR%", NULL);
2854 okShell(rc > 32, "returned %lu\n", rc);
2855 okChildInt("argcA", 4);
2856 okChildString("argvA3", "Exec");
2857 todo_wine okChildPath("longPath", path);
2859 /* Not a colon-separated directory list */
2860 sprintf(dirpath, "%s:%s", curdir, tmpdir);
2861 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2862 NULL, "test2.exe", params, dirpath, NULL);
2863 okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2866 START_TEST(shlexec)
2869 myARGC = winetest_get_mainargs(&myARGV);
2870 if (myARGC >= 3)
2872 doChild(myARGC, myARGV);
2873 /* Skip the tests/failures trace for child processes */
2874 ExitProcess(winetest_get_failures());
2877 init_test();
2879 test_commandline2argv();
2880 test_argify();
2881 test_lpFile_parsed();
2882 test_filename();
2883 test_fileurls();
2884 test_urls();
2885 test_find_executable();
2886 test_lnks();
2887 test_exes();
2888 test_dde();
2889 test_dde_default_app();
2890 test_directory();
2892 cleanup_test();