#include "shellprv.h" #include "shlexec.h" #include "netview.h" extern "C" { #include } #include #include "ole2dup.h" #include #include "ids.h" #define SAFE_DEBUGSTR(str) ((str) ? (str) : "") HINSTANCE Window_GetInstance(HWND hwnd) { DWORD idProcess; GetWindowThreadProcessId(hwnd, &idProcess); // HINSTANCEs are pointers valid only within // a single process, so 33 is returned to indicate success // as 0-32 are reserved for error. (Actually 32 is supposed // to be a valid success return but some apps get it wrong.) return (HINSTANCE)(DWORD_PTR)(idProcess ? 33 : 0); } // Return TRUE if the window belongs to a 32bit or a Win4.0 app. // NB We can't just check if it's a 32bit window // since many apps use 16bit ddeml windows to communicate with the shell // On NT we can. BOOL Window_IsLFNAware(HWND hwnd) { // 32-bit window return LOWORD(GetWindowLongPtr(hwnd,GWLP_HINSTANCE)) == 0; } #define COPYTODST(_szdst, _szend, _szsrc, _ulen, _ret) \ { \ UINT _utemp = _ulen; \ if ((UINT)(_szend-_szdst) < _utemp + 1) { \ return(_ret); \ } \ StrCpyN(_szdst, _szsrc, _utemp + 1); \ _szdst += _utemp; \ } /* Returns NULL if this is the last parm, pointer to next space otherwise */ LPTSTR _GetNextParm(LPCTSTR lpSrc, LPTSTR lpDst, UINT cchDst) { LPCTSTR lpNextQuote, lpNextSpace; LPTSTR lpEnd = lpDst+cchDst-1; // dec to account for trailing NULL BOOL fQuote; // quoted string? BOOL fDoubleQuote; // is this quote a double quote? while (*lpSrc == TEXT(' ')) ++lpSrc; if (!*lpSrc) return(NULL); fQuote = (*lpSrc == TEXT('"')); if (fQuote) lpSrc++; // skip leading quote for (;;) { lpNextQuote = StrChr(lpSrc, TEXT('"')); if (!fQuote) { // for an un-quoted string, copy all chars to first space/null lpNextSpace = StrChr(lpSrc, TEXT(' ')); if (!lpNextSpace) // null before space! (end of string) { if (!lpNextQuote) { // copy all chars to the null if (lpDst) { COPYTODST(lpDst, lpEnd, lpSrc, lstrlen(lpSrc), NULL); } return NULL; } else { // we have a quote to convert. Fall through. } } else if (!lpNextQuote || lpNextSpace < lpNextQuote) { // copy all chars to the space if (lpDst) { COPYTODST(lpDst, lpEnd, lpSrc, (UINT)(lpNextSpace-lpSrc), NULL); } return (LPTSTR)lpNextSpace; } else { // quote before space. Fall through to convert quote. } } else if (!lpNextQuote) { // a quoted string without a terminating quote? Illegal! ASSERT(0); return NULL; } // we have a potential quote to convert ASSERT(lpNextQuote); fDoubleQuote = *(lpNextQuote+1) == TEXT('"'); if (fDoubleQuote) lpNextQuote++; // so the quote is copied if (lpDst) { COPYTODST(lpDst, lpEnd, lpSrc, (UINT) (lpNextQuote-lpSrc), NULL); } lpSrc = lpNextQuote+1; if (!fDoubleQuote) { // we just copied the rest of this quoted string. if this wasn't // quoted, it's an illegal string... treat the quote as a space. ASSERT(fQuote); return (LPTSTR)lpSrc; } } } #define PEMAGIC ((WORD)'P'+((WORD)'E'<<8)) // Returns TRUE is app is LFN aware. // This assumes all Win32 apps are LFN aware. BOOL App_IsLFNAware(LPCTSTR pszFile) { BOOL fRet = FALSE; // Assume Win 4.0 apps and Win32 apps are LFN aware. DWORD dw = GetExeType(pszFile); // TraceMsg(TF_SHELLEXEC, "s.aila: %s %s %x", pszFile, szFile, dw); if ((LOWORD(dw) == PEMAGIC) || ((LOWORD(dw) == NEMAGIC) && (HIWORD(dw) >= 0x0400))) { TCHAR sz[MAX_PATH]; PathToAppPathKey(pszFile, sz, ARRAYSIZE(sz)); fRet = (ERROR_SUCCESS != SHGetValue(HKEY_LOCAL_MACHINE, sz, TEXT("UseShortName"), NULL, NULL, NULL)); } return fRet; } // apps can tag themselves in a way so we know we can pass an URL on the cmd // line. this uses the existance of a value called "UseURL" under the // App Paths key in the registry associated with the app that is passed in. // pszPath is the path to the exe BOOL DoesAppWantUrl(LPCTSTR pszPath) { TCHAR sz[MAX_PATH]; PathToAppPathKey(pszPath, sz, ARRAYSIZE(sz)); return (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, sz, TEXT("UseURL"), NULL, NULL, NULL)); } BOOL _AppIsLFNAware(LPCTSTR pszFile) { TCHAR szFile[MAX_PATH]; // Does it look like a DDE command? if (pszFile && *pszFile && (*pszFile != TEXT('['))) { // Nope - Hopefully just a regular old command %1 thing. lstrcpyn(szFile, pszFile, ARRAYSIZE(szFile)); LPTSTR pszArgs = PathGetArgs(szFile); if (*pszArgs) *(pszArgs - 1) = TEXT('\0'); PathRemoveBlanks(szFile); // remove any blanks that may be after the command PathUnquoteSpaces(szFile); return App_IsLFNAware(szFile); } return FALSE; } // in: // lpFile exe name (used for %0 or %1 in replacement string) // lpFrom string template to sub params and file into "excel.exe %1 %2 /n %3" // lpParams parameter list "foo.txt bar.txt" // out: // lpTo output string with all parameters replaced // // supports: // %* replace with all parameters // %0, %1 replace with file // %n use nth parameter // // replace parameter placeholders (%1 %2 ... %n) with parameters // UINT ReplaceParameters(LPTSTR lpTo, UINT cchTo, LPCTSTR lpFile, LPCTSTR lpFrom, LPCTSTR lpParms, int nShow, DWORD * pdwHotKey, BOOL fLFNAware, LPCITEMIDLIST lpID, LPITEMIDLIST *ppidlGlobal) { int i; TCHAR c; LPCTSTR lpT; TCHAR sz[MAX_PATH]; BOOL fFirstParam = TRUE; LPTSTR lpEnd = lpTo + cchTo - 1; // dec to allow trailing NULL LPTSTR pToOrig = lpTo; for (; *lpFrom; lpFrom++) { if (*lpFrom == TEXT('%')) { switch (*(++lpFrom)) { case TEXT('~'): // Copy all parms starting with nth (n >= 2 and <= 9) c = *(++lpFrom); if (c >= TEXT('2') && c <= TEXT('9')) { for (i = 2, lpT = lpParms; i < c-TEXT('0') && lpT; i++) { lpT = _GetNextParm(lpT, NULL, 0); } if (lpT) { COPYTODST(lpTo, lpEnd, lpT, lstrlen(lpT), SE_ERR_ACCESSDENIED); } } else { lpFrom -= 2; // Backup over %~ and pass through goto NormalChar; } break; case TEXT('*'): // Copy all parms if (lpParms) { COPYTODST(lpTo, lpEnd, lpParms, lstrlen(lpParms), SE_ERR_ACCESSDENIED); } break; case TEXT('0'): case TEXT('1'): // %0, %1, copy the file name // If the filename comes first then we don't need to convert it to // a shortname. If it appears anywhere else and the app is not LFN // aware then we must. if (!(fFirstParam || fLFNAware || _AppIsLFNAware(pToOrig)) && GetShortPathName(lpFile, sz, ARRAYSIZE(sz)) > 0) { TraceMsg(TF_SHELLEXEC, "ShellExecuteEx: Getting short version of path."); COPYTODST(lpTo, lpEnd, sz, lstrlen(sz), SE_ERR_ACCESSDENIED); } else { TraceMsg(TF_SHELLEXEC, "ShellExecuteEx: Using long version of path."); COPYTODST(lpTo, lpEnd, lpFile, lstrlen(lpFile), SE_ERR_ACCESSDENIED); } break; case TEXT('2'): case TEXT('3'): case TEXT('4'): case TEXT('5'): case TEXT('6'): case TEXT('7'): case TEXT('8'): case TEXT('9'): for (i = *lpFrom-TEXT('2'), lpT = lpParms; lpT; --i) { if (i) lpT = _GetNextParm(lpT, NULL, 0); else { sz[0] = '\0'; // ensure a valid string, regardless of what happens within _GetNextParm _GetNextParm(lpT, sz, ARRAYSIZE(sz)); COPYTODST(lpTo, lpEnd, sz, lstrlen(sz), SE_ERR_ACCESSDENIED); break; } } break; case TEXT('s'): case TEXT('S'): wnsprintf(sz, ARRAYSIZE(sz), TEXT("%ld"), nShow); COPYTODST(lpTo, lpEnd, sz, lstrlen(sz), SE_ERR_ACCESSDENIED); break; case TEXT('h'): case TEXT('H'): wnsprintf(sz, ARRAYSIZE(sz), TEXT("%X"), pdwHotKey ? *pdwHotKey : 0); COPYTODST(lpTo, lpEnd, sz, lstrlen(sz), SE_ERR_ACCESSDENIED); if (pdwHotKey) *pdwHotKey = 0; break; // Note that a new global IDList is created for each case TEXT('i'): case TEXT('I'): // Note that a single global ID list is created and used over // again, so that it may be easily destroyed if anything // goes wrong if (ppidlGlobal) { if (lpID && !*ppidlGlobal) { *ppidlGlobal = (LPITEMIDLIST)SHAllocShared(lpID,ILGetSize(lpID),GetCurrentProcessId()); if (!*ppidlGlobal) { return SE_ERR_OOM; } } wnsprintf(sz, ARRAYSIZE(sz), TEXT(":%ld:%ld"), *ppidlGlobal, GetCurrentProcessId()); } else { StrCpyN(sz,TEXT(":0"), ARRAYSIZE(sz)); } COPYTODST(lpTo, lpEnd, sz, lstrlen(sz), SE_ERR_ACCESSDENIED); break; case TEXT('l'): case TEXT('L'): // Like %1 only using the long name. // REVIEW UNDONE IANEL Remove the fFirstParam and fLFNAware stuff as soon as this // is up and running. TraceMsg(TF_SHELLEXEC, "ShellExecuteEx: Using long version of path."); COPYTODST(lpTo, lpEnd, lpFile, lstrlen(lpFile), SE_ERR_ACCESSDENIED); break; case TEXT('D'): case TEXT('d'): { // %D gives the display name of an object. if (lpID && SUCCEEDED(SHGetNameAndFlags(lpID, SHGDN_FORPARSING, sz, ARRAYSIZE(sz), NULL))) { COPYTODST(lpTo, lpEnd, sz, lstrlen(sz), SE_ERR_ACCESSDENIED); } else return SE_ERR_ACCESSDENIED; break; } default: goto NormalChar; } // TraceMsg(TF_SHELLEXEC, "s.rp: Past first param (1)."); fFirstParam = FALSE; } else { NormalChar: // not a "%?" thing, just copy this to the destination if (lpEnd-lpTo < 2) { // Always check for room for DBCS char return(SE_ERR_ACCESSDENIED); } *lpTo++ = *lpFrom; // Special case for things like "%1" ie don't clear the first param flag // if we hit a dbl-quote. if (*lpFrom != TEXT('"')) { // TraceMsg(TF_SHELLEXEC, "s.rp: Past first param (2)."); fFirstParam = FALSE; } else if (IsDBCSLeadByte(*lpFrom)) { *lpTo++ = *(++lpFrom); } } } // We should always have enough room since we dec'ed cchTo when determining // lpEnd *lpTo = 0; // This means success return(0); } HWND ThreadID_GetVisibleWindow(DWORD dwID) { HWND hwnd; for (hwnd = GetWindow(GetDesktopWindow(), GW_CHILD); hwnd; hwnd = GetWindow(hwnd, GW_HWNDNEXT)) { DWORD dwIDTmp = GetWindowThreadProcessId(hwnd, NULL); TraceMsg(TF_SHELLEXEC, "s.ti_gvw: Hwnd %x Thread ID %x.", hwnd, dwIDTmp); if (IsWindowVisible(hwnd) && (dwIDTmp == dwID)) { TraceMsg(TF_SHELLEXEC, "s.ti_gvw: Found match %x.", hwnd); return hwnd; } } return NULL; } void ActivateHandler(HWND hwnd, DWORD_PTR dwHotKey) { ASSERT(hwnd); hwnd = GetTopParentWindow(hwnd); // returns non-NULL for any non-NULL input HWND hwndT = GetLastActivePopup(hwnd); // returns non-NULL for any non-NULL input if (!IsWindowVisible(hwndT)) { DWORD dwID = GetWindowThreadProcessId(hwnd, NULL); TraceMsg(TF_SHELLEXEC, "ActivateHandler: Hwnd %x Thread ID %x.", hwnd, dwID); ASSERT(dwID); // Find the first visible top level window owned by the // same guy that's handling the DDE conversation. hwnd = ThreadID_GetVisibleWindow(dwID); if (hwnd) { hwndT = GetLastActivePopup(hwnd); if (IsIconic(hwnd)) { TraceMsg(TF_SHELLEXEC, "ActivateHandler: Window is iconic, restoring."); ShowWindow(hwnd,SW_RESTORE); } else { TraceMsg(TF_SHELLEXEC, "ActivateHandler: Window is normal, bringing to top."); BringWindowToTop(hwnd); if (hwndT && hwnd != hwndT) BringWindowToTop(hwndT); } // set the hotkey if (dwHotKey) { SendMessage(hwnd, WM_SETHOTKEY, dwHotKey, 0); } } } } BOOL FindExistingDrv(LPCTSTR pszUNCRoot, LPTSTR pszLocalName, DWORD cchLocalName) { int iDrive; for (iDrive = 0; iDrive < 26; iDrive++) { if (IsRemoteDrive(iDrive)) { TCHAR szDriveName[3]; DWORD cb = MAX_PATH; szDriveName[0] = (TCHAR)iDrive + (TCHAR)TEXT('A'); szDriveName[1] = TEXT(':'); szDriveName[2] = 0; SHWNetGetConnection(szDriveName, pszLocalName, &cb); if (lstrcmpi(pszUNCRoot, pszLocalName) == 0) { StrCpyN(pszLocalName, szDriveName, cchLocalName); return(TRUE); } } } return(FALSE); } // Returns whether the given net path exists. This fails for NON net paths. // BOOL NetPathExists(LPCTSTR lpszPath, DWORD *lpdwType) { BOOL fResult = FALSE; NETRESOURCE nr; LPTSTR lpSystem; DWORD dwRes, dwSize = 1024; void * lpv; if (!lpszPath || !*lpszPath) return FALSE; lpv = (void *)LocalAlloc(LPTR, dwSize); if (!lpv) return FALSE; TryWNetAgain: nr.dwScope = RESOURCE_GLOBALNET; nr.dwType = RESOURCETYPE_ANY; nr.dwDisplayType = 0; nr.lpLocalName = NULL; nr.lpRemoteName = (LPTSTR)lpszPath; nr.lpProvider = NULL; nr.lpComment = NULL; dwRes = WNetGetResourceInformation(&nr, lpv, &dwSize, &lpSystem); // If our buffer wasn't big enough, try a bigger buffer... if (dwRes == WN_MORE_DATA) { void * tmp = LocalReAlloc(lpv, dwSize, LMEM_MOVEABLE); if (!tmp) { LocalFree(lpv); SetLastError(ERROR_OUTOFMEMORY); return FALSE; } lpv = tmp; goto TryWNetAgain; } fResult = (dwRes == WN_SUCCESS); if (fResult && lpdwType) *lpdwType = ((LPNETRESOURCE)lpv)->dwType; LocalFree(lpv); return fResult; } HRESULT _CheckExistingNet(LPCTSTR pszFile, LPCTSTR pszRoot, BOOL fPrint) { // // This used to be a call to GetFileAttributes(), but // GetFileAttributes() doesn't handle net paths very well. // However, we need to be careful, because other shell code // expects SHValidateUNC to return false for paths that point // to print shares. // HRESULT hr = S_FALSE; if (!PathIsRoot(pszFile)) { // if we are checking for a printshare, then it must be a Root if (fPrint) hr = E_FAIL; else if (PathFileExists(pszFile)) hr = S_OK; } if (S_FALSE == hr) { DWORD dwType; if (NetPathExists(pszRoot, &dwType)) { if (fPrint ? dwType != RESOURCETYPE_PRINT : dwType == RESOURCETYPE_PRINT) hr = E_FAIL; else hr = S_OK; } else if (-1 != GetFileAttributes(pszRoot)) { // // IE 4.01 SP1 QFE #104. GetFileAttributes now called // as a last resort become some clients often fail when using // WNetGetResourceInformation. For example, many NFS clients were // broken because of this. // hr = S_OK; } } if (hr == E_FAIL) SetLastError(ERROR_NOT_SUPPORTED); return hr; } HRESULT _CheckNetUse(HWND hwnd, LPTSTR pszShare, UINT fConnect, LPTSTR pszOut, DWORD cchOut) { NETRESOURCE rc; DWORD dw, err; DWORD dwRedir = CONNECT_TEMPORARY; if (!(fConnect & VALIDATEUNC_NOUI)) dwRedir |= CONNECT_INTERACTIVE; if (fConnect & VALIDATEUNC_CONNECT) dwRedir |= CONNECT_REDIRECT; // VALIDATE_PRINT happens only after a failed attempt to validate for // a file. That previous attempt will have given the option to // connect to other media -- don't do it here or the user will be // presented with the same dialog twice when the first one is cancelled. if (fConnect & VALIDATEUNC_PRINT) dwRedir |= CONNECT_CURRENT_MEDIA; rc.lpRemoteName = pszShare; rc.lpLocalName = NULL; rc.lpProvider = NULL; rc.dwType = (fConnect & VALIDATEUNC_PRINT) ? RESOURCETYPE_PRINT : RESOURCETYPE_DISK; err = WNetUseConnection(hwnd, &rc, NULL, NULL, dwRedir, pszOut, &cchOut, &dw); TraceMsg(TF_SHELLEXEC, "SHValidateUNC WNetUseConnection(%s) returned %x", pszShare, err); if (err) { SetLastError(err); return E_FAIL; } else if (fConnect & VALIDATEUNC_PRINT) { // just because WNetUse succeeded, doesnt mean // NetPathExists will. if it fails then // we shouldnt succeed this call regardless // because we are only interested in print shares. if (!NetPathExists(pszShare, &dw) || (dw != RESOURCETYPE_PRINT)) { SetLastError(ERROR_NOT_SUPPORTED); return E_FAIL; } } return S_OK; } // // SHValidateUNC // // This function validates a UNC path by calling WNetAddConnection3. // It will make it possible for the user to type a remote (RNA) UNC // app/document name from Start->Run dialog. // // fConnect - flags controling what to do // // VALIDATEUNC_NOUI // dont bring up stinking UI! // VALIDATEUNC_CONNECT // connect a drive letter // VALIDATEUNC_PRINT // validate as print share instead of disk share // BOOL WINAPI SHValidateUNC(HWND hwndOwner, LPTSTR pszFile, UINT fConnect) { HRESULT hr; TCHAR szShare[MAX_PATH]; BOOL fPrint = (fConnect & VALIDATEUNC_PRINT); UINT cchOrig = lstrlen(pszFile) + 1; ASSERT(PathIsUNC(pszFile)); ASSERT((fConnect & ~VALIDATEUNC_VALID) == 0); ASSERT((fConnect & VALIDATEUNC_CONNECT) ? !fPrint : TRUE); lstrcpyn(szShare, pszFile, ARRAYSIZE(szShare)); if (!PathStripToRoot(szShare)) { SetLastError(ERROR_PATH_NOT_FOUND); return FALSE; } if (fConnect & VALIDATEUNC_CONNECT) hr = S_FALSE; else hr = _CheckExistingNet(pszFile, szShare, fPrint); if (S_FALSE == hr) { TCHAR szAccessName[MAX_PATH]; if (!fPrint && FindExistingDrv(szShare, szAccessName, ARRAYSIZE(szAccessName))) { hr = S_OK; } else hr = _CheckNetUse(hwndOwner, szShare, fConnect, szAccessName, SIZECHARS(szAccessName)); if (S_OK == hr && !fPrint) { StrCatBuff(szAccessName, pszFile + lstrlen(szShare), ARRAYSIZE(szAccessName)); // The name should only get shorter, so no need to check length lstrcpyn(pszFile, szAccessName, cchOrig); // Handle the root case if (cchOrig >= 4 && pszFile[2] == TEXT('\0')) { pszFile[2] = TEXT('\\'); pszFile[3] = TEXT('\0'); } hr = _CheckExistingNet(pszFile, szShare, FALSE); } } return (hr == S_OK); } HINSTANCE WINAPI RealShellExecuteExA(HWND hwnd, LPCSTR lpOp, LPCSTR lpFile, LPCSTR lpArgs, LPCSTR lpDir, LPSTR lpResult, LPCSTR lpTitle, LPSTR lpReserved, WORD nShowCmd, LPHANDLE lphProcess, DWORD dwFlags) { SHELLEXECUTEINFOA sei = { sizeof(SHELLEXECUTEINFOA), SEE_MASK_FLAG_NO_UI|SEE_MASK_FORCENOIDLIST, hwnd, lpOp, lpFile, lpArgs, lpDir, nShowCmd, NULL}; TraceMsg(TF_SHELLEXEC, "RealShellExecuteExA(%04X, %s, %s, %s, %s, %s, %s, %s, %d, %08lX, %d)", hwnd, lpOp, lpFile, lpArgs, lpDir, lpResult, lpTitle, lpReserved, nShowCmd, lphProcess, dwFlags); // Pass along the lpReserved parameter to the new process if (lpReserved) { sei.fMask |= SEE_MASK_RESERVED; sei.hInstApp = (HINSTANCE)lpReserved; } // Pass along the lpTitle parameter to the new process if (lpTitle) { sei.fMask |= SEE_MASK_HASTITLE; sei.lpClass = lpTitle; } // Pass along the SEPARATE_VDM flag if (dwFlags & EXEC_SEPARATE_VDM) { sei.fMask |= SEE_MASK_FLAG_SEPVDM; } // Pass along the NO_CONSOLE flag if (dwFlags & EXEC_NO_CONSOLE) { sei.fMask |= SEE_MASK_NO_CONSOLE; } if (lphProcess) { // Return the process handle sei.fMask |= SEE_MASK_NOCLOSEPROCESS; ShellExecuteExA(&sei); *lphProcess = sei.hProcess; } else { ShellExecuteExA(&sei); } return sei.hInstApp; } HINSTANCE WINAPI RealShellExecuteExW(HWND hwnd, LPCWSTR lpOp, LPCWSTR lpFile, LPCWSTR lpArgs, LPCWSTR lpDir, LPWSTR lpResult, LPCWSTR lpTitle, LPWSTR lpReserved, WORD nShowCmd, LPHANDLE lphProcess, DWORD dwFlags) { SHELLEXECUTEINFOW sei = { sizeof(SHELLEXECUTEINFOW), SEE_MASK_FLAG_NO_UI|SEE_MASK_FORCENOIDLIST, hwnd, lpOp, lpFile, lpArgs, lpDir, nShowCmd, NULL}; TraceMsg(TF_SHELLEXEC, "RealShellExecuteExW(%04X, %s, %s, %s, %s, %s, %s, %s, %d, %08lX, %d)", hwnd, lpOp, lpFile, lpArgs, lpDir, lpResult, lpTitle, lpReserved, nShowCmd, lphProcess, dwFlags); if (lpReserved) { sei.fMask |= SEE_MASK_RESERVED; sei.hInstApp = (HINSTANCE)lpReserved; } if (lpTitle) { sei.fMask |= SEE_MASK_HASTITLE; sei.lpClass = lpTitle; } if (dwFlags & EXEC_SEPARATE_VDM) { sei.fMask |= SEE_MASK_FLAG_SEPVDM; } if (dwFlags & EXEC_NO_CONSOLE) { sei.fMask |= SEE_MASK_NO_CONSOLE; } if (lphProcess) { // Return the process handle sei.fMask |= SEE_MASK_NOCLOSEPROCESS; ShellExecuteExW(&sei); *lphProcess = sei.hProcess; } else { ShellExecuteExW(&sei); } return sei.hInstApp; } HINSTANCE WINAPI RealShellExecuteA(HWND hwnd, LPCSTR lpOp, LPCSTR lpFile, LPCSTR lpArgs, LPCSTR lpDir, LPSTR lpResult, LPCSTR lpTitle, LPSTR lpReserved, WORD nShowCmd, LPHANDLE lphProcess) { TraceMsg(TF_SHELLEXEC, "RealShellExecuteA(%04X, %s, %s, %s, %s, %s, %s, %s, %d, %08lX)", hwnd, lpOp, lpFile, lpArgs, lpDir, lpResult, lpTitle, lpReserved, nShowCmd, lphProcess); return RealShellExecuteExA(hwnd,lpOp,lpFile,lpArgs,lpDir,lpResult,lpTitle,lpReserved,nShowCmd,lphProcess,0); } HINSTANCE RealShellExecuteW(HWND hwnd, LPCWSTR lpOp, LPCWSTR lpFile, LPCWSTR lpArgs, LPCWSTR lpDir, LPWSTR lpResult, LPCWSTR lpTitle, LPWSTR lpReserved, WORD nShowCmd, LPHANDLE lphProcess) { TraceMsg(TF_SHELLEXEC, "RealShellExecuteW(%04X, %s, %s, %s, %s, %s, %s, %s, %d, %08lX)", hwnd, lpOp, lpFile, lpArgs, lpDir, lpResult, lpTitle, lpReserved, nShowCmd, lphProcess); return RealShellExecuteExW(hwnd,lpOp,lpFile,lpArgs,lpDir,lpResult,lpTitle,lpReserved,nShowCmd,lphProcess,0); } HINSTANCE WINAPI ShellExecute(HWND hwnd, LPCTSTR lpOp, LPCTSTR lpFile, LPCTSTR lpArgs, LPCTSTR lpDir, int nShowCmd) { // NB The FORCENOIDLIST flag stops us from going through the ShellExecPidl() // code (for backwards compatability with progman). // DDEWAIT makes us synchronous, and gets around threads without // msg pumps and ones that are killed immediately after shellexec() SHELLEXECUTEINFO sei = { sizeof(SHELLEXECUTEINFO), 0, hwnd, lpOp, lpFile, lpArgs, lpDir, nShowCmd, NULL}; ULONG fMask = SEE_MASK_FLAG_NO_UI|SEE_MASK_FORCENOIDLIST; if(!(SHGetAppCompatFlags(ACF_WIN95SHLEXEC) & ACF_WIN95SHLEXEC)) fMask |= SEE_MASK_FLAG_DDEWAIT; sei.fMask = fMask; TraceMsg(TF_SHELLEXEC, "ShellExecute(%04X, %s, %s, %s, %s, %d)", hwnd, lpOp, lpFile, lpArgs, lpDir, nShowCmd); ShellExecuteEx(&sei); return sei.hInstApp; } HINSTANCE WINAPI ShellExecuteA(HWND hwnd, LPCSTR lpOp, LPCSTR lpFile, LPCSTR lpArgs, LPCSTR lpDir, int nShowCmd) { // NB The FORCENOIDLIST flag stops us from going through the ShellExecPidl() // code (for backwards compatability with progman). // DDEWAIT makes us synchronous, and gets around threads without // msg pumps and ones that are killed immediately after shellexec() SHELLEXECUTEINFOA sei = { sizeof(SHELLEXECUTEINFOA), 0, hwnd, lpOp, lpFile, lpArgs, lpDir, nShowCmd, NULL}; ULONG fMask = SEE_MASK_FLAG_NO_UI|SEE_MASK_FORCENOIDLIST; if (!(SHGetAppCompatFlags(ACF_WIN95SHLEXEC) & ACF_WIN95SHLEXEC)) fMask |= SEE_MASK_FLAG_DDEWAIT; sei.fMask = fMask; TraceMsg(TF_SHELLEXEC, "ShellExecuteA(%04X, %S, %S, %S, %S, %d)", hwnd, SAFE_DEBUGSTR(lpOp), SAFE_DEBUGSTR(lpFile), SAFE_DEBUGSTR(lpArgs), SAFE_DEBUGSTR(lpDir), nShowCmd); ShellExecuteExA(&sei); return sei.hInstApp; } // Returns TRUE if the specified app is listed under the specified key STDAPI_(BOOL) IsNameListedUnderKey(LPCTSTR pszFileName, LPCTSTR pszKey) { HKEY hkey; // Enum through the list of apps. if (RegOpenKeyEx(HKEY_CURRENT_USER, pszKey, 0, KEY_READ, &hkey) == ERROR_SUCCESS) { TCHAR szValue[MAX_PATH], szData[MAX_PATH]; DWORD dwType, cbData = sizeof(szData); DWORD cchValue = ARRAYSIZE(szValue); int iValue = 0; while (RegEnumValue(hkey, iValue, szValue, &cchValue, NULL, &dwType, (LPBYTE)szData, &cbData) == ERROR_SUCCESS) { if (lstrcmpi(szData, pszFileName) == 0) { RegCloseKey(hkey); return TRUE; } cbData = sizeof(szData); cchValue = ARRAYSIZE(szValue); iValue++; } RegCloseKey(hkey); } return FALSE; } #define REGSTR_PATH_POLICIES_EXPLORER REGSTR_PATH_POLICIES TEXT("\\Explorer\\RestrictRun") #define REGSTR_PATH_POLICIES_EXPLORER_DISALLOW REGSTR_PATH_POLICIES TEXT("\\Explorer\\DisallowRun") //---------------------------------------------------------------------------- // Returns TRUE if the specified app is not on the list of unrestricted apps. BOOL RestrictedApp(LPCTSTR pszApp) { LPTSTR pszFileName = PathFindFileName(pszApp); TraceMsg(TF_SHELLEXEC, "RestrictedApp: %s ", pszFileName); // Special cases: // Apps you can always run. if (lstrcmpi(pszFileName, c_szRunDll) == 0) return FALSE; if (lstrcmpi(pszFileName, TEXT("systray.exe")) == 0) return FALSE; return !IsNameListedUnderKey(pszFileName, REGSTR_PATH_POLICIES_EXPLORER); } //---------------------------------------------------------------------------- // Returns TRUE if the specified app is on the list of disallowed apps. // not much safety gained from filename checking. BOOL DisallowedApp(LPCTSTR pszApp) { LPTSTR pszFileName = PathFindFileName(pszApp); TraceMsg(TF_SHELLEXEC, "DisallowedApp: %s ", pszFileName); return IsNameListedUnderKey(pszFileName, REGSTR_PATH_POLICIES_EXPLORER_DISALLOW); } /* * Returns: * S_OK or error. * *phrHook is hook result if S_OK is returned, otherwise it is S_FALSE. */ HRESULT InvokeShellExecuteHook(REFGUID clsidHook, LPSHELLEXECUTEINFO pei, HRESULT *phrHook) { *phrHook = S_FALSE; IUnknown *punk; HRESULT hr = SHExtCoCreateInstance(NULL, &clsidHook, NULL, IID_PPV_ARG(IUnknown, &punk)); if (hr == S_OK) { IShellExecuteHook *pshexhk; hr = punk->QueryInterface(IID_PPV_ARG(IShellExecuteHook, &pshexhk)); if (hr == S_OK) { *phrHook = pshexhk->Execute(pei); pshexhk->Release(); } else { IShellExecuteHookA *pshexhkA; hr = punk->QueryInterface(IID_PPV_ARG(IShellExecuteHookA, &pshexhkA)); if (SUCCEEDED(hr)) { SHELLEXECUTEINFOA seia; UINT cchVerb = 0; UINT cchFile = 0; UINT cchParameters = 0; UINT cchDirectory = 0; UINT cchClass = 0; LPSTR lpszBuffer; seia = *(SHELLEXECUTEINFOA*)pei; // Copy all of the binary data if (pei->lpVerb) { cchVerb = WideCharToMultiByte(CP_ACP,0, pei->lpVerb, -1, NULL, 0, NULL, NULL) + 1; } if (pei->lpFile) cchFile = WideCharToMultiByte(CP_ACP,0, pei->lpFile, -1, NULL, 0, NULL, NULL)+1; if (pei->lpParameters) cchParameters = WideCharToMultiByte(CP_ACP,0, pei->lpParameters, -1, NULL, 0, NULL, NULL)+1; if (pei->lpDirectory) cchDirectory = WideCharToMultiByte(CP_ACP,0, pei->lpDirectory, -1, NULL, 0, NULL, NULL)+1; if (_UseClassName(pei->fMask) && pei->lpClass) cchClass = WideCharToMultiByte(CP_ACP,0, pei->lpClass, -1, NULL, 0, NULL, NULL)+1; lpszBuffer = (LPSTR) alloca(cchVerb+cchFile+cchParameters+cchDirectory+cchClass); seia.lpVerb = NULL; seia.lpFile = NULL; seia.lpParameters = NULL; seia.lpDirectory = NULL; seia.lpClass = NULL; // // Convert all of the strings to ANSI // if (pei->lpVerb) { WideCharToMultiByte(CP_ACP, 0, pei->lpVerb, -1, lpszBuffer, cchVerb, NULL, NULL); seia.lpVerb = lpszBuffer; lpszBuffer += cchVerb; } if (pei->lpFile) { WideCharToMultiByte(CP_ACP, 0, pei->lpFile, -1, lpszBuffer, cchFile, NULL, NULL); seia.lpFile = lpszBuffer; lpszBuffer += cchFile; } if (pei->lpParameters) { WideCharToMultiByte(CP_ACP, 0, pei->lpParameters, -1, lpszBuffer, cchParameters, NULL, NULL); seia.lpParameters = lpszBuffer; lpszBuffer += cchParameters; } if (pei->lpDirectory) { WideCharToMultiByte(CP_ACP, 0, pei->lpDirectory, -1, lpszBuffer, cchDirectory, NULL, NULL); seia.lpDirectory = lpszBuffer; lpszBuffer += cchDirectory; } if (_UseClassName(pei->fMask) && pei->lpClass) { WideCharToMultiByte(CP_ACP, 0, pei->lpClass, -1, lpszBuffer, cchClass, NULL, NULL); seia.lpClass = lpszBuffer; } *phrHook = pshexhkA->Execute(&seia); pei->hInstApp = seia.hInstApp; // hook may set hProcess (e.g. CURLExec creates dummy process // to signal IEAK that IE setup failed -- in browser only mode) pei->hProcess = seia.hProcess; pshexhkA->Release(); } } punk->Release(); } return(hr); } const TCHAR c_szShellExecuteHooks[] = REGSTR_PATH_EXPLORER TEXT("\\ShellExecuteHooks"); /* * Returns: * S_OK Execution handled by hook. pei->hInstApp filled in. * S_FALSE Execution not handled by hook. pei->hInstApp not filled in. * E_... Error during execution by hook. pei->hInstApp filled in. */ HRESULT TryShellExecuteHooks(LPSHELLEXECUTEINFO pei) { HRESULT hr = S_FALSE; HKEY hkeyHooks; // Enumerate the list of hooks. A hook is registered as a GUID value of the // c_szShellExecuteHooks key. if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szShellExecuteHooks, 0, KEY_READ, &hkeyHooks) == ERROR_SUCCESS) { DWORD dwiValue; TCHAR szCLSID[GUIDSTR_MAX]; DWORD cchCLSID; // Invoke each hook. A hook returns S_FALSE if it does not handle the // exec. Stop when a hook returns S_OK (handled) or an error. for (cchCLSID = ARRAYSIZE(szCLSID), dwiValue = 0; RegEnumValue(hkeyHooks, dwiValue, szCLSID, &cchCLSID, NULL, NULL, NULL, NULL) == ERROR_SUCCESS; cchCLSID = ARRAYSIZE(szCLSID), dwiValue++) { CLSID clsidHook; if (SUCCEEDED(SHCLSIDFromString(szCLSID, &clsidHook))) { HRESULT hrHook; if (InvokeShellExecuteHook(clsidHook, pei, &hrHook) == S_OK && hrHook != S_FALSE) { hr = hrHook; break; } } } RegCloseKey(hkeyHooks); } ASSERT(hr == S_FALSE || (hr == S_OK && ISSHELLEXECSUCCEEDED(pei->hInstApp)) || (FAILED(hr) && ! ISSHELLEXECSUCCEEDED(pei->hInstApp))); return(hr); } BOOL InRunDllProcess(void) { static BOOL s_fInRunDll = -1; if (-1 == s_fInRunDll) { TCHAR sz[MAX_PATH]; s_fInRunDll = FALSE; if (GetModuleFileName(NULL, sz, SIZECHARS(sz))) { // // WARNING - rundll often seems to fail to add the DDEWAIT flag, and // it often needs to since it is common to use rundll as a fire // and forget process, and it exits too early. // // note: this changes DDE flags for any app with "rundll" in its name // shouldnt be a big deal from a security point of view. if (StrStrI(sz, TEXT("rundll"))) s_fInRunDll = TRUE; } } return s_fInRunDll; } #ifdef DEBUG /*---------------------------------------------------------- Purpose: Validation function for SHELLEXECUTEINFO */ BOOL IsValidPSHELLEXECUTEINFO(LPSHELLEXECUTEINFO pei) { // // Note that we do *NOT* validate hInstApp, for several reasons. // // 1. It is an OUT parameter, not an IN parameter. // 2. It often contains an error code (see documentation). // 3. Even when it contains an HINSTANCE, it's an HINSTANCE // in another process, so we can't validate it anyway. // return (IS_VALID_WRITE_PTR(pei, SHELLEXECUTEINFO) && IS_VALID_SIZE(pei->cbSize, sizeof(*pei)) && (IsFlagSet(pei->fMask, SEE_MASK_FLAG_NO_UI) || NULL == pei->hwnd || IS_VALID_HANDLE(pei->hwnd, WND)) && (NULL == pei->lpVerb || IS_VALID_STRING_PTR(pei->lpVerb, -1)) && (NULL == pei->lpFile || IS_VALID_STRING_PTR(pei->lpFile, -1)) && (NULL == pei->lpParameters || IS_VALID_STRING_PTR(pei->lpParameters, -1)) && (NULL == pei->lpDirectory || IS_VALID_STRING_PTR(pei->lpDirectory, -1)) && (IsFlagClear(pei->fMask, SEE_MASK_IDLIST) || IsFlagSet(pei->fMask, SEE_MASK_INVOKEIDLIST) || // because SEE_MASK_IDLIST is part of SEE_MASK_INVOKEIDLIST this line will IS_VALID_PIDL((LPCITEMIDLIST)(pei->lpIDList))) && // defer to the next clause if the superset is true (IsFlagClear(pei->fMask, SEE_MASK_INVOKEIDLIST) || NULL == pei->lpIDList || IS_VALID_PIDL((LPCITEMIDLIST)(pei->lpIDList))) && (!_UseClassName(pei->fMask) || IS_VALID_STRING_PTR(pei->lpClass, -1)) && (!_UseTitleName(pei->fMask) || NULL == pei->lpClass || IS_VALID_STRING_PTR(pei->lpClass, -1)) && (!_UseClassKey(pei->fMask) || IS_VALID_HANDLE(pei->hkeyClass, KEY)) && (IsFlagClear(pei->fMask, SEE_MASK_ICON) || IS_VALID_HANDLE(pei->hIcon, ICON))); } #endif // DEBUG // // ShellExecuteEx // // returns TRUE if the execute succeeded, in which case // hInstApp should be the hinstance of the app executed (>32) // NOTE: in some cases the HINSTANCE cannot (currently) be determined. // In these cases, hInstApp is set to 42. // // returns FALSE if the execute did not succeed, in which case // GetLastError will contain error information // For backwards compatibility, hInstApp will contain the // best SE_ERR_ error information (<=32) possible. // BOOL WINAPI ShellExecuteEx(LPSHELLEXECUTEINFO pei) { DWORD err = NOERROR; // Don't overreact if CoInitializeEx fails; it just means we // can't do our shell hooks. HRESULT hrInit = SHCoInitialize(); if (IS_VALID_STRUCT_PTR(pei, SHELLEXECUTEINFO) && sizeof(*pei) == pei->cbSize) { // This internal bit prevents error message box reporting // when we recurse back into ShellExecuteEx ULONG ulOriginalMask = pei->fMask; pei->fMask |= SEE_MASK_FLAG_SHELLEXEC; if (SHRegGetBoolUSValue(TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer"), TEXT("MaximizeApps"), FALSE, FALSE)) // && (GetSystemMetrics(SM_CYSCREEN)<=600)) { switch (pei->nShow) { case SW_NORMAL: case SW_SHOW: case SW_RESTORE: case SW_SHOWDEFAULT: pei->nShow = SW_MAXIMIZE; } } if (!(pei->fMask & SEE_MASK_FLAG_DDEWAIT) && InRunDllProcess()) { // // WARNING - rundll often seems to fail to add the DDEWAIT flag, and // it often needs to since it is common to use rundll as a fire // and forget process, and it exits too early. // pei->fMask |= (SEE_MASK_FLAG_DDEWAIT | SEE_MASK_WAITFORINPUTIDLE); } // ShellExecuteNormal does its own SetLastError err = ShellExecuteNormal(pei); // Mike's attempt to be consistent in error reporting: if (err != ERROR_SUCCESS) { // we shouldn't put up errors on dll's not found. // this is handled WITHIN shellexecuteNormal because // sometimes kernel will put up the message for us, and sometimes // we need to. we've put the curtion at ShellExecuteNormal // LEGACY - ERROR_RESTRICTED_APP was never mapped to a valid error - ZekeL 2001-FEB-14 // because we always called _ShellExecuteError() before // resetting the mask to ulOriginalMask, we never mapped // ERROR_RESTRICTED_APP (which is -1) to a valid code if (err != ERROR_DLL_NOT_FOUND && err != ERROR_CANCELLED) { _ShellExecuteError(pei, NULL, err); } } pei->fMask = ulOriginalMask; } else { // Failed parameter validation pei->hInstApp = (HINSTANCE)SE_ERR_ACCESSDENIED; err = ERROR_ACCESS_DENIED; } SHCoUninitialize(hrInit); if (err != ERROR_SUCCESS) SetLastError(err); return err == ERROR_SUCCESS; } //+------------------------------------------------------------------------- // // Function: ShellExecuteExA // // Synopsis: Thunks ANSI call to ShellExecuteA to ShellExecuteW // // Arguments: [pei] -- pointer to an ANSI SHELLEXECUTINFO struct // // Returns: BOOL success value // // History: 2-04-95 bobday Created // 2-06-95 davepl Changed to ConvertStrings // // Notes: // //-------------------------------------------------------------------------- inline BOOL _ThunkClass(ULONG fMask) { return (fMask & SEE_MASK_HASLINKNAME) || (fMask & SEE_MASK_HASTITLE) || _UseClassName(fMask); } BOOL WINAPI ShellExecuteExA(LPSHELLEXECUTEINFOA pei) { if (pei->cbSize != sizeof(SHELLEXECUTEINFOA)) { pei->hInstApp = (HINSTANCE)SE_ERR_ACCESSDENIED; SetLastError(ERROR_ACCESS_DENIED); return FALSE; } SHELLEXECUTEINFOW seiw = {0}; seiw.cbSize = sizeof(SHELLEXECUTEINFOW); seiw.fMask = pei->fMask; seiw.hwnd = pei->hwnd; seiw.nShow = pei->nShow; if (_UseClassKey(pei->fMask)) seiw.hkeyClass = pei->hkeyClass; if (pei->fMask & SEE_MASK_IDLIST) seiw.lpIDList = pei->lpIDList; if (pei->fMask & SEE_MASK_HOTKEY) seiw.dwHotKey = pei->dwHotKey; if (pei->fMask & SEE_MASK_ICON) seiw.hIcon = pei->hIcon; // Thunk the text fields as appropriate ThunkText *pThunkText = ConvertStrings(6, pei->lpVerb, pei->lpFile, pei->lpParameters, pei->lpDirectory, _ThunkClass(pei->fMask) ? pei->lpClass : NULL, (pei->fMask & SEE_MASK_RESERVED) ? pei->hInstApp : NULL); if (NULL == pThunkText) { pei->hInstApp = (HINSTANCE)SE_ERR_OOM; return FALSE; } // Set our UNICODE text fields to point to the thunked strings seiw.lpVerb = pThunkText->m_pStr[0]; seiw.lpFile = pThunkText->m_pStr[1]; seiw.lpParameters = pThunkText->m_pStr[2]; seiw.lpDirectory = pThunkText->m_pStr[3]; seiw.lpClass = pThunkText->m_pStr[4]; seiw.hInstApp = (HINSTANCE)pThunkText->m_pStr[5]; // If we are passed the SEE_MASK_FILEANDURL flag, this means that // we have a lpFile parameter that has both the CacheFilename and the URL // (seperated by a single NULL, eg. "CacheFileName\0UrlName). We therefore // need to special case the thunking of pei->lpFile. LPWSTR pwszFileAndUrl = NULL; if (pei->fMask & SEE_MASK_FILEANDURL) { int iUrlLength; int iCacheFileLength = lstrlenW(pThunkText->m_pStr[1]); WCHAR wszURL[INTERNET_MAX_URL_LENGTH]; LPSTR pszUrlPart = (LPSTR)&pei->lpFile[iCacheFileLength + 1]; if (IsBadStringPtrA(pszUrlPart, INTERNET_MAX_URL_LENGTH) || !PathIsURLA(pszUrlPart)) { ASSERT(FALSE); } else { // we have a vaild URL, so thunk it iUrlLength = lstrlenA(pszUrlPart); DWORD cchFileAndUrl = iUrlLength + iCacheFileLength + 2; pwszFileAndUrl = (LPWSTR)LocalAlloc(LPTR, cchFileAndUrl * sizeof(WCHAR)); if (!pwszFileAndUrl) { pei->hInstApp = (HINSTANCE)SE_ERR_OOM; return FALSE; } SHAnsiToUnicode(pszUrlPart, wszURL, ARRAYSIZE(wszURL)); // construct the wide multi-string StrCpyNW(pwszFileAndUrl, pThunkText->m_pStr[1], cchFileAndUrl); StrCpyNW(&pwszFileAndUrl[iCacheFileLength + 1], wszURL, cchFileAndUrl - (iCacheFileLength + 1)); seiw.lpFile = pwszFileAndUrl; } } // Call the real UNICODE ShellExecuteEx BOOL fRet = ShellExecuteEx(&seiw); pei->hInstApp = seiw.hInstApp; if (pei->fMask & SEE_MASK_NOCLOSEPROCESS) { pei->hProcess = seiw.hProcess; } LocalFree(pThunkText); if (pwszFileAndUrl) LocalFree(pwszFileAndUrl); return fRet; } // To display an error message appropriately, call this if ShellExecuteEx fails. void _DisplayShellExecError(ULONG fMask, HWND hwnd, LPCTSTR pszFile, LPCTSTR pszTitle, DWORD dwErr) { if (!(fMask & SEE_MASK_FLAG_NO_UI)) { if (dwErr != ERROR_CANCELLED) { LPCTSTR pszHeader; UINT ids; // don't display "user cancelled", the user knows that already // make sure parent window is the foreground window if (hwnd) SetForegroundWindow(hwnd); if (pszTitle) pszHeader = pszTitle; else pszHeader = pszFile; // Use our messages when we can -- they're more descriptive switch (dwErr) { case 0: case ERROR_NOT_ENOUGH_MEMORY: case ERROR_OUTOFMEMORY: ids = IDS_LowMemError; break; case ERROR_FILE_NOT_FOUND: ids = IDS_RunFileNotFound; break; case ERROR_PATH_NOT_FOUND: case ERROR_BAD_PATHNAME: ids = IDS_PathNotFound; break; case ERROR_TOO_MANY_OPEN_FILES: ids = IDS_TooManyOpenFiles; break; case ERROR_ACCESS_DENIED: ids = IDS_RunAccessDenied; break; case ERROR_BAD_FORMAT: // NB CreateProcess, when execing a Win16 apps maps just about all of // these errors to BadFormat. Not very useful but there it is. ids = IDS_BadFormat; break; case ERROR_SHARING_VIOLATION: ids = IDS_ShareError; break; case ERROR_OLD_WIN_VERSION: ids = IDS_OldWindowsVer; break; case ERROR_APP_WRONG_OS: ids = IDS_OS2AppError; break; case ERROR_SINGLE_INSTANCE_APP: ids = IDS_MultipleDS; break; case ERROR_RMODE_APP: ids = IDS_RModeApp; break; case ERROR_INVALID_DLL: ids = IDS_InvalidDLL; break; case ERROR_NO_ASSOCIATION: ids = IDS_NoAssocError; break; case ERROR_DDE_FAIL: ids = IDS_DDEFailError; break; case ERROR_BAD_NET_NAME: case ERROR_SEM_TIMEOUT: ids = IDS_REASONS_BADNETNAME; break; // LEGACY - ERROR_RESTRICTED_APP was never mapped to a valid error - ZekeL 2001-FEB-14 // because we always called _ShellExecuteError() before // resetting the mask to ulOriginalMask, we never mapped // ERROR_RESTRICTED_APP (which is -1) to a valid code case ERROR_RESTRICTED_APP: ids = IDS_RESTRICTIONS; // restrictions like to use IDS_RESTRICTIONSTITLE if (!pszTitle) pszHeader = MAKEINTRESOURCE(IDS_RESTRICTIONSTITLE); break; // If we don't get a match, let the system handle it for us default: ids = 0; SHSysErrorMessageBox( hwnd, pszHeader, IDS_SHLEXEC_ERROR, dwErr, pszFile, MB_OK | MB_ICONSTOP); break; } if (ids) { ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(ids), pszHeader, (ids == IDS_LowMemError)? (MB_OK | MB_ICONSTOP | MB_SYSTEMMODAL):(MB_OK | MB_ICONSTOP), pszFile); } } } SetLastError(dwErr); // The message box may have clobbered. } void _ShellExecuteError(LPSHELLEXECUTEINFO pei, LPCTSTR lpTitle, DWORD dwErr) { ASSERT(!ISSHELLEXECSUCCEEDED(pei->hInstApp)); // if dwErr not passed in, get it if (dwErr == 0) dwErr = GetLastError(); _DisplayShellExecError(pei->fMask, pei->hwnd, pei->lpFile, lpTitle, dwErr); } //---------------------------------------------------------------------------- // Given a file name and directory, get the path to the execuatable that // would be exec'd if you tried to ShellExecute this thing. HINSTANCE WINAPI FindExecutable(LPCTSTR lpFile, LPCTSTR lpDirectory, LPTSTR lpResult) { HINSTANCE hInstance = (HINSTANCE)42; // assume success must be > 32 TCHAR szOldDir[MAX_PATH]; TCHAR szFile[MAX_PATH]; LPCTSTR dirs[2]; // Progman relies on lpResult being a ptr to an null string on error. *lpResult = TEXT('\0'); GetCurrentDirectory(ARRAYSIZE(szOldDir), szOldDir); if (lpDirectory && *lpDirectory) SetCurrentDirectory(lpDirectory); else lpDirectory = szOldDir; // needed for PathResolve() if (!GetShortPathName(lpFile, szFile, ARRAYSIZE(szFile))) { // if the lpFile is unqualified or bogus, let's use it down // in PathResolve. lstrcpyn(szFile, lpFile, ARRAYSIZE(szFile)); } // get fully qualified path and add .exe extension if needed dirs[0] = (LPTSTR)lpDirectory; dirs[1] = NULL; if (!PathResolve(szFile, dirs, PRF_VERIFYEXISTS | PRF_TRYPROGRAMEXTENSIONS | PRF_FIRSTDIRDEF)) { // File doesn't exist, return file not found. hInstance = (HINSTANCE)SE_ERR_FNF; goto Exit; } TraceMsg(TF_SHELLEXEC, "FindExecutable: PathResolve -> %s", (LPCSTR)szFile); if (PathIsExe(szFile)) { // public API, can't change to have cch. StrCpyN(lpResult, szFile, MAX_PATH); // assumed length! goto Exit; } if (SUCCEEDED(AssocQueryString(0, ASSOCSTR_EXECUTABLE, szFile, NULL, szFile, (LPDWORD)MAKEINTRESOURCE(SIZECHARS(szFile))))) { StrCpyN(lpResult, szFile, MAX_PATH); // assumed length! } else { hInstance = (HINSTANCE)SE_ERR_NOASSOC; } Exit: TraceMsg(TF_SHELLEXEC, "FindExec(%s) ==> %s", (LPTSTR)lpFile, (LPTSTR)lpResult); SetCurrentDirectory(szOldDir); return hInstance; } HINSTANCE WINAPI FindExecutableA(LPCSTR lpFile, LPCSTR lpDirectory, LPSTR lpResult) { HINSTANCE hResult; WCHAR wszResult[MAX_PATH]; ThunkText * pThunkText = ConvertStrings(2, lpFile, lpDirectory); *lpResult = '\0'; if (NULL == pThunkText) { return (HINSTANCE)SE_ERR_OOM; } hResult = FindExecutableW(pThunkText->m_pStr[0], pThunkText->m_pStr[1], wszResult); LocalFree(pThunkText); // FindExecutableW terminates wszResult for us, so this is safe // even if the above call fails // Thunk the output result string back to ANSI. If the conversion fails, // or if the default char is used, we fail the API call. // public API, assume MAX_PATH if (0 == WideCharToMultiByte(CP_ACP, 0, wszResult, -1, lpResult, MAX_PATH, NULL, NULL)) { SetLastError((DWORD)E_FAIL); return (HINSTANCE) SE_ERR_FNF; } return hResult; } //---------------------------------------------------------------------------- // Data structures for our wait for file open functions // typedef struct _WaitForItem * PWAITFORITEM; typedef struct _WaitForItem { DWORD dwSize; DWORD fOperation; // Operation to perform PWAITFORITEM pwfiNext; HANDLE hEvent; // Handle to event that was registered. UINT iWaiting; // Number of clients that are waiting. ITEMIDLIST idlItem; // pidl to wait for } WAITFORITEM; // // This is the form of the structure that is shoved into the shared memory // block. It must be the 32-bit version for interoperability reasons. // typedef struct _WaitForItem32 { DWORD dwSize; DWORD fOperation; // Operation to perform DWORD NotUsed1; LONG hEvent; // Truncated event handle UINT NotUsed2; ITEMIDLIST idlItem; // pidl to wait for } WAITFORITEM32, *PWAITFORITEM32; // // These macros enforce type safety so people are forced to use the // WAITFORITEM32 structure when accessing the shared memory block. // #define SHLockWaitForItem(h, pid) ((PWAITFORITEM32)SHLockShared(h, pid)) __inline void SHUnlockWaitForItem(PWAITFORITEM32 pwfi) { SHUnlockShared(pwfi); } PWAITFORITEM g_pwfiHead = NULL; HANDLE SHWaitOp_OperateInternal(DWORD fOperation, LPCITEMIDLIST pidlItem) { PWAITFORITEM pwfi; HANDLE hEvent = (HANDLE)NULL; for (pwfi = g_pwfiHead; pwfi != NULL; pwfi = pwfi->pwfiNext) { if (ILIsEqual(&(pwfi->idlItem), pidlItem)) { hEvent = pwfi->hEvent; break; } } if (fOperation & WFFO_ADD) { if (!pwfi) { UINT uSize; UINT uSizeIDList = 0; if (pidlItem) uSizeIDList = ILGetSize(pidlItem); uSize = sizeof(WAITFORITEM) + uSizeIDList; // Create an event to wait for hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (hEvent) pwfi = (PWAITFORITEM)SHAlloc(uSize); if (pwfi) { pwfi->dwSize = uSize; // pwfi->fOperation = 0; // Meaningless pwfi->hEvent = hEvent; pwfi->iWaiting = ((fOperation & WFFO_WAIT) != 0); memcpy(&(pwfi->idlItem), pidlItem, uSizeIDList); // now link it in pwfi->pwfiNext = g_pwfiHead; g_pwfiHead = pwfi; } } } if (pwfi) { if (fOperation & WFFO_WAIT) pwfi->iWaiting++; if (fOperation & WFFO_SIGNAL) SetEvent(hEvent); if (fOperation & WFFO_REMOVE) pwfi->iWaiting--; // decrement in use count; // Only check removal case if not adding if ((fOperation & WFFO_ADD) == 0) { // Remove it if nobody waiting on it if (pwfi->iWaiting == 0) { if (g_pwfiHead == pwfi) g_pwfiHead = pwfi->pwfiNext; else { PWAITFORITEM pwfiT = g_pwfiHead; while ((pwfiT != NULL) && (pwfiT->pwfiNext != pwfi)) pwfiT = pwfiT->pwfiNext; ASSERT(pwfiT != NULL); if (pwfiT != NULL) pwfiT->pwfiNext = pwfi->pwfiNext; } // Close the handle CloseHandle(pwfi->hEvent); // Free the memory SHFree(pwfi); hEvent = NULL; // NULL indicates nobody waiting... (for remove case) } } } return hEvent; } void SHWaitOp_Operate(HANDLE hWait, DWORD dwProcId) { PWAITFORITEM32 pwfiFind = SHLockWaitForItem(hWait, dwProcId); if (pwfiFind) { pwfiFind->hEvent = HandleToLong(SHWaitOp_OperateInternal(pwfiFind->fOperation, &(pwfiFind->idlItem))); SHUnlockWaitForItem(pwfiFind); } } HANDLE SHWaitOp_Create(DWORD fOperation, LPCITEMIDLIST pidlItem, DWORD dwProcId) { UINT uSizeIDList = pidlItem ? ILGetSize(pidlItem) : 0; UINT uSize = sizeof(WAITFORITEM32) + uSizeIDList; HANDLE hWaitOp = SHAllocShared(NULL, uSize, dwProcId); if (hWaitOp) { PWAITFORITEM32 pwfi = SHLockWaitForItem(hWaitOp,dwProcId); if (pwfi) { pwfi->dwSize = uSize; pwfi->fOperation = fOperation; pwfi->NotUsed1 = 0; pwfi->hEvent = HandleToLong((HANDLE)NULL); pwfi->NotUsed2 = 0; if (pidlItem) memcpy(&(pwfi->idlItem), pidlItem, uSizeIDList); SHUnlockWaitForItem(pwfi); } else { // clean up SHFreeShared(hWaitOp, dwProcId); hWaitOp = NULL; } } return hWaitOp; } // This function allows the cabinet to wait for a // file (in particular folders) to signal us that they are in an open state. // This should take care of several synchronazation problems with the shell // not knowing when a folder is in the process of being opened or not // STDAPI_(DWORD) SHWaitForFileToOpen(LPCITEMIDLIST pidl, UINT uOptions, DWORD dwTimeout) { HWND hwndShell; HANDLE hWaitOp; HANDLE hEvent = NULL; DWORD dwProcIdSrc = GetCurrentProcessId(); DWORD dwReturn = WAIT_OBJECT_0; // we need a default hwndShell = GetShellWindow(); if ((uOptions & (WFFO_WAIT | WFFO_ADD)) != 0) { if (hwndShell) { DWORD dwProcIdDst; GetWindowThreadProcessId(hwndShell, &dwProcIdDst); // Do just the add and/or wait portions hWaitOp = SHWaitOp_Create(uOptions & (WFFO_WAIT | WFFO_ADD), pidl, dwProcIdSrc); if (hWaitOp) { SendMessage(hwndShell, CWM_WAITOP, (WPARAM)hWaitOp, (LPARAM)dwProcIdSrc); // Now get the hEvent and convert to a local handle PWAITFORITEM32 pwfi = SHLockWaitForItem(hWaitOp, dwProcIdSrc); if (pwfi) { hEvent = SHMapHandle(LongToHandle(pwfi->hEvent),dwProcIdDst, dwProcIdSrc, EVENT_ALL_ACCESS, 0); SHUnlockWaitForItem(pwfi); } SHFreeShared(hWaitOp,dwProcIdSrc); } } else { // Do just the add and/or wait portions hEvent = SHWaitOp_OperateInternal(uOptions & (WFFO_WAIT | WFFO_ADD), pidl); } if (hEvent) { if (uOptions & WFFO_WAIT) dwReturn = SHProcessMessagesUntilEvent(NULL, hEvent, dwTimeout); if (hwndShell) // Close the duplicated handle. CloseHandle(hEvent); } } if (uOptions & WFFO_REMOVE) { if (hwndShell) { hWaitOp = SHWaitOp_Create(WFFO_REMOVE, pidl, dwProcIdSrc); if (hWaitOp) { SendMessage(hwndShell, CWM_WAITOP, (WPARAM)hWaitOp, (LPARAM)dwProcIdSrc); SHFreeShared(hWaitOp,dwProcIdSrc); } } else { SHWaitOp_OperateInternal(WFFO_REMOVE, pidl); } } return dwReturn; } // Signals that the file is open // STDAPI_(BOOL) SignalFileOpen(LPCITEMIDLIST pidl) { BOOL fResult = FALSE; HWND hwndShell = GetShellWindow(); if (hwndShell) { PWAITFORITEM32 pwfi; DWORD dwProcId = GetCurrentProcessId(); HANDLE hWaitOp = SHWaitOp_Create(WFFO_SIGNAL, pidl, dwProcId); if (hWaitOp) { SendMessage(hwndShell, CWM_WAITOP, (WPARAM)hWaitOp, (LPARAM)dwProcId); // Now get the hEvent to determine return value... pwfi = SHLockWaitForItem(hWaitOp, dwProcId); if (pwfi) { fResult = (LongToHandle(pwfi->hEvent) != (HANDLE)NULL); SHUnlockWaitForItem(pwfi); } SHFreeShared(hWaitOp,dwProcId); } } else { fResult = (SHWaitOp_OperateInternal(WFFO_SIGNAL, pidl) == (HANDLE)NULL); } // Let everyone know that we opened something UINT uMsg = RegisterWindowMessage(SH_FILEOPENED); BroadcastSystemMessage(BSF_POSTMESSAGE, BSM_ALLCOMPONENTS, uMsg, NULL, NULL); return fResult; } // // Checks to see if darwin is enabled. // BOOL IsDarwinEnabled() { static BOOL s_fDarwinEnabled = TRUE; static BOOL s_fInit = FALSE; if (!s_fInit) { HKEY hkeyPolicy = 0; if (RegOpenKeyEx(HKEY_CURRENT_USER, REGSTR_PATH_POLICIES_EXPLORER, 0, KEY_READ, &hkeyPolicy) == ERROR_SUCCESS) { if (SHQueryValueEx(hkeyPolicy, TEXT("DisableMSI"), NULL, NULL, NULL, NULL) == ERROR_SUCCESS) { s_fDarwinEnabled = FALSE; // policy turns MSI off } RegCloseKey(hkeyPolicy); } s_fInit = TRUE; } return s_fDarwinEnabled; } // takes the darwin ID string from the registry, and calls darwin to get the // full path to the application. // // IN: pszDarwinDescriptor - this has the contents of the darwin key read out of the registry. // it should contain a string like "[Darwin-ID-for-App] /switches". // // OUT: pszDarwinComand - the full path to the application to this buffer w/ switches. // STDAPI ParseDarwinID(LPTSTR pszDarwinDescriptor, LPTSTR pszDarwinCommand, DWORD cchDarwinCommand) { DWORD dwError = CommandLineFromMsiDescriptor(pszDarwinDescriptor, pszDarwinCommand, &cchDarwinCommand); return HRESULT_FROM_WIN32(dwError); }