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 > ARRAY_SIZE(szNameBuf)) 764 { 765 /* Name too big: alloc a buffer for it */ 766 if (!(lpszName = heap_alloc(dwMaxLen*sizeof(CHAR)))) 767 { 768 ret = ERROR_NOT_ENOUGH_MEMORY; 769 goto cleanup; 770 } 771 } 772 773 774 /* Recursively delete all the subkeys */ 775 while (TRUE) 776 { 777 dwSize = dwMaxLen; 778 if (RegEnumKeyExA(hSubKey, 0, lpszName, &dwSize, NULL, 779 NULL, NULL, NULL)) break; 780 781 ret = myRegDeleteTreeA(hSubKey, lpszName); 782 if (ret) goto cleanup; 783 } 784 785 if (lpszSubKey) 786 ret = RegDeleteKeyA(hKey, lpszSubKey); 787 else 788 while (TRUE) 789 { 790 dwSize = dwMaxLen; 791 if (RegEnumValueA(hKey, 0, lpszName, &dwSize, 792 NULL, NULL, NULL, NULL)) break; 793 794 ret = RegDeleteValueA(hKey, lpszName); 795 if (ret) goto cleanup; 796 } 797 798 cleanup: 799 /* Free buffer if allocated */ 800 if (lpszName != szNameBuf) 801 heap_free(lpszName); 802 if(lpszSubKey) 803 RegCloseKey(hSubKey); 804 return ret; 805 } 806 807 static void delete_test_class(const char* classname) 808 { 809 myRegDeleteTreeA(HKEY_CLASSES_ROOT, classname); 810 } 811 812 static void delete_test_association(const char* extension) 813 { 814 char classname[MAX_PATH]; 815 816 sprintf(classname, "shlexec%s", extension); 817 delete_test_class(classname); 818 myRegDeleteTreeA(HKEY_CLASSES_ROOT, extension); 819 } 820 821 static void create_test_verb_dde(const char* classname, const char* verb, 822 int rawcmd, const char* cmdtail, const char *ddeexec, 823 const char *application, const char *topic, 824 const char *ifexec) 825 { 826 HKEY hkey_shell, hkey_verb, hkey_cmd; 827 char shell[MAX_PATH]; 828 char* cmd; 829 LONG rc; 830 831 strcpy(assoc_desc, " Assoc "); 832 strcat_param(assoc_desc, "class", classname); 833 strcat_param(assoc_desc, "verb", verb); 834 sprintf(shell, "%d", rawcmd); 835 strcat_param(assoc_desc, "rawcmd", shell); 836 strcat_param(assoc_desc, "cmdtail", cmdtail); 837 strcat_param(assoc_desc, "ddeexec", ddeexec); 838 strcat_param(assoc_desc, "app", application); 839 strcat_param(assoc_desc, "topic", topic); 840 strcat_param(assoc_desc, "ifexec", ifexec); 841 842 sprintf(shell, "%s\\shell", classname); 843 rc=RegOpenKeyExA(HKEY_CLASSES_ROOT, shell, 0, 844 KEY_CREATE_SUB_KEY, &hkey_shell); 845 ok(rc == ERROR_SUCCESS, "%s key creation failed with %d\n", shell, rc); 846 847 rc=RegCreateKeyExA(hkey_shell, verb, 0, NULL, 0, KEY_CREATE_SUB_KEY, 848 NULL, &hkey_verb, NULL); 849 ok(rc == ERROR_SUCCESS, "%s verb key creation failed with %d\n", verb, rc); 850 851 rc=RegCreateKeyExA(hkey_verb, "command", 0, NULL, 0, KEY_SET_VALUE, 852 NULL, &hkey_cmd, NULL); 853 ok(rc == ERROR_SUCCESS, "\'command\' key creation failed with %d\n", rc); 854 855 if (rawcmd) 856 { 857 rc=RegSetValueExA(hkey_cmd, NULL, 0, REG_SZ, (LPBYTE)cmdtail, strlen(cmdtail)+1); 858 } 859 else 860 { 861 cmd = heap_alloc(strlen(argv0) + 10 + strlen(child_file) + 2 + strlen(cmdtail) + 1); 862 sprintf(cmd,"%s shlexec \"%s\" %s", argv0, child_file, cmdtail); 863 rc=RegSetValueExA(hkey_cmd, NULL, 0, REG_SZ, (LPBYTE)cmd, strlen(cmd)+1); 864 ok(rc == ERROR_SUCCESS, "setting command failed with %d\n", rc); 865 heap_free(cmd); 866 } 867 868 if (ddeexec) 869 { 870 HKEY hkey_ddeexec, hkey_application, hkey_topic, hkey_ifexec; 871 872 rc=RegCreateKeyExA(hkey_verb, "ddeexec", 0, NULL, 0, KEY_SET_VALUE | 873 KEY_CREATE_SUB_KEY, NULL, &hkey_ddeexec, NULL); 874 ok(rc == ERROR_SUCCESS, "\'ddeexec\' key creation failed with %d\n", rc); 875 rc=RegSetValueExA(hkey_ddeexec, NULL, 0, REG_SZ, (LPBYTE)ddeexec, 876 strlen(ddeexec)+1); 877 ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc); 878 879 if (application) 880 { 881 rc=RegCreateKeyExA(hkey_ddeexec, "application", 0, NULL, 0, KEY_SET_VALUE, 882 NULL, &hkey_application, NULL); 883 ok(rc == ERROR_SUCCESS, "\'application\' key creation failed with %d\n", rc); 884 885 rc=RegSetValueExA(hkey_application, NULL, 0, REG_SZ, (LPBYTE)application, 886 strlen(application)+1); 887 ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc); 888 CloseHandle(hkey_application); 889 } 890 if (topic) 891 { 892 rc=RegCreateKeyExA(hkey_ddeexec, "topic", 0, NULL, 0, KEY_SET_VALUE, 893 NULL, &hkey_topic, NULL); 894 ok(rc == ERROR_SUCCESS, "\'topic\' key creation failed with %d\n", rc); 895 rc=RegSetValueExA(hkey_topic, NULL, 0, REG_SZ, (LPBYTE)topic, 896 strlen(topic)+1); 897 ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc); 898 CloseHandle(hkey_topic); 899 } 900 if (ifexec) 901 { 902 rc=RegCreateKeyExA(hkey_ddeexec, "ifexec", 0, NULL, 0, KEY_SET_VALUE, 903 NULL, &hkey_ifexec, NULL); 904 ok(rc == ERROR_SUCCESS, "\'ifexec\' key creation failed with %d\n", rc); 905 rc=RegSetValueExA(hkey_ifexec, NULL, 0, REG_SZ, (LPBYTE)ifexec, 906 strlen(ifexec)+1); 907 ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc); 908 CloseHandle(hkey_ifexec); 909 } 910 CloseHandle(hkey_ddeexec); 911 } 912 913 CloseHandle(hkey_shell); 914 CloseHandle(hkey_verb); 915 CloseHandle(hkey_cmd); 916 } 917 918 /* Creates a class' non-DDE test verb. 919 * This function is meant to be used to create long term test verbs and thus 920 * does not trace them. 921 */ 922 static void create_test_verb(const char* classname, const char* verb, 923 int rawcmd, const char* cmdtail) 924 { 925 create_test_verb_dde(classname, verb, rawcmd, cmdtail, NULL, NULL, 926 NULL, NULL); 927 reset_association_description(); 928 } 929 930 931 /*** 932 * 933 * GetLongPathNameA equivalent that supports Win95 and WinNT 934 * 935 ***/ 936 937 static DWORD get_long_path_name(const char* shortpath, char* longpath, DWORD longlen) 938 { 939 char tmplongpath[MAX_PATH]; 940 const char* p; 941 DWORD sp = 0, lp = 0; 942 DWORD tmplen; 943 WIN32_FIND_DATAA wfd; 944 HANDLE goit; 945 946 if (!shortpath || !shortpath[0]) 947 return 0; 948 949 if (shortpath[1] == ':') 950 { 951 tmplongpath[0] = shortpath[0]; 952 tmplongpath[1] = ':'; 953 lp = sp = 2; 954 } 955 956 while (shortpath[sp]) 957 { 958 /* check for path delimiters and reproduce them */ 959 if (shortpath[sp] == '\\' || shortpath[sp] == '/') 960 { 961 if (!lp || tmplongpath[lp-1] != '\\') 962 { 963 /* strip double "\\" */ 964 tmplongpath[lp++] = '\\'; 965 } 966 tmplongpath[lp] = 0; /* terminate string */ 967 sp++; 968 continue; 969 } 970 971 p = shortpath + sp; 972 if (sp == 0 && p[0] == '.' && (p[1] == '/' || p[1] == '\\')) 973 { 974 tmplongpath[lp++] = *p++; 975 tmplongpath[lp++] = *p++; 976 } 977 for (; *p && *p != '/' && *p != '\\'; p++); 978 tmplen = p - (shortpath + sp); 979 lstrcpynA(tmplongpath + lp, shortpath + sp, tmplen + 1); 980 /* Check if the file exists and use the existing file name */ 981 goit = FindFirstFileA(tmplongpath, &wfd); 982 if (goit == INVALID_HANDLE_VALUE) 983 return 0; 984 FindClose(goit); 985 strcpy(tmplongpath + lp, wfd.cFileName); 986 lp += strlen(tmplongpath + lp); 987 sp += tmplen; 988 } 989 tmplen = strlen(shortpath) - 1; 990 if ((shortpath[tmplen] == '/' || shortpath[tmplen] == '\\') && 991 (tmplongpath[lp - 1] != '/' && tmplongpath[lp - 1] != '\\')) 992 tmplongpath[lp++] = shortpath[tmplen]; 993 tmplongpath[lp] = 0; 994 995 tmplen = strlen(tmplongpath) + 1; 996 if (tmplen <= longlen) 997 { 998 strcpy(longpath, tmplongpath); 999 tmplen--; /* length without 0 */ 1000 } 1001 1002 return tmplen; 1003 } 1004 1005 1006 /*** 1007 * 1008 * Tests 1009 * 1010 ***/ 1011 1012 static const char* testfiles[]= 1013 { 1014 "%s\\test file.shlexec", 1015 "%s\\%%nasty%% $file.shlexec", 1016 "%s\\test file.noassoc", 1017 "%s\\test file.noassoc.shlexec", 1018 "%s\\test file.shlexec.noassoc", 1019 "%s\\test_shortcut_shlexec.lnk", 1020 "%s\\test_shortcut_exe.lnk", 1021 "%s\\test file.shl", 1022 "%s\\test file.shlfoo", 1023 "%s\\test file.sha", 1024 "%s\\test file.sfe", 1025 "%s\\test file.shlproto", 1026 "%s\\masked file.shlexec", 1027 "%s\\masked", 1028 "%s\\test file.sde", 1029 "%s\\test file.exe", 1030 "%s\\test2.exe", 1031 "%s\\simple.shlexec", 1032 "%s\\drawback_file.noassoc", 1033 "%s\\drawback_file.noassoc foo.shlexec", 1034 "%s\\drawback_nonexist.noassoc foo.shlexec", 1035 NULL 1036 }; 1037 1038 typedef struct 1039 { 1040 const char* verb; 1041 const char* basename; 1042 int todo; 1043 INT_PTR rc; 1044 } filename_tests_t; 1045 1046 static filename_tests_t filename_tests[]= 1047 { 1048 /* Test bad / nonexistent filenames */ 1049 {NULL, "%s\\nonexistent.shlexec", 0x0, SE_ERR_FNF}, 1050 {NULL, "%s\\nonexistent.noassoc", 0x0, SE_ERR_FNF}, 1051 1052 /* Standard tests */ 1053 {NULL, "%s\\test file.shlexec", 0x0, 33}, 1054 {NULL, "%s\\test file.shlexec.", 0x0, 33}, 1055 {NULL, "%s\\%%nasty%% $file.shlexec", 0x0, 33}, 1056 {NULL, "%s/test file.shlexec", 0x0, 33}, 1057 1058 /* Test filenames with no association */ 1059 {NULL, "%s\\test file.noassoc", 0x0, SE_ERR_NOASSOC}, 1060 1061 /* Test double extensions */ 1062 {NULL, "%s\\test file.noassoc.shlexec", 0x0, 33}, 1063 {NULL, "%s\\test file.shlexec.noassoc", 0x0, SE_ERR_NOASSOC}, 1064 1065 /* Test alternate verbs */ 1066 {"LowerL", "%s\\nonexistent.shlexec", 0x0, SE_ERR_FNF}, 1067 {"LowerL", "%s\\test file.noassoc", 0x0, SE_ERR_NOASSOC}, 1068 1069 {"QuotedLowerL", "%s\\test file.shlexec", 0x0, 33}, 1070 {"QuotedUpperL", "%s\\test file.shlexec", 0x0, 33}, 1071 1072 {"notaverb", "%s\\test file.shlexec", 0x10, SE_ERR_NOASSOC}, 1073 1074 {"averb", "%s\\test file.sha", 0x10, 33}, 1075 1076 /* Test file masked due to space */ 1077 {NULL, "%s\\masked file.shlexec", 0x0, 33}, 1078 /* Test if quoting prevents the masking */ 1079 {NULL, "%s\\masked file.shlexec", 0x40, 33}, 1080 /* Test with incorrect quote */ 1081 {NULL, "\"%s\\masked file.shlexec", 0x0, SE_ERR_FNF}, 1082 1083 /* Test extension / URI protocol collision */ 1084 {NULL, "%s\\test file.shlproto", 0x0, SE_ERR_NOASSOC}, 1085 1086 {NULL, NULL, 0} 1087 }; 1088 1089 static filename_tests_t noquotes_tests[]= 1090 { 1091 /* Test unquoted '%1' thingies */ 1092 {"NoQuotes", "%s\\test file.shlexec", 0xa, 33}, 1093 {"LowerL", "%s\\test file.shlexec", 0xa, 33}, 1094 {"UpperL", "%s\\test file.shlexec", 0xa, 33}, 1095 1096 {NULL, NULL, 0} 1097 }; 1098 1099 static void test_lpFile_parsed(void) 1100 { 1101 char fileA[MAX_PATH]; 1102 INT_PTR rc; 1103 1104 if (skip_shlexec_tests) 1105 { 1106 skip("No filename parsing tests due to lack of .shlexec association\n"); 1107 return; 1108 } 1109 1110 /* existing "drawback_file.noassoc" prevents finding "drawback_file.noassoc foo.shlexec" on wine */ 1111 sprintf(fileA, "%s\\drawback_file.noassoc foo.shlexec", tmpdir); 1112 rc=shell_execute(NULL, fileA, NULL, NULL); 1113 okShell(rc > 32, "failed: rc=%lu\n", rc); 1114 1115 /* if quoted, existing "drawback_file.noassoc" not prevents finding "drawback_file.noassoc foo.shlexec" on wine */ 1116 sprintf(fileA, "\"%s\\drawback_file.noassoc foo.shlexec\"", tmpdir); 1117 rc=shell_execute(NULL, fileA, NULL, NULL); 1118 okShell(rc > 32 || broken(rc == SE_ERR_FNF) /* Win95/NT4 */, 1119 "failed: rc=%lu\n", rc); 1120 1121 /* error should be SE_ERR_FNF, not SE_ERR_NOASSOC */ 1122 sprintf(fileA, "\"%s\\drawback_file.noassoc\" foo.shlexec", tmpdir); 1123 rc=shell_execute(NULL, fileA, NULL, NULL); 1124 okShell(rc == SE_ERR_FNF, "returned %lu\n", rc); 1125 1126 /* ""command"" not works on wine (and real win9x and w2k) */ 1127 sprintf(fileA, "\"\"%s\\simple.shlexec\"\"", tmpdir); 1128 rc=shell_execute(NULL, fileA, NULL, NULL); 1129 todo_wine okShell(rc > 32 || broken(rc == SE_ERR_FNF) /* Win9x/2000 */, 1130 "failed: rc=%lu\n", rc); 1131 1132 /* nonexisting "drawback_nonexist.noassoc" not prevents finding "drawback_nonexist.noassoc foo.shlexec" on wine */ 1133 sprintf(fileA, "%s\\drawback_nonexist.noassoc foo.shlexec", tmpdir); 1134 rc=shell_execute(NULL, fileA, NULL, NULL); 1135 okShell(rc > 32, "failed: rc=%lu\n", rc); 1136 1137 /* is SEE_MASK_DOENVSUBST default flag? Should only be when XP emulates 9x (XP bug or real 95 or ME behavior ?) */ 1138 rc=shell_execute(NULL, "%TMPDIR%\\simple.shlexec", NULL, NULL); 1139 todo_wine okShell(rc == SE_ERR_FNF, "returned %lu\n", rc); 1140 1141 /* quoted */ 1142 rc=shell_execute(NULL, "\"%TMPDIR%\\simple.shlexec\"", NULL, NULL); 1143 todo_wine okShell(rc == SE_ERR_FNF, "returned %lu\n", rc); 1144 1145 /* test SEE_MASK_DOENVSUBST works */ 1146 rc=shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI, 1147 NULL, "%TMPDIR%\\simple.shlexec", NULL, NULL, NULL); 1148 okShell(rc > 32, "failed: rc=%lu\n", rc); 1149 1150 /* quoted lpFile does not work on real win95 and nt4 */ 1151 rc=shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI, 1152 NULL, "\"%TMPDIR%\\simple.shlexec\"", NULL, NULL, NULL); 1153 okShell(rc > 32 || broken(rc == SE_ERR_FNF) /* Win95/NT4 */, 1154 "failed: rc=%lu\n", rc); 1155 } 1156 1157 typedef struct 1158 { 1159 const char* cmd; 1160 const char* args[11]; 1161 int todo; 1162 } cmdline_tests_t; 1163 1164 static const cmdline_tests_t cmdline_tests[] = 1165 { 1166 {"exe", 1167 {"exe", NULL}, 0}, 1168 1169 {"exe arg1 arg2 \"arg three\" 'four five` six\\ $even)", 1170 {"exe", "arg1", "arg2", "arg three", "'four", "five`", "six\\", "$even)", NULL}, 0}, 1171 1172 {"exe arg=1 arg-2 three\tfour\rfour\nfour ", 1173 {"exe", "arg=1", "arg-2", "three", "four\rfour\nfour", NULL}, 0}, 1174 1175 {"exe arg\"one\" \"second\"arg thirdarg ", 1176 {"exe", "argone", "secondarg", "thirdarg", NULL}, 0}, 1177 1178 /* Don't lose unclosed quoted arguments */ 1179 {"exe arg1 \"unclosed", 1180 {"exe", "arg1", "unclosed", NULL}, 0}, 1181 1182 {"exe arg1 \"", 1183 {"exe", "arg1", "", NULL}, 0}, 1184 1185 /* cmd's metacharacters have no special meaning */ 1186 {"exe \"one^\" \"arg\"&two three|four", 1187 {"exe", "one^", "arg&two", "three|four", NULL}, 0}, 1188 1189 /* Environment variables are not interpreted either */ 1190 {"exe %TMPDIR% %2", 1191 {"exe", "%TMPDIR%", "%2", NULL}, 0}, 1192 1193 /* If not followed by a quote, backslashes go through as is */ 1194 {"exe o\\ne t\\\\wo t\\\\\\ree f\\\\\\\\our ", 1195 {"exe", "o\\ne", "t\\\\wo", "t\\\\\\ree", "f\\\\\\\\our", NULL}, 0}, 1196 1197 {"exe \"o\\ne\" \"t\\\\wo\" \"t\\\\\\ree\" \"f\\\\\\\\our\" ", 1198 {"exe", "o\\ne", "t\\\\wo", "t\\\\\\ree", "f\\\\\\\\our", NULL}, 0}, 1199 1200 /* When followed by a quote their number is halved and the remainder 1201 * escapes the quote 1202 */ 1203 {"exe \\\"one \\\\\"two\" \\\\\\\"three \\\\\\\\\"four\" end", 1204 {"exe", "\"one", "\\two", "\\\"three", "\\\\four", "end", NULL}, 0}, 1205 1206 {"exe \"one\\\" still\" \"two\\\\\" \"three\\\\\\\" still\" \"four\\\\\\\\\" end", 1207 {"exe", "one\" still", "two\\", "three\\\" still", "four\\\\", "end", NULL}, 0}, 1208 1209 /* One can put a quote in an unquoted string by tripling it, that is in 1210 * effect quoting it like so """ -> ". The general rule is as follows: 1211 * 3n quotes -> n quotes 1212 * 3n+1 quotes -> n quotes plus start of a quoted string 1213 * 3n+2 quotes -> n quotes (plus an empty string from the remaining pair) 1214 * Nicely, when n is 0 we get the standard rules back. 1215 */ 1216 {"exe two\"\"quotes next", 1217 {"exe", "twoquotes", "next", NULL}, 0}, 1218 1219 {"exe three\"\"\"quotes next", 1220 {"exe", "three\"quotes", "next", NULL}, 0}, 1221 1222 {"exe four\"\"\"\" quotes\" next 4%3=1", 1223 {"exe", "four\" quotes", "next", "4%3=1", NULL}, 0}, 1224 1225 {"exe five\"\"\"\"\"quotes next", 1226 {"exe", "five\"quotes", "next", NULL}, 0}, 1227 1228 {"exe six\"\"\"\"\"\"quotes next", 1229 {"exe", "six\"\"quotes", "next", NULL}, 0}, 1230 1231 {"exe seven\"\"\"\"\"\"\" quotes\" next 7%3=1", 1232 {"exe", "seven\"\" quotes", "next", "7%3=1", NULL}, 0}, 1233 1234 {"exe twelve\"\"\"\"\"\"\"\"\"\"\"\"quotes next", 1235 {"exe", "twelve\"\"\"\"quotes", "next", NULL}, 0}, 1236 1237 {"exe thirteen\"\"\"\"\"\"\"\"\"\"\"\"\" quotes\" next 13%3=1", 1238 {"exe", "thirteen\"\"\"\" quotes", "next", "13%3=1", NULL}, 0}, 1239 1240 /* Inside a quoted string the opening quote is added to the set of 1241 * consecutive quotes to get the effective quotes count. This gives: 1242 * 1+3n quotes -> n quotes 1243 * 1+3n+1 quotes -> n quotes plus closes the quoted string 1244 * 1+3n+2 quotes -> n+1 quotes plus closes the quoted string 1245 */ 1246 {"exe \"two\"\"quotes next", 1247 {"exe", "two\"quotes", "next", NULL}, 0}, 1248 1249 {"exe \"two\"\" next", 1250 {"exe", "two\"", "next", NULL}, 0}, 1251 1252 {"exe \"three\"\"\" quotes\" next 4%3=1", 1253 {"exe", "three\" quotes", "next", "4%3=1", NULL}, 0}, 1254 1255 {"exe \"four\"\"\"\"quotes next", 1256 {"exe", "four\"quotes", "next", NULL}, 0}, 1257 1258 {"exe \"five\"\"\"\"\"quotes next", 1259 {"exe", "five\"\"quotes", "next", NULL}, 0}, 1260 1261 {"exe \"six\"\"\"\"\"\" quotes\" next 7%3=1", 1262 {"exe", "six\"\" quotes", "next", "7%3=1", NULL}, 0}, 1263 1264 {"exe \"eleven\"\"\"\"\"\"\"\"\"\"\"quotes next", 1265 {"exe", "eleven\"\"\"\"quotes", "next", NULL}, 0}, 1266 1267 {"exe \"twelve\"\"\"\"\"\"\"\"\"\"\"\" quotes\" next 13%3=1", 1268 {"exe", "twelve\"\"\"\" quotes", "next", "13%3=1", NULL}, 0}, 1269 1270 /* Escaped consecutive quotes are fun */ 1271 {"exe \"the crazy \\\\\"\"\"\\\\\" quotes", 1272 {"exe", "the crazy \\\"\\", "quotes", NULL}, 0}, 1273 1274 /* The executable path has its own rules!!! 1275 * - Backslashes have no special meaning. 1276 * - If the first character is a quote, then the second quote ends the 1277 * executable path. 1278 * - The previous rule holds even if the next character is not a space! 1279 * - If the first character is not a quote, then quotes have no special 1280 * meaning either and the executable path stops at the first space. 1281 * - The consecutive quotes rules don't apply either. 1282 * - Even if there is no space between the executable path and the first 1283 * argument, the latter is parsed using the regular rules. 1284 */ 1285 {"exe\"file\"path arg1", 1286 {"exe\"file\"path", "arg1", NULL}, 0}, 1287 1288 {"exe\"file\"path\targ1", 1289 {"exe\"file\"path", "arg1", NULL}, 0}, 1290 1291 {"exe\"path\\ arg1", 1292 {"exe\"path\\", "arg1", NULL}, 0}, 1293 1294 {"\\\"exe \"arg one\"", 1295 {"\\\"exe", "arg one", NULL}, 0}, 1296 1297 {"\"spaced exe\" \"next arg\"", 1298 {"spaced exe", "next arg", NULL}, 0}, 1299 1300 {"\"spaced exe\"\t\"next arg\"", 1301 {"spaced exe", "next arg", NULL}, 0}, 1302 1303 {"\"exe\"arg\" one\" argtwo", 1304 {"exe", "arg one", "argtwo", NULL}, 0}, 1305 1306 {"\"spaced exe\\\"arg1 arg2", 1307 {"spaced exe\\", "arg1", "arg2", NULL}, 0}, 1308 1309 {"\"two\"\" arg1 ", 1310 {"two", " arg1 ", NULL}, 0}, 1311 1312 {"\"three\"\"\" arg2", 1313 {"three", "", "arg2", NULL}, 0}, 1314 1315 {"\"four\"\"\"\"arg1", 1316 {"four", "\"arg1", NULL}, 0}, 1317 1318 /* If the first character is a space then the executable path is empty */ 1319 {" \"arg\"one argtwo", 1320 {"", "argone", "argtwo", NULL}, 0}, 1321 1322 {NULL, {NULL}, 0} 1323 }; 1324 1325 static BOOL test_one_cmdline(const cmdline_tests_t* test) 1326 { 1327 WCHAR cmdW[MAX_PATH], argW[MAX_PATH]; 1328 LPWSTR *cl2a; 1329 int cl2a_count; 1330 LPWSTR *argsW; 1331 int i, count; 1332 1333 /* trace("----- cmd='%s'\n", test->cmd); */ 1334 MultiByteToWideChar(CP_ACP, 0, test->cmd, -1, cmdW, ARRAY_SIZE(cmdW)); 1335 argsW = cl2a = CommandLineToArgvW(cmdW, &cl2a_count); 1336 if (argsW == NULL && cl2a_count == -1) 1337 { 1338 win_skip("CommandLineToArgvW not implemented, skipping\n"); 1339 return FALSE; 1340 } 1341 ok(!argsW[cl2a_count] || broken(argsW[cl2a_count] != NULL) /* before Vista */, 1342 "expected NULL-terminated list of commandline arguments\n"); 1343 1344 count = 0; 1345 while (test->args[count]) 1346 count++; 1347 todo_wine_if(test->todo & 0x1) 1348 ok(cl2a_count == count, "%s: expected %d arguments, but got %d\n", test->cmd, count, cl2a_count); 1349 1350 for (i = 0; i < cl2a_count; i++) 1351 { 1352 if (i < count) 1353 { 1354 MultiByteToWideChar(CP_ACP, 0, test->args[i], -1, argW, ARRAY_SIZE(argW)); 1355 todo_wine_if(test->todo & (1 << (i+4))) 1356 ok(!lstrcmpW(*argsW, argW), "%s: arg[%d] expected %s but got %s\n", test->cmd, i, wine_dbgstr_w(argW), wine_dbgstr_w(*argsW)); 1357 } 1358 else todo_wine_if(test->todo & 0x1) 1359 ok(0, "%s: got extra arg[%d]=%s\n", test->cmd, i, wine_dbgstr_w(*argsW)); 1360 argsW++; 1361 } 1362 LocalFree(cl2a); 1363 return TRUE; 1364 } 1365 1366 static void test_commandline2argv(void) 1367 { 1368 static const WCHAR exeW[] = {'e','x','e',0}; 1369 const cmdline_tests_t* test; 1370 WCHAR strW[MAX_PATH]; 1371 LPWSTR *args; 1372 int numargs; 1373 DWORD le; 1374 1375 test = cmdline_tests; 1376 while (test->cmd) 1377 { 1378 if (!test_one_cmdline(test)) 1379 return; 1380 test++; 1381 } 1382 1383 SetLastError(0xdeadbeef); 1384 args = CommandLineToArgvW(exeW, NULL); 1385 le = GetLastError(); 1386 ok(args == NULL && le == ERROR_INVALID_PARAMETER, "expected NULL with ERROR_INVALID_PARAMETER got %p with %u\n", args, le); 1387 1388 SetLastError(0xdeadbeef); 1389 args = CommandLineToArgvW(NULL, NULL); 1390 le = GetLastError(); 1391 ok(args == NULL && le == ERROR_INVALID_PARAMETER, "expected NULL with ERROR_INVALID_PARAMETER got %p with %u\n", args, le); 1392 1393 *strW = 0; 1394 args = CommandLineToArgvW(strW, &numargs); 1395 ok(numargs == 1 || broken(numargs > 1), "expected 1 args, got %d\n", numargs); 1396 ok(!args || (!args[numargs] || broken(args[numargs] != NULL) /* before Vista */), 1397 "expected NULL-terminated list of commandline arguments\n"); 1398 if (numargs == 1) 1399 { 1400 GetModuleFileNameW(NULL, strW, ARRAY_SIZE(strW)); 1401 ok(!lstrcmpW(args[0], strW), "wrong path to the current executable: %s instead of %s\n", wine_dbgstr_w(args[0]), wine_dbgstr_w(strW)); 1402 } 1403 if (args) LocalFree(args); 1404 } 1405 1406 /* The goal here is to analyze how ShellExecute() builds the command that 1407 * will be run. The tricky part is that there are three transformation 1408 * steps between the 'parameters' string we pass to ShellExecute() and the 1409 * argument list we observe in the child process: 1410 * - The parsing of 'parameters' string into individual arguments. The tests 1411 * show this is done differently from both CreateProcess() and 1412 * CommandLineToArgv()! 1413 * - The way the command 'formatting directives' such as %1, %2, etc are 1414 * handled. 1415 * - And the way the resulting command line is then parsed to yield the 1416 * argument list we check. 1417 */ 1418 typedef struct 1419 { 1420 const char* verb; 1421 const char* params; 1422 int todo; 1423 const char *cmd; 1424 const char *broken; 1425 } argify_tests_t; 1426 1427 static const argify_tests_t argify_tests[] = 1428 { 1429 /* Start with three simple parameters. Notice that one can reorder and 1430 * duplicate the parameters. Also notice how %* take the raw input 1431 * parameters string, including the trailing spaces, no matter what 1432 * arguments have already been used. 1433 */ 1434 {"Params232S", "p2 p3 p4 ", TRUE, 1435 " p2 p3 \"p2\" \"p2 p3 p4 \""}, 1436 1437 /* Unquoted argument references like %2 don't automatically quote their 1438 * argument. Similarly, when they are quoted they don't escape the quotes 1439 * that their argument may contain. 1440 */ 1441 {"Params232S", "\"p two\" p3 p4 ", TRUE, 1442 " p two p3 \"p two\" \"\"p two\" p3 p4 \""}, 1443 1444 /* Only single digits are supported so only %1 to %9. Shown here with %20 1445 * because %10 is a pain. 1446 */ 1447 {"Params20", "p", FALSE, 1448 " \"p0\""}, 1449 1450 /* Only (double-)quotes have a special meaning. */ 1451 {"Params23456", "'p2 p3` p4\\ $even", FALSE, 1452 " \"'p2\" \"p3`\" \"p4\\\" \"$even\" \"\""}, 1453 1454 {"Params23456", "p=2 p-3 p4\tp4\rp4\np4", TRUE, 1455 " \"p=2\" \"p-3\" \"p4\tp4\rp4\np4\" \"\" \"\""}, 1456 1457 /* In unquoted strings, quotes are treated are a parameter separator just 1458 * like spaces! However they can be doubled to get a literal quote. 1459 * Specifically: 1460 * 2n quotes -> n quotes 1461 * 2n+1 quotes -> n quotes and a parameter separator 1462 */ 1463 {"Params23456789", "one\"quote \"p four\" one\"quote p7", TRUE, 1464 " \"one\" \"quote\" \"p four\" \"one\" \"quote\" \"p7\" \"\" \"\""}, 1465 1466 {"Params23456789", "two\"\"quotes \"p three\" two\"\"quotes p5", TRUE, 1467 " \"two\"quotes\" \"p three\" \"two\"quotes\" \"p5\" \"\" \"\" \"\" \"\""}, 1468 1469 {"Params23456789", "three\"\"\"quotes \"p four\" three\"\"\"quotes p6", TRUE, 1470 " \"three\"\" \"quotes\" \"p four\" \"three\"\" \"quotes\" \"p6\" \"\" \"\""}, 1471 1472 {"Params23456789", "four\"\"\"\"quotes \"p three\" four\"\"\"\"quotes p5", TRUE, 1473 " \"four\"\"quotes\" \"p three\" \"four\"\"quotes\" \"p5\" \"\" \"\" \"\" \"\""}, 1474 1475 /* Quoted strings cannot be continued by tacking on a non space character 1476 * either. 1477 */ 1478 {"Params23456", "\"p two\"p3 \"p four\"p5 p6", TRUE, 1479 " \"p two\" \"p3\" \"p four\" \"p5\" \"p6\""}, 1480 1481 /* In quoted strings, the quotes are halved and an odd number closes the 1482 * string. Specifically: 1483 * 2n quotes -> n quotes 1484 * 2n+1 quotes -> n quotes and closes the string and hence the parameter 1485 */ 1486 {"Params23456789", "\"one q\"uote \"p four\" \"one q\"uote p7", TRUE, 1487 " \"one q\" \"uote\" \"p four\" \"one q\" \"uote\" \"p7\" \"\" \"\""}, 1488 1489 {"Params23456789", "\"two \"\" quotes\" \"p three\" \"two \"\" quotes\" p5", TRUE, 1490 " \"two \" quotes\" \"p three\" \"two \" quotes\" \"p5\" \"\" \"\" \"\" \"\""}, 1491 1492 {"Params23456789", "\"three q\"\"\"uotes \"p four\" \"three q\"\"\"uotes p7", TRUE, 1493 " \"three q\"\" \"uotes\" \"p four\" \"three q\"\" \"uotes\" \"p7\" \"\" \"\""}, 1494 1495 {"Params23456789", "\"four \"\"\"\" quotes\" \"p three\" \"four \"\"\"\" quotes\" p5", TRUE, 1496 " \"four \"\" quotes\" \"p three\" \"four \"\" quotes\" \"p5\" \"\" \"\" \"\" \"\""}, 1497 1498 /* The quoted string rules also apply to consecutive quotes at the start 1499 * of a parameter but don't count the opening quote! 1500 */ 1501 {"Params23456789", "\"\"twoquotes \"p four\" \"\"twoquotes p7", TRUE, 1502 " \"\" \"twoquotes\" \"p four\" \"\" \"twoquotes\" \"p7\" \"\" \"\""}, 1503 1504 {"Params23456789", "\"\"\"three quotes\" \"p three\" \"\"\"three quotes\" p5", TRUE, 1505 " \"\"three quotes\" \"p three\" \"\"three quotes\" \"p5\" \"\" \"\" \"\" \"\""}, 1506 1507 {"Params23456789", "\"\"\"\"fourquotes \"p four\" \"\"\"\"fourquotes p7", TRUE, 1508 " \"\"\" \"fourquotes\" \"p four\" \"\"\" \"fourquotes\" \"p7\" \"\" \"\""}, 1509 1510 /* An unclosed quoted string gets lost! */ 1511 {"Params23456", "p2 \"p3\" \"p4 is lost", TRUE, 1512 " \"p2\" \"p3\" \"\" \"\" \"\"", 1513 " \"p2\" \"p3\" \"p3\" \"\" \"\""}, /* NT4/2k */ 1514 1515 /* Backslashes have no special meaning even when preceding quotes. All 1516 * they do is start an unquoted string. 1517 */ 1518 {"Params23456", "\\\"p\\three \"pfour\\\" pfive", TRUE, 1519 " \"\\\" \"p\\three\" \"pfour\\\" \"pfive\" \"\""}, 1520 1521 /* Environment variables are left untouched. */ 1522 {"Params23456", "%TMPDIR% %t %c", FALSE, 1523 " \"%TMPDIR%\" \"%t\" \"%c\" \"\" \"\""}, 1524 1525 /* %~2 is equivalent to %*. However %~3 and higher include the spaces 1526 * before the parameter! 1527 * (but not the previous parameter's closing quote fortunately) 1528 */ 1529 {"Params2345Etc", "p2 p3 \"p4\" p5 p6 ", TRUE, 1530 " ~2=\"p2 p3 \"p4\" p5 p6 \" ~3=\" p3 \"p4\" p5 p6 \" ~4=\" \"p4\" p5 p6 \" ~5= p5 p6 "}, 1531 1532 /* %~n works even if there is no nth parameter. */ 1533 {"Params9Etc", "p2 p3 p4 p5 p6 p7 p8 ", TRUE, 1534 " ~9=\" \""}, 1535 1536 {"Params9Etc", "p2 p3 p4 p5 p6 p7 ", TRUE, 1537 " ~9=\"\""}, 1538 1539 /* The %~n directives also transmit the tenth parameter and beyond. */ 1540 {"Params9Etc", "p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 and beyond!", TRUE, 1541 " ~9=\" p9 p10 p11 and beyond!\""}, 1542 1543 /* Bad formatting directives lose their % sign, except those followed by 1544 * a tilde! Environment variables are not expanded but lose their % sign. 1545 */ 1546 {"ParamsBad", "p2 p3 p4 p5", TRUE, 1547 " \"% - %~ %~0 %~1 %~a %~* a b c TMPDIR\""}, 1548 1549 {0} 1550 }; 1551 1552 static void test_argify(void) 1553 { 1554 char fileA[MAX_PATH], params[2*MAX_PATH+12]; 1555 INT_PTR rc; 1556 const argify_tests_t* test; 1557 const char *bad; 1558 const char* cmd; 1559 1560 /* Test with a long parameter */ 1561 for (rc = 0; rc < MAX_PATH; rc++) 1562 fileA[rc] = 'a' + rc % 26; 1563 fileA[MAX_PATH-1] = '\0'; 1564 sprintf(params, "shlexec \"%s\" %s", child_file, fileA); 1565 1566 /* We need NOZONECHECKS on Win2003 to block a dialog */ 1567 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, params, NULL, NULL); 1568 okShell(rc > 32, "failed: rc=%lu\n", rc); 1569 okChildInt("argcA", 4); 1570 okChildPath("argvA3", fileA); 1571 1572 if (skip_shlexec_tests) 1573 { 1574 skip("No argify tests due to lack of .shlexec association\n"); 1575 return; 1576 } 1577 1578 create_test_verb("shlexec.shlexec", "Params232S", 0, "Params232S %2 %3 \"%2\" \"%*\""); 1579 create_test_verb("shlexec.shlexec", "Params23456", 0, "Params23456 \"%2\" \"%3\" \"%4\" \"%5\" \"%6\""); 1580 create_test_verb("shlexec.shlexec", "Params23456789", 0, "Params23456789 \"%2\" \"%3\" \"%4\" \"%5\" \"%6\" \"%7\" \"%8\" \"%9\""); 1581 create_test_verb("shlexec.shlexec", "Params2345Etc", 0, "Params2345Etc ~2=\"%~2\" ~3=\"%~3\" ~4=\"%~4\" ~5=%~5"); 1582 create_test_verb("shlexec.shlexec", "Params9Etc", 0, "Params9Etc ~9=\"%~9\""); 1583 create_test_verb("shlexec.shlexec", "Params20", 0, "Params20 \"%20\""); 1584 create_test_verb("shlexec.shlexec", "ParamsBad", 0, "ParamsBad \"%% %- %~ %~0 %~1 %~a %~* %a %b %c %TMPDIR%\""); 1585 1586 sprintf(fileA, "%s\\test file.shlexec", tmpdir); 1587 1588 test = argify_tests; 1589 while (test->params) 1590 { 1591 bad = test->broken ? test->broken : test->cmd; 1592 1593 rc = shell_execute_ex(SEE_MASK_DOENVSUBST, test->verb, fileA, test->params, NULL, NULL); 1594 okShell(rc > 32, "failed: rc=%lu\n", rc); 1595 1596 cmd = getChildString("Child", "cmdlineA"); 1597 /* Our commands are such that the verb immediately precedes the 1598 * part we are interested in. 1599 */ 1600 if (cmd) cmd = strstr(cmd, test->verb); 1601 if (cmd) cmd += strlen(test->verb); 1602 if (!cmd) cmd = "(null)"; 1603 todo_wine_if(test->todo) 1604 okShell(!strcmp(cmd, test->cmd) || broken(!strcmp(cmd, bad)), 1605 "expected '%s', got '%s'\n", cmd, test->cmd); 1606 test++; 1607 } 1608 } 1609 1610 static void test_filename(void) 1611 { 1612 char filename[MAX_PATH]; 1613 const filename_tests_t* test; 1614 char* c; 1615 INT_PTR rc; 1616 1617 if (skip_shlexec_tests) 1618 { 1619 skip("No ShellExecute/filename tests due to lack of .shlexec association\n"); 1620 return; 1621 } 1622 1623 test=filename_tests; 1624 while (test->basename) 1625 { 1626 BOOL quotedfile = FALSE; 1627 1628 if (skip_noassoc_tests && test->rc == SE_ERR_NOASSOC) 1629 { 1630 win_skip("Skipping shellexecute of file with unassociated extension\n"); 1631 test++; 1632 continue; 1633 } 1634 1635 sprintf(filename, test->basename, tmpdir); 1636 if (strchr(filename, '/')) 1637 { 1638 c=filename; 1639 while (*c) 1640 { 1641 if (*c=='\\') 1642 *c='/'; 1643 c++; 1644 } 1645 } 1646 if ((test->todo & 0x40)==0) 1647 { 1648 rc=shell_execute(test->verb, filename, NULL, NULL); 1649 } 1650 else 1651 { 1652 char quoted[MAX_PATH + 2]; 1653 1654 quotedfile = TRUE; 1655 sprintf(quoted, "\"%s\"", filename); 1656 rc=shell_execute(test->verb, quoted, NULL, NULL); 1657 } 1658 if (rc > 32) 1659 rc=33; 1660 okShell(rc==test->rc || 1661 broken(quotedfile && rc == SE_ERR_FNF), /* NT4 */ 1662 "failed: rc=%ld err=%u\n", rc, GetLastError()); 1663 if (rc == 33) 1664 { 1665 const char* verb; 1666 todo_wine_if(test->todo & 0x2) 1667 okChildInt("argcA", 5); 1668 verb=(test->verb ? test->verb : "Open"); 1669 todo_wine_if(test->todo & 0x4) 1670 okChildString("argvA3", verb); 1671 todo_wine_if(test->todo & 0x8) 1672 okChildPath("argvA4", filename); 1673 } 1674 test++; 1675 } 1676 1677 test=noquotes_tests; 1678 while (test->basename) 1679 { 1680 sprintf(filename, test->basename, tmpdir); 1681 rc=shell_execute(test->verb, filename, NULL, NULL); 1682 if (rc > 32) 1683 rc=33; 1684 todo_wine_if(test->todo & 0x1) 1685 okShell(rc==test->rc, "failed: rc=%ld err=%u\n", rc, GetLastError()); 1686 if (rc==0) 1687 { 1688 int count; 1689 const char* verb; 1690 char* str; 1691 1692 verb=(test->verb ? test->verb : "Open"); 1693 todo_wine_if(test->todo & 0x4) 1694 okChildString("argvA3", verb); 1695 1696 count=4; 1697 str=filename; 1698 while (1) 1699 { 1700 char attrib[18]; 1701 char* space; 1702 space=strchr(str, ' '); 1703 if (space) 1704 *space='\0'; 1705 sprintf(attrib, "argvA%d", count); 1706 todo_wine_if(test->todo & 0x8) 1707 okChildPath(attrib, str); 1708 count++; 1709 if (!space) 1710 break; 1711 str=space+1; 1712 } 1713 todo_wine_if(test->todo & 0x2) 1714 okChildInt("argcA", count); 1715 } 1716 test++; 1717 } 1718 1719 if (dllver.dwMajorVersion != 0) 1720 { 1721 /* The more recent versions of shell32.dll accept quoted filenames 1722 * while older ones (e.g. 4.00) don't. Still we want to test this 1723 * because IE 6 depends on the new behavior. 1724 * One day we may need to check the exact version of the dll but for 1725 * now making sure DllGetVersion() is present is sufficient. 1726 */ 1727 sprintf(filename, "\"%s\\test file.shlexec\"", tmpdir); 1728 rc=shell_execute(NULL, filename, NULL, NULL); 1729 okShell(rc > 32, "failed: rc=%ld err=%u\n", rc, GetLastError()); 1730 okChildInt("argcA", 5); 1731 okChildString("argvA3", "Open"); 1732 sprintf(filename, "%s\\test file.shlexec", tmpdir); 1733 okChildPath("argvA4", filename); 1734 } 1735 1736 sprintf(filename, "\"%s\\test file.sha\"", tmpdir); 1737 rc=shell_execute(NULL, filename, NULL, NULL); 1738 todo_wine okShell(rc > 32, "failed: rc=%ld err=%u\n", rc, GetLastError()); 1739 okChildInt("argcA", 5); 1740 todo_wine okChildString("argvA3", "averb"); 1741 sprintf(filename, "%s\\test file.sha", tmpdir); 1742 todo_wine okChildPath("argvA4", filename); 1743 } 1744 1745 typedef struct 1746 { 1747 const char* urlprefix; 1748 const char* basename; 1749 int flags; 1750 int todo; 1751 } fileurl_tests_t; 1752 1753 #define URL_SUCCESS 0x1 1754 #define USE_COLON 0x2 1755 #define USE_BSLASH 0x4 1756 1757 static fileurl_tests_t fileurl_tests[]= 1758 { 1759 /* How many slashes does it take... */ 1760 {"file:", "%s\\test file.shlexec", URL_SUCCESS, 0}, 1761 {"file:/", "%s\\test file.shlexec", URL_SUCCESS, 0}, 1762 {"file://", "%s\\test file.shlexec", URL_SUCCESS, 0}, 1763 {"file:///", "%s\\test file.shlexec", URL_SUCCESS, 0}, 1764 {"File:///", "%s\\test file.shlexec", URL_SUCCESS, 0}, 1765 {"file:////", "%s\\test file.shlexec", URL_SUCCESS, 0}, 1766 {"file://///", "%s\\test file.shlexec", 0, 0}, 1767 1768 /* Test with Windows-style paths */ 1769 {"file:///", "%s\\test file.shlexec", URL_SUCCESS | USE_COLON, 0}, 1770 {"file:///", "%s\\test file.shlexec", URL_SUCCESS | USE_BSLASH, 0}, 1771 1772 /* Check handling of hostnames */ 1773 {"file://localhost/", "%s\\test file.shlexec", URL_SUCCESS, 0}, 1774 {"file://localhost:80/", "%s\\test file.shlexec", 0, 0}, 1775 {"file://LocalHost/", "%s\\test file.shlexec", URL_SUCCESS, 0}, 1776 {"file://127.0.0.1/", "%s\\test file.shlexec", 0, 0}, 1777 {"file://::1/", "%s\\test file.shlexec", 0, 0}, 1778 {"file://notahost/", "%s\\test file.shlexec", 0, 0}, 1779 1780 /* Environment variables are not expanded in URLs */ 1781 {"%urlprefix%", "%s\\test file.shlexec", 0, 0x1}, 1782 {"file:///", "%%TMPDIR%%\\test file.shlexec", 0, 0}, 1783 1784 /* Test shortcuts vs. URLs */ 1785 {"file://///", "%s\\test_shortcut_shlexec.lnk", 0, 0x1c}, 1786 1787 /* Confuse things by mixing protocols */ 1788 {"file://", "shlproto://foo/bar", USE_COLON, 0}, 1789 1790 {NULL, NULL, 0, 0} 1791 }; 1792 1793 static void test_fileurls(void) 1794 { 1795 char filename[MAX_PATH], fileurl[MAX_PATH], longtmpdir[MAX_PATH]; 1796 char command[MAX_PATH]; 1797 const fileurl_tests_t* test; 1798 char *s; 1799 INT_PTR rc; 1800 1801 if (skip_shlexec_tests) 1802 { 1803 skip("No file URL tests due to lack of .shlexec association\n"); 1804 return; 1805 } 1806 1807 rc = shell_execute_ex(SEE_MASK_FLAG_NO_UI, NULL, 1808 "file:///nosuchfile.shlexec", NULL, NULL, NULL); 1809 if (rc > 32) 1810 { 1811 win_skip("shell32 is too old (likely < 4.72). Skipping the file URL tests\n"); 1812 return; 1813 } 1814 1815 get_long_path_name(tmpdir, longtmpdir, ARRAY_SIZE(longtmpdir)); 1816 SetEnvironmentVariableA("urlprefix", "file:///"); 1817 1818 test=fileurl_tests; 1819 while (test->basename) 1820 { 1821 /* Build the file URL */ 1822 sprintf(filename, test->basename, longtmpdir); 1823 strcpy(fileurl, test->urlprefix); 1824 strcat(fileurl, filename); 1825 s = fileurl + strlen(test->urlprefix); 1826 while (*s) 1827 { 1828 if (!(test->flags & USE_COLON) && *s == ':') 1829 *s = '|'; 1830 else if (!(test->flags & USE_BSLASH) && *s == '\\') 1831 *s = '/'; 1832 s++; 1833 } 1834 1835 /* Test it first with FindExecutable() */ 1836 rc = (INT_PTR)FindExecutableA(fileurl, NULL, command); 1837 ok(rc == SE_ERR_FNF, "FindExecutable(%s) failed: bad rc=%lu\n", fileurl, rc); 1838 1839 /* Then ShellExecute() */ 1840 if ((test->todo & 0x10) == 0) 1841 rc = shell_execute(NULL, fileurl, NULL, NULL); 1842 else todo_wait 1843 rc = shell_execute(NULL, fileurl, NULL, NULL); 1844 if (bad_shellexecute) 1845 { 1846 win_skip("shell32 is too old (likely 4.72). Skipping the file URL tests\n"); 1847 break; 1848 } 1849 if (test->flags & URL_SUCCESS) 1850 { 1851 todo_wine_if(test->todo & 0x1) 1852 okShell(rc > 32, "failed: bad rc=%lu\n", rc); 1853 } 1854 else 1855 { 1856 todo_wine_if(test->todo & 0x1) 1857 okShell(rc == SE_ERR_FNF || rc == SE_ERR_PNF || 1858 broken(rc == SE_ERR_ACCESSDENIED) /* win2000 */, 1859 "failed: bad rc=%lu\n", rc); 1860 } 1861 if (rc == 33) 1862 { 1863 todo_wine_if(test->todo & 0x2) 1864 okChildInt("argcA", 5); 1865 todo_wine_if(test->todo & 0x4) 1866 okChildString("argvA3", "Open"); 1867 todo_wine_if(test->todo & 0x8) 1868 okChildPath("argvA4", filename); 1869 } 1870 test++; 1871 } 1872 1873 SetEnvironmentVariableA("urlprefix", NULL); 1874 } 1875 1876 static void test_urls(void) 1877 { 1878 char url[MAX_PATH]; 1879 INT_PTR rc; 1880 1881 if (!create_test_class("fakeproto", FALSE)) 1882 { 1883 skip("Unable to create 'fakeproto' class for URL tests\n"); 1884 return; 1885 } 1886 create_test_verb("fakeproto", "open", 0, "URL %1"); 1887 1888 create_test_class("shlpaverb", TRUE); 1889 create_test_verb("shlpaverb", "averb", 0, "PAVerb \"%1\""); 1890 1891 /* Protocols must be properly declared */ 1892 rc = shell_execute(NULL, "notaproto://foo", NULL, NULL); 1893 ok(rc == SE_ERR_NOASSOC || broken(rc == SE_ERR_ACCESSDENIED), 1894 "%s returned %lu\n", shell_call, rc); 1895 1896 rc = shell_execute(NULL, "fakeproto://foo/bar", NULL, NULL); 1897 todo_wine ok(rc == SE_ERR_NOASSOC || broken(rc == SE_ERR_ACCESSDENIED), 1898 "%s returned %lu\n", shell_call, rc); 1899 1900 /* Here's a real live one */ 1901 rc = shell_execute(NULL, "shlproto://foo/bar", NULL, NULL); 1902 ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc); 1903 okChildInt("argcA", 5); 1904 okChildString("argvA3", "URL"); 1905 okChildString("argvA4", "shlproto://foo/bar"); 1906 1907 /* Check default verb detection */ 1908 rc = shell_execute(NULL, "shlpaverb://foo/bar", NULL, NULL); 1909 todo_wine ok(rc > 32 || /* XP+IE7 - Win10 */ 1910 broken(rc == SE_ERR_NOASSOC), /* XP+IE6 */ 1911 "%s failed: rc=%lu\n", shell_call, rc); 1912 if (rc > 32) 1913 { 1914 okChildInt("argcA", 5); 1915 todo_wine okChildString("argvA3", "PAVerb"); 1916 todo_wine okChildString("argvA4", "shlpaverb://foo/bar"); 1917 } 1918 1919 /* But alternative verbs are a recent feature! */ 1920 rc = shell_execute("averb", "shlproto://foo/bar", NULL, NULL); 1921 ok(rc > 32 || /* Win8 - Win10 */ 1922 broken(rc == SE_ERR_ACCESSDENIED), /* XP - Win7 */ 1923 "%s failed: rc=%lu\n", shell_call, rc); 1924 if (rc > 32) 1925 { 1926 okChildString("argvA3", "AVerb"); 1927 okChildString("argvA4", "shlproto://foo/bar"); 1928 } 1929 1930 /* A .lnk ending does not turn a URL into a shortcut */ 1931 rc = shell_execute(NULL, "shlproto://foo/bar.lnk", NULL, NULL); 1932 ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc); 1933 okChildInt("argcA", 5); 1934 okChildString("argvA3", "URL"); 1935 okChildString("argvA4", "shlproto://foo/bar.lnk"); 1936 1937 /* Neither does a .exe extension */ 1938 rc = shell_execute(NULL, "shlproto://foo/bar.exe", NULL, NULL); 1939 ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc); 1940 okChildInt("argcA", 5); 1941 okChildString("argvA3", "URL"); 1942 okChildString("argvA4", "shlproto://foo/bar.exe"); 1943 1944 /* But a class name overrides it */ 1945 rc = shell_execute(NULL, "shlproto://foo/bar", "shlexec.shlexec", NULL); 1946 ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc); 1947 okChildInt("argcA", 5); 1948 okChildString("argvA3", "URL"); 1949 okChildString("argvA4", "shlproto://foo/bar"); 1950 1951 /* Environment variables are expanded in URLs (but not in file URLs!) */ 1952 rc = shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI, 1953 NULL, "shlproto://%TMPDIR%/bar", NULL, NULL, NULL); 1954 okShell(rc > 32, "failed: rc=%lu\n", rc); 1955 okChildInt("argcA", 5); 1956 sprintf(url, "shlproto://%s/bar", tmpdir); 1957 okChildString("argvA3", "URL"); 1958 okChildStringBroken("argvA4", url, "shlproto://%TMPDIR%/bar"); 1959 1960 /* But only after the path has been identified as a URL */ 1961 SetEnvironmentVariableA("urlprefix", "shlproto:///"); 1962 rc = shell_execute(NULL, "%urlprefix%foo", NULL, NULL); 1963 todo_wine ok(rc == SE_ERR_FNF, "%s returned %lu\n", shell_call, rc); 1964 SetEnvironmentVariableA("urlprefix", NULL); 1965 1966 delete_test_class("fakeproto"); 1967 delete_test_class("shlpaverb"); 1968 } 1969 1970 static void test_find_executable(void) 1971 { 1972 char notepad_path[MAX_PATH]; 1973 char filename[MAX_PATH]; 1974 char command[MAX_PATH]; 1975 const filename_tests_t* test; 1976 INT_PTR rc; 1977 1978 if (!create_test_association(".sfe")) 1979 { 1980 skip("Unable to create association for '.sfe'\n"); 1981 return; 1982 } 1983 create_test_verb("shlexec.sfe", "Open", 1, "%1"); 1984 1985 /* Don't test FindExecutable(..., NULL), it always crashes */ 1986 1987 strcpy(command, "your word"); 1988 if (0) /* Can crash on Vista! */ 1989 { 1990 rc=(INT_PTR)FindExecutableA(NULL, NULL, command); 1991 ok(rc == SE_ERR_FNF || rc > 32 /* nt4 */, "FindExecutable(NULL) returned %ld\n", rc); 1992 ok(strcmp(command, "your word") != 0, "FindExecutable(NULL) returned command=[%s]\n", command); 1993 } 1994 1995 GetSystemDirectoryA( notepad_path, MAX_PATH ); 1996 strcat( notepad_path, "\\notepad.exe" ); 1997 1998 /* Search for something that should be in the system-wide search path (no default directory) */ 1999 strcpy(command, "your word"); 2000 rc=(INT_PTR)FindExecutableA("notepad.exe", NULL, command); 2001 ok(rc > 32, "FindExecutable(%s) returned %ld\n", "notepad.exe", rc); 2002 ok(strcasecmp(command, notepad_path) == 0, "FindExecutable(%s) returned command=[%s]\n", "notepad.exe", command); 2003 2004 /* Search for something that should be in the system-wide search path (with default directory) */ 2005 strcpy(command, "your word"); 2006 rc=(INT_PTR)FindExecutableA("notepad.exe", tmpdir, command); 2007 ok(rc > 32, "FindExecutable(%s) returned %ld\n", "notepad.exe", rc); 2008 ok(strcasecmp(command, notepad_path) == 0, "FindExecutable(%s) returned command=[%s]\n", "notepad.exe", command); 2009 2010 strcpy(command, "your word"); 2011 rc=(INT_PTR)FindExecutableA(tmpdir, NULL, command); 2012 ok(rc == SE_ERR_NOASSOC /* >= win2000 */ || rc > 32 /* win98, nt4 */, "FindExecutable(NULL) returned %ld\n", rc); 2013 ok(strcmp(command, "your word") != 0, "FindExecutable(NULL) returned command=[%s]\n", command); 2014 2015 sprintf(filename, "%s\\test file.sfe", tmpdir); 2016 rc=(INT_PTR)FindExecutableA(filename, NULL, command); 2017 ok(rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc); 2018 /* Depending on the platform, command could be '%1' or 'test file.sfe' */ 2019 2020 rc=(INT_PTR)FindExecutableA("test file.sfe", tmpdir, command); 2021 ok(rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc); 2022 2023 rc=(INT_PTR)FindExecutableA("test file.sfe", NULL, command); 2024 ok(rc == SE_ERR_FNF, "FindExecutable(%s) returned %ld\n", filename, rc); 2025 2026 delete_test_association(".sfe"); 2027 2028 if (!create_test_association(".shl")) 2029 { 2030 skip("Unable to create association for '.shl'\n"); 2031 return; 2032 } 2033 create_test_verb("shlexec.shl", "Open", 0, "Open"); 2034 2035 sprintf(filename, "%s\\test file.shl", tmpdir); 2036 rc=(INT_PTR)FindExecutableA(filename, NULL, command); 2037 ok(rc == SE_ERR_FNF /* NT4 */ || rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc); 2038 2039 sprintf(filename, "%s\\test file.shlfoo", tmpdir); 2040 rc=(INT_PTR)FindExecutableA(filename, NULL, command); 2041 2042 delete_test_association(".shl"); 2043 2044 if (rc > 32) 2045 { 2046 /* On Windows XP and 2003 FindExecutable() is completely broken. 2047 * Probably what it does is convert the filename to 8.3 format, 2048 * which as a side effect converts the '.shlfoo' extension to '.shl', 2049 * and then tries to find an association for '.shl'. This means it 2050 * will normally fail on most extensions with more than 3 characters, 2051 * like '.mpeg', etc. 2052 * Also it means we cannot do any other test. 2053 */ 2054 win_skip("FindExecutable() is broken -> not running 4+ character extension tests\n"); 2055 return; 2056 } 2057 2058 if (skip_shlexec_tests) 2059 { 2060 skip("No FindExecutable/filename tests due to lack of .shlexec association\n"); 2061 return; 2062 } 2063 2064 test=filename_tests; 2065 while (test->basename) 2066 { 2067 sprintf(filename, test->basename, tmpdir); 2068 if (strchr(filename, '/')) 2069 { 2070 char* c; 2071 c=filename; 2072 while (*c) 2073 { 2074 if (*c=='\\') 2075 *c='/'; 2076 c++; 2077 } 2078 } 2079 /* Win98 does not '\0'-terminate command! */ 2080 memset(command, '\0', sizeof(command)); 2081 rc=(INT_PTR)FindExecutableA(filename, NULL, command); 2082 if (rc > 32) 2083 rc=33; 2084 todo_wine_if(test->todo & 0x10) 2085 ok(rc==test->rc, "FindExecutable(%s) failed: rc=%ld\n", filename, rc); 2086 if (rc > 32) 2087 { 2088 BOOL equal; 2089 equal=strcmp(command, argv0) == 0 || 2090 /* NT4 returns an extra 0x8 character! */ 2091 (strlen(command) == strlen(argv0)+1 && strncmp(command, argv0, strlen(argv0)) == 0); 2092 todo_wine_if(test->todo & 0x20) 2093 ok(equal, "FindExecutable(%s) returned command='%s' instead of '%s'\n", 2094 filename, command, argv0); 2095 } 2096 test++; 2097 } 2098 } 2099 2100 2101 static filename_tests_t lnk_tests[]= 2102 { 2103 /* Pass bad / nonexistent filenames as a parameter */ 2104 {NULL, "%s\\nonexistent.shlexec", 0xa, 33}, 2105 {NULL, "%s\\nonexistent.noassoc", 0xa, 33}, 2106 2107 /* Pass regular paths as a parameter */ 2108 {NULL, "%s\\test file.shlexec", 0xa, 33}, 2109 {NULL, "%s/%%nasty%% $file.shlexec", 0xa, 33}, 2110 2111 /* Pass filenames with no association as a parameter */ 2112 {NULL, "%s\\test file.noassoc", 0xa, 33}, 2113 2114 {NULL, NULL, 0} 2115 }; 2116 2117 static void test_lnks(void) 2118 { 2119 char filename[MAX_PATH]; 2120 char params[MAX_PATH]; 2121 const filename_tests_t* test; 2122 INT_PTR rc; 2123 2124 if (skip_shlexec_tests) 2125 skip("No FindExecutable/filename tests due to lack of .shlexec association\n"); 2126 else 2127 { 2128 /* Should open through our association */ 2129 sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir); 2130 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, NULL); 2131 okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError()); 2132 okChildInt("argcA", 5); 2133 okChildString("argvA3", "Open"); 2134 sprintf(params, "%s\\test file.shlexec", tmpdir); 2135 get_long_path_name(params, filename, sizeof(filename)); 2136 okChildPath("argvA4", filename); 2137 2138 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_DOENVSUBST, NULL, "%TMPDIR%\\test_shortcut_shlexec.lnk", NULL, NULL, NULL); 2139 okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError()); 2140 okChildInt("argcA", 5); 2141 okChildString("argvA3", "Open"); 2142 sprintf(params, "%s\\test file.shlexec", tmpdir); 2143 get_long_path_name(params, filename, sizeof(filename)); 2144 okChildPath("argvA4", filename); 2145 } 2146 2147 /* Should just run our executable */ 2148 sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir); 2149 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, NULL); 2150 okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError()); 2151 okChildInt("argcA", 4); 2152 okChildString("argvA3", "Lnk"); 2153 2154 if (!skip_shlexec_tests) 2155 { 2156 /* An explicit class overrides lnk's ContextMenuHandler */ 2157 rc=shell_execute_ex(SEE_MASK_CLASSNAME | SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, "shlexec.shlexec"); 2158 okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError()); 2159 okChildInt("argcA", 5); 2160 okChildString("argvA3", "Open"); 2161 okChildPath("argvA4", filename); 2162 } 2163 2164 if (dllver.dwMajorVersion>=6) 2165 { 2166 char* c; 2167 /* Recent versions of shell32.dll accept '/'s in shortcut paths. 2168 * Older versions don't or are quite buggy in this regard. 2169 */ 2170 sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir); 2171 c=filename; 2172 while (*c) 2173 { 2174 if (*c=='\\') 2175 *c='/'; 2176 c++; 2177 } 2178 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, NULL); 2179 okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError()); 2180 okChildInt("argcA", 4); 2181 okChildString("argvA3", "Lnk"); 2182 } 2183 2184 sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir); 2185 test=lnk_tests; 2186 while (test->basename) 2187 { 2188 params[0]='\"'; 2189 sprintf(params+1, test->basename, tmpdir); 2190 strcat(params,"\""); 2191 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, params, 2192 NULL, NULL); 2193 if (rc > 32) 2194 rc=33; 2195 todo_wine_if(test->todo & 0x1) 2196 okShell(rc==test->rc, "failed: rc=%lu err=%u\n", rc, GetLastError()); 2197 if (rc==0) 2198 { 2199 todo_wine_if(test->todo & 0x2) 2200 okChildInt("argcA", 5); 2201 todo_wine_if(test->todo & 0x4) 2202 okChildString("argvA3", "Lnk"); 2203 sprintf(params, test->basename, tmpdir); 2204 okChildPath("argvA4", params); 2205 } 2206 test++; 2207 } 2208 } 2209 2210 2211 static void test_exes(void) 2212 { 2213 char filename[MAX_PATH]; 2214 char params[1024]; 2215 INT_PTR rc; 2216 2217 sprintf(params, "shlexec \"%s\" Exec", child_file); 2218 2219 /* We need NOZONECHECKS on Win2003 to block a dialog */ 2220 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, params, 2221 NULL, NULL); 2222 okShell(rc > 32, "returned %lu\n", rc); 2223 okChildInt("argcA", 4); 2224 okChildString("argvA3", "Exec"); 2225 2226 if (! skip_noassoc_tests) 2227 { 2228 sprintf(filename, "%s\\test file.noassoc", tmpdir); 2229 if (CopyFileA(argv0, filename, FALSE)) 2230 { 2231 rc=shell_execute(NULL, filename, params, NULL); 2232 todo_wine { 2233 okShell(rc==SE_ERR_NOASSOC, "returned %lu\n", rc); 2234 } 2235 } 2236 } 2237 else 2238 { 2239 win_skip("Skipping shellexecute of file with unassociated extension\n"); 2240 } 2241 2242 /* test combining executable and parameters */ 2243 sprintf(filename, "%s shlexec \"%s\" Exec", argv0, child_file); 2244 rc = shell_execute(NULL, filename, NULL, NULL); 2245 okShell(rc == SE_ERR_FNF, "returned %lu\n", rc); 2246 2247 sprintf(filename, "\"%s\" shlexec \"%s\" Exec", argv0, child_file); 2248 rc = shell_execute(NULL, filename, NULL, NULL); 2249 okShell(rc == SE_ERR_FNF, "returned %lu\n", rc); 2250 2251 /* A verb, even if invalid, overrides the normal handling of executables */ 2252 todo_wait rc = shell_execute_ex(SEE_MASK_FLAG_NO_UI, 2253 "notaverb", argv0, NULL, NULL, NULL); 2254 todo_wine okShell(rc == SE_ERR_NOASSOC, "returned %lu\n", rc); 2255 2256 if (!skip_shlexec_tests) 2257 { 2258 /* A class overrides the normal handling of executables too */ 2259 /* FIXME SEE_MASK_FLAG_NO_UI is only needed due to Wine's bug */ 2260 rc = shell_execute_ex(SEE_MASK_CLASSNAME | SEE_MASK_FLAG_NO_UI, 2261 NULL, argv0, NULL, NULL, ".shlexec"); 2262 todo_wine okShell(rc > 32, "returned %lu\n", rc); 2263 okChildInt("argcA", 5); 2264 todo_wine okChildString("argvA3", "Open"); 2265 todo_wine okChildPath("argvA4", argv0); 2266 } 2267 } 2268 2269 typedef struct 2270 { 2271 const char* command; 2272 const char* ddeexec; 2273 const char* application; 2274 const char* topic; 2275 const char* ifexec; 2276 int expectedArgs; 2277 const char* expectedDdeExec; 2278 BOOL broken; 2279 } dde_tests_t; 2280 2281 static dde_tests_t dde_tests[] = 2282 { 2283 /* Test passing and not passing command-line 2284 * argument, no DDE */ 2285 {"", NULL, NULL, NULL, NULL, 0, ""}, 2286 {"\"%1\"", NULL, NULL, NULL, NULL, 1, ""}, 2287 2288 /* Test passing and not passing command-line 2289 * argument, with DDE */ 2290 {"", "[open(\"%1\")]", "shlexec", "dde", NULL, 0, "[open(\"%s\")]"}, 2291 {"\"%1\"", "[open(\"%1\")]", "shlexec", "dde", NULL, 1, "[open(\"%s\")]"}, 2292 2293 /* Test unquoted %1 in command and ddeexec 2294 * (test filename has space) */ 2295 {"%1", "[open(%1)]", "shlexec", "dde", NULL, 2, "[open(%s)]", TRUE /* before vista */}, 2296 2297 /* Test ifexec precedence over ddeexec */ 2298 {"", "[open(\"%1\")]", "shlexec", "dde", "[ifexec(\"%1\")]", 0, "[ifexec(\"%s\")]"}, 2299 2300 /* Test default DDE topic */ 2301 {"", "[open(\"%1\")]", "shlexec", NULL, NULL, 0, "[open(\"%s\")]"}, 2302 2303 /* Test default DDE application */ 2304 {"", "[open(\"%1\")]", NULL, "dde", NULL, 0, "[open(\"%s\")]"}, 2305 2306 {NULL} 2307 }; 2308 2309 static int waitforinputidle_count; 2310 static DWORD WINAPI hooked_WaitForInputIdle(HANDLE process, DWORD timeout) 2311 { 2312 waitforinputidle_count++; 2313 if (winetest_debug > 1) 2314 trace("WaitForInputIdle() waiting for dde event timeout=min(%u,5s)\n", timeout); 2315 timeout = timeout < 5000 ? timeout : 5000; 2316 return WaitForSingleObject(dde_ready_event, timeout); 2317 } 2318 2319 /* 2320 * WaitForInputIdle() will normally return immediately for console apps. That's 2321 * a problem for us because ShellExecute will assume that an app is ready to 2322 * receive DDE messages after it has called WaitForInputIdle() on that app. 2323 * To work around that we install our own version of WaitForInputIdle() that 2324 * will wait for the child to explicitly tell us that it is ready. We do that 2325 * by changing the entry for WaitForInputIdle() in the shell32 import address 2326 * table. 2327 */ 2328 static void hook_WaitForInputIdle(DWORD (WINAPI *new_func)(HANDLE, DWORD)) 2329 { 2330 char *base; 2331 PIMAGE_NT_HEADERS nt_headers; 2332 DWORD import_directory_rva; 2333 PIMAGE_IMPORT_DESCRIPTOR import_descriptor; 2334 int hook_count = 0; 2335 2336 base = (char *) GetModuleHandleA("shell32.dll"); 2337 nt_headers = (PIMAGE_NT_HEADERS)(base + ((PIMAGE_DOS_HEADER) base)->e_lfanew); 2338 import_directory_rva = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; 2339 2340 /* Search for the correct imported module by walking the import descriptors */ 2341 import_descriptor = (PIMAGE_IMPORT_DESCRIPTOR)(base + import_directory_rva); 2342 while (U(*import_descriptor).OriginalFirstThunk != 0) 2343 { 2344 char *import_module_name; 2345 2346 import_module_name = base + import_descriptor->Name; 2347 if (lstrcmpiA(import_module_name, "user32.dll") == 0 || 2348 lstrcmpiA(import_module_name, "user32") == 0) 2349 { 2350 PIMAGE_THUNK_DATA int_entry; 2351 PIMAGE_THUNK_DATA iat_entry; 2352 2353 /* The import name table and import address table are two parallel 2354 * arrays. We need the import name table to find the imported 2355 * routine and the import address table to patch the address, so 2356 * walk them side by side */ 2357 int_entry = (PIMAGE_THUNK_DATA)(base + U(*import_descriptor).OriginalFirstThunk); 2358 iat_entry = (PIMAGE_THUNK_DATA)(base + import_descriptor->FirstThunk); 2359 while (int_entry->u1.Ordinal != 0) 2360 { 2361 if (! IMAGE_SNAP_BY_ORDINAL(int_entry->u1.Ordinal)) 2362 { 2363 PIMAGE_IMPORT_BY_NAME import_by_name; 2364 import_by_name = (PIMAGE_IMPORT_BY_NAME)(base + int_entry->u1.AddressOfData); 2365 if (lstrcmpA((char *) import_by_name->Name, "WaitForInputIdle") == 0) 2366 { 2367 /* Found the correct routine in the correct imported module. Patch it. */ 2368 DWORD old_prot; 2369 VirtualProtect(&iat_entry->u1.Function, sizeof(ULONG_PTR), PAGE_READWRITE, &old_prot); 2370 iat_entry->u1.Function = (ULONG_PTR) new_func; 2371 VirtualProtect(&iat_entry->u1.Function, sizeof(ULONG_PTR), old_prot, &old_prot); 2372 if (winetest_debug > 1) 2373 trace("Hooked %s.WaitForInputIdle\n", import_module_name); 2374 hook_count++; 2375 break; 2376 } 2377 } 2378 int_entry++; 2379 iat_entry++; 2380 } 2381 break; 2382 } 2383 2384 import_descriptor++; 2385 } 2386 ok(hook_count, "Could not hook WaitForInputIdle()\n"); 2387 } 2388 2389 static void test_dde(void) 2390 { 2391 char filename[MAX_PATH], defApplication[MAX_PATH]; 2392 const dde_tests_t* test; 2393 char params[1024]; 2394 INT_PTR rc; 2395 HANDLE map; 2396 char *shared_block; 2397 DWORD ddeflags; 2398 2399 hook_WaitForInputIdle(hooked_WaitForInputIdle); 2400 2401 sprintf(filename, "%s\\test file.sde", tmpdir); 2402 2403 /* Default service is application name minus path and extension */ 2404 strcpy(defApplication, strrchr(argv0, '\\')+1); 2405 *strchr(defApplication, '.') = 0; 2406 2407 map = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 2408 4096, "winetest_shlexec_dde_map"); 2409 shared_block = MapViewOfFile(map, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 4096); 2410 2411 ddeflags = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI; 2412 test = dde_tests; 2413 while (test->command) 2414 { 2415 if (!create_test_association(".sde")) 2416 { 2417 skip("Unable to create association for '.sde'\n"); 2418 return; 2419 } 2420 create_test_verb_dde("shlexec.sde", "Open", 0, test->command, test->ddeexec, 2421 test->application, test->topic, test->ifexec); 2422 2423 if (test->application != NULL || test->topic != NULL) 2424 { 2425 strcpy(shared_block, test->application ? test->application : defApplication); 2426 strcpy(shared_block + strlen(shared_block) + 1, test->topic ? test->topic : SZDDESYS_TOPIC); 2427 } 2428 else 2429 { 2430 shared_block[0] = '\0'; 2431 shared_block[1] = '\0'; 2432 } 2433 ddeExec[0] = 0; 2434 2435 waitforinputidle_count = 0; 2436 dde_ready_event = CreateEventA(NULL, TRUE, FALSE, "winetest_shlexec_dde_ready"); 2437 rc = shell_execute_ex(ddeflags, NULL, filename, NULL, NULL, NULL); 2438 CloseHandle(dde_ready_event); 2439 if (!(ddeflags & SEE_MASK_WAITFORINPUTIDLE) && rc == SE_ERR_DDEFAIL && 2440 GetLastError() == ERROR_FILE_NOT_FOUND && 2441 strcmp(winetest_platform, "windows") == 0) 2442 { 2443 /* Windows 10 does not call WaitForInputIdle() for DDE which creates 2444 * a race condition as the DDE server may not have time to start up. 2445 * When that happens the test fails with the above results and we 2446 * compensate by forcing the WaitForInputIdle() call. 2447 */ 2448 trace("Adding SEE_MASK_WAITFORINPUTIDLE for Windows 10\n"); 2449 ddeflags |= SEE_MASK_WAITFORINPUTIDLE; 2450 delete_test_association(".sde"); 2451 Sleep(CHILD_DDE_TIMEOUT); 2452 continue; 2453 } 2454 okShell(32 < rc, "failed: rc=%lu err=%u\n", rc, GetLastError()); 2455 if (test->ddeexec) 2456 okShell(waitforinputidle_count == 1 || 2457 broken(waitforinputidle_count == 0) /* Win10 race */, 2458 "WaitForInputIdle() was called %u times\n", 2459 waitforinputidle_count); 2460 else 2461 okShell(waitforinputidle_count == 0, "WaitForInputIdle() was called %u times for a non-DDE case\n", waitforinputidle_count); 2462 2463 if (32 < rc) 2464 { 2465 if (test->broken) 2466 okChildIntBroken("argcA", test->expectedArgs + 3); 2467 else 2468 okChildInt("argcA", test->expectedArgs + 3); 2469 2470 if (test->expectedArgs == 1) okChildPath("argvA3", filename); 2471 2472 sprintf(params, test->expectedDdeExec, filename); 2473 okChildPath("ddeExec", params); 2474 } 2475 reset_association_description(); 2476 2477 delete_test_association(".sde"); 2478 test++; 2479 } 2480 2481 UnmapViewOfFile(shared_block); 2482 CloseHandle(map); 2483 hook_WaitForInputIdle((void *) WaitForInputIdle); 2484 } 2485 2486 #define DDE_DEFAULT_APP_VARIANTS 3 2487 typedef struct 2488 { 2489 const char* command; 2490 const char* expectedDdeApplication[DDE_DEFAULT_APP_VARIANTS]; 2491 int todo; 2492 int rc[DDE_DEFAULT_APP_VARIANTS]; 2493 } dde_default_app_tests_t; 2494 2495 static dde_default_app_tests_t dde_default_app_tests[] = 2496 { 2497 /* There are three possible sets of results: Windows <= 2000, XP SP1 and 2498 * >= XP SP2. Use the first two tests to determine which results to expect. 2499 */ 2500 2501 /* Test unquoted existing filename with a space */ 2502 {"%s\\test file.exe", {"test file", "test file", "test"}, 0x0, {33, 33, 33}}, 2503 {"%s\\test2 file.exe", {"test2", "", "test2"}, 0x0, {33, 5, 33}}, 2504 2505 /* Test unquoted existing filename with a space */ 2506 {"%s\\test file.exe param", {"test file", "test file", "test"}, 0x0, {33, 33, 33}}, 2507 2508 /* Test quoted existing filename with a space */ 2509 {"\"%s\\test file.exe\"", {"test file", "test file", "test file"}, 0x0, {33, 33, 33}}, 2510 {"\"%s\\test file.exe\" param", {"test file", "test file", "test file"}, 0x0, {33, 33, 33}}, 2511 2512 /* Test unquoted filename with a space that doesn't exist, but 2513 * test2.exe does */ 2514 {"%s\\test2 file.exe param", {"test2", "", "test2"}, 0x0, {33, 5, 33}}, 2515 2516 /* Test quoted filename with a space that does not exist */ 2517 {"\"%s\\test2 file.exe\"", {"", "", "test2 file"}, 0x0, {5, 2, 33}}, 2518 {"\"%s\\test2 file.exe\" param", {"", "", "test2 file"}, 0x0, {5, 2, 33}}, 2519 2520 /* Test filename supplied without the extension */ 2521 {"%s\\test2", {"test2", "", "test2"}, 0x0, {33, 5, 33}}, 2522 {"%s\\test2 param", {"test2", "", "test2"}, 0x0, {33, 5, 33}}, 2523 2524 /* Test an unquoted nonexistent filename */ 2525 {"%s\\notexist.exe", {"", "", "notexist"}, 0x0, {5, 2, 33}}, 2526 {"%s\\notexist.exe param", {"", "", "notexist"}, 0x0, {5, 2, 33}}, 2527 2528 /* Test an application that will be found on the path */ 2529 {"cmd", {"cmd", "cmd", "cmd"}, 0x0, {33, 33, 33}}, 2530 {"cmd param", {"cmd", "cmd", "cmd"}, 0x0, {33, 33, 33}}, 2531 2532 /* Test an application that will not be found on the path */ 2533 {"xyzwxyzwxyz", {"", "", "xyzwxyzwxyz"}, 0x0, {5, 2, 33}}, 2534 {"xyzwxyzwxyz param", {"", "", "xyzwxyzwxyz"}, 0x0, {5, 2, 33}}, 2535 2536 {NULL, {NULL}, 0, {0}} 2537 }; 2538 2539 typedef struct 2540 { 2541 char *filename; 2542 DWORD threadIdParent; 2543 } dde_thread_info_t; 2544 2545 static DWORD CALLBACK ddeThread(LPVOID arg) 2546 { 2547 dde_thread_info_t *info = arg; 2548 assert(info && info->filename); 2549 PostThreadMessageA(info->threadIdParent, 2550 WM_QUIT, 2551 shell_execute_ex(SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI, NULL, info->filename, NULL, NULL, NULL), 2552 0); 2553 ExitThread(0); 2554 } 2555 2556 static void test_dde_default_app(void) 2557 { 2558 char filename[MAX_PATH]; 2559 HSZ hszApplication; 2560 dde_thread_info_t info = { filename, GetCurrentThreadId() }; 2561 const dde_default_app_tests_t* test; 2562 char params[1024]; 2563 DWORD threadId; 2564 MSG msg; 2565 INT_PTR rc; 2566 int which = 0; 2567 HDDEDATA ret; 2568 BOOL b; 2569 2570 post_quit_on_execute = FALSE; 2571 ddeInst = 0; 2572 rc = DdeInitializeA(&ddeInst, ddeCb, CBF_SKIP_ALLNOTIFICATIONS | CBF_FAIL_ADVISES | 2573 CBF_FAIL_POKES | CBF_FAIL_REQUESTS, 0); 2574 ok(rc == DMLERR_NO_ERROR, "got %lx\n", rc); 2575 2576 sprintf(filename, "%s\\test file.sde", tmpdir); 2577 2578 /* It is strictly not necessary to register an application name here, but wine's 2579 * DdeNameService implementation complains if 0 is passed instead of 2580 * hszApplication with DNS_FILTEROFF */ 2581 hszApplication = DdeCreateStringHandleA(ddeInst, "shlexec", CP_WINANSI); 2582 hszTopic = DdeCreateStringHandleA(ddeInst, "shlexec", CP_WINANSI); 2583 ok(hszApplication && hszTopic, "got %p and %p\n", hszApplication, hszTopic); 2584 ret = DdeNameService(ddeInst, hszApplication, 0, DNS_REGISTER | DNS_FILTEROFF); 2585 ok(ret != 0, "got %p\n", ret); 2586 2587 test = dde_default_app_tests; 2588 while (test->command) 2589 { 2590 HANDLE thread; 2591 2592 if (!create_test_association(".sde")) 2593 { 2594 skip("Unable to create association for '.sde'\n"); 2595 return; 2596 } 2597 sprintf(params, test->command, tmpdir); 2598 create_test_verb_dde("shlexec.sde", "Open", 1, params, "[test]", NULL, 2599 "shlexec", NULL); 2600 ddeApplication[0] = 0; 2601 2602 /* No application will be run as we will respond to the first DDE event, 2603 * so don't wait for it */ 2604 SetEvent(hEvent); 2605 2606 thread = CreateThread(NULL, 0, ddeThread, &info, 0, &threadId); 2607 ok(thread != NULL, "got %p\n", thread); 2608 while (GetMessageA(&msg, NULL, 0, 0)) DispatchMessageA(&msg); 2609 rc = msg.wParam > 32 ? 33 : msg.wParam; 2610 2611 /* The first two tests determine which set of results to expect. 2612 * First check the platform as only the first set of results is 2613 * acceptable for Wine. 2614 */ 2615 if (strcmp(winetest_platform, "wine")) 2616 { 2617 if (test == dde_default_app_tests) 2618 { 2619 if (strcmp(ddeApplication, test->expectedDdeApplication[0])) 2620 which = 2; 2621 } 2622 else if (test == dde_default_app_tests + 1) 2623 { 2624 if (which == 0 && rc == test->rc[1]) 2625 which = 1; 2626 trace("DDE result variant %d\n", which); 2627 } 2628 } 2629 2630 todo_wine_if(test->todo & 0x1) 2631 okShell(rc==test->rc[which], "failed: rc=%lu err=%u\n", 2632 rc, GetLastError()); 2633 if (rc == 33) 2634 { 2635 todo_wine_if(test->todo & 0x2) 2636 ok(!strcmp(ddeApplication, test->expectedDdeApplication[which]), 2637 "Expected application '%s', got '%s'\n", 2638 test->expectedDdeApplication[which], ddeApplication); 2639 } 2640 reset_association_description(); 2641 2642 delete_test_association(".sde"); 2643 test++; 2644 } 2645 2646 ret = DdeNameService(ddeInst, hszApplication, 0, DNS_UNREGISTER); 2647 ok(ret != 0, "got %p\n", ret); 2648 b = DdeFreeStringHandle(ddeInst, hszTopic); 2649 ok(b, "got %d\n", b); 2650 b = DdeFreeStringHandle(ddeInst, hszApplication); 2651 ok(b, "got %d\n", b); 2652 b = DdeUninitialize(ddeInst); 2653 ok(b, "got %d\n", b); 2654 } 2655 2656 static void init_test(void) 2657 { 2658 HMODULE hdll; 2659 HRESULT (WINAPI *pDllGetVersion)(DLLVERSIONINFO*); 2660 char filename[MAX_PATH]; 2661 WCHAR lnkfile[MAX_PATH]; 2662 char params[1024]; 2663 const char* const * testfile; 2664 lnk_desc_t desc; 2665 DWORD rc; 2666 HRESULT r; 2667 2668 hdll=GetModuleHandleA("shell32.dll"); 2669 pDllGetVersion=(void*)GetProcAddress(hdll, "DllGetVersion"); 2670 if (pDllGetVersion) 2671 { 2672 dllver.cbSize=sizeof(dllver); 2673 pDllGetVersion(&dllver); 2674 trace("major=%d minor=%d build=%d platform=%d\n", 2675 dllver.dwMajorVersion, dllver.dwMinorVersion, 2676 dllver.dwBuildNumber, dllver.dwPlatformID); 2677 } 2678 else 2679 { 2680 memset(&dllver, 0, sizeof(dllver)); 2681 } 2682 2683 r = CoInitialize(NULL); 2684 ok(r == S_OK, "CoInitialize failed (0x%08x)\n", r); 2685 if (FAILED(r)) 2686 exit(1); 2687 2688 rc=GetModuleFileNameA(NULL, argv0, sizeof(argv0)); 2689 ok(rc != 0 && rc < sizeof(argv0), "got %d\n", rc); 2690 if (GetFileAttributesA(argv0)==INVALID_FILE_ATTRIBUTES) 2691 { 2692 strcat(argv0, ".so"); 2693 ok(GetFileAttributesA(argv0)!=INVALID_FILE_ATTRIBUTES, 2694 "unable to find argv0!\n"); 2695 } 2696 2697 /* Older versions (win 2k) fail tests if there is a space in 2698 the path. */ 2699 if (dllver.dwMajorVersion <= 5) 2700 strcpy(filename, "c:\\"); 2701 else 2702 GetTempPathA(sizeof(filename), filename); 2703 GetTempFileNameA(filename, "wt", 0, tmpdir); 2704 GetLongPathNameA(tmpdir, tmpdir, sizeof(tmpdir)); 2705 DeleteFileA( tmpdir ); 2706 rc = CreateDirectoryA( tmpdir, NULL ); 2707 ok( rc, "failed to create %s err %u\n", tmpdir, GetLastError() ); 2708 /* Set %TMPDIR% for the tests */ 2709 SetEnvironmentVariableA("TMPDIR", tmpdir); 2710 2711 rc = GetTempFileNameA(tmpdir, "wt", 0, child_file); 2712 ok(rc != 0, "got %d\n", rc); 2713 init_event(child_file); 2714 2715 /* Set up the test files */ 2716 testfile=testfiles; 2717 while (*testfile) 2718 { 2719 HANDLE hfile; 2720 2721 sprintf(filename, *testfile, tmpdir); 2722 hfile=CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 2723 FILE_ATTRIBUTE_NORMAL, NULL); 2724 if (hfile==INVALID_HANDLE_VALUE) 2725 { 2726 trace("unable to create '%s': err=%u\n", filename, GetLastError()); 2727 assert(0); 2728 } 2729 CloseHandle(hfile); 2730 testfile++; 2731 } 2732 2733 /* Setup the test shortcuts */ 2734 sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir); 2735 MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, ARRAY_SIZE(lnkfile)); 2736 desc.description=NULL; 2737 desc.workdir=NULL; 2738 sprintf(filename, "%s\\test file.shlexec", tmpdir); 2739 desc.path=filename; 2740 desc.pidl=NULL; 2741 desc.arguments="ignored"; 2742 desc.showcmd=0; 2743 desc.icon=NULL; 2744 desc.icon_id=0; 2745 desc.hotkey=0; 2746 create_lnk(lnkfile, &desc, 0); 2747 2748 sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir); 2749 MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, ARRAY_SIZE(lnkfile)); 2750 desc.description=NULL; 2751 desc.workdir=NULL; 2752 desc.path=argv0; 2753 desc.pidl=NULL; 2754 sprintf(params, "shlexec \"%s\" Lnk", child_file); 2755 desc.arguments=params; 2756 desc.showcmd=0; 2757 desc.icon=NULL; 2758 desc.icon_id=0; 2759 desc.hotkey=0; 2760 create_lnk(lnkfile, &desc, 0); 2761 2762 /* Create a basic association suitable for most tests */ 2763 if (!create_test_association(".shlexec")) 2764 { 2765 skip_shlexec_tests = TRUE; 2766 skip("Unable to create association for '.shlexec'\n"); 2767 return; 2768 } 2769 create_test_verb("shlexec.shlexec", "Open", 0, "Open \"%1\""); 2770 create_test_verb("shlexec.shlexec", "NoQuotes", 0, "NoQuotes %1"); 2771 create_test_verb("shlexec.shlexec", "LowerL", 0, "LowerL %l"); 2772 create_test_verb("shlexec.shlexec", "QuotedLowerL", 0, "QuotedLowerL \"%l\""); 2773 create_test_verb("shlexec.shlexec", "UpperL", 0, "UpperL %L"); 2774 create_test_verb("shlexec.shlexec", "QuotedUpperL", 0, "QuotedUpperL \"%L\""); 2775 2776 create_test_association(".sha"); 2777 create_test_verb("shlexec.sha", "averb", 0, "AVerb \"%1\""); 2778 2779 create_test_class("shlproto", TRUE); 2780 create_test_verb("shlproto", "open", 0, "URL \"%1\""); 2781 create_test_verb("shlproto", "averb", 0, "AVerb \"%1\""); 2782 2783 /* Set an environment variable to see if it is inherited */ 2784 SetEnvironmentVariableA("ShlexecVar", "Present"); 2785 } 2786 2787 static void cleanup_test(void) 2788 { 2789 char filename[MAX_PATH]; 2790 const char* const * testfile; 2791 2792 /* Delete the test files */ 2793 testfile=testfiles; 2794 while (*testfile) 2795 { 2796 sprintf(filename, *testfile, tmpdir); 2797 /* Make sure we can delete the files ('test file.noassoc' is read-only now) */ 2798 SetFileAttributesA(filename, FILE_ATTRIBUTE_NORMAL); 2799 DeleteFileA(filename); 2800 testfile++; 2801 } 2802 DeleteFileA(child_file); 2803 RemoveDirectoryA(tmpdir); 2804 2805 /* Delete the test association */ 2806 delete_test_association(".shlexec"); 2807 delete_test_association(".sha"); 2808 delete_test_class("shlproto"); 2809 2810 CloseHandle(hEvent); 2811 2812 CoUninitialize(); 2813 } 2814 2815 static void test_directory(void) 2816 { 2817 char path[MAX_PATH], curdir[MAX_PATH]; 2818 char params[1024], dirpath[1024]; 2819 INT_PTR rc; 2820 2821 sprintf(path, "%s\\test2.exe", tmpdir); 2822 CopyFileA(argv0, path, FALSE); 2823 2824 sprintf(params, "shlexec \"%s\" Exec", child_file); 2825 2826 /* Test with the current directory */ 2827 GetCurrentDirectoryA(sizeof(curdir), curdir); 2828 SetCurrentDirectoryA(tmpdir); 2829 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI, 2830 NULL, "test2.exe", params, NULL, NULL); 2831 okShell(rc > 32, "returned %lu\n", rc); 2832 okChildInt("argcA", 4); 2833 okChildString("argvA3", "Exec"); 2834 todo_wine okChildPath("longPath", path); 2835 SetCurrentDirectoryA(curdir); 2836 2837 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI, 2838 NULL, "test2.exe", params, NULL, NULL); 2839 okShell(rc == SE_ERR_FNF, "returned %lu\n", rc); 2840 2841 /* Explicitly specify the directory to use */ 2842 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI, 2843 NULL, "test2.exe", params, tmpdir, NULL); 2844 okShell(rc > 32, "returned %lu\n", rc); 2845 okChildInt("argcA", 4); 2846 okChildString("argvA3", "Exec"); 2847 todo_wine okChildPath("longPath", path); 2848 2849 /* Specify it through an environment variable */ 2850 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI, 2851 NULL, "test2.exe", params, "%TMPDIR%", NULL); 2852 todo_wine okShell(rc == SE_ERR_FNF, "returned %lu\n", rc); 2853 2854 rc=shell_execute_ex(SEE_MASK_DOENVSUBST|SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI, 2855 NULL, "test2.exe", params, "%TMPDIR%", NULL); 2856 okShell(rc > 32, "returned %lu\n", rc); 2857 okChildInt("argcA", 4); 2858 okChildString("argvA3", "Exec"); 2859 todo_wine okChildPath("longPath", path); 2860 2861 /* Not a colon-separated directory list */ 2862 sprintf(dirpath, "%s:%s", curdir, tmpdir); 2863 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI, 2864 NULL, "test2.exe", params, dirpath, NULL); 2865 okShell(rc == SE_ERR_FNF, "returned %lu\n", rc); 2866 } 2867 2868 START_TEST(shlexec) 2869 { 2870 2871 myARGC = winetest_get_mainargs(&myARGV); 2872 if (myARGC >= 3) 2873 { 2874 doChild(myARGC, myARGV); 2875 /* Skip the tests/failures trace for child processes */ 2876 ExitProcess(winetest_get_failures()); 2877 } 2878 2879 init_test(); 2880 2881 test_commandline2argv(); 2882 test_argify(); 2883 test_lpFile_parsed(); 2884 test_filename(); 2885 test_fileurls(); 2886 test_urls(); 2887 test_find_executable(); 2888 test_lnks(); 2889 test_exes(); 2890 test_dde(); 2891 test_dde_default_app(); 2892 test_directory(); 2893 2894 cleanup_test(); 2895 } 2896