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