Deal with the case of loading an empty PIDL from a stream better in
[wine] / dlls / shell32 / shlexec.c
1 /*
2  *                              Shell Library Functions
3  *
4  * Copyright 1998 Marcus Meissner
5  * Copyright 2002 Eric Pouech
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #include "config.h"
23 #include "wine/port.h"
24
25 #include <stdlib.h>
26 #include <string.h>
27 #include <stdio.h>
28 #ifdef HAVE_UNISTD_H
29 # include <unistd.h>
30 #endif
31 #include <ctype.h>
32 #include <assert.h>
33
34 #include "windef.h"
35 #include "winerror.h"
36 #include "winreg.h"
37 #include "wownt32.h"
38 #include "heap.h"
39 #include "shellapi.h"
40 #include "shlobj.h"
41 #include "shlwapi.h"
42 #include "ddeml.h"
43
44 #include "wine/winbase16.h"
45 #include "shell32_main.h"
46 #include "undocshell.h"
47
48 #include "wine/debug.h"
49
50 WINE_DEFAULT_DEBUG_CHANNEL(exec);
51
52 /***********************************************************************
53  * this function is supposed to expand the escape sequences found in the registry
54  * some diving reported that the following were used:
55  * + %1, %2...  seem to report to parameter of index N in ShellExecute pmts
56  *      %1 file
57  *      %2 printer
58  *      %3 driver
59  *      %4 port
60  * %I address of a global item ID (explorer switch /idlist)
61  * %L seems to be %1 as long filename followed by the 8+3 variation
62  * %S ???
63  * %* all following parameters (see batfile)
64  */
65 static BOOL argify(char* res, int len, const char* fmt, const char* lpFile)
66 {
67     char        xlpFile[1024];
68     BOOL        done = FALSE;
69
70     while (*fmt)
71     {
72         if (*fmt == '%')
73         {
74             switch (*++fmt)
75             {
76             case '\0':
77             case '%':
78                 *res++ = '%';
79                 break;
80             case '1':
81             case '*':
82                 if (!done || (*fmt == '1'))
83                 {
84                     if (SearchPathA(NULL, lpFile, ".exe", sizeof(xlpFile), xlpFile, NULL))
85                     {
86                         strcpy(res, xlpFile);
87                         res += strlen(xlpFile);
88                     }
89                     else
90                     {
91                         strcpy(res, lpFile);
92                         res += strlen(lpFile);
93                     }
94                 }
95                 break;
96             /*
97              * IE uses this alot for activating things such as windows media
98              * player. This is not verified to be fully correct but it appears
99              * to work just fine.
100              */
101             case 'L':
102                 strcpy(res,lpFile);
103                 res += strlen(lpFile);
104                 break;
105
106             default: FIXME("Unknown escape sequence %%%c\n", *fmt);
107             }
108             fmt++;
109             done = TRUE;
110         }
111         else
112             *res++ = *fmt++;
113     }
114     *res = '\0';
115     return done;
116 }
117
118 /*************************************************************************
119  *      SHELL_ExecuteA [Internal]
120  *
121  */
122 static UINT SHELL_ExecuteA(char *lpCmd, LPSHELLEXECUTEINFOA sei, BOOL shWait)
123 {
124     STARTUPINFOA  startup;
125     PROCESS_INFORMATION info;
126     UINT retval = 31;
127
128     TRACE("Execute %s from directory %s\n", lpCmd, sei->lpDirectory);
129     ZeroMemory(&startup,sizeof(STARTUPINFOA));
130     startup.cb = sizeof(STARTUPINFOA);
131     startup.dwFlags = STARTF_USESHOWWINDOW;
132     startup.wShowWindow = sei->nShow;
133     if (CreateProcessA(NULL, lpCmd, NULL, NULL, FALSE, 0,
134                        NULL, sei->lpDirectory, &startup, &info))
135     {
136         /* Give 30 seconds to the app to come up, if desired. Probably only needed
137            when starting app immediately before making a DDE connection. */
138         if (shWait)
139             if (WaitForInputIdle( info.hProcess, 30000 ) == -1)
140                 WARN("WaitForInputIdle failed: Error %ld\n", GetLastError() );
141         retval = 33;
142         if(sei->fMask & SEE_MASK_NOCLOSEPROCESS)
143             sei->hProcess = info.hProcess;
144         else
145             CloseHandle( info.hProcess );
146         CloseHandle( info.hThread );
147     }
148     else if ((retval = GetLastError()) >= 32)
149     {
150         FIXME("Strange error set by CreateProcess: %d\n", retval);
151         retval = ERROR_BAD_FORMAT;
152     }
153
154     sei->hInstApp = (HINSTANCE)retval;
155     return retval;
156 }
157
158 /***********************************************************************
159  *           SHELL_TryAppPath
160  *
161  * Helper function for SHELL_FindExecutable
162  * @param lpResult - pointer to a buffer of size MAX_PATH
163  * On entry: szName is a filename (probably without path separators).
164  * On exit: if szName found in "App Path", place full path in lpResult, and return true
165  */
166 static BOOL SHELL_TryAppPath( LPCSTR szName, LPSTR lpResult)
167 {
168     HKEY hkApp = 0;
169     char szAppKey[256];
170     LONG len;
171     LONG res; 
172     BOOL found = FALSE;
173
174     sprintf(szAppKey, "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\%s", szName);
175     res = RegOpenKeyExA(HKEY_LOCAL_MACHINE, szAppKey, 0, KEY_READ, &hkApp);
176     if (res) {
177         /*TRACE("RegOpenKeyExA(HKEY_LOCAL_MACHINE, %s,) returns %ld\n", szAppKey, res);*/
178         goto end;
179     }
180
181     len = MAX_PATH;
182     res = RegQueryValueA(hkApp, NULL, lpResult, &len);
183     if (res) {
184         /*TRACE("RegQueryValueA(hkApp, NULL,) returns %ld\n", res);*/
185         goto end;
186     }
187     /*TRACE("%s -> %s\n", szName, lpResult);*/
188     found = TRUE;
189
190 end:
191     if (hkApp) RegCloseKey(hkApp);
192     return found;
193 }
194
195 /*************************************************************************
196  *      SHELL_FindExecutable [Internal]
197  *
198  * Utility for code sharing between FindExecutable and ShellExecute
199  * in:
200  *      lpFile the name of a file
201  *      lpOperation the operation on it (open)
202  * out:
203  *      lpResult a buffer, big enough :-(, to store the command to do the
204  *              operation on the file
205  *      key a buffer, big enough, to get the key name to do actually the
206  *              command (it'll be used afterwards for more information
207  *              on the operation)
208  */
209 static UINT SHELL_FindExecutable(LPCSTR lpPath, LPCSTR lpFile, LPCSTR lpOperation,
210                                  LPSTR lpResult, LPSTR key)
211 {
212     char *extension = NULL; /* pointer to file extension */
213     char tmpext[5];         /* local copy to mung as we please */
214     char filetype[256];     /* registry name for this filetype */
215     LONG filetypelen = 256; /* length of above */
216     char command[256];      /* command from registry */
217     LONG commandlen = 256;  /* This is the most DOS can handle :) */
218     char buffer[256];       /* Used to GetProfileString */
219     UINT retval = 31;  /* default - 'No association was found' */
220     char *tok;              /* token pointer */
221     char xlpFile[256] = ""; /* result of SearchPath */
222
223     TRACE("%s\n", (lpFile != NULL) ? lpFile : "-");
224
225     lpResult[0] = '\0'; /* Start off with an empty return string */
226     if (key) *key = '\0';
227
228     /* trap NULL parameters on entry */
229     if ((lpFile == NULL) || (lpResult == NULL) || (lpOperation == NULL))
230     {
231         WARN("(lpFile=%s,lpResult=%s,lpOperation=%s): NULL parameter\n",
232              lpFile, lpOperation, lpResult);
233         return 2; /* File not found. Close enough, I guess. */
234     }
235
236     if (SHELL_TryAppPath( lpFile, lpResult ))
237     {
238         TRACE("found %s via App Paths\n", lpResult);
239         return 33;
240     }
241
242     if (SearchPathA(lpPath, lpFile, ".exe", sizeof(xlpFile), xlpFile, NULL))
243     {
244         TRACE("SearchPathA returned non-zero\n");
245         lpFile = xlpFile;
246         /* Hey, isn't this value ignored?  Why make this call?  Shouldn't we return here?  --dank*/
247     }
248
249     /* First thing we need is the file's extension */
250     extension = strrchr(xlpFile, '.'); /* Assume last "." is the one; */
251                                        /* File->Run in progman uses */
252                                        /* .\FILE.EXE :( */
253     TRACE("xlpFile=%s,extension=%s\n", xlpFile, extension);
254
255     if ((extension == NULL) || (extension == &xlpFile[strlen(xlpFile)]))
256     {
257         WARN("Returning 31 - No association\n");
258         return 31; /* no association */
259     }
260
261     /* Make local copy & lowercase it for reg & 'programs=' lookup */
262     lstrcpynA(tmpext, extension, 5);
263     CharLowerA(tmpext);
264     TRACE("%s file\n", tmpext);
265
266     /* Three places to check: */
267     /* 1. win.ini, [windows], programs (NB no leading '.') */
268     /* 2. Registry, HKEY_CLASS_ROOT\<filetype>\shell\open\command */
269     /* 3. win.ini, [extensions], extension (NB no leading '.' */
270     /* All I know of the order is that registry is checked before */
271     /* extensions; however, it'd make sense to check the programs */
272     /* section first, so that's what happens here. */
273
274     /* See if it's a program - if GetProfileString fails, we skip this
275      * section. Actually, if GetProfileString fails, we've probably
276      * got a lot more to worry about than running a program... */
277     if (GetProfileStringA("windows", "programs", "exe pif bat cmd com",
278                           buffer, sizeof(buffer)) > 0)
279     {
280         UINT i;
281
282         for (i = 0;i<strlen(buffer); i++) buffer[i] = tolower(buffer[i]);
283
284         tok = strtok(buffer, " \t"); /* ? */
285         while (tok!= NULL)
286         {
287             if (strcmp(tok, &tmpext[1]) == 0) /* have to skip the leading "." */
288             {
289                 strcpy(lpResult, xlpFile);
290                 /* Need to perhaps check that the file has a path
291                  * attached */
292                 TRACE("found %s\n", lpResult);
293                 return 33;
294
295                 /* Greater than 32 to indicate success FIXME According to the
296                  * docs, I should be returning a handle for the
297                  * executable. Does this mean I'm supposed to open the
298                  * executable file or something? More RTFM, I guess... */
299             }
300             tok = strtok(NULL, " \t");
301         }
302     }
303
304     /* Check registry */
305     if (RegQueryValueA(HKEY_CLASSES_ROOT, tmpext, filetype,
306                        &filetypelen) == ERROR_SUCCESS)
307     {
308         filetype[filetypelen] = '\0';
309         TRACE("File type: %s\n", filetype);
310
311         /* Looking for ...buffer\shell\lpOperation\command */
312         strcat(filetype, "\\shell\\");
313         strcat(filetype, lpOperation);
314         strcat(filetype, "\\command");
315
316         if (RegQueryValueA(HKEY_CLASSES_ROOT, filetype, command,
317                            &commandlen) == ERROR_SUCCESS)
318         {
319             if (key) strcpy(key, filetype);
320 #if 0
321             LPSTR tmp;
322             char param[256];
323             LONG paramlen = 256;
324
325             /* FIXME: it seems all Windows version don't behave the same here.
326              * the doc states that this ddeexec information can be found after
327              * the exec names.
328              * on Win98, it doesn't appear, but I think it does on Win2k
329              */
330             /* Get the parameters needed by the application
331                from the associated ddeexec key */
332             tmp = strstr(filetype, "command");
333             tmp[0] = '\0';
334             strcat(filetype, "ddeexec");
335
336             if (RegQueryValueA(HKEY_CLASSES_ROOT, filetype, param, &paramlen) == ERROR_SUCCESS)
337             {
338                 strcat(command, " ");
339                 strcat(command, param);
340                 commandlen += paramlen;
341             }
342 #endif
343             command[commandlen] = '\0';
344             argify(lpResult, sizeof(lpResult), command, xlpFile);
345             retval = 33; /* FIXME see above */
346         }
347     }
348     else /* Check win.ini */
349     {
350         /* Toss the leading dot */
351         extension++;
352         if (GetProfileStringA("extensions", extension, "", command,
353                               sizeof(command)) > 0)
354         {
355             if (strlen(command) != 0)
356             {
357                 strcpy(lpResult, command);
358                 tok = strstr(lpResult, "^"); /* should be ^.extension? */
359                 if (tok != NULL)
360                 {
361                     tok[0] = '\0';
362                     strcat(lpResult, xlpFile); /* what if no dir in xlpFile? */
363                     tok = strstr(command, "^"); /* see above */
364                     if ((tok != NULL) && (strlen(tok)>5))
365                     {
366                         strcat(lpResult, &tok[5]);
367                     }
368                 }
369                 retval = 33; /* FIXME - see above */
370             }
371         }
372     }
373
374     TRACE("returning %s\n", lpResult);
375     return retval;
376 }
377
378 /******************************************************************
379  *              dde_cb
380  *
381  * callback for the DDE connection. not really usefull
382  */
383 static HDDEDATA CALLBACK dde_cb(UINT uType, UINT uFmt, HCONV hConv,
384                                 HSZ hsz1, HSZ hsz2,
385                                 HDDEDATA hData, DWORD dwData1, DWORD dwData2)
386 {
387     return NULL;
388 }
389
390 /******************************************************************
391  *              dde_connect
392  *
393  * ShellExecute helper. Used to do an operation with a DDE connection
394  *
395  * Handles both the direct connection (try #1), and if it fails,
396  * launching an application and trying (#2) to connect to it
397  *
398  */
399 static unsigned dde_connect(char* key, char* start, char* ddeexec,
400                             const char* lpFile,
401                             LPSHELLEXECUTEINFOA sei, SHELL_ExecuteA1632 execfunc)
402 {
403     char*       endkey = key + strlen(key);
404     char        app[256], topic[256], ifexec[256], res[256];
405     LONG        applen, topiclen, ifexeclen;
406     char*       exec;
407     DWORD       ddeInst = 0;
408     DWORD       tid;
409     HSZ         hszApp, hszTopic;
410     HCONV       hConv;
411     unsigned    ret = 31;
412
413     strcpy(endkey, "\\application");
414     applen = sizeof(app);
415     if (RegQueryValueA(HKEY_CLASSES_ROOT, key, app, &applen) != ERROR_SUCCESS)
416     {
417         FIXME("default app name NIY %s\n", key);
418         return 2;
419     }
420
421     strcpy(endkey, "\\topic");
422     topiclen = sizeof(topic);
423     if (RegQueryValueA(HKEY_CLASSES_ROOT, key, topic, &topiclen) != ERROR_SUCCESS)
424     {
425         strcpy(topic, "System");
426     }
427
428     if (DdeInitializeA(&ddeInst, dde_cb, APPCMD_CLIENTONLY, 0L) != DMLERR_NO_ERROR)
429     {
430         return 2;
431     }
432
433     hszApp = DdeCreateStringHandleA(ddeInst, app, CP_WINANSI);
434     hszTopic = DdeCreateStringHandleA(ddeInst, topic, CP_WINANSI);
435
436     hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL);
437     exec = ddeexec;
438     if (!hConv)
439     {
440         TRACE("Launching '%s'\n", start);
441         ret = execfunc(start, sei, TRUE);
442         if (ret < 32)
443         {
444             TRACE("Couldn't launch\n");
445             goto error;
446         }
447         hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL);
448         if (!hConv)
449         {
450             TRACE("Couldn't connect. ret=%d\n", ret);
451             ret = 30; /* whatever */
452             goto error;
453         }
454         strcpy(endkey, "\\ifexec");
455         ifexeclen = sizeof(ifexec);
456         if (RegQueryValueA(HKEY_CLASSES_ROOT, key, ifexec, &ifexeclen) == ERROR_SUCCESS)
457         {
458             exec = ifexec;
459         }
460     }
461
462     argify(res, sizeof(res), exec, lpFile);
463     TRACE("%s %s => %s\n", exec, lpFile, res);
464
465     ret = (DdeClientTransaction(res, strlen(res) + 1, hConv, 0L, 0,
466                                 XTYP_EXECUTE, 10000, &tid) != DMLERR_NO_ERROR) ? 31 : 33;
467     DdeDisconnect(hConv);
468  error:
469     DdeUninitialize(ddeInst);
470     return ret;
471 }
472
473 /*************************************************************************
474  *      execute_from_key [Internal]
475  */
476 static UINT execute_from_key(LPSTR key, LPCSTR lpFile, LPSHELLEXECUTEINFOA sei, SHELL_ExecuteA1632 execfunc)
477 {
478     char cmd[1024] = "";
479     LONG cmdlen = sizeof(cmd);
480     UINT retval = 31;
481
482     /* Get the application for the registry */
483     if (RegQueryValueA(HKEY_CLASSES_ROOT, key, cmd, &cmdlen) == ERROR_SUCCESS)
484     {
485         LPSTR tmp;
486         char param[256] = "";
487         LONG paramlen = 256;
488
489         /* Get the parameters needed by the application
490            from the associated ddeexec key */
491         tmp = strstr(key, "command");
492         assert(tmp);
493         strcpy(tmp, "ddeexec");
494
495         if (RegQueryValueA(HKEY_CLASSES_ROOT, key, param, &paramlen) == ERROR_SUCCESS)
496         {
497             TRACE("Got ddeexec %s => %s\n", key, param);
498             retval = dde_connect(key, cmd, param, lpFile, sei, execfunc);
499         }
500         else
501         {
502             /* Is there a replace() function anywhere? */
503             cmd[cmdlen] = '\0';
504             argify(param, sizeof(param), cmd, lpFile);
505             retval = execfunc(param, sei, FALSE);
506         }
507     }
508     else TRACE("ooch\n");
509
510     return retval;
511 }
512
513 /*************************************************************************
514  * FindExecutableA                      [SHELL32.@]
515  */
516 HINSTANCE WINAPI FindExecutableA(LPCSTR lpFile, LPCSTR lpDirectory, LPSTR lpResult)
517 {
518     UINT retval = 31;    /* default - 'No association was found' */
519     char old_dir[1024];
520
521     TRACE("File %s, Dir %s\n",
522           (lpFile != NULL ? lpFile : "-"), (lpDirectory != NULL ? lpDirectory : "-"));
523
524     lpResult[0] = '\0'; /* Start off with an empty return string */
525
526     /* trap NULL parameters on entry */
527     if ((lpFile == NULL) || (lpResult == NULL))
528     {
529         /* FIXME - should throw a warning, perhaps! */
530         return (HINSTANCE)2; /* File not found. Close enough, I guess. */
531     }
532
533     if (lpDirectory)
534     {
535         GetCurrentDirectoryA(sizeof(old_dir), old_dir);
536         SetCurrentDirectoryA(lpDirectory);
537     }
538
539     retval = SHELL_FindExecutable(lpDirectory, lpFile, "open", lpResult, NULL);
540
541     TRACE("returning %s\n", lpResult);
542     if (lpDirectory)
543         SetCurrentDirectoryA(old_dir);
544     return (HINSTANCE)retval;
545 }
546
547 /*************************************************************************
548  * FindExecutableW                      [SHELL32.@]
549  */
550 HINSTANCE WINAPI FindExecutableW(LPCWSTR lpFile, LPCWSTR lpDirectory, LPWSTR lpResult)
551 {
552     FIXME("(%p,%p,%p): stub\n", lpFile, lpDirectory, lpResult);
553     return (HINSTANCE)31;    /* default - 'No association was found' */
554 }
555
556 /*************************************************************************
557  *      ShellExecuteExA32 [Internal]
558  */
559 BOOL WINAPI ShellExecuteExA32 (LPSHELLEXECUTEINFOA sei, SHELL_ExecuteA1632 execfunc)
560 {
561     CHAR szApplicationName[MAX_PATH],szCommandline[MAX_PATH],szPidl[20],fileName[MAX_PATH];
562     LPSTR pos;
563     int gap, len;
564     char lpstrProtocol[256];
565     LPCSTR lpFile,lpOperation;
566     UINT retval = 31;
567     char cmd[1024];
568     BOOL done;
569
570     TRACE("mask=0x%08lx hwnd=%p verb=%s file=%s parm=%s dir=%s show=0x%08x class=%s\n",
571             sei->fMask, sei->hwnd, debugstr_a(sei->lpVerb),
572             debugstr_a(sei->lpFile), debugstr_a(sei->lpParameters),
573             debugstr_a(sei->lpDirectory), sei->nShow,
574             (sei->fMask & SEE_MASK_CLASSNAME) ? debugstr_a(sei->lpClass) : "not used");
575
576     sei->hProcess = NULL;
577     ZeroMemory(szApplicationName,MAX_PATH);
578     if (sei->lpFile)
579         strcpy(szApplicationName, sei->lpFile);
580
581     ZeroMemory(szCommandline,MAX_PATH);
582     if (sei->lpParameters)
583         strcpy(szCommandline, sei->lpParameters);
584
585     if (sei->fMask & (SEE_MASK_INVOKEIDLIST | SEE_MASK_ICON | SEE_MASK_HOTKEY |
586         SEE_MASK_CONNECTNETDRV | SEE_MASK_FLAG_DDEWAIT |
587         SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI | SEE_MASK_UNICODE |
588         SEE_MASK_NO_CONSOLE | SEE_MASK_ASYNCOK | SEE_MASK_HMONITOR ))
589     {
590         FIXME("flags ignored: 0x%08lx\n", sei->fMask);
591     }
592
593     /* process the IDList */
594     if ( (sei->fMask & SEE_MASK_INVOKEIDLIST) == SEE_MASK_INVOKEIDLIST) /*0x0c*/
595     {
596         SHGetPathFromIDListA (sei->lpIDList,szApplicationName);
597         TRACE("-- idlist=%p (%s)\n", sei->lpIDList, szApplicationName);
598     }
599     else
600     {
601         if (sei->fMask & SEE_MASK_IDLIST )
602         {
603             pos = strstr(szCommandline, "%I");
604             if (pos)
605             {
606                 LPVOID pv;
607                 HGLOBAL hmem = SHAllocShared ( sei->lpIDList, ILGetSize(sei->lpIDList), 0);
608                 pv = SHLockShared(hmem,0);
609                 sprintf(szPidl,":%p",pv );
610                 SHUnlockShared(pv);
611
612                 gap = strlen(szPidl);
613                 len = strlen(pos)-2;
614                 memmove(pos+gap,pos+2,len);
615                 memcpy(pos,szPidl,gap);
616             }
617         }
618     }
619
620     if (sei->fMask & (SEE_MASK_CLASSNAME | SEE_MASK_CLASSKEY))
621     {
622         /* launch a document by fileclass like 'WordPad.Document.1' */
623         /* the Commandline contains 'c:\Path\wordpad.exe "%1"' */
624         /* FIXME: szCommandline should not be of a fixed size. Plus MAX_PATH is way too short! */
625         if (sei->fMask & SEE_MASK_CLASSKEY)
626             HCR_GetExecuteCommandEx(sei->hkeyClass,
627                                     (sei->fMask & SEE_MASK_CLASSNAME) ? sei->lpClass: NULL,
628                                     (sei->lpVerb) ? sei->lpVerb : "open", szCommandline, sizeof(szCommandline));
629         else if (sei->fMask & SEE_MASK_CLASSNAME)
630             HCR_GetExecuteCommandA(sei->lpClass, (sei->lpVerb) ? sei->lpVerb :
631                                   "open", szCommandline, sizeof(szCommandline));
632
633         /* FIXME: get the extension of lpFile, check if it fits to the lpClass */
634         TRACE("SEE_MASK_CLASSNAME->'%s', doc->'%s'\n", szCommandline, szApplicationName);
635
636         cmd[0] = '\0';
637         done = argify(cmd, sizeof(cmd), szCommandline, szApplicationName);
638         if (!done && szApplicationName[0])
639         {
640             strcat(cmd, " ");
641             strcat(cmd, szApplicationName);
642         }
643         retval = execfunc(cmd, sei, FALSE);
644         if (retval > 32)
645             return TRUE;
646         else
647             return FALSE;
648     }
649
650     /* We set the default to open, and that should generally work.
651        But that is not really the way the MS docs say to do it. */
652     if (sei->lpVerb == NULL)
653         lpOperation = "open";
654     else
655         lpOperation = sei->lpVerb;
656
657     /* Else, try to execute the filename */
658     TRACE("execute:'%s','%s'\n",szApplicationName, szCommandline);
659
660     strcpy(fileName, szApplicationName);
661     lpFile = fileName;
662     if (szCommandline[0]) {
663         strcat(szApplicationName, " ");
664         strcat(szApplicationName, szCommandline);
665     }
666
667     retval = execfunc(szApplicationName, sei, FALSE);
668     if (retval > 32)
669         return TRUE;
670
671     /* Else, try to find the executable */
672     cmd[0] = '\0';
673     retval = SHELL_FindExecutable(sei->lpDirectory, lpFile, lpOperation, cmd, lpstrProtocol);
674     if (retval > 32)  /* Found */
675     {
676         CHAR szQuotedCmd[MAX_PATH+2];
677         /* Must quote to handle case where cmd contains spaces, 
678          * else security hole if malicious user creates executable file "C:\\Program"
679          */
680         if (szCommandline[0])
681             sprintf(szQuotedCmd, "\"%s\" %s", cmd, szCommandline);
682         else
683             sprintf(szQuotedCmd, "\"%s\"", cmd);
684         TRACE("%s/%s => %s/%s\n", szApplicationName, lpOperation, szQuotedCmd, lpstrProtocol);
685         if (*lpstrProtocol)
686             retval = execute_from_key(lpstrProtocol, szApplicationName, sei, execfunc);
687         else
688             retval = execfunc(szQuotedCmd, sei, FALSE);
689     }
690     else if (PathIsURLA((LPSTR)lpFile))    /* File not found, check for URL */
691     {
692         LPSTR lpstrRes;
693         INT iSize;
694
695         lpstrRes = strchr(lpFile, ':');
696         if (lpstrRes)
697             iSize = lpstrRes - lpFile;
698         else
699             iSize = strlen(lpFile);
700
701         TRACE("Got URL: %s\n", lpFile);
702         /* Looking for ...protocol\shell\lpOperation\command */
703         strncpy(lpstrProtocol, lpFile, iSize);
704         lpstrProtocol[iSize] = '\0';
705         strcat(lpstrProtocol, "\\shell\\");
706         strcat(lpstrProtocol, lpOperation);
707         strcat(lpstrProtocol, "\\command");
708
709         /* Remove File Protocol from lpFile */
710         /* In the case file://path/file     */
711         if (!strncasecmp(lpFile, "file", iSize))
712         {
713             lpFile += iSize;
714             while (*lpFile == ':') lpFile++;
715         }
716         retval = execute_from_key(lpstrProtocol, lpFile, sei, execfunc);
717     }
718     /* Check if file specified is in the form www.??????.*** */
719     else if (!strncasecmp(lpFile, "www", 3))
720     {
721         /* if so, append lpFile http:// and call ShellExecute */
722         char lpstrTmpFile[256] = "http://" ;
723         strcat(lpstrTmpFile, lpFile);
724         retval = (UINT)ShellExecuteA(sei->hwnd, lpOperation, lpstrTmpFile, NULL, NULL, 0);
725     }
726
727     if (retval <= 32)
728     {
729         sei->hInstApp = (HINSTANCE)retval;
730         return FALSE;
731     }
732
733     sei->hInstApp = (HINSTANCE)33;
734     return TRUE;
735 }
736
737 /*************************************************************************
738  * ShellExecuteA                        [SHELL32.290]
739  */
740 HINSTANCE WINAPI ShellExecuteA(HWND hWnd, LPCSTR lpOperation,LPCSTR lpFile,
741                                LPCSTR lpParameters,LPCSTR lpDirectory, INT iShowCmd)
742 {
743     SHELLEXECUTEINFOA sei;
744     HANDLE hProcess = 0;
745
746     TRACE("\n");
747     sei.cbSize = sizeof(sei);
748     sei.fMask = 0;
749     sei.hwnd = hWnd;
750     sei.lpVerb = lpOperation;
751     sei.lpFile = lpFile;
752     sei.lpParameters = lpParameters;
753     sei.lpDirectory = lpDirectory;
754     sei.nShow = iShowCmd;
755     sei.lpIDList = 0;
756     sei.lpClass = 0;
757     sei.hkeyClass = 0;
758     sei.dwHotKey = 0;
759     sei.hProcess = hProcess;
760
761     ShellExecuteExA32 (&sei, SHELL_ExecuteA);
762     return sei.hInstApp;
763 }
764
765 /*************************************************************************
766  * ShellExecuteEx                               [SHELL32.291]
767  *
768  */
769 BOOL WINAPI ShellExecuteExAW (LPVOID sei)
770 {
771     if (SHELL_OsIsUnicode())
772         return ShellExecuteExW (sei);
773     return ShellExecuteExA32 (sei, SHELL_ExecuteA);
774 }
775
776 /*************************************************************************
777  * ShellExecuteExA                              [SHELL32.292]
778  *
779  */
780 BOOL WINAPI ShellExecuteExA (LPSHELLEXECUTEINFOA sei)
781 {
782     return  ShellExecuteExA32 (sei, SHELL_ExecuteA);
783 }
784
785 /*************************************************************************
786  * ShellExecuteExW                              [SHELL32.293]
787  *
788  */
789 BOOL WINAPI ShellExecuteExW (LPSHELLEXECUTEINFOW sei)
790 {
791     SHELLEXECUTEINFOA seiA;
792     DWORD ret;
793
794     TRACE("%p\n", sei);
795
796     memcpy(&seiA, sei, sizeof(SHELLEXECUTEINFOA));
797
798     if (sei->lpVerb)
799         seiA.lpVerb = HEAP_strdupWtoA( GetProcessHeap(), 0, sei->lpVerb);
800
801     if (sei->lpFile)
802         seiA.lpFile = HEAP_strdupWtoA( GetProcessHeap(), 0, sei->lpFile);
803
804     if (sei->lpParameters)
805         seiA.lpParameters = HEAP_strdupWtoA( GetProcessHeap(), 0, sei->lpParameters);
806
807     if (sei->lpDirectory)
808         seiA.lpDirectory = HEAP_strdupWtoA( GetProcessHeap(), 0, sei->lpDirectory);
809
810     if ((sei->fMask & SEE_MASK_CLASSNAME) && sei->lpClass)
811         seiA.lpClass = HEAP_strdupWtoA( GetProcessHeap(), 0, sei->lpClass);
812     else
813         seiA.lpClass = NULL;
814
815     ret = ShellExecuteExA(&seiA);
816
817     if (seiA.lpVerb)    HeapFree( GetProcessHeap(), 0, (LPSTR) seiA.lpVerb );
818     if (seiA.lpFile)    HeapFree( GetProcessHeap(), 0, (LPSTR) seiA.lpFile );
819     if (seiA.lpParameters)      HeapFree( GetProcessHeap(), 0, (LPSTR) seiA.lpParameters );
820     if (seiA.lpDirectory)       HeapFree( GetProcessHeap(), 0, (LPSTR) seiA.lpDirectory );
821     if (seiA.lpClass)   HeapFree( GetProcessHeap(), 0, (LPSTR) seiA.lpClass );
822
823     return ret;
824 }
825
826 /*************************************************************************
827  * ShellExecuteW                        [SHELL32.294]
828  * from shellapi.h
829  * WINSHELLAPI HINSTANCE APIENTRY ShellExecuteW(HWND hwnd, LPCWSTR lpOperation,
830  * LPCWSTR lpFile, LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd);
831  */
832 HINSTANCE WINAPI ShellExecuteW(HWND hwnd, LPCWSTR lpOperation, LPCWSTR lpFile,
833                                LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd)
834 {
835     SHELLEXECUTEINFOW sei;
836     HANDLE hProcess = 0;
837
838     TRACE("\n");
839     sei.cbSize = sizeof(sei);
840     sei.fMask = 0;
841     sei.hwnd = hwnd;
842     sei.lpVerb = lpOperation;
843     sei.lpFile = lpFile;
844     sei.lpParameters = lpParameters;
845     sei.lpDirectory = lpDirectory;
846     sei.nShow = nShowCmd;
847     sei.lpIDList = 0;
848     sei.lpClass = 0;
849     sei.hkeyClass = 0;
850     sei.dwHotKey = 0;
851     sei.hProcess = hProcess;
852
853     ShellExecuteExW (&sei);
854     return sei.hInstApp;
855 }