1 /* 2 * Shell Library Functions 3 * 4 * Copyright 1998 Marcus Meissner 5 * Copyright 2002 Eric Pouech 6 * Copyright 2018-2024 Katayama Hirofumi MZ 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Lesser General Public 10 * License as published by the Free Software Foundation; either 11 * version 2.1 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Lesser General Public License for more details. 17 * 18 * You should have received a copy of the GNU Lesser General Public 19 * License along with this library; if not, write to the Free Software 20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 21 */ 22 23 #include "precomp.h" 24 #include <undocshell.h> 25 26 WINE_DEFAULT_DEBUG_CHANNEL(exec); 27 28 EXTERN_C BOOL PathIsExeW(LPCWSTR lpszPath); 29 30 #define SEE_MASK_CLASSALL (SEE_MASK_CLASSNAME | SEE_MASK_CLASSKEY) 31 32 typedef UINT_PTR (*SHELL_ExecuteW32)(const WCHAR *lpCmd, WCHAR *env, BOOL shWait, 33 const SHELLEXECUTEINFOW *sei, LPSHELLEXECUTEINFOW sei_out); 34 35 // Is the current process a rundll32.exe? 36 static BOOL SHELL_InRunDllProcess(VOID) 37 { 38 WCHAR szModule[MAX_PATH]; 39 static INT s_bInDllProcess = -1; 40 41 if (s_bInDllProcess != -1) 42 return s_bInDllProcess; 43 44 s_bInDllProcess = GetModuleFileNameW(NULL, szModule, _countof(szModule)) && 45 (StrStrIW(PathFindFileNameW(szModule), L"rundll") != NULL); 46 return s_bInDllProcess; 47 } 48 49 static void ParseNoTildeEffect(PWSTR &res, LPCWSTR &args, DWORD &len, DWORD &used, int argNum) 50 { 51 bool firstCharQuote = false; 52 bool quotes_opened = false; 53 bool backslash_encountered = false; 54 55 for (int curArg = 0; curArg <= argNum && *args; ++curArg) 56 { 57 firstCharQuote = false; 58 if (*args == '"') 59 { 60 quotes_opened = true; 61 firstCharQuote = true; 62 args++; 63 } 64 65 while(*args) 66 { 67 if (*args == '\\') 68 { 69 // if we found a backslash then flip the variable 70 backslash_encountered = !backslash_encountered; 71 } 72 else if (*args == '"') 73 { 74 if (quotes_opened) 75 { 76 if (*(args + 1) != '"') 77 { 78 quotes_opened = false; 79 args++; 80 break; 81 } 82 else 83 { 84 args++; 85 } 86 } 87 else 88 { 89 quotes_opened = true; 90 } 91 92 backslash_encountered = false; 93 } 94 else 95 { 96 backslash_encountered = false; 97 if (*args == ' ' && !firstCharQuote) 98 break; 99 } 100 101 if (curArg == argNum) 102 { 103 used++; 104 if (used < len) 105 *res++ = *args; 106 } 107 108 args++; 109 } 110 111 while(*args == ' ') 112 ++args; 113 } 114 } 115 116 static void ParseTildeEffect(PWSTR &res, LPCWSTR &args, DWORD &len, DWORD &used, int argNum) 117 { 118 bool quotes_opened = false; 119 bool backslash_encountered = false; 120 121 for (int curArg = 0; curArg <= argNum && *args; ++curArg) 122 { 123 while(*args) 124 { 125 if (*args == '\\') 126 { 127 // if we found a backslash then flip the variable 128 backslash_encountered = !backslash_encountered; 129 } 130 else if (*args == '"') 131 { 132 if (quotes_opened) 133 { 134 if (*(args + 1) != '"') 135 { 136 quotes_opened = false; 137 } 138 else 139 { 140 args++; 141 } 142 } 143 else 144 { 145 quotes_opened = true; 146 } 147 148 backslash_encountered = false; 149 } 150 else 151 { 152 backslash_encountered = false; 153 if (*args == ' ' && !quotes_opened && curArg != argNum) 154 break; 155 } 156 157 if (curArg == argNum) 158 { 159 used++; 160 if (used < len) 161 *res++ = *args; 162 } 163 164 args++; 165 } 166 } 167 } 168 169 /*********************************************************************** 170 * SHELL_ArgifyW [Internal] 171 * 172 * this function is supposed to expand the escape sequences found in the registry 173 * some diving reported that the following were used: 174 * + %1, %2... seem to report to parameter of index N in ShellExecute pmts 175 * %1 file 176 * %2 printer 177 * %3 driver 178 * %4 port 179 * %I address of a global item ID (explorer switch /idlist) 180 * %L seems to be %1 as long filename followed by the 8+3 variation 181 * %S ??? 182 * %W Working directory 183 * %V Use either %L or %W 184 * %* all following parameters (see batfile) 185 * 186 * The way we parse the command line arguments was determined through extensive 187 * testing and can be summed up by the following rules" 188 * 189 * - %2 190 * - if first letter is " break on first non literal " and include any white spaces 191 * - if first letter is NOT " break on first " or white space 192 * - if " is opened any pair of consecutive " results in ONE literal " 193 * 194 * - %~2 195 * - use rules from here http://www.autohotkey.net/~deleyd/parameters/parameters.htm 196 */ 197 198 static BOOL SHELL_ArgifyW(WCHAR* out, DWORD len, const WCHAR* fmt, const WCHAR* lpFile, LPITEMIDLIST pidl, LPCWSTR args, DWORD* out_len, const WCHAR* lpDir) 199 { 200 WCHAR xlpFile[1024]; 201 BOOL done = FALSE; 202 BOOL found_p1 = FALSE; 203 PWSTR res = out; 204 PCWSTR cmd; 205 DWORD used = 0; 206 bool tildeEffect = false; 207 208 TRACE("Before parsing: %p, %d, %s, %s, %p, %p\n", out, len, debugstr_w(fmt), 209 debugstr_w(lpFile), pidl, args); 210 211 while (*fmt) 212 { 213 if (*fmt == '%') 214 { 215 switch (*++fmt) 216 { 217 case '\0': 218 case '%': 219 { 220 used++; 221 if (used < len) 222 *res++ = '%'; 223 }; 224 break; 225 226 case '*': 227 { 228 if (args) 229 { 230 if (*fmt == '*') 231 { 232 used++; 233 while(*args) 234 { 235 used++; 236 if (used < len) 237 *res++ = *args++; 238 else 239 args++; 240 } 241 used++; 242 break; 243 } 244 } 245 }; 246 break; 247 248 case '~': 249 250 case '2': 251 case '3': 252 case '4': 253 case '5': 254 case '6': 255 case '7': 256 case '8': 257 case '9': 258 //case '0': 259 { 260 if (*fmt == '~') 261 { 262 fmt++; 263 tildeEffect = true; 264 } 265 266 if (args) 267 { 268 if (tildeEffect) 269 { 270 ParseTildeEffect(res, args, len, used, *fmt - '2'); 271 tildeEffect = false; 272 } 273 else 274 { 275 ParseNoTildeEffect(res, args, len, used, *fmt - '2'); 276 } 277 } 278 }; 279 break; 280 281 case '1': 282 if (!done || (*fmt == '1')) 283 { 284 /*FIXME Is the call to SearchPathW() really needed? We already have separated out the parameter string in args. */ 285 if (SearchPathW(lpDir, lpFile, L".exe", ARRAY_SIZE(xlpFile), xlpFile, NULL)) 286 cmd = xlpFile; 287 else 288 cmd = lpFile; 289 290 used += wcslen(cmd); 291 if (used < len) 292 { 293 wcscpy(res, cmd); 294 res += wcslen(cmd); 295 } 296 } 297 found_p1 = TRUE; 298 break; 299 300 /* 301 * IE uses this a lot for activating things such as windows media 302 * player. This is not verified to be fully correct but it appears 303 * to work just fine. 304 */ 305 case 'l': 306 case 'L': 307 if (lpFile) 308 { 309 used += wcslen(lpFile); 310 if (used < len) 311 { 312 wcscpy(res, lpFile); 313 res += wcslen(lpFile); 314 } 315 } 316 found_p1 = TRUE; 317 break; 318 319 case 'w': 320 case 'W': 321 if (lpDir) 322 { 323 used += wcslen(lpDir); 324 if (used < len) 325 { 326 wcscpy(res, lpDir); 327 res += wcslen(lpDir); 328 } 329 } 330 break; 331 332 case 'v': 333 case 'V': 334 if (lpFile) 335 { 336 used += wcslen(lpFile); 337 if (used < len) 338 { 339 wcscpy(res, lpFile); 340 res += wcslen(lpFile); 341 } 342 found_p1 = TRUE; 343 } 344 else if (lpDir) 345 { 346 used += wcslen(lpDir); 347 if (used < len) 348 { 349 wcscpy(res, lpDir); 350 res += wcslen(lpDir); 351 } 352 } 353 break; 354 355 case 'i': 356 case 'I': 357 if (pidl) 358 { 359 DWORD chars = 0; 360 /* %p should not exceed 8, maybe 16 when looking forward to 64bit. 361 * allowing a buffer of 100 should more than exceed all needs */ 362 WCHAR buf[100]; 363 LPVOID pv; 364 HGLOBAL hmem = SHAllocShared(pidl, ILGetSize(pidl), 0); 365 pv = SHLockShared(hmem, 0); 366 chars = swprintf(buf, L":%p", pv); 367 368 if (chars >= ARRAY_SIZE(buf)) 369 ERR("pidl format buffer too small!\n"); 370 371 used += chars; 372 373 if (used < len) 374 { 375 wcscpy(res, buf); 376 res += chars; 377 } 378 SHUnlockShared(pv); 379 } 380 found_p1 = TRUE; 381 break; 382 383 default: 384 /* 385 * Check if this is an env-variable here... 386 */ 387 388 /* Make sure that we have at least one more %.*/ 389 if (strchrW(fmt, '%')) 390 { 391 WCHAR tmpBuffer[1024]; 392 PWSTR tmpB = tmpBuffer; 393 WCHAR tmpEnvBuff[MAX_PATH]; 394 DWORD envRet; 395 396 while (*fmt != '%') 397 *tmpB++ = *fmt++; 398 *tmpB++ = 0; 399 400 TRACE("Checking %s to be an env-var\n", debugstr_w(tmpBuffer)); 401 402 envRet = GetEnvironmentVariableW(tmpBuffer, tmpEnvBuff, MAX_PATH); 403 if (envRet == 0 || envRet > MAX_PATH) 404 { 405 used += wcslen(tmpBuffer); 406 if (used < len) 407 { 408 wcscpy( res, tmpBuffer ); 409 res += wcslen(tmpBuffer); 410 } 411 } 412 else 413 { 414 used += wcslen(tmpEnvBuff); 415 if (used < len) 416 { 417 wcscpy( res, tmpEnvBuff ); 418 res += wcslen(tmpEnvBuff); 419 } 420 } 421 } 422 done = TRUE; 423 break; 424 } 425 /* Don't skip past terminator (catch a single '%' at the end) */ 426 if (*fmt != '\0') 427 { 428 fmt++; 429 } 430 } 431 else 432 { 433 used ++; 434 if (used < len) 435 *res++ = *fmt++; 436 else 437 fmt++; 438 } 439 } 440 441 used++; 442 if (res - out < static_cast<int>(len)) 443 *res = '\0'; 444 else 445 out[len-1] = '\0'; 446 447 TRACE("used %i of %i space\n", used, len); 448 if (out_len) 449 *out_len = used; 450 451 TRACE("After parsing: %p, %d, %s, %s, %p, %p\n", out, len, debugstr_w(fmt), 452 debugstr_w(lpFile), pidl, args); 453 454 return found_p1; 455 } 456 457 static HRESULT SHELL_GetPathFromIDListForExecuteW(LPCITEMIDLIST pidl, LPWSTR pszPath, UINT uOutSize) 458 { 459 STRRET strret; 460 CComPtr<IShellFolder> desktop; 461 462 HRESULT hr = SHGetDesktopFolder(&desktop); 463 464 if (SUCCEEDED(hr)) 465 { 466 hr = desktop->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &strret); 467 468 if (SUCCEEDED(hr)) 469 StrRetToStrNW(pszPath, uOutSize, &strret, pidl); 470 } 471 472 return hr; 473 } 474 475 /************************************************************************* 476 * SHELL_ExecuteW [Internal] 477 * 478 */ 479 static UINT_PTR SHELL_ExecuteW(const WCHAR *lpCmd, WCHAR *env, BOOL shWait, 480 const SHELLEXECUTEINFOW *psei, LPSHELLEXECUTEINFOW psei_out) 481 { 482 STARTUPINFOW startup; 483 PROCESS_INFORMATION info; 484 UINT_PTR retval = SE_ERR_NOASSOC; 485 UINT gcdret = 0; 486 WCHAR curdir[MAX_PATH]; 487 DWORD dwCreationFlags; 488 const WCHAR *lpDirectory = NULL; 489 490 TRACE("Execute %s from directory %s\n", debugstr_w(lpCmd), debugstr_w(psei->lpDirectory)); 491 492 /* make sure we don't fail the CreateProcess if the calling app passes in 493 * a bad working directory */ 494 if (psei->lpDirectory && psei->lpDirectory[0]) 495 { 496 DWORD attr = GetFileAttributesW(psei->lpDirectory); 497 if (attr != INVALID_FILE_ATTRIBUTES && attr & FILE_ATTRIBUTE_DIRECTORY) 498 lpDirectory = psei->lpDirectory; 499 } 500 501 /* ShellExecute specifies the command from psei->lpDirectory 502 * if present. Not from the current dir as CreateProcess does */ 503 if (lpDirectory) 504 if ((gcdret = GetCurrentDirectoryW( MAX_PATH, curdir))) 505 if (!SetCurrentDirectoryW( lpDirectory)) 506 ERR("cannot set directory %s\n", debugstr_w(lpDirectory)); 507 508 ZeroMemory(&startup, sizeof(STARTUPINFOW)); 509 startup.cb = sizeof(STARTUPINFOW); 510 startup.dwFlags = STARTF_USESHOWWINDOW; 511 startup.wShowWindow = psei->nShow; 512 dwCreationFlags = CREATE_UNICODE_ENVIRONMENT; 513 if (!(psei->fMask & SEE_MASK_NO_CONSOLE)) 514 dwCreationFlags |= CREATE_NEW_CONSOLE; 515 startup.lpTitle = (LPWSTR)(psei->fMask & (SEE_MASK_HASLINKNAME | SEE_MASK_HASTITLE) ? psei->lpClass : NULL); 516 517 if (psei->fMask & SEE_MASK_HASLINKNAME) 518 startup.dwFlags |= STARTF_TITLEISLINKNAME; 519 520 if (CreateProcessW(NULL, (LPWSTR)lpCmd, NULL, NULL, FALSE, dwCreationFlags, env, 521 lpDirectory, &startup, &info)) 522 { 523 /* Give 30 seconds to the app to come up, if desired. Probably only needed 524 when starting app immediately before making a DDE connection. */ 525 if (shWait) 526 if (WaitForInputIdle(info.hProcess, 30000) == WAIT_FAILED) 527 WARN("WaitForInputIdle failed: Error %d\n", GetLastError() ); 528 retval = 33; 529 530 if (psei->fMask & SEE_MASK_NOCLOSEPROCESS) 531 psei_out->hProcess = info.hProcess; 532 else 533 CloseHandle( info.hProcess ); 534 CloseHandle( info.hThread ); 535 } 536 else if ((retval = GetLastError()) >= 32) 537 { 538 WARN("CreateProcess returned error %ld\n", retval); 539 retval = ERROR_BAD_FORMAT; 540 } 541 542 TRACE("returning %lu\n", retval); 543 544 psei_out->hInstApp = (HINSTANCE)retval; 545 546 if (gcdret) 547 if (!SetCurrentDirectoryW(curdir)) 548 ERR("cannot return to directory %s\n", debugstr_w(curdir)); 549 550 return retval; 551 } 552 553 554 /*********************************************************************** 555 * SHELL_BuildEnvW [Internal] 556 * 557 * Build the environment for the new process, adding the specified 558 * path to the PATH variable. Returned pointer must be freed by caller. 559 */ 560 static LPWSTR SHELL_BuildEnvW( const WCHAR *path ) 561 { 562 CHeapPtr<WCHAR, CLocalAllocator> new_env; 563 WCHAR *strings, *p, *p2; 564 int total = wcslen(path) + 1; 565 BOOL got_path = FALSE; 566 567 if (!(strings = GetEnvironmentStringsW())) return NULL; 568 p = strings; 569 while (*p) 570 { 571 int len = wcslen(p) + 1; 572 if (!_wcsnicmp( p, L"PATH=", 5 )) got_path = TRUE; 573 total += len; 574 p += len; 575 } 576 if (!got_path) total += 5; /* we need to create PATH */ 577 total++; /* terminating null */ 578 579 if (!new_env.Allocate(total)) 580 { 581 FreeEnvironmentStringsW(strings); 582 return NULL; 583 } 584 p = strings; 585 p2 = new_env; 586 while (*p) 587 { 588 int len = wcslen(p) + 1; 589 memcpy(p2, p, len * sizeof(WCHAR)); 590 if (!_wcsnicmp( p, L"PATH=", 5 )) 591 { 592 p2[len - 1] = ';'; 593 wcscpy( p2 + len, path ); 594 p2 += wcslen(path) + 1; 595 } 596 p += len; 597 p2 += len; 598 } 599 if (!got_path) 600 { 601 wcscpy(p2, L"PATH="); 602 wcscat(p2, path); 603 p2 += wcslen(p2) + 1; 604 } 605 *p2 = 0; 606 FreeEnvironmentStringsW(strings); 607 return new_env.Detach(); 608 } 609 610 /*********************************************************************** 611 * SHELL_TryAppPathW [Internal] 612 * 613 * Helper function for SHELL_FindExecutable 614 * @param lpResult - pointer to a buffer of size MAX_PATH 615 * On entry: szName is a filename (probably without path separators). 616 * On exit: if szName found in "App Path", place full path in lpResult, and return true 617 */ 618 static BOOL SHELL_TryAppPathW( LPCWSTR szName, LPWSTR lpResult, WCHAR **env) 619 { 620 HKEY hkApp = NULL; 621 WCHAR buffer[1024]; 622 DWORD len, dwType; 623 LONG res; 624 BOOL found = FALSE; 625 626 if (env) *env = NULL; 627 wcscpy(buffer, L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\"); 628 wcscat(buffer, szName); 629 res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, buffer, 0, KEY_READ, &hkApp); 630 if (res) 631 { 632 // Add ".exe" extension, if extension does not exists 633 if (PathAddExtensionW(buffer, L".exe")) 634 { 635 res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, buffer, 0, KEY_READ, &hkApp); 636 } 637 if (res) goto end; 638 } 639 640 len = MAX_PATH * sizeof(WCHAR); 641 res = SHRegQueryValueExW(hkApp, NULL, NULL, &dwType, (LPBYTE)lpResult, &len); 642 if (res != ERROR_SUCCESS || dwType != REG_SZ) 643 goto end; 644 645 found = TRUE; 646 647 if (env) 648 { 649 len = sizeof(buffer); 650 res = SHRegQueryValueExW(hkApp, L"Path", NULL, &dwType, (LPBYTE)buffer, &len); 651 if (res == ERROR_SUCCESS && dwType == REG_SZ && buffer[0]) 652 *env = SHELL_BuildEnvW(buffer); 653 } 654 655 end: 656 if (hkApp) RegCloseKey(hkApp); 657 return found; 658 } 659 660 /************************************************************************* 661 * SHELL_FindExecutableByVerb [Internal] 662 * 663 * called from SHELL_FindExecutable or SHELL_execute_class 664 * in/out: 665 * classname a buffer, big enough, to get the key name to do actually the 666 * command "WordPad.Document.1\\shell\\open\\command" 667 * passed as "WordPad.Document.1" 668 * in: 669 * lpVerb the operation on it (open) 670 * commandlen the size of command buffer (in bytes) 671 * out: 672 * command a buffer, to store the command to do the 673 * operation on the file 674 * key a buffer, big enough, to get the key name to do actually the 675 * command "WordPad.Document.1\\shell\\open\\command" 676 * Can be NULL 677 */ 678 static UINT SHELL_FindExecutableByVerb(LPCWSTR lpVerb, LPWSTR key, LPWSTR classname, LPWSTR command, LONG commandlen) 679 { 680 HKEY hkeyClass; 681 WCHAR verb[MAX_PATH]; 682 683 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, classname, 0, 0x02000000, &hkeyClass)) 684 return SE_ERR_NOASSOC; 685 if (!HCR_GetDefaultVerbW(hkeyClass, lpVerb, verb, ARRAY_SIZE(verb))) 686 return SE_ERR_NOASSOC; 687 RegCloseKey(hkeyClass); 688 689 /* Looking for ...buffer\shell\<verb>\command */ 690 wcscat(classname, L"\\shell\\"); 691 wcscat(classname, verb); 692 wcscat(classname, L"\\command"); 693 694 if (RegQueryValueW(HKEY_CLASSES_ROOT, classname, command, 695 &commandlen) == ERROR_SUCCESS) 696 { 697 commandlen /= sizeof(WCHAR); 698 if (key) wcscpy(key, classname); 699 #if 0 700 LPWSTR tmp; 701 WCHAR param[256]; 702 LONG paramlen = sizeof(param); 703 704 /* FIXME: it seems all Windows version don't behave the same here. 705 * the doc states that this ddeexec information can be found after 706 * the exec names. 707 * on Win98, it doesn't appear, but I think it does on Win2k 708 */ 709 /* Get the parameters needed by the application 710 from the associated ddeexec key */ 711 tmp = strstrW(classname, L"\\command"); 712 tmp[0] = '\0'; 713 wcscat(classname, wDdeexec); 714 if (RegQueryValueW(HKEY_CLASSES_ROOT, classname, param, 715 ¶mlen) == ERROR_SUCCESS) 716 { 717 paramlen /= sizeof(WCHAR); 718 wcscat(command, L" "); 719 wcscat(command, param); 720 commandlen += paramlen; 721 } 722 #endif 723 724 command[commandlen] = '\0'; 725 726 return 33; /* FIXME see SHELL_FindExecutable() */ 727 } 728 729 return SE_ERR_NOASSOC; 730 } 731 732 /************************************************************************* 733 * SHELL_FindExecutable [Internal] 734 * 735 * Utility for code sharing between FindExecutable and ShellExecute 736 * in: 737 * lpFile the name of a file 738 * lpVerb the operation on it (open) 739 * out: 740 * lpResult a buffer, big enough :-(, to store the command to do the 741 * operation on the file 742 * key a buffer, big enough, to get the key name to do actually the 743 * command (it'll be used afterwards for more information 744 * on the operation) 745 */ 746 static UINT SHELL_FindExecutable(LPCWSTR lpPath, LPCWSTR lpFile, LPCWSTR lpVerb, 747 LPWSTR lpResult, DWORD resultLen, LPWSTR key, WCHAR **env, LPITEMIDLIST pidl, LPCWSTR args) 748 { 749 WCHAR *extension = NULL; /* pointer to file extension */ 750 WCHAR classname[256]; /* registry name for this file type */ 751 LONG classnamelen = sizeof(classname); /* length of above */ 752 WCHAR command[1024]; /* command from registry */ 753 WCHAR wBuffer[256]; /* Used to GetProfileString */ 754 UINT retval = SE_ERR_NOASSOC; 755 WCHAR *tok; /* token pointer */ 756 WCHAR xlpFile[256]; /* result of SearchPath */ 757 DWORD attribs; /* file attributes */ 758 759 TRACE("%s\n", debugstr_w(lpFile)); 760 761 if (!lpResult) 762 return ERROR_INVALID_PARAMETER; 763 764 xlpFile[0] = '\0'; 765 lpResult[0] = '\0'; /* Start off with an empty return string */ 766 if (key) *key = '\0'; 767 768 /* trap NULL parameters on entry */ 769 if (!lpFile) 770 { 771 WARN("(lpFile=%s,lpResult=%s): NULL parameter\n", 772 debugstr_w(lpFile), debugstr_w(lpResult)); 773 return ERROR_FILE_NOT_FOUND; /* File not found. Close enough, I guess. */ 774 } 775 776 if (SHELL_TryAppPathW( lpFile, lpResult, env )) 777 { 778 TRACE("found %s via App Paths\n", debugstr_w(lpResult)); 779 return 33; 780 } 781 782 if (SearchPathW(lpPath, lpFile, L".exe", ARRAY_SIZE(xlpFile), xlpFile, NULL)) 783 { 784 TRACE("SearchPathW returned non-zero\n"); 785 lpFile = xlpFile; 786 /* The file was found in the application-supplied default directory (or the system search path) */ 787 } 788 else if (lpPath && SearchPathW(NULL, lpFile, L".exe", ARRAY_SIZE(xlpFile), xlpFile, NULL)) 789 { 790 TRACE("SearchPathW returned non-zero\n"); 791 lpFile = xlpFile; 792 /* The file was found in one of the directories in the system-wide search path */ 793 } 794 795 attribs = GetFileAttributesW(lpFile); 796 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) 797 { 798 wcscpy(classname, L"Folder"); 799 } 800 else 801 { 802 /* Did we get something? Anything? */ 803 if (xlpFile[0] == 0) 804 { 805 TRACE("Returning SE_ERR_FNF\n"); 806 return SE_ERR_FNF; 807 } 808 /* First thing we need is the file's extension */ 809 extension = wcsrchr(xlpFile, '.'); /* Assume last "." is the one; */ 810 /* File->Run in progman uses */ 811 /* .\FILE.EXE :( */ 812 TRACE("xlpFile=%s,extension=%s\n", debugstr_w(xlpFile), debugstr_w(extension)); 813 814 if (extension == NULL || extension[1] == 0) 815 { 816 WARN("Returning SE_ERR_NOASSOC\n"); 817 return SE_ERR_NOASSOC; 818 } 819 820 /* Three places to check: */ 821 /* 1. win.ini, [windows], programs (NB no leading '.') */ 822 /* 2. Registry, HKEY_CLASS_ROOT\<classname>\shell\open\command */ 823 /* 3. win.ini, [extensions], extension (NB no leading '.' */ 824 /* All I know of the order is that registry is checked before */ 825 /* extensions; however, it'd make sense to check the programs */ 826 /* section first, so that's what happens here. */ 827 828 /* See if it's a program - if GetProfileString fails, we skip this 829 * section. Actually, if GetProfileString fails, we've probably 830 * got a lot more to worry about than running a program... */ 831 if (GetProfileStringW(L"windows", L"programs", L"exe pif bat cmd com", wBuffer, ARRAY_SIZE(wBuffer)) > 0) 832 { 833 CharLowerW(wBuffer); 834 tok = wBuffer; 835 while (*tok) 836 { 837 WCHAR *p = tok; 838 while (*p && *p != ' ' && *p != '\t') p++; 839 if (*p) 840 { 841 *p++ = 0; 842 while (*p == ' ' || *p == '\t') p++; 843 } 844 845 if (wcsicmp(tok, &extension[1]) == 0) /* have to skip the leading "." */ 846 { 847 wcscpy(lpResult, xlpFile); 848 /* Need to perhaps check that the file has a path 849 * attached */ 850 TRACE("found %s\n", debugstr_w(lpResult)); 851 return 33; 852 /* Greater than 32 to indicate success */ 853 } 854 tok = p; 855 } 856 } 857 858 /* Check registry */ 859 if (RegQueryValueW(HKEY_CLASSES_ROOT, extension, classname, 860 &classnamelen) == ERROR_SUCCESS) 861 { 862 classnamelen /= sizeof(WCHAR); 863 if (classnamelen == ARRAY_SIZE(classname)) 864 classnamelen--; 865 866 classname[classnamelen] = '\0'; 867 TRACE("File type: %s\n", debugstr_w(classname)); 868 } 869 else 870 { 871 *classname = '\0'; 872 } 873 } 874 875 if (*classname) 876 { 877 /* pass the verb string to SHELL_FindExecutableByVerb() */ 878 retval = SHELL_FindExecutableByVerb(lpVerb, key, classname, command, sizeof(command)); 879 880 if (retval > 32) 881 { 882 DWORD finishedLen; 883 SHELL_ArgifyW(lpResult, resultLen, command, xlpFile, pidl, args, &finishedLen, lpPath); 884 if (finishedLen > resultLen) 885 ERR("Argify buffer not large enough.. truncated\n"); 886 /* Remove double quotation marks and command line arguments */ 887 if (*lpResult == '"') 888 { 889 WCHAR *p = lpResult; 890 while (*(p + 1) != '"') 891 { 892 *p = *(p + 1); 893 p++; 894 } 895 *p = '\0'; 896 } 897 else 898 { 899 /* Truncate on first space */ 900 WCHAR *p = lpResult; 901 while (*p != ' ' && *p != '\0') 902 p++; 903 *p = '\0'; 904 } 905 } 906 } 907 else /* Check win.ini */ 908 { 909 /* Toss the leading dot */ 910 extension++; 911 if (GetProfileStringW(L"extensions", extension, L"", command, ARRAY_SIZE(command)) > 0) 912 { 913 if (wcslen(command) != 0) 914 { 915 wcscpy(lpResult, command); 916 tok = wcschr(lpResult, '^'); /* should be ^.extension? */ 917 if (tok != NULL) 918 { 919 tok[0] = '\0'; 920 wcscat(lpResult, xlpFile); /* what if no dir in xlpFile? */ 921 tok = wcschr(command, '^'); /* see above */ 922 if ((tok != NULL) && (wcslen(tok) > 5)) 923 { 924 wcscat(lpResult, &tok[5]); 925 } 926 } 927 retval = 33; /* FIXME - see above */ 928 } 929 } 930 } 931 932 TRACE("returning %s\n", debugstr_w(lpResult)); 933 return retval; 934 } 935 936 /****************************************************************** 937 * dde_cb 938 * 939 * callback for the DDE connection. not really useful 940 */ 941 static HDDEDATA CALLBACK dde_cb(UINT uType, UINT uFmt, HCONV hConv, 942 HSZ hsz1, HSZ hsz2, HDDEDATA hData, 943 ULONG_PTR dwData1, ULONG_PTR dwData2) 944 { 945 TRACE("dde_cb: %04x, %04x, %p, %p, %p, %p, %08lx, %08lx\n", 946 uType, uFmt, hConv, hsz1, hsz2, hData, dwData1, dwData2); 947 return NULL; 948 } 949 950 /****************************************************************** 951 * dde_connect 952 * 953 * ShellExecute helper. Used to do an operation with a DDE connection 954 * 955 * Handles both the direct connection (try #1), and if it fails, 956 * launching an application and trying (#2) to connect to it 957 * 958 */ 959 static unsigned dde_connect(const WCHAR* key, const WCHAR* start, WCHAR* ddeexec, 960 const WCHAR* lpFile, WCHAR *env, 961 LPCWSTR szCommandline, LPITEMIDLIST pidl, SHELL_ExecuteW32 execfunc, 962 const SHELLEXECUTEINFOW *psei, LPSHELLEXECUTEINFOW psei_out) 963 { 964 WCHAR regkey[256]; 965 WCHAR * endkey = regkey + wcslen(key); 966 WCHAR app[256], topic[256], ifexec[256], static_res[256]; 967 CHeapPtr<WCHAR, CLocalAllocator> dynamic_res; 968 WCHAR * res; 969 LONG applen, topiclen, ifexeclen; 970 WCHAR * exec; 971 DWORD ddeInst = 0; 972 DWORD tid; 973 DWORD resultLen, endkeyLen; 974 HSZ hszApp, hszTopic; 975 HCONV hConv; 976 HDDEDATA hDdeData; 977 unsigned ret = SE_ERR_NOASSOC; 978 BOOL unicode = !(GetVersion() & 0x80000000); 979 980 if (strlenW(key) + 1 > ARRAY_SIZE(regkey)) 981 { 982 FIXME("input parameter %s larger than buffer\n", debugstr_w(key)); 983 return 2; 984 } 985 wcscpy(regkey, key); 986 endkeyLen = ARRAY_SIZE(regkey) - (endkey - regkey); 987 if (strlenW(L"\\application") + 1 > endkeyLen) 988 { 989 FIXME("endkey %s overruns buffer\n", debugstr_w(L"\\application")); 990 return 2; 991 } 992 wcscpy(endkey, L"\\application"); 993 applen = sizeof(app); 994 if (RegQueryValueW(HKEY_CLASSES_ROOT, regkey, app, &applen) != ERROR_SUCCESS) 995 { 996 WCHAR command[1024], fullpath[MAX_PATH]; 997 LPWSTR ptr = NULL; 998 DWORD ret = 0; 999 1000 /* Get application command from start string and find filename of application */ 1001 if (*start == '"') 1002 { 1003 if (strlenW(start + 1) + 1 > ARRAY_SIZE(command)) 1004 { 1005 FIXME("size of input parameter %s larger than buffer\n", 1006 debugstr_w(start + 1)); 1007 return 2; 1008 } 1009 wcscpy(command, start + 1); 1010 if ((ptr = wcschr(command, '"'))) 1011 * ptr = 0; 1012 ret = SearchPathW(NULL, command, L".exe", ARRAY_SIZE(fullpath), fullpath, &ptr); 1013 } 1014 else 1015 { 1016 LPCWSTR p; 1017 LPWSTR space; 1018 for (p = start; (space = const_cast<LPWSTR>(strchrW(p, ' '))); p = space + 1) 1019 { 1020 int idx = space - start; 1021 memcpy(command, start, idx * sizeof(WCHAR)); 1022 command[idx] = '\0'; 1023 if ((ret = SearchPathW(NULL, command, L".exe", ARRAY_SIZE(fullpath), fullpath, &ptr))) 1024 break; 1025 } 1026 if (!ret) 1027 ret = SearchPathW(NULL, start, L".exe", ARRAY_SIZE(fullpath), fullpath, &ptr); 1028 } 1029 1030 if (!ret) 1031 { 1032 ERR("Unable to find application path for command %s\n", debugstr_w(start)); 1033 return ERROR_ACCESS_DENIED; 1034 } 1035 if (strlenW(ptr) + 1 > ARRAY_SIZE(app)) 1036 { 1037 FIXME("size of found path %s larger than buffer\n", debugstr_w(ptr)); 1038 return 2; 1039 } 1040 wcscpy(app, ptr); 1041 1042 /* Remove extensions (including .so) */ 1043 ptr = app + wcslen(app) - 3; 1044 if (ptr > app && !wcscmp(ptr, L".so")) 1045 *ptr = 0; 1046 1047 ptr = const_cast<LPWSTR>(strrchrW(app, '.')); 1048 assert(ptr); 1049 *ptr = 0; 1050 } 1051 1052 if (strlenW(L"\\topic") + 1 > endkeyLen) 1053 { 1054 FIXME("endkey %s overruns buffer\n", debugstr_w(L"\\topic")); 1055 return 2; 1056 } 1057 wcscpy(endkey, L"\\topic"); 1058 topiclen = sizeof(topic); 1059 if (RegQueryValueW(HKEY_CLASSES_ROOT, regkey, topic, &topiclen) != ERROR_SUCCESS) 1060 { 1061 wcscpy(topic, L"System"); 1062 } 1063 1064 if (unicode) 1065 { 1066 if (DdeInitializeW(&ddeInst, dde_cb, APPCMD_CLIENTONLY, 0L) != DMLERR_NO_ERROR) 1067 return 2; 1068 } 1069 else 1070 { 1071 if (DdeInitializeA(&ddeInst, dde_cb, APPCMD_CLIENTONLY, 0L) != DMLERR_NO_ERROR) 1072 return 2; 1073 } 1074 1075 hszApp = DdeCreateStringHandleW(ddeInst, app, CP_WINUNICODE); 1076 hszTopic = DdeCreateStringHandleW(ddeInst, topic, CP_WINUNICODE); 1077 1078 hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL); 1079 exec = ddeexec; 1080 if (!hConv) 1081 { 1082 TRACE("Launching %s\n", debugstr_w(start)); 1083 ret = execfunc(start, env, TRUE, psei, psei_out); 1084 if (ret <= 32) 1085 { 1086 TRACE("Couldn't launch\n"); 1087 goto error; 1088 } 1089 /* if ddeexec is NULL, then we just need to exit here */ 1090 if (ddeexec == NULL) 1091 { 1092 TRACE("Exiting because ddeexec is NULL. ret=42.\n"); 1093 /* See https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew */ 1094 /* for reason why we use 42 here and also "Shell32_apitest ShellExecuteW" regression test */ 1095 return 42; 1096 } 1097 /* if ddeexec is 'empty string', then we just need to exit here */ 1098 if (wcscmp(ddeexec, L"") == 0) 1099 { 1100 TRACE("Exiting because ddeexec is 'empty string'. ret=42.\n"); 1101 /* See https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew */ 1102 /* for reason why we use 42 here and also "Shell32_apitest ShellExecuteW" regression test */ 1103 return 42; 1104 } 1105 hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL); 1106 if (!hConv) 1107 { 1108 TRACE("Couldn't connect. ret=%d\n", ret); 1109 DdeUninitialize(ddeInst); 1110 SetLastError(ERROR_DDE_FAIL); 1111 return 30; /* whatever */ 1112 } 1113 if (strlenW(L"\\ifexec") + 1 > endkeyLen) 1114 { 1115 FIXME("endkey %s overruns buffer\n", debugstr_w(L"\\ifexec")); 1116 return 2; 1117 } 1118 strcpyW(endkey, L"\\ifexec"); 1119 ifexeclen = sizeof(ifexec); 1120 if (RegQueryValueW(HKEY_CLASSES_ROOT, regkey, ifexec, &ifexeclen) == ERROR_SUCCESS) 1121 { 1122 exec = ifexec; 1123 } 1124 } 1125 1126 SHELL_ArgifyW(static_res, ARRAY_SIZE(static_res), exec, lpFile, pidl, szCommandline, &resultLen, NULL); 1127 if (resultLen > ARRAY_SIZE(static_res)) 1128 { 1129 dynamic_res.Allocate(resultLen); 1130 res = dynamic_res; 1131 SHELL_ArgifyW(dynamic_res, resultLen, exec, lpFile, pidl, szCommandline, NULL, NULL); 1132 } 1133 else 1134 res = static_res; 1135 TRACE("%s %s => %s\n", debugstr_w(exec), debugstr_w(lpFile), debugstr_w(res)); 1136 1137 /* It's documented in the KB 330337 that IE has a bug and returns 1138 * error DMLERR_NOTPROCESSED on XTYP_EXECUTE request. 1139 */ 1140 if (unicode) 1141 hDdeData = DdeClientTransaction((LPBYTE)res, (strlenW(res) + 1) * sizeof(WCHAR), hConv, 0L, 0, XTYP_EXECUTE, 30000, &tid); 1142 else 1143 { 1144 DWORD lenA = WideCharToMultiByte(CP_ACP, 0, res, -1, NULL, 0, NULL, NULL); 1145 CHeapPtr<char, CLocalAllocator> resA; 1146 resA.Allocate(lenA); 1147 WideCharToMultiByte(CP_ACP, 0, res, -1, resA, lenA, NULL, NULL); 1148 hDdeData = DdeClientTransaction( (LPBYTE)(LPSTR)resA, lenA, hConv, 0L, 0, 1149 XTYP_EXECUTE, 10000, &tid ); 1150 } 1151 if (hDdeData) 1152 DdeFreeDataHandle(hDdeData); 1153 else 1154 WARN("DdeClientTransaction failed with error %04x\n", DdeGetLastError(ddeInst)); 1155 ret = 33; 1156 1157 DdeDisconnect(hConv); 1158 1159 error: 1160 DdeUninitialize(ddeInst); 1161 1162 return ret; 1163 } 1164 1165 /************************************************************************* 1166 * execute_from_key [Internal] 1167 */ 1168 static UINT_PTR execute_from_key(LPCWSTR key, LPCWSTR lpFile, WCHAR *env, 1169 LPCWSTR szCommandline, LPCWSTR executable_name, 1170 SHELL_ExecuteW32 execfunc, 1171 LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out) 1172 { 1173 WCHAR cmd[256], param[1024], ddeexec[256]; 1174 DWORD cmdlen = sizeof(cmd), ddeexeclen = sizeof(ddeexec); 1175 UINT_PTR retval = SE_ERR_NOASSOC; 1176 DWORD resultLen; 1177 LPWSTR tmp; 1178 1179 TRACE("%s %s %s %s %s\n", debugstr_w(key), debugstr_w(lpFile), debugstr_w(env), 1180 debugstr_w(szCommandline), debugstr_w(executable_name)); 1181 1182 cmd[0] = '\0'; 1183 param[0] = '\0'; 1184 1185 /* Get the application from the registry */ 1186 if (RegQueryValueW(HKEY_CLASSES_ROOT, key, cmd, (LONG *)&cmdlen) == ERROR_SUCCESS) 1187 { 1188 TRACE("got cmd: %s\n", debugstr_w(cmd)); 1189 1190 /* Is there a replace() function anywhere? */ 1191 cmdlen /= sizeof(WCHAR); 1192 if (cmdlen >= ARRAY_SIZE(cmd)) 1193 cmdlen = ARRAY_SIZE(cmd) - 1; 1194 cmd[cmdlen] = '\0'; 1195 SHELL_ArgifyW(param, ARRAY_SIZE(param), cmd, lpFile, (LPITEMIDLIST)psei->lpIDList, szCommandline, &resultLen, 1196 (psei->lpDirectory && *psei->lpDirectory) ? psei->lpDirectory : NULL); 1197 if (resultLen > ARRAY_SIZE(param)) 1198 ERR("Argify buffer not large enough, truncating\n"); 1199 } 1200 1201 /* Get the parameters needed by the application 1202 from the associated ddeexec key */ 1203 tmp = const_cast<LPWSTR>(strstrW(key, L"command")); 1204 assert(tmp); 1205 wcscpy(tmp, L"ddeexec"); 1206 1207 if (RegQueryValueW(HKEY_CLASSES_ROOT, key, ddeexec, (LONG *)&ddeexeclen) == ERROR_SUCCESS) 1208 { 1209 TRACE("Got ddeexec %s => %s\n", debugstr_w(key), debugstr_w(ddeexec)); 1210 if (!param[0]) strcpyW(param, executable_name); 1211 retval = dde_connect(key, param, ddeexec, lpFile, env, szCommandline, (LPITEMIDLIST)psei->lpIDList, execfunc, psei, psei_out); 1212 } 1213 else if (param[0]) 1214 { 1215 TRACE("executing: %s\n", debugstr_w(param)); 1216 retval = execfunc(param, env, FALSE, psei, psei_out); 1217 } 1218 else 1219 WARN("Nothing appropriate found for %s\n", debugstr_w(key)); 1220 1221 return retval; 1222 } 1223 1224 /************************************************************************* 1225 * FindExecutableA [SHELL32.@] 1226 */ 1227 HINSTANCE WINAPI FindExecutableA(LPCSTR lpFile, LPCSTR lpDirectory, LPSTR lpResult) 1228 { 1229 HINSTANCE retval; 1230 WCHAR *wFile = NULL, *wDirectory = NULL; 1231 WCHAR wResult[MAX_PATH]; 1232 1233 if (lpFile) __SHCloneStrAtoW(&wFile, lpFile); 1234 if (lpDirectory) __SHCloneStrAtoW(&wDirectory, lpDirectory); 1235 1236 retval = FindExecutableW(wFile, wDirectory, wResult); 1237 WideCharToMultiByte(CP_ACP, 0, wResult, -1, lpResult, MAX_PATH, NULL, NULL); 1238 SHFree(wFile); 1239 SHFree(wDirectory); 1240 1241 TRACE("returning %s\n", lpResult); 1242 return retval; 1243 } 1244 1245 /************************************************************************* 1246 * FindExecutableW [SHELL32.@] 1247 * 1248 * This function returns the executable associated with the specified file 1249 * for the default verb. 1250 * 1251 * PARAMS 1252 * lpFile [I] The file to find the association for. This must refer to 1253 * an existing file otherwise FindExecutable fails and returns 1254 * SE_ERR_FNF. 1255 * lpResult [O] Points to a buffer into which the executable path is 1256 * copied. This parameter must not be NULL otherwise 1257 * FindExecutable() segfaults. The buffer must be of size at 1258 * least MAX_PATH characters. 1259 * 1260 * RETURNS 1261 * A value greater than 32 on success, less than or equal to 32 otherwise. 1262 * See the SE_ERR_* constants. 1263 * 1264 * NOTES 1265 * On Windows XP and 2003, FindExecutable() seems to first convert the 1266 * filename into 8.3 format, thus taking into account only the first three 1267 * characters of the extension, and expects to find an association for those. 1268 * However other Windows versions behave sanely. 1269 */ 1270 HINSTANCE WINAPI FindExecutableW(LPCWSTR lpFile, LPCWSTR lpDirectory, LPWSTR lpResult) 1271 { 1272 UINT_PTR retval; 1273 WCHAR old_dir[MAX_PATH], res[MAX_PATH]; 1274 DWORD cch = _countof(res); 1275 LPCWSTR dirs[2]; 1276 1277 TRACE("File %s, Dir %s\n", debugstr_w(lpFile), debugstr_w(lpDirectory)); 1278 1279 *lpResult = UNICODE_NULL; 1280 1281 GetCurrentDirectoryW(_countof(old_dir), old_dir); 1282 1283 if (lpDirectory && *lpDirectory) 1284 { 1285 SetCurrentDirectoryW(lpDirectory); 1286 dirs[0] = lpDirectory; 1287 } 1288 else 1289 { 1290 dirs[0] = old_dir; 1291 } 1292 dirs[1] = NULL; 1293 1294 if (!GetShortPathNameW(lpFile, res, _countof(res))) 1295 StringCchCopyW(res, _countof(res), lpFile); 1296 1297 if (PathResolveW(res, dirs, PRF_TRYPROGRAMEXTENSIONS | PRF_FIRSTDIRDEF)) 1298 { 1299 // NOTE: The last parameter of this AssocQueryStringW call is "strange" in Windows. 1300 if (PathIsExeW(res) || 1301 SUCCEEDED(AssocQueryStringW(ASSOCF_NONE, ASSOCSTR_EXECUTABLE, res, NULL, res, &cch))) 1302 { 1303 StringCchCopyW(lpResult, MAX_PATH, res); 1304 retval = 42; 1305 } 1306 else 1307 { 1308 retval = SE_ERR_NOASSOC; 1309 } 1310 } 1311 else 1312 { 1313 retval = SE_ERR_FNF; 1314 } 1315 1316 TRACE("returning %s\n", debugstr_w(lpResult)); 1317 SetCurrentDirectoryW(old_dir); 1318 return (HINSTANCE)retval; 1319 } 1320 1321 /* FIXME: is this already implemented somewhere else? */ 1322 static HKEY ShellExecute_GetClassKey(const SHELLEXECUTEINFOW *sei) 1323 { 1324 LPCWSTR ext = NULL, lpClass = NULL; 1325 CHeapPtr<WCHAR, CLocalAllocator> cls; 1326 DWORD type = 0, sz = 0; 1327 HKEY hkey = 0; 1328 LONG r; 1329 1330 if (sei->fMask & SEE_MASK_CLASSALL) 1331 return sei->hkeyClass; 1332 1333 if (sei->fMask & SEE_MASK_CLASSNAME) 1334 lpClass = sei->lpClass; 1335 else 1336 { 1337 ext = PathFindExtensionW(sei->lpFile); 1338 TRACE("ext = %s\n", debugstr_w(ext)); 1339 if (!ext) 1340 return hkey; 1341 1342 r = RegOpenKeyW(HKEY_CLASSES_ROOT, ext, &hkey); 1343 if (r != ERROR_SUCCESS) 1344 return hkey; 1345 1346 r = RegQueryValueExW(hkey, NULL, 0, &type, NULL, &sz); 1347 if (r == ERROR_SUCCESS && type == REG_SZ) 1348 { 1349 sz += sizeof (WCHAR); 1350 cls.Allocate(sz / sizeof(WCHAR)); 1351 cls[0] = 0; 1352 RegQueryValueExW(hkey, NULL, 0, &type, (LPBYTE)(LPWSTR)cls, &sz); 1353 } 1354 1355 RegCloseKey( hkey ); 1356 lpClass = cls; 1357 } 1358 1359 TRACE("class = %s\n", debugstr_w(lpClass)); 1360 1361 hkey = 0; 1362 if (lpClass) 1363 RegOpenKeyW( HKEY_CLASSES_ROOT, lpClass, &hkey); 1364 1365 return hkey; 1366 } 1367 1368 static HRESULT shellex_get_dataobj( LPSHELLEXECUTEINFOW sei, CComPtr<IDataObject>& dataObj) 1369 { 1370 CComHeapPtr<ITEMIDLIST> allocatedPidl; 1371 LPITEMIDLIST pidl = NULL; 1372 1373 if (sei->fMask & SEE_MASK_CLASSALL) 1374 { 1375 pidl = (LPITEMIDLIST)sei->lpIDList; 1376 } 1377 else 1378 { 1379 WCHAR fullpath[MAX_PATH]; 1380 BOOL ret; 1381 1382 fullpath[0] = 0; 1383 ret = GetFullPathNameW(sei->lpFile, MAX_PATH, fullpath, NULL); 1384 if (!ret) 1385 return HRESULT_FROM_WIN32(GetLastError()); 1386 1387 pidl = ILCreateFromPathW(fullpath); 1388 allocatedPidl.Attach(pidl); 1389 } 1390 1391 CComPtr<IShellFolder> shf; 1392 LPCITEMIDLIST pidllast = NULL; 1393 HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &shf), &pidllast); 1394 if (FAILED_UNEXPECTEDLY(hr)) 1395 return hr; 1396 1397 return shf->GetUIObjectOf(NULL, 1, &pidllast, IID_NULL_PPV_ARG(IDataObject, &dataObj)); 1398 } 1399 1400 static HRESULT shellex_run_context_menu_default(IShellExtInit *obj, 1401 LPSHELLEXECUTEINFOW sei) 1402 { 1403 CComPtr<IContextMenu> cm = NULL; 1404 CMINVOKECOMMANDINFOEX ici; 1405 MENUITEMINFOW info; 1406 WCHAR string[0x80]; 1407 INT i, n, def = -1; 1408 HMENU hmenu = 0; 1409 HRESULT r; 1410 1411 TRACE("%p %p\n", obj, sei); 1412 1413 r = obj->QueryInterface(IID_PPV_ARG(IContextMenu, &cm)); 1414 if (FAILED(r)) 1415 return r; 1416 1417 hmenu = CreateMenu(); 1418 if (!hmenu) 1419 goto end; 1420 1421 /* the number of the last menu added is returned in r */ 1422 r = cm->QueryContextMenu(hmenu, 0, 0x20, 0x7fff, CMF_DEFAULTONLY); 1423 if (FAILED(r)) 1424 goto end; 1425 1426 n = GetMenuItemCount(hmenu); 1427 for (i = 0; i < n; i++) 1428 { 1429 memset(&info, 0, sizeof(info)); 1430 info.cbSize = sizeof info; 1431 info.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_STATE | MIIM_DATA | MIIM_ID; 1432 info.dwTypeData = string; 1433 info.cch = sizeof string; 1434 string[0] = 0; 1435 GetMenuItemInfoW(hmenu, i, TRUE, &info); 1436 1437 TRACE("menu %d %s %08x %08lx %08x %08x\n", i, debugstr_w(string), 1438 info.fState, info.dwItemData, info.fType, info.wID); 1439 if ((!sei->lpVerb && (info.fState & MFS_DEFAULT)) || 1440 (sei->lpVerb && !lstrcmpiW(sei->lpVerb, string))) 1441 { 1442 def = i; 1443 break; 1444 } 1445 } 1446 1447 r = E_FAIL; 1448 if (def == -1) 1449 goto end; 1450 1451 memset(&ici, 0, sizeof ici); 1452 ici.cbSize = sizeof ici; 1453 ici.fMask = CMIC_MASK_UNICODE | (sei->fMask & (SEE_MASK_NO_CONSOLE | SEE_MASK_NOASYNC | SEE_MASK_ASYNCOK | SEE_MASK_FLAG_NO_UI)); 1454 ici.nShow = sei->nShow; 1455 ici.lpVerb = MAKEINTRESOURCEA(def); 1456 ici.hwnd = sei->hwnd; 1457 ici.lpParametersW = sei->lpParameters; 1458 1459 r = cm->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici); 1460 1461 TRACE("invoke command returned %08x\n", r); 1462 1463 end: 1464 if (hmenu) 1465 DestroyMenu( hmenu ); 1466 return r; 1467 } 1468 1469 static HRESULT shellex_load_object_and_run(HKEY hkey, LPCGUID guid, LPSHELLEXECUTEINFOW sei) 1470 { 1471 TRACE("%p %s %p\n", hkey, debugstr_guid(guid), sei); 1472 1473 CCoInit coInit; 1474 1475 if (FAILED_UNEXPECTEDLY(coInit.hr)) 1476 return coInit.hr; 1477 1478 CComPtr<IShellExtInit> obj; 1479 HRESULT hr = CoCreateInstance(*guid, NULL, CLSCTX_INPROC_SERVER, 1480 IID_PPV_ARG(IShellExtInit, &obj)); 1481 if (FAILED_UNEXPECTEDLY(hr)) 1482 return hr; 1483 1484 CComPtr<IDataObject> dataobj; 1485 hr = shellex_get_dataobj(sei, dataobj); 1486 if (FAILED_UNEXPECTEDLY(hr)) 1487 return hr; 1488 1489 hr = obj->Initialize(NULL, dataobj, hkey); 1490 if (FAILED_UNEXPECTEDLY(hr)) 1491 return hr; 1492 1493 CComPtr<IObjectWithSite> ows; 1494 hr = obj->QueryInterface(IID_PPV_ARG(IObjectWithSite, &ows)); 1495 if (FAILED_UNEXPECTEDLY(hr)) 1496 return hr; 1497 1498 ows->SetSite(NULL); 1499 1500 return shellex_run_context_menu_default(obj, sei); 1501 } 1502 1503 static HRESULT shellex_get_contextmenu(LPSHELLEXECUTEINFOW sei, CComPtr<IContextMenu>& cm) 1504 { 1505 CComHeapPtr<ITEMIDLIST> allocatedPidl; 1506 LPITEMIDLIST pidl = NULL; 1507 1508 if (sei->lpIDList) 1509 { 1510 pidl = (LPITEMIDLIST)sei->lpIDList; 1511 } 1512 else 1513 { 1514 SFGAOF sfga = 0; 1515 HRESULT hr = SHParseDisplayName(sei->lpFile, NULL, &allocatedPidl, SFGAO_STORAGECAPMASK, &sfga); 1516 if (FAILED(hr)) 1517 { 1518 WCHAR Buffer[MAX_PATH] = {}; 1519 // FIXME: MAX_PATH..... 1520 UINT retval = SHELL_FindExecutable(sei->lpDirectory, sei->lpFile, sei->lpVerb, Buffer, _countof(Buffer), NULL, NULL, NULL, sei->lpParameters); 1521 if (retval <= 32) 1522 return HRESULT_FROM_WIN32(retval); 1523 1524 hr = SHParseDisplayName(Buffer, NULL, &allocatedPidl, SFGAO_STORAGECAPMASK, &sfga); 1525 // This should not happen, we found it... 1526 if (FAILED_UNEXPECTEDLY(hr)) 1527 return hr; 1528 } 1529 1530 pidl = allocatedPidl; 1531 } 1532 1533 CComPtr<IShellFolder> shf; 1534 LPCITEMIDLIST pidllast = NULL; 1535 HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &shf), &pidllast); 1536 if (FAILED(hr)) 1537 return hr; 1538 1539 return shf->GetUIObjectOf(NULL, 1, &pidllast, IID_NULL_PPV_ARG(IContextMenu, &cm)); 1540 } 1541 1542 static HRESULT ShellExecute_ContextMenuVerb(LPSHELLEXECUTEINFOW sei) 1543 { 1544 TRACE("%p\n", sei); 1545 1546 CCoInit coInit; 1547 1548 if (FAILED_UNEXPECTEDLY(coInit.hr)) 1549 return coInit.hr; 1550 1551 CComPtr<IContextMenu> cm; 1552 HRESULT hr = shellex_get_contextmenu(sei, cm); 1553 if (FAILED_UNEXPECTEDLY(hr)) 1554 return hr; 1555 1556 CComHeapPtr<char> verb, parameters; 1557 __SHCloneStrWtoA(&verb, sei->lpVerb); 1558 __SHCloneStrWtoA(¶meters, sei->lpParameters); 1559 1560 BOOL fDefault = !sei->lpVerb || !sei->lpVerb[0]; 1561 CMINVOKECOMMANDINFOEX ici = { sizeof(ici) }; 1562 ici.fMask = (sei->fMask & (SEE_MASK_NO_CONSOLE | SEE_MASK_ASYNCOK | SEE_MASK_FLAG_NO_UI)) | CMIC_MASK_UNICODE; 1563 ici.nShow = sei->nShow; 1564 if (!fDefault) 1565 { 1566 ici.lpVerb = verb; 1567 ici.lpVerbW = sei->lpVerb; 1568 } 1569 ici.hwnd = sei->hwnd; 1570 ici.lpParameters = parameters; 1571 ici.lpParametersW = sei->lpParameters; 1572 if ((sei->fMask & (SEE_MASK_HASLINKNAME | SEE_MASK_CLASSNAME)) == SEE_MASK_HASLINKNAME) 1573 { 1574 ici.fMask |= CMIC_MASK_HASLINKNAME; 1575 ici.lpTitleW = sei->lpClass; 1576 } 1577 1578 HMENU hMenu = CreatePopupMenu(); 1579 hr = cm->QueryContextMenu(hMenu, 0, 1, 0x7fff, fDefault ? CMF_DEFAULTONLY : 0); 1580 if (!FAILED_UNEXPECTEDLY(hr)) 1581 { 1582 if (fDefault) 1583 { 1584 INT uDefault = GetMenuDefaultItem(hMenu, FALSE, 0); 1585 uDefault = (uDefault != -1) ? uDefault - 1 : 0; 1586 ici.lpVerb = MAKEINTRESOURCEA(uDefault); 1587 ici.lpVerbW = MAKEINTRESOURCEW(uDefault); 1588 } 1589 1590 hr = cm->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici); 1591 if (!FAILED_UNEXPECTEDLY(hr)) 1592 hr = S_OK; 1593 } 1594 1595 DestroyMenu(hMenu); 1596 1597 return hr; 1598 } 1599 1600 1601 1602 /************************************************************************* 1603 * ShellExecute_FromContextMenu [Internal] 1604 */ 1605 static LONG ShellExecute_FromContextMenuHandlers( LPSHELLEXECUTEINFOW sei ) 1606 { 1607 HKEY hkey, hkeycm = 0; 1608 WCHAR szguid[39]; 1609 HRESULT hr; 1610 GUID guid; 1611 DWORD i; 1612 LONG r; 1613 1614 TRACE("%s\n", debugstr_w(sei->lpFile)); 1615 1616 hkey = ShellExecute_GetClassKey(sei); 1617 if (!hkey) 1618 return ERROR_FUNCTION_FAILED; 1619 1620 r = RegOpenKeyW(hkey, L"shellex\\ContextMenuHandlers", &hkeycm); 1621 if (r == ERROR_SUCCESS) 1622 { 1623 i = 0; 1624 while (1) 1625 { 1626 r = RegEnumKeyW(hkeycm, i++, szguid, ARRAY_SIZE(szguid)); 1627 if (r != ERROR_SUCCESS) 1628 break; 1629 1630 hr = CLSIDFromString(szguid, &guid); 1631 if (SUCCEEDED(hr)) 1632 { 1633 /* stop at the first one that succeeds in running */ 1634 hr = shellex_load_object_and_run(hkey, &guid, sei); 1635 if (SUCCEEDED(hr)) 1636 break; 1637 } 1638 } 1639 RegCloseKey(hkeycm); 1640 } 1641 1642 if (hkey != sei->hkeyClass) 1643 RegCloseKey(hkey); 1644 return r; 1645 } 1646 1647 static UINT_PTR SHELL_quote_and_execute(LPCWSTR wcmd, LPCWSTR wszParameters, LPCWSTR lpstrProtocol, LPCWSTR wszApplicationName, LPWSTR env, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc); 1648 1649 static UINT_PTR SHELL_execute_class(LPCWSTR wszApplicationName, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc) 1650 { 1651 WCHAR execCmd[1024], classname[1024]; 1652 /* launch a document by fileclass like 'WordPad.Document.1' */ 1653 /* the Commandline contains 'c:\Path\wordpad.exe "%1"' */ 1654 /* FIXME: wcmd should not be of a fixed size. Fixed to 1024, MAX_PATH is way too short! */ 1655 ULONG cmask = (psei->fMask & SEE_MASK_CLASSALL); 1656 DWORD resultLen; 1657 BOOL done; 1658 UINT_PTR rslt; 1659 1660 /* FIXME: remove following block when SHELL_quote_and_execute supports hkeyClass parameter */ 1661 if (cmask != SEE_MASK_CLASSNAME) 1662 { 1663 WCHAR wcmd[1024]; 1664 HCR_GetExecuteCommandW((cmask == SEE_MASK_CLASSKEY) ? psei->hkeyClass : NULL, 1665 (cmask == SEE_MASK_CLASSNAME) ? psei->lpClass : NULL, 1666 psei->lpVerb, 1667 execCmd, sizeof(execCmd)); 1668 1669 /* FIXME: get the extension of lpFile, check if it fits to the lpClass */ 1670 TRACE("SEE_MASK_CLASSNAME->%s, doc->%s\n", debugstr_w(execCmd), debugstr_w(wszApplicationName)); 1671 1672 wcmd[0] = '\0'; 1673 done = SHELL_ArgifyW(wcmd, ARRAY_SIZE(wcmd), execCmd, wszApplicationName, (LPITEMIDLIST)psei->lpIDList, psei->lpParameters, 1674 &resultLen, (psei->lpDirectory && *psei->lpDirectory) ? psei->lpDirectory : NULL); 1675 if (!done && wszApplicationName[0]) 1676 { 1677 strcatW(wcmd, L" "); 1678 if (*wszApplicationName != '"') 1679 { 1680 strcatW(wcmd, L"\""); 1681 strcatW(wcmd, wszApplicationName); 1682 strcatW(wcmd, L"\""); 1683 } 1684 else 1685 strcatW(wcmd, wszApplicationName); 1686 } 1687 if (resultLen > ARRAY_SIZE(wcmd)) 1688 ERR("Argify buffer not large enough... truncating\n"); 1689 return execfunc(wcmd, NULL, FALSE, psei, psei_out); 1690 } 1691 1692 strcpyW(classname, psei->lpClass); 1693 rslt = SHELL_FindExecutableByVerb(psei->lpVerb, NULL, classname, execCmd, sizeof(execCmd)); 1694 1695 TRACE("SHELL_FindExecutableByVerb returned %u (%s, %s)\n", (unsigned int)rslt, debugstr_w(classname), debugstr_w(execCmd)); 1696 if (33 > rslt) 1697 return rslt; 1698 rslt = SHELL_quote_and_execute( execCmd, L"", classname, 1699 wszApplicationName, NULL, psei, 1700 psei_out, execfunc ); 1701 return rslt; 1702 1703 } 1704 1705 static BOOL SHELL_translate_idlist(LPSHELLEXECUTEINFOW sei, LPWSTR wszParameters, DWORD parametersLen, LPWSTR wszApplicationName, DWORD dwApplicationNameLen) 1706 { 1707 WCHAR buffer[MAX_PATH]; 1708 BOOL appKnownSingular = FALSE; 1709 1710 /* last chance to translate IDList: now also allow CLSID paths */ 1711 if (SUCCEEDED(SHELL_GetPathFromIDListForExecuteW((LPCITEMIDLIST)sei->lpIDList, buffer, ARRAY_SIZE(buffer)))) { 1712 if (buffer[0] == ':' && buffer[1] == ':') { 1713 /* open shell folder for the specified class GUID */ 1714 if (strlenW(buffer) + 1 > parametersLen) 1715 ERR("parameters len exceeds buffer size (%i > %i), truncating\n", 1716 lstrlenW(buffer) + 1, parametersLen); 1717 lstrcpynW(wszParameters, buffer, parametersLen); 1718 if (strlenW(L"explorer.exe") > dwApplicationNameLen) 1719 ERR("application len exceeds buffer size (%i), truncating\n", 1720 dwApplicationNameLen); 1721 lstrcpynW(wszApplicationName, L"explorer.exe", dwApplicationNameLen); 1722 appKnownSingular = TRUE; 1723 1724 sei->fMask &= ~SEE_MASK_INVOKEIDLIST; 1725 } else { 1726 WCHAR target[MAX_PATH]; 1727 DWORD attribs; 1728 DWORD resultLen; 1729 /* Check if we're executing a directory and if so use the 1730 handler for the Folder class */ 1731 strcpyW(target, buffer); 1732 attribs = GetFileAttributesW(buffer); 1733 if (attribs != INVALID_FILE_ATTRIBUTES && 1734 (attribs & FILE_ATTRIBUTE_DIRECTORY) && 1735 HCR_GetExecuteCommandW(0, L"Folder", 1736 sei->lpVerb, 1737 buffer, sizeof(buffer))) { 1738 SHELL_ArgifyW(wszApplicationName, dwApplicationNameLen, 1739 buffer, target, (LPITEMIDLIST)sei->lpIDList, NULL, &resultLen, 1740 (sei->lpDirectory && *sei->lpDirectory) ? sei->lpDirectory : NULL); 1741 if (resultLen > dwApplicationNameLen) 1742 ERR("Argify buffer not large enough... truncating\n"); 1743 appKnownSingular = FALSE; 1744 } 1745 sei->fMask &= ~SEE_MASK_INVOKEIDLIST; 1746 } 1747 } 1748 return appKnownSingular; 1749 } 1750 1751 static BOOL 1752 SHELL_InvokePidl( 1753 _In_ LPSHELLEXECUTEINFOW sei, 1754 _In_ LPCITEMIDLIST pidl) 1755 { 1756 // Bind pidl 1757 CComPtr<IShellFolder> psfFolder; 1758 LPCITEMIDLIST pidlLast; 1759 HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &psfFolder), &pidlLast); 1760 if (FAILED_UNEXPECTEDLY(hr)) 1761 return FALSE; 1762 1763 // Get the context menu to invoke a command 1764 CComPtr<IContextMenu> pCM; 1765 hr = psfFolder->GetUIObjectOf(NULL, 1, &pidlLast, IID_NULL_PPV_ARG(IContextMenu, &pCM)); 1766 if (FAILED_UNEXPECTEDLY(hr)) 1767 return FALSE; 1768 1769 // Invoke a command 1770 CMINVOKECOMMANDINFO ici = { sizeof(ici) }; 1771 ici.fMask = (sei->fMask & (SEE_MASK_NO_CONSOLE | SEE_MASK_ASYNCOK | SEE_MASK_FLAG_NO_UI)); 1772 ici.nShow = sei->nShow; 1773 ici.hwnd = sei->hwnd; 1774 char szVerb[VERBKEY_CCHMAX]; 1775 if (sei->lpVerb && sei->lpVerb[0]) 1776 { 1777 WideCharToMultiByte(CP_ACP, 0, sei->lpVerb, -1, szVerb, _countof(szVerb), NULL, NULL); 1778 szVerb[_countof(szVerb) - 1] = ANSI_NULL; // Avoid buffer overrun 1779 ici.lpVerb = szVerb; 1780 } 1781 else // The default verb? 1782 { 1783 HMENU hMenu = CreatePopupMenu(); 1784 const INT idCmdFirst = 1, idCmdLast = 0x7FFF; 1785 hr = pCM->QueryContextMenu(hMenu, 0, idCmdFirst, idCmdLast, CMF_DEFAULTONLY); 1786 if (FAILED_UNEXPECTEDLY(hr)) 1787 { 1788 DestroyMenu(hMenu); 1789 return FALSE; 1790 } 1791 1792 INT nDefaultID = GetMenuDefaultItem(hMenu, FALSE, 0); 1793 DestroyMenu(hMenu); 1794 if (nDefaultID == -1) 1795 nDefaultID = idCmdFirst; 1796 1797 ici.lpVerb = MAKEINTRESOURCEA(nDefaultID - idCmdFirst); 1798 } 1799 hr = pCM->InvokeCommand(&ici); 1800 1801 return !FAILED_UNEXPECTEDLY(hr); 1802 } 1803 1804 static UINT_PTR SHELL_quote_and_execute(LPCWSTR wcmd, LPCWSTR wszParameters, LPCWSTR wszKeyname, LPCWSTR wszApplicationName, LPWSTR env, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc) 1805 { 1806 UINT_PTR retval; 1807 DWORD len; 1808 CHeapPtr<WCHAR, CLocalAllocator> wszQuotedCmd; 1809 1810 /* Length of quotes plus length of command plus NULL terminator */ 1811 len = 2 + lstrlenW(wcmd) + 1; 1812 if (wszParameters[0]) 1813 { 1814 /* Length of space plus length of parameters */ 1815 len += 1 + lstrlenW(wszParameters); 1816 } 1817 wszQuotedCmd.Allocate(len); 1818 /* Must quote to handle case where cmd contains spaces, 1819 * else security hole if malicious user creates executable file "C:\\Program" 1820 */ 1821 strcpyW(wszQuotedCmd, L"\""); 1822 strcatW(wszQuotedCmd, wcmd); 1823 strcatW(wszQuotedCmd, L"\""); 1824 if (wszParameters[0]) 1825 { 1826 strcatW(wszQuotedCmd, L" "); 1827 strcatW(wszQuotedCmd, wszParameters); 1828 } 1829 1830 TRACE("%s/%s => %s/%s\n", debugstr_w(wszApplicationName), debugstr_w(psei->lpVerb), debugstr_w(wszQuotedCmd), debugstr_w(wszKeyname)); 1831 1832 if (*wszKeyname) 1833 retval = execute_from_key(wszKeyname, wszApplicationName, env, psei->lpParameters, wcmd, execfunc, psei, psei_out); 1834 else 1835 retval = execfunc(wszQuotedCmd, env, FALSE, psei, psei_out); 1836 1837 return retval; 1838 } 1839 1840 static UINT_PTR SHELL_execute_url(LPCWSTR lpFile, LPCWSTR wcmd, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc) 1841 { 1842 UINT_PTR retval; 1843 CHeapPtr<WCHAR, CLocalAllocator> lpstrProtocol; 1844 LPCWSTR lpstrRes; 1845 INT iSize; 1846 DWORD len; 1847 1848 lpstrRes = strchrW(lpFile, ':'); 1849 if (lpstrRes) 1850 iSize = lpstrRes - lpFile; 1851 else 1852 iSize = strlenW(lpFile); 1853 1854 TRACE("Got URL: %s\n", debugstr_w(lpFile)); 1855 /* Looking for ...<protocol>\shell\<lpVerb>\command */ 1856 len = iSize + lstrlenW(L"\\shell\\") + lstrlenW(L"\\command") + 1; 1857 if (psei->lpVerb && *psei->lpVerb) 1858 len += lstrlenW(psei->lpVerb); 1859 else 1860 len += lstrlenW(L"open"); 1861 lpstrProtocol.Allocate(len); 1862 memcpy(lpstrProtocol, lpFile, iSize * sizeof(WCHAR)); 1863 lpstrProtocol[iSize] = '\0'; 1864 strcatW(lpstrProtocol, L"\\shell\\"); 1865 strcatW(lpstrProtocol, psei->lpVerb && *psei->lpVerb ? psei->lpVerb : L"open"); 1866 strcatW(lpstrProtocol, L"\\command"); 1867 1868 retval = execute_from_key(lpstrProtocol, lpFile, NULL, psei->lpParameters, 1869 wcmd, execfunc, psei, psei_out); 1870 1871 return retval; 1872 } 1873 1874 static void do_error_dialog(UINT_PTR retval, HWND hwnd, WCHAR* filename) 1875 { 1876 WCHAR msg[2048]; 1877 DWORD_PTR msgArguments[3] = { (DWORD_PTR)filename, 0, 0 }; 1878 DWORD error_code; 1879 1880 error_code = GetLastError(); 1881 if (retval == SE_ERR_NOASSOC) 1882 LoadStringW(shell32_hInstance, IDS_SHLEXEC_NOASSOC, msg, ARRAY_SIZE(msg)); 1883 else 1884 FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY, 1885 NULL, 1886 error_code, 1887 LANG_USER_DEFAULT, 1888 msg, 1889 ARRAY_SIZE(msg), 1890 (va_list*)msgArguments); 1891 1892 MessageBoxW(hwnd, msg, NULL, MB_ICONERROR); 1893 } 1894 1895 static WCHAR *expand_environment( const WCHAR *str ) 1896 { 1897 CHeapPtr<WCHAR, CLocalAllocator> buf; 1898 DWORD len; 1899 1900 len = ExpandEnvironmentStringsW(str, NULL, 0); 1901 if (!len) return NULL; 1902 1903 if (!buf.Allocate(len)) 1904 return NULL; 1905 1906 len = ExpandEnvironmentStringsW(str, buf, len); 1907 if (!len) 1908 return NULL; 1909 1910 return buf.Detach(); 1911 } 1912 1913 /************************************************************************* 1914 * SHELL_execute [Internal] 1915 */ 1916 static BOOL SHELL_execute(LPSHELLEXECUTEINFOW sei, SHELL_ExecuteW32 execfunc) 1917 { 1918 static const DWORD unsupportedFlags = 1919 SEE_MASK_ICON | SEE_MASK_HOTKEY | 1920 SEE_MASK_CONNECTNETDRV | SEE_MASK_FLAG_DDEWAIT | 1921 SEE_MASK_ASYNCOK | SEE_MASK_HMONITOR; 1922 1923 DWORD len; 1924 UINT_PTR retval = SE_ERR_NOASSOC; 1925 BOOL appKnownSingular = FALSE; 1926 1927 /* make a local copy of the LPSHELLEXECUTEINFO structure and work with this from now on */ 1928 SHELLEXECUTEINFOW sei_tmp = *sei; 1929 1930 TRACE("mask=0x%08x hwnd=%p verb=%s file=%s parm=%s dir=%s show=0x%08x class=%s\n", 1931 sei_tmp.fMask, sei_tmp.hwnd, debugstr_w(sei_tmp.lpVerb), 1932 debugstr_w(sei_tmp.lpFile), debugstr_w(sei_tmp.lpParameters), 1933 debugstr_w(sei_tmp.lpDirectory), sei_tmp.nShow, 1934 ((sei_tmp.fMask & SEE_MASK_CLASSALL) == SEE_MASK_CLASSNAME) ? 1935 debugstr_w(sei_tmp.lpClass) : "not used"); 1936 1937 sei->hProcess = NULL; 1938 1939 /* make copies of all path/command strings */ 1940 CHeapPtr<WCHAR, CLocalAllocator> wszApplicationName; 1941 DWORD dwApplicationNameLen = MAX_PATH + 2; 1942 if (!sei_tmp.lpFile) 1943 { 1944 wszApplicationName.Allocate(dwApplicationNameLen); 1945 *wszApplicationName = '\0'; 1946 } 1947 else if (*sei_tmp.lpFile == '\"' && sei_tmp.lpFile[(len = strlenW(sei_tmp.lpFile))-1] == '\"') 1948 { 1949 if(len-1 >= dwApplicationNameLen) 1950 dwApplicationNameLen = len; 1951 1952 wszApplicationName.Allocate(dwApplicationNameLen); 1953 memcpy(wszApplicationName, sei_tmp.lpFile + 1, len * sizeof(WCHAR)); 1954 1955 if(len > 2) 1956 wszApplicationName[len-2] = '\0'; 1957 appKnownSingular = TRUE; 1958 1959 TRACE("wszApplicationName=%s\n", debugstr_w(wszApplicationName)); 1960 } 1961 else 1962 { 1963 DWORD l = strlenW(sei_tmp.lpFile) + 1; 1964 if(l > dwApplicationNameLen) dwApplicationNameLen = l + 1; 1965 wszApplicationName.Allocate(dwApplicationNameLen); 1966 memcpy(wszApplicationName, sei_tmp.lpFile, l * sizeof(WCHAR)); 1967 1968 if (wszApplicationName[2] == 0 && wszApplicationName[1] == L':' && 1969 ((L'A' <= wszApplicationName[0] && wszApplicationName[0] <= L'Z') || 1970 (L'a' <= wszApplicationName[0] && wszApplicationName[0] <= L'z'))) 1971 { 1972 // 'C:' --> 'C:\' 1973 PathAddBackslashW(wszApplicationName); 1974 } 1975 } 1976 1977 WCHAR parametersBuffer[1024]; 1978 LPWSTR wszParameters = parametersBuffer; 1979 CHeapPtr<WCHAR, CLocalAllocator> wszParamAlloc; 1980 DWORD parametersLen = _countof(parametersBuffer); 1981 1982 if (sei_tmp.lpParameters) 1983 { 1984 len = lstrlenW(sei_tmp.lpParameters) + 1; 1985 if (len > parametersLen) 1986 { 1987 wszParamAlloc.Allocate(len); 1988 wszParameters = wszParamAlloc; 1989 parametersLen = len; 1990 } 1991 strcpyW(wszParameters, sei_tmp.lpParameters); 1992 } 1993 else 1994 *wszParameters = L'\0'; 1995 1996 // Get the working directory 1997 WCHAR dirBuffer[MAX_PATH]; 1998 LPWSTR wszDir = dirBuffer; 1999 wszDir[0] = UNICODE_NULL; 2000 CHeapPtr<WCHAR, CLocalAllocator> wszDirAlloc; 2001 if (sei_tmp.lpDirectory && *sei_tmp.lpDirectory) 2002 { 2003 if (sei_tmp.fMask & SEE_MASK_DOENVSUBST) 2004 { 2005 LPWSTR tmp = expand_environment(sei_tmp.lpDirectory); 2006 if (tmp) 2007 { 2008 wszDirAlloc.Attach(tmp); 2009 wszDir = wszDirAlloc; 2010 } 2011 } 2012 else 2013 { 2014 __SHCloneStrW(&wszDirAlloc, sei_tmp.lpDirectory); 2015 if (wszDirAlloc) 2016 wszDir = wszDirAlloc; 2017 } 2018 } 2019 if (!wszDir[0]) 2020 { 2021 ::GetCurrentDirectoryW(_countof(dirBuffer), dirBuffer); 2022 wszDir = dirBuffer; 2023 } 2024 // NOTE: ShellExecute should accept the invalid working directory for historical reason. 2025 if (!PathIsDirectoryW(wszDir)) 2026 { 2027 INT iDrive = PathGetDriveNumberW(wszDir); 2028 if (iDrive >= 0) 2029 { 2030 PathStripToRootW(wszDir); 2031 if (!PathIsDirectoryW(wszDir)) 2032 { 2033 ::GetWindowsDirectoryW(dirBuffer, _countof(dirBuffer)); 2034 wszDir = dirBuffer; 2035 } 2036 } 2037 } 2038 2039 /* adjust string pointers to point to the new buffers */ 2040 sei_tmp.lpFile = wszApplicationName; 2041 sei_tmp.lpParameters = wszParameters; 2042 sei_tmp.lpDirectory = wszDir; 2043 2044 if (sei_tmp.fMask & unsupportedFlags) 2045 { 2046 FIXME("flags ignored: 0x%08x\n", sei_tmp.fMask & unsupportedFlags); 2047 } 2048 2049 /* process the IDList */ 2050 if (sei_tmp.fMask & SEE_MASK_IDLIST && 2051 (sei_tmp.fMask & SEE_MASK_INVOKEIDLIST) != SEE_MASK_INVOKEIDLIST) 2052 { 2053 CComPtr<IShellExecuteHookW> pSEH; 2054 2055 HRESULT hr = SHBindToParent((LPCITEMIDLIST)sei_tmp.lpIDList, IID_PPV_ARG(IShellExecuteHookW, &pSEH), NULL); 2056 2057 if (SUCCEEDED(hr)) 2058 { 2059 hr = pSEH->Execute(&sei_tmp); 2060 if (hr == S_OK) 2061 return TRUE; 2062 } 2063 2064 SHGetPathFromIDListW((LPCITEMIDLIST)sei_tmp.lpIDList, wszApplicationName); 2065 appKnownSingular = TRUE; 2066 TRACE("-- idlist=%p (%s)\n", sei_tmp.lpIDList, debugstr_w(wszApplicationName)); 2067 } 2068 2069 if (sei_tmp.fMask & SEE_MASK_DOENVSUBST) 2070 { 2071 WCHAR *tmp = expand_environment(sei_tmp.lpFile); 2072 if (tmp) 2073 { 2074 wszApplicationName.Attach(tmp); 2075 sei_tmp.lpFile = wszApplicationName; 2076 } 2077 2078 tmp = expand_environment(sei_tmp.lpDirectory); 2079 if (tmp) 2080 { 2081 wszDirAlloc.Attach(tmp); 2082 sei_tmp.lpDirectory = wszDir = wszDirAlloc; 2083 } 2084 } 2085 2086 if ((sei_tmp.fMask & SEE_MASK_INVOKEIDLIST) == SEE_MASK_INVOKEIDLIST) 2087 { 2088 HRESULT hr = ShellExecute_ContextMenuVerb(&sei_tmp); 2089 if (SUCCEEDED(hr)) 2090 { 2091 sei->hInstApp = (HINSTANCE)42; 2092 return TRUE; 2093 } 2094 } 2095 2096 2097 if (ERROR_SUCCESS == ShellExecute_FromContextMenuHandlers(&sei_tmp)) 2098 { 2099 sei->hInstApp = (HINSTANCE) 33; 2100 return TRUE; 2101 } 2102 2103 if (sei_tmp.fMask & SEE_MASK_CLASSALL) 2104 { 2105 retval = SHELL_execute_class(wszApplicationName, &sei_tmp, sei, execfunc); 2106 if (retval <= 32 && !(sei_tmp.fMask & SEE_MASK_FLAG_NO_UI)) 2107 { 2108 OPENASINFO Info; 2109 2110 //FIXME 2111 // need full path 2112 2113 Info.pcszFile = wszApplicationName; 2114 Info.pcszClass = NULL; 2115 Info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_EXEC; 2116 2117 //if (SHOpenWithDialog(sei_tmp.hwnd, &Info) != S_OK) 2118 DBG_UNREFERENCED_LOCAL_VARIABLE(Info); 2119 do_error_dialog(retval, sei_tmp.hwnd, wszApplicationName); 2120 } 2121 return retval > 32; 2122 } 2123 2124 if (!(sei_tmp.fMask & SEE_MASK_IDLIST) && // Not an ID List 2125 (StrCmpNIW(sei_tmp.lpFile, L"shell:", 6) == 0 || 2126 StrCmpNW(sei_tmp.lpFile, L"::{", 3) == 0)) 2127 { 2128 CComHeapPtr<ITEMIDLIST> pidlParsed; 2129 HRESULT hr = SHParseDisplayName(sei_tmp.lpFile, NULL, &pidlParsed, 0, NULL); 2130 if (SUCCEEDED(hr) && SHELL_InvokePidl(&sei_tmp, pidlParsed)) 2131 { 2132 sei_tmp.hInstApp = (HINSTANCE)UlongToHandle(42); 2133 return TRUE; 2134 } 2135 } 2136 2137 /* Has the IDList not yet been translated? */ 2138 if (sei_tmp.fMask & SEE_MASK_IDLIST) 2139 { 2140 appKnownSingular = SHELL_translate_idlist( &sei_tmp, wszParameters, 2141 parametersLen, 2142 wszApplicationName, 2143 dwApplicationNameLen ); 2144 } 2145 2146 /* convert file URLs */ 2147 if (UrlIsFileUrlW(sei_tmp.lpFile)) 2148 { 2149 CHeapPtr<WCHAR, CLocalAllocator> buf; 2150 DWORD size = MAX_PATH; 2151 if (!buf.Allocate(size) || FAILED(PathCreateFromUrlW(sei_tmp.lpFile, buf, &size, 0))) 2152 return SE_ERR_OOM; 2153 2154 wszApplicationName.Attach(buf.Detach()); 2155 sei_tmp.lpFile = wszApplicationName; 2156 } 2157 else /* or expand environment strings (not both!) */ 2158 { 2159 LPWSTR tmp = expand_environment(sei_tmp.lpFile); 2160 if (tmp) 2161 { 2162 wszApplicationName.Attach(tmp); 2163 /* appKnownSingular unmodified */ 2164 sei_tmp.lpFile = wszApplicationName; 2165 } 2166 } 2167 2168 /* Else, try to execute the filename */ 2169 TRACE("execute: %s,%s,%s\n", debugstr_w(wszApplicationName), debugstr_w(wszParameters), debugstr_w(wszDir)); 2170 2171 /* separate out command line arguments from executable file name */ 2172 LPCWSTR lpFile; 2173 WCHAR wfileName[MAX_PATH]; 2174 if (!*sei_tmp.lpParameters && !appKnownSingular) 2175 { 2176 /* If the executable path is quoted, handle the rest of the command line as parameters. */ 2177 if (sei_tmp.lpFile[0] == L'"') 2178 { 2179 LPWSTR src = wszApplicationName/*sei_tmp.lpFile*/ + 1; 2180 LPWSTR dst = wfileName; 2181 LPWSTR end; 2182 2183 /* copy the unquoted executable path to 'wfileName' */ 2184 while(*src && *src != L'"') 2185 *dst++ = *src++; 2186 2187 *dst = L'\0'; 2188 2189 if (*src == L'"') 2190 { 2191 end = ++src; 2192 2193 while(isspaceW(*src)) 2194 ++src; 2195 } 2196 else 2197 end = src; 2198 2199 /* copy the parameter string to 'wszParameters' */ 2200 strcpyW(wszParameters, src); 2201 2202 /* terminate previous command string after the quote character */ 2203 *end = L'\0'; 2204 lpFile = wfileName; 2205 } 2206 /* We have to test sei instead of sei_tmp because sei_tmp had its 2207 * input fMask modified above in SHELL_translate_idlist. 2208 * This code is needed to handle the case where we only have an 2209 * lpIDList with multiple CLSID/PIDL's (not 'My Computer' only) */ 2210 else if ((sei->fMask & SEE_MASK_IDLIST) == SEE_MASK_IDLIST) 2211 { 2212 WCHAR buffer[MAX_PATH], xlpFile[MAX_PATH]; 2213 LPWSTR space, s; 2214 2215 LPWSTR beg = wszApplicationName; 2216 for(s = beg; (space = const_cast<LPWSTR>(strchrW(s, L' '))); s = space + 1) 2217 { 2218 int idx = space - sei_tmp.lpFile; 2219 memcpy(buffer, sei_tmp.lpFile, idx * sizeof(WCHAR)); 2220 buffer[idx] = '\0'; 2221 2222 if (SearchPathW(*sei_tmp.lpDirectory ? sei_tmp.lpDirectory : NULL, 2223 buffer, L".exe", _countof(xlpFile), xlpFile, NULL)) 2224 { 2225 /* separate out command from parameter string */ 2226 LPCWSTR p = space + 1; 2227 2228 while(isspaceW(*p)) 2229 ++p; 2230 2231 strcpyW(wszParameters, p); 2232 *space = L'\0'; 2233 2234 break; 2235 } 2236 } 2237 lpFile = sei_tmp.lpFile; 2238 } 2239 else 2240 { 2241 lpFile = sei_tmp.lpFile; 2242 } 2243 } 2244 else 2245 lpFile = sei_tmp.lpFile; 2246 2247 WCHAR wcmdBuffer[1024]; 2248 LPWSTR wcmd = wcmdBuffer; 2249 DWORD wcmdLen = _countof(wcmdBuffer); 2250 CHeapPtr<WCHAR, CLocalAllocator> wcmdAlloc; 2251 2252 /* Only execute if it has an executable extension */ 2253 if (PathIsExeW(lpFile)) 2254 { 2255 len = lstrlenW(wszApplicationName) + 3; 2256 if (sei_tmp.lpParameters[0]) 2257 len += 1 + lstrlenW(wszParameters); 2258 if (len > wcmdLen) 2259 { 2260 wcmdAlloc.Allocate(len); 2261 wcmd = wcmdAlloc; 2262 wcmdLen = len; 2263 } 2264 swprintf(wcmd, L"\"%s\"", (LPWSTR)wszApplicationName); 2265 if (sei_tmp.lpParameters[0]) 2266 { 2267 strcatW(wcmd, L" "); 2268 strcatW(wcmd, wszParameters); 2269 } 2270 2271 retval = execfunc(wcmd, NULL, FALSE, &sei_tmp, sei); 2272 if (retval > 32) 2273 return TRUE; 2274 } 2275 2276 /* Else, try to find the executable */ 2277 WCHAR wszKeyname[256]; 2278 CHeapPtr<WCHAR, CLocalAllocator> env; 2279 wcmd[0] = UNICODE_NULL; 2280 retval = SHELL_FindExecutable(sei_tmp.lpDirectory, lpFile, sei_tmp.lpVerb, wcmd, wcmdLen, wszKeyname, &env, (LPITEMIDLIST)sei_tmp.lpIDList, sei_tmp.lpParameters); 2281 if (retval > 32) /* Found */ 2282 { 2283 retval = SHELL_quote_and_execute(wcmd, wszParameters, wszKeyname, 2284 wszApplicationName, env, &sei_tmp, 2285 sei, execfunc); 2286 } 2287 else if (PathIsDirectoryW(lpFile)) 2288 { 2289 WCHAR wExec[MAX_PATH]; 2290 CHeapPtr<WCHAR, CLocalAllocator> lpQuotedFile; 2291 if (lpQuotedFile.Allocate(strlenW(lpFile) + 3)) 2292 { 2293 retval = SHELL_FindExecutable(sei_tmp.lpDirectory, L"explorer", 2294 L"open", wExec, MAX_PATH, 2295 NULL, &env, NULL, NULL); 2296 if (retval > 32) 2297 { 2298 swprintf(lpQuotedFile, L"\"%s\"", lpFile); 2299 retval = SHELL_quote_and_execute(wExec, lpQuotedFile, 2300 wszKeyname, 2301 wszApplicationName, env, 2302 &sei_tmp, sei, execfunc); 2303 } 2304 } 2305 else 2306 retval = 0; /* Out of memory */ 2307 } 2308 else if (PathIsURLW(lpFile)) /* File not found, check for URL */ 2309 { 2310 retval = SHELL_execute_url(lpFile, wcmd, &sei_tmp, sei, execfunc ); 2311 } 2312 /* Check if file specified is in the form www.??????.*** */ 2313 else if (!strncmpiW(lpFile, L"www", 3)) 2314 { 2315 /* if so, prefix lpFile with http:// and call ShellExecute */ 2316 WCHAR lpstrTmpFile[256]; 2317 strcpyW(lpstrTmpFile, L"http://"); 2318 strcatW(lpstrTmpFile, lpFile); 2319 retval = (UINT_PTR)ShellExecuteW(sei_tmp.hwnd, sei_tmp.lpVerb, lpstrTmpFile, NULL, NULL, 0); 2320 } 2321 2322 TRACE("retval %lu\n", retval); 2323 2324 if (retval <= 32 && !(sei_tmp.fMask & SEE_MASK_FLAG_NO_UI)) 2325 { 2326 OPENASINFO Info; 2327 2328 //FIXME 2329 // need full path 2330 2331 Info.pcszFile = wszApplicationName; 2332 Info.pcszClass = NULL; 2333 Info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_EXEC; 2334 2335 //if (SHOpenWithDialog(sei_tmp.hwnd, &Info) != S_OK) 2336 DBG_UNREFERENCED_LOCAL_VARIABLE(Info); 2337 do_error_dialog(retval, sei_tmp.hwnd, wszApplicationName); 2338 } 2339 2340 sei->hInstApp = (HINSTANCE)(retval > 32 ? 33 : retval); 2341 2342 return retval > 32; 2343 } 2344 2345 /************************************************************************* 2346 * ShellExecuteA [SHELL32.290] 2347 */ 2348 HINSTANCE WINAPI ShellExecuteA(HWND hWnd, LPCSTR lpVerb, LPCSTR lpFile, 2349 LPCSTR lpParameters, LPCSTR lpDirectory, INT iShowCmd) 2350 { 2351 SHELLEXECUTEINFOA sei; 2352 2353 TRACE("%p,%s,%s,%s,%s,%d\n", 2354 hWnd, debugstr_a(lpVerb), debugstr_a(lpFile), 2355 debugstr_a(lpParameters), debugstr_a(lpDirectory), iShowCmd); 2356 2357 sei.cbSize = sizeof(sei); 2358 sei.fMask = SEE_MASK_FLAG_NO_UI; 2359 sei.hwnd = hWnd; 2360 sei.lpVerb = lpVerb; 2361 sei.lpFile = lpFile; 2362 sei.lpParameters = lpParameters; 2363 sei.lpDirectory = lpDirectory; 2364 sei.nShow = iShowCmd; 2365 sei.lpIDList = 0; 2366 sei.lpClass = 0; 2367 sei.hkeyClass = 0; 2368 sei.dwHotKey = 0; 2369 sei.hProcess = 0; 2370 2371 ShellExecuteExA(&sei); 2372 return sei.hInstApp; 2373 } 2374 2375 static DWORD 2376 ShellExecute_Normal(_Inout_ LPSHELLEXECUTEINFOW sei) 2377 { 2378 // FIXME 2379 return SHELL_execute(sei, SHELL_ExecuteW) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND; 2380 } 2381 2382 static VOID 2383 ShellExecute_ShowError( 2384 _In_ const SHELLEXECUTEINFOW *ExecInfo, 2385 _In_opt_ LPCWSTR pszCaption, 2386 _In_ DWORD dwError) 2387 { 2388 // FIXME: Show error message 2389 } 2390 2391 /************************************************************************* 2392 * ShellExecuteExA [SHELL32.292] 2393 */ 2394 BOOL 2395 WINAPI 2396 DECLSPEC_HOTPATCH 2397 ShellExecuteExA(LPSHELLEXECUTEINFOA sei) 2398 { 2399 SHELLEXECUTEINFOW seiW; 2400 BOOL ret; 2401 WCHAR *wVerb = NULL, *wFile = NULL, *wParameters = NULL, *wDirectory = NULL, *wClass = NULL; 2402 2403 TRACE("%p\n", sei); 2404 2405 if (sei->cbSize != sizeof(SHELLEXECUTEINFOA)) 2406 { 2407 sei->hInstApp = (HINSTANCE)ERROR_ACCESS_DENIED; 2408 SetLastError(ERROR_ACCESS_DENIED); 2409 return FALSE; 2410 } 2411 2412 memcpy(&seiW, sei, sizeof(SHELLEXECUTEINFOW)); 2413 2414 seiW.cbSize = sizeof(SHELLEXECUTEINFOW); 2415 2416 if (sei->lpVerb) 2417 seiW.lpVerb = __SHCloneStrAtoW(&wVerb, sei->lpVerb); 2418 2419 if (sei->lpFile) 2420 seiW.lpFile = __SHCloneStrAtoW(&wFile, sei->lpFile); 2421 2422 if (sei->lpParameters) 2423 seiW.lpParameters = __SHCloneStrAtoW(&wParameters, sei->lpParameters); 2424 2425 if (sei->lpDirectory) 2426 seiW.lpDirectory = __SHCloneStrAtoW(&wDirectory, sei->lpDirectory); 2427 2428 if ((sei->fMask & SEE_MASK_CLASSALL) == SEE_MASK_CLASSNAME && sei->lpClass) 2429 seiW.lpClass = __SHCloneStrAtoW(&wClass, sei->lpClass); 2430 else 2431 seiW.lpClass = NULL; 2432 2433 ret = ShellExecuteExW(&seiW); 2434 2435 sei->hInstApp = seiW.hInstApp; 2436 2437 if (sei->fMask & SEE_MASK_NOCLOSEPROCESS) 2438 sei->hProcess = seiW.hProcess; 2439 2440 SHFree(wVerb); 2441 SHFree(wFile); 2442 SHFree(wParameters); 2443 SHFree(wDirectory); 2444 SHFree(wClass); 2445 2446 return ret; 2447 } 2448 2449 /************************************************************************* 2450 * ShellExecuteExW [SHELL32.293] 2451 */ 2452 BOOL 2453 WINAPI 2454 DECLSPEC_HOTPATCH 2455 ShellExecuteExW(LPSHELLEXECUTEINFOW sei) 2456 { 2457 HRESULT hrCoInit; 2458 DWORD dwError; 2459 ULONG fOldMask; 2460 2461 if (sei->cbSize != sizeof(SHELLEXECUTEINFOW)) 2462 { 2463 sei->hInstApp = (HINSTANCE)UlongToHandle(SE_ERR_ACCESSDENIED); 2464 SetLastError(ERROR_ACCESS_DENIED); 2465 return FALSE; 2466 } 2467 2468 hrCoInit = SHCoInitializeAnyApartment(); 2469 2470 if (SHRegGetBoolUSValueW(L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer", 2471 L"MaximizeApps", FALSE, FALSE)) 2472 { 2473 switch (sei->nShow) 2474 { 2475 case SW_SHOW: 2476 case SW_SHOWDEFAULT: 2477 case SW_SHOWNORMAL: 2478 case SW_RESTORE: 2479 sei->nShow = SW_SHOWMAXIMIZED; 2480 break; 2481 default: 2482 break; 2483 } 2484 } 2485 2486 fOldMask = sei->fMask; 2487 2488 if (!(fOldMask & SEE_MASK_NOASYNC) && SHELL_InRunDllProcess()) 2489 sei->fMask |= SEE_MASK_WAITFORINPUTIDLE | SEE_MASK_NOASYNC; 2490 2491 dwError = ShellExecute_Normal(sei); 2492 2493 if (dwError && dwError != ERROR_DLL_NOT_FOUND && dwError != ERROR_CANCELLED) 2494 ShellExecute_ShowError(sei, NULL, dwError); 2495 2496 sei->fMask = fOldMask; 2497 2498 if (SUCCEEDED(hrCoInit)) 2499 CoUninitialize(); 2500 2501 if (dwError) 2502 SetLastError(dwError); 2503 2504 return dwError == ERROR_SUCCESS; 2505 } 2506 2507 /************************************************************************* 2508 * ShellExecuteW [SHELL32.294] 2509 * from shellapi.h 2510 * WINSHELLAPI HINSTANCE APIENTRY ShellExecuteW(HWND hwnd, LPCWSTR lpVerb, 2511 * LPCWSTR lpFile, LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd); 2512 */ 2513 HINSTANCE WINAPI ShellExecuteW(HWND hwnd, LPCWSTR lpVerb, LPCWSTR lpFile, 2514 LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd) 2515 { 2516 SHELLEXECUTEINFOW sei; 2517 2518 TRACE("\n"); 2519 sei.cbSize = sizeof(sei); 2520 sei.fMask = SEE_MASK_FLAG_NO_UI; 2521 sei.hwnd = hwnd; 2522 sei.lpVerb = lpVerb; 2523 sei.lpFile = lpFile; 2524 sei.lpParameters = lpParameters; 2525 sei.lpDirectory = lpDirectory; 2526 sei.nShow = nShowCmd; 2527 sei.lpIDList = 0; 2528 sei.lpClass = 0; 2529 sei.hkeyClass = 0; 2530 sei.dwHotKey = 0; 2531 sei.hProcess = 0; 2532 2533 SHELL_execute(&sei, SHELL_ExecuteW); 2534 return sei.hInstApp; 2535 } 2536 2537 /************************************************************************* 2538 * WOWShellExecute [SHELL32.@] 2539 * 2540 * FIXME: the callback function most likely doesn't work the same way on Windows. 2541 */ 2542 EXTERN_C HINSTANCE WINAPI WOWShellExecute(HWND hWnd, LPCSTR lpVerb, LPCSTR lpFile, 2543 LPCSTR lpParameters, LPCSTR lpDirectory, INT iShowCmd, void *callback) 2544 { 2545 SHELLEXECUTEINFOW seiW; 2546 WCHAR *wVerb = NULL, *wFile = NULL, *wParameters = NULL, *wDirectory = NULL; 2547 HANDLE hProcess = 0; 2548 2549 seiW.lpVerb = lpVerb ? __SHCloneStrAtoW(&wVerb, lpVerb) : NULL; 2550 seiW.lpFile = lpFile ? __SHCloneStrAtoW(&wFile, lpFile) : NULL; 2551 seiW.lpParameters = lpParameters ? __SHCloneStrAtoW(&wParameters, lpParameters) : NULL; 2552 seiW.lpDirectory = lpDirectory ? __SHCloneStrAtoW(&wDirectory, lpDirectory) : NULL; 2553 2554 seiW.cbSize = sizeof(seiW); 2555 seiW.fMask = 0; 2556 seiW.hwnd = hWnd; 2557 seiW.nShow = iShowCmd; 2558 seiW.lpIDList = 0; 2559 seiW.lpClass = 0; 2560 seiW.hkeyClass = 0; 2561 seiW.dwHotKey = 0; 2562 seiW.hProcess = hProcess; 2563 2564 SHELL_execute(&seiW, (SHELL_ExecuteW32)callback); 2565 2566 SHFree(wVerb); 2567 SHFree(wFile); 2568 SHFree(wParameters); 2569 SHFree(wDirectory); 2570 return seiW.hInstApp; 2571 } 2572 2573 /************************************************************************* 2574 * OpenAs_RunDLLW [SHELL32.@] 2575 */ 2576 EXTERN_C void WINAPI 2577 OpenAs_RunDLLW(HWND hwnd, HINSTANCE hinst, LPCWSTR cmdline, int cmdshow) 2578 { 2579 OPENASINFO info; 2580 TRACE("%p, %p, %s, %d\n", hwnd, hinst, debugstr_w(cmdline), cmdshow); 2581 2582 ZeroMemory(&info, sizeof(info)); 2583 info.pcszFile = cmdline; 2584 info.pcszClass = NULL; 2585 info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC; 2586 2587 SHOpenWithDialog(hwnd, &info); 2588 } 2589 2590 /************************************************************************* 2591 * OpenAs_RunDLLA [SHELL32.@] 2592 */ 2593 EXTERN_C void WINAPI 2594 OpenAs_RunDLLA(HWND hwnd, HINSTANCE hinst, LPCSTR cmdline, int cmdshow) 2595 { 2596 LPWSTR pszCmdLineW = NULL; 2597 TRACE("%p, %p, %s, %d\n", hwnd, hinst, debugstr_a(cmdline), cmdshow); 2598 2599 if (cmdline) 2600 __SHCloneStrAtoW(&pszCmdLineW, cmdline); 2601 OpenAs_RunDLLW(hwnd, hinst, pszCmdLineW, cmdshow); 2602 SHFree(pszCmdLineW); 2603 } 2604 2605 /*************************************************************************/ 2606 2607 static LPCWSTR 2608 SplitParams(LPCWSTR psz, LPWSTR pszArg0, size_t cchArg0) 2609 { 2610 LPCWSTR pch; 2611 size_t ich = 0; 2612 if (*psz == L'"') 2613 { 2614 // 1st argument is quoted. the string in quotes is quoted 1st argument. 2615 // [pch] --> [pszArg0+ich] 2616 for (pch = psz + 1; *pch && ich + 1 < cchArg0; ++ich, ++pch) 2617 { 2618 if (*pch == L'"' && pch[1] == L'"') 2619 { 2620 // doubled double quotations found! 2621 pszArg0[ich] = L'"'; 2622 } 2623 else if (*pch == L'"') 2624 { 2625 // single double quotation found! 2626 ++pch; 2627 break; 2628 } 2629 else 2630 { 2631 // otherwise 2632 pszArg0[ich] = *pch; 2633 } 2634 } 2635 } 2636 else 2637 { 2638 // 1st argument is unquoted. non-space sequence is 1st argument. 2639 // [pch] --> [pszArg0+ich] 2640 for (pch = psz; *pch && !iswspace(*pch) && ich + 1 < cchArg0; ++ich, ++pch) 2641 { 2642 pszArg0[ich] = *pch; 2643 } 2644 } 2645 pszArg0[ich] = 0; 2646 2647 // skip space 2648 while (iswspace(*pch)) 2649 ++pch; 2650 2651 return pch; 2652 } 2653 2654 HRESULT WINAPI ShellExecCmdLine( 2655 HWND hwnd, 2656 LPCWSTR pwszCommand, 2657 LPCWSTR pwszStartDir, 2658 int nShow, 2659 LPVOID pUnused, 2660 DWORD dwSeclFlags) 2661 { 2662 SHELLEXECUTEINFOW info; 2663 DWORD dwSize, dwError, dwType, dwFlags = SEE_MASK_DOENVSUBST | SEE_MASK_NOASYNC; 2664 LPCWSTR pszVerb = NULL; 2665 WCHAR szFile[MAX_PATH], szFile2[MAX_PATH]; 2666 HRESULT hr; 2667 LPCWSTR pchParams; 2668 LPWSTR lpCommand = NULL; 2669 2670 if (pwszCommand == NULL) 2671 RaiseException(EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE, 2672 1, (ULONG_PTR*)pwszCommand); 2673 2674 __SHCloneStrW(&lpCommand, pwszCommand); 2675 StrTrimW(lpCommand, L" \t"); 2676 2677 if (dwSeclFlags & SECL_NO_UI) 2678 dwFlags |= SEE_MASK_FLAG_NO_UI; 2679 if (dwSeclFlags & SECL_LOG_USAGE) 2680 dwFlags |= SEE_MASK_FLAG_LOG_USAGE; 2681 if (dwSeclFlags & SECL_USE_IDLIST) 2682 dwFlags |= SEE_MASK_INVOKEIDLIST; 2683 2684 if (dwSeclFlags & SECL_RUNAS) 2685 { 2686 dwSize = 0; 2687 hr = AssocQueryStringW(ASSOCF_NONE, ASSOCSTR_COMMAND, lpCommand, L"RunAs", NULL, &dwSize); 2688 if (SUCCEEDED(hr) && dwSize != 0) 2689 { 2690 pszVerb = L"runas"; 2691 } 2692 } 2693 2694 if (PathIsURLW(lpCommand) || UrlIsW(lpCommand, URLIS_APPLIABLE)) 2695 { 2696 StringCchCopyW(szFile, _countof(szFile), lpCommand); 2697 pchParams = NULL; 2698 } 2699 else 2700 { 2701 PCWSTR apPathList[2]; 2702 2703 pchParams = SplitParams(lpCommand, szFile, _countof(szFile)); 2704 if (szFile[0] != UNICODE_NULL && szFile[1] == L':' && 2705 szFile[2] == UNICODE_NULL) 2706 { 2707 PathAddBackslashW(szFile); 2708 } 2709 2710 WCHAR szCurDir[MAX_PATH]; 2711 GetCurrentDirectoryW(_countof(szCurDir), szCurDir); 2712 if (pwszStartDir) 2713 { 2714 SetCurrentDirectoryW(pwszStartDir); 2715 } 2716 2717 if ((PathIsRelativeW(szFile) && 2718 GetFullPathNameW(szFile, _countof(szFile2), szFile2, NULL) && 2719 PathFileExistsW(szFile2)) || 2720 SearchPathW(NULL, szFile, NULL, _countof(szFile2), szFile2, NULL)) 2721 { 2722 StringCchCopyW(szFile, _countof(szFile), szFile2); 2723 } 2724 2725 apPathList[0] = pwszStartDir; 2726 apPathList[1] = NULL; 2727 PathFindOnPathExW(szFile, apPathList, WHICH_DEFAULT); 2728 2729 if (!(dwSeclFlags & SECL_ALLOW_NONEXE)) 2730 { 2731 if (!GetBinaryTypeW(szFile, &dwType)) 2732 { 2733 SHFree(lpCommand); 2734 2735 if (!(dwSeclFlags & SECL_NO_UI)) 2736 { 2737 WCHAR szText[128 + MAX_PATH], szFormat[128]; 2738 LoadStringW(shell32_hInstance, IDS_FILE_NOT_FOUND, szFormat, _countof(szFormat)); 2739 StringCchPrintfW(szText, _countof(szText), szFormat, szFile); 2740 MessageBoxW(hwnd, szText, NULL, MB_ICONERROR); 2741 } 2742 return CO_E_APPNOTFOUND; 2743 } 2744 } 2745 else 2746 { 2747 if (GetFileAttributesW(szFile) == INVALID_FILE_ATTRIBUTES) 2748 { 2749 SHFree(lpCommand); 2750 2751 if (!(dwSeclFlags & SECL_NO_UI)) 2752 { 2753 WCHAR szText[128 + MAX_PATH], szFormat[128]; 2754 LoadStringW(shell32_hInstance, IDS_FILE_NOT_FOUND, szFormat, _countof(szFormat)); 2755 StringCchPrintfW(szText, _countof(szText), szFormat, szFile); 2756 MessageBoxW(hwnd, szText, NULL, MB_ICONERROR); 2757 } 2758 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); 2759 } 2760 } 2761 } 2762 2763 ZeroMemory(&info, sizeof(info)); 2764 info.cbSize = sizeof(info); 2765 info.fMask = dwFlags; 2766 info.hwnd = hwnd; 2767 info.lpVerb = pszVerb; 2768 info.lpFile = szFile; 2769 info.lpParameters = (pchParams && *pchParams) ? pchParams : NULL; 2770 info.lpDirectory = pwszStartDir; 2771 info.nShow = nShow; 2772 if (ShellExecuteExW(&info)) 2773 { 2774 if (info.lpIDList) 2775 CoTaskMemFree(info.lpIDList); 2776 2777 SHFree(lpCommand); 2778 2779 return S_OK; 2780 } 2781 2782 dwError = GetLastError(); 2783 2784 SHFree(lpCommand); 2785 2786 return HRESULT_FROM_WIN32(dwError); 2787 } 2788 2789 /************************************************************************* 2790 * RealShellExecuteExA (SHELL32.266) 2791 */ 2792 EXTERN_C 2793 HINSTANCE WINAPI 2794 RealShellExecuteExA( 2795 _In_opt_ HWND hwnd, 2796 _In_opt_ LPCSTR lpOperation, 2797 _In_opt_ LPCSTR lpFile, 2798 _In_opt_ LPCSTR lpParameters, 2799 _In_opt_ LPCSTR lpDirectory, 2800 _In_opt_ LPSTR lpReturn, 2801 _In_opt_ LPCSTR lpTitle, 2802 _In_opt_ LPVOID lpReserved, 2803 _In_ INT nCmdShow, 2804 _Out_opt_ PHANDLE lphProcess, 2805 _In_ DWORD dwFlags) 2806 { 2807 SHELLEXECUTEINFOA ExecInfo; 2808 2809 TRACE("(%p, %s, %s, %s, %s, %p, %s, %p, %u, %p, %lu)\n", 2810 hwnd, debugstr_a(lpOperation), debugstr_a(lpFile), debugstr_a(lpParameters), 2811 debugstr_a(lpDirectory), lpReserved, debugstr_a(lpTitle), 2812 lpReserved, nCmdShow, lphProcess, dwFlags); 2813 2814 ZeroMemory(&ExecInfo, sizeof(ExecInfo)); 2815 ExecInfo.cbSize = sizeof(ExecInfo); 2816 ExecInfo.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_UNKNOWN_0x1000; 2817 ExecInfo.hwnd = hwnd; 2818 ExecInfo.lpVerb = lpOperation; 2819 ExecInfo.lpFile = lpFile; 2820 ExecInfo.lpParameters = lpParameters; 2821 ExecInfo.lpDirectory = lpDirectory; 2822 ExecInfo.nShow = (WORD)nCmdShow; 2823 2824 if (lpReserved) 2825 { 2826 ExecInfo.fMask |= SEE_MASK_USE_RESERVED; 2827 ExecInfo.hInstApp = (HINSTANCE)lpReserved; 2828 } 2829 2830 if (lpTitle) 2831 { 2832 ExecInfo.fMask |= SEE_MASK_HASTITLE; 2833 ExecInfo.lpClass = lpTitle; 2834 } 2835 2836 if (dwFlags & 1) 2837 ExecInfo.fMask |= SEE_MASK_FLAG_SEPVDM; 2838 2839 if (dwFlags & 2) 2840 ExecInfo.fMask |= SEE_MASK_NO_CONSOLE; 2841 2842 if (lphProcess == NULL) 2843 { 2844 ShellExecuteExA(&ExecInfo); 2845 } 2846 else 2847 { 2848 ExecInfo.fMask |= SEE_MASK_NOCLOSEPROCESS; 2849 ShellExecuteExA(&ExecInfo); 2850 *lphProcess = ExecInfo.hProcess; 2851 } 2852 2853 return ExecInfo.hInstApp; 2854 } 2855 2856 /************************************************************************* 2857 * RealShellExecuteExW (SHELL32.267) 2858 */ 2859 EXTERN_C 2860 HINSTANCE WINAPI 2861 RealShellExecuteExW( 2862 _In_opt_ HWND hwnd, 2863 _In_opt_ LPCWSTR lpOperation, 2864 _In_opt_ LPCWSTR lpFile, 2865 _In_opt_ LPCWSTR lpParameters, 2866 _In_opt_ LPCWSTR lpDirectory, 2867 _In_opt_ LPWSTR lpReturn, 2868 _In_opt_ LPCWSTR lpTitle, 2869 _In_opt_ LPVOID lpReserved, 2870 _In_ INT nCmdShow, 2871 _Out_opt_ PHANDLE lphProcess, 2872 _In_ DWORD dwFlags) 2873 { 2874 SHELLEXECUTEINFOW ExecInfo; 2875 2876 TRACE("(%p, %s, %s, %s, %s, %p, %s, %p, %u, %p, %lu)\n", 2877 hwnd, debugstr_w(lpOperation), debugstr_w(lpFile), debugstr_w(lpParameters), 2878 debugstr_w(lpDirectory), lpReserved, debugstr_w(lpTitle), 2879 lpReserved, nCmdShow, lphProcess, dwFlags); 2880 2881 ZeroMemory(&ExecInfo, sizeof(ExecInfo)); 2882 ExecInfo.cbSize = sizeof(ExecInfo); 2883 ExecInfo.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_UNKNOWN_0x1000; 2884 ExecInfo.hwnd = hwnd; 2885 ExecInfo.lpVerb = lpOperation; 2886 ExecInfo.lpFile = lpFile; 2887 ExecInfo.lpParameters = lpParameters; 2888 ExecInfo.lpDirectory = lpDirectory; 2889 ExecInfo.nShow = (WORD)nCmdShow; 2890 2891 if (lpReserved) 2892 { 2893 ExecInfo.fMask |= SEE_MASK_USE_RESERVED; 2894 ExecInfo.hInstApp = (HINSTANCE)lpReserved; 2895 } 2896 2897 if (lpTitle) 2898 { 2899 ExecInfo.fMask |= SEE_MASK_HASTITLE; 2900 ExecInfo.lpClass = lpTitle; 2901 } 2902 2903 if (dwFlags & 1) 2904 ExecInfo.fMask |= SEE_MASK_FLAG_SEPVDM; 2905 2906 if (dwFlags & 2) 2907 ExecInfo.fMask |= SEE_MASK_NO_CONSOLE; 2908 2909 if (lphProcess == NULL) 2910 { 2911 ShellExecuteExW(&ExecInfo); 2912 } 2913 else 2914 { 2915 ExecInfo.fMask |= SEE_MASK_NOCLOSEPROCESS; 2916 ShellExecuteExW(&ExecInfo); 2917 *lphProcess = ExecInfo.hProcess; 2918 } 2919 2920 return ExecInfo.hInstApp; 2921 } 2922 2923 /************************************************************************* 2924 * RealShellExecuteA (SHELL32.265) 2925 */ 2926 EXTERN_C 2927 HINSTANCE WINAPI 2928 RealShellExecuteA( 2929 _In_opt_ HWND hwnd, 2930 _In_opt_ LPCSTR lpOperation, 2931 _In_opt_ LPCSTR lpFile, 2932 _In_opt_ LPCSTR lpParameters, 2933 _In_opt_ LPCSTR lpDirectory, 2934 _In_opt_ LPSTR lpReturn, 2935 _In_opt_ LPCSTR lpTitle, 2936 _In_opt_ LPVOID lpReserved, 2937 _In_ INT nCmdShow, 2938 _Out_opt_ PHANDLE lphProcess) 2939 { 2940 return RealShellExecuteExA(hwnd, 2941 lpOperation, 2942 lpFile, 2943 lpParameters, 2944 lpDirectory, 2945 lpReturn, 2946 lpTitle, 2947 lpReserved, 2948 nCmdShow, 2949 lphProcess, 2950 0); 2951 } 2952 2953 /************************************************************************* 2954 * RealShellExecuteW (SHELL32.268) 2955 */ 2956 EXTERN_C 2957 HINSTANCE WINAPI 2958 RealShellExecuteW( 2959 _In_opt_ HWND hwnd, 2960 _In_opt_ LPCWSTR lpOperation, 2961 _In_opt_ LPCWSTR lpFile, 2962 _In_opt_ LPCWSTR lpParameters, 2963 _In_opt_ LPCWSTR lpDirectory, 2964 _In_opt_ LPWSTR lpReturn, 2965 _In_opt_ LPCWSTR lpTitle, 2966 _In_opt_ LPVOID lpReserved, 2967 _In_ INT nCmdShow, 2968 _Out_opt_ PHANDLE lphProcess) 2969 { 2970 return RealShellExecuteExW(hwnd, 2971 lpOperation, 2972 lpFile, 2973 lpParameters, 2974 lpDirectory, 2975 lpReturn, 2976 lpTitle, 2977 lpReserved, 2978 nCmdShow, 2979 lphProcess, 2980 0); 2981 } 2982