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