SHELL_SHGetPathFromIDList()
[reactos.git] / reactos / lib / 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 "heap.h"
41 #include "shellapi.h"
42 #include "wingdi.h"
43 #include "winuser.h"
44 #include "shlobj.h"
45 #include "shlwapi.h"
46 #include "ddeml.h"
47
48 #include "wine/winbase16.h"
49 #include "shell32_main.h"
50 #include "undocshell.h"
51
52 #include "wine/debug.h"
53
54 WINE_DEFAULT_DEBUG_CHANNEL(exec);
55
56 /***********************************************************************
57 * this function is supposed to expand the escape sequences found in the registry
58 * some diving reported that the following were used:
59 * + %1, %2... seem to report to parameter of index N in ShellExecute pmts
60 * %1 file
61 * %2 printer
62 * %3 driver
63 * %4 port
64 * %I address of a global item ID (explorer switch /idlist)
65 * %L seems to be %1 as long filename followed by the 8+3 variation
66 * %S ???
67 * %* all following parameters (see batfile)
68 */
69 static BOOL argify(char* out, int len, const char* fmt, const char* lpFile, LPITEMIDLIST pidl, LPCSTR args)
70 {
71 char xlpFile[1024];
72 BOOL done = FALSE;
73 LPVOID pv;
74 char *res = out;
75 const char *cmd;
76
77 while (*fmt)
78 {
79 if (*fmt == '%')
80 {
81 switch (*++fmt)
82 {
83 case '\0':
84 case '%':
85 *res++ = '%';
86 break;
87
88 case '2':
89 case '3':
90 case '4':
91 case '5':
92 case '6':
93 case '7':
94 case '8':
95 case '9':
96 case '0':
97 case '*':
98 if (args)
99 {
100 if (*fmt == '*')
101 {
102 *res++ = '"';
103 while(*args)
104 *res++ = *args++;
105 *res++ = '"';
106 }
107 else
108 {
109 *res++ = '"';
110 while(*args && !isspace(*args))
111 *res++ = *args++;
112 *res++ = '"';
113
114 while(isspace(*args))
115 ++args;
116 }
117 }
118 else
119 {
120 case '1':
121 if (!done || (*fmt == '1'))
122 {
123 /*FIXME Is SearchPath() really needed? We already have separated out the parameter string in args. */
124 if (SearchPathA(NULL, lpFile, ".exe", sizeof(xlpFile), xlpFile, NULL))
125 cmd = xlpFile;
126 else
127 cmd = lpFile;
128
129 /* Add double quotation marks unless we already have them (e.g.: "%1" %* for exefile) */
130 if (res != out && *(res - 1) == '"')
131 {
132 strcpy(res, cmd);
133 res += strlen(cmd);
134 }
135 else
136 {
137 *res++ = '"';
138 strcpy(res, cmd);
139 res += strlen(cmd);
140 *res++ = '"';
141 }
142 }
143 }
144 break;
145
146 /*
147 * IE uses this alot for activating things such as windows media
148 * player. This is not verified to be fully correct but it appears
149 * to work just fine.
150 */
151 case 'l':
152 case 'L':
153 if (lpFile) {
154 strcpy(res, lpFile);
155 res += strlen(lpFile);
156 }
157 break;
158
159 case 'i':
160 case 'I':
161 if (pidl) {
162 HGLOBAL hmem = SHAllocShared(pidl, ILGetSize(pidl), 0);
163 pv = SHLockShared(hmem, 0);
164 res += sprintf(res, ":%p", pv);
165 SHUnlockShared(pv);
166 }
167 break;
168
169 default: FIXME("Unknown escape sequence %%%c\n", *fmt);
170 }
171 fmt++;
172 done = TRUE;
173 }
174 else
175 *res++ = *fmt++;
176 }
177 *res = '\0';
178 return done;
179 }
180
181 /*************************************************************************
182 * _ResolveShortCut [Internal]
183 *
184 */
185 static HRESULT _ResolveShortCut(LPWSTR path, LPWSTR wdir, LPWSTR args, HWND hwnd, int* pshowcmd, LPITEMIDLIST* ppidl)
186 {
187 IShellFolder* psf;
188
189 HRESULT hr = SHGetDesktopFolder(&psf);
190
191 *ppidl = NULL;
192
193 if (SUCCEEDED(hr)) {
194 LPITEMIDLIST pidl;
195 ULONG l;
196
197 hr = IShellFolder_ParseDisplayName(psf, 0, 0, path, &l, &pidl, 0);
198
199 if (SUCCEEDED(hr)) {
200 IShellLinkW* psl;
201
202 hr = IShellFolder_GetUIObjectOf(psf, NULL, 1, (LPCITEMIDLIST*)&pidl, &IID_IShellLinkW, NULL, (LPVOID*)&psl);
203
204 if (SUCCEEDED(hr)) {
205 hr = IShellLinkW_Resolve(psl, hwnd, SLR_NO_UI);
206
207 if (SUCCEEDED(hr)) {
208 hr = IShellLinkW_GetPath(psl, path, MAX_PATH, NULL, SLGP_UNCPRIORITY);
209
210 if (SUCCEEDED(hr)) {
211 if (!*path)
212 /* We could not translate the PIDL in the shell link into a valid file system path - so return the PIDL instead. */
213 hr = IShellLinkW_GetIDList(psl, ppidl);
214
215 if (SUCCEEDED(hr)) {
216 /* get command line arguments and display mode if available */
217 IShellLinkW_GetWorkingDirectory(psl, wdir, MAX_PATH);
218 IShellLinkW_GetArguments(psl, args, MAX_PATH);
219 IShellLinkW_GetShowCmd(psl, pshowcmd);
220 }
221 }
222 }
223
224 IShellLinkW_Release(psl);
225 }
226
227 SHFree(pidl);
228 }
229
230 IShellFolder_Release(psf);
231 }
232
233 return hr;
234 }
235
236 /*************************************************************************
237 * SHELL_ExecuteA [Internal]
238 *
239 */
240 static UINT SHELL_ExecuteA(char *lpCmd, void *env, const char* lpDir, LPSHELLEXECUTEINFOA sei, BOOL shWait)
241 {
242 STARTUPINFOA startup;
243 PROCESS_INFORMATION info;
244 UINT retval = 31;
245
246 TRACE("Execute %s from directory %s\n", lpCmd, lpDir);
247 ZeroMemory(&startup,sizeof(STARTUPINFOA));
248 startup.cb = sizeof(STARTUPINFOA);
249 startup.dwFlags = STARTF_USESHOWWINDOW;
250 startup.wShowWindow = sei->nShow;
251 if (CreateProcessA(NULL, lpCmd, NULL, NULL, FALSE, 0,
252 env, lpDir, &startup, &info))
253 {
254 /* Give 30 seconds to the app to come up, if desired. Probably only needed
255 when starting app immediately before making a DDE connection. */
256 if (shWait)
257 if (WaitForInputIdle( info.hProcess, 30000 ) == -1)
258 WARN("WaitForInputIdle failed: Error %ld\n", GetLastError() );
259 retval = 33;
260 if(sei->fMask & SEE_MASK_NOCLOSEPROCESS)
261 sei->hProcess = info.hProcess;
262 else
263 CloseHandle( info.hProcess );
264 CloseHandle( info.hThread );
265 }
266 else if ((retval = GetLastError()) >= 32)
267 {
268 FIXME("Strange error set by CreateProcess: %d\n", retval);
269 retval = ERROR_BAD_FORMAT;
270 }
271
272 sei->hInstApp = (HINSTANCE)retval;
273 return retval;
274 }
275
276
277 /***********************************************************************
278 * build_env
279 *
280 * Build the environment for the new process, adding the specified
281 * path to the PATH variable. Returned pointer must be freed by caller.
282 */
283 static void *build_env( const char *path )
284 {
285 char *strings, *new_env;
286 char *p, *p2;
287 int total = strlen(path) + 1;
288 BOOL got_path = FALSE;
289
290 if (!(strings = GetEnvironmentStringsA())) return NULL;
291 p = strings;
292 while (*p)
293 {
294 int len = strlen(p) + 1;
295 if (!strncasecmp( p, "PATH=", 5 )) got_path = TRUE;
296 total += len;
297 p += len;
298 }
299 if (!got_path) total += 5; /* we need to create PATH */
300 total++; /* terminating null */
301
302 if (!(new_env = HeapAlloc( GetProcessHeap(), 0, total )))
303 {
304 FreeEnvironmentStringsA( strings );
305 return NULL;
306 }
307 p = strings;
308 p2 = new_env;
309 while (*p)
310 {
311 int len = strlen(p) + 1;
312 memcpy( p2, p, len );
313 if (!strncasecmp( p, "PATH=", 5 ))
314 {
315 p2[len - 1] = ';';
316 strcpy( p2 + len, path );
317 p2 += strlen(path) + 1;
318 }
319 p += len;
320 p2 += len;
321 }
322 if (!got_path)
323 {
324 strcpy( p2, "PATH=" );
325 strcat( p2, path );
326 p2 += strlen(p2) + 1;
327 }
328 *p2 = 0;
329 FreeEnvironmentStringsA( strings );
330 return new_env;
331 }
332
333
334 /***********************************************************************
335 * SHELL_TryAppPath
336 *
337 * Helper function for SHELL_FindExecutable
338 * @param lpResult - pointer to a buffer of size MAX_PATH
339 * On entry: szName is a filename (probably without path separators).
340 * On exit: if szName found in "App Path", place full path in lpResult, and return true
341 */
342 static BOOL SHELL_TryAppPath( LPCSTR szName, LPSTR lpResult, void**env)
343 {
344 HKEY hkApp = 0;
345 char buffer[256];
346 LONG len;
347 LONG res;
348 BOOL found = FALSE;
349
350 if (env) *env = NULL;
351 sprintf(buffer, "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\%s", szName);
352 res = RegOpenKeyExA(HKEY_LOCAL_MACHINE, buffer, 0, KEY_READ, &hkApp);
353 if (res) goto end;
354
355 len = MAX_PATH;
356 res = RegQueryValueA(hkApp, NULL, lpResult, &len);
357 if (res) goto end;
358 found = TRUE;
359
360 if (env)
361 {
362 DWORD count = sizeof(buffer);
363 if (!RegQueryValueExA(hkApp, "Path", NULL, NULL, buffer, &count) && buffer[0])
364 *env = build_env( buffer );
365 }
366
367 end:
368 if (hkApp) RegCloseKey(hkApp);
369 return found;
370 }
371
372 static UINT _FindExecutableByOperation(LPCSTR lpPath, LPCSTR lpFile, LPCSTR lpOperation, LPSTR key, LPSTR filetype, LPSTR command)
373 {
374 LONG commandlen = 256; /* This is the most DOS can handle :) */
375
376 /* Looking for ...buffer\shell\<verb>\command */
377 strcat(filetype, "\\shell\\");
378 strcat(filetype, lpOperation);
379 strcat(filetype, "\\command");
380
381 if (RegQueryValueA(HKEY_CLASSES_ROOT, filetype, command, &commandlen) == ERROR_SUCCESS)
382 {
383 if (key) strcpy(key, filetype);
384 #if 0
385 LPSTR tmp;
386 char param[256];
387 LONG paramlen = 256;
388
389 /* FIXME: it seems all Windows version don't behave the same here.
390 * the doc states that this ddeexec information can be found after
391 * the exec names.
392 * on Win98, it doesn't appear, but I think it does on Win2k
393 */
394 /* Get the parameters needed by the application
395 from the associated ddeexec key */
396 tmp = strstr(filetype, "command");
397 tmp[0] = '\0';
398 strcat(filetype, "ddeexec");
399
400 if (RegQueryValueA(HKEY_CLASSES_ROOT, filetype, param, &paramlen) == ERROR_SUCCESS)
401 {
402 strcat(command, " ");
403 strcat(command, param);
404 commandlen += paramlen;
405 }
406 #endif
407
408 command[commandlen] = '\0';
409
410 return 33; /* FIXME see SHELL_FindExecutable() */
411 }
412
413 return 31; /* default - 'No association was found' */
414 }
415
416 /*************************************************************************
417 * SHELL_FindExecutable [Internal]
418 *
419 * Utility for code sharing between FindExecutable and ShellExecute
420 * in:
421 * lpFile the name of a file
422 * lpOperation the operation on it (open)
423 * out:
424 * lpResult a buffer, big enough :-(, to store the command to do the
425 * operation on the file
426 * key a buffer, big enough, to get the key name to do actually the
427 * command (it'll be used afterwards for more information
428 * on the operation)
429 */
430 UINT SHELL_FindExecutable(LPCSTR lpPath, LPCSTR lpFile, LPCSTR lpOperation,
431 LPSTR lpResult, LPSTR key, void **env, LPITEMIDLIST pidl, LPCSTR args)
432 {
433 char *extension = NULL; /* pointer to file extension */
434 char tmpext[5]; /* local copy to munge as we please */
435 char filetype[256]; /* registry name for this filetype */
436 LONG filetypelen = 256; /* length of above */
437 char command[256]; /* command from registry */
438 char buffer[256]; /* Used to GetProfileString */
439 UINT retval = 31; /* default - 'No association was found' */
440 char *tok; /* token pointer */
441 char xlpFile[256] = ""; /* result of SearchPath */
442 DWORD attribs; /* file attributes */
443
444 TRACE("%s\n", (lpFile != NULL) ? lpFile : "-");
445
446 lpResult[0] = '\0'; /* Start off with an empty return string */
447 if (key) *key = '\0';
448
449 /* trap NULL parameters on entry */
450 if ((lpFile == NULL) || (lpResult == NULL))
451 {
452 WARN("(lpFile=%s,lpResult=%s): NULL parameter\n", lpFile, lpResult);
453 return 2; /* File not found. Close enough, I guess. */
454 }
455
456 if (SHELL_TryAppPath( lpFile, lpResult, env ))
457 {
458 TRACE("found %s via App Paths\n", lpResult);
459 return 33;
460 }
461
462 if (SearchPathA(lpPath, lpFile, ".exe", sizeof(xlpFile), xlpFile, NULL))
463 {
464 TRACE("SearchPathA returned non-zero\n");
465 lpFile = xlpFile;
466 /* Hey, isn't this value ignored? Why make this call? Shouldn't we return here? --dank*/
467 }
468
469 attribs = GetFileAttributesA(lpFile);
470
471 if (attribs!=INVALID_FILE_ATTRIBUTES && (attribs&FILE_ATTRIBUTE_DIRECTORY))
472 {
473 strcpy(filetype, "Folder");
474 filetypelen = 6; /* strlen("Folder") */
475 }
476 else
477 {
478 /* First thing we need is the file's extension */
479 extension = PathFindExtensionA(xlpFile); /* Assume last "." is the one; */
480 /* File->Run in progman uses */
481 /* .\FILE.EXE :( */
482 TRACE("xlpFile=%s,extension=%s\n", xlpFile, extension);
483
484 if ((extension == NULL) || (extension == &xlpFile[strlen(xlpFile)]))
485 {
486 WARN("Returning 31 - No association\n");
487 return 31; /* no association */
488 }
489
490 /* Make local copy & lowercase it for reg & 'programs=' lookup */
491 lstrcpynA(tmpext, extension, 5);
492 CharLowerA(tmpext);
493 TRACE("%s file\n", tmpext);
494
495 /* Three places to check: */
496 /* 1. win.ini, [windows], programs (NB no leading '.') */
497 /* 2. Registry, HKEY_CLASS_ROOT\<filetype>\shell\open\command */
498 /* 3. win.ini, [extensions], extension (NB no leading '.' */
499 /* All I know of the order is that registry is checked before */
500 /* extensions; however, it'd make sense to check the programs */
501 /* section first, so that's what happens here. */
502
503 /* See if it's a program - if GetProfileString fails, we skip this
504 * section. Actually, if GetProfileString fails, we've probably
505 * got a lot more to worry about than running a program... */
506 if (GetProfileStringA("windows", "programs", "exe pif bat cmd com",
507 buffer, sizeof(buffer)) > 0)
508 {
509 UINT i;
510
511 for (i=0; i<strlen(buffer); i++) buffer[i] = tolower(buffer[i]);
512
513 tok = strtok(buffer, " \t"); /* ? */
514 while (tok!= NULL)
515 {
516 if (strcmp(tok, &tmpext[1]) == 0) /* have to skip the leading "." */
517 {
518 strcpy(lpResult, xlpFile);
519 /* Need to perhaps check that the file has a path
520 * attached */
521 TRACE("found %s\n", lpResult);
522 return 33;
523
524 /* Greater than 32 to indicate success FIXME According to the
525 * docs, I should be returning a handle for the
526 * executable. Does this mean I'm supposed to open the
527 * executable file or something? More RTFM, I guess... */
528 }
529 tok = strtok(NULL, " \t");
530 }
531 }
532
533 /* Check registry */
534 if (RegQueryValueA(HKEY_CLASSES_ROOT, tmpext, filetype, &filetypelen) != ERROR_SUCCESS)
535 *filetype = '\0';
536 }
537
538 if (*filetype)
539 {
540 if (lpOperation)
541 {
542 /* pass the operation string to _FindExecutableByOperation() */
543 filetype[filetypelen] = '\0';
544 retval = _FindExecutableByOperation(lpPath, lpFile, lpOperation, key, filetype, command);
545 }
546 else
547 {
548 char operation[MAX_PATH];
549 HKEY hkey;
550
551 strcat(filetype, "\\shell");
552
553 /* enumerate the operation subkeys in the registry and search for one with an associated command */
554 if (RegOpenKeyA(HKEY_CLASSES_ROOT, filetype, &hkey) == ERROR_SUCCESS)
555 {
556 int idx = 0;
557 for(;; ++idx)
558 {
559 if (RegEnumKeyA(hkey, idx, operation, MAX_PATH) != ERROR_SUCCESS)
560 break;
561
562 filetype[filetypelen] = '\0';
563 retval = _FindExecutableByOperation(lpPath, lpFile, operation, key, filetype, command);
564
565 if (retval > 32)
566 break;
567 }
568
569 RegCloseKey(hkey);
570 }
571 }
572
573 if (retval > 32)
574 {
575 argify(lpResult, sizeof(lpResult), command, xlpFile, pidl, args);
576
577 /* Remove double quotation marks */
578 if (*lpResult == '"')
579 {
580 char *p = lpResult;
581 while (*(p + 1) != '"' && *(p + 1) != ' ')
582 {
583 *p = *(p + 1);
584 p++;
585 }
586 *p = '\0';
587 }
588 }
589 }
590 else if (extension) /* Check win.ini */
591 {
592 /* Toss the leading dot */
593 extension++;
594 if (GetProfileStringA("extensions", extension, "", command,
595 sizeof(command)) > 0)
596 {
597 if (strlen(command) != 0)
598 {
599 strcpy(lpResult, command);
600 tok = strstr(lpResult, "^"); /* should be ^.extension? */
601
602 if (tok != NULL)
603 {
604 tok[0] = '\0';
605 strcat(lpResult, xlpFile); /* what if no dir in xlpFile? */
606 tok = strstr(command, "^"); /* see above */
607 if ((tok != NULL) && (strlen(tok)>5))
608 strcat(lpResult, &tok[5]);
609 }
610
611 retval = 33; /* FIXME - see above */
612 }
613 }
614 }
615
616 TRACE("returning %s\n", lpResult);
617 return retval;
618 }
619
620 /******************************************************************
621 * dde_cb
622 *
623 * callback for the DDE connection. not really usefull
624 */
625 static HDDEDATA CALLBACK dde_cb(UINT uType, UINT uFmt, HCONV hConv,
626 HSZ hsz1, HSZ hsz2,
627 HDDEDATA hData, DWORD dwData1, DWORD dwData2)
628 {
629 return NULL;
630 }
631
632 /******************************************************************
633 * dde_connect
634 *
635 * ShellExecute helper. Used to do an operation with a DDE connection
636 *
637 * Handles both the direct connection (try #1), and if it fails,
638 * launching an application and trying (#2) to connect to it
639 *
640 */
641 static unsigned dde_connect(char* key, char* start, char* ddeexec,
642 const char* lpFile, const char* lpDir, void *env,
643 LPSHELLEXECUTEINFOA sei, SHELL_ExecuteA1632 execfunc,
644 LPCSTR szCommandline, LPITEMIDLIST pidl)
645 {
646 char* endkey = key + strlen(key);
647 char app[256], topic[256], ifexec[256];
648 LONG applen, topiclen, ifexeclen;
649 char* exec;
650 DWORD ddeInst = 0;
651 DWORD tid;
652 HSZ hszApp, hszTopic;
653 HCONV hConv;
654 unsigned ret = 31;
655
656 strcpy(endkey, "\\application");
657 applen = sizeof(app);
658 if (RegQueryValueA(HKEY_CLASSES_ROOT, key, app, &applen) != ERROR_SUCCESS)
659 {
660 FIXME("default app name NIY %s\n", key);
661 return 2;
662 }
663
664 strcpy(endkey, "\\topic");
665 topiclen = sizeof(topic);
666 if (RegQueryValueA(HKEY_CLASSES_ROOT, key, topic, &topiclen) != ERROR_SUCCESS)
667 {
668 strcpy(topic, "System");
669 }
670
671 if (DdeInitializeA(&ddeInst, dde_cb, APPCMD_CLIENTONLY, 0L) != DMLERR_NO_ERROR)
672 {
673 return 2;
674 }
675
676 hszApp = DdeCreateStringHandleA(ddeInst, app, CP_WINANSI);
677 hszTopic = DdeCreateStringHandleA(ddeInst, topic, CP_WINANSI);
678
679 hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL);
680 exec = ddeexec;
681 if (!hConv)
682 {
683 TRACE("Launching '%s'\n", start);
684 ret = execfunc(start, env, lpDir, sei, TRUE);
685 if (ret < 32)
686 {
687 TRACE("Couldn't launch\n");
688 goto error;
689 }
690 hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL);
691 if (!hConv)
692 {
693 TRACE("Couldn't connect. ret=%d\n", ret);
694 ret = 30; /* whatever */
695 goto error;
696 }
697 strcpy(endkey, "\\ifexec");
698 ifexeclen = sizeof(ifexec);
699 if (RegQueryValueA(HKEY_CLASSES_ROOT, key, ifexec, &ifexeclen) == ERROR_SUCCESS)
700 {
701 exec = ifexec;
702 }
703 }
704
705 #if 0 /* argify has already been called. */
706 argify(res, sizeof(res), exec, lpFile, pidl, szCommandline);
707 TRACE("%s %s => %s\n", exec, lpFile, res);
708 #endif
709
710 ret = (DdeClientTransaction((LPBYTE)szCommandline, strlen(szCommandline) + 1, hConv, 0L, 0,
711 XTYP_EXECUTE, 10000, &tid) != DMLERR_NO_ERROR) ? 31 : 33;
712 DdeDisconnect(hConv);
713 error:
714 DdeUninitialize(ddeInst);
715 return ret;
716 }
717
718 /*************************************************************************
719 * execute_from_key [Internal]
720 */
721 static UINT execute_from_key(LPSTR key, LPCSTR lpFile, LPCSTR lpDir, void *env,
722 LPSHELLEXECUTEINFOA sei, SHELL_ExecuteA1632 execfunc,
723 LPCSTR szCommandline, LPITEMIDLIST pidl)
724 {
725 char cmd[1024] = "";
726 LONG cmdlen = sizeof(cmd);
727 UINT retval = 31;
728
729 /* Get the application for the registry */
730 if (RegQueryValueA(HKEY_CLASSES_ROOT, key, cmd, &cmdlen) == ERROR_SUCCESS)
731 {
732 LPSTR tmp;
733 char param[256] = "";
734 LONG paramlen = 256;
735
736 /* Get the parameters needed by the application
737 from the associated ddeexec key */
738 tmp = strstr(key, "command");
739 assert(tmp);
740 strcpy(tmp, "ddeexec");
741
742 if (RegQueryValueA(HKEY_CLASSES_ROOT, key, param, &paramlen) == ERROR_SUCCESS)
743 {
744 TRACE("Got ddeexec %s => %s\n", key, param);
745 retval = dde_connect(key, cmd, param, lpFile, lpDir, env, sei, execfunc, szCommandline, pidl);
746 }
747 else
748 {
749 #if 0 /* argify() has already been called. */
750 /* Is there a replace() function anywhere? */
751 cmd[cmdlen] = '\0';
752 argify(param, sizeof(param), cmd, lpFile, pidl, szCommandline);
753 #else
754 strcpy(param, szCommandline);
755 #endif
756 retval = execfunc(param, env, lpDir, sei, FALSE);
757 }
758 }
759 else TRACE("ooch\n");
760
761 return retval;
762 }
763
764 /*************************************************************************
765 * FindExecutableA [SHELL32.@]
766 */
767 HINSTANCE WINAPI FindExecutableA(LPCSTR lpFile, LPCSTR lpDirectory, LPSTR lpResult)
768 {
769 UINT retval = 31; /* default - 'No association was found' */
770 char old_dir[1024];
771
772 TRACE("File %s, Dir %s\n",
773 (lpFile != NULL ? lpFile : "-"), (lpDirectory != NULL ? lpDirectory : "-"));
774
775 lpResult[0] = '\0'; /* Start off with an empty return string */
776
777 /* trap NULL parameters on entry */
778 if ((lpFile == NULL) || (lpResult == NULL))
779 {
780 /* FIXME - should throw a warning, perhaps! */
781 return (HINSTANCE)2; /* File not found. Close enough, I guess. */
782 }
783
784 if (lpDirectory)
785 {
786 GetCurrentDirectoryA(sizeof(old_dir), old_dir);
787 SetCurrentDirectoryA(lpDirectory);
788 }
789
790 retval = SHELL_FindExecutable(lpDirectory, lpFile, "open", lpResult, NULL, NULL, NULL, NULL);
791
792 TRACE("returning %s\n", lpResult);
793 if (lpDirectory)
794 SetCurrentDirectoryA(old_dir);
795 return (HINSTANCE)retval;
796 }
797
798 /*************************************************************************
799 * FindExecutableW [SHELL32.@]
800 */
801 HINSTANCE WINAPI FindExecutableW(LPCWSTR lpFile, LPCWSTR lpDirectory, LPWSTR lpResult)
802 {
803 FIXME("(%p,%p,%p): stub\n", lpFile, lpDirectory, lpResult);
804 return (HINSTANCE)31; /* default - 'No association was found' */
805 }
806
807 /*************************************************************************
808 * ShellExecuteExA32 [Internal]
809 *
810 * FIXME: use PathResolveA() to search for the fully qualified executable path
811 * use PathProcessCommandA() to processes the command line string
812 */
813 BOOL WINAPI ShellExecuteExA32 (LPSHELLEXECUTEINFOA sei, SHELL_ExecuteA1632 execfunc)
814 {
815 CHAR szApplicationName[MAX_PATH+2], szCommandline[MAX_PATH], fileName[MAX_PATH], dir[MAX_PATH];
816 char res[MAX_PATH];
817 void *env;
818 char lpstrProtocol[256];
819 LPCSTR lpFile;
820 UINT retval = 31;
821 char cmd[1024];
822 const char* ext;
823 BOOL done;
824
825 LPITEMIDLIST pidl = sei->lpIDList;
826 LPITEMIDLIST tmpPidl = NULL;
827
828 TRACE("mask=0x%08lx hwnd=%p verb=%s file=%s parm=%s dir=%s show=0x%08x class=%s\n",
829 sei->fMask, sei->hwnd, debugstr_a(sei->lpVerb),
830 debugstr_a(sei->lpFile), debugstr_a(sei->lpParameters),
831 debugstr_a(sei->lpDirectory), sei->nShow,
832 (sei->fMask & SEE_MASK_CLASSNAME) ? debugstr_a(sei->lpClass) : "not used");
833
834 sei->hProcess = NULL;
835
836 if (sei->lpFile)
837 strcpy(szApplicationName, sei->lpFile);
838 else
839 *szApplicationName = '\0';
840
841 if (sei->lpParameters)
842 strcpy(szCommandline, sei->lpParameters);
843 else
844 *szCommandline = '\0';
845
846 if (sei->lpDirectory)
847 strcpy(dir, sei->lpDirectory);
848 else
849 *dir = '\0';
850
851 if (sei->fMask & (SEE_MASK_ICON | SEE_MASK_HOTKEY |
852 SEE_MASK_CONNECTNETDRV | SEE_MASK_FLAG_DDEWAIT |
853 SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI | SEE_MASK_UNICODE |
854 SEE_MASK_NO_CONSOLE | SEE_MASK_ASYNCOK | SEE_MASK_HMONITOR ))
855 {
856 FIXME("flags ignored: 0x%08lx\n", sei->fMask);
857 }
858
859 /* process the IDList */
860 if (sei->fMask & SEE_MASK_INVOKEIDLIST) /* 0x0c: includes SEE_MASK_IDLIST */
861 {
862 IShellExecuteHookA* pSEH;
863
864 HRESULT hr = SHBindToParent(pidl, &IID_IShellExecuteHookA, (LPVOID*)&pSEH, NULL);
865
866 if (SUCCEEDED(hr)) {
867 hr = IShellExecuteHookA_Execute(pSEH, sei);
868
869 IShellExecuteHookA_Release(pSEH);
870
871 if (hr == S_OK)
872 return TRUE;
873 }
874
875 if (!SHGetPathFromIDListA(pidl, szApplicationName))
876 return FALSE;
877
878 TRACE("-- idlist=%p (%s)\n", pidl, szApplicationName);
879 }
880
881 if (sei->fMask & (SEE_MASK_CLASSNAME | SEE_MASK_CLASSKEY))
882 {
883 /* launch a document by fileclass like 'WordPad.Document.1' */
884 /* the Commandline contains 'c:\Path\wordpad.exe "%1"' */
885 /* FIXME: szCommandline should not be of a fixed size. Plus MAX_PATH is way too short! */
886 if (sei->fMask & SEE_MASK_CLASSKEY)
887 HCR_GetExecuteCommandExA(sei->hkeyClass,
888 (sei->fMask & SEE_MASK_CLASSNAME) ? sei->lpClass: NULL,
889 (sei->lpVerb) ? sei->lpVerb : "open", szCommandline, sizeof(szCommandline));
890 else if (sei->fMask & SEE_MASK_CLASSNAME)
891 HCR_GetExecuteCommandA(sei->lpClass, (sei->lpVerb) ? sei->lpVerb :
892 "open", szCommandline, sizeof(szCommandline));
893
894 /* FIXME: get the extension of lpFile, check if it fits to the lpClass */
895 TRACE("SEE_MASK_CLASSNAME->'%s', doc->'%s'\n", szCommandline, szApplicationName);
896
897 cmd[0] = '\0';
898 done = argify(cmd, sizeof(cmd), szCommandline, szApplicationName, pidl, NULL);
899 if (!done && szApplicationName[0])
900 {
901 strcat(cmd, " ");
902 strcat(cmd, szApplicationName);
903 }
904 retval = execfunc(cmd, NULL, dir, sei, FALSE);
905 if (retval > 32)
906 return TRUE;
907 else
908 return FALSE;
909 }
910
911 /* Else, try to execute the filename */
912 TRACE("execute:'%s','%s'\n", szApplicationName, szCommandline);
913
914 /* resolve shell shortcuts */
915 ext = PathFindExtensionA(szApplicationName);
916 if (ext && !strcasecmp(ext, ".lnk")) { /* or check for: shell_attribs & SFGAO_LINK */
917 WCHAR cmd[MAX_PATH], args[MAX_PATH], wdir[MAX_PATH];
918
919 if (MultiByteToWideChar(CP_ACP, 0, szApplicationName, -1, cmd, MAX_PATH)) {
920 MultiByteToWideChar(CP_ACP, 0, dir, -1, wdir, MAX_PATH);
921
922 if (SUCCEEDED(_ResolveShortCut(cmd, wdir, args, sei->hwnd, &sei->nShow, &tmpPidl))) {
923 if (!*cmd && tmpPidl) {
924 /* We got a PIDL instead of a file system path. */
925 if (SHGetPathFromIDListA(tmpPidl, cmd))
926 tmpPidl = NULL;
927
928 if (cmd[0]==':' && cmd[1]==':') {
929 /* open shell folder for the specified class GUID */
930 strcpy(szApplicationName, "explorer.exe");
931 WideCharToMultiByte(CP_ACP, 0, cmd, -1, szCommandline, MAX_PATH, NULL, NULL);
932
933 *cmd = '\0';
934 } else if (HCR_GetExecuteCommandA("Folder", sei->lpVerb? sei->lpVerb: "open", szCommandline, sizeof(szCommandline))) {
935 res[0] = '\0';
936
937 /*FIXME: seems to have problems in some cases */
938 if (argify(res, sizeof(res), szCommandline, NULL, tmpPidl, NULL))
939 strcpy(szApplicationName, res);
940
941 szCommandline[0] = '\0';
942 *cmd = '\0';
943 }
944 }
945
946 if (*cmd) {
947 WideCharToMultiByte(CP_ACP, 0, cmd, -1, szApplicationName, MAX_PATH, NULL, NULL);
948 WideCharToMultiByte(CP_ACP, 0, wdir, -1, dir, MAX_PATH, NULL, NULL);
949 WideCharToMultiByte(CP_ACP, 0, args, -1, szCommandline, MAX_PATH, NULL, NULL);
950 }
951
952 /* If we prevoiusly had a PIDL, it is now resolved, so forget it. */
953 pidl = tmpPidl;
954 } else
955 FIXME("We could not resolve the shell shortcut.\n");
956 }
957 }
958
959 /* The following code is needed for example to resolve a shortcut
960 to control panel applet "Keyboard", since this is accessed using
961 "rundll32.exe shell32.dll,Control_RunDLL %1,%*" with a command line
962 parameter received from ISF_ControlPanel_fnGetDisplayNameOf(). */
963 if (!*szCommandline) {
964 /* If the executable path is quoted, handle the rest of the command line as parameters. */
965 if (*szApplicationName == '"') {
966 LPSTR src = szApplicationName + 1;
967 LPSTR dst = fileName;
968 LPSTR end;
969
970 /* copy the unquoted executabe path to 'fileName' */
971 while(*src && *src!='"')
972 *dst++ = *src++;
973
974 *dst = '\0';
975
976 if (*src == '"') {
977 end = ++src;
978
979 while(isspace(*src))
980 ++src;
981 } else
982 end = src;
983
984 /* copy the paremeter string to 'szCommandline' */
985 strcpy(szCommandline, src);
986
987 /* terminate 'szApplicationName' after the quote character */
988 *end = '\0';
989 }
990 else
991 {
992 /* If the executrable name is not quoted, we have to use this search loop here,
993 that in CreateProcess() is not sufficient because it does not handle shell links. */
994 LPSTR space, s;
995 char buffer[MAX_PATH], xlpFile[MAX_PATH];
996
997 LPSTR beg = szApplicationName;
998 for(s=beg; (space=strchr(s, ' ')); s=space+1) {
999 int idx = space-szApplicationName;
1000 strncpy(buffer, szApplicationName, idx);
1001 buffer[idx] = '\0';
1002
1003 if (SearchPathA(*dir? dir: NULL, buffer, ".exe", sizeof(xlpFile), xlpFile, NULL))
1004 {
1005 /* separate out command from parameter string */
1006 LPCSTR p = space + 1;
1007
1008 while(isspace(*p))
1009 ++p;
1010
1011 strcpy(szCommandline, p);
1012 *space = '\0';
1013
1014 break;
1015 }
1016 }
1017
1018 strcpy(fileName, szApplicationName);
1019 }
1020 } else
1021 strcpy(fileName, szApplicationName);
1022
1023 lpFile = fileName;
1024
1025 if (szCommandline[0]) {
1026 strcat(szApplicationName, " ");
1027 strcat(szApplicationName, szCommandline);
1028 }
1029
1030 retval = execfunc(szApplicationName, NULL, dir, sei, FALSE);
1031 if (retval > 32)
1032 {
1033 /* Now, that we have successfully launched a process, we can free the PIDL.
1034 It may have been used before for %I command line options. */
1035 if (tmpPidl)
1036 SHFree(tmpPidl);
1037
1038 TRACE("execfunc: retval=%d sei->hInstApp=%p\n", retval, sei->hInstApp);
1039 return TRUE;
1040 }
1041
1042 /* Else, try to find the executable */
1043 cmd[0] = '\0';
1044 retval = SHELL_FindExecutable(*dir? dir: NULL, lpFile, sei->lpVerb, cmd, lpstrProtocol, &env, pidl, szCommandline);
1045 if (retval > 32) /* Found */
1046 {
1047 #if 0 /* SHELL_FindExecutable() already quoted by calling argify() */
1048 CHAR szQuotedCmd[MAX_PATH+2];
1049 /* Must quote to handle case where cmd contains spaces,
1050 * else security hole if malicious user creates executable file "C:\\Program"
1051 *
1052 * FIXME: If we don't have set explicitly command line arguments, we must first
1053 * split executable path from optional command line arguments. Otherwise we would quote
1054 * the complete string with executable path _and_ arguments, which is not what we want.
1055 */
1056 if (szCommandline[0])
1057 sprintf(szQuotedCmd, "\"%s\" %s", cmd, szCommandline);
1058 else
1059 sprintf(szQuotedCmd, "\"%s\"", cmd);
1060 TRACE("%s/%s => %s/%s\n", szApplicationName, sei->lpVerb?sei->lpVerb:"NULL", szQuotedCmd, lpstrProtocol);
1061 if (*lpstrProtocol)
1062 retval = execute_from_key(lpstrProtocol, lpFile, env, dir, sei, execfunc, szCommandline, pidl);
1063 else
1064 retval = execfunc(szQuotedCmd, env, dir, sei, FALSE);
1065 #else
1066 if (*lpstrProtocol)
1067 retval = execute_from_key(lpstrProtocol, lpFile, env, dir, sei, execfunc, cmd, pidl);
1068 else
1069 retval = execfunc(cmd, env, dir, sei, FALSE);
1070 #endif
1071 if (env) HeapFree( GetProcessHeap(), 0, env );
1072 }
1073 else if (PathIsURLA((LPSTR)lpFile)) /* File not found, check for URL */
1074 {
1075 LPSTR lpstrRes;
1076 INT iSize;
1077
1078 lpstrRes = strchr(lpFile, ':');
1079 if (lpstrRes)
1080 iSize = lpstrRes - lpFile;
1081 else
1082 iSize = strlen(lpFile);
1083
1084 TRACE("Got URL: %s\n", lpFile);
1085 /* Looking for ...protocol\shell\<verb>\command */
1086 strncpy(lpstrProtocol, lpFile, iSize);
1087 lpstrProtocol[iSize] = '\0';
1088 strcat(lpstrProtocol, "\\shell\\");
1089 strcat(lpstrProtocol, sei->lpVerb? sei->lpVerb: "open"); /*FIXME: enumerate registry subkeys - compare with the loop into SHELL_FindExecutable() */
1090 strcat(lpstrProtocol, "\\command");
1091
1092 /* Remove File Protocol from lpFile */
1093 /* In the case file://path/file */
1094 if (!strncasecmp(lpFile, "file", iSize))
1095 {
1096 lpFile += iSize;
1097 while (*lpFile == ':') lpFile++;
1098 }
1099 retval = execute_from_key(lpstrProtocol, lpFile, NULL, dir, sei, execfunc, szCommandline, pidl);
1100 }
1101 /* Check if file specified is in the form www.??????.*** */
1102 else if (!strncasecmp(lpFile, "www", 3))
1103 {
1104 /* if so, append lpFile http:// and call ShellExecute */
1105 char lpstrTmpFile[256] = "http://" ;
1106 strcat(lpstrTmpFile, lpFile);
1107 retval = (UINT)ShellExecuteA(sei->hwnd, sei->lpVerb, lpstrTmpFile, NULL, NULL, 0);
1108 }
1109
1110 /* Now we can free the PIDL. It may have been used before for %I command line options. */
1111 if (tmpPidl)
1112 SHFree(tmpPidl);
1113
1114 TRACE("ShellExecuteExA32 retval=%d\n", retval);
1115
1116 if (retval <= 32)
1117 {
1118 sei->hInstApp = (HINSTANCE)retval;
1119 return FALSE;
1120 }
1121
1122 sei->hInstApp = (HINSTANCE)33;
1123 return TRUE;
1124 }
1125
1126 /*************************************************************************
1127 * ShellExecuteA [SHELL32.290]
1128 */
1129 HINSTANCE WINAPI ShellExecuteA(HWND hWnd, LPCSTR lpOperation,LPCSTR lpFile,
1130 LPCSTR lpParameters,LPCSTR lpDirectory, INT iShowCmd)
1131 {
1132 SHELLEXECUTEINFOA sei;
1133 HANDLE hProcess = 0;
1134
1135 TRACE("\n");
1136 sei.cbSize = sizeof(sei);
1137 sei.fMask = 0;
1138 sei.hwnd = hWnd;
1139 sei.lpVerb = lpOperation;
1140 sei.lpFile = lpFile;
1141 sei.lpParameters = lpParameters;
1142 sei.lpDirectory = lpDirectory;
1143 sei.nShow = iShowCmd;
1144 sei.lpIDList = 0;
1145 sei.lpClass = 0;
1146 sei.hkeyClass = 0;
1147 sei.dwHotKey = 0;
1148 sei.hProcess = hProcess;
1149
1150 ShellExecuteExA32 (&sei, SHELL_ExecuteA);
1151 return sei.hInstApp;
1152 }
1153
1154 /*************************************************************************
1155 * ShellExecuteEx [SHELL32.291]
1156 *
1157 */
1158 BOOL WINAPI ShellExecuteExAW (LPVOID sei)
1159 {
1160 if (SHELL_OsIsUnicode())
1161 return ShellExecuteExW (sei);
1162 return ShellExecuteExA32 (sei, SHELL_ExecuteA);
1163 }
1164
1165 /*************************************************************************
1166 * ShellExecuteExA [SHELL32.292]
1167 *
1168 */
1169 BOOL WINAPI ShellExecuteExA (LPSHELLEXECUTEINFOA sei)
1170 {
1171 BOOL ret = ShellExecuteExA32 (sei, SHELL_ExecuteA);
1172
1173 TRACE("ShellExecuteExA(): ret=%d\n", ret);
1174
1175 return ret;
1176 }
1177
1178 /*************************************************************************
1179 * ShellExecuteExW [SHELL32.293]
1180 *
1181 */
1182 BOOL WINAPI ShellExecuteExW (LPSHELLEXECUTEINFOW sei)
1183 {
1184 SHELLEXECUTEINFOA seiA;
1185 BOOL ret;
1186
1187 TRACE("%p\n", sei);
1188
1189 memcpy(&seiA, sei, sizeof(SHELLEXECUTEINFOA));
1190
1191 if (sei->lpVerb)
1192 seiA.lpVerb = HEAP_strdupWtoA( GetProcessHeap(), 0, sei->lpVerb);
1193
1194 if (sei->lpFile)
1195 seiA.lpFile = HEAP_strdupWtoA( GetProcessHeap(), 0, sei->lpFile);
1196
1197 if (sei->lpParameters)
1198 seiA.lpParameters = HEAP_strdupWtoA( GetProcessHeap(), 0, sei->lpParameters);
1199
1200 if (sei->lpDirectory)
1201 seiA.lpDirectory = HEAP_strdupWtoA( GetProcessHeap(), 0, sei->lpDirectory);
1202
1203 if ((sei->fMask & SEE_MASK_CLASSNAME) && sei->lpClass)
1204 seiA.lpClass = HEAP_strdupWtoA( GetProcessHeap(), 0, sei->lpClass);
1205 else
1206 seiA.lpClass = NULL;
1207
1208 ret = ShellExecuteExA(&seiA);
1209
1210 if (seiA.lpVerb) HeapFree( GetProcessHeap(), 0, (LPSTR) seiA.lpVerb );
1211 if (seiA.lpFile) HeapFree( GetProcessHeap(), 0, (LPSTR) seiA.lpFile );
1212 if (seiA.lpParameters) HeapFree( GetProcessHeap(), 0, (LPSTR) seiA.lpParameters );
1213 if (seiA.lpDirectory) HeapFree( GetProcessHeap(), 0, (LPSTR) seiA.lpDirectory );
1214 if (seiA.lpClass) HeapFree( GetProcessHeap(), 0, (LPSTR) seiA.lpClass );
1215
1216 sei->hInstApp = seiA.hInstApp;
1217
1218 TRACE("ShellExecuteExW(): ret=%d\n", ret);
1219
1220 return ret;
1221 }
1222
1223 /*************************************************************************
1224 * ShellExecuteW [SHELL32.294]
1225 * from shellapi.h
1226 * WINSHELLAPI HINSTANCE APIENTRY ShellExecuteW(HWND hwnd, LPCWSTR lpOperation,
1227 * LPCWSTR lpFile, LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd);
1228 */
1229 HINSTANCE WINAPI ShellExecuteW(HWND hwnd, LPCWSTR lpOperation, LPCWSTR lpFile,
1230 LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd)
1231 {
1232 SHELLEXECUTEINFOW sei;
1233 HANDLE hProcess = 0;
1234 int ret;
1235
1236 TRACE("\n");
1237 sei.cbSize = sizeof(sei);
1238 sei.fMask = 0;
1239 sei.hwnd = hwnd;
1240 sei.lpVerb = lpOperation;
1241 sei.lpFile = lpFile;
1242 sei.lpParameters = lpParameters;
1243 sei.lpDirectory = lpDirectory;
1244 sei.nShow = nShowCmd;
1245 sei.lpIDList = 0;
1246 sei.lpClass = 0;
1247 sei.hkeyClass = 0;
1248 sei.dwHotKey = 0;
1249 sei.hProcess = hProcess;
1250
1251 ret = ShellExecuteExW(&sei);
1252
1253 TRACE("ShellExecuteW(): ret=%d module=%p", ret, sei.hInstApp);
1254 return sei.hInstApp;
1255 }