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