|
|
#include "shellprv.h"
#include <shlobjp.h>
#include "shelllnk.h"
#include "datautil.h"
#include "ids.h" // For String Resource identifiers
#include "pif.h" // For manipulating PIF files
#include "trayp.h" // For WMTRAY_* messages
#include "views.h" // For FSIDM_OPENPRN
#include "os.h" // For Win32MoveFile ...
#include "util.h" // For GetMenuIndexForCanonicalVerb
#include "defcm.h" // For CDefFolderMenu_Create2Ex
#include "uemapp.h"
#include <filterr.h>
#include "folder.h"
#include <msi.h>
#include <msip.h>
#include "treewkcb.h"
#define GetLastHRESULT() HRESULT_FROM_WIN32(GetLastError())
// Flags for FindInFilder.fifFlags
//
// The drive referred to by the shortcut does not exist.
// Let pTracker search for it, but do not perform an old-style
// ("downlevel") search of our own.
#define FIF_NODRIVE 0x0001
// Only if the file we found scores more than this number do we
// even show the user this result, any thing less than this would
// be too shameful of us to show the user.
#define MIN_SHOW_USER_SCORE 10
// magic score that stops searches and causes us not to warn
// whe the link is actually found
#define MIN_NO_UI_SCORE 40
// If no User Interface will be provided during the search,
// then do not search more than 3 seconds.
#define NOUI_SEARCH_TIMEOUT (3 * 1000)
// If a User Interface will be provided during the search,
// then search as much as 2 minutes.
#define UI_SEARCH_TIMEOUT (120 * 1000)
#define LNKTRACK_HINTED_UPLEVELS 4 // directory levels to search upwards from last know object locn
#define LNKTRACK_DESKTOP_DOWNLEVELS 4 // infinite downlevels
#define LNKTRACK_ROOT_DOWNLEVELS 4 // levels down from root of fixed disks
#define LNKTRACK_HINTED_DOWNLEVELS 4 // levels down at each level on way up during hinted uplevels
class CLinkResolver : public CBaseTreeWalkerCB { public: CLinkResolver(CTracker *ptrackerobject, const WIN32_FIND_DATA *pofd, UINT dwResolveFlags, DWORD TrackerRestrictions, DWORD fifFlags);
int Resolve(HWND hwnd, LPCTSTR pszPath, LPCTSTR pszCurFile); void GetResult(LPTSTR psz, UINT cch);
// IShellTreeWalkerCallBack
STDMETHODIMP FoundFile(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd); STDMETHODIMP EnterFolder(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd);
private: ~CLinkResolver();
static DWORD CALLBACK _ThreadStartCallBack(void *pv); static DWORD CALLBACK _SearchThreadProc(void *pv); static BOOL_PTR CALLBACK _DlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam);
void _HeuristicSearch(); void _InitDlg(HWND hDlg); DWORD _Search(); DWORD _GetTimeOut(); int _ScoreFindData(const WIN32_FIND_DATA *pfd); HRESULT _ProcessFoundFile(LPCTSTR pszPath, WIN32_FIND_DATAW * pwfdw); BOOL _SearchInFolder(LPCTSTR pszFolder, int cLevels); HRESULT _InitWalkObject();
HANDLE _hThread; DWORD _dwTimeOutDelta; HWND _hDlg; UINT_PTR _idtDelayedShow; // timer for delayed-show
DWORD _fifFlags; // FIF_ flags
CTracker *_ptracker; // Implements ObjectID-based link tracking
DWORD _TrackerRestrictions; // Flags from the TrkMendRestrictions enumeration
DWORD _dwSearchFlags; int _iFolderBonus; WCHAR _wszSearchSpec[64]; // holds file extension filter for search
LPCWSTR _pwszSearchSpec; // NULL for folders
IShellTreeWalker *_pstw;
BOOL _fFindLnk; // are we looking for a lnk file?
DWORD _dwMatch; // must match attributes
WIN32_FIND_DATA _ofd; // original find data
DWORD _dwTimeLimit; // don't go past this
BOOL _bContinue; // keep going
LPCTSTR _pszSearchOrigin; // path where current search originated, to help avoid dup searchs
LPCTSTR _pszSearchOriginFirst; // path where search originated, to help avoid dup searchs
int _iScore; // score for current item
WIN32_FIND_DATA _fdFound; // results
WIN32_FIND_DATA _sfd; // to save stack space
UINT _dwResolveFlags; // SLR_ flags
TCHAR _szSearchStart[MAX_PATH]; };
// NOTE:(seanf) This is sleazy - This fn is defined in shlobj.h, but only if urlmon.h
// was included first. Rather than monkey with the include order in
// shellprv.h, we'll duplicate the prototype here, where SOFTDISTINFO
// is now defined.
SHDOCAPI_(DWORD) SoftwareUpdateMessageBox(HWND hWnd, LPCWSTR pszDistUnit, DWORD dwFlags, LPSOFTDISTINFO psdi);
// The following strings are used to support the shell link set path hack that
// allows us to bless links for Darwin without exposing stuff from IShellLinkDataList
#define DARWINGUID_TAG TEXT("::{9db1186e-40df-11d1-aa8c-00c04fb67863}:")
#define LOGO3GUID_TAG TEXT("::{9db1186f-40df-11d1-aa8c-00c04fb67863}:")
#define TF_DEBUGLINKCODE 0x00800000
EXTERN_C BOOL IsFolderShortcut(LPCTSTR pszName);
class CDarwinContextMenuCB : public IContextMenuCB { public: CDarwinContextMenuCB() : _cRef(1) { }
// IUnknown
STDMETHOD(QueryInterface)(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CDarwinContextMenuCB, IContextMenuCB), // IID_IContextMenuCB
{ 0 }, }; return QISearch(this, qit, riid, ppv); }
STDMETHOD_(ULONG,AddRef)() { return InterlockedIncrement(&_cRef); }
STDMETHOD_(ULONG,Release)() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; }
// IContextMenuCB
STDMETHOD(CallBack)(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam);
public:
void SetProductCodeFromDarwinID(LPCTSTR szDarwinID) { MsiDecomposeDescriptor(szDarwinID, _szProductCode, NULL, NULL, NULL); }
private: LONG _cRef; TCHAR _szProductCode[MAX_PATH]; };
CShellLink::CShellLink() : _cRef(1) { _ptracker = new CTracker(this); _ResetPersistData(); }
CShellLink::~CShellLink() { _ResetPersistData(); // free all data
if (_pcbDarwin) { _pcbDarwin->Release(); }
if (_pdtSrc) { _pdtSrc->Release(); }
if (_pxi) { _pxi->Release(); }
if (_pxiA) { _pxiA->Release(); }
if (_pxthumb) { _pxthumb->Release(); }
Str_SetPtr(&_pszCurFile, NULL); Str_SetPtr(&_pszRelSource, NULL);
if (_ptracker) { delete _ptracker; } }
// Private interface used for testing
/* 7c9e512f-41d7-11d1-8e2e-00c04fb9386d */ EXTERN_C const IID IID_ISLTracker = { 0x7c9e512f, 0x41d7, 0x11d1, {0x8e, 0x2e, 0x00, 0xc0, 0x4f, 0xb9, 0x38, 0x6d} };
STDMETHODIMP CShellLink::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CShellLink, IShellLinkA), QITABENT(CShellLink, IShellLinkW), QITABENT(CShellLink, IPersistFile), QITABENT(CShellLink, IPersistStream), QITABENT(CShellLink, IShellExtInit), QITABENTMULTI(CShellLink, IContextMenu, IContextMenu3), QITABENTMULTI(CShellLink, IContextMenu2, IContextMenu3), QITABENT(CShellLink, IContextMenu3), QITABENT(CShellLink, IDropTarget), QITABENT(CShellLink, IExtractIconA), QITABENT(CShellLink, IExtractIconW), QITABENT(CShellLink, IShellLinkDataList), QITABENT(CShellLink, IQueryInfo), QITABENT(CShellLink, IPersistPropertyBag), QITABENT(CShellLink, IObjectWithSite), QITABENT(CShellLink, IServiceProvider), QITABENT(CShellLink, IFilter), QITABENT(CShellLink, IExtractImage2), QITABENTMULTI(CShellLink, IExtractImage, IExtractImage2), QITABENT(CShellLink, ICustomizeInfoTip), { 0 }, }; HRESULT hr = QISearch(this, qit, riid, ppvObj); if (FAILED(hr) && (IID_ISLTracker == riid) && _ptracker) { // ISLTracker is a private test interface, and isn't implemented
*ppvObj = SAFECAST(_ptracker, ISLTracker*); _ptracker->AddRef(); hr = S_OK; } return hr; }
STDMETHODIMP_(ULONG) CShellLink::AddRef() { return InterlockedIncrement(&_cRef); }
void CShellLink::_ClearTrackerData() { if (_ptracker) _ptracker->InitNew(); }
void CShellLink::_ResetPersistData() { Pidl_Set(&_pidl, NULL);
_FreeLinkInfo(); _ClearTrackerData();
Str_SetPtr(&_pszName, NULL); Str_SetPtr(&_pszRelPath, NULL); Str_SetPtr(&_pszWorkingDir, NULL); Str_SetPtr(&_pszArgs, NULL); Str_SetPtr(&_pszIconLocation, NULL); Str_SetPtr(&_pszPrefix, NULL);
if (_pExtraData) { SHFreeDataBlockList(_pExtraData); _pExtraData = NULL; }
// init data members. all others are zero inited
memset(&_sld, 0, sizeof(_sld));
_sld.iShowCmd = SW_SHOWNORMAL;
_bExpandedIcon = FALSE; }
STDMETHODIMP_(ULONG) CShellLink::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; }
#ifdef DEBUG
void DumpPLI(PCLINKINFO pli) { DebugMsg(DM_TRACE, TEXT("DumpPLI:")); if (pli) { const void *p; if (GetLinkInfoData(pli, LIDT_VOLUME_SERIAL_NUMBER, &p)) DebugMsg(DM_TRACE, TEXT("\tSerial #\t%8X"), *(DWORD *)p);
if (GetLinkInfoData(pli, LIDT_DRIVE_TYPE, &p)) DebugMsg(DM_TRACE, TEXT("\tDrive Type\t%d"), *(DWORD *)p);
if (GetLinkInfoData(pli, LIDT_VOLUME_LABEL, &p)) DebugMsg(DM_TRACE, TEXT("\tLabel\t%hs"), p);
if (GetLinkInfoData(pli, LIDT_LOCAL_BASE_PATH, &p)) DebugMsg(DM_TRACE, TEXT("\tBase Path\t%hs"), p);
if (GetLinkInfoData(pli, LIDT_NET_RESOURCE, &p)) DebugMsg(DM_TRACE, TEXT("\tNet Res\t%hs"), p);
if (GetLinkInfoData(pli, LIDT_COMMON_PATH_SUFFIX, &p)) DebugMsg(DM_TRACE, TEXT("\tPath Sufix\t%hs"), p); } } #else
#define DumpPLI(p)
#endif
// Compare _sld to a WIN32_FIND_DATA
BOOL CShellLink::_IsEqualFindData(const WIN32_FIND_DATA *pfd) { return (pfd->dwFileAttributes == _sld.dwFileAttributes) && (CompareFileTime(&pfd->ftCreationTime, &_sld.ftCreationTime) == 0) && (CompareFileTime(&pfd->ftLastWriteTime, &_sld.ftLastWriteTime) == 0) && (pfd->nFileSizeLow == _sld.nFileSizeLow); }
BOOL CShellLink::_SetFindData(const WIN32_FIND_DATA *pfd) { if (!_IsEqualFindData(pfd)) { _sld.dwFileAttributes = pfd->dwFileAttributes; _sld.ftCreationTime = pfd->ftCreationTime; _sld.ftLastAccessTime = pfd->ftLastAccessTime; _sld.ftLastWriteTime = pfd->ftLastWriteTime; _sld.nFileSizeLow = pfd->nFileSizeLow; _bDirty = TRUE; return TRUE; } return FALSE; }
// make a copy into LocalAlloc memory, to avoid having to load linkinfo.dll
// just to call DestroyLinkInfo()
PLINKINFO CopyLinkInfo(PCLINKINFO pcliSrc) { ASSERT(pcliSrc); DWORD dwSize = pcliSrc->ucbSize; // size of this thing
PLINKINFO pli = (PLINKINFO)LocalAlloc(LPTR, dwSize); // make a copy
if (pli) CopyMemory(pli, pcliSrc, dwSize); return pli; }
void CShellLink::_FreeLinkInfo() { if (_pli) { LocalFree((HLOCAL)_pli); _pli = NULL; } }
// creates a LINKINFO _pli from a given file name
//
// returns:
//
// success, pointer to the LINKINFO
// NULL this link does not have LINKINFO
PLINKINFO CShellLink::_GetLinkInfo(LPCTSTR pszPath) { // this bit disables LINKINFO tracking on a per link basis, this is set
// externally by admins to make links more "transparent"
if (!(_sld.dwFlags & SLDF_FORCE_NO_LINKINFO)) { if (pszPath) { PLINKINFO pliNew; if (CreateLinkInfo(pszPath, &pliNew)) { // avoid marking the link dirty if the linkinfo
// blocks are the same, comparing the bits
// gives us an accurate positive test
if (!_pli || (_pli->ucbSize != pliNew->ucbSize) || memcmp(_pli, pliNew, pliNew->ucbSize)) { _FreeLinkInfo();
_pli = CopyLinkInfo(pliNew); _bDirty = TRUE; }
DumpPLI(_pli);
DestroyLinkInfo(pliNew); } } } return _pli; }
void PathGetRelative(LPTSTR pszPath, LPCTSTR pszFrom, DWORD dwAttrFrom, LPCTSTR pszRel) { TCHAR szRoot[MAX_PATH];
StringCchCopy(szRoot, ARRAYSIZE(szRoot), pszFrom); if (!(dwAttrFrom & FILE_ATTRIBUTE_DIRECTORY)) { PathRemoveFileSpec(szRoot); }
ASSERT(PathIsRelative(pszRel));
PathCombine(pszPath, szRoot, pszRel); }
//
// update the working dir to match changes being made to the link target
//
void CShellLink::_UpdateWorkingDir(LPCTSTR pszNew) { TCHAR szOld[MAX_PATH], szPath[MAX_PATH];
if ((_sld.dwFlags & SLDF_HAS_DARWINID) || (_pszWorkingDir == NULL) || (_pszWorkingDir[0] == 0) || StrChr(_pszWorkingDir, TEXT('%')) || (_pidl == NULL) || !SHGetPathFromIDList(_pidl, szOld) || (lstrcmpi(szOld, pszNew) == 0)) { return; }
if (PathRelativePathTo(szPath, szOld, _sld.dwFileAttributes, _pszWorkingDir, FILE_ATTRIBUTE_DIRECTORY)) { PathGetRelative(szOld, pszNew, GetFileAttributes(pszNew), szPath); // get result is szOld
if (PathIsDirectory(szOld)) { DebugMsg(DM_TRACE, TEXT("working dir updated to %s"), szOld); Str_SetPtr(&_pszWorkingDir, szOld); _bDirty = TRUE; } } }
HRESULT CShellLink::_SetSimplePIDL(LPCTSTR pszPath) { LPITEMIDLIST pidl; WIN32_FIND_DATA fd = {0}; fd.dwFileAttributes = _sld.dwFileAttributes; HRESULT hr = SHSimpleIDListFromFindData(pszPath, &fd, &pidl); if (SUCCEEDED(hr)) { hr = _SetPIDLPath(pidl, NULL, FALSE); ILFree(pidl); } return hr; }
// set the pidl either based on a new pidl or a path
// this will set the dirty flag if this info is different from the current
//
// in:
// pidlNew if non-null, use as new PIDL for link
// pszPath if non-null, create a pidl for this and set it
//
// returns:
// hr based on success
// FAILED() codes on failure (parsing failure for path case)
HRESULT CShellLink::_SetPIDLPath(LPCITEMIDLIST pidl, LPCTSTR pszPath, BOOL bUpdateTrackingData) { LPITEMIDLIST pidlCreated; HRESULT hr;
if (pszPath && !pidl) { // path as input. this can map the pidl into the alias form (relative to
// ::{my docs} for example) but allow link to override that behavior
ILCFP_FLAGS ilcfpFlags = (_sld.dwFlags & SLDF_NO_PIDL_ALIAS) ? ILCFP_FLAG_NO_MAP_ALIAS : ILCFP_FLAG_NORMAL;
hr = ILCreateFromPathEx(pszPath, NULL, ilcfpFlags, &pidlCreated, NULL); // Force a SHGetPathFromIDList later so that the linkinfo will not get confused by letter case changing
// as in c:\Winnt\System32\App.exe versus C:\WINNT\system32\app.exe
pszPath = NULL; } else if (!pszPath && pidl) { // pidl as input, make copy that we will keep
hr = SHILClone(pidl, &pidlCreated); } else if (!pszPath && !pidl) { pidlCreated = NULL; // setting to empty
hr = S_OK; } else { // can't set path and pidl at the same time
hr = E_FAIL; }
if (SUCCEEDED(hr)) { // this data needs to be kept in sync with _pidl
_RemoveExtraDataSection(EXP_SPECIAL_FOLDER_SIG);
if (pidlCreated) { TCHAR szPath[MAX_PATH];
if (!_pidl || !ILIsEqual(_pidl, pidlCreated)) { // new pidl
_bDirty = TRUE; }
if (!pszPath && SHGetPathFromIDList(pidlCreated, szPath)) { pszPath = szPath; }
if (pszPath) { // needs old _pidl to work
_UpdateWorkingDir(pszPath); }
ILFree(_pidl); _pidl = pidlCreated;
if (pszPath) { if (bUpdateTrackingData) { // this is a file/folder, get tracking info (ignore failures)
_GetLinkInfo(pszPath); // the LinkInfo (_pli)
_GetFindDataAndTracker(pszPath); // tracker & find data
} } else { // not a file, clear the tracking info
WIN32_FIND_DATA fd = {0}; _SetFindData(&fd); _ClearTrackerData(); _FreeLinkInfo(); } } else { // clear out the contents of the link
_ResetPersistData(); _bDirty = TRUE; } }
return hr; }
// compute the relative path for the target is there is one
// pszPath is optionatl to test if there is a relative path
BOOL CShellLink::_GetRelativePath(LPTSTR pszPath) { BOOL bRet = FALSE;
LPCTSTR pszPathRel = _pszRelSource ? _pszRelSource : _pszCurFile; if (pszPathRel && _pszRelPath) { TCHAR szRoot[MAX_PATH];
StringCchCopy(szRoot, ARRAYSIZE(szRoot), pszPathRel); PathRemoveFileSpec(szRoot); // pszPathRel is a file (not a directory)
// this can fail for really deep paths
if (PathCombine(pszPath, szRoot, _pszRelPath)) { bRet = TRUE; } } return bRet; }
void CShellLink::_GetFindData(WIN32_FIND_DATA *pfd) { ZeroMemory(pfd, sizeof(*pfd));
pfd->dwFileAttributes = _sld.dwFileAttributes; pfd->ftCreationTime = _sld.ftCreationTime; pfd->ftLastAccessTime = _sld.ftLastAccessTime; pfd->ftLastWriteTime = _sld.ftLastWriteTime; pfd->nFileSizeLow = _sld.nFileSizeLow;
TCHAR szPath[MAX_PATH]; SHGetPathFromIDList(_pidl, szPath); ASSERT(szPath[0]); // no one should call this on a pidl without a path
StringCchCopy(pfd->cFileName, ARRAYSIZE(pfd->cFileName), PathFindFileName(szPath)); }
STDMETHODIMP CShellLink::GetPath(LPWSTR pszFile, int cchFile, WIN32_FIND_DATAW *pfd, DWORD fFlags) { TCHAR szPath[MAX_PATH];
if (_sld.dwFlags & SLDF_HAS_DARWINID) { // For darwin enabled links, we do NOT want to have to go and call
// ParseDarwinID here because that could possible force the app to install.
// So, instead we return the path to the icon as the path for darwin enable
// shortcuts. This allows the icon to be correct and since the darwin icon
// will always be an .exe, ensuring that the context menu will be correct.
SHExpandEnvironmentStrings(_pszIconLocation ? _pszIconLocation : TEXT(""), szPath, ARRAYSIZE(szPath)); } else { DumpPLI(_pli);
if (!_pidl || !SHGetPathFromIDListEx(_pidl, szPath, (fFlags & SLGP_SHORTPATH) ? GPFIDL_ALTNAME : 0)) szPath[0] = 0;
// Must do the pfd thing before we munge szPath, because the stuff
// we do to szPath might render it unsuitable for PathFindFileName.
// (For example, "C:\WINNT\Profiles\Bob" might turn into "%USERPROFILE%",
// and we want to make sure we save "Bob" before it's too late.)
if (pfd) { memset(pfd, 0, sizeof(*pfd)); if (szPath[0]) { pfd->dwFileAttributes = _sld.dwFileAttributes; pfd->ftCreationTime = _sld.ftCreationTime; pfd->ftLastAccessTime = _sld.ftLastAccessTime; pfd->ftLastWriteTime = _sld.ftLastWriteTime; pfd->nFileSizeLow = _sld.nFileSizeLow; SHTCharToUnicode(PathFindFileName(szPath), pfd->cFileName, ARRAYSIZE(pfd->cFileName)); } }
if ((_sld.dwFlags & SLDF_HAS_EXP_SZ) && (fFlags & SLGP_RAWPATH)) { // Special case where we grab the Target name from
// the extra data section of the link rather than from
// the pidl. We do this after we grab the name from the pidl
// so that if we fail, then there is still some hope that a
// name can be returned.
LPEXP_SZ_LINK pszl = (LPEXP_SZ_LINK)SHFindDataBlock(_pExtraData, EXP_SZ_LINK_SIG); if (pszl) { SHUnicodeToTChar(pszl->swzTarget, szPath, ARRAYSIZE(szPath)); DebugMsg(DM_TRACE, TEXT("CShellLink::GetPath() %s (from xtra data)"), szPath); } } }
if (pszFile) { SHTCharToUnicode(szPath, pszFile, cchFile); }
// note the lame return semantics, check for S_OK to be sure you have a path
return szPath[0] ? S_OK : S_FALSE; }
STDMETHODIMP CShellLink::GetIDList(LPITEMIDLIST *ppidl) { if (_pidl) { return SHILClone(_pidl, ppidl); }
*ppidl = NULL; return S_FALSE; // success but empty
}
#ifdef DEBUG
#define DumpTimes(ftCreate, ftAccessed, ftWrite) \
DebugMsg(DM_TRACE, TEXT("create %8x%8x"), ftCreate.dwLowDateTime, ftCreate.dwHighDateTime); \ DebugMsg(DM_TRACE, TEXT("accessed %8x%8x"), ftAccessed.dwLowDateTime, ftAccessed.dwHighDateTime); \ DebugMsg(DM_TRACE, TEXT("write %8x%8x"), ftWrite.dwLowDateTime, ftWrite.dwHighDateTime);
#else
#define DumpTimes(ftCreate, ftAccessed, ftWrite)
#endif
void CheckAndFixNullCreateTime(LPCTSTR pszFile, FILETIME *pftCreationTime, const FILETIME *pftLastWriteTime) { if (IsNullTime(pftCreationTime) && !IsNullTime(pftLastWriteTime)) { // this file has a bogus create time, set it to the last accessed time
HANDLE hfile = CreateFile(pszFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE != hfile) { DebugMsg(DM_TRACE, TEXT("create %8x%8x"), pftCreationTime->dwLowDateTime, pftCreationTime->dwHighDateTime);
if (SetFileTime(hfile, pftLastWriteTime, NULL, NULL)) { // get the time back to make sure we match the precision of the file system
*pftCreationTime = *pftLastWriteTime; // patch this up
#ifdef DEBUG
{ FILETIME ftCreate, ftAccessed, ftWrite; if (GetFileTime((HANDLE)hfile, &ftCreate, &ftAccessed, &ftWrite)) { // we can't be sure that ftCreate == pftCreationTime because the GetFileTime
// spec says that the granularity of Set and Get may be different.
DumpTimes(ftCreate, ftAccessed, ftWrite); } } #endif
} else { DebugMsg(DM_TRACE, TEXT("unable to set create time")); } CloseHandle(hfile); } } }
//
// sets the current links find data and link tracker based on a path.
//
// returns:
// S_OK the file/folder is there
// FAILED(hr) the file could not be found
// _bDirty set if the find data for the file (or tracker data) has been updated
//
HRESULT CShellLink::_GetFindDataAndTracker(LPCTSTR pszPath) { WIN32_FIND_DATA fd = {0}; HRESULT hr = S_OK; // Open the file or directory or root path. We have to set FILE_FLAG_BACKUP_SEMANTICS
// to get CreateFile to give us directory handles.
HANDLE hFile = CreateFile(pszPath, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (INVALID_HANDLE_VALUE != hFile) { // Get the file attributes
BY_HANDLE_FILE_INFORMATION fi; if (GetFileInformationByHandle(hFile, &fi)) { fd.dwFileAttributes = fi.dwFileAttributes; fd.ftCreationTime = fi.ftCreationTime; fd.ftLastAccessTime = fi.ftLastAccessTime; fd.ftLastWriteTime = fi.ftLastWriteTime; fd.nFileSizeLow = fi.nFileSizeLow;
// save the Object IDs as well.
if (_ptracker) { if (SUCCEEDED(_ptracker->InitFromHandle(hFile, pszPath))) { if (_ptracker->IsDirty()) _bDirty = TRUE; } else { // Save space in the .lnk file
_ptracker->InitNew(); } } } else { hr = GetLastHRESULT(); } CloseHandle(hFile); } else { hr = GetLastHRESULT(); }
if (SUCCEEDED(hr)) { // If this file doesn't have a create time for some reason, set it to be the
// current last-write time.
CheckAndFixNullCreateTime(pszPath, &fd.ftCreationTime, &fd.ftLastWriteTime); _SetFindData(&fd); // update _bDirty
} return hr; }
// IShellLink::SetIDList()
//
// note: the error returns here are really poor, they don't express
// any failures that might have occured (out of memory for example)
STDMETHODIMP CShellLink::SetIDList(LPCITEMIDLIST pidlnew) { _SetPIDLPath(pidlnew, NULL, TRUE); return S_OK; // return
}
BOOL DifferentStrings(LPCTSTR psz1, LPCTSTR psz2) { if (psz1 && psz2) { return lstrcmp(psz1, psz2); } else { return (!psz1 && psz2) || (psz1 && !psz2); } }
// NOTE: NULL string ptr is valid argument for this function
HRESULT CShellLink::_SetField(LPTSTR *ppszField, LPCWSTR pszValueW) { TCHAR szValue[INFOTIPSIZE], *pszValue;
if (pszValueW) { SHUnicodeToTChar(pszValueW, szValue, ARRAYSIZE(szValue)); pszValue = szValue; } else { pszValue = NULL; }
if (DifferentStrings(*ppszField, pszValue)) { _bDirty = TRUE; }
Str_SetPtr(ppszField, pszValue); return S_OK; }
HRESULT CShellLink::_SetField(LPTSTR *ppszField, LPCSTR pszValueA) { TCHAR szValue[INFOTIPSIZE], *pszValue;
if (pszValueA) { SHAnsiToTChar(pszValueA, szValue, ARRAYSIZE(szValue)); pszValue = szValue; } else { pszValue = NULL; }
if (DifferentStrings(*ppszField, pszValue)) { _bDirty = TRUE; }
Str_SetPtr(ppszField, pszValue); return S_OK; }
HRESULT CShellLink::_GetField(LPCTSTR pszField, LPWSTR pszValue, int cchValue) { if (pszField == NULL) { *pszValue = 0; } else { SHLoadIndirectString(pszField, pszValue, cchValue, NULL); }
return S_OK; }
HRESULT CShellLink::_GetField(LPCTSTR pszField, LPSTR pszValue, int cchValue) { LPWSTR pwsz = (LPWSTR)alloca(cchValue * sizeof(WCHAR));
_GetField(pszField, pwsz, cchValue);
SHUnicodeToAnsi(pwsz, pszValue, cchValue);
return S_OK; }
// order is important
const int c_rgcsidlUserFolders[] = { CSIDL_MYPICTURES | TEST_SUBFOLDER, CSIDL_PERSONAL | TEST_SUBFOLDER, CSIDL_DESKTOPDIRECTORY | TEST_SUBFOLDER, CSIDL_COMMON_DESKTOPDIRECTORY | TEST_SUBFOLDER, };
STDAPI_(void) SHMakeDescription(LPCITEMIDLIST pidlDesc, int ids, LPTSTR pszDesc, UINT cch) { LPCITEMIDLIST pidlName = pidlDesc; TCHAR szPath[MAX_PATH], szFormat[64]; DWORD gdn;
ASSERT(pidlDesc); //
// we want to only show the INFOLDER name for
// folders the user sees often. so in the desktop
// or mydocs or mypics we just show that name.
// otherwise show the whole path.
//
// NOTE - there can be some weirdness if you start making
// shortcuts to special folders off the desktop
// specifically if you make a shortcut to mydocs the comment
// ends up being %USERPROFILE%, but this is a rare enough
// case that i dont think we need to worry too much.
//
SHGetNameAndFlags(pidlDesc, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath), NULL); int csidl = GetSpecialFolderID(szPath, c_rgcsidlUserFolders, ARRAYSIZE(c_rgcsidlUserFolders)); if (-1 != csidl) { gdn = SHGDN_INFOLDER | SHGDN_FORADDRESSBAR; switch (csidl) { case CSIDL_DESKTOPDIRECTORY: case CSIDL_COMMON_DESKTOPDIRECTORY: { ULONG cb; if (csidl == GetSpecialFolderParentIDAndOffset(pidlDesc, &cb)) { // reorient based off the desktop.
pidlName = (LPCITEMIDLIST)(((BYTE *)pidlDesc) + cb); } } break;
case CSIDL_PERSONAL: if (SUCCEEDED(GetMyDocumentsDisplayName(szPath, ARRAYSIZE(szPath)))) pidlName = NULL; break;
default: break; } } else gdn = SHGDN_FORPARSING | SHGDN_FORADDRESSBAR;
if (pidlName) { SHGetNameAndFlags(pidlName, gdn, szPath, ARRAYSIZE(szPath), NULL); }
if (ids != -1) { LoadString(HINST_THISDLL, ids, szFormat, ARRAYSIZE(szFormat));
wnsprintf(pszDesc, cch, szFormat, szPath); } else StrCpyN(pszDesc, szPath, cch); }
void _MakeDescription(LPCITEMIDLIST pidlTo, LPTSTR pszDesc, UINT cch) { LPCITEMIDLIST pidlInner; if (ILIsRooted(pidlTo)) { pidlInner = ILRootedFindIDList(pidlTo); } else { pidlInner = pidlTo; }
LPITEMIDLIST pidlParent = ILCloneParent(pidlInner); if (pidlParent) { SHMakeDescription(pidlParent, IDS_LOCATION, pszDesc, cch); ILFree(pidlParent); } else { *pszDesc = 0; } }
STDMETHODIMP CShellLink::GetDescription(LPWSTR pszDesc, int cchMax) { return _GetField(_pszName, pszDesc, cchMax); }
STDMETHODIMP CShellLink::GetDescription(LPSTR pszDesc, int cchMax) { return _GetField(_pszName, pszDesc, cchMax); }
STDMETHODIMP CShellLink::SetDescription(LPCWSTR pszDesc) { return _SetField(&_pszName, pszDesc); }
STDMETHODIMP CShellLink::SetDescription(LPCSTR pszDesc) { return _SetField(&_pszName, pszDesc); }
STDMETHODIMP CShellLink::GetWorkingDirectory(LPWSTR pszDir, int cchDir) { return _GetField(_pszWorkingDir, pszDir, cchDir); }
STDMETHODIMP CShellLink::GetWorkingDirectory(LPSTR pszDir, int cchDir) { return _GetField(_pszWorkingDir, pszDir, cchDir); }
STDMETHODIMP CShellLink::SetWorkingDirectory(LPCWSTR pszWorkingDir) { return _SetField(&_pszWorkingDir, pszWorkingDir); }
STDMETHODIMP CShellLink::SetWorkingDirectory(LPCSTR pszDir) { return _SetField(&_pszWorkingDir, pszDir); }
STDMETHODIMP CShellLink::GetArguments(LPWSTR pszArgs, int cchArgs) { return _GetField(_pszArgs, pszArgs, cchArgs); }
STDMETHODIMP CShellLink::GetArguments(LPSTR pszArgs, int cch) { return _GetField(_pszArgs, pszArgs, cch); }
STDMETHODIMP CShellLink::SetArguments(LPCWSTR pszArgs) { return _SetField(&_pszArgs, pszArgs); }
STDMETHODIMP CShellLink::SetArguments(LPCSTR pszArgs) { return _SetField(&_pszArgs, pszArgs); }
STDMETHODIMP CShellLink::GetHotkey(WORD *pwHotkey) { *pwHotkey = _sld.wHotkey; return S_OK; }
STDMETHODIMP CShellLink::SetHotkey(WORD wHotkey) { if (_sld.wHotkey != wHotkey) { _bDirty = TRUE; _sld.wHotkey = wHotkey; } return S_OK; }
STDMETHODIMP CShellLink::GetShowCmd(int *piShowCmd) { *piShowCmd = _sld.iShowCmd; return S_OK; }
STDMETHODIMP CShellLink::SetShowCmd(int iShowCmd) { if (_sld.iShowCmd != iShowCmd) { _bDirty = TRUE; } _sld.iShowCmd = iShowCmd; return S_OK; }
// IShellLinkW::GetIconLocation
STDMETHODIMP CShellLink::GetIconLocation(LPWSTR pszIconPath, int cchIconPath, int *piIcon) { _UpdateIconFromExpIconSz();
_GetField(_pszIconLocation, pszIconPath, cchIconPath); *piIcon = _sld.iIcon;
return S_OK; }
// IShellLinkA::GetIconLocation
STDMETHODIMP CShellLink::GetIconLocation(LPSTR pszPath, int cch, int *piIcon) { WCHAR szPath[MAX_PATH]; HRESULT hr = GetIconLocation(szPath, ARRAYSIZE(szPath), piIcon); if (SUCCEEDED(hr)) { SHUnicodeToAnsi(szPath, pszPath, cch); } return hr; }
// IShellLinkW::SetIconLocation
// NOTE:
// pszIconPath may be NULL
STDMETHODIMP CShellLink::SetIconLocation(LPCWSTR pszIconPath, int iIcon) { TCHAR szIconPath[MAX_PATH];
if (pszIconPath) { SHUnicodeToTChar(pszIconPath, szIconPath, ARRAYSIZE(szIconPath)); }
if (pszIconPath) { HANDLE hToken; TCHAR szIconPathEnc[MAX_PATH];
if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hToken) == FALSE) { hToken = NULL; }
if (PathUnExpandEnvStringsForUser(hToken, szIconPath, szIconPathEnc, ARRAYSIZE(szIconPathEnc)) != 0) { EXP_SZ_LINK expLink;
// mark that link has expandable strings, and add them
_sld.dwFlags |= SLDF_HAS_EXP_ICON_SZ; // should this be unique for icons?
LPEXP_SZ_LINK lpNew = (LPEXP_SZ_LINK)SHFindDataBlock(_pExtraData, EXP_SZ_ICON_SIG); if (!lpNew) { lpNew = &expLink; expLink.cbSize = 0; expLink.dwSignature = EXP_SZ_ICON_SIG; }
// store both A and W version (for no good reason!)
SHTCharToAnsi(szIconPathEnc, lpNew->szTarget, ARRAYSIZE(lpNew->szTarget)); SHTCharToUnicode(szIconPathEnc, lpNew->swzTarget, ARRAYSIZE(lpNew->swzTarget));
// See if this is a new entry that we need to add
if (lpNew->cbSize == 0) { lpNew->cbSize = sizeof(*lpNew); _AddExtraDataSection((DATABLOCK_HEADER *)lpNew); } } else { _sld.dwFlags &= ~SLDF_HAS_EXP_ICON_SZ; _RemoveExtraDataSection(EXP_SZ_ICON_SIG); } if (hToken != NULL) { CloseHandle(hToken); } }
_SetField(&_pszIconLocation, pszIconPath);
if (_sld.iIcon != iIcon) { _sld.iIcon = iIcon; _bDirty = TRUE; }
if ((_sld.dwFlags & SLDF_HAS_DARWINID) && pszIconPath) { // NOTE: The comment below is for darwin as it shipped in win98/IE4.01,
// and is fixed in the > NT5 versions of the shell's darwin implementation.
//
// for darwin enalbed links, we make the path point to the
// icon location (which is must be of the type (ie same ext) as the real
// destination. So, if I want a darwin link to readme.txt, the shell
// needs the icon to be icon1.txt, which is not good!!. This ensures
// that the context menu will be correct and allows us to return
// from CShellLink::GetPath & CShellLink::GetIDList without faulting the
// application in because we lie to people and tell them that we
// really point to our icon, which is the same type as the real target,
// thus making our context menu be correct.
_SetPIDLPath(NULL, szIconPath, FALSE); }
return S_OK; }
// IShellLinkA::SetIconLocation
STDMETHODIMP CShellLink::SetIconLocation(LPCSTR pszPath, int iIcon) { WCHAR szPath[MAX_PATH]; LPWSTR pszPathW;
if (pszPath) { SHAnsiToUnicode(pszPath, szPath, ARRAYSIZE(szPath)); pszPathW = szPath; } else { pszPathW = NULL; }
return SetIconLocation(pszPathW, iIcon); }
HRESULT CShellLink::_InitExtractImage() { HRESULT hr; if (_pxthumb) { hr = S_OK; } else { hr = _GetUIObject(NULL, IID_PPV_ARG(IExtractImage, &_pxthumb)); } return hr; }
// IExtractImage
STDMETHODIMP CShellLink::GetLocation(LPWSTR pszPathBuffer, DWORD cch, DWORD * pdwPriority, const SIZE * prgSize, DWORD dwRecClrDepth, DWORD *pdwFlags) { HRESULT hr = _InitExtractImage(); if (SUCCEEDED(hr)) { hr = _pxthumb->GetLocation(pszPathBuffer, cch, pdwPriority, prgSize, dwRecClrDepth, pdwFlags); } return hr; }
STDMETHODIMP CShellLink::Extract(HBITMAP *phBmpThumbnail) { HRESULT hr = _InitExtractImage(); if (SUCCEEDED(hr)) { hr = _pxthumb->Extract(phBmpThumbnail); } return hr; }
STDMETHODIMP CShellLink::GetDateStamp(FILETIME *pftDateStamp) { HRESULT hr = _InitExtractImage(); if (SUCCEEDED(hr)) { IExtractImage2 * pExtract2; hr = _pxthumb->QueryInterface(IID_PPV_ARG(IExtractImage2, &pExtract2)); if (SUCCEEDED(hr)) { hr = pExtract2->GetDateStamp(pftDateStamp); pExtract2->Release(); } } return hr; }
// set the relative path, this is used before a link is saved so we know what
// we should use to store the link relative to as well as before the link is resolved
// so we know the new path to use with the saved relative path.
//
// in:
// pszPathRel path to make link target relative to, must be a path to
// a file, not a directory.
//
// dwReserved must be 0
//
// returns:
// S_OK relative path is set
//
STDMETHODIMP CShellLink::SetRelativePath(LPCWSTR pszPathRel, DWORD dwRes) { if (dwRes != 0) { return E_INVALIDARG; }
return _SetField(&_pszRelSource, pszPathRel); }
STDMETHODIMP CShellLink::SetRelativePath(LPCSTR pszPathRel, DWORD dwRes) { if (dwRes != 0) { return E_INVALIDARG; }
return _SetField(&_pszRelSource, pszPathRel); }
// IShellLink::Resolve()
//
// If SLR_UPDATE isn't set, check IPersistFile::IsDirty after
// calling this to see if the link info has changed and save it.
//
// returns:
// S_OK all things good
// S_FALSE user canceled (bummer, should be ERROR_CANCELLED)
STDMETHODIMP CShellLink::Resolve(HWND hwnd, DWORD dwResolveFlags) { HRESULT hr = _Resolve(hwnd, dwResolveFlags, 0); if (HRESULT_FROM_WIN32(ERROR_CANCELLED) == hr) { hr = S_FALSE; }
return hr; }
// converts version in text format (a,b,c,d) into two dwords (a,b), (c,d)
// The printed version number is of format a.b.d (but, we don't care)
// NOTE: Stolen from inet\urlmon\download\helpers.cxx
HRESULT GetVersionFromString(TCHAR *szBuf, DWORD *pdwFileVersionMS, DWORD *pdwFileVersionLS) { const TCHAR *pch = szBuf; TCHAR ch; USHORT n = 0; USHORT a = 0; USHORT b = 0; USHORT c = 0; USHORT d = 0;
enum HAVE { HAVE_NONE, HAVE_A, HAVE_B, HAVE_C, HAVE_D } have = HAVE_NONE;
*pdwFileVersionMS = 0; *pdwFileVersionLS = 0;
if (!pch) // default to zero if none provided
return S_OK;
if (lstrcmp(pch, TEXT("-1,-1,-1,-1")) == 0) { *pdwFileVersionMS = 0xffffffff; *pdwFileVersionLS = 0xffffffff; return S_OK; }
for (ch = *pch++;;ch = *pch++) { if ((ch == ',') || (ch == '\0')) { switch (have) { case HAVE_NONE: a = n; have = HAVE_A; break;
case HAVE_A: b = n; have = HAVE_B; break;
case HAVE_B: c = n; have = HAVE_C; break;
case HAVE_C: d = n; have = HAVE_D; break;
case HAVE_D: return E_INVALIDARG; // invalid arg
}
if (ch == '\0') { // all done convert a,b,c,d into two dwords of version
*pdwFileVersionMS = ((a << 16)|b); *pdwFileVersionLS = ((c << 16)|d);
return S_OK; }
n = 0; // reset
} else if ((ch < '0') || (ch > '9')) return E_INVALIDARG; // invalid arg
else n = n*10 + (ch - '0');
} /* end forever */
// NEVERREACHED
}
// Purpose: A _Resolve-time check to see if the link has
// Logo3 application channel
//
// Inputs: [LPCTSTR] - pszLogo3ID - the id/keyname for
// our Logo3 software.
//
// Outputs: [BOOL]
// - TRUE if our peek at the registry
// indicates we have an ad to show
// - FALSE indicates no new version
// to advertise.
//
// Algorithm: Check the software update registry info for the
// ID embedded in the link. This is a sleazy hack
// to avoid loading shdocvw and urlmon, which are
// the normal code path for this check.
// NOTE: The version checking logic is stolen from
// shell\shdocvw\sftupmb.cpp
HRESULT GetLogo3SoftwareUpdateInfo(LPCTSTR pszLogo3ID, LPSOFTDISTINFO psdi) { HRESULT hr = S_OK; HKEY hkeyDistInfo = 0; HKEY hkeyAvail = 0; HKEY hkeyAdvertisedVersion = 0; DWORD lResult = 0; DWORD dwSize = 0; DWORD dwType; TCHAR szBuffer[MAX_PATH]; TCHAR szVersionBuf[MAX_PATH]; DWORD dwLen = 0; DWORD dwCurAdvMS = 0; DWORD dwCurAdvLS = 0;
wnsprintf(szBuffer, ARRAYSIZE(szBuffer), TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%s"), pszLogo3ID);
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szBuffer, 0, KEY_READ, &hkeyDistInfo) != ERROR_SUCCESS) { hr = E_FAIL; goto Exit; } if (RegOpenKeyEx(hkeyDistInfo, TEXT("AvailableVersion"), 0, KEY_READ, &hkeyAvail) != ERROR_SUCCESS) { hr = E_FAIL; goto Exit; }
dwSize = sizeof(lResult); if (SHQueryValueEx(hkeyAvail, TEXT("Precache"), 0, &dwType, (unsigned char *)&lResult, &dwSize) == ERROR_SUCCESS) { // Precached value was the code download HR
if (lResult == S_OK) psdi->dwFlags = SOFTDIST_FLAG_USAGE_PRECACHE; }
dwSize = sizeof(szVersionBuf); if (SHQueryValueEx(hkeyAvail, TEXT("AdvertisedVersion"), NULL, &dwType, szVersionBuf, &dwSize) == ERROR_SUCCESS) { GetVersionFromString(szVersionBuf, &psdi->dwAdvertisedVersionMS, &psdi->dwAdvertisedVersionLS); // Get the AdState, if any
dwSize = sizeof(psdi->dwAdState); SHQueryValueEx(hkeyAvail, TEXT("AdState"), NULL, NULL, &psdi->dwAdState, &dwSize); }
dwSize = sizeof(szVersionBuf); if (SHQueryValueEx(hkeyAvail, NULL, NULL, &dwType, szVersionBuf, &dwSize) != ERROR_SUCCESS) { hr = S_FALSE; goto Exit; }
if (FAILED(GetVersionFromString(szVersionBuf, &psdi->dwUpdateVersionMS, &psdi->dwUpdateVersionLS))) { hr = S_FALSE; goto Exit; }
dwLen = sizeof(psdi->dwInstalledVersionMS); if (SHQueryValueEx(hkeyDistInfo, TEXT("VersionMajor"), 0, &dwType, &psdi->dwInstalledVersionMS, &dwLen) != ERROR_SUCCESS) { hr = S_FALSE; goto Exit; }
dwLen = sizeof(psdi->dwInstalledVersionLS); if (SHQueryValueEx(hkeyDistInfo, TEXT("VersionMinor"), 0, &dwType, &psdi->dwInstalledVersionLS, &dwLen) != ERROR_SUCCESS) { hr = S_FALSE; goto Exit; }
if (psdi->dwUpdateVersionMS > psdi->dwInstalledVersionMS || (psdi->dwUpdateVersionMS == psdi->dwInstalledVersionMS && psdi->dwUpdateVersionLS > psdi->dwInstalledVersionLS)) { hr = S_OK; } else { hr = S_FALSE; }
Exit: if (hkeyAdvertisedVersion) { RegCloseKey(hkeyAdvertisedVersion); }
if (hkeyAvail) { RegCloseKey(hkeyAvail); }
if (hkeyDistInfo) { RegCloseKey(hkeyDistInfo); }
return hr; }
// Purpose: A _Resolve-time check to see if the link has
// Logo3 application channel
//
// Inputs: [LPCTSTR] - pszLogo3ID - the id/keyname for
// our Logo3 software.
//
// Outputs: [BOOL]
// - TRUE if our peek at the registry
// indicates we have an ad to show
// - FALSE indicates no new version
// to advertise.
//
// Algorithm: Check the software update registry info for the
// ID embedded in the link. This is a sleazy hack
// to avoid loading shdocvw and urlmon, which are
// the normal code path for this check.
// The version checking logic is stolen from
BOOL FLogo3RegPeek(LPCTSTR pszLogo3ID) { BOOL bHaveAd = FALSE; SOFTDISTINFO sdi = { 0 }; DWORD dwAdStateNew = SOFTDIST_ADSTATE_NONE;
HRESULT hr = GetLogo3SoftwareUpdateInfo(pszLogo3ID, &sdi); // we need an HREF to work properly. The title and abstract are negotiable.
if (SUCCEEDED(hr)) { // see if this is an update the user already knows about.
// If it is, then skip the dialog.
if ( (sdi.dwUpdateVersionMS >= sdi.dwInstalledVersionMS || (sdi.dwUpdateVersionMS == sdi.dwInstalledVersionMS && sdi.dwUpdateVersionLS >= sdi.dwInstalledVersionLS)) && (sdi.dwUpdateVersionMS >= sdi.dwAdvertisedVersionMS || (sdi.dwUpdateVersionMS == sdi.dwAdvertisedVersionMS && sdi.dwUpdateVersionLS >= sdi.dwAdvertisedVersionLS))) { if (hr == S_OK) // new version
{ // we have a pending update, either on the net, or downloaded
if (sdi.dwFlags & SOFTDIST_FLAG_USAGE_PRECACHE) { dwAdStateNew = SOFTDIST_ADSTATE_DOWNLOADED; } else { dwAdStateNew = SOFTDIST_ADSTATE_AVAILABLE; } } else if (sdi.dwUpdateVersionMS == sdi.dwInstalledVersionMS && sdi.dwUpdateVersionLS == sdi.dwInstalledVersionLS) { // if installed version matches advertised, then we autoinstalled already
// NOTE: If the user gets gets channel notification, then runs out
// to the store and buys the new version, then installs it, we'll
// mistake this for an auto-install.
dwAdStateNew = SOFTDIST_ADSTATE_INSTALLED; }
// only show the dialog if we've haven't been in this ad state before for
// this update version
if (dwAdStateNew > sdi.dwAdState) { bHaveAd = TRUE; } } // if update is a newer version than advertised
}
return bHaveAd; }
// Purpose: A _Resolve-time check to see if the link has
// Logo3 application channel
//
// Inputs: [HWND] hwnd
// - The parent window (which could be the desktop).
// [DWORD] dwResolveFlags
// - Flags from the SLR_FLAGS enumeration.
//
// returns:
// S_OK The user wants to pursue the
// software update thus we should not continue
//
// S_FALSE No software update, or the user doesn't want it now.
// proceed with regular resolve path
//
// Algorithm: Check the software update registry info for the
// ID embedded in the link. If there's a new version
// advertised, prompt the user with shdocvw's message
// box. If the mb says update, tell the caller we
// don't want the link target, as we're headed to the
// link update page.
HRESULT CShellLink::_ResolveLogo3Link(HWND hwnd, DWORD dwResolveFlags) { HRESULT hr = S_FALSE; // default to no update.
if ((_sld.dwFlags & SLDF_HAS_LOGO3ID) && !SHRestricted(REST_NOLOGO3CHANNELNOTIFY)) { LPEXP_DARWIN_LINK pdl = (LPEXP_DARWIN_LINK)SHFindDataBlock(_pExtraData, EXP_LOGO3_ID_SIG); if (pdl) { TCHAR szLogo3ID[MAX_PATH]; WCHAR szwLogo3ID[MAX_PATH]; int cchBlessData; WCHAR *pwch;
TCHAR *pch = pdl->szwDarwinID;
// Ideally, we support multiple, semi-colon delmited IDs, for now
// just grab the first one.
for (pwch = pdl->szwDarwinID, cchBlessData = 0; *pch != ';' && *pch != '\0' && cchBlessData < MAX_PATH; pch++, pwch++, cchBlessData++) { szLogo3ID[cchBlessData] = *pch; szwLogo3ID[cchBlessData] = *pwch; } // and terminate
szLogo3ID[cchBlessData] = '\0'; szwLogo3ID[cchBlessData] = L'\0'; // Before well haul in shdocvw, we'll sneak a peak at our Logo3 reg goo
if (!(dwResolveFlags & SLR_NO_UI) && FLogo3RegPeek(szLogo3ID)) { // stuff stolen from shdocvw\util.cpp's CheckSoftwareUpdateUI
BOOL fLaunchUpdate = FALSE; SOFTDISTINFO sdi = { 0 }; sdi.cbSize = sizeof(sdi);
int nRes = SoftwareUpdateMessageBox(hwnd, szwLogo3ID, 0, &sdi);
if (nRes != IDABORT) { if (nRes == IDYES) { // NOTE: This differ's from Shdocvw in that we don't
// have the cool internal navigation stuff to play with.
// Originally, this was done with ShellExecEx. This failed
// because the http hook wasn't 100% reliable on Win95.
//ShellExecuteW(NULL, NULL, sdi.szHREF, NULL, NULL, 0);
hr = HlinkNavigateString(NULL, sdi.szHREF);
} // if user wants update
if (sdi.szTitle != NULL) SHFree(sdi.szTitle); if (sdi.szAbstract != NULL) SHFree(sdi.szAbstract); if (sdi.szHREF != NULL) SHFree(sdi.szHREF); fLaunchUpdate = nRes == IDYES && SUCCEEDED(hr); } // if no message box abort (error)
if (fLaunchUpdate) { hr = S_OK; } } } }
return hr; }
BOOL _TryRestoreConnection(HWND hwnd, LPCTSTR pszPath) { BOOL bRet = FALSE; if (!PathIsUNC(pszPath) && IsDisconnectedNetDrive(DRIVEID(pszPath))) { TCHAR szDrive[4]; szDrive[0] = *pszPath; szDrive[1] = TEXT(':'); szDrive[2] = 0; bRet = WNetRestoreConnection(hwnd, szDrive) == WN_SUCCESS; } return bRet; }
//
// updates then resolves LinkInfo associated with a CShellLink instance
// if the resolve results in a new path updates the pidl to the new path
//
// in:
// hwnd to post resolve UI on (if dwFlags indicates UI)
// dwResolveFlags IShellLink::Resolve() flags
//
// in/out:
// pszPath may be updated with new path to use in case of failure
//
// returns:
// FAILED() we failed the update, either UI cancel or memory failure,
// be sure to respect ERROR_CANCELLED
// S_OK we have a valid pli and pidl read to be used OR
// we should search for this path using the link search code
HRESULT CShellLink::_ResolveLinkInfo(HWND hwnd, DWORD dwResolveFlags, LPTSTR pszPath, DWORD *pfifFlags) { HRESULT hr; if (SHRestricted(REST_LINKRESOLVEIGNORELINKINFO)) { _TryRestoreConnection((dwResolveFlags & SLR_NO_UI) ? NULL : hwnd, pszPath); hr = _SetPIDLPath(NULL, pszPath, TRUE); } else { ASSERTMSG(_pli != NULL, "_ResolveLinkInfo should only be called when _pli != NULL");
DWORD dwLinkInfoFlags = (RLI_IFL_CONNECT | RLI_IFL_TEMPORARY);
if (!PathIsRoot(pszPath)) dwLinkInfoFlags |= RLI_IFL_LOCAL_SEARCH;
if (!(dwResolveFlags & SLR_NO_UI)) dwLinkInfoFlags |= RLI_IFL_ALLOW_UI;
ASSERT(!(dwLinkInfoFlags & RLI_IFL_UPDATE));
TCHAR szResolvedPath[MAX_PATH]; DWORD dwOutFlags; if (ResolveLinkInfo(_pli, szResolvedPath, dwLinkInfoFlags, hwnd, &dwOutFlags, NULL)) { ASSERT(!(dwOutFlags & RLI_OFL_UPDATED));
PathRemoveBackslash(szResolvedPath); // remove extra trailing slashes
StrCpyN(pszPath, szResolvedPath, MAX_PATH); // in case of failure, use this
// net connection might have been re-established, try again
hr = _SetPIDLPath(NULL, pszPath, TRUE); } else { // don't try searching this drive/volume again
*pfifFlags |= FIF_NODRIVE; hr = GetLastHRESULT(); } } return hr; }
DWORD TimeoutDeltaFromResolveFlags(DWORD dwResolveFlags) { DWORD dwTimeOutDelta; if (SLR_NO_UI & dwResolveFlags) { dwTimeOutDelta = HIWORD(dwResolveFlags); if (dwTimeOutDelta == 0) { dwTimeOutDelta = NOUI_SEARCH_TIMEOUT; } else if (dwTimeOutDelta == 0xFFFF) { TCHAR szTimeOut[10]; DWORD cbTimeOut = sizeof(szTimeOut); if (ERROR_SUCCESS == SHRegGetValue(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Tracking\\TimeOut"), NULL, SRRF_RT_REG_SZ, NULL, szTimeOut, &cbTimeOut)) { dwTimeOutDelta = StrToInt(szTimeOut); } else { dwTimeOutDelta = NOUI_SEARCH_TIMEOUT; } } } else { dwTimeOutDelta = UI_SEARCH_TIMEOUT; } return dwTimeOutDelta; }
// allows the name space to be able to hook the resolve process and thus
// provide custom behavior. this is used for reg items and shortcuts to the
// MyDocs folder
//
// this also resolves by re-parsing the relative parsing name as the optimal way
// to run the success case of ::Resolve()
//
// returns:
// S_OK this resolution was taken care of
// HRESULT_FROM_WIN32(ERROR_CANCELLED) UI cancel
// HRESULT_FROM_WIN32(ERROR_TIMEOUT) timeout on the parse
// other FAILED() codes (implies name space did not resolve for you)
HRESULT CShellLink::_ResolveIDList(HWND hwnd, DWORD dwResolveFlags) { ASSERT(!(_sld.dwFlags & SLDF_HAS_DARWINID)); HRESULT hr = E_FAIL; // generic failure, we did not handle this
IShellFolder* psf; LPCITEMIDLIST pidlChild; if (_pidl && SUCCEEDED(SHBindToIDListParent(_pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlChild))) { IResolveShellLink *prl = NULL; // 2 ways to get the link resolve object
// 1. ask the folder for the resolver for the item
if (FAILED(psf->GetUIObjectOf(NULL, 1, &pidlChild, IID_PPV_ARG_NULL(IResolveShellLink, &prl)))) { // 2. bind to the object directly and ask it (CreateViewObject)
IShellFolder *psfItem; if (SUCCEEDED(psf->BindToObject(pidlChild, NULL, IID_PPV_ARG(IShellFolder, &psfItem)))) { psfItem->CreateViewObject(NULL, IID_PPV_ARG(IResolveShellLink, &prl)); psfItem->Release(); } } if (prl) { hr = prl->ResolveShellLink(SAFECAST(this, IShellLink*), hwnd, dwResolveFlags); prl->Release(); } else { // perf short circuit: avoid the many net round trips that happen in
// _SetPIDLPath() in the common success case where the file is there
// we validate the target based on reparsing the relative name
//
// this is a universal way to "resolve" an object in the name space
// note, code here is very similart to SHGetRealIDL() but this version
// does not mask the error cases that we need to detect
TCHAR szName[MAX_PATH]; if (SUCCEEDED(DisplayNameOf(psf, pidlChild, SHGDN_FORPARSING | SHGDN_INFOLDER, szName, ARRAYSIZE(szName)))) { // we limit this to file system items for compat with some name spaces
// (WinCE) that support parse, but do a bad job of it
if (SHGetAttributes(psf, pidlChild, SFGAO_FILESYSTEM)) { IBindCtx *pbcTimeout; BindCtx_CreateWithTimeoutDelta(TimeoutDeltaFromResolveFlags(dwResolveFlags), &pbcTimeout);
if (dwResolveFlags & SLR_NO_UI) { hwnd = NULL; // make sure parse does not get this
} LPITEMIDLIST pidlChildNew; hr = psf->ParseDisplayName(hwnd, pbcTimeout, szName, NULL, &pidlChildNew, NULL); if (SUCCEEDED(hr)) { // no construct the new full IDList and set that
// note many pidls here, make sure we don't leak any
LPITEMIDLIST pidlParent = ILCloneParent(_pidl); if (pidlParent) { LPITEMIDLIST pidlFull = ILCombine(pidlParent, pidlChildNew); if (pidlFull) { // we set this as the new target of this link,
// with FALSE for bUpdateTrackingData to avoid the cost of that
hr = _SetPIDLPath(pidlFull, NULL, FALSE); ILFree(pidlFull); } ILFree(pidlParent); } ILFree(pidlChildNew); } if (pbcTimeout) pbcTimeout->Release(); } } } psf->Release(); } return hr; }
BOOL CShellLink::_ResolveDarwin(HWND hwnd, DWORD dwResolveFlags, HRESULT *phr) { // check to see if this is a Darwin link
BOOL bIsDrawinLink = _sld.dwFlags & SLDF_HAS_DARWINID; if (bIsDrawinLink) { HRESULT hr = S_OK; // we only envoke darwin if they are passing the correct SLR_INVOKE_MSI
// flag. This prevents poor apps from going and calling resolve and
// faulting in a bunch of darwin apps.
if ((dwResolveFlags & SLR_INVOKE_MSI) && IsDarwinEnabled()) { LPEXP_DARWIN_LINK pdl = (LPEXP_DARWIN_LINK)SHFindDataBlock(_pExtraData, EXP_DARWIN_ID_SIG); if (pdl) { TCHAR szDarwinCommand[MAX_PATH];
hr = ParseDarwinID(pdl->szwDarwinID, szDarwinCommand, SIZECHARS(szDarwinCommand)); if (FAILED(hr) || HRESULT_CODE(hr) == ERROR_SUCCESS_REBOOT_REQUIRED || HRESULT_CODE(hr) == ERROR_SUCCESS_REBOOT_INITIATED) { switch (HRESULT_CODE(hr)) { case ERROR_INSTALL_USEREXIT: // User pressed cancel. They don't need UI.
case ERROR_SUCCESS_REBOOT_INITIATED: // Machine is going to reboot
case ERROR_SUCCESS_REBOOT_REQUIRED: // dont run the darwin app in all of the above cases,
// ERROR_CANCELLED suppresses further error UI
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); break;
default: if (!(dwResolveFlags & SLR_NO_UI)) { TCHAR szTemp[MAX_PATH]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, HRESULT_CODE(hr), 0, szTemp, ARRAYSIZE(szTemp), NULL);
ShellMessageBox(HINST_THISDLL, hwnd, szTemp, MAKEINTRESOURCE(IDS_LINKERROR), MB_OK | MB_ICONSTOP, NULL, NULL); hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); } break; } } else { // We want to fire an event for the product code, not the path. Do this here since we've got the product code.
if (_pcbDarwin) { _pcbDarwin->SetProductCodeFromDarwinID(pdl->szwDarwinID); }
PathUnquoteSpaces(szDarwinCommand); hr = _SetPIDLPath(NULL, szDarwinCommand, FALSE); } } } *phr = hr; } return bIsDrawinLink; }
// if the link has encoded env vars we will set them now, possibly updating _pidl
void CShellLink::_SetIDListFromEnvVars() { TCHAR szPath[MAX_PATH];
// check to see whether this link has expandable environment strings
if (_GetExpandedPath(szPath, ARRAYSIZE(szPath))) { if (FAILED(_SetPIDLPath(NULL, szPath, TRUE))) { // The target file is no longer valid so we should dump the EXP_SZ section before
// we continue. Note that we don't set bDirty here, that is only set later if
// we actually resolve this link to a new path or pidl. The result is we'll only
// save this modification if a new target is found and accepted by the user.
_sld.dwFlags &= ~SLDF_HAS_EXP_SZ;
_SetSimplePIDL(szPath); } } }
HRESULT CShellLink::_ResolveRemovable(HWND hwnd, LPCTSTR pszPath) { HANDLE hfind; WIN32_FIND_DATA fd; HRESULT hr = FindFirstRetryRemovable(hwnd, _punkSite, pszPath, &fd, &hfind); if (S_OK == hr) { FindClose(hfind); // throw that out
hr = _SetPIDLPath(NULL, pszPath, TRUE); } return hr; }
_inline BOOL FAILED_AND_NOT_STOP_ERROR(HRESULT hr) { return FAILED(hr) && (HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr) && (HRESULT_FROM_WIN32(ERROR_TIMEOUT) != hr); }
//
// implementation for IShellLink::Resolve and IShellLinkTracker::Resolve
//
// Inputs: hwnd
// - The parent window (which could be the desktop).
// dwResolveFlags
// - Flags from the SLR_FLAGS enumeration.
// dwTracker
// - Restrict CTracker::Resolve from the
// TrkMendRestrictions enumeration
//
// Outputs: S_OK resolution was successful
//
// Algorithm: Look for the link target and update the link path and IDList.
// Check IPersistFile::IsDirty after calling this to see if the
// link info has changed as a result.
//
HRESULT CShellLink::_Resolve(HWND hwnd, DWORD dwResolveFlags, DWORD dwTracker) { if (S_OK == _ResolveLogo3Link(hwnd, dwResolveFlags)) { // the link is being updated or the user canceled
// either case we bail
return HRESULT_FROM_WIN32(ERROR_CANCELLED); }
HRESULT hr = S_OK;
if (!_ResolveDarwin(hwnd, dwResolveFlags, &hr)) { _SetIDListFromEnvVars(); // possibly sets _pidl via env vars
// normal link resolve sequence starts here
hr = _ResolveIDList(hwnd, dwResolveFlags);
if (FAILED_AND_NOT_STOP_ERROR(hr)) { TCHAR szPath[MAX_PATH];
if (_pidl == NULL) { // APP COMPAT! Inso Quick View Plus demands S_OK on empty .lnk
hr = S_OK; } else if (SHGetPathFromIDList(_pidl, szPath) && !PathIsRoot(szPath)) { DWORD fifFlags = 0;
// file system specific link tracking kicks in now
// see if it is where it was before...
// see if it there is a UNC or net path alias, if so try that
if (!(dwResolveFlags & SLR_NOLINKINFO) && _pli) { hr = _ResolveLinkInfo(hwnd, dwResolveFlags, szPath, &fifFlags); } else { hr = E_FAIL; }
if (FAILED_AND_NOT_CANCELED(hr)) { // use the relative path info if that is available
TCHAR szNew[MAX_PATH]; if (_GetRelativePath(szNew)) { if (StrCmpI(szNew, szPath)) { StringCchCopy(szPath, ARRAYSIZE(szPath), szNew); // use this in case of failure
hr = _SetPIDLPath(NULL, szPath, TRUE); } } }
if (FAILED_AND_NOT_CANCELED(hr) && !(dwResolveFlags & SLR_NO_UI) && PathRetryRemovable(hr, szPath)) { // do prompt for removable media if approprate
hr = _ResolveRemovable(hwnd, szPath); fifFlags &= ~FIF_NODRIVE; // now it is back
}
if (FAILED_AND_NOT_CANCELED(hr)) { WIN32_FIND_DATA fd; _GetFindData(&fd); // fd input to search
// standard places failed, now do the search/track stuff
CLinkResolver *prs = new CLinkResolver(_ptracker, &fd, dwResolveFlags, dwTracker, fifFlags); if (prs) { int id = prs->Resolve(hwnd, szPath, _pszCurFile); if (IDOK == id) { // get fully qualified result
prs->GetResult(szPath, ARRAYSIZE(szPath)); hr = _SetPIDLPath(NULL, szPath, TRUE); ASSERT(SUCCEEDED(hr) ? _bDirty : TRUE) // must be dirty on success
} else { ASSERT(!_bDirty); // should not be dirty now
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); } prs->Release(); } else { hr = E_OUTOFMEMORY; } } } else { // non file system target, validate it. this is another way to "resolve" name space
// objects. the other method is inside of _ResolveIDList() where we do the
// name -> pidl round trip via parse calls. that version is restricted to
// file system parts of the name space to avoid compat issues so we end up
// here for all other name spaces
ULONG dwAttrib = SFGAO_VALIDATE; // to check for existance
hr = SHGetNameAndFlags(_pidl, SHGDN_NORMAL, szPath, ARRAYSIZE(szPath), &dwAttrib); if (FAILED(hr)) { if (!(dwResolveFlags & SLR_NO_UI)) { ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_CANTFINDORIGINAL), NULL, MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND, szPath); hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); } } } } }
// if the link is dirty update it (if it was loaded from a file)
if (SUCCEEDED(hr) && _bDirty && (dwResolveFlags & SLR_UPDATE)) Save((LPCOLESTR)NULL, TRUE);
ASSERT(SUCCEEDED(hr) ? S_OK == hr : TRUE); // make sure no S_FALSE values get through
return hr; }
// This will just add a section to the end of the extra data -- it does
// not check to see if the section already exists, etc.
void CShellLink::_AddExtraDataSection(DATABLOCK_HEADER *peh) { if (SHAddDataBlock(&_pExtraData, peh)) { _bDirty = TRUE; } }
// This will remove the extra data section with the given signature.
void CShellLink::_RemoveExtraDataSection(DWORD dwSig) { if (SHRemoveDataBlock(&_pExtraData, dwSig)) { _bDirty = TRUE; } }
// Darwin and Logo3 blessings share the same structure
HRESULT CShellLink::BlessLink(LPCTSTR *ppszPath, DWORD dwSignature) { EXP_DARWIN_LINK expLink; TCHAR szBlessID[MAX_PATH]; int cchBlessData; TCHAR *pch;
// Copy the blessing data and advance *ppszPath to the end of the data.
for (pch = szBlessID, cchBlessData = 0; **ppszPath != ':' && **ppszPath != '\0' && cchBlessData < MAX_PATH; pch++, (*ppszPath)++, cchBlessData++) { *pch = **ppszPath; }
// Terminate the blessing data
*pch = 0; // Set the magic flag
if (dwSignature == EXP_DARWIN_ID_SIG) { _sld.dwFlags |= SLDF_HAS_DARWINID; } else if (dwSignature == EXP_LOGO3_ID_SIG) { _sld.dwFlags |= SLDF_HAS_LOGO3ID; } else { TraceMsg(TF_WARNING, "BlessLink was passed a bad data block signature."); return E_INVALIDARG; }
// locate the old block, if it's there
LPEXP_DARWIN_LINK lpNew = (LPEXP_DARWIN_LINK)SHFindDataBlock(_pExtraData, dwSignature); // if not, use our stack var
if (!lpNew) { lpNew = &expLink; expLink.dbh.cbSize = 0; expLink.dbh.dwSignature = dwSignature; }
SHTCharToAnsi(szBlessID, lpNew->szDarwinID, ARRAYSIZE(lpNew->szDarwinID)); SHTCharToUnicode(szBlessID, lpNew->szwDarwinID, ARRAYSIZE(lpNew->szwDarwinID));
// See if this is a new entry that we need to add
if (lpNew->dbh.cbSize == 0) { lpNew->dbh.cbSize = sizeof(*lpNew); _AddExtraDataSection((DATABLOCK_HEADER *)lpNew); }
return S_OK; }
// in/out:
// ppszPathIn
HRESULT CShellLink::_CheckForLinkBlessing(LPCTSTR *ppszPathIn) { HRESULT hr = S_FALSE; // default to no-error, no blessing
while (SUCCEEDED(hr) && (*ppszPathIn)[0] == ':' && (*ppszPathIn)[1] == ':') { // identify type of link blessing and perform
if (StrCmpNI(*ppszPathIn, DARWINGUID_TAG, ARRAYSIZE(DARWINGUID_TAG) - 1) == 0) { *ppszPathIn = *ppszPathIn + ARRAYSIZE(DARWINGUID_TAG) - 1; hr = BlessLink(ppszPathIn, EXP_DARWIN_ID_SIG); } else if (StrCmpNI(*ppszPathIn, LOGO3GUID_TAG, ARRAYSIZE(LOGO3GUID_TAG) - 1) == 0) { *ppszPathIn = *ppszPathIn + ARRAYSIZE(LOGO3GUID_TAG) - 1; HRESULT hrBless = BlessLink(ppszPathIn, EXP_LOGO3_ID_SIG); // if the blessing failed, report the error, otherwise keep the
// default hr == S_FALSE or the result of the Darwin blessing.
if (FAILED(hrBless)) hr = hrBless; } else { break; } } return hr; }
// TODO: Remove OLD_DARWIN stuff once we have transitioned Darwin to
// the new link blessing syntax.
#define OLD_DARWIN
int CShellLink::_IsOldDarwin(LPCTSTR pszPath) { #ifdef OLD_DARWIN
int iLength = lstrlen(pszPath); if ((pszPath[0] == TEXT('[')) && (pszPath[iLength - 1] == TEXT(']'))) { return iLength; } #endif
return 0; }
// we have a path that is enclosed in []'s,
// so this must be a Darwin link.
HRESULT CShellLink::_SetPathOldDarwin(LPCTSTR pszPath) { TCHAR szDarwinID[MAX_PATH];
// strip off the []'s
StringCchCopy(szDarwinID, ARRAYSIZE(szDarwinID), &pszPath[1]); szDarwinID[lstrlen(pszPath) - 1] = 0;
_sld.dwFlags |= SLDF_HAS_DARWINID;
EXP_DARWIN_LINK expLink; LPEXP_DARWIN_LINK pedl = (LPEXP_DARWIN_LINK)SHFindDataBlock(_pExtraData, EXP_DARWIN_ID_SIG); if (!pedl) { pedl = &expLink; expLink.dbh.cbSize = 0; expLink.dbh.dwSignature = EXP_DARWIN_ID_SIG; }
SHTCharToAnsi(szDarwinID, pedl->szDarwinID, ARRAYSIZE(pedl->szDarwinID)); SHTCharToUnicode(szDarwinID, pedl->szwDarwinID, ARRAYSIZE(pedl->szwDarwinID));
// See if this is a new entry that we need to add
if (pedl->dbh.cbSize == 0) { pedl->dbh.cbSize = sizeof(*pedl); _AddExtraDataSection((DATABLOCK_HEADER *)pedl); }
// For darwin links, we ignore the path and pidl for now. We would
// normally call _SetPIDLPath and SetIDList but we skip these
// steps for darwin links because all _SetPIDLPath does is set the pidl
// and all SetIDList does is set fd (the WIN32_FIND_DATA)
// for the target, and we dont have a target since we are a darwin link.
return S_OK; }
// IShellLink::SetPath()
STDMETHODIMP CShellLink::SetPath(LPCWSTR pszPathW) { HRESULT hr; TCHAR szPath[MAX_PATH]; LPCTSTR pszPath;
// NOTE: all the other Set* functions allow NULL pointer to be passed in, but this
// one does not because it would AV.
if (!pszPathW) { return E_INVALIDARG; } else if (_sld.dwFlags & SLDF_HAS_DARWINID) { return S_FALSE; // a darwin link already, then we dont allow the path to change
}
SHUnicodeToTChar(pszPathW, szPath, ARRAYSIZE(szPath));
pszPath = szPath;
int iLength = _IsOldDarwin(pszPath); if (iLength) { hr = _SetPathOldDarwin(pszPath); } else { // Check for ::<guid>:<data>: prefix, which signals us to bless the
// the lnk with extra data. NOTE: we pass the &pszPath here so that this fn can
// advance the string pointer past the ::<guid>:<data>: sections and point to
// the path, if there is one.
hr = _CheckForLinkBlessing(&pszPath); if (S_OK != hr) { // Check to see if the target has any expandable environment strings
// in it. If so, set the appropriate information in the CShellLink
// data.
TCHAR szExpPath[MAX_PATH]; SHExpandEnvironmentStrings(pszPath, szExpPath, ARRAYSIZE(szExpPath));
if (lstrcmp(szExpPath, pszPath)) { _sld.dwFlags |= SLDF_HAS_EXP_SZ; // link has expandable strings
EXP_SZ_LINK expLink; LPEXP_SZ_LINK pel = (LPEXP_SZ_LINK)SHFindDataBlock(_pExtraData, EXP_SZ_LINK_SIG); if (!pel) { pel = &expLink; expLink.cbSize = 0; expLink.dwSignature = EXP_SZ_LINK_SIG; }
// store both A and W version (for no good reason!)
SHTCharToAnsi(pszPath, pel->szTarget, ARRAYSIZE(pel->szTarget)); SHTCharToUnicode(pszPath, pel->swzTarget, ARRAYSIZE(pel->swzTarget));
// See if this is a new entry that we need to add
if (pel->cbSize == 0) { pel->cbSize = sizeof(*pel); _AddExtraDataSection((DATABLOCK_HEADER *)pel); } hr = _SetPIDLPath(NULL, szExpPath, TRUE); } else { _sld.dwFlags &= ~SLDF_HAS_EXP_SZ; _RemoveExtraDataSection(EXP_SZ_LINK_SIG);
hr = _SetPIDLPath(NULL, pszPath, TRUE); }
if (FAILED(hr)) { PathResolve(szExpPath, NULL, PRF_TRYPROGRAMEXTENSIONS); hr = _SetSimplePIDL(szExpPath); } } } return hr; }
STDMETHODIMP CShellLink::GetClassID(CLSID *pClassID) { *pClassID = CLSID_ShellLink; return S_OK; }
STDMETHODIMP CShellLink::IsDirty() { return _bDirty ? S_OK : S_FALSE; }
HRESULT LinkInfo_LoadFromStream(IStream *pstm, PLINKINFO *ppli, DWORD cbMax) { DWORD dwSize; ULONG cbBytesRead;
if (*ppli) { LocalFree((HLOCAL)*ppli); *ppli = NULL; }
HRESULT hr = pstm->Read(&dwSize, sizeof(dwSize), &cbBytesRead); // size of data
if (SUCCEEDED(hr) && (cbBytesRead == sizeof(dwSize))) { if (dwSize <= cbMax) { if (dwSize >= sizeof(dwSize)) // must be at least this big
{ /* Yes. Read remainder of LinkInfo into local memory. */ PLINKINFO pli = (PLINKINFO)LocalAlloc(LPTR, dwSize); if (pli) { *(DWORD *)pli = dwSize; // Copy size
dwSize -= sizeof(dwSize); // Read remainder of LinkInfo
hr = pstm->Read(((DWORD *)pli) + 1, dwSize, &cbBytesRead); // Note that if the linkinfo is invalid, we still return S_OK
// because linkinfo is not essential to the shortcut
if (SUCCEEDED(hr) && (cbBytesRead == dwSize) && IsValidLinkInfo(pli)) *ppli = pli; // LinkInfo read successfully
else LocalFree((HLOCAL)pli); } } } else { // This will happen if the .lnk is corrupted and the size in the stream
// is larger than the physical file on disk.
hr = E_FAIL; } } return hr; }
// Decodes the CSIDL_ relative target pidl
void CShellLink::_DecodeSpecialFolder() { LPEXP_SPECIAL_FOLDER pData = (LPEXP_SPECIAL_FOLDER)SHFindDataBlock(_pExtraData, EXP_SPECIAL_FOLDER_SIG); if (pData) { LPITEMIDLIST pidlFolder = SHCloneSpecialIDList(NULL, pData->idSpecialFolder, FALSE); if (pidlFolder) { ASSERT(IS_VALID_PIDL(_pidl));
LPITEMIDLIST pidlTarget = _ILSkip(_pidl, pData->cbOffset); LPITEMIDLIST pidlSanityCheck = _pidl;
while (!ILIsEmpty(pidlSanityCheck) && (pidlSanityCheck < pidlTarget)) { // We go one step at a time until pidlSanityCheck == pidlTarget. If we reach the end
// of pidlSanityCheck, or if we go past pidlTarget, before this condition is met then
// we have an invalid pData->cbOffset.
pidlSanityCheck = _ILNext(pidlSanityCheck); }
if (pidlSanityCheck == pidlTarget) { LPITEMIDLIST pidlNew = ILCombine(pidlFolder, pidlTarget); if (pidlNew) { _SetPIDLPath(pidlNew, NULL, FALSE); ILFree(pidlNew); } } ILFree(pidlFolder); }
// in case above stuff fails for some reason
_RemoveExtraDataSection(EXP_SPECIAL_FOLDER_SIG); } }
HRESULT CShellLink::_UpdateIconFromExpIconSz() { HRESULT hr = S_FALSE; // only try once per link instance
if (!_bExpandedIcon) { TCHAR szExpIconPath[MAX_PATH];
if (_sld.dwFlags & SLDF_HAS_EXP_ICON_SZ) { LPEXP_SZ_LINK pszl = (LPEXP_SZ_LINK)SHFindDataBlock(_pExtraData, EXP_SZ_ICON_SIG); if (pszl) { if (SHExpandEnvironmentStringsW(pszl->swzTarget, szExpIconPath, ARRAYSIZE(szExpIconPath)) && PathFileExists(szExpIconPath)) { hr = S_OK; } } else { ASSERTMSG(FALSE, "CShellLink::_UpdateIconAtLoad - lnk has SLDF_HAS_EXP_ICON_SZ but no actual datablock!!"); hr = E_FAIL; } }
if (hr == S_OK) { // update _pszIconLocation if its different from the expanded string
if (lstrcmpi(_pszIconLocation, szExpIconPath) != 0) { _SetField(&_pszIconLocation, szExpIconPath); _bDirty = TRUE; } }
_bExpandedIcon = TRUE; }
return hr; }
STDMETHODIMP CShellLink::Load(IStream *pstm) { ULONG cbBytes; DWORD cbSize;
TraceMsg(TF_DEBUGLINKCODE, "Loading link from stream.");
_ResetPersistData(); // clear out our state
HRESULT hr = pstm->Read(&cbSize, sizeof(cbSize), &cbBytes); if (SUCCEEDED(hr)) { if (cbBytes == sizeof(cbSize)) { if (cbSize == sizeof(_sld)) { hr = pstm->Read((LPBYTE)&_sld + sizeof(cbSize), sizeof(_sld) - sizeof(cbSize), &cbBytes); if (SUCCEEDED(hr) && cbBytes == (sizeof(_sld) - sizeof(cbSize)) && IsEqualGUID(_sld.clsid, CLSID_ShellLink)) { _sld.cbSize = sizeof(_sld);
switch (_sld.iShowCmd) { case SW_SHOWNORMAL: case SW_SHOWMINNOACTIVE: case SW_SHOWMAXIMIZED: break;
default: DebugMsg(DM_TRACE, TEXT("Shortcut Load, mapping bogus ShowCmd: %d"), _sld.iShowCmd); _sld.iShowCmd = SW_SHOWNORMAL; break; }
// save so we can generate notify on save
_wOldHotkey = _sld.wHotkey;
// read all of the members
if (_sld.dwFlags & SLDF_HAS_ID_LIST) { // ILLoadFromStream() verifies the integrity of the pidl
// so that even if the file has been corrupted we dont fault here
hr = ILLoadFromStream(pstm, &_pidl); if (FAILED(hr)) { // In theory this will only happen due to file corruption, but I've seen this too
// often not to suspect that we might be doing something wrong.
// turn off the flag, which we know is on to start with
_sld.dwFlags &= ~SLDF_HAS_ID_LIST; Pidl_Set(&_pidl, NULL); _bDirty = TRUE;
// continue as though there was no SLDF_HAS_ID_LIST flag to start with
// REVIEW: should we only continue if certain other sections are also included
// in the link? What will happen if SLDF_HAS_ID_LIST was the only data set for
// this link file? We would get a null link.
} }
if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_LINK_INFO)) { DWORD cbMaxRead; // We need to worry about link files that are corrupt. So read the link
// size so we don't keep reading for ever in case the stream has an invalid
// size in it.
// We need to check if it is a valid pidl because hackers will
// try to create invalid pidls to crash the system or run buffer
// over run attacks. -BryanSt
STATSTG stat; if (SUCCEEDED(pstm->Stat(&stat, STATFLAG_NONAME))) cbMaxRead = stat.cbSize.LowPart; else cbMaxRead = 0xFFFFFFFF;
hr = LinkInfo_LoadFromStream(pstm, &_pli, cbMaxRead); if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_FORCE_NO_LINKINFO)) { _FreeLinkInfo(); // labotimizing link
} }
if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_NAME)) { TraceMsg(TF_DEBUGLINKCODE, " CShellLink: Loading Name..."); hr = Str_SetFromStream(pstm, &_pszName, _sld.dwFlags & SLDF_UNICODE); }
if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_RELPATH)) { hr = Str_SetFromStream(pstm, &_pszRelPath, _sld.dwFlags & SLDF_UNICODE); if (!_pidl && SUCCEEDED(hr)) { TCHAR szTmp[MAX_PATH]; if (_GetRelativePath(szTmp)) _SetPIDLPath(NULL, szTmp, TRUE); } }
if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_WORKINGDIR)) { TraceMsg(TF_DEBUGLINKCODE, " CShellLink: Loading Working Dir..."); hr = Str_SetFromStream(pstm, &_pszWorkingDir, _sld.dwFlags & SLDF_UNICODE); }
if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_ARGS)) { TraceMsg(TF_DEBUGLINKCODE, " CShellLink: Loading Arguments..."); hr = Str_SetFromStream(pstm, &_pszArgs, _sld.dwFlags & SLDF_UNICODE); }
if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_ICONLOCATION)) { TraceMsg(TF_DEBUGLINKCODE, " CShellLink: Loading Icon Location..."); hr = Str_SetFromStream(pstm, &_pszIconLocation, _sld.dwFlags & SLDF_UNICODE); }
if (SUCCEEDED(hr)) { TraceMsg(TF_DEBUGLINKCODE, " CShellLink: Loading Data Block..."); hr = SHReadDataBlockList(pstm, &_pExtraData); }
// reset the darwin info on load
if (_sld.dwFlags & SLDF_HAS_DARWINID) { // since darwin links rely so heavily on the icon, do this now
_UpdateIconFromExpIconSz();
// we should never have a darwin link that is missing
// the icon path
if (_pszIconLocation) { // we always put back the icon path as the pidl at
// load time since darwin could change the path or
// to the app (eg: new version of the app)
TCHAR szPath[MAX_PATH]; // expand any env. strings in the icon path before
// creating the pidl.
SHExpandEnvironmentStrings(_pszIconLocation, szPath, ARRAYSIZE(szPath)); _SetPIDLPath(NULL, szPath, FALSE); } } else { // The Darwin stuff above creates a new pidl, which
// would cause this stuff to blow up. We should never
// get both at once, but let's be extra robust...
//
// Since we store the offset into the pidl here, and
// the pidl can change for various reasons, we can
// only do this once at load time. Do it here.
//
if (_pidl) { _DecodeSpecialFolder(); } }
if (SUCCEEDED(hr) && _ptracker) { // load the tracker from extra data
EXP_TRACKER *pData = (LPEXP_TRACKER)SHFindDataBlock(_pExtraData, EXP_TRACKER_SIG); if (pData) { hr = _ptracker->Load(pData->abTracker, pData->cbSize - sizeof(EXP_TRACKER)); if (FAILED(hr)) { // Failure of the Tracker isn't just cause to make
// the shortcut unusable. So just re-init it and move on.
_ptracker->InitNew(); hr = S_OK; } } }
if (SUCCEEDED(hr)) _bDirty = FALSE; } else { DebugMsg(DM_TRACE, TEXT("failed to read link struct")); hr = E_FAIL; // invalid file size
} } else { DebugMsg(DM_TRACE, TEXT("invalid length field in link:%d"), cbBytes); hr = E_FAIL; // invalid file size
} } else if (cbBytes == 0) { _sld.cbSize = 0; // zero length file is ok
} else { hr = E_FAIL; // invalid file size
} } return hr; }
// set the relative path
// in:
// pszRelSource fully qualified path to a file (must be file, not directory)
// to be used to find a relative path with the link target.
//
// returns:
// S_OK relative path is set
// S_FALSE pszPathRel is not relative to the destination or the
// destionation is not a file (could be link to a pidl only)
// notes:
// set the dirty bit if this is a new relative path
//
HRESULT CShellLink::_SetRelativePath(LPCTSTR pszRelSource) { TCHAR szPath[MAX_PATH], szDest[MAX_PATH];
ASSERT(!PathIsRelative(pszRelSource));
if (_pidl == NULL || !SHGetPathFromIDList(_pidl, szDest)) { DebugMsg(DM_TRACE, TEXT("SetRelative called on non path link")); return S_FALSE; }
// assume pszRelSource is a file, not a directory
if (PathRelativePathTo(szPath, pszRelSource, 0, szDest, _sld.dwFileAttributes)) { pszRelSource = szPath; } else { DebugMsg(DM_TRACE, TEXT("paths are not relative")); pszRelSource = NULL; // clear the stored relative path below
}
_SetField(&_pszRelPath, pszRelSource);
return S_OK; }
BOOL CShellLink::_EncodeSpecialFolder() { BOOL bRet = FALSE;
if (_pidl) { // make sure we don't already have a EXP_SPECIAL_FOLDER_SIG data block, otherwise we would
// end up with two of these and the first one would win on read.
// If you hit this ASSERT in a debugger, contact ToddB with a remote. We need to figure out
// why we are corrupting our shortcuts.
ASSERT(NULL == SHFindDataBlock(_pExtraData, EXP_SPECIAL_FOLDER_SIG));
EXP_SPECIAL_FOLDER exp; exp.idSpecialFolder = GetSpecialFolderParentIDAndOffset(_pidl, &exp.cbOffset); if (exp.idSpecialFolder) { exp.cbSize = sizeof(exp); exp.dwSignature = EXP_SPECIAL_FOLDER_SIG;
_AddExtraDataSection((DATABLOCK_HEADER *)&exp); bRet = TRUE; } }
return bRet; }
HRESULT LinkInfo_SaveToStream(IStream *pstm, PCLINKINFO pcli) { ULONG cbBytes; DWORD dwSize = *(DWORD *)pcli; // Get LinkInfo size
HRESULT hr = pstm->Write(pcli, dwSize, &cbBytes); if (SUCCEEDED(hr) && (cbBytes != dwSize)) hr = E_FAIL; return hr; }
//
// Replaces the tracker extra data with current tracker state
//
HRESULT CShellLink::_UpdateTracker() { ULONG ulSize = _ptracker->GetSize();
if (!_ptracker->IsLoaded()) { _RemoveExtraDataSection(EXP_TRACKER_SIG); return S_OK; }
if (!_ptracker->IsDirty()) { return S_OK; }
HRESULT hr = E_FAIL; // Make sure the Tracker size is a multiple of DWORDs.
// If we hit this assert then we would have mis-aligned stuff stored in the extra data.
//
if (EVAL(0 == (ulSize & 3))) { EXP_TRACKER *pExpTracker = (EXP_TRACKER *)LocalAlloc(LPTR, ulSize + sizeof(DATABLOCK_HEADER)); if (pExpTracker) { _RemoveExtraDataSection(EXP_TRACKER_SIG); pExpTracker->cbSize = ulSize + sizeof(DATABLOCK_HEADER); pExpTracker->dwSignature = EXP_TRACKER_SIG; _ptracker->Save(pExpTracker->abTracker, ulSize); _AddExtraDataSection((DATABLOCK_HEADER *)&pExpTracker->cbSize); DebugMsg(DM_TRACE, TEXT("_UpdateTracker: EXP_TRACKER at %08X."), &pExpTracker->cbSize);
LocalFree(pExpTracker); hr = S_OK; } }
return hr; }
STDMETHODIMP CShellLink::Save(IStream *pstm, BOOL fClearDirty) { ULONG cbBytes; BOOL fEncode;
_sld.cbSize = sizeof(_sld); _sld.clsid = CLSID_ShellLink; // _sld.dwFlags = 0;
// We do the following & instead of zeroing because the SLDF_HAS_EXP_SZ and
// SLDF_RUN_IN_SEPARATE and SLDF_RUNAS_USER and SLDF_HAS_DARWINID are passed to us and are valid,
// the others can be reconstructed below, but these three can not, so we need to
// preserve them!
_sld.dwFlags &= (SLDF_HAS_EXP_SZ | SLDF_HAS_EXP_ICON_SZ | SLDF_RUN_IN_SEPARATE | SLDF_HAS_DARWINID | SLDF_HAS_LOGO3ID | SLDF_RUNAS_USER | SLDF_RUN_WITH_SHIMLAYER);
if (_pszRelSource) { _SetRelativePath(_pszRelSource); }
_sld.dwFlags |= SLDF_UNICODE;
fEncode = FALSE; if (_pidl) { _sld.dwFlags |= SLDF_HAS_ID_LIST;
// we dont want to have special folder tracking for darwin links
if (!(_sld.dwFlags & SLDF_HAS_DARWINID)) fEncode = _EncodeSpecialFolder(); }
if (_pli) _sld.dwFlags |= SLDF_HAS_LINK_INFO;
if (_pszName && _pszName[0]) _sld.dwFlags |= SLDF_HAS_NAME; if (_pszRelPath && _pszRelPath[0]) _sld.dwFlags |= SLDF_HAS_RELPATH; if (_pszWorkingDir && _pszWorkingDir[0]) _sld.dwFlags |= SLDF_HAS_WORKINGDIR; if (_pszArgs && _pszArgs[0]) _sld.dwFlags |= SLDF_HAS_ARGS; if (_pszIconLocation && _pszIconLocation[0]) _sld.dwFlags |= SLDF_HAS_ICONLOCATION;
HRESULT hr = pstm->Write(&_sld, sizeof(_sld), &cbBytes);
if (SUCCEEDED(hr) && (cbBytes == sizeof(_sld))) { if (_pidl) hr = ILSaveToStream(pstm, _pidl);
if (SUCCEEDED(hr) && _pli) hr = LinkInfo_SaveToStream(pstm, _pli);
if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_NAME)) hr = Stream_WriteString(pstm, _pszName, _sld.dwFlags & SLDF_UNICODE); if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_RELPATH)) hr = Stream_WriteString(pstm, _pszRelPath, _sld.dwFlags & SLDF_UNICODE); if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_WORKINGDIR)) hr = Stream_WriteString(pstm, _pszWorkingDir, _sld.dwFlags & SLDF_UNICODE); if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_ARGS)) hr = Stream_WriteString(pstm, _pszArgs, _sld.dwFlags & SLDF_UNICODE); if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_ICONLOCATION)) hr = Stream_WriteString(pstm, _pszIconLocation, _sld.dwFlags & SLDF_UNICODE);
if (SUCCEEDED(hr) && _ptracker && _ptracker->WasLoadedAtLeastOnce()) hr = _UpdateTracker();
if (SUCCEEDED(hr)) { hr = SHWriteDataBlockList(pstm, _pExtraData); }
if (SUCCEEDED(hr) && fClearDirty) _bDirty = FALSE; } else { DebugMsg(DM_TRACE, TEXT("Failed to write link")); hr = E_FAIL; }
if (fEncode) { _RemoveExtraDataSection(EXP_SPECIAL_FOLDER_SIG); }
return hr; }
STDMETHODIMP CShellLink::GetSizeMax(ULARGE_INTEGER *pcbSize) { pcbSize->LowPart = 16 * 1024; // 16k? who knows...
pcbSize->HighPart = 0; return S_OK; }
BOOL PathIsPif(LPCTSTR pszPath) { return lstrcmpi(PathFindExtension(pszPath), TEXT(".pif")) == 0; }
HRESULT CShellLink::_LoadFromPIF(LPCTSTR pszPath) { HANDLE hPif = PifMgr_OpenProperties(pszPath, NULL, 0, 0); if (hPif == 0) return E_FAIL;
PROPPRG ProgramProps = {0};
if (!PifMgr_GetProperties(hPif, (LPSTR)MAKEINTATOM(GROUP_PRG), &ProgramProps, sizeof(ProgramProps), 0)) { return E_FAIL; }
SetDescription(ProgramProps.achTitle); SetWorkingDirectory(ProgramProps.achWorkDir); SetArguments(PathGetArgsA(ProgramProps.achCmdLine)); SetHotkey(ProgramProps.wHotKey); SetIconLocation(ProgramProps.achIconFile, ProgramProps.wIconIndex);
TCHAR szTemp[MAX_PATH]; SHAnsiToTChar(ProgramProps.achCmdLine, szTemp, ARRAYSIZE(szTemp));
PathRemoveArgs(szTemp);
// If this is a network path, we want to create a simple pidl
// instead of a full pidl to circumvent net hits
if (PathIsNetworkPath(szTemp)) { _SetSimplePIDL(szTemp); } else { _SetPIDLPath(NULL, szTemp, FALSE); }
if (ProgramProps.flPrgInit & PRGINIT_MINIMIZED) { SetShowCmd(SW_SHOWMINNOACTIVE); } else if (ProgramProps.flPrgInit & PRGINIT_MAXIMIZED) { SetShowCmd(SW_SHOWMAXIMIZED); } else { SetShowCmd(SW_SHOWNORMAL); }
PifMgr_CloseProperties(hPif, 0);
_bDirty = FALSE;
return S_OK; }
HRESULT CShellLink::_LoadFromFile(LPCTSTR pszPath) { HRESULT hr;
if (PathIsPif(pszPath)) { hr = _LoadFromPIF(pszPath); } else { IStream *pstm; hr = SHCreateStreamOnFile(pszPath, STGM_READ | STGM_SHARE_DENY_WRITE, &pstm); if (SUCCEEDED(hr)) { hr = Load(pstm); pstm->Release(); } }
if (SUCCEEDED(hr)) { TCHAR szPath[MAX_PATH];
if (_pidl && SHGetPathFromIDList(_pidl, szPath) && !lstrcmpi(szPath, pszPath)) { DebugMsg(DM_TRACE, TEXT("Link points to itself, aaahhh!")); hr = E_FAIL; } else { Str_SetPtr(&_pszCurFile, pszPath); } } else if (IsFolderShortcut(pszPath)) { // this support here is a hack to make Office file open work. that code
// depends on loading folder shortcuts using CLSID_ShellLink. this is because
// we lie about the attributes of folder shortcuts to office to make other
// stuff work.
TCHAR szPath[MAX_PATH]; PathCombine(szPath, pszPath, TEXT("target.lnk"));
IStream *pstm; hr = SHCreateStreamOnFile(szPath, STGM_READ | STGM_SHARE_DENY_WRITE, &pstm); if (SUCCEEDED(hr)) { hr = Load(pstm); pstm->Release(); } }
ASSERT(!_bDirty);
return hr; }
STDMETHODIMP CShellLink::Load(LPCOLESTR pwszFile, DWORD grfMode) { HRESULT hr = E_INVALIDARG; TraceMsg(TF_DEBUGLINKCODE, "Loading link from file %ls.", pwszFile);
if (pwszFile) { hr = _LoadFromFile(pwszFile);
// convert the succeeded code to S_OK so that THOSE DUMB apps like HitNrun
// who do hr == 0 don't fail miserably.
if (SUCCEEDED(hr)) hr = S_OK; } return hr; }
HRESULT CShellLink::_SaveAsLink(LPCTSTR pszPath) { TraceMsg(TF_DEBUGLINKCODE, "Save link to file %s.", pszPath);
IStream *pstm; HRESULT hr = SHCreateStreamOnFile(pszPath, STGM_CREATE | STGM_WRITE | STGM_SHARE_DENY_WRITE, &pstm); if (SUCCEEDED(hr)) { if (_pszRelSource == NULL) _SetRelativePath(pszPath);
hr = Save(pstm, TRUE);
if (SUCCEEDED(hr)) { hr = pstm->Commit(0); }
pstm->Release();
if (FAILED(hr)) { DeleteFile(pszPath); } }
return hr; }
BOOL RenameChangeExtension(LPTSTR pszPathSave, LPCTSTR pszExt, BOOL fMove) { TCHAR szPathSrc[MAX_PATH];
StrCpyN(szPathSrc, pszPathSave, ARRAYSIZE(szPathSrc)); PathRenameExtension(pszPathSave, pszExt);
// this may fail because the source file does not exist, but we dont care
if (fMove && lstrcmpi(szPathSrc, pszPathSave) != 0) { DWORD dwAttrib;
PathYetAnotherMakeUniqueName(pszPathSave, pszPathSave, NULL, NULL); dwAttrib = GetFileAttributes(szPathSrc); if ((dwAttrib == 0xFFFFFFFF) || (dwAttrib & FILE_ATTRIBUTE_READONLY)) { // Source file is read only, don't want to change the extension
// because we won't be able to write any changes to the file...
return FALSE; } Win32MoveFile(szPathSrc, pszPathSave, FALSE); }
return TRUE; }
// out:
// pszDir MAX_PATH path to get directory, maybe with env expanded
//
// returns:
// TRUE has a working directory, pszDir filled in.
// FALSE no working dir, if the env expands to larger than the buffer size (MAX_PATH)
// this will be returned (FALSE)
//
BOOL CShellLink::_GetWorkingDir(LPTSTR pszDir) { *pszDir = 0;
if (_pszWorkingDir && _pszWorkingDir[0]) { return (SHExpandEnvironmentStrings(_pszWorkingDir, pszDir, MAX_PATH) != 0); }
return FALSE; }
HRESULT CShellLink::_SaveAsPIF(LPCTSTR pszPath, BOOL fPath) { HANDLE hPif; PROPPRG ProgramProps; HRESULT hr; TCHAR szDir[MAX_PATH]; TCHAR achPath[MAX_PATH];
//
// get filename and convert it to a short filename
//
if (fPath) { hr = GetPath(achPath, ARRAYSIZE(achPath), NULL, 0); PathGetShortPath(achPath); ASSERT(!PathIsPif(achPath)); ASSERT(LOWORD(GetExeType(achPath)) == 0x5A4D); ASSERT(PathIsPif(pszPath)); ASSERT(hr == S_OK); } else { StrCpyN(achPath, pszPath, ARRAYSIZE(achPath)); }
DebugMsg(DM_TRACE, TEXT("_SaveAsPIF(%s,%s)"), achPath, pszPath);
#if 0
//
// we should use OPENPROPS_INHIBITPIF to prevent PIFMGR from making a
// temp .pif file in \windows\pif but it does not work now.
//
hPif = PifMgr_OpenProperties(achPath, pszPath, 0, OPENPROPS_INHIBITPIF); #else
hPif = PifMgr_OpenProperties(achPath, pszPath, 0, 0); #endif
if (hPif == 0) { return E_FAIL; }
if (!PifMgr_GetProperties(hPif,(LPSTR)MAKEINTATOM(GROUP_PRG), &ProgramProps, sizeof(ProgramProps), 0)) { DebugMsg(DM_TRACE, TEXT("_SaveToPIF: PifMgr_GetProperties *failed*")); hr = E_FAIL; goto Error1; }
// Set a title based on the link name.
if (_pszName && _pszName[0]) { SHTCharToAnsi(_pszName, ProgramProps.achTitle, sizeof(ProgramProps.achTitle)); }
// if no work dir. is given default to the dir of the app.
if (_GetWorkingDir(szDir)) {
TCHAR szTemp[PIFDEFPATHSIZE];
GetShortPathName(szDir, szTemp, ARRAYSIZE(szTemp)); SHTCharToAnsi(szTemp, ProgramProps.achWorkDir, ARRAYSIZE(ProgramProps.achWorkDir)); } else if (fPath && !PathIsUNC(achPath)) { TCHAR szTemp[PIFDEFPATHSIZE]; StrCpyN(szTemp, achPath, ARRAYSIZE(szTemp)); PathRemoveFileSpec(szTemp); SHTCharToAnsi(szTemp, ProgramProps.achWorkDir, ARRAYSIZE(ProgramProps.achWorkDir)); }
// And for those network share points we need to quote blanks...
PathQuoteSpaces(achPath);
// add the args to build the full command line
if (_pszArgs && _pszArgs[0]) { StringCchCat(achPath, ARRAYSIZE(achPath), c_szSpace); StringCchCat(achPath, ARRAYSIZE(achPath), _pszArgs); }
if (fPath) { SHTCharToAnsi(achPath, ProgramProps.achCmdLine, ARRAYSIZE(ProgramProps.achCmdLine)); }
if (_sld.iShowCmd == SW_SHOWMAXIMIZED) { ProgramProps.flPrgInit |= PRGINIT_MAXIMIZED; } if ((_sld.iShowCmd == SW_SHOWMINIMIZED) || (_sld.iShowCmd == SW_SHOWMINNOACTIVE)) { ProgramProps.flPrgInit |= PRGINIT_MINIMIZED; }
if (_sld.wHotkey) { ProgramProps.wHotKey = _sld.wHotkey; }
if (_pszIconLocation && _pszIconLocation[0]) { SHTCharToAnsi(_pszIconLocation, ProgramProps.achIconFile, ARRAYSIZE(ProgramProps.achIconFile)); ProgramProps.wIconIndex = (WORD) _sld.iIcon; }
if (!PifMgr_SetProperties(hPif, (LPSTR)MAKEINTATOM(GROUP_PRG), &ProgramProps, sizeof(ProgramProps), 0)) { DebugMsg(DM_TRACE, TEXT("_SaveToPIF: PifMgr_SetProperties *failed*")); hr = E_FAIL; } else { hr = S_OK; }
_bDirty = FALSE;
Error1: PifMgr_CloseProperties(hPif, 0); return hr; }
// This will allow global hotkeys to be available immediately instead
// of having to wait for the StartMenu to pick them up.
// Similarly this will remove global hotkeys immediately if req.
const UINT c_rgHotKeyFolders[] = { CSIDL_PROGRAMS, CSIDL_COMMON_PROGRAMS, CSIDL_STARTMENU, CSIDL_COMMON_STARTMENU, CSIDL_DESKTOPDIRECTORY, CSIDL_COMMON_DESKTOPDIRECTORY, };
void HandleGlobalHotkey(LPCTSTR pszFile, WORD wHotkeyOld, WORD wHotkeyNew) { if (PathIsEqualOrSubFolderOf(pszFile, c_rgHotKeyFolders, ARRAYSIZE(c_rgHotKeyFolders))) { // Find tray?
HWND hwndTray = FindWindow(TEXT(WNDCLASS_TRAYNOTIFY), 0); if (hwndTray) { // Yep.
if (wHotkeyOld) SendMessage(hwndTray, WMTRAY_SCUNREGISTERHOTKEY, wHotkeyOld, 0); if (wHotkeyNew) { ATOM atom = GlobalAddAtom(pszFile); if (atom) { SendMessage(hwndTray, WMTRAY_SCREGISTERHOTKEY, wHotkeyNew, (LPARAM)atom); GlobalDeleteAtom(atom); } } } } }
HRESULT CShellLink::_SaveToFile(LPTSTR pszPathSave, BOOL fRemember) { HRESULT hr = E_FAIL; BOOL fDosApp; BOOL fFile; TCHAR szPathSrc[MAX_PATH]; BOOL fWasSameFile = _pszCurFile && (lstrcmpi(pszPathSave, _pszCurFile) == 0); BOOL bFileExisted = PathFileExistsAndAttributes(pszPathSave, NULL);
// when saving darwin links we dont want to resolve the path
if (_sld.dwFlags & SLDF_HAS_DARWINID) { fRemember = FALSE; hr = _SaveAsLink(pszPathSave); goto Update; }
GetPath(szPathSrc, ARRAYSIZE(szPathSrc), NULL, 0);
fFile = !(_sld.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); fDosApp = fFile && LOWORD(GetExeType(szPathSrc)) == 0x5A4D;
// handle a link to link case. (or link to pif)
//
// NOTE: we loose all new attributes, including icon, but it's been this way since Win95.
if (fFile && (PathIsPif(szPathSrc) || PathIsLnk(szPathSrc))) { if (RenameChangeExtension(pszPathSave, PathFindExtension(szPathSrc), fWasSameFile)) { if (CopyFile(szPathSrc, pszPathSave, FALSE)) { if (PathIsPif(pszPathSave)) hr = _SaveAsPIF(pszPathSave, FALSE); else hr = S_OK; } } else { hr = E_FAIL; } } else if (fDosApp) { // if the linked to file is a DOS app, we need to write a .PIF file
if (RenameChangeExtension(pszPathSave, TEXT(".pif"), fWasSameFile)) { hr = _SaveAsPIF(pszPathSave, TRUE); } else { hr = E_FAIL; } } else { // else write a link file
if (PathIsPif(pszPathSave)) { if (!RenameChangeExtension(pszPathSave, TEXT(".lnk"), fWasSameFile)) { hr = E_FAIL; goto Update; } } hr = _SaveAsLink(pszPathSave); }
Update: if (SUCCEEDED(hr)) { // Knock out file close
SHChangeNotify(bFileExisted ? SHCNE_UPDATEITEM : SHCNE_CREATE, SHCNF_PATH, pszPathSave, NULL); SHChangeNotify(SHCNE_FREESPACE, SHCNF_PATH, pszPathSave, NULL);
if (_wOldHotkey != _sld.wHotkey) { HandleGlobalHotkey(pszPathSave, _wOldHotkey, _sld.wHotkey); }
if (fRemember) { Str_SetPtr(&_pszCurFile, pszPathSave); } }
return hr; }
STDMETHODIMP CShellLink::Save(LPCOLESTR pwszFile, BOOL fRemember) { TCHAR szSavePath[MAX_PATH];
if (pwszFile == NULL) { if (_pszCurFile == NULL) { // fail
return E_FAIL; }
StringCchCopy(szSavePath, ARRAYSIZE(szSavePath), _pszCurFile); } else { SHUnicodeToTChar(pwszFile, szSavePath, ARRAYSIZE(szSavePath)); }
return _SaveToFile(szSavePath, fRemember); }
STDMETHODIMP CShellLink::SaveCompleted(LPCOLESTR pwszFile) { return S_OK; }
STDMETHODIMP CShellLink::GetCurFile(LPOLESTR *ppszFile) { if (_pszCurFile == NULL) { *ppszFile = NULL; return S_FALSE; } return SHStrDup(_pszCurFile, ppszFile); }
STDMETHODIMP CShellLink::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID) { HRESULT hr;
ASSERT(_sld.iShowCmd == SW_SHOWNORMAL);
if (pdtobj) { STGMEDIUM medium = {0}; FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; hr = pdtobj->GetData(&fmte, &medium); if (SUCCEEDED(hr)) { TCHAR szPath[MAX_PATH]; DragQueryFile((HDROP)medium.hGlobal, 0, szPath, ARRAYSIZE(szPath)); hr = _LoadFromFile(szPath);
ReleaseStgMedium(&medium); } else { LPIDA pida = DataObj_GetHIDA(pdtobj, &medium); if (pida) { IShellFolder *psf; hr = SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, IDA_GetIDListPtr(pida, -1), &psf)); if (SUCCEEDED(hr)) { IStream *pstm; hr = psf->BindToStorage(IDA_GetIDListPtr(pida, 0), NULL, IID_PPV_ARG(IStream, &pstm)); if (SUCCEEDED(hr)) { hr = Load(pstm); pstm->Release(); } psf->Release(); }
HIDA_ReleaseStgMedium(pida, &medium); } } } else { hr = E_FAIL; }
return hr; }
STDAPI CDarwinContextMenuCB::CallBack(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam) { HRESULT hr = S_OK; LPITEMIDLIST pidl;
switch (uMsg) { case DFM_MERGECONTEXTMENU: // S_FALSE indicates no need to get verbs from extensions.
hr = S_FALSE; break;
case DFM_MERGECONTEXTMENU_TOP: { UINT uFlags = (UINT)wParam; LPQCMINFO pqcm = (LPQCMINFO)lParam;
CDefFolderMenu_MergeMenu(HINST_THISDLL, (uFlags & CMF_EXTENDEDVERBS) ? MENU_GENERIC_CONTROLPANEL_VERBS : MENU_GENERIC_OPEN_VERBS, // if extended verbs then add "Run as..."
0, pqcm);
SetMenuDefaultItem(pqcm->hmenu, 0, MF_BYPOSITION); break; }
case DFM_GETHELPTEXT: LoadStringA(HINST_THISDLL, LOWORD(wParam) + IDS_MH_FSIDM_FIRST, (LPSTR)lParam, HIWORD(wParam)); break;
case DFM_GETHELPTEXTW: LoadStringW(HINST_THISDLL, LOWORD(wParam) + IDS_MH_FSIDM_FIRST, (LPWSTR)lParam, HIWORD(wParam)); break;
// NTRAID94991-2000/03/16-MikeSh- DFM_MAPCOMMANDNAME DFM_GETVERB[A|W] not implemented
case DFM_INVOKECOMMANDEX: switch (wParam) { case FSIDM_OPENPRN: case FSIDM_RUNAS: hr = PidlFromDataObject(pdtobj, &pidl); if (SUCCEEDED(hr)) { CMINVOKECOMMANDINFOEX iciex; SHELLEXECUTEINFO sei; DFMICS* pdfmics = (DFMICS *)lParam; LPVOID pvFree;
ICI2ICIX(pdfmics->pici, &iciex, &pvFree); ICIX2SEI(&iciex, &sei); sei.fMask |= SEE_MASK_IDLIST; sei.lpIDList = pidl;
if (wParam == FSIDM_RUNAS) { // we only set the verb in the "Run As..." case since we want
// the "open" verb for darwin links to really execute the default action.
sei.lpVerb = TEXT("runas"); }
if (ShellExecuteEx(&sei)) { // Tell UEM that we ran a Darwin app
if (_szProductCode[0]) { UEMFireEvent(&UEMIID_SHELL, UEME_RUNPATH, UEMF_XEVENT, -1, (LPARAM)_szProductCode); } } ILFree(pidl); if (pvFree) { LocalFree(pvFree); } } // Never return E_NOTIMPL or defcm will try to do a default thing
if (hr == E_NOTIMPL) hr = E_FAIL; break;
default: // This is common menu items, use the default code.
hr = S_FALSE; break; } break; // DFM_INVOKECOMMANDEX
default: hr = E_NOTIMPL; break; } return hr; }
//
// CShellLink::CreateDarwinContextMenuForPidl (non-virtual)
//
// Worker function for CShellLink::CreateDarwinContextMenu that tries
// to create the context menu for the specified pidl.
HRESULT CShellLink::_CreateDarwinContextMenuForPidl(HWND hwnd, LPCITEMIDLIST pidlTarget, IContextMenu **pcmOut) { LPITEMIDLIST pidlFolder, pidlItem;
HRESULT hr = SHILClone(pidlTarget, &pidlFolder); if (SUCCEEDED(hr)) { if (ILRemoveLastID(pidlFolder) && (pidlItem = ILFindLastID(pidlTarget))) { IShellFolder *psf; hr = SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, pidlFolder, &psf)); if (SUCCEEDED(hr)) { if (!_pcbDarwin) { _pcbDarwin = new CDarwinContextMenuCB(); } if (_pcbDarwin) { HKEY ahkeys[1] = { NULL }; RegOpenKey(HKEY_CLASSES_ROOT, TEXT("MSILink"), &ahkeys[0]); hr = CDefFolderMenu_Create2Ex( pidlFolder, hwnd, 1, (LPCITEMIDLIST *)&pidlItem, psf, _pcbDarwin, ARRAYSIZE(ahkeys), ahkeys, pcmOut);
SHRegCloseKeys(ahkeys, ARRAYSIZE(ahkeys)); } else { hr = E_OUTOFMEMORY; } psf->Release(); } } else { // Darwin shortcut to the desktop? I don't think so.
hr = E_FAIL; } ILFree(pidlFolder); } return hr; }
//
// CShellLink::CreateDarwinContextMenu (non-virtual)
//
// Creates a context menu for a Darwin shortcut. This is special because
// the ostensible target is an .EXE file, but in reality it could be
// anything. (It's just an .EXE file until the shortcut gets resolved.)
// Consequently, we can't create a real context menu for the item because
// we don't know what kind of context menu to create. We just cook up
// a generic-looking one.
//
// Bonus annoyance: _pidl might be invalid, so you need to have
// a fallback plan if it's not there. We will use c_idlDrives as our
// fallback. That's a pidl guaranteed actually to exist.
//
// Note that this means you can't invoke a command on the fallback object,
// but that's okay because ShellLink will always resolve the object to
// a real file and create a new context menu before invoking.
//
HRESULT CShellLink::_CreateDarwinContextMenu(HWND hwnd, IContextMenu **pcmOut) { HRESULT hr;
*pcmOut = NULL;
if (_pidl == NULL || FAILED(hr = _CreateDarwinContextMenuForPidl(hwnd, _pidl, pcmOut))) { // The link target is busted for some reason - use the fallback pidl
hr = _CreateDarwinContextMenuForPidl(hwnd, (LPCITEMIDLIST)&c_idlDrives, pcmOut); }
return hr; }
BOOL CShellLink::_GetExpandedPath(LPTSTR psz, DWORD cch) { if (_sld.dwFlags & SLDF_HAS_EXP_SZ) { LPEXP_SZ_LINK pesl = (LPEXP_SZ_LINK)SHFindDataBlock(_pExtraData, EXP_SZ_LINK_SIG); if (pesl) { TCHAR sz[MAX_PATH]; sz[0] = 0; // prefer the UNICODE version...
if (pesl->swzTarget[0]) SHUnicodeToTChar(pesl->swzTarget, sz, SIZECHARS(sz));
if (!sz[0] && pesl->szTarget[0]) SHAnsiToTChar(pesl->szTarget, sz, SIZECHARS(sz));
if (sz[0]) { return SHExpandEnvironmentStrings(sz, psz, cch); } } else { _sld.dwFlags &= ~SLDF_HAS_EXP_SZ; } }
return FALSE; }
#define DEFAULT_TIMEOUT 7500 // 7 1/2 seconds...
DWORD g_dwNetLinkTimeout = (DWORD)-1;
DWORD _GetNetLinkTimeout() { if (g_dwNetLinkTimeout == -1) { DWORD cb = sizeof(g_dwNetLinkTimeout); if (FAILED(SKGetValue(SHELLKEY_HKCU_EXPLORER, NULL, TEXT("NetLinkTimeout"), NULL, &g_dwNetLinkTimeout, &cb))) g_dwNetLinkTimeout = DEFAULT_TIMEOUT; } return g_dwNetLinkTimeout; }
DWORD CShellLink::_VerifyPathThreadProc(void *pv) { LPTSTR psz = (LPTSTR)pv; PathStripToRoot(psz); BOOL bFoundRoot = PathFileExistsAndAttributes(psz, NULL); // does WNet stuff for us
LocalFree(psz); // this thread owns this buffer
return bFoundRoot; // retrieved via GetExitCodeThread()
}
// since net timeouts can be very long this is a manual way to timeout
// an operation explictly rather than waiting for the net layers to do their
// long timeouts
HRESULT CShellLink::_ShortNetTimeout() { HRESULT hr = S_OK; // assume good
TCHAR szPath[MAX_PATH]; if (_pidl && SHGetPathFromIDList(_pidl, szPath) && PathIsNetworkPath(szPath)) { hr = E_OUTOFMEMORY; // assume failure (2 cases below)
LPTSTR psz = StrDup(szPath); // give thread a copy of string to avoid buffer liftime issues
if (psz) { DWORD dwID; HANDLE hThread = CreateThread(NULL, 0, _VerifyPathThreadProc, psz, 0, &dwID); if (hThread) { // assume timeout...
hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); // timeout return value
if (WAIT_OBJECT_0 == WaitForSingleObject(hThread, _GetNetLinkTimeout())) { // thread finished
DWORD dw; if (GetExitCodeThread(hThread, &dw) && dw) { hr = S_OK; // bool thread result maps to S_OK
} } CloseHandle(hThread); } else { LocalFree(psz); } } } return hr; }
//
// This function returns the specified UI object from the link source.
//
// Parameters:
// hwnd -- optional hwnd for UI (for drop target)
// riid -- Specifies the interface (IID_IDropTarget, IID_IExtractIcon, IID_IContextMenu, ...)
// ppv -- Specifies the place to return the pointer.
//
// Notes:
// Don't put smart-resolving code here. Such a thing should be done
// BEFORE calling this function.
//
HRESULT CShellLink::_GetUIObject(HWND hwnd, REFIID riid, void **ppv) { *ppv = NULL; // Do this once and for all
HRESULT hr = E_FAIL;
if (_sld.dwFlags & SLDF_HAS_DARWINID) { // We commandeer a couple of IIDs if this is a Darwin link.
// Must do this before any pseudo-resolve goo because Darwin
// shortcuts don't resolve the normal way.
if (IsEqualIID(riid, IID_IContextMenu)) { // Custom Darwin context menu.
hr = _CreateDarwinContextMenu(hwnd, (IContextMenu **)ppv); } else if (!IsEqualIID(riid, IID_IDropTarget) && _pidl) { hr = SHGetUIObjectFromFullPIDL(_pidl, hwnd, riid, ppv); } } else { TCHAR szPath[MAX_PATH]; if (!_pidl && _GetExpandedPath(szPath, SIZECHARS(szPath))) { _SetSimplePIDL(szPath); }
if (_pidl) { hr = SHGetUIObjectFromFullPIDL(_pidl, hwnd, riid, ppv); } } return hr; }
STDMETHODIMP CShellLink::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { HRESULT hr;
if (_cmTarget == NULL) { hr = _GetUIObject(NULL, IID_PPV_ARG(IContextMenu, _cmTarget.GetOutputPtr())); if (FAILED(hr)) return hr;
ASSERT(_cmTarget); }
// save these if in case we need to rebuild the cm because the resolve change the
// target of the link
_indexMenuSave = indexMenu; _idCmdFirstSave = idCmdFirst; _idCmdLastSave = idCmdLast; _uFlagsSave = uFlags;
uFlags |= CMF_VERBSONLY;
if (_sld.dwFlags & SLDF_RUNAS_USER) { // "runas" for exe's is an extenede verb, so we have to ask for those as well.
uFlags |= CMF_EXTENDEDVERBS; }
hr = _cmTarget.QueryContextMenu(this, hmenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
// set default verb to "runas" if the "Run as different user" checkbox is checked
if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_RUNAS_USER)) { int i = _cmTarget.GetMenuIndexForCanonicalVerb(this, hmenu, idCmdFirst, L"runas");
if (i != -1) { // we found runas, so set it as the default
SetMenuDefaultItem(hmenu, i, MF_BYPOSITION); } else { // the checkbox was enabled and checked, which means that the "runas" verb was supposed
// to be in the context menu, but we couldnt find it.
ASSERTMSG(FALSE, "CSL::QueryContextMenu - failed to set 'runas' as default context menu item!"); } }
return hr; }
HRESULT CShellLink::_InvokeCommandAsync(LPCMINVOKECOMMANDINFO pici) { TCHAR szWorkingDir[MAX_PATH]; CHAR szVerb[32]; CHAR szWorkingDirAnsi[MAX_PATH]; WCHAR szVerbW[32];
szVerb[0] = 0;
// if needed, get the canonical name in case the IContextMenu changes as
// a result of the resolve call BUT only do this for folders (to be safe)
// as that is typically the only case where this happens
// sepcifically we resolve from a D:\ -> \\SERVER\SHARE
if (IS_INTRESOURCE(pici->lpVerb) && (_sld.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { _cmTarget.GetCommandString(this, LOWORD(pici->lpVerb), GCS_VERBA, NULL, szVerb, ARRAYSIZE(szVerb)); }
ASSERT(!_bDirty);
// we pass SLR_ENVOKE_MSI since we WANT to invoke darwin since we are
// really going to execute the link now
DWORD slrFlags = SLR_INVOKE_MSI; if (pici->fMask & CMIC_MASK_FLAG_NO_UI) { slrFlags |= SLR_NO_UI; } HRESULT hr = _Resolve(pici->hwnd, slrFlags, 0); if (hr == S_OK) { if (_bDirty) { // the context menu we have for this link is out of date - recreate it
_cmTarget.AtomicRelease();
hr = _GetUIObject(NULL, IID_PPV_ARG(IContextMenu, _cmTarget.GetOutputPtr())); if (SUCCEEDED(hr)) { HMENU hmenu = CreatePopupMenu(); if (hmenu) { hr = _cmTarget.QueryContextMenu(this, hmenu, _indexMenuSave, _idCmdFirstSave, _idCmdLastSave, _uFlagsSave | CMF_VERBSONLY); DestroyMenu(hmenu); } } Save((LPCOLESTR)NULL, TRUE); // don't care if this fails...
} else { szVerb[0] = 0; ASSERT(SUCCEEDED(hr)); }
if (SUCCEEDED(hr)) { TCHAR szArgs[MAX_PATH]; TCHAR szExpArgs[MAX_PATH]; CMINVOKECOMMANDINFOEX ici = {0}; CHAR szArgsAnsi[MAX_PATH];
// copy to local ici
memcpy(&ici, pici, min(sizeof(ici), pici->cbSize)); ici.cbSize = sizeof(ici);
if (szVerb[0]) { ici.lpVerb = szVerb; SHAnsiToUnicode(szVerb, szVerbW, ARRAYSIZE(szVerbW)); ici.lpVerbW = szVerbW; } // build the args from those passed in cated on the end of the the link args
StrCpyN(szArgs, _pszArgs ? _pszArgs : c_szNULL, ARRAYSIZE(szArgs)); if (ici.lpParameters) { int nArgLen = lstrlen(szArgs); LPCTSTR lpParameters; WCHAR szParameters[MAX_PATH];
if (ici.cbSize < CMICEXSIZE_NT4 || (ici.fMask & CMIC_MASK_UNICODE) != CMIC_MASK_UNICODE) { SHAnsiToUnicode(ici.lpParameters, szParameters, ARRAYSIZE(szParameters)); lpParameters = szParameters; } else { lpParameters = ici.lpParametersW; } StrCpyN(szArgs + nArgLen, c_szSpace, ARRAYSIZE(szArgs) - nArgLen - 1); StrCpyN(szArgs + nArgLen + 1, lpParameters, ARRAYSIZE(szArgs) - nArgLen - 2); }
// Expand environment strings in szArgs
SHExpandEnvironmentStrings(szArgs, szExpArgs, ARRAYSIZE(szExpArgs));
SHTCharToAnsi(szExpArgs, szArgsAnsi, ARRAYSIZE(szArgsAnsi)); ici.lpParameters = szArgsAnsi; ici.lpParametersW = szExpArgs; ici.fMask |= CMIC_MASK_UNICODE;
// if we have a working dir in the link over ride what is passed in
if (_GetWorkingDir(szWorkingDir)) { LPCTSTR pszDir = PathIsDirectory(szWorkingDir) ? szWorkingDir : NULL; if (pszDir) { SHTCharToAnsi(pszDir, szWorkingDirAnsi, ARRAYSIZE(szWorkingDirAnsi)); ici.lpDirectory = szWorkingDirAnsi; ici.lpDirectoryW = pszDir; } }
// set RUN IN SEPARATE VDM if needed
if (_sld.dwFlags & SLDF_RUN_IN_SEPARATE) { ici.fMask |= CMIC_MASK_FLAG_SEP_VDM; } // and of course use our hotkey
if (_sld.wHotkey) { ici.dwHotKey = _sld.wHotkey; ici.fMask |= CMIC_MASK_HOTKEY; }
// override normal runs, but let special show cmds through
if (ici.nShow == SW_SHOWNORMAL) { DebugMsg(DM_TRACE, TEXT("using shorcut show cmd")); ici.nShow = _sld.iShowCmd; }
//
// On NT we want to pass the title to the
// thing that we are about to start.
//
// CMIC_MASK_HASLINKNAME means that the lpTitle is really
// the full path to the shortcut. The console subsystem
// sees the bit and reads all his properties directly from
// the LNK file.
//
// ShellExecuteEx also uses the path to the shortcut so it knows
// what to set in the SHCNEE_SHORTCUTINVOKE notification.
//
if (!(ici.fMask & CMIC_MASK_HASLINKNAME) && !(ici.fMask & CMIC_MASK_HASTITLE)) { if (_pszCurFile) { ici.lpTitle = NULL; // Title is one or the other...
ici.lpTitleW = _pszCurFile; ici.fMask |= CMIC_MASK_HASLINKNAME | CMIC_MASK_HASTITLE; } } ASSERT((ici.nShow > SW_HIDE) && (ici.nShow <= SW_MAX));
IBindCtx *pbc; hr = _MaybeAddShim(&pbc); if (SUCCEEDED(hr)) { hr = _cmTarget.InvokeCommand(this, (LPCMINVOKECOMMANDINFO)&ici); if (pbc) { pbc->Release(); } } } } return hr; }
// Structure which encapsulates the paramters needed for InvokeCommand (so
// that we can pass both parameters though a single LPARAM in CreateThread)
typedef struct { CShellLink *psl; CMINVOKECOMMANDINFOEX ici; } ICMPARAMS;
#define ICM_BASE_SIZE (sizeof(ICMPARAMS) - sizeof(CMINVOKECOMMANDINFOEX))
// Runs as a separate thread, does the actual work of calling the "real"
// InvokeCommand
DWORD CALLBACK CShellLink::_InvokeThreadProc(void *pv) { ICMPARAMS * pParams = (ICMPARAMS *) pv; CShellLink *psl = pParams->psl; IBindCtx *pbcRelease;
HRESULT hr = TBCRegisterObjectParam(TBCDIDASYNC, SAFECAST(psl, IShellLink *), &pbcRelease); if (SUCCEEDED(hr)) { // since we are ASYNC, this hwnd may now go bad. we just assume it has.
// we will make sure it doesnt by giving a chance for it to go bad
if (IsWindow(pParams->ici.hwnd)) { Sleep(100); } if (!IsWindow(pParams->ici.hwnd)) pParams->ici.hwnd = NULL; hr = psl->_InvokeCommandAsync((LPCMINVOKECOMMANDINFO)&pParams->ici); pbcRelease->Release(); }
psl->Release();
LocalFree(pParams); return (DWORD) hr; }
// CShellLink::InvokeCommand
//
// Function that spins a thread to do the real work, which has been moved into
// CShellLink::InvokeCommandASync.
HRESULT CShellLink::InvokeCommand(LPCMINVOKECOMMANDINFO piciIn) { HRESULT hr = S_OK; DWORD cchVerb, cchParameters, cchDirectory; DWORD cchVerbW, cchParametersW, cchDirectoryW; LPCMINVOKECOMMANDINFOEX pici = (LPCMINVOKECOMMANDINFOEX) piciIn; const BOOL fUnicode = pici->cbSize >= CMICEXSIZE_NT4 && (pici->fMask & CMIC_MASK_UNICODE) == CMIC_MASK_UNICODE;
if (_cmTarget == NULL) return E_FAIL;
if (0 == (piciIn->fMask & CMIC_MASK_ASYNCOK)) { // Caller didn't indicate that Async startup was OK, so we call
// InvokeCommandAync SYNCHRONOUSLY
return _InvokeCommandAsync(piciIn); }
// Calc how much space we will need to duplicate the INVOKECOMMANDINFO
DWORD cbBaseSize = (DWORD)(ICM_BASE_SIZE + max(piciIn->cbSize, sizeof(CMINVOKECOMMANDINFOEX)));
// One byte slack in case of Unicode roundup for pPosW, below
DWORD cbSize = cbBaseSize + 1;
if (HIWORD(pici->lpVerb)) { cbSize += (cchVerb = pici->lpVerb ? (lstrlenA(pici->lpVerb) + 1) : 0) * sizeof(CHAR); } cbSize += (cchParameters = pici->lpParameters ? (lstrlenA(pici->lpParameters) + 1) : 0) * sizeof(CHAR); cbSize += (cchDirectory = pici->lpDirectory ? (lstrlenA(pici->lpDirectory) + 1) : 0) * sizeof(CHAR);
if (HIWORD(pici->lpVerbW)) { cbSize += (cchVerbW = pici->lpVerbW ? (lstrlenW(pici->lpVerbW) + 1) : 0) * sizeof(WCHAR); } cbSize += (cchParametersW= pici->lpParametersW? (lstrlenW(pici->lpParametersW) + 1) : 0) * sizeof(WCHAR); cbSize += (cchDirectoryW = pici->lpDirectoryW ? (lstrlenW(pici->lpDirectoryW) + 1) : 0) * sizeof(WCHAR);
ICMPARAMS *pParams = (ICMPARAMS *) LocalAlloc(LPTR, cbSize); if (NULL == pParams) { hr = E_OUTOFMEMORY; return hr; }
// Text data will start going in right after the structure
CHAR *pPos = (CHAR *)((LPBYTE)pParams + cbBaseSize);
// Start with a copy of the static fields
CopyMemory(&pParams->ici, pici, min(sizeof(pParams->ici), pici->cbSize));
// Walk along and dupe all of the string pointer fields
if (HIWORD(pici->lpVerb)) { pPos += cchVerb ? lstrcpyA(pPos, pici->lpVerb), pParams->ici.lpVerb = pPos, cchVerb : 0; } pPos += cchParameters ? lstrcpyA(pPos, pici->lpParameters), pParams->ici.lpParameters = pPos, cchParameters : 0; pPos += cchDirectory ? lstrcpyA(pPos, pici->lpDirectory), pParams->ici.lpDirectory = pPos, cchDirectory : 0;
WCHAR *pPosW = (WCHAR *) ((DWORD_PTR)pPos & 0x1 ? pPos + 1 : pPos); // Ensure Unicode alignment
if (HIWORD(pici->lpVerbW)) { pPosW += cchVerbW ? lstrcpyW(pPosW, pici->lpVerbW), pParams->ici.lpVerbW = pPosW, cchVerbW : 0; } pPosW += cchParametersW? lstrcpyW(pPosW, pici->lpParametersW),pParams->ici.lpParametersW= pPosW, cchParametersW : 0; pPosW += cchDirectoryW ? lstrcpyW(pPosW, pici->lpDirectoryW), pParams->ici.lpDirectoryW = pPosW, cchDirectoryW : 0;
// Pass all of the info off to the worker thread that will call the actual
// InvokeCommand API for us
//Set the object pointer to this object
pParams->psl = this; pParams->psl->AddRef(); // need to be able to be refcounted,
// so that the dataobject we create
// will stick around as long as needed.
if (!SHCreateThread(_InvokeThreadProc, pParams, CTF_COINIT | CTF_REF_COUNTED, NULL)) { // Couldn't start the thread, so the onus is on us to clean up
pParams->psl->Release(); LocalFree(pParams); hr = E_OUTOFMEMORY; }
return hr; }
HRESULT CShellLink::GetCommandString(UINT_PTR idCmd, UINT wFlags, UINT *pmf, LPSTR pszName, UINT cchMax) { if (_cmTarget) { return _cmTarget.GetCommandString(this, idCmd, wFlags, pmf, pszName, cchMax); } else { return E_FAIL; } }
//
// Note that we do not do a SetSite around the call to the inner HandleMenuMsg
// It isn't necessary (yet)
//
HRESULT CShellLink::TargetContextMenu::HandleMenuMsg2(IShellLink *outer, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult) { return SHForwardContextMenuMsg(_pcmTarget, uMsg, wParam, lParam, plResult, NULL==plResult); }
STDMETHODIMP CShellLink::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult) { if (_cmTarget) { return _cmTarget.HandleMenuMsg2(this, uMsg, wParam, lParam, plResult); }
return E_NOTIMPL; }
STDMETHODIMP CShellLink::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam) { return HandleMenuMsg2(uMsg, wParam, lParam, NULL); }
HRESULT CShellLink::_InitDropTarget() { if (_pdtSrc) { return S_OK; }
HWND hwnd; IUnknown_GetWindow(_punkSite, &hwnd); return _GetUIObject(hwnd, IID_PPV_ARG(IDropTarget, &_pdtSrc)); }
STDMETHODIMP CShellLink::DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { HRESULT hr = _InitDropTarget(); if (SUCCEEDED(hr)) { _grfKeyStateLast = grfKeyState; hr = _pdtSrc->DragEnter(pdtobj, grfKeyState, pt, pdwEffect); } else { *pdwEffect = DROPEFFECT_NONE; } return hr; }
STDMETHODIMP CShellLink::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { HRESULT hr = _InitDropTarget(); if (SUCCEEDED(hr)) { _grfKeyStateLast = grfKeyState; hr = _pdtSrc->DragOver(grfKeyState, pt, pdwEffect); } else { *pdwEffect = DROPEFFECT_NONE; } return hr; }
STDMETHODIMP CShellLink::DragLeave() { HRESULT hr = _InitDropTarget(); if (SUCCEEDED(hr)) { hr = _pdtSrc->DragLeave(); } return hr; }
STDMETHODIMP CShellLink::Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { HWND hwnd = NULL; HRESULT hr = _InitDropTarget(); if (SUCCEEDED(hr)) { IUnknown_GetWindow(_punkSite, &hwnd);
_pdtSrc->DragLeave(); // leave from the un-resolved drop target.
hr = _Resolve(hwnd, 0, 0); // track the target
if (S_OK == hr) { IDropTarget *pdtgtResolved; if (SUCCEEDED(_GetUIObject(hwnd, IID_PPV_ARG(IDropTarget, &pdtgtResolved)))) { IUnknown_SetSite(pdtgtResolved, SAFECAST(this, IShellLink *));
SHSimulateDrop(pdtgtResolved, pdtobj, _grfKeyStateLast, &pt, pdwEffect);
IUnknown_SetSite(pdtgtResolved, NULL);
pdtgtResolved->Release(); } } }
if (FAILED_AND_NOT_CANCELED(hr)) { TCHAR szLinkSrc[MAX_PATH]; if (_pidl && SHGetPathFromIDList(_pidl, szLinkSrc)) { ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_ENUMERR_PATHNOTFOUND), MAKEINTRESOURCE(IDS_LINKERROR), MB_OK | MB_ICONEXCLAMATION, NULL, szLinkSrc); } }
if (hr != S_OK) { // make sure nothing happens (if we failed)
*pdwEffect = DROPEFFECT_NONE; }
return hr; }
STDMETHODIMP CShellLink::GetInfoTip(DWORD dwFlags, WCHAR **ppwszTip) { TCHAR szTip[INFOTIPSIZE]; TCHAR szDesc[INFOTIPSIZE];
StrCpyN(szTip, _pszPrefix ? _pszPrefix : TEXT(""), ARRAYSIZE(szTip));
// QITIPF_USENAME could be replaced with ICustomizeInfoTip::SetPrefixText()
if ((dwFlags & QITIPF_USENAME) && _pszCurFile) { SHFILEINFO sfi; if (SHGetFileInfo(_pszCurFile, 0, &sfi, sizeof(sfi), SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES)) { if (szTip[0]) StrCatBuff(szTip, TEXT("\n"), ARRAYSIZE(szTip)); StrCatBuff(szTip, sfi.szDisplayName, ARRAYSIZE(szTip)); } } GetDescription(szDesc, ARRAYSIZE(szDesc));
// if there is no comment, then we create one based on
// the target's location. only do this if we are not
// a darwin link, since the location has no meaning there
if (!szDesc[0] && !(_sld.dwFlags & SLDF_HAS_DARWINID) && !(dwFlags & QITIPF_LINKNOTARGET)) { if (dwFlags & QITIPF_LINKUSETARGET) { SHMakeDescription(_pidl, -1, szDesc, ARRAYSIZE(szDesc)); } else { _MakeDescription(_pidl, szDesc, ARRAYSIZE(szDesc)); } } else if (szDesc[0] == TEXT('@')) { WCHAR sz[INFOTIPSIZE];
if (SUCCEEDED(SHLoadIndirectString(szDesc, sz, ARRAYSIZE(sz), NULL))) { StrCpyN(szDesc, sz, ARRAYSIZE(szDesc)); } }
if (szDesc[0]) { if (szTip[0]) { StrCatBuff(szTip, TEXT("\n"), ARRAYSIZE(szTip)); }
StrCatBuff(szTip, szDesc, ARRAYSIZE(szTip)); }
if (*szTip) { return SHStrDup(szTip, ppwszTip); } else { *ppwszTip = NULL; return S_FALSE; } }
STDMETHODIMP CShellLink::GetInfoFlags(DWORD *pdwFlags) { pdwFlags = 0; return E_NOTIMPL; }
HRESULT CShellLink::_GetExtractIcon(REFIID riid, void **ppv) { HRESULT hr;
if (_pszIconLocation && _pszIconLocation[0]) { TCHAR szPath[MAX_PATH]; // update our _pszIconLocation if we have a EXP_SZ_ICON_SIG datablock
_UpdateIconFromExpIconSz(); if (_pszIconLocation[0] == TEXT('.')) { TCHAR szBogusFile[MAX_PATH]; // We allow people to set ".txt" for an icon path. In this case
// we cook up a simple pidl and use it to get to the IExtractIcon for
// whatever extension the user has specified.
hr = SHGetFolderPath(NULL, CSIDL_DESKTOPDIRECTORY, NULL, 0, szBogusFile); if (SUCCEEDED(hr)) { PathAppend(szBogusFile, TEXT("*")); lstrcatn(szBogusFile, _pszIconLocation, ARRAYSIZE(szBogusFile)); LPITEMIDLIST pidl = SHSimpleIDListFromPath(szBogusFile); if (pidl) { hr = SHGetUIObjectFromFullPIDL(pidl, NULL, riid, ppv); ILFree(pidl); } else { hr = E_OUTOFMEMORY; } } } else if ((_sld.iIcon == 0) && _pidl && SHGetPathFromIDList(_pidl, szPath) && (lstrcmpi(szPath, _pszIconLocation) == 0)) { // IExtractIconA/W
hr = _GetUIObject(NULL, riid, ppv); } else { hr = SHCreateDefExtIcon(_pszIconLocation, _sld.iIcon, _sld.iIcon, GIL_PERINSTANCE, -1, riid, ppv); } } else { // IExtractIconA/W
hr = _GetUIObject(NULL, riid, ppv); }
return hr; }
HRESULT CShellLink::_InitExtractIcon() { if (_pxi || _pxiA) return S_OK;
HRESULT hr = _GetExtractIcon(IID_PPV_ARG(IExtractIconW, &_pxi)); if (FAILED(hr)) { hr = _GetExtractIcon(IID_PPV_ARG(IExtractIconA, &_pxiA)); }
return hr; }
// IExtractIconW::GetIconLocation
STDMETHODIMP CShellLink::GetIconLocation(UINT uFlags, LPWSTR pszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags) { // If we are in a situation where a shortcut points to itself (or LinkA <--> LinkB), then break the recursion here...
if (uFlags & GIL_FORSHORTCUT) { RIPMSG(uFlags & GIL_FORSHORTCUT,"CShellLink::GIL called with GIL_FORSHORTCUT (uFlags=%x)",uFlags); return E_INVALIDARG; }
HRESULT hr = _InitExtractIcon();
if (SUCCEEDED(hr)) { uFlags |= GIL_FORSHORTCUT;
if (_pxi) { hr = _pxi->GetIconLocation(uFlags, pszIconFile, cchMax, piIndex, pwFlags); } else if (_pxiA) { CHAR sz[MAX_PATH]; hr = _pxiA->GetIconLocation(uFlags, sz, ARRAYSIZE(sz), piIndex, pwFlags); if (SUCCEEDED(hr) && hr != S_FALSE) SHAnsiToUnicode(sz, pszIconFile, cchMax); } if (SUCCEEDED(hr)) { _gilFlags = *pwFlags; } } return hr; }
// IExtractIconA::GetIconLocation
STDMETHODIMP CShellLink::GetIconLocation(UINT uFlags, LPSTR pszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags) { WCHAR szFile[MAX_PATH]; HRESULT hr = GetIconLocation(uFlags, szFile, ARRAYSIZE(szFile), piIndex, pwFlags); if (SUCCEEDED(hr)) { SHUnicodeToAnsi(szFile, pszIconFile, cchMax); } return hr; }
// IExtractIconW::Extract
STDMETHODIMP CShellLink::Extract(LPCWSTR pszFile, UINT nIconIndex, HICON *phiconLarge, HICON *phiconSmall, UINT nIconSize) { HRESULT hr = _InitExtractIcon(); if (SUCCEEDED(hr)) { // GIL_PERCLASS, GIL_PERINSTANCE
if ((_gilFlags & GIL_PERINSTANCE) || !(_gilFlags & GIL_PERCLASS)) { hr = _ShortNetTimeout(); // probe the net path
}
if (SUCCEEDED(hr)) // check again for _ShortNetTimeout() above case
{ if (_pxi) { hr = _pxi->Extract(pszFile, nIconIndex, phiconLarge, phiconSmall, nIconSize); } else if (_pxiA) { CHAR sz[MAX_PATH]; SHUnicodeToAnsi(pszFile, sz, ARRAYSIZE(sz)); hr = _pxiA->Extract(sz, nIconIndex, phiconLarge, phiconSmall, nIconSize); } } } return hr; }
// IExtractIconA::Extract
STDMETHODIMP CShellLink::Extract(LPCSTR pszFile, UINT nIconIndex, HICON *phiconLarge, HICON *phiconSmall, UINT nIconSize) { WCHAR szFile[MAX_PATH]; SHAnsiToUnicode(pszFile, szFile, ARRAYSIZE(szFile)); return Extract(szFile, nIconIndex, phiconLarge, phiconSmall, nIconSize); }
STDMETHODIMP CShellLink::AddDataBlock(void *pdb) { _AddExtraDataSection((DATABLOCK_HEADER *)pdb); return S_OK; }
STDMETHODIMP CShellLink::CopyDataBlock(DWORD dwSig, void **ppdb) { DATABLOCK_HEADER *peh = (DATABLOCK_HEADER *)SHFindDataBlock(_pExtraData, dwSig); if (peh) { *ppdb = LocalAlloc(LPTR, peh->cbSize); if (*ppdb) { CopyMemory(*ppdb, peh, peh->cbSize); return S_OK; } return E_OUTOFMEMORY; } *ppdb = NULL; return E_FAIL; }
STDMETHODIMP CShellLink::RemoveDataBlock(DWORD dwSig) { _RemoveExtraDataSection(dwSig); return S_OK; }
STDMETHODIMP CShellLink::GetFlags(DWORD *pdwFlags) { *pdwFlags = _sld.dwFlags; return S_OK; }
STDMETHODIMP CShellLink::SetFlags(DWORD dwFlags) { if (dwFlags != _sld.dwFlags) { _bDirty = TRUE; _sld.dwFlags = dwFlags; return S_OK; } return S_FALSE; // no change made
}
STDMETHODIMP CShellLink::GetPath(LPSTR pszFile, int cchFile, WIN32_FIND_DATAA *pfd, DWORD fFlags) { WCHAR szPath[MAX_PATH]; WIN32_FIND_DATAW wfd;
//Call the unicode version
HRESULT hr = GetPath(szPath, ARRAYSIZE(szPath), &wfd, fFlags);
if (pszFile) { SHUnicodeToAnsi(szPath, pszFile, cchFile); } if (pfd) { if (szPath[0]) { pfd->dwFileAttributes = wfd.dwFileAttributes; pfd->ftCreationTime = wfd.ftCreationTime; pfd->ftLastAccessTime = wfd.ftLastAccessTime; pfd->ftLastWriteTime = wfd.ftLastWriteTime; pfd->nFileSizeLow = wfd.nFileSizeLow; pfd->nFileSizeHigh = wfd.nFileSizeHigh;
SHUnicodeToAnsi(wfd.cFileName, pfd->cFileName, ARRAYSIZE(pfd->cFileName)); } else { ZeroMemory(pfd, sizeof(*pfd)); } } return hr; }
STDMETHODIMP CShellLink::SetPath(LPCSTR pszPath) { WCHAR szPath[MAX_PATH]; LPWSTR pszPathW; if (pszPath) { SHAnsiToUnicode(pszPath, szPath, ARRAYSIZE(szPath)); pszPathW = szPath; } else { pszPathW = NULL; }
return SetPath(pszPathW); }
STDAPI CShellLink_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) { *ppv = NULL;
HRESULT hr; CShellLink *pshlink = new CShellLink(); if (pshlink) { hr = pshlink->QueryInterface(riid, ppv); pshlink->Release(); } else { hr = E_OUTOFMEMORY; } return hr; }
STDMETHODIMP CShellLink::Save(IPropertyBag* pPropBag, BOOL fClearDirty, BOOL fSaveAllProperties) { return E_NOTIMPL; }
STDMETHODIMP CShellLink::InitNew(void) { _ResetPersistData(); // clear out our state
return S_OK; }
STDMETHODIMP CShellLink::Load(IPropertyBag* pPropBag, IErrorLog* pErrorLog) { _ResetPersistData(); // clear out our state
TCHAR szPath[MAX_PATH];
// TBD: Shortcut key, Run, Icon, Working Dir, Description
INT iCSIDL; HRESULT hr = SHPropertyBag_ReadInt(pPropBag, L"TargetSpecialFolder", &iCSIDL); if (SUCCEEDED(hr)) { hr = SHGetFolderPath(NULL, iCSIDL, NULL, SHGFP_TYPE_CURRENT, szPath); } else { szPath[0] = 0; hr = S_FALSE; }
if (SUCCEEDED(hr)) { WCHAR wsz[MAX_PATH]; if (SUCCEEDED(SHPropertyBag_ReadStr(pPropBag, L"Target", wsz, ARRAYSIZE(wsz)))) { TCHAR szTempPath[MAX_PATH]; SHUnicodeToTChar(wsz, szTempPath, ARRAYSIZE(szTempPath)); // Do we need to append it to the Special path?
if (szPath[0]) { // Yes
if (!PathAppend(szPath, szTempPath)) { hr = E_FAIL; } } else { // No, there is no special path
// Maybe we have an Env Var to expand
if (0 == SHExpandEnvironmentStrings(szTempPath, szPath, ARRAYSIZE(szPath))) { hr = E_FAIL; } } } else if (0 == szPath[0]) { // make sure not empty
hr = E_FAIL; } if (SUCCEEDED(hr)) { // FALSE for bUpdateTrackingData as we won't need any tracking data
// for links loaded via a property bag
hr = _SetPIDLPath(NULL, szPath, FALSE); } } return hr; }
STDMETHODIMP CShellLink::QueryService(REFGUID guidService, REFIID riid, void **ppv) { if (guidService == SID_LinkSite) return QueryInterface(riid, ppv); return IUnknown_QueryService(_punkSite, guidService, riid, ppv); }
const FULLPROPSPEC c_rgProps[] = { { PSGUID_SUMMARYINFORMATION, { PRSPEC_PROPID, PIDSI_COMMENTS } }, };
STDMETHODIMP CShellLink::Init(ULONG grfFlags, ULONG cAttributes, const FULLPROPSPEC *rgAttributes, ULONG *pFlags) { *pFlags = 0;
if (grfFlags & IFILTER_INIT_APPLY_INDEX_ATTRIBUTES) { // start at the beginning
_iChunkIndex = 0; } else { // indicate EOF
_iChunkIndex = ARRAYSIZE(c_rgProps); } _iValueIndex = 0; return S_OK; } STDMETHODIMP CShellLink::GetChunk(STAT_CHUNK *pStat) { HRESULT hr = S_OK; if (_iChunkIndex < ARRAYSIZE(c_rgProps)) { pStat->idChunk = _iChunkIndex + 1; pStat->idChunkSource = _iChunkIndex + 1; pStat->breakType = CHUNK_EOP; pStat->flags = CHUNK_VALUE; pStat->locale = GetSystemDefaultLCID(); pStat->attribute = c_rgProps[_iChunkIndex]; pStat->cwcStartSource = 0; pStat->cwcLenSource = 0;
_iValueIndex = 0; _iChunkIndex++; } else { hr = FILTER_E_END_OF_CHUNKS; } return hr; }
STDMETHODIMP CShellLink::GetText(ULONG *pcwcBuffer, WCHAR *awcBuffer) { return FILTER_E_NO_TEXT; } STDMETHODIMP CShellLink::GetValue(PROPVARIANT **ppPropValue) { HRESULT hr; if ((_iChunkIndex <= ARRAYSIZE(c_rgProps)) && (_iValueIndex < 1)) { *ppPropValue = (PROPVARIANT*)CoTaskMemAlloc(sizeof(PROPVARIANT)); if (*ppPropValue) { (*ppPropValue)->vt = VT_BSTR;
if (_pszName) { (*ppPropValue)->bstrVal = SysAllocStringT(_pszName); } else { // since _pszName is null, return an empty bstr
(*ppPropValue)->bstrVal = SysAllocStringT(TEXT("")); }
if ((*ppPropValue)->bstrVal) { hr = S_OK; } else { CoTaskMemFree(*ppPropValue); *ppPropValue = NULL; hr = E_OUTOFMEMORY; } } else { hr = E_OUTOFMEMORY; }
_iValueIndex++; } else { hr = FILTER_E_NO_MORE_VALUES; } return hr; } STDMETHODIMP CShellLink::BindRegion(FILTERREGION origPos, REFIID riid, void **ppunk) { *ppunk = NULL; return E_NOTIMPL; }
// ICustomizeInfoTip
STDMETHODIMP CShellLink::SetPrefixText(LPCWSTR pszPrefix) { Str_SetPtrW(&_pszPrefix, pszPrefix); return S_OK; }
STDMETHODIMP CShellLink::SetExtraProperties(const SHCOLUMNID *pscid, UINT cscid) { return S_OK; }
HRESULT CShellLink::_MaybeAddShim(IBindCtx **ppbcRelease) { // set the __COMPAT_LAYER environment variable if necessary
HRESULT hr = S_FALSE; *ppbcRelease = 0; if ((_sld.dwFlags & SLDF_RUN_WITH_SHIMLAYER)) { EXP_SHIMLAYER* pShimData = (EXP_SHIMLAYER*)SHFindDataBlock(_pExtraData, EXP_SHIMLAYER_SIG);
if (pShimData && pShimData->wszLayerEnvName[0]) { // we shouldnt recurse
ASSERT(FAILED(TBCGetEnvironmentVariable(TEXT("__COMPAT_LAYER"), NULL, 0))); hr = TBCSetEnvironmentVariable(L"__COMPAT_LAYER", pShimData->wszLayerEnvName, ppbcRelease); } } return hr; }
DWORD CALLBACK CLinkResolver::_ThreadStartCallBack(void *pv) { CLinkResolver *prs = (CLinkResolver *)pv; prs->_hThread = OpenThread(SYNCHRONIZE, FALSE, GetCurrentThreadId()); prs->AddRef(); return 0; }
DWORD CLinkResolver::_Search() { // Attempt to find the link using the CTracker
// object (which uses NTFS object IDs and persisted information
// about link-source moves).
if (_ptracker) { HRESULT hr = _ptracker->Search(_dwTimeLimit, // GetTickCount()-relative timeout
&_ofd, // Original WIN32_FIND_DATA
&_fdFound, // WIN32_FIND_DATA of new location
_dwResolveFlags, // SLR_ flags
_TrackerRestrictions); // TrkMendRestriction flags
if (SUCCEEDED(hr)) { // We've found the link source, and we're certain it's correct.
// So set the score to the highest possible value, and
// return.
_iScore = MIN_NO_UI_SCORE; _bContinue = FALSE; } else if (HRESULT_FROM_WIN32(ERROR_POTENTIAL_FILE_FOUND) == hr) { // We've found "a" link source, but we're not certain it's correct.
// Allow the search algorithm below to run and see if it finds
// a better match.
_iScore = MIN_NO_UI_SCORE - 1; } else if (HRESULT_FROM_WIN32(ERROR_SERVICE_REQUEST_TIMEOUT) == hr) { // The CTracker search stopped because we've timed out.
_bContinue = FALSE; } }
// Attempt to find the link source using an enumerative search
// (unless the downlevel search has been suppressed by the caller)
if (_bContinue && !(_fifFlags & FIF_NODRIVE)) { _HeuristicSearch(); }
if (_hDlg) { PostMessage(_hDlg, WM_COMMAND, IDOK, 0); }
return _iScore; }
DWORD CALLBACK CLinkResolver::_SearchThreadProc(void *pv) { // Sleep(45 * 1000); // test the network long time out case
CLinkResolver *prs = (CLinkResolver *)pv; DWORD dwRet = prs->_Search(); prs->Release(); // AddRef in the CallBack while thread creation.
return dwRet; }
DWORD CLinkResolver::_GetTimeOut() { if (0 == _dwTimeOutDelta) { _dwTimeOutDelta = TimeoutDeltaFromResolveFlags(_dwResolveFlags); } return _dwTimeOutDelta; }
#define IDT_SHOWME 1
#define IDT_NO_UI_TIMEOUT 2
void CLinkResolver::_InitDlg(HWND hDlg) { _hDlg = hDlg; if (SHCreateThread(_SearchThreadProc, this, CTF_COINIT | CTF_FREELIBANDEXIT, _ThreadStartCallBack)) { CloseHandle(_hThread); _hThread = NULL;
if (_dwResolveFlags & SLR_NO_UI) { SetTimer(hDlg, IDT_NO_UI_TIMEOUT, _GetTimeOut(), 0); } else { TCHAR szFmt[128], szTemp[MAX_PATH + ARRAYSIZE(szFmt)]; GetDlgItemText(hDlg, IDD_NAME, szFmt, ARRAYSIZE(szFmt)); wnsprintf(szTemp, ARRAYSIZE(szTemp), szFmt, _ofd.cFileName); SetDlgItemText(hDlg, IDD_NAME, szTemp); HWND hwndAni = GetDlgItem(hDlg, IDD_STATUS); Animate_Open(hwndAni, MAKEINTRESOURCE(IDA_SEARCH)); // open the resource
Animate_Play(hwndAni, 0, -1, -1); // play from start to finish and repeat
// delay showing the dialog for the common case where we quickly
// find the target (in less than 1/2 a sec)
_idtDelayedShow = SetTimer(hDlg, IDT_SHOWME, 500, 0); } } else { EndDialog(hDlg, IDCANCEL); } }
BOOL_PTR CALLBACK CLinkResolver::_DlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam) { CLinkResolver *prs = (CLinkResolver *)GetWindowLongPtr(hDlg, DWLP_USER); switch (wMsg) { case WM_INITDIALOG: // This Dialog is created in Synchronous to the Worker thread who already has Addref'd prs, so
// no need to Addref it here.
SetWindowLongPtr(hDlg, DWLP_USER, lParam); prs = (CLinkResolver *)lParam; prs->_InitDlg(hDlg); break; case WM_COMMAND: switch (GET_WM_COMMAND_ID(wParam, lParam)) { case IDD_BROWSE: prs->_hDlg = NULL; // don't let the thread close us
prs->_bContinue = FALSE; // cancel thread
Animate_Stop(GetDlgItem(hDlg, IDD_STATUS)); if (GetFileNameFromBrowse(hDlg, prs->_sfd.cFileName, ARRAYSIZE(prs->_sfd.cFileName), prs->_pszSearchOriginFirst, prs->_ofd.cFileName, NULL, NULL)) { HANDLE hfind = FindFirstFile(prs->_sfd.cFileName, &prs->_fdFound); ASSERT(hfind != INVALID_HANDLE_VALUE); FindClose(hfind); StringCchCopy(prs->_fdFound.cFileName, ARRAYSIZE(prs->_fdFound.cFileName), prs->_sfd.cFileName); prs->_iScore = MIN_NO_UI_SCORE; wParam = IDOK; } else { wParam = IDCANCEL; } // Fall through...
case IDCANCEL: // tell searching thread to stop
prs->_bContinue = FALSE; // if the searching thread is currently in the tracker
// waiting for results, wake it up and tell it to abort
if (prs->_ptracker) prs->_ptracker->CancelSearch(); // Fall through...
case IDOK: // thread posts this to us
EndDialog(hDlg, GET_WM_COMMAND_ID(wParam, lParam)); break; } break; case WM_TIMER: KillTimer(hDlg, wParam); // both are one shots
switch (wParam) { case IDT_NO_UI_TIMEOUT: PostMessage(prs->_hDlg, WM_COMMAND, IDCANCEL, 0); break; case IDT_SHOWME: prs->_idtDelayedShow = 0; ShowWindow(hDlg, SW_SHOW); break; } break; case WM_WINDOWPOSCHANGING: if ((prs->_dwResolveFlags & SLR_NO_UI) || prs->_idtDelayedShow) { WINDOWPOS *pwp = (WINDOWPOS *)lParam; pwp->flags &= ~SWP_SHOWWINDOW; } break; default: return FALSE; } return TRUE; }
typedef struct { LPCTSTR pszLinkName; LPCTSTR pszNewTarget; LPCTSTR pszCurFile; } DEADLINKDATA;
BOOL_PTR DeadLinkProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { DEADLINKDATA *pdld = (DEADLINKDATA *)GetWindowPtr(hwnd, DWLP_USER);
switch (uMsg) { case WM_INITDIALOG: pdld = (DEADLINKDATA*)lParam; SetWindowPtr(hwnd, DWLP_USER, pdld);
HWNDWSPrintf(GetDlgItem(hwnd, IDC_DEADTEXT1), PathFindFileName(pdld->pszLinkName)); if (GetDlgItem(hwnd, IDC_DEADTEXT2)) PathSetDlgItemPath(hwnd, IDC_DEADTEXT2, pdld->pszNewTarget); return TRUE;
case WM_COMMAND: switch (GET_WM_COMMAND_ID(wParam, lParam)) { case IDC_DELETE: { TCHAR szName[MAX_PATH + 1] = {0}; SHFILEOPSTRUCT fo = { hwnd, FO_DELETE, szName, NULL, FOF_NOCONFIRMATION };
StrCpyN(szName, pdld->pszCurFile, ARRAYSIZE(szName)); SHFileOperation(&fo); } // fall through...
case IDCANCEL: case IDOK: EndDialog(hwnd, GET_WM_COMMAND_ID(wParam, lParam)); break; } break; } return FALSE; }
// in:
// hwnd for UI if needed
//
// returns:
// IDOK found something
// IDNO didn't find it
// IDCANCEL user canceled the operation
int CLinkResolver::Resolve(HWND hwnd, LPCTSTR pszPath, LPCTSTR pszCurFile) { StrCpyN(_szSearchStart, pszPath, ARRAYSIZE(_szSearchStart)); PathRemoveFileSpec(_szSearchStart); _dwTimeLimit = GetTickCount() + _GetTimeOut(); int id = IDCANCEL;
if (SLR_NO_UI == (SLR_NO_UI_WITH_MSG_PUMP & _dwResolveFlags)) { if (SHCreateThread(_SearchThreadProc, this, CTF_COINIT | CTF_FREELIBANDEXIT, _ThreadStartCallBack)) { // don't care if it completes or times out. as long as it has a result
WaitForSingleObject(_hThread, _GetTimeOut()); CloseHandle(_hThread); _hThread = NULL; _bContinue = FALSE; // cancel that thread if it is still running
id = IDOK; } } else { id = (int)DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_LINK_SEARCH), hwnd, _DlgProc, (LPARAM)this); }
if (IDOK == id) { if (_iScore < MIN_NO_UI_SCORE) { if (_dwResolveFlags & SLR_NO_UI) { id = IDCANCEL; } else { // we must display UI since this file is questionable
if (_fifFlags & FIF_NODRIVE) { LPCTSTR pszName = pszCurFile ? (LPCTSTR)PathFindFileName(pszCurFile) : c_szNULL; ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_LINKUNAVAILABLE), MAKEINTRESOURCE(IDS_LINKERROR), MB_OK | MB_ICONEXCLAMATION, pszName); id = IDCANCEL; } else if (pszCurFile) { DEADLINKDATA dld; dld.pszLinkName = pszPath; dld.pszNewTarget = _fdFound.cFileName; dld.pszCurFile = pszCurFile; int idDlg = _iScore <= MIN_SHOW_USER_SCORE ? DLG_DEADSHORTCUT : DLG_DEADSHORTCUT_MATCH; id = (int)DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(idDlg), hwnd, DeadLinkProc, (LPARAM)&dld); } else if (_iScore <= MIN_SHOW_USER_SCORE) { ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_LINKNOTFOUND), MAKEINTRESOURCE(IDS_LINKERROR), MB_OK | MB_ICONEXCLAMATION, PathFindFileName(pszPath)); id = IDCANCEL; } else { if (IDYES == ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_LINKCHANGED), MAKEINTRESOURCE(IDS_LINKERROR), MB_YESNO | MB_ICONEXCLAMATION, PathFindFileName(pszPath), _fdFound.cFileName)) { id = IDOK; } else { id = IDCANCEL; } } } } } _ofd = _fdFound; return id; }
void CLinkResolver::GetResult(LPTSTR psz, UINT cch) { // _ofd.cFileName is a fully qualified name (strange for win32_find_data usage)
StrCpyN(psz, _ofd.cFileName, cch); }
CLinkResolver::CLinkResolver(CTracker *ptrackerobject, const WIN32_FIND_DATA *pofd, UINT dwResolveFlags, DWORD TrackerRestrictions, DWORD fifFlags) : _dwTimeOutDelta(0), _bContinue(TRUE), _hThread(NULL), _pstw(NULL), _ptracker(ptrackerobject), _dwResolveFlags(dwResolveFlags), _TrackerRestrictions(TrackerRestrictions), _fifFlags(fifFlags) { if (_ptracker) { _ptracker->AddRef(); }
_ofd = *pofd; // original find data
_pszSearchOriginFirst = _szSearchStart; _pszSearchOrigin = _szSearchStart; _dwMatch = _ofd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; // must match bits
}
CLinkResolver::~CLinkResolver() { if (_ptracker) { _ptracker->Release(); }
ATOMICRELEASE(_pstw);
ASSERT(NULL == _hThread); }
HRESULT CLinkResolver::_InitWalkObject() { HRESULT hr = _pstw ? S_OK : CoCreateInstance(CLSID_CShellTreeWalker, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellTreeWalker, &_pstw)); if (SUCCEEDED(hr)) { ASSERT(_pwszSearchSpec == NULL); // Note: We only search files with the same extension, this saves us a lot
// of useless work and from the humiliation of coming up with a ridiculous answer
_dwSearchFlags = WT_NOTIFYFOLDERENTER | WT_EXCLUDEWALKROOT; if (_dwMatch & FILE_ATTRIBUTE_DIRECTORY) { _dwSearchFlags |= WT_FOLDERONLY; } else { // Note that this does the right thing if the file has no extension
LPTSTR pszExt = PathFindExtension(_ofd.cFileName); _wszSearchSpec[0] = L'*'; SHTCharToUnicode(pszExt, &_wszSearchSpec[1], ARRAYSIZE(_wszSearchSpec) - 1); _pwszSearchSpec = _wszSearchSpec;
// Shortcuts to shortcuts are generally not allowed, but the
// Personal Start Menu uses them for link tracking purposes...
_fFindLnk = PathIsLnk(_ofd.cFileName); } } return hr; }
//
// Compare two FileTime structures. First, see if they're really equal
// (using CompareFileTime). If not, see if one has 10ms granularity,
// and if the other rounds down to the same value. This is done
// to handle the case where a file is moved from NTFS to FAT;
// FAT file tiems are 10ms granularity, while NTFS is 100ns. When
// an NTFS file is moved to FAT, its time is rounded down.
//
#define NTFS_UNITS_PER_FAT_UNIT 100000
BOOL IsEqualFileTimesWithTruncation(const FILETIME *pft1, const FILETIME *pft2) { ULARGE_INTEGER uli1, uli2; ULARGE_INTEGER *puliFAT, *puliNTFS; FILETIME ftFAT, ftNTFS;
if (0 == CompareFileTime(pft1, pft2)) return TRUE;
uli1.LowPart = pft1->dwLowDateTime; uli1.HighPart = pft1->dwHighDateTime;
uli2.LowPart = pft2->dwLowDateTime; uli2.HighPart = pft2->dwHighDateTime;
// Is one of the times 10ms granular?
if (0 == (uli1.QuadPart % NTFS_UNITS_PER_FAT_UNIT)) { puliFAT = &uli1; puliNTFS = &uli2; } else if (0 == (uli2.QuadPart % NTFS_UNITS_PER_FAT_UNIT)) { puliFAT = &uli2; puliNTFS = &uli1; } else { // Neither time appears to be FAT, so they're
// really different.
return FALSE; }
// If uliNTFS is already 10ms granular, then again the two times
// are really different.
if (0 == (puliNTFS->QuadPart % NTFS_UNITS_PER_FAT_UNIT)) { return FALSE; }
// Now see if the FAT time is the same as the NTFS time
// when the latter is rounded down to the nearest 10ms.
puliNTFS->QuadPart = (puliNTFS->QuadPart / NTFS_UNITS_PER_FAT_UNIT) * NTFS_UNITS_PER_FAT_UNIT;
ftNTFS.dwLowDateTime = puliNTFS->LowPart; ftNTFS.dwHighDateTime = puliNTFS->HighPart; ftFAT.dwLowDateTime = puliFAT->LowPart; ftFAT.dwHighDateTime = puliFAT->HighPart;
return (0 == CompareFileTime(&ftFAT, &ftNTFS)); }
//
// compute a weighted score for a given find
//
int CLinkResolver::_ScoreFindData(const WIN32_FIND_DATA *pfd) { int iScore = 0;
BOOL bSameName = lstrcmpi(_ofd.cFileName, pfd->cFileName) == 0;
BOOL bSameExt = lstrcmpi(PathFindExtension(_ofd.cFileName), PathFindExtension(pfd->cFileName)) == 0;
BOOL bHasCreateDate = !IsNullTime(&pfd->ftCreationTime);
BOOL bSameCreateDate = bHasCreateDate && IsEqualFileTimesWithTruncation(&pfd->ftCreationTime, &_ofd.ftCreationTime);
BOOL bSameWriteTime = !IsNullTime(&pfd->ftLastWriteTime) && IsEqualFileTimesWithTruncation(&pfd->ftLastWriteTime, &_ofd.ftLastWriteTime);
if (bSameName || bSameCreateDate) { if (bSameName) iScore += bHasCreateDate ? 16 : 32;
if (bSameCreateDate) { iScore += 32;
if (bSameExt) iScore += 8; }
if (bSameWriteTime) iScore += 8;
if (pfd->nFileSizeLow == _ofd.nFileSizeLow) iScore += 4;
// if it is in the same folder as the original give it a slight bonus
iScore += _iFolderBonus; } else { // doesn't have create date, apply different rules
if (bSameExt) iScore += 8;
if (bSameWriteTime) iScore += 8;
if (pfd->nFileSizeLow == _ofd.nFileSizeLow) iScore += 4; }
return iScore; }
//
// Helper function for both EnterFolder and FoundFile
//
HRESULT CLinkResolver::_ProcessFoundFile(LPCTSTR pszPath, WIN32_FIND_DATAW * pwfdw) { HRESULT hr = S_OK;
if (_fFindLnk || !PathIsLnk(pwfdw->cFileName)) { // both are files or folders, see how it scores
int iScore = _ScoreFindData(pwfdw);
if (iScore > _iScore) { _fdFound = *pwfdw;
// store the score and fully qualified path
_iScore = iScore; StrCpyN(_fdFound.cFileName, pszPath, ARRAYSIZE(_fdFound.cFileName)); } }
if ((_iScore >= MIN_NO_UI_SCORE) || (GetTickCount() >= _dwTimeLimit)) { _bContinue = FALSE; hr = E_FAIL; } return hr; }
// IShellTreeWalkerCallBack::FoundFile
HRESULT CLinkResolver::FoundFile(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd) { if (!_bContinue) { return E_FAIL; }
// We should've excluded files if we're looking for a folder
ASSERT(!(_dwMatch & FILE_ATTRIBUTE_DIRECTORY));
return _ProcessFoundFile(pwszPath, pwfd); }
//
// IShellTreeWalkerCallBack::EnterFolder
//
HRESULT CLinkResolver::EnterFolder(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd) { HRESULT hr = S_OK; // Respond quickly to the Cancel button.
if (!_bContinue) { return E_FAIL; }
// Once we enter a directory, we lose the "we are still in the starting
// folder" bonus.
_iFolderBonus = 0;
if (PathIsPrefix(pwszPath, _pszSearchOrigin) || IS_SYSTEM_HIDDEN(pwfd->dwFileAttributes)) { // If we're about to enter a directory we've already looked in,
// or if this is superhidden (implies recycle bin dirs), then skip it.
return S_FALSE; }
// If our target was a folder, treat this folder as a file found
if (_dwMatch & FILE_ATTRIBUTE_DIRECTORY) { hr = _ProcessFoundFile(pwszPath, pwfd); } return hr; }
BOOL CLinkResolver::_SearchInFolder(LPCTSTR pszFolder, int cLevels) { int iMaxDepth = 0;
// cLevels == -1 means inifinite depth
if (cLevels != -1) { _dwSearchFlags |= WT_MAXDEPTH; iMaxDepth = cLevels; } else { _dwSearchFlags &= ~WT_MAXDEPTH; }
// Our folder bonus code lies on the fact that files in the
// starting folder come before anything else.
ASSERT(!(_dwSearchFlags & WT_FOLDERFIRST));
_pstw->WalkTree(_dwSearchFlags, pszFolder, _pwszSearchSpec, iMaxDepth, SAFECAST(this, IShellTreeWalkerCallBack *)); _iFolderBonus = 0; // You only get one chance at the folder bonus
return _bContinue; }
//
// search function for heuristic based link resolution
// the result will be in _fdFound.cFileName
//
void CLinkResolver::_HeuristicSearch() { if (!SHRestricted(REST_NORESOLVESEARCH) && !(SLR_NOSEARCH & _dwResolveFlags) && SUCCEEDED(_InitWalkObject())) { int cUp = LNKTRACK_HINTED_UPLEVELS; BOOL bSearchOrigin = TRUE; TCHAR szRealSearchOrigin[MAX_PATH], szFolderPath[MAX_PATH];
// search up from old location
// In the olden days pszSearchOriginFirst was verified to be a valid directory
// (ie it returned TRUE to PathIsDirectory) and _HeuristicSearch was never called
// if this was not true. Alas, this is no more. Why not search the desktop and
// fixed drives anyway? In the interest of saving some time the check that used
// to be in FindInFolder which caused an early out is now here instead. The rub
// is that we only skip the downlevel search of the original volume instead of
// skipping the entire link resolution phase.
StringCchCopy(szRealSearchOrigin, ARRAYSIZE(szRealSearchOrigin), _pszSearchOriginFirst); while (!PathIsDirectory(szRealSearchOrigin)) { if (PathIsRoot(szRealSearchOrigin) || !PathRemoveFileSpec(szRealSearchOrigin)) { DebugMsg(DM_TRACE, TEXT("root path does not exists %s"), szRealSearchOrigin); bSearchOrigin = FALSE; break; } }
if (bSearchOrigin) { StringCchCopy(szFolderPath, ARRAYSIZE(szFolderPath), szRealSearchOrigin); _pszSearchOrigin = szRealSearchOrigin;
// Files found in the starting folder get a slight bonus.
// _iFolderBonus is set to zero by
// CLinkResolver::EnterFolder when we leave
// the starting folder and enter a new one.
_iFolderBonus = 2;
while (cUp-- != 0 && _SearchInFolder(szFolderPath, LNKTRACK_HINTED_DOWNLEVELS)) { if (PathIsRoot(szFolderPath) || !PathRemoveFileSpec(szFolderPath)) break; } }
if (_bContinue) { // search down from desktop
if (S_OK == SHGetFolderPath(NULL, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_CURRENT, szFolderPath)) { _pszSearchOrigin = szFolderPath; _SearchInFolder(szFolderPath, LNKTRACK_DESKTOP_DOWNLEVELS); } }
if (_bContinue) { // search down from root of fixed drives
TCHAR szRoot[4]; _pszSearchOrigin = szRoot;
for (int i = 0; _bContinue && (i < 26); i++) { if (GetDriveType(PathBuildRoot(szRoot, i)) == DRIVE_FIXED) { StringCchCopy(szFolderPath, ARRAYSIZE(szFolderPath), szRoot); _SearchInFolder(szFolderPath, LNKTRACK_ROOT_DOWNLEVELS); } } }
if (_bContinue && bSearchOrigin) { // resume search of last volume (should do an exclude list)
StringCchCopy(szFolderPath, ARRAYSIZE(szFolderPath), szRealSearchOrigin); _pszSearchOrigin = szRealSearchOrigin;
while (_SearchInFolder(szFolderPath, -1)) { if (PathIsRoot(szFolderPath) || !PathRemoveFileSpec(szFolderPath)) break; } } } }
|