Fix subclassing to support nested messages.
[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 <stdarg.h>
28 #include <stdio.h>
29 #ifdef HAVE_UNISTD_H
30 # include <unistd.h>
31 #endif
32 #include <ctype.h>
33 #include <assert.h>
34
35 #include "windef.h"
36 #include "winbase.h"
37 #include "winerror.h"
38 #include "winreg.h"
39 #include "wownt32.h"
40 #include "shellapi.h"
41 #include "wingdi.h"
42 #include "winuser.h"
43 #include "shlobj.h"
44 #include "shlwapi.h"
45 #include "ddeml.h"
46
47 #include "wine/winbase16.h"
48 #include "shell32_main.h"
49 #include "undocshell.h"
50
51 #include "wine/debug.h"
52
53 WINE_DEFAULT_DEBUG_CHANNEL(exec);
54
55 static const WCHAR wszOpen[] = {'o','p','e','n',0};
56 static const WCHAR wszExe[] = {'.','e','x','e',0};
57 static const WCHAR wszShell[] = {'\\','s','h','e','l','l','\\',0};
58
59
60 /***********************************************************************
61  *      SHELL_ArgifyW [Internal]
62  *
63  * this function is supposed to expand the escape sequences found in the registry
64  * some diving reported that the following were used:
65  * + %1, %2...  seem to report to parameter of index N in ShellExecute pmts
66  *      %1 file
67  *      %2 printer
68  *      %3 driver
69  *      %4 port
70  * %I address of a global item ID (explorer switch /idlist)
71  * %L seems to be %1 as long filename followed by the 8+3 variation
72  * %S ???
73  * %* all following parameters (see batfile)
74  */
75 static BOOL SHELL_ArgifyW(WCHAR* res, int len, const WCHAR* fmt, const WCHAR* lpFile)
76 {
77     WCHAR       xlpFile[1024];
78     BOOL        done = FALSE;
79
80     while (*fmt)
81     {
82         if (*fmt == '%')
83         {
84             switch (*++fmt)
85             {
86             case '\0':
87             case '%':
88                 *res++ = '%';
89                 break;
90             case '1':
91             case '*':
92                 if (!done || (*fmt == '1'))
93                 {
94                     if (SearchPathW(NULL, lpFile, wszExe, sizeof(xlpFile)/sizeof(WCHAR), xlpFile, NULL))
95                     {
96                         strcpyW(res, xlpFile);
97                         res += strlenW(xlpFile);
98                     }
99                     else
100                     {
101                         strcpyW(res, lpFile);
102                         res += strlenW(lpFile);
103                     }
104                 }
105                 break;
106             /*
107              * IE uses this alot for activating things such as windows media
108              * player. This is not verified to be fully correct but it appears
109              * to work just fine.
110              */
111             case 'L':
112                 strcpyW(res,lpFile);
113                 res += strlenW(lpFile);
114                 break;
115
116             default: FIXME("Unknown escape sequence %%%c\n", *fmt);
117             }
118             fmt++;
119             done = TRUE;
120         }
121         else
122             *res++ = *fmt++;
123     }
124     *res = '\0';
125     return done;
126 }
127
128 /*************************************************************************
129  *      SHELL_ExecuteW [Internal]
130  *
131  */
132 static UINT SHELL_ExecuteW(WCHAR *lpCmd, void *env, LPSHELLEXECUTEINFOW sei, BOOL shWait)
133 {
134     STARTUPINFOW  startup;
135     PROCESS_INFORMATION info;
136     UINT retval = 31;
137
138     TRACE("Execute %s from directory %s\n", debugstr_w(lpCmd), debugstr_w(sei->lpDirectory));
139     ZeroMemory(&startup,sizeof(STARTUPINFOW));
140     startup.cb = sizeof(STARTUPINFOW);
141     startup.dwFlags = STARTF_USESHOWWINDOW;
142     startup.wShowWindow = sei->nShow;
143     if (CreateProcessW(NULL, lpCmd, NULL, NULL, FALSE, 0,
144                        env, sei->lpDirectory, &startup, &info))
145     {
146         /* Give 30 seconds to the app to come up, if desired. Probably only needed
147            when starting app immediately before making a DDE connection. */
148         if (shWait)
149             if (WaitForInputIdle( info.hProcess, 30000 ) == -1)
150                 WARN("WaitForInputIdle failed: Error %ld\n", GetLastError() );
151         retval = 33;
152         if(sei->fMask & SEE_MASK_NOCLOSEPROCESS)
153             sei->hProcess = info.hProcess;
154         else
155             CloseHandle( info.hProcess );
156         CloseHandle( info.hThread );
157     }
158     else if ((retval = GetLastError()) >= 32)
159     {
160         FIXME("Strange error set by CreateProcess: %d\n", retval);
161         retval = ERROR_BAD_FORMAT;
162     }
163
164     TRACE("returning %u\n", retval);
165
166     sei->hInstApp = (HINSTANCE)retval;
167     return retval;
168 }
169
170
171 /***********************************************************************
172  *           SHELL_BuildEnvW    [Internal]
173  *
174  * Build the environment for the new process, adding the specified
175  * path to the PATH variable. Returned pointer must be freed by caller.
176  */
177 static void *SHELL_BuildEnvW( const WCHAR *path )
178 {
179     static const WCHAR wPath[] = {'P','A','T','H','=',0};
180     WCHAR *strings, *new_env;
181     WCHAR *p, *p2;
182     int total = strlenW(path) + 1;
183     BOOL got_path = FALSE;
184
185     if (!(strings = GetEnvironmentStringsW())) return NULL;
186     p = strings;
187     while (*p)
188     {
189         int len = strlenW(p) + 1;
190         if (!strncmpiW( p, wPath, 5 )) got_path = TRUE;
191         total += len;
192         p += len;
193     }
194     if (!got_path) total += 5;  /* we need to create PATH */
195     total++;  /* terminating null */
196
197     if (!(new_env = HeapAlloc( GetProcessHeap(), 0, total * sizeof(WCHAR) )))
198     {
199         FreeEnvironmentStringsW( strings );
200         return NULL;
201     }
202     p = strings;
203     p2 = new_env;
204     while (*p)
205     {
206         int len = strlenW(p) + 1;
207         memcpy( p2, p, len * sizeof(WCHAR) );
208         if (!strncmpiW( p, wPath, 5 ))
209         {
210             p2[len - 1] = ';';
211             strcpyW( p2 + len, path );
212             p2 += strlenW(path) + 1;
213         }
214         p += len;
215         p2 += len;
216     }
217     if (!got_path)
218     {
219         strcpyW( p2, wPath );
220         strcatW( p2, path );
221         p2 += strlenW(p2) + 1;
222     }
223     *p2 = 0;
224     FreeEnvironmentStringsW( strings );
225     return new_env;
226 }
227
228
229 /***********************************************************************
230  *           SHELL_TryAppPathW  [Internal]
231  *
232  * Helper function for SHELL_FindExecutable
233  * @param lpResult - pointer to a buffer of size MAX_PATH
234  * On entry: szName is a filename (probably without path separators).
235  * On exit: if szName found in "App Path", place full path in lpResult, and return true
236  */
237 static BOOL SHELL_TryAppPathW( LPCWSTR szName, LPWSTR lpResult, void**env)
238 {
239     static const WCHAR wszKeyAppPaths[] = {'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\','W','i','n','d','o','w','s',
240         '\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\','A','p','p',' ','P','a','t','h','s','\\',0};
241     static const WCHAR wPath[] = {'P','a','t','h',0};
242     HKEY hkApp = 0;
243     WCHAR buffer[1024];
244     LONG len;
245     LONG res;
246     BOOL found = FALSE;
247
248     if (env) *env = NULL;
249     strcpyW(buffer, wszKeyAppPaths);
250     strcatW(buffer, szName);
251     res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, buffer, 0, KEY_READ, &hkApp);
252     if (res) goto end;
253
254     len = MAX_PATH*sizeof(WCHAR);
255     res = RegQueryValueW(hkApp, NULL, lpResult, &len);
256     if (res) goto end;
257     found = TRUE;
258
259     if (env)
260     {
261         DWORD count = sizeof(buffer);
262         if (!RegQueryValueExW(hkApp, wPath, NULL, NULL, (LPBYTE)buffer, &count) && buffer[0])
263             *env = SHELL_BuildEnvW( buffer );
264     }
265
266 end:
267     if (hkApp) RegCloseKey(hkApp);
268     return found;
269 }
270
271 static UINT SHELL_FindExecutableByOperation(LPCWSTR lpPath, LPCWSTR lpFile, LPCWSTR lpOperation, LPWSTR key, LPWSTR filetype, LPWSTR command, LONG commandlen)
272 {
273     static const WCHAR wCommand[] = {'\\','c','o','m','m','a','n','d',0};
274
275     /* Looking for ...buffer\shell\<verb>\command */
276     strcatW(filetype, wszShell);
277     strcatW(filetype, lpOperation);
278     strcatW(filetype, wCommand);
279
280     if (RegQueryValueW(HKEY_CLASSES_ROOT, filetype, command,
281                        &commandlen) == ERROR_SUCCESS)
282     {
283         commandlen /= sizeof(WCHAR);
284         if (key) strcpyW(key, filetype);
285 #if 0
286         LPWSTR tmp;
287         WCHAR param[256];
288         LONG paramlen = sizeof(param);
289         static const WCHAR wSpace[] = {' ',0};
290
291         /* FIXME: it seems all Windows version don't behave the same here.
292          * the doc states that this ddeexec information can be found after
293          * the exec names.
294          * on Win98, it doesn't appear, but I think it does on Win2k
295          */
296         /* Get the parameters needed by the application
297            from the associated ddeexec key */
298         tmp = strstrW(filetype, wCommand);
299         tmp[0] = '\0';
300         strcatW(filetype, wDdeexec);
301         if (RegQueryValueW(HKEY_CLASSES_ROOT, filetype, param,
302                                      &paramlen) == ERROR_SUCCESS)
303         {
304             paramlen /= sizeof(WCHAR);
305             strcatW(command, wSpace);
306             strcatW(command, param);
307             commandlen += paramlen;
308         }
309 #endif
310
311         command[commandlen] = '\0';
312
313         return 33; /* FIXME see SHELL_FindExecutable() */
314     }
315
316     return 31;  /* default - 'No association was found' */
317 }
318
319 /*************************************************************************
320  *      SHELL_FindExecutable [Internal]
321  *
322  * Utility for code sharing between FindExecutable and ShellExecute
323  * in:
324  *      lpFile the name of a file
325  *      lpOperation the operation on it (open)
326  * out:
327  *      lpResult a buffer, big enough :-(, to store the command to do the
328  *              operation on the file
329  *      key a buffer, big enough, to get the key name to do actually the
330  *              command (it'll be used afterwards for more information
331  *              on the operation)
332  */
333 static UINT SHELL_FindExecutable(LPCWSTR lpPath, LPCWSTR lpFile, LPCWSTR lpOperation,
334                                  LPWSTR lpResult, LPWSTR key, void **env)
335 {
336     static const WCHAR wWindows[] = {'w','i','n','d','o','w','s',0};
337     static const WCHAR wPrograms[] = {'p','r','o','g','r','a','m','s',0};
338     static const WCHAR wExtensions[] = {'e','x','e',' ','p','i','f',' ','b','a','t',' ','c','m','d',' ','c','o','m',0};
339     WCHAR *extension = NULL; /* pointer to file extension */
340     WCHAR wtmpext[5];        /* local copy to mung as we please */
341     WCHAR filetype[256];     /* registry name for this filetype */
342     LONG  filetypelen = sizeof(filetype); /* length of above */
343     WCHAR command[256];      /* command from registry */
344     WCHAR wBuffer[256];      /* Used to GetProfileString */
345     UINT  retval = 31;       /* default - 'No association was found' */
346     WCHAR *tok;              /* token pointer */
347     WCHAR xlpFile[256] = {0}; /* result of SearchPath */
348
349     TRACE("%s\n", (lpFile != NULL) ? debugstr_w(lpFile) : "-");
350
351     lpResult[0] = '\0'; /* Start off with an empty return string */
352     if (key) *key = '\0';
353
354     /* trap NULL parameters on entry */
355     if ((lpFile == NULL) || (lpResult == NULL) || (lpOperation == NULL))
356     {
357         WARN("(lpFile=%s,lpResult=%s,lpOperation=%s): NULL parameter\n",
358              debugstr_w(lpFile), debugstr_w(lpOperation), debugstr_w(lpResult));
359         return 2; /* File not found. Close enough, I guess. */
360     }
361
362     if (SHELL_TryAppPathW( lpFile, lpResult, env ))
363     {
364         TRACE("found %s via App Paths\n", debugstr_w(lpResult));
365         return 33;
366     }
367
368     if (SearchPathW(lpPath, lpFile, wszExe, sizeof(xlpFile)/sizeof(WCHAR), xlpFile, NULL))
369     {
370         TRACE("SearchPathW returned non-zero\n");
371         lpFile = xlpFile;
372         /* Hey, isn't this value ignored?  Why make this call?  Shouldn't we return here?  --dank*/
373     }
374
375     /* First thing we need is the file's extension */
376     extension = strrchrW(xlpFile, '.'); /* Assume last "." is the one; */
377                                        /* File->Run in progman uses */
378                                        /* .\FILE.EXE :( */
379     TRACE("xlpFile=%s,extension=%s\n", debugstr_w(xlpFile), debugstr_w(extension));
380
381     if ((extension == NULL) || (extension == &xlpFile[strlenW(xlpFile)]))
382     {
383         WARN("Returning 31 - No association\n");
384         return 31; /* no association */
385     }
386
387     /* Make local copy & lowercase it for reg & 'programs=' lookup */
388     lstrcpynW(wtmpext, extension, 5);
389     CharLowerW(wtmpext);
390     TRACE("%s file\n", debugstr_w(wtmpext));
391
392     /* Three places to check: */
393     /* 1. win.ini, [windows], programs (NB no leading '.') */
394     /* 2. Registry, HKEY_CLASS_ROOT\<filetype>\shell\open\command */
395     /* 3. win.ini, [extensions], extension (NB no leading '.' */
396     /* All I know of the order is that registry is checked before */
397     /* extensions; however, it'd make sense to check the programs */
398     /* section first, so that's what happens here. */
399
400     /* See if it's a program - if GetProfileString fails, we skip this
401      * section. Actually, if GetProfileString fails, we've probably
402      * got a lot more to worry about than running a program... */
403     if (GetProfileStringW(wWindows, wPrograms, wExtensions, wBuffer, sizeof(wBuffer)/sizeof(WCHAR)) > 0)
404     {
405         CharLowerW(wBuffer);
406         tok = wBuffer;
407         while (*tok)
408         {
409             WCHAR *p = tok;
410             while (*p && *p != ' ' && *p != '\t') p++;
411             if (*p)
412             {
413                 *p++ = 0;
414                 while (*p == ' ' || *p == '\t') p++;
415             }
416
417             if (strcmpW(tok, &wtmpext[1]) == 0) /* have to skip the leading "." */
418             {
419                 strcpyW(lpResult, xlpFile);
420                 /* Need to perhaps check that the file has a path
421                  * attached */
422                 TRACE("found %s\n", debugstr_w(lpResult));
423                 return 33;
424
425                 /* Greater than 32 to indicate success FIXME According to the
426                  * docs, I should be returning a handle for the
427                  * executable. Does this mean I'm supposed to open the
428                  * executable file or something? More RTFM, I guess... */
429             }
430             tok = p;
431         }
432     }
433
434     /* Check registry */
435     if (RegQueryValueW(HKEY_CLASSES_ROOT, wtmpext, filetype, 
436                         &filetypelen) == ERROR_SUCCESS)
437     {
438         filetypelen /= sizeof(WCHAR);
439         filetype[filetypelen] = '\0';
440         TRACE("File type: %s\n", debugstr_w(filetype));
441     }
442
443     if (*filetype)
444     {
445         if (lpOperation)
446         {
447             /* pass the operation string to SHELL_FindExecutableByOperation() */
448             filetype[filetypelen] = '\0';
449             retval = SHELL_FindExecutableByOperation(lpPath, lpFile, lpOperation, key, filetype, command, sizeof(command));
450         }
451         else
452         {
453             WCHAR operation[MAX_PATH];
454             HKEY hkey;
455
456             /* Looking for ...buffer\shell\<operation>\command */
457             strcatW(filetype, wszShell);
458
459             /* enumerate the operation subkeys in the registry and search for one with an associated command */
460             if (RegOpenKeyW(HKEY_CLASSES_ROOT, filetype, &hkey) == ERROR_SUCCESS)
461             {
462                 int idx = 0;
463                 for(;; ++idx)
464                 {
465                     if (RegEnumKeyW(hkey, idx, operation, MAX_PATH) != ERROR_SUCCESS)
466                         break;
467
468                     filetype[filetypelen] = '\0';
469                     retval = SHELL_FindExecutableByOperation(lpPath, lpFile, operation, key, filetype, command, sizeof(command));
470
471                     if (retval > 32)
472                         break;
473             }
474                 RegCloseKey(hkey);
475             }
476         }
477
478         if (retval > 32)
479         {
480             SHELL_ArgifyW(lpResult, 1024 /*FIXME*/, command, xlpFile);
481         }
482     }
483     else /* Check win.ini */
484     {
485         static const WCHAR wExtensions[] = {'e','x','t','e','n','s','i','o','n','s',0};
486         static const WCHAR wEmpty[] = {0};
487
488         /* Toss the leading dot */
489         extension++;
490         if (GetProfileStringW(wExtensions, extension, wEmpty, command, sizeof(command)/sizeof(WCHAR)) > 0)
491         {
492             if (strlenW(command) != 0)
493             {
494                 strcpyW(lpResult, command);
495                 tok = strchrW(lpResult, '^'); /* should be ^.extension? */
496                 if (tok != NULL)
497                 {
498                     tok[0] = '\0';
499                     strcatW(lpResult, xlpFile); /* what if no dir in xlpFile? */
500                     tok = strchrW(command, '^'); /* see above */
501                     if ((tok != NULL) && (strlenW(tok)>5))
502                     {
503                         strcatW(lpResult, &tok[5]);
504                     }
505                 }
506                 retval = 33; /* FIXME - see above */
507             }
508         }
509     }
510
511     TRACE("returning %s\n", debugstr_w(lpResult));
512     return retval;
513 }
514
515 /******************************************************************
516  *              dde_cb
517  *
518  * callback for the DDE connection. not really usefull
519  */
520 static HDDEDATA CALLBACK dde_cb(UINT uType, UINT uFmt, HCONV hConv,
521                                 HSZ hsz1, HSZ hsz2,
522                                 HDDEDATA hData, DWORD dwData1, DWORD dwData2)
523 {
524     return NULL;
525 }
526
527 /******************************************************************
528  *              dde_connect
529  *
530  * ShellExecute helper. Used to do an operation with a DDE connection
531  *
532  * Handles both the direct connection (try #1), and if it fails,
533  * launching an application and trying (#2) to connect to it
534  *
535  */
536 static unsigned dde_connect(WCHAR* key, WCHAR* start, WCHAR* ddeexec,
537                             const WCHAR* lpFile, void *env,
538                             LPSHELLEXECUTEINFOW sei, SHELL_ExecuteW32 execfunc)
539 {
540     static const WCHAR wApplication[] = {'\\','a','p','p','l','i','c','a','t','i','o','n',0};
541     static const WCHAR wTopic[] = {'\\','t','o','p','i','c',0};
542     WCHAR *     endkey = key + strlenW(key);
543     WCHAR       app[256], topic[256], ifexec[256], res[256];
544     LONG        applen, topiclen, ifexeclen;
545     WCHAR *     exec;
546     DWORD       ddeInst = 0;
547     DWORD       tid;
548     HSZ         hszApp, hszTopic;
549     HCONV       hConv;
550     unsigned    ret = 31;
551
552     strcpyW(endkey, wApplication);
553     applen = sizeof(app);
554     if (RegQueryValueW(HKEY_CLASSES_ROOT, key, app, &applen) != ERROR_SUCCESS)
555     {
556         FIXME("default app name NIY %s\n", debugstr_w(key));
557         return 2;
558     }
559
560     strcpyW(endkey, wTopic);
561     topiclen = sizeof(topic);
562     if (RegQueryValueW(HKEY_CLASSES_ROOT, key, topic, &topiclen) != ERROR_SUCCESS)
563     {
564         static const WCHAR wSystem[] = {'S','y','s','t','e','m',0};
565         strcpyW(topic, wSystem);
566     }
567
568     if (DdeInitializeW(&ddeInst, dde_cb, APPCMD_CLIENTONLY, 0L) != DMLERR_NO_ERROR)
569     {
570         return 2;
571     }
572
573     hszApp = DdeCreateStringHandleW(ddeInst, app, CP_WINANSI);
574     hszTopic = DdeCreateStringHandleW(ddeInst, topic, CP_WINANSI);
575
576     hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL);
577     exec = ddeexec;
578     if (!hConv)
579     {
580         static const WCHAR wIfexec[] = {'\\','i','f','e','x','e','c',0};
581         TRACE("Launching '%s'\n", debugstr_w(start));
582         ret = execfunc(start, env, sei, TRUE);
583         if (ret < 32)
584         {
585             TRACE("Couldn't launch\n");
586             goto error;
587         }
588         hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL);
589         if (!hConv)
590         {
591             TRACE("Couldn't connect. ret=%d\n", ret);
592             DdeUninitialize(ddeInst);
593             SetLastError(ERROR_DDE_FAIL);
594             return 30; /* whatever */
595         }
596         strcpyW(endkey, wIfexec);
597         ifexeclen = sizeof(ifexec);
598         if (RegQueryValueW(HKEY_CLASSES_ROOT, key, ifexec, &ifexeclen) == ERROR_SUCCESS)
599         {
600             exec = ifexec;
601         }
602     }
603
604     SHELL_ArgifyW(res, sizeof(res)/sizeof(WCHAR), exec, lpFile);
605     TRACE("%s %s => %s\n", debugstr_w(exec), debugstr_w(lpFile), debugstr_w(res));
606
607     ret = (DdeClientTransaction((LPBYTE)res, (strlenW(res) + 1) * sizeof(WCHAR), hConv, 0L, 0,
608                                 XTYP_EXECUTE, 10000, &tid) != DMLERR_NO_ERROR) ? 31 : 33;
609     DdeDisconnect(hConv);
610  error:
611     DdeUninitialize(ddeInst);
612     return ret;
613 }
614
615 /*************************************************************************
616  *      execute_from_key [Internal]
617  */
618 static UINT execute_from_key(LPWSTR key, LPCWSTR lpFile, void *env,
619                              LPSHELLEXECUTEINFOW sei, SHELL_ExecuteW32 execfunc)
620 {
621     WCHAR cmd[1024] = {0};
622     LONG cmdlen = sizeof(cmd);
623     UINT retval = 31;
624
625     /* Get the application for the registry */
626     if (RegQueryValueW(HKEY_CLASSES_ROOT, key, cmd, &cmdlen) == ERROR_SUCCESS)
627     {
628         static const WCHAR wCommand[] = {'c','o','m','m','a','n','d',0};
629         static const WCHAR wDdeexec[] = {'d','d','e','e','x','e','c',0};
630         LPWSTR tmp;
631         WCHAR param[256] = {0};
632         LONG paramlen = sizeof(param);
633
634         /* Get the parameters needed by the application
635            from the associated ddeexec key */
636         tmp = strstrW(key, wCommand);
637         assert(tmp);
638         strcpyW(tmp, wDdeexec);
639
640         if (RegQueryValueW(HKEY_CLASSES_ROOT, key, param, &paramlen) == ERROR_SUCCESS)
641         {
642             TRACE("Got ddeexec %s => %s\n", debugstr_w(key), debugstr_w(param));
643             retval = dde_connect(key, cmd, param, lpFile, env, sei, execfunc);
644         }
645         else
646         {
647             /* Is there a replace() function anywhere? */
648             cmdlen /= sizeof(WCHAR);
649             cmd[cmdlen] = '\0';
650             SHELL_ArgifyW(param, sizeof(param)/sizeof(WCHAR), cmd, lpFile);
651             retval = execfunc(param, env, sei, FALSE);
652         }
653     }
654     else TRACE("ooch\n");
655
656     return retval;
657 }
658
659 /*************************************************************************
660  * FindExecutableA                      [SHELL32.@]
661  */
662 HINSTANCE WINAPI FindExecutableA(LPCSTR lpFile, LPCSTR lpDirectory, LPSTR lpResult)
663 {
664     HINSTANCE retval;
665     WCHAR *wFile = NULL, *wDirectory = NULL;
666     WCHAR wResult[MAX_PATH];
667
668     if (lpFile) __SHCloneStrAtoW(&wFile, lpFile);
669     if (lpDirectory) __SHCloneStrAtoW(&wDirectory, lpDirectory);
670
671     retval = FindExecutableW(wFile, wDirectory, wResult);
672     WideCharToMultiByte(CP_ACP, 0, wResult, -1, lpResult, MAX_PATH, NULL, NULL);
673     if (wFile) SHFree( wFile );
674     if (wDirectory) SHFree( wDirectory );
675
676     TRACE("returning %s\n", lpResult);
677     return (HINSTANCE)retval;
678 }
679
680 /*************************************************************************
681  * FindExecutableW                      [SHELL32.@]
682  */
683 HINSTANCE WINAPI FindExecutableW(LPCWSTR lpFile, LPCWSTR lpDirectory, LPWSTR lpResult)
684 {
685     UINT retval = 31;    /* default - 'No association was found' */
686     WCHAR old_dir[1024];
687
688     TRACE("File %s, Dir %s\n",
689           (lpFile != NULL ? debugstr_w(lpFile) : "-"), (lpDirectory != NULL ? debugstr_w(lpDirectory) : "-"));
690
691     lpResult[0] = '\0'; /* Start off with an empty return string */
692
693     /* trap NULL parameters on entry */
694     if ((lpFile == NULL) || (lpResult == NULL))
695     {
696         /* FIXME - should throw a warning, perhaps! */
697         return (HINSTANCE)2; /* File not found. Close enough, I guess. */
698     }
699
700     if (lpDirectory)
701     {
702         GetCurrentDirectoryW(sizeof(old_dir)/sizeof(WCHAR), old_dir);
703         SetCurrentDirectoryW(lpDirectory);
704     }
705
706     retval = SHELL_FindExecutable(lpDirectory, lpFile, wszOpen, lpResult, NULL, NULL);
707
708     TRACE("returning %s\n", debugstr_w(lpResult));
709     if (lpDirectory)
710         SetCurrentDirectoryW(old_dir);
711     return (HINSTANCE)retval;
712 }
713
714 /*************************************************************************
715  *      ShellExecuteExW32 [Internal]
716  */
717 BOOL WINAPI ShellExecuteExW32 (LPSHELLEXECUTEINFOW sei, SHELL_ExecuteW32 execfunc)
718 {
719     static const WCHAR wQuote[] = {'"',0};
720     static const WCHAR wSpace[] = {' ',0};
721     static const WCHAR wWww[] = {'w','w','w',0};
722     static const WCHAR wFile[] = {'f','i','l','e',0};
723     static const WCHAR wHttp[] = {'h','t','t','p',':','/','/',0};
724     WCHAR wszApplicationName[MAX_PATH+2],wszCommandline[1024],wszPidl[20],wfileName[MAX_PATH];
725     LPWSTR pos;
726     void *env;
727     int gap, len;
728     WCHAR lpstrProtocol[256];
729     LPCWSTR lpFile;
730     UINT retval = 31;
731     WCHAR wcmd[1024];
732     BOOL done;
733
734     TRACE("mask=0x%08lx hwnd=%p verb=%s file=%s parm=%s dir=%s show=0x%08x class=%s\n",
735             sei->fMask, sei->hwnd, debugstr_w(sei->lpVerb),
736             debugstr_w(sei->lpFile), debugstr_w(sei->lpParameters),
737             debugstr_w(sei->lpDirectory), sei->nShow,
738             (sei->fMask & SEE_MASK_CLASSNAME) ? debugstr_w(sei->lpClass) : "not used");
739
740     sei->hProcess = NULL;
741     ZeroMemory(wszApplicationName,MAX_PATH);
742     if (sei->lpFile)
743         strcpyW(wszApplicationName, sei->lpFile);
744
745     ZeroMemory(wszCommandline,1024);
746     if (sei->lpParameters)
747         strcpyW(wszCommandline, sei->lpParameters);
748
749     if (sei->fMask & (SEE_MASK_INVOKEIDLIST | SEE_MASK_ICON | SEE_MASK_HOTKEY |
750         SEE_MASK_CONNECTNETDRV | SEE_MASK_FLAG_DDEWAIT |
751         SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI | SEE_MASK_UNICODE |
752         SEE_MASK_NO_CONSOLE | SEE_MASK_ASYNCOK | SEE_MASK_HMONITOR ))
753     {
754         FIXME("flags ignored: 0x%08lx\n", sei->fMask);
755     }
756
757     /* process the IDList */
758     if ( (sei->fMask & SEE_MASK_INVOKEIDLIST) == SEE_MASK_INVOKEIDLIST) /*0x0c*/
759     {
760         wszApplicationName[0] = '"';
761         SHGetPathFromIDListW(sei->lpIDList,wszApplicationName + 1);
762         strcatW(wszApplicationName, wQuote);
763         TRACE("-- idlist=%p (%s)\n", sei->lpIDList, debugstr_w(wszApplicationName));
764     }
765     else
766     {
767         if (sei->fMask & SEE_MASK_IDLIST )
768         {
769             static const WCHAR wI[] = {'%','I',0}, wP[] = {':','%','p',0};
770             pos = strstrW(wszCommandline, wI);
771             if (pos)
772             {
773                 LPVOID pv;
774                 HGLOBAL hmem = SHAllocShared ( sei->lpIDList, ILGetSize(sei->lpIDList), 0);
775                 pv = SHLockShared(hmem,0);
776                 sprintfW(wszPidl,wP,pv );
777                 SHUnlockShared(pv);
778
779                 gap = strlenW(wszPidl);
780                 len = strlenW(pos)-2;
781                 memmove(pos+gap,pos+2,len*sizeof(WCHAR));
782                 memcpy(pos,wszPidl,gap*sizeof(WCHAR));
783             }
784         }
785     }
786
787     if (sei->fMask & (SEE_MASK_CLASSNAME | SEE_MASK_CLASSKEY))
788     {
789         /* launch a document by fileclass like 'WordPad.Document.1' */
790         /* the Commandline contains 'c:\Path\wordpad.exe "%1"' */
791         /* FIXME: szCommandline should not be of a fixed size. Fixed to 1024, MAX_PATH is way too short! */
792         HCR_GetExecuteCommandW((sei->fMask & SEE_MASK_CLASSKEY) ? sei->hkeyClass : NULL,
793                                (sei->fMask & SEE_MASK_CLASSNAME) ? sei->lpClass: NULL,
794                                (sei->lpVerb) ? sei->lpVerb : wszOpen,
795                                wszCommandline, sizeof(wszCommandline)/sizeof(WCHAR));
796
797         /* FIXME: get the extension of lpFile, check if it fits to the lpClass */
798         TRACE("SEE_MASK_CLASSNAME->'%s', doc->'%s'\n", debugstr_w(wszCommandline), debugstr_w(wszApplicationName));
799
800         wcmd[0] = '\0';
801         done = SHELL_ArgifyW(wcmd, sizeof(wcmd)/sizeof(WCHAR), wszCommandline, wszApplicationName);
802         if (!done && wszApplicationName[0])
803         {
804             strcatW(wcmd, wSpace);
805             strcatW(wcmd, wszApplicationName);
806         }
807         retval = execfunc(wcmd, NULL, sei, FALSE);
808         if (retval > 32)
809             return TRUE;
810         else
811             return FALSE;
812     }
813
814     /* Else, try to execute the filename */
815     TRACE("execute:'%s','%s'\n", debugstr_w(wszApplicationName), debugstr_w(wszCommandline));
816
817     strcpyW(wfileName, wszApplicationName);
818     lpFile = wfileName;
819     if (wszCommandline[0]) {
820         strcatW(wszApplicationName, wSpace);
821         strcatW(wszApplicationName, wszCommandline);
822     }
823
824     retval = execfunc(wszApplicationName, NULL, sei, FALSE);
825     if (retval > 32)
826         return TRUE;
827
828     /* Else, try to find the executable */
829     wcmd[0] = '\0';
830     retval = SHELL_FindExecutable(sei->lpDirectory, lpFile, sei->lpVerb, wcmd, lpstrProtocol, &env);
831     if (retval > 32)  /* Found */
832     {
833         WCHAR wszQuotedCmd[MAX_PATH+2];
834         /* Must quote to handle case where cmd contains spaces, 
835          * else security hole if malicious user creates executable file "C:\\Program"
836          */
837         strcpyW(wszQuotedCmd, wQuote);
838         strcatW(wszQuotedCmd, wcmd);
839         strcatW(wszQuotedCmd, wQuote);
840         if (wszCommandline[0]) {
841             strcatW(wszQuotedCmd, wSpace);
842             strcatW(wszQuotedCmd, wszCommandline);
843         }
844         TRACE("%s/%s => %s/%s\n", debugstr_w(wszApplicationName), debugstr_w(sei->lpVerb), debugstr_w(wszQuotedCmd), debugstr_w(lpstrProtocol));
845         if (*lpstrProtocol)
846             retval = execute_from_key(lpstrProtocol, wszApplicationName, env, sei, execfunc);
847         else
848             retval = execfunc(wszQuotedCmd, env, sei, FALSE);
849         if (env) HeapFree( GetProcessHeap(), 0, env );
850     }
851     else if (PathIsURLW((LPWSTR)lpFile))    /* File not found, check for URL */
852     {
853         static const WCHAR wShell[] = {'\\','s','h','e','l','l','\\',0};
854         static const WCHAR wCommand[] = {'\\','c','o','m','m','a','n','d',0};
855         LPWSTR lpstrRes;
856         INT iSize;
857
858         lpstrRes = strchrW(lpFile, ':');
859         if (lpstrRes)
860             iSize = lpstrRes - lpFile;
861         else
862             iSize = strlenW(lpFile);
863
864         TRACE("Got URL: %s\n", debugstr_w(lpFile));
865         /* Looking for ...protocol\shell\lpOperation\command */
866         strncpyW(lpstrProtocol, lpFile, iSize);
867         lpstrProtocol[iSize] = '\0';
868         strcatW(lpstrProtocol, wShell);
869         strcatW(lpstrProtocol, sei->lpVerb? sei->lpVerb: wszOpen);
870         strcatW(lpstrProtocol, wCommand);
871
872         /* Remove File Protocol from lpFile */
873         /* In the case file://path/file     */
874         if (!strncmpiW(lpFile, wFile, iSize))
875         {
876             lpFile += iSize;
877             while (*lpFile == ':') lpFile++;
878         }
879         retval = execute_from_key(lpstrProtocol, lpFile, NULL, sei, execfunc);
880     }
881     /* Check if file specified is in the form www.??????.*** */
882     else if (!strncmpiW(lpFile, wWww, 3))
883     {
884         /* if so, append lpFile http:// and call ShellExecute */
885         WCHAR lpstrTmpFile[256];
886         strcpyW(lpstrTmpFile, wHttp);
887         strcatW(lpstrTmpFile, lpFile);
888         retval = (UINT)ShellExecuteW(sei->hwnd, sei->lpVerb, lpstrTmpFile, NULL, NULL, 0);
889     }
890
891     TRACE("retval %u\n", retval);
892
893     if (retval <= 32)
894     {
895         sei->hInstApp = (HINSTANCE)retval;
896         return FALSE;
897     }
898
899     sei->hInstApp = (HINSTANCE)33;
900     return TRUE;
901 }
902
903 /*************************************************************************
904  * ShellExecuteA                        [SHELL32.290]
905  */
906 HINSTANCE WINAPI ShellExecuteA(HWND hWnd, LPCSTR lpOperation,LPCSTR lpFile,
907                                LPCSTR lpParameters,LPCSTR lpDirectory, INT iShowCmd)
908 {
909     SHELLEXECUTEINFOA sei;
910     HANDLE hProcess = 0;
911
912     TRACE("\n");
913     sei.cbSize = sizeof(sei);
914     sei.fMask = 0;
915     sei.hwnd = hWnd;
916     sei.lpVerb = lpOperation;
917     sei.lpFile = lpFile;
918     sei.lpParameters = lpParameters;
919     sei.lpDirectory = lpDirectory;
920     sei.nShow = iShowCmd;
921     sei.lpIDList = 0;
922     sei.lpClass = 0;
923     sei.hkeyClass = 0;
924     sei.dwHotKey = 0;
925     sei.hProcess = hProcess;
926
927     ShellExecuteExA (&sei);
928     return sei.hInstApp;
929 }
930
931 /*************************************************************************
932  * ShellExecuteEx                               [SHELL32.291]
933  *
934  */
935 BOOL WINAPI ShellExecuteExAW (LPVOID sei)
936 {
937     if (SHELL_OsIsUnicode())
938         return ShellExecuteExW32 (sei, SHELL_ExecuteW);
939     return ShellExecuteExA (sei);
940 }
941
942 /*************************************************************************
943  * ShellExecuteExA                              [SHELL32.292]
944  *
945  */
946 BOOL WINAPI ShellExecuteExA (LPSHELLEXECUTEINFOA sei)
947 {
948     SHELLEXECUTEINFOW seiW;
949     BOOL ret;
950     WCHAR *wVerb = NULL, *wFile = NULL, *wParameters = NULL, *wDirectory = NULL, *wClass = NULL;
951
952     TRACE("%p\n", sei);
953
954     memcpy(&seiW, sei, sizeof(SHELLEXECUTEINFOW));
955
956     if (sei->lpVerb)
957         seiW.lpVerb = __SHCloneStrAtoW(&wVerb, sei->lpVerb);
958
959     if (sei->lpFile)
960         seiW.lpFile = __SHCloneStrAtoW(&wFile, sei->lpFile);
961
962     if (sei->lpParameters)
963         seiW.lpParameters = __SHCloneStrAtoW(&wParameters, sei->lpParameters);
964
965     if (sei->lpDirectory)
966         seiW.lpDirectory = __SHCloneStrAtoW(&wDirectory, sei->lpDirectory);
967
968     if ((sei->fMask & SEE_MASK_CLASSNAME) && sei->lpClass)
969         seiW.lpClass = __SHCloneStrAtoW(&wClass, sei->lpClass);
970     else
971         seiW.lpClass = NULL;
972
973     ret = ShellExecuteExW32 (&seiW, SHELL_ExecuteW);
974
975     sei->hInstApp = seiW.hInstApp;
976
977     if (wVerb) SHFree(wVerb);
978     if (wFile) SHFree(wFile);
979     if (wParameters) SHFree(wParameters);
980     if (wDirectory) SHFree(wDirectory);
981     if (wClass) SHFree(wClass);
982
983     return ret;
984 }
985
986 /*************************************************************************
987  * ShellExecuteExW                              [SHELL32.293]
988  *
989  */
990 BOOL WINAPI ShellExecuteExW (LPSHELLEXECUTEINFOW sei)
991 {
992     return  ShellExecuteExW32 (sei, SHELL_ExecuteW);
993 }
994
995 /*************************************************************************
996  * ShellExecuteW                        [SHELL32.294]
997  * from shellapi.h
998  * WINSHELLAPI HINSTANCE APIENTRY ShellExecuteW(HWND hwnd, LPCWSTR lpOperation,
999  * LPCWSTR lpFile, LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd);
1000  */
1001 HINSTANCE WINAPI ShellExecuteW(HWND hwnd, LPCWSTR lpOperation, LPCWSTR lpFile,
1002                                LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd)
1003 {
1004     SHELLEXECUTEINFOW sei;
1005     HANDLE hProcess = 0;
1006
1007     TRACE("\n");
1008     sei.cbSize = sizeof(sei);
1009     sei.fMask = 0;
1010     sei.hwnd = hwnd;
1011     sei.lpVerb = lpOperation;
1012     sei.lpFile = lpFile;
1013     sei.lpParameters = lpParameters;
1014     sei.lpDirectory = lpDirectory;
1015     sei.nShow = nShowCmd;
1016     sei.lpIDList = 0;
1017     sei.lpClass = 0;
1018     sei.hkeyClass = 0;
1019     sei.dwHotKey = 0;
1020     sei.hProcess = hProcess;
1021
1022     ShellExecuteExW32 (&sei, SHELL_ExecuteW);
1023     return sei.hInstApp;
1024 }