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