Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

4919 lines
143 KiB

// shell link stuff
//
// broken link to file senarios:
// file on non removeable volume:
// file renamed in place creation date
// file moved on same volume find by name or create date
//
// link to root (drives)
// verify volume type
//
#include "shellprv.h"
#pragma hdrstop
#include "lnktrack.h"
#ifdef ENABLE_TRACK
#include "tracker.h"
int g_fNewTrack = FALSE;
#endif
// this turns on unicode support for the string fields in the link persistant data
// this does not include UNICODE support in the pild, or the LINKINFO structure,
// that work needs to be done independantly
//
// #define TEST_UNICODE_LINKS 1 // make sure we can read unicode links
// #define TEST_EXTRA_DATA
// this is to make sure the link file format is extensible
#ifdef TEST_EXTRA_DATA
BOOL bTestExtra = FALSE;
#endif // TEST_EXTRA_DATA
IPersistFileVtbl c_PersistFile_Vtbl; // forward declaration
IPersistStreamVtbl c_PersistStream_Vtbl; // forward declaration
extern IShellExtInitVtbl c_ShellExtInit_Vtbl; // forward declaration
extern IContextMenu2Vtbl c_ContextMenu_Vtbl; // forward declaration
IDropTargetVtbl c_DropTarget_Vtbl; // forward declaration
#ifdef ENABLE_TRACK
extern IShellLinkTrackerVtbl c_ShellLinkTracker_Vtbl; // forward declaration
#endif
HRESULT CShellLink_Resolve(IShellLink *psl, HWND hwnd, DWORD fFlags);
HRESULT CShellLink_PS_Load(IPersistStream *pps, IStream *pstm);
HRESULT CShellLink_PS_Save(IPersistStream *pps, IStream *pstm, BOOL fClearDirty);
HRESULT CShellLink_Save(IPersistFile *psf, LPCOLESTR pwszFile, BOOL fRemember);
HRESULT Link_LoadFromFile(CShellLink *this, LPCTSTR pszPath);
HRESULT Link_SaveToFile(CShellLink *this, LPTSTR pszPath, BOOL fRemember);
BOOL Link_ResolveRelative(CShellLink *this, LPTSTR pszPath);
HRESULT PSLoadThroughFileCache(IPersistStream *pps, LPCTSTR pszPath);
HRESULT CShellLink_GetDropTarget(CShellLink *this, IDropTarget **ppdt);
void PSUpdateFileCache(IPersistStream *pps, LPCTSTR pszPath);
void Link_AddExtraDataSection( CShellLink *this, DWORD UNALIGNED * lpData );
LPVOID Link_ReadExtraDataSection( CShellLink *this, DWORD dwSig );
void Link_RemoveExtraDataSection( CShellLink *this, DWORD dwSig );
/*
** Link_FSEvent()
**
** watch FS events, and make sure our cache gets updated
*/
void Link_FSEvent(LONG lEvent, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra)
{
TCHAR szPath[MAX_PATH];
if (pidl == NULL || ILIsEmpty(pidl))
return;
#ifdef FULL_DEBUG
if (lEvent != SHCNE_UPDATEIMAGE && lEvent != SHCNE_FREESPACE)
{
SHGetPathFromIDList(pidl, szPath);
if (PathIsLink(szPath))
{
switch(lEvent)
{
case SHCNE_RENAMEITEM: DebugMsg(DM_TRACE, TEXT("LinkFSEvent: rename %s"), szPath); break;
case SHCNE_DELETE: DebugMsg(DM_TRACE, TEXT("LinkFSEvent: delete %s"), szPath); break;
case SHCNE_UPDATEITEM: DebugMsg(DM_TRACE, TEXT("LinkFSEvent: update %s"), szPath); break;
default: DebugMsg(DM_TRACE, TEXT("LinkFSEvent: %08lX %s"), lEvent, szPath); break;
}
}
}
#endif
switch (lEvent)
{
case SHCNE_RENAMEITEM:
case SHCNE_DELETE:
case SHCNE_UPDATEITEM:
if (SHGetPathFromIDList(pidl, szPath) && PathIsLink(szPath))
{
PSUpdateFileCache(NULL, szPath);
}
break;
}
}
DWORD _Link_GUO_Worker( LPVOID lpv )
{
LPTSTR lpszTarget = (LPTSTR)lpv;
BOOL fRootExists;
TCHAR szRoot[MAX_PATH];
lstrcpyn(szRoot,lpszTarget,ARRAYSIZE(szRoot));
PathStripToRoot(szRoot);
if (PathIsUNC( szRoot ))
{
fRootExists = NetPathExists( szRoot, NULL );
}
else
{
fRootExists = PathFileExists( szRoot );
}
LocalFree((HLOCAL)lpszTarget);
return fRootExists;
}
static DWORD g_NetLinkTimeout = (DWORD)-1;
const TCHAR c_szNetLinkTimeout[] = TEXT("NetLinkTimeout");
DWORD _GetNetLinkTimeout( REFIID riid )
{
DWORD dwNetLinkTimeout;
if (g_NetLinkTimeout == -1)
{
HKEY hkey = SHGetExplorerHkey(HKEY_CURRENT_USER, FALSE);
if (hkey)
{
DWORD dwType;
DWORD dwSize = SIZEOF(dwNetLinkTimeout);
// read in the registry value
if (RegQueryValueEx(hkey, c_szNetLinkTimeout, NULL, &dwType,
(LPBYTE)&dwNetLinkTimeout, &dwSize) == ERROR_SUCCESS)
{
g_NetLinkTimeout = dwNetLinkTimeout;
return g_NetLinkTimeout;
}
}
g_NetLinkTimeout = 0;
}
if (g_NetLinkTimeout == 0)
{
#define TARGET_GUO_DEFAULT_TIMEOUT 7500
#define TARGET_GUO_DT_DEFAULT_TIMEOUT 1000
if (IsEqualIID(riid, &IID_IDropTarget))
dwNetLinkTimeout = TARGET_GUO_DT_DEFAULT_TIMEOUT;
else
dwNetLinkTimeout = TARGET_GUO_DEFAULT_TIMEOUT;
return dwNetLinkTimeout;
}
return g_NetLinkTimeout;
}
//
// CShellLink::GetUIObject (non-virtual)
//
// This function returns the specified UI object from the link source.
//
// Parameters:
// this -- Specifies the shell link object
// hwnd -- optional hwnd for UI (for drop target)
// riid -- Specifies the interface (IID_IDropTarget, IID_IExtractIcon, IID_IContextMenu, ...)
// ppvOut -- Specifies the place to return the pointer.
//
// Notes:
// Don't put smart-resolving code here. Such a thing should be done
// BEFORE calling this cuntion.
//
HRESULT Link_GetUIObject(CShellLink *this, HWND hwnd, REFIID riid, void **ppvOut)
{
HRESULT hres;
IShellFolder *psf;
LPITEMIDLIST pidl;
TCHAR szPath[MAX_PATH+1];
BOOL fPossiblySlow;
BOOL fSimpleIDList = FALSE;
if (this->sld.dwFlags & SLDF_HAS_EXP_SZ) {
LPEXP_SZ_LINK lpData;
for( lpData = (LPEXP_SZ_LINK)this->pExtraData;
lpData && lpData->cbSize && (lpData->dwSignature!=EXP_SZ_LINK_SIG);
lpData = (LPEXP_SZ_LINK)(((LPBYTE)lpData) + lpData->cbSize)
);
if (lpData && lpData->cbSize) {
#ifdef UNICODE
{
TCHAR szTmp[MAX_PATH];
ualstrcpy(szTmp, lpData->swzTarget);
ExpandEnvironmentStrings( szTmp, szPath, MAX_PATH );
}
#else
ExpandEnvironmentStrings( lpData->szTarget, szPath, MAX_PATH );
#endif
szPath[ MAX_PATH-1 ] = TEXT('\0');
if (this->pidl)
ILFree( this->pidl );
fSimpleIDList = PathIsUNC(szPath);
if (!fSimpleIDList)
fSimpleIDList = IsRemoteDrive(DRIVEID(szPath));
if (fSimpleIDList)
this->pidl = SHSimpleIDListFromPath( szPath );
else
this->pidl = ILCreateFromPath( szPath );
} else {
DebugMsg(DM_TRACE, TEXT("Link_GetUIObject called on EXP_SZ link without EXP_SZ section!"));
*ppvOut = NULL;
return E_FAIL;
}
}
if ((this->pidl == NULL) && (this->sld.dwFlags & SLDF_HAS_RELPATH))
{
Link_ResolveRelative( this, szPath );
}
if (this->pidl == NULL)
{
DebugMsg(DM_TRACE, TEXT("Link_GetUIObject called on un-initialized (empty) link"));
*ppvOut = NULL;
return E_FAIL;
}
if (SHGetPathFromIDList(this->pidl,szPath) && szPath[0] != TEXT('\0'))
{
fPossiblySlow = PathIsUNC(szPath);
if (!fPossiblySlow)
fPossiblySlow = IsRemoteDrive(DRIVEID(szPath));
if (fPossiblySlow)
{
HANDLE hThread;
DWORD dwID;
LPTSTR lpszTarget = LocalAlloc(LPTR, (lstrlen(szPath) + 1) * SIZEOF(TCHAR));
if (lpszTarget)
{
lstrcpy(lpszTarget, szPath);
hThread = CreateThread(NULL, 0, _Link_GUO_Worker,
(LPVOID)lpszTarget, 0, &dwID);
if (NULL == hThread)
{
LocalFree((HLOCAL)lpszTarget);
}
else
{
DWORD dwRetVal;
DWORD dwWaitResult;
dwWaitResult = WaitForSingleObject(hThread,
_GetNetLinkTimeout( riid ));
if (WAIT_OBJECT_0 != dwWaitResult)
{
CloseHandle(hThread);
return E_FAIL;
}
GetExitCodeThread(hThread, &dwRetVal);
if (!dwRetVal)
{
CloseHandle(hThread);
return E_FAIL;
}
CloseHandle(hThread);
}
if (fSimpleIDList)
{
if (this->pidl)
ILFree( this->pidl );
this->pidl = ILCreateFromPath( szPath );
}
}
}
}
hres = SHBindToIDListParent(this->pidl, &IID_IShellFolder, &psf, &pidl);
if (SUCCEEDED(hres))
{
hres = psf->lpVtbl->GetUIObjectOf(psf, hwnd, 1, &pidl, riid, NULL, ppvOut);
psf->lpVtbl->Release(psf);
}
return hres;
}
HRESULT CShellLink_QueryInterface(IShellLink *psl, REFIID riid, void **ppvObj)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
HRESULT hres = E_NOINTERFACE;
#if 0
TCHAR szGUID[GUIDSTR_MAX];
StringFromGUID2A(riid, szGUID, GUIDSTR_MAX);
DebugMsg(DM_TRACE, TEXT("CShellLink::QueryInterface %s"), szGUID);
#endif
*ppvObj = NULL;
if (IsEqualIID(riid, &IID_IShellLink) || IsEqualIID(riid, &IID_IUnknown))
{
*ppvObj = this;
this->cRef++;
return S_OK;
}
#ifdef USE_DATA_OBJ
else if (IsEqualIID(riid, &IID_IDataObject))
{
Assert(this->fDataAlreadyResolved == FALSE);
*ppvObj = &this->dobj;
this->cRef++;
return S_OK;
}
#endif // USE_DATA_OBJ
else if (IsEqualIID(riid, &IID_IPersistFile))
{
*ppvObj = &this->pf;
this->cRef++;
return S_OK;
}
else if (IsEqualIID(riid, &IID_IPersistStream))
{
*ppvObj = &this->ps;
this->cRef++;
return S_OK;
}
else if (IsEqualIID(riid, &IID_IShellExtInit))
{
*ppvObj = &this->si;
this->cRef++;
return S_OK;
}
else if (IsEqualIID(riid, &IID_IContextMenu) ||
IsEqualIID(riid, &IID_IContextMenu2))
{
*ppvObj = &this->cm;
this->cRef++;
return S_OK;
}
else if (IsEqualIID(riid, &IID_IDropTarget))
{
hres = CShellLink_GetDropTarget(this, (IDropTarget**)ppvObj);
}
else if (IsEqualIID(riid, &IID_IExtractIcon)
#ifdef UNICODE
|| IsEqualIID(riid, &IID_IExtractIconA)
#endif
)
{
//
// we have a few cases:
//
// is there a icon location?
//
// is it exactly the same as the destination of the link?
// dont use the location, forward to PIDL
// else
// use the icon location in the link.
//
// no icon location so forward IExtractIcon to PIDL.
//
// no PIDL, or error return a generic DOC icon
//
if (this->pszIconLocation && this->pszIconLocation[0])
{
TCHAR szPath[MAX_PATH];
if (this->sld.iIcon == 0 &&
this->pidl && SHGetPathFromIDList(this->pidl, szPath) &&
lstrcmpi(szPath, this->pszIconLocation) == 0)
{
hres = Link_GetUIObject(this, NULL, riid, ppvObj);
}
else
{
hres = SHCreateDefExtIcon(this->pszIconLocation, this->sld.iIcon,
-1, GIL_PERINSTANCE,(IExtractIcon **)ppvObj);
}
}
else
{
hres = Link_GetUIObject(this, NULL, riid, ppvObj);
}
if (FAILED(hres))
{
hres = SHCreateDefExtIcon(NULL, II_DOCNOASSOC,
-1, 0, (IExtractIcon **)ppvObj);
}
#ifdef UNICODE
if (SUCCEEDED(hres) && IsEqualIID(riid, &IID_IExtractIconA))
{
LPEXTRACTICON pxicon = *ppvObj;
hres = pxicon->lpVtbl->QueryInterface(pxicon,riid,ppvObj);
pxicon->lpVtbl->Release(pxicon);
}
#endif
}
#ifdef UNICODE
else if (IsEqualIID(riid, &IID_IShellLinkA))
{
*ppvObj = &this->slA;
this->cRef++;
return NOERROR;
}
#endif
#ifdef ENABLE_TRACK
else if( IsEqualIID(riid, &IID_IShellLinkTracker))
{
*ppvObj = &this->slt;
this->cRef++;
return NOERROR;
}
#endif // ENABLE_TRACK
else
{
DebugMsg(DM_TRACE, TEXT("CShellLink::QueryInterface failed"));
}
return hres;
}
ULONG CShellLink_AddRef(IShellLink *psl)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
this->cRef++;
return this->cRef;
}
// put the linkinfo into the default empty state
// free all the data in this link an reset all state to empty
void Link_ResetPersistData(CShellLink *this)
{
if (this->pidl)
{
ILFree(this->pidl);
this->pidl = NULL;
}
if (this->pli)
{
LocalFree((HLOCAL)this->pli);
this->pli = NULL;
}
Str_SetPtr(&this->pszName, NULL);
Str_SetPtr(&this->pszRelPath, NULL);
Str_SetPtr(&this->pszWorkingDir, NULL);
Str_SetPtr(&this->pszArgs, NULL);
Str_SetPtr(&this->pszIconLocation, NULL);
if (this->pExtraData)
{
LocalFree((HLOCAL)this->pExtraData);
this->pExtraData = NULL;
}
#ifdef ENABLE_TRACK
_fmemset(this->ptracker, 0, sizeof(*(this->ptracker)));
#endif
// init data members. all others are zero inited
_fmemset(&this->sld, 0, SIZEOF(this->sld));
this->sld.iShowCmd = SW_SHOWNORMAL;
}
ULONG CShellLink_Release(IShellLink *psl)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
this->cRef--;
if (this->cRef > 0)
return this->cRef;
Link_ResetPersistData(this); // free all data
Str_SetPtr(&this->pszCurFile, NULL);
Str_SetPtr(&this->pszRelSource, NULL);
if (this->pcmTarget)
this->pcmTarget->lpVtbl->Release(this->pcmTarget);
if (this->pdtSrc)
this->pdtSrc->lpVtbl->Release(this->pdtSrc);
#ifdef ENABLE_TRACK
if (this->ptracker)
{
LocalFree((HLOCAL)this->ptracker);
this->ptracker = NULL;
}
#endif
LocalFree((HLOCAL)this);
return 0;
}
HRESULT Link_LoadFromPath(LPTSTR szPath, IShellLink **ppsl)
{
IShellLink *psl;
HRESULT hres = CShellLink_CreateInstance(NULL, &IID_IShellLink, &psl);
if (SUCCEEDED(hres))
{
WCHAR wszPath[MAX_PATH];
IPersistFile *ppf;
psl->lpVtbl->QueryInterface(psl, &IID_IPersistFile, &ppf);
StrToOleStr(wszPath, szPath);
hres = ppf->lpVtbl->Load(ppf, wszPath, 0);
ppf->lpVtbl->Release(ppf);
if (FAILED(hres))
{
psl->lpVtbl->Release(psl);
psl = NULL;
}
}
*ppsl = psl;
return hres;
}
BOOL IsEqualFindData(CShellLink *this, const WIN32_FIND_DATA *pfd)
{
return (pfd->dwFileAttributes == this->sld.dwFileAttributes) &&
(CompareFileTime(&pfd->ftCreationTime, &this->sld.ftCreationTime) == 0) &&
(CompareFileTime(&pfd->ftLastWriteTime, &this->sld.ftLastWriteTime) == 0) &&
(pfd->nFileSizeLow == this->sld.nFileSizeLow);
}
void SetFindData(CShellLink *this, const WIN32_FIND_DATA *pfd, const TCHAR *ptszPath)
{
#ifdef ENABLE_TRACK
//
// initialize the tracker with identity information
//
if (g_fNewTrack && ptszPath && S_OK == Tracker_InitFromPath(this->ptracker, ptszPath))
this->bDirty = this->bDirty || Tracker_IsDirty(this->ptracker);
#else
UNREFERENCED_PARAMETER(ptszPath);
#endif
if (!IsEqualFindData(this, pfd))
{
this->sld.dwFileAttributes = pfd->dwFileAttributes;
this->sld.ftCreationTime = pfd->ftCreationTime;
this->sld.ftLastAccessTime = pfd->ftLastAccessTime;
this->sld.ftLastWriteTime = pfd->ftLastWriteTime;
this->sld.nFileSizeLow = pfd->nFileSizeLow;
this->bDirty = TRUE;
}
}
void GetFindData(CShellLink *this, WIN32_FIND_DATA *pfd)
{
TCHAR szPath[MAX_PATH];
pfd->dwFileAttributes = this->sld.dwFileAttributes;
pfd->ftCreationTime = this->sld.ftCreationTime;
pfd->ftLastAccessTime = this->sld.ftLastAccessTime;
pfd->ftLastWriteTime = this->sld.ftLastWriteTime;
pfd->nFileSizeLow = this->sld.nFileSizeLow;
pfd->nFileSizeHigh = 0;
SHGetPathFromIDList(this->pidl, szPath);
// no one should call this on a pidl without a path
Assert(szPath[0]);
lstrcpy(pfd->cFileName, PathFindFileName(szPath));
}
#if 0
void DumpPLI(PCLINKINFO pli)
{
LPCTSTR p;
if (!pli)
return;
DebugMsg(DM_TRACE, TEXT("DumpPLI:"));
if (g_pfnGetLinkInfoData(pli, LIDT_VOLUME_SERIAL_NUMBER, &p))
DebugMsg(DM_TRACE, TEXT("\tSerial #\t%d"), *p);
if (g_pfnGetLinkInfoData(pli, LIDT_DRIVE_TYPE, &p))
DebugMsg(DM_TRACE, TEXT("\tDrive Type\t%d"), *p);
if (g_pfnGetLinkInfoData(pli, LIDT_VOLUME_LABEL, &p))
DebugMsg(DM_TRACE, TEXT("\tLabel\t%s"), p);
if (g_pfnGetLinkInfoData(pli, LIDT_LOCAL_BASE_PATH, &p))
DebugMsg(DM_TRACE, TEXT("\tBase Path\t%s"), p);
if (g_pfnGetLinkInfoData(pli, LIDT_NET_RESOURCE, &p))
DebugMsg(DM_TRACE, TEXT("\tNet Res\t%s"), p);
if (g_pfnGetLinkInfoData(pli, LIDT_COMMON_PATH_SUFFIX, &p))
DebugMsg(DM_TRACE, TEXT("\tPath Sufix\t%s"), p);
}
#else
#define DumpPLI(p)
#endif
HRESULT CShellLink_GetPath(IShellLink *psl, LPTSTR pszFile, int cchMaxPath, WIN32_FIND_DATA *pfd, DWORD fFlags)
{
TCHAR szPath[MAX_PATH];
CShellLink *this = IToClass(CShellLink, sl, psl);
VDATEINPUTBUF(pszFile, TCHAR, cchMaxPath);
DumpPLI(this->pli);
if ((this->pidl == NULL) && (this->sld.dwFlags & SLDF_HAS_RELPATH))
{
TCHAR szTmp[ MAX_PATH +1 ];
Link_ResolveRelative( this, szTmp );
}
if (!this->pidl || !SHGetPathFromIDListEx(this->pidl, szPath, (fFlags & SLGP_SHORTPATH) ? GPFIDL_ALTNAME : 0))
szPath[0] = 0;
if (this->sld.dwFlags & SLDF_HAS_EXP_SZ)
{
LPEXP_SZ_LINK lpExtraData;
DWORD dwSize = 0;
// 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.
for( lpExtraData = (LPEXP_SZ_LINK)this->pExtraData;
lpExtraData;
lpExtraData = (LPEXP_SZ_LINK)(((LPBYTE)lpExtraData) + dwSize))
{
dwSize = lpExtraData->cbSize;
if (dwSize)
{
if (lpExtraData->dwSignature == EXP_SZ_LINK_SIG)
{
#ifdef UNICODE
ualstrcpy( szPath, lpExtraData->swzTarget );
#else
lstrcpyA( szPath, lpExtraData->szTarget );
#endif
DebugMsg( DM_TRACE, TEXT("CShellLink::GetPath() %s (from xtra data)"), szPath );
}
} else {
break;
}
}
}
if (pszFile)
{
lstrcpyn(pszFile, szPath, cchMaxPath);
// DebugMsg(DM_TRACE, "CShellLink::GetPath() %s", szPath);
}
if (pfd)
{
if (szPath[0])
GetFindData(this, pfd);
else
_fmemset(pfd, 0, SIZEOF(*pfd));
}
return (this->pidl != NULL && szPath[0] == 0) ? S_FALSE : S_OK;
}
#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, WIN32_FIND_DATA *pfd)
{
if (IsNullTime(&pfd->ftCreationTime))
{
#ifdef UNICODE
HFILE hfile = (HFILE)CreateFile( pszFile,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
#else
HFILE hfile = _lopen(pszFile, OF_READWRITE | OF_SHARE_DENY_NONE);
#endif
DebugMsg(DM_TRACE, TEXT("ShellLink::SetPath, NULL create time"));
// this file has a bogus create time, set it to the last accessed time
if (hfile != HFILE_ERROR)
{
DebugMsg(DM_TRACE, TEXT("ShellLink::SetPath, pfd times"));
DumpTimes(pfd->ftCreationTime, pfd->ftLastAccessTime, pfd->ftLastWriteTime);
if (SetFileTime((HANDLE)hfile, &pfd->ftLastWriteTime, NULL, NULL))
{
// BUGBUG: get the time back to make sure we match the precision of the file system
pfd->ftCreationTime = pfd->ftLastWriteTime; // patch this up
#ifdef DEBUG
{
FILETIME ftCreate, ftAccessed, ftWrite;
GetFileTime((HANDLE)hfile, &ftCreate, &ftAccessed, &ftWrite);
AssertMsg(CompareFileTime(&ftCreate, &pfd->ftCreationTime) == 0, TEXT("create times don't match"));
DumpTimes(ftCreate, ftAccessed, ftWrite);
}
#endif
}
else
{
DebugMsg(DM_TRACE, TEXT("ShellLink::SetPath, unable to set create time"));
}
_lclose(hfile);
}
}
}
HRESULT CShellLink_GetIDList(IShellLink *psl, LPITEMIDLIST *ppidl)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
if (this->pidl)
*ppidl = ILClone(this->pidl);
else
*ppidl = NULL;
return S_OK;
}
BOOL DifferentStrings(LPCTSTR psz1, LPCTSTR psz2)
{
if (psz1 && psz2)
return lstrcmp(psz1, psz2);
else
return (!psz1 && psz2) || (psz1 && !psz2);
}
// 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 Link_SetRelativePath(CShellLink *this, LPCTSTR pszRelSource)
{
TCHAR szPath[MAX_PATH], szDest[MAX_PATH];
Assert(!PathIsRelative(pszRelSource));
if (this->pidl == NULL || !SHGetPathFromIDList(this->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, this->sld.dwFileAttributes))
{
pszRelSource = szPath;
}
else
{
DebugMsg(DM_TRACE, TEXT("paths are not relative"));
pszRelSource = NULL; // clear the stored relative path below
}
if (DifferentStrings(this->pszRelPath, pszRelSource))
{
this->bDirty = TRUE;
Str_SetPtr(&this->pszRelPath, pszRelSource);
}
return S_OK;
}
// 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
//
HRESULT CShellLink_SetRelativePath(IShellLink *psl, LPCTSTR pszPathRel, DWORD dwReserved)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
if (dwReserved != 0)
return E_FAIL;
Str_SetPtr(&this->pszRelSource, pszPathRel);
return S_OK;
}
/*
** CopyLinkInfo()
**
** Copies LinkInfo into local memory.
**
** Arguments: pcliSrc - source LinkInfo
** ppliDest - pointer to PLINKINFO to be filled in with pointer
** to local copy
**
** Returns: HRESULT
**
** Side Effects: none
*/
PLINKINFO CopyLinkInfo(PCLINKINFO pcliSrc)
{
DWORD dwSize;
PLINKINFO pli;
Assert(pcliSrc);
dwSize = *(UNALIGNED DWORD *)pcliSrc; // size of this thing
pli = (void*)LocalAlloc(LPTR, dwSize); // make a copy
if (pli)
CopyMemory(pli, pcliSrc, dwSize);
return pli;
}
// Creates LinkInfo for a CShellLink instance that does not yet have one
// create the LinkInfo from the pidl (assumed to be to a path) for this link
//
// returns:
//
// success, pointer to the LINKINFO
// NULL this link does not have LINKINFO
PLINKINFO GetLinkInfo(CShellLink *this)
{
PLINKINFO pliNew;
TCHAR szPath[MAX_PATH];
if ((this->pidl == NULL) && (this->sld.dwFlags & SLDF_HAS_RELPATH))
{
TCHAR szTmp[ MAX_PATH +1 ];
Link_ResolveRelative( this, szTmp );
}
if (!this->pidl || !SHGetPathFromIDList(this->pidl, szPath))
{
DebugMsg(DM_TRACE, TEXT("GetLinkInfo called for non file link"));
return NULL;
}
if (this->pli)
{
LocalFree((HLOCAL)this->pli);
this->pli = NULL;
}
if (this->sld.dwFlags & SLDF_FORCE_NO_LINKINFO)
{
DebugMsg( DM_TRACE, TEXT("Found a labotimized link, not creating LINKINFO"));
return NULL;
}
if (g_pfnCreateLinkInfo(szPath, &pliNew))
{
this->pli = CopyLinkInfo(pliNew);
this->bDirty = TRUE;
g_pfnDestroyLinkInfo(pliNew);
}
return this->pli;
}
void PathGetRelative(LPTSTR pszPath, LPCTSTR pszFrom, DWORD dwAttrFrom, LPCTSTR pszRel)
{
TCHAR szRoot[MAX_PATH];
lstrcpy(szRoot, pszFrom);
if (!(dwAttrFrom & FILE_ATTRIBUTE_DIRECTORY))
PathRemoveFileSpec(szRoot);
Assert(PathIsRelative(pszRel));
PathCombine(pszPath, szRoot, pszRel);
}
// #define TEST_RPT
#ifdef TEST_RPT
int OldPathCommonPrefix(LPCTSTR pszFile1, LPCTSTR pszFile2, LPTSTR pszPath)
{
TCHAR achFile1[MAX_PATH];
TCHAR achFile2[MAX_PATH];
int ichRet = 0;
LPCTSTR pszT1, pszT2, pszNext1, pszNext2;
TCHAR ch1, ch2;
lstrcpy(achFile1, pszFile1);
lstrcpy(achFile2, pszFile2);
CharUpper(achFile1);
CharUpper(achFile2);
for (pszT1 = achFile1, pszT2 = achFile2 ;
(ch1 = *pszT1) && (ch2 = *pszT2) ;
pszT1 = pszNext1, pszT2 = pszNext2)
{
pszNext1 = CharNext(pszT1);
pszNext2 = CharNext(pszT2);
if (pszNext1 - pszT1 != pszNext2 - pszT2)
break;
if (pszNext1 - pszT1 == 1) // if (single byte character)
{
if (ch1 != ch2)
{
break;
}
if (ch1 == TEXT('\\'))
ichRet = pszT1 - (LPCTSTR)achFile1;
}
else // (doulbe byte character)
{
if ( *(WORD *)pszT1 != *(WORD *)pszT2 )
break;
}
}
// NB - The middle expression in the for-loop above can
// short circuit so fix it here.
ch2 = *pszT2;
if ((!ch1 && (!ch2 || ch2 == TEXT('\\'))) ||
(!ch2 && (ch1 == TEXT('\\'))))
{
//
// If we've reached the end of one of one or both of the
// strings we have to update ichRet.
//
ichRet = pszT1 - (LPCTSTR)achFile1;
}
if (pszPath)
{
hmemcpy(pszPath, pszFile1, ichRet);
pszPath[ichRet] = TEXT('\0');
}
return ichRet;
}
void TPathCommonPrefix(LPCTSTR pszFrom, LPCTSTR pszTo)
{
TCHAR szPath[MAX_PATH], szPathOld[MAX_PATH];
PathCommonPrefix(pszFrom, pszTo, szPath);
DebugMsg(DM_TRACE, TEXT("PCP(%s,%s) -> %s"), pszFrom, pszTo, szPath);
OldPathCommonPrefix(pszFrom, pszTo, szPathOld);
if (!PathIsUNC(szPath))
{
if (lstrcmpi(szPath, szPathOld) != 0)
{
DebugMsg(DM_TRACE, TEXT("old:%s new:%s"), szPathOld, szPath);
}
}
}
void TPathRelativePathTo(LPCTSTR pszFrom, LPCTSTR pszTo)
{
TCHAR szPath[MAX_PATH];
TCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
PathRelativePathTo(szPath, pszFrom, FILE_ATTRIBUTE_DIRECTORY, pszTo, FILE_ATTRIBUTE_DIRECTORY);
DebugMsg(DM_TRACE, TEXT("PathRelativePathTo(%s,%s) -> %s"), pszFrom, pszTo, szPath);
PathCombine(szFrom, pszFrom, TEXT("FileFrom"));
PathRelativePathTo(szPath, szFrom, 0, pszTo, FILE_ATTRIBUTE_DIRECTORY);
DebugMsg(DM_TRACE, TEXT("PathRelativePathTo(%s,%s) -> %s"), szFrom, pszTo, szPath);
PathCombine(szTo, pszTo, TEXT("FileTo"));
PathRelativePathTo(szPath, szFrom, 0, szTo, 0);
DebugMsg(DM_TRACE, TEXT("PathRelativePathTo(%s,%s) -> %s"), szFrom, szTo, szPath);
PathRelativePathTo(szPath, pszFrom, FILE_ATTRIBUTE_DIRECTORY, szTo, 0);
DebugMsg(DM_TRACE, TEXT("PathRelativePathTo(%s,%s) -> %s"), pszFrom, szTo, szPath);
}
void TestPathRelativePathTo()
{
TPathCommonPrefix(TEXT("C:\\"), TEXT("C:\\"));
TPathCommonPrefix(TEXT("C:\\a"), TEXT("C:\\"));
TPathCommonPrefix(TEXT("C:\\"), TEXT("C:\\a"));
TPathCommonPrefix(TEXT("C:\\a"), TEXT("C:\\a"));
TPathCommonPrefix(TEXT("C:\\a\\b"), TEXT("C:\\a"));
TPathCommonPrefix(TEXT("C:\\a\\b\\c"), TEXT("C:\\a"));
TPathCommonPrefix(TEXT("\\\\foo\\bar"), TEXT("C:\\a"));
TPathCommonPrefix(TEXT("\\\\foo\\bar"), TEXT("\\\\foo"));
TPathCommonPrefix(TEXT("\\\\foo\\bar"), TEXT("\\\\foo\\bar"));
TPathCommonPrefix(TEXT("\\\\foo\\bar"), TEXT("\\\\foo\\bar\\a"));
TPathCommonPrefix(TEXT("\\\\foo\\bar\\a"), TEXT("\\\\foo\\bar"));
TPathCommonPrefix(TEXT("\\\\foo\\bar\\a"), TEXT("\\\\foo\\bar\\a"));
TPathCommonPrefix(TEXT("\\\\foo\\bar\\a"), TEXT("\\\\foo\\bar\\a\\b"));
TPathCommonPrefix(TEXT("\\\\foo\\bar"), TEXT("\\\\foo\\bar\\"));
TPathCommonPrefix(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b"));
TPathCommonPrefix(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b\\c"));
TPathCommonPrefix(TEXT("C:\\a\\b\\c\\"), TEXT("C:\\a\\b\\c"));
TPathCommonPrefix(TEXT("C:\\a\\b\\c\\"), TEXT("C:\\a\\b\\c\\"));
TPathCommonPrefix(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b\\c\\d"));
TPathCommonPrefix(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b\\c\\d\\e"));
TPathCommonPrefix(TEXT("C:\\a\\b\\c"), TEXT("C:\\x"));
TPathCommonPrefix(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\x"));
TPathRelativePathTo(TEXT("C:\\"), TEXT("C:\\"));
TPathRelativePathTo(TEXT("C:\\a"), TEXT("C:\\a"));
TPathRelativePathTo(TEXT("C:\\a\\b"), TEXT("C:\\a"));
TPathRelativePathTo(TEXT("C:\\a\\b\\c"), TEXT("C:\\a"));
TPathRelativePathTo(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b"));
TPathRelativePathTo(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b\\c"));
TPathRelativePathTo(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b\\c\\d"));
TPathRelativePathTo(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b\\c\\d\\e"));
TPathRelativePathTo(TEXT("C:\\a\\b\\c"), TEXT("C:\\x"));
TPathRelativePathTo(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\x"));
}
#endif // TEST_RPT
#ifdef USE_DATA_OBJ
HGLOBAL BuildLinkSrcDescriptor(LPCTSTR pszFile)
{
UINT cbFile = (lstrlen(pszFile) + 1) * SIZEOF(WCHAR);
LINKSRCDESCRIPTOR *plsd = GlobalAlloc(GPTR, SIZEOF(LINKSRCDESCRIPTOR) + cbFile);
if (plsd)
{
plsd->cbSize = SIZEOF(LINKSRCDESCRIPTOR) + cbFile;
// plsd->clsid = CLSID_NULL;
// plsd->dwDrawAspect;
// plsd->sizel;
// plsd->pointl;
// plsd->dwStatus;
// plsd->dwFullUserTypeName;
plsd->dwSrcOfCopy = SIZEOF(LINKSRCDESCRIPTOR);
StrToOleStr((LPWSTR)&plsd[1], pszFile); // copy and widen...
}
return (HGLOBAL)plsd;
}
HRESULT CreateLinkSrc(LPCTSTR pszPath, IStream **ppstm)
{
HRESULT hres;
IStream *pstm = CreateMemStream(NULL, 0);
if (pstm)
{
IMoniker *pmk;
WCHAR wszPath[MAX_PATH];
StrToOleStr(wszPath, pszPath);
hres = CreateFileMoniker(wszPath, &pmk);
if (SUCCEEDED(hres))
{
CLSID clsid;
// WriteClassStm()
pmk->lpVtbl->GetClassID(pmk, &clsid);
pstm->lpVtbl->Write(pstm, &clsid, SIZEOF(CLSID), NULL);
hres = pmk->lpVtbl->Save(pmk, pstm, FALSE);
if (SUCCEEDED(hres))
{
const LARGE_INTEGER liOffset = {0, 0};
// set the seek pointer to the start of the stream
pstm->lpVtbl->Seek(pstm, liOffset, STREAM_SEEK_SET, NULL);
}
else
{
DebugMsg(DM_TRACE, TEXT("Save to mem stream failed"));
pstm->lpVtbl->Release(pstm);
pstm = NULL;
}
pmk->lpVtbl->Release(pmk);
}
else
{
DebugMsg(DM_TRACE, TEXT("Failed CreateFileMoniker"));
pstm->lpVtbl->Release(pstm);
pstm = NULL;
}
}
else
{
DebugMsg(DM_ERROR, TEXT("Failed to create mem stream"));
hres = E_OUTOFMEMORY;
}
*ppstm = pstm;
return hres;
}
HRESULT CShellLink_DO_QueryInterface(IDataObject *pdtobj, REFIID riid, void **ppvObj)
{
CShellLink *this = IToClass(CShellLink, dobj, pdtobj);
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
}
ULONG CShellLink_DO_AddRef(IDataObject *pdtobj)
{
CShellLink *this = IToClass(CShellLink, dobj, pdtobj);
return CShellLink_AddRef(&this->sl);
}
ULONG CShellLink_DO_Release(IDataObject *pdtobj)
{
CShellLink *this = IToClass(CShellLink, dobj, pdtobj);
return CShellLink_Release(&this->sl);
}
// get a path to create the file moniker to for the link data object
BOOL GetFilePathForData(CShellLink *this, LPTSTR pszPath)
{
if (this->fDataAlreadyResolved ||
SUCCEEDED(CShellLink_Resolve(&this->sl, NULL, SLR_NO_UI)))
{
// be sure to not resolve everytime GetData is called as it is slow
this->fDataAlreadyResolved = TRUE;
if (this->pidl && !(this->sld.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
SHGetPathFromIDList(this->pidl, pszPath))
return TRUE;
}
return FALSE;
}
HRESULT CShellLink_GetData(IDataObject *pdtobj, FORMATETC *pformatetcIn, STGMEDIUM *pmedium)
{
CShellLink *this = IToClass(CShellLink, dobj, pdtobj);
TCHAR szPath[MAX_PATH];
HRESULT hres = DATA_E_FORMATETC;
#ifdef DEBUG
TCHAR szName[64];
if (!GetClipboardFormatName(pformatetcIn->cfFormat, szName, ARRAYSIZE(szName)))
wsprintf(szName, TEXT("#%d"), pformatetcIn->cfFormat);
DebugMsg(DM_TRACE, TEXT("CShellLink::GetData(%s)"), szName);
#endif
pmedium->hGlobal = NULL;
pmedium->pUnkForRelease = NULL;
if ((CF_LINKSOURCEDESCRIPTOR == pformatetcIn->cfFormat) &&
(pformatetcIn->tymed & TYMED_HGLOBAL))
{
if (GetFilePathForData(this, szPath))
{
pmedium->hGlobal = BuildLinkSrcDescriptor(szPath);
if (pmedium->hGlobal)
{
pmedium->tymed = TYMED_HGLOBAL;
hres = S_OK;
}
}
}
else if ((CF_LINKSOURCE == pformatetcIn->cfFormat) &&
(pformatetcIn->tymed & TYMED_ISTREAM))
{
if (GetFilePathForData(this, szPath))
{
hres = CreateLinkSrc(szPath, &pmedium->pstm);
pmedium->tymed = TYMED_ISTREAM;
}
}
#ifdef DEBUG
if (hres == S_OK)
{
DebugMsg(DM_TRACE, TEXT("GetData returns S_OK"));
}
#endif
return hres;
}
HRESULT CShellLink_GetDataHere(IDataObject *pdtobj, FORMATETC *pformatetc, STGMEDIUM *pmedium )
{
DebugMsg(DM_ERROR, TEXT("CShellLink_GetDataHere not implemented"));
return E_NOTIMPL;
}
HRESULT CShellLink_QueryGetData(IDataObject *pdtobj, FORMATETC *pformatetcIn)
{
#ifdef DEBUG
TCHAR szName[64];
if (!GetClipboardFormatName(pformatetcIn->cfFormat, szName, ARRAYSIZE(szName)))
wsprintf(szName, TEXT("#%d"), pformatetcIn->cfFormat);
DebugMsg(DM_TRACE, TEXT("CShellLink::QueryGetData(%s)"), szName);
#endif
if (pformatetcIn->cfFormat == CF_LINKSOURCEDESCRIPTOR ||
pformatetcIn->cfFormat == CF_LINKSOURCE)
{
DebugMsg(DM_TRACE, TEXT("QueryGetData says S_OK"));
return S_OK;
}
else
{
return S_FALSE;
}
}
HRESULT CShellLink_GetCanonicalFormatEtc(IDataObject *pdtobj, FORMATETC *pformatetc, FORMATETC *pformatetcOut)
{
return DATA_S_SAMEFORMATETC;
}
HRESULT CShellLink_SetData(IDataObject *pdtobj, FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease)
{
DebugMsg(DM_ERROR, TEXT("CShellLink_SetData not implemented"));
return E_NOTIMPL;
}
HRESULT CShellLink_EnumFormatEtc(IDataObject *pdtobj, DWORD dwDirection, LPENUMFORMATETC *ppenumFormatEtc)
{
FORMATETC fmts[2] = {
{ CF_LINKSOURCE, NULL, DVASPECT_CONTENT, -1, TYMED_ISTREAM },
{ CF_LINKSOURCEDESCRIPTOR, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }
};
return SHCreateStdEnumFmtEtc(ARRAYSIZE(fmts), fmts, ppenumFormatEtc);
}
HRESULT CShellLink_Advise(IDataObject *pdtobj, FORMATETC *pFormatetc, DWORD advf, LPADVISESINK pAdvSink, DWORD *pdwConnection)
{
return OLE_E_ADVISENOTSUPPORTED;
}
HRESULT CShellLink_Unadvise(IDataObject *pdtobj, DWORD dwConnection)
{
return OLE_E_ADVISENOTSUPPORTED;
}
HRESULT CShellLink_EnumAdvise(IDataObject *pdtobj, LPENUMSTATDATA *ppenumAdvise)
{
return OLE_E_ADVISENOTSUPPORTED;
}
#pragma data_seg(".text", "CODE")
IDataObjectVtbl c_DataObj_Vtbl = {
CShellLink_DO_QueryInterface, CShellLink_DO_AddRef, CShellLink_DO_Release,
CShellLink_GetData,
CShellLink_GetDataHere,
CShellLink_QueryGetData,
CShellLink_GetCanonicalFormatEtc,
CShellLink_SetData,
CShellLink_EnumFormatEtc,
CShellLink_Advise,
CShellLink_Unadvise,
CShellLink_EnumAdvise
};
#pragma data_seg()
#endif // USE_DATA_OBJ
//
// update the working dir to match changes being made to the link target
//
void UpdateWorkingDir(CShellLink *this, LPCITEMIDLIST pidlNew)
{
TCHAR szOld[MAX_PATH], szNew[MAX_PATH], szPath[MAX_PATH];
if (this->pszWorkingDir == NULL ||
this->pszWorkingDir[0] == 0 ||
this->pidl == NULL ||
!SHGetPathFromIDList(this->pidl, szOld) ||
StrChr(szOld, TEXT('%')) || // has environement var %USER%
!SHGetPathFromIDList(pidlNew, szNew))
return;
if (PathRelativePathTo(szPath, szOld, this->sld.dwFileAttributes, this->pszWorkingDir, FILE_ATTRIBUTE_DIRECTORY))
{
PathGetRelative(szOld, szNew, GetFileAttributes(szNew), szPath); // get result is szOld
if (PathIsDirectory(szOld))
{
DebugMsg(DM_TRACE, TEXT("working dir updated to %s"), szOld);
Str_SetPtr(&this->pszWorkingDir, szOld);
this->bDirty = TRUE;
}
}
}
// 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
// pfdNew find data for this file (if we already have it)
//
// returns:
// TRUE successfully set the pidl (or it was unchanged)
// FALSE memory failure or pszPath does not exist
BOOL SetPIDLPath(CShellLink *this, LPCITEMIDLIST pidlNew, LPCTSTR pszPath, const WIN32_FIND_DATA *pfdNew)
{
LPITEMIDLIST pidlCreated = NULL;
#ifdef TEST_RPT
TestPathRelativePathTo();
#endif
if (pszPath && !pidlNew)
{
TCHAR szPath[MAX_PATH];
// the path is the same as the current pidl, short circuit the disk hits
if (this->pidl && SHGetPathFromIDList(this->pidl, szPath) && !lstrcmpi(szPath, pszPath))
return TRUE;
pidlCreated = ILCreateFromPath(pszPath);
if (pidlCreated == NULL)
{
TCHAR achPath[MAX_PATH];
DebugMsg(DM_TRACE, TEXT("Failed to create pidl for link (trying simple PIDL)"));
lstrcpy(achPath, pszPath);
PathResolve(achPath, NULL, PRF_TRYPROGRAMEXTENSIONS);
pidlCreated = SHSimpleIDListFromPath(achPath);
}
if (!pidlCreated)
{
DebugMsg(DM_TRACE, TEXT("Failed to create pidl for link"));
return FALSE;
}
pidlNew = pidlCreated;
}
if (!pidlNew)
{
if (this->pidl)
{
ILFree(this->pidl);
this->pidl = NULL;
this->bDirty = TRUE;
}
if (this->pli)
{
LocalFree((HLOCAL)this->pli);
this->pli = NULL;
}
_fmemset(&this->sld, 0, SIZEOF(this->sld));
}
else
{
// NOTE: this can result in an asser in the fs compare items if the 2 pidls
// are both simple. but since this is just an optimization to avoid resetting
// the pidl if it is the same that can be ignored.
if (!this->pidl || !ILIsEqual(this->pidl, pidlNew))
{
/* Yes. Save updated path IDL. */
LPITEMIDLIST pidlClone = ILClone(pidlNew);
if (pidlClone)
{
UpdateWorkingDir(this, pidlClone);
if (this->pidl)
ILFree(this->pidl);
this->pidl = pidlClone;
GetLinkInfo(this); // construct the LinkInfo (pli)
DumpPLI(this->pli);
}
else
{
DebugMsg(DM_TRACE, TEXT("SetPIDLPath ILClone failed"));
return FALSE;
}
this->bDirty = TRUE;
}
if (pfdNew)
SetFindData(this, pfdNew, pszPath);
}
if (pidlCreated)
ILFree(pidlCreated);
return TRUE;
}
// sees if this link might have a relative path
//
//
// out:
// pszPath returned new path
//
// returns:
// TRUE we found a relative path and it exists
// FALSE outa luck.
//
BOOL Link_ResolveRelative(CShellLink *this, LPTSTR pszPath)
{
LPCTSTR pszPathRel;
TCHAR szRoot[MAX_PATH];
// pszRelSource overrides pszCurFile
pszPathRel = this->pszRelSource ? this->pszRelSource : this->pszCurFile;
if (pszPathRel == NULL || this->pszRelPath == NULL)
return FALSE;
lstrcpy(szRoot, pszPathRel);
PathRemoveFileSpec(szRoot); // pszfrom is a file (not a directory)
PathCombine(pszPath, szRoot, this->pszRelPath);
if (PathFileExists(pszPath))
{
DebugMsg(DM_TRACE, TEXT("Link_ResolveRelative() returning %s"), pszPath);
return SetPIDLPath(this, NULL, pszPath, NULL);
}
return FALSE;
}
//
// 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)
// dwFlags to be passed to ResolveLinkInfo
//
// returns:
// TRUE we have a valid pli and pidl read to be used OR
// we should search for this path using the link search code
// FALSE we failed the update, either UI cancel or memory failure
//
BOOL UpdateAndResolveLinkInfo(CShellLink *this, HWND hwnd, DWORD dwFlags)
{
TCHAR szResolvedPath[MAX_PATH];
if (SHRestricted(REST_LINKRESOLVEIGNORELINKINFO))
{
SHGetPathFromIDList(this->pidl, szResolvedPath);
if (PathFileExists(szResolvedPath))
return TRUE;
else if (IsNetDrive(DRIVEID(szResolvedPath))==2)
{
TCHAR szDrive[4];
szDrive[0] = szResolvedPath[0];
szDrive[1] = TEXT(':');
szDrive[2] = TEXT('\0');
WNetRestoreConnection(hwnd, szDrive);
}
return TRUE;
}
if (this->pli)
{
TCHAR szResolvedPath[MAX_PATH];
DWORD dwOutFlags;
BOOL bResolved = FALSE;
Assert(! (dwFlags & RLI_IFL_UPDATE));
if (g_pfnResolveLinkInfo(this->pli, szResolvedPath, dwFlags, hwnd,
&dwOutFlags, NULL))
{
Assert(! (dwOutFlags & RLI_OFL_UPDATED));
bResolved = TRUE;
// we have to hit the disk again to set the new path
// DebugMsg(DM_TRACE, "Resolved LinkInfo -> %s %4x", szResolvedPath, dwOutFlags);
if (PathFileExists(szResolvedPath))
return SetPIDLPath(this, NULL, szResolvedPath, NULL);
else
DebugMsg(DM_TRACE, TEXT("Link referent %s not found."), szResolvedPath);
}
else if (GetLastError() == ERROR_CANCELLED)
{
DebugMsg(DM_TRACE, TEXT("ResolveLinkInfo() failed, user canceled."));
return FALSE;
}
DebugMsg(DM_TRACE, TEXT("ResolveLinkInfo() failed."));
// Resolve failed, or resolve succeeded but the file was not found.
// Try PIDL path.
SHGetPathFromIDList(this->pidl, szResolvedPath);
Assert(szResolvedPath[0]);
if (PathFileExists(szResolvedPath) || Link_ResolveRelative(this, szResolvedPath))
{
// this can happen when linkinfo can't find the original drive
// serial # on the device. could be that the drive was
// dblspaced or some disk utility wacked on it.
DebugMsg(DM_TRACE, TEXT("Link referent %s found on different volume but same path. LinkInfo will be updated."), szResolvedPath);
/* Update LinkInfo to refer to PIDL path. */
GetLinkInfo(this);
return TRUE;
}
if (bResolved)
return TRUE; // please search for this
// BUGBUG: UI goes here
// 1) if it is a floppy ask to insert
// 2) if on unshared media tell them that
// 3) net problems, tell 'em
if (dwFlags & RLI_IFL_ALLOW_UI)
{
LPCTSTR pszName = this->pszCurFile ? (LPCTSTR)PathFindFileName(this->pszCurFile) : c_szNULL;
ShellMessageBox(HINST_THISDLL, hwnd,
MAKEINTRESOURCE(IDS_LINKUNAVAILABLE),
MAKEINTRESOURCE(IDS_LINKERROR),
MB_OK | MB_ICONEXCLAMATION, pszName);
}
return FALSE;
}
else
return TRUE; // search for this
}
// pidl can be full or not... this should determine the robustness of the
// link (and the speed)
BOOL PathIsPif(LPCTSTR pszPath)
{
return lstrcmpi(PathFindExtension(pszPath), c_szDotPif) == 0;
}
HRESULT CShellLink_SetIDList(IShellLink *psl, LPCITEMIDLIST pidl)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
HRESULT hr = S_OK;
TCHAR szPath[MAX_PATH];
if (pidl != this->pidl)
SetPIDLPath(this, pidl, NULL, NULL);
// is this a pidl to a file?
if (this->pidl && SHGetPathFromIDList(this->pidl, szPath))
{
// DebugMsg(DM_TRACE, "ShellLink::SetIDList(%s)", szPath);
if (PathIsRoot(szPath))
{
_fmemset(&this->sld, 0, SIZEOF(this->sld));
this->sld.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
}
else
{
WIN32_FIND_DATA fd;
#ifdef WINNT
WIN32_FILE_ATTRIBUTE_DATA fad;
// Get the file attributes (GetFileAttributesEx is
// faster than FindFirstFile, so we'll use the former,
// and copy the data into the structure of the latter).
_fmemset( &fd, 0, sizeof(fd) );
if( GetFileAttributesEx( szPath, GetFileExInfoStandard, &fad ))
{
fd.dwFileAttributes = fad.dwFileAttributes;
fd.ftCreationTime = fad.ftCreationTime;
fd.ftLastAccessTime = fad.ftLastAccessTime;
fd.ftLastWriteTime = fad.ftLastWriteTime;
fd.nFileSizeLow = fad.nFileSizeLow;
}
#else
HANDLE hfind = FindFirstFile(szPath, &fd);
if (hfind != INVALID_HANDLE_VALUE)
{
FindClose(hfind);
}
#endif
else
{
hr = S_FALSE;
}
CheckAndFixNullCreateTime(szPath, &fd);
SetFindData(this, &fd, szPath);
}
}
return hr;
}
HRESULT CShellLink_SetPath(IShellLink *psl, LPCTSTR szPath)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
TCHAR szBuffer[ MAX_PATH ];
LPCTSTR lpszPath = szPath;
// Check to see if the target has any expandable environment strings
// in it. If so, set the appropriate information in the CShellLink
// data. BUGBUG ricktu -- this should be converted to use IPropertyStream.
ExpandEnvironmentStrings( szPath, szBuffer, MAX_PATH );
szBuffer[MAX_PATH-1] = TEXT('\0');
if (lstrcmp(szBuffer, szPath)) {
EXP_SZ_LINK expLink;
LPEXP_SZ_LINK lpNew = NULL;
// mark that link has expandable strings, and add them
this->sld.dwFlags |= SLDF_HAS_EXP_SZ;
for( lpNew = (LPEXP_SZ_LINK)this->pExtraData;
lpNew && lpNew->cbSize && lpNew->dwSignature!=EXP_SZ_LINK_SIG;
lpNew = (LPEXP_SZ_LINK)(((LPBYTE)lpNew) + lpNew->cbSize)
);
if ((!lpNew) || (lpNew->cbSize==0))
{
lpNew = &expLink;
lpNew->cbSize = 0;
}
if (lpNew) {
lpNew->dwSignature = EXP_SZ_LINK_SIG;
#ifdef UNICODE
WideCharToMultiByte( CP_ACP, 0,
szPath, -1,
(LPSTR)lpNew->szTarget, MAX_PATH,
NULL, NULL
);
lpNew->szTarget[MAX_PATH-1] = '\0';
ualstrcpy( lpNew->swzTarget, szPath );
#else
lstrcpyA( lpNew->szTarget, szPath );
// BUGBUG Fix this alignment problem
{
WCHAR wszTmp[MAX_PATH];
MultiByteToWideChar( CP_ACP, 0,
szPath, -1,
wszTmp, MAX_PATH
);
ualstrcpyW(lpNew->swzTarget, wszTmp);
}
lpNew->swzTarget[MAX_PATH-1] = L'\0';
#endif
}
// See if this is a new entry that we need to add
if (lpNew->cbSize==0)
{
lpNew->cbSize = sizeof( EXP_SZ_LINK );
Link_AddExtraDataSection( this, (DWORD UNALIGNED *)lpNew );
}
lpszPath = szBuffer;
} else {
this->sld.dwFlags &= (~SLDF_HAS_EXP_SZ);
Link_RemoveExtraDataSection( this, EXP_SZ_LINK_SIG );
}// end of BUGBUG ricktu.
SetPIDLPath(this, NULL, lpszPath, NULL);
return CShellLink_SetIDList(psl, this->pidl);
}
HRESULT CShellLink_GetDescription(IShellLink *psl, LPTSTR pszFile, int cchMaxPath)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
VDATEINPUTBUF(pszFile, TCHAR, cchMaxPath);
if (!this->pszName)
*pszFile = 0;
else
lstrcpyn(pszFile, this->pszName, cchMaxPath);
return S_OK;
}
HRESULT CShellLink_SetDescription(IShellLink *psl, LPCTSTR pszFile)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
if (DifferentStrings(this->pszName, pszFile)) // case sensitive
this->bDirty = TRUE;
Str_SetPtr(&this->pszName, pszFile);
return S_OK;
}
HRESULT CShellLink_GetWorkingDirectory(IShellLink *psl, LPTSTR pszFile, int cchMaxPath)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
VDATEINPUTBUF(pszFile, TCHAR, cchMaxPath);
if (!this->pszWorkingDir)
*pszFile = 0;
else
{
#ifdef MYDIR_LIVES
if (lstrcmp(this->pszWorkingDir, c_szMyDirTag) != 0)
{
#endif
lstrcpyn(pszFile, this->pszWorkingDir, cchMaxPath);
#ifdef MYDIR_LIVES
}
else
{
TCHAR szMyDirPath[MAX_PATH];
SHGetSpecialFolderPath(NULL, szMyDirPath, CSIDL_PERSONAL, TRUE);
lstrcpyn(pszFile, szMyDirPath, cchMaxPath);
}
#endif //MYDIR_LIVES
}
return S_OK;
}
HRESULT CShellLink_SetWorkingDirectory(IShellLink *psl, LPCTSTR pszFile)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
#ifdef MYDIR_LIVES
TCHAR szMyDirPath[MAX_PATH];
SHGetSpecialFolderPath(NULL, szMyDirPath, CSIDL_PERSONAL, TRUE);
// If string compares to special directory substitute our special
// tag back in...
if (pszFile && (lstrcmpi(pszFile, szMyDirPath) == 0))
pszFile = c_szMyDirTag;
#endif
if (DifferentStrings(this->pszWorkingDir, pszFile))
this->bDirty = TRUE;
Str_SetPtr(&this->pszWorkingDir, pszFile);
return S_OK;
}
HRESULT CShellLink_GetArguments(IShellLink *psl, LPTSTR pszFile, int cchMaxPath)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
VDATEINPUTBUF(pszFile, TCHAR, cchMaxPath);
if (!this->pszArgs)
*pszFile = 0;
else
lstrcpyn(pszFile, this->pszArgs, cchMaxPath);
return S_OK;
}
HRESULT CShellLink_SetArguments(IShellLink *psl, LPCTSTR pszArgs)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
if (DifferentStrings(this->pszArgs, pszArgs))
this->bDirty = TRUE;
Str_SetPtr(&this->pszArgs, pszArgs);
return S_OK;
}
HRESULT CShellLink_GetHotkey(IShellLink *psl, WORD *pwHotkey)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
*pwHotkey = this->sld.wHotkey;
return S_OK;
}
HRESULT CShellLink_SetHotkey(IShellLink *psl, WORD wHotkey)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
if (this->sld.wHotkey != wHotkey)
this->bDirty = TRUE;
this->sld.wHotkey = wHotkey;
return S_OK;
}
HRESULT CShellLink_GetShowCmd(IShellLink *psl, int *piShowCmd)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
*piShowCmd = this->sld.iShowCmd;
return S_OK;
}
HRESULT CShellLink_SetShowCmd(IShellLink *psl, int iShowCmd)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
if (this->sld.iShowCmd != iShowCmd)
this->bDirty = TRUE;
this->sld.iShowCmd = iShowCmd;
return S_OK;
}
HRESULT CShellLink_GetIconLocation(IShellLink *psl, LPTSTR pszIconPath, int cchIconPath, int *piIcon)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
VDATEINPUTBUF(pszIconPath, TCHAR, cchIconPath);
if (!this->pszIconLocation)
*pszIconPath = 0;
else
lstrcpyn(pszIconPath, this->pszIconLocation, cchIconPath);
*piIcon = this->sld.iIcon;
return S_OK;
}
HRESULT CShellLink_SetIconLocation(IShellLink *psl, LPCTSTR pszIconPath, int iIcon)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
if (DifferentStrings(this->pszIconLocation, pszIconPath) || (this->sld.iIcon != iIcon))
this->bDirty = TRUE;
Str_SetPtr(&this->pszIconLocation, pszIconPath);
this->sld.iIcon = iIcon;
return S_OK;
}
//+------------------------------------------------------------------
//
// Function: ResolveLink
//
// Purpose: Provide the implementation for
// IShellLink::Resolve and IShellLinkTracker::Resolve
//
// Inputs: [CShellLink] *this
// - Pointer to the ShellLink object.
// [HWND] hwnd
// - The parent window (which could be the desktop).
// [DWORD] fFlags
// - Flags from the SLR_FLAGS enumeration.
// [DWORD] dwTrackFlags
// - Restrictions on the link-tracking algorithm (TRACK_* flags).
// [DWORD] dwTickCountDeadline
// - Timeout on the link-tracking algorithm, in absolute time
// (WRT GetTickCount()).
// [DWORD] dwReserved
// - Reserved for future expansion.
//
// Outputs: [HRESULT]
// - S_OK resolution was successful
// S_FALSE user canceled
//
// 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 ResolveLink( CShellLink * this,
HWND hwnd,
DWORD fFlags,
DWORD dwTrackFlags,
DWORD dwTickCountDeadline,
DWORD dwReserved)
{
HRESULT hres = S_OK;
HANDLE hfind;
WIN32_FIND_DATA fd;
TCHAR szPath[MAX_PATH];
DWORD dwResolveFlags;
// check to see whether this link has expandable environment strings
if (this->sld.dwFlags & SLDF_HAS_EXP_SZ)
{
TCHAR szExp[ MAX_PATH ];
LPEXP_SZ_LINK lpData;
// yep, so create a new pidl that points to expanded path
for( lpData = (LPEXP_SZ_LINK)this->pExtraData;
lpData && lpData->cbSize && (lpData->dwSignature!=EXP_SZ_LINK_SIG);
lpData = (LPEXP_SZ_LINK) (((LPBYTE)lpData) + lpData->cbSize)
);
if (lpData && lpData->cbSize)
{
#ifdef UNICODE
{
TCHAR szTmp[MAX_PATH]; // BUGBUG Fix this alignment problem
ualstrcpy(szTmp, lpData->swzTarget);
ExpandEnvironmentStrings( szTmp,
szExp,
ARRAYSIZE(szExp)
);
}
#else
ExpandEnvironmentStrings( lpData->szTarget,
szExp,
ARRAYSIZE(szExp)
);
#endif
szExp[ MAX_PATH-1 ] = TEXT('\0');
}
if (this->pidl)
ILFree( this->pidl );
this->pidl = ILCreateFromPath( szExp );
}
// ensure that this is a link to a file, if not assume it is ok...
if (!this->pidl || !SHGetPathFromIDList(this->pidl, szPath))
return S_OK;
dwResolveFlags = (RLI_IFL_CONNECT | RLI_IFL_TEMPORARY);
if (!PathIsRoot(szPath))
dwResolveFlags |= RLI_IFL_LOCAL_SEARCH;
if (!(fFlags & SLR_NO_UI))
dwResolveFlags |= RLI_IFL_ALLOW_UI;
if (!UpdateAndResolveLinkInfo(this, hwnd, dwResolveFlags))
{
DebugMsg(DM_TRACE, TEXT("UpdateAndResolveLinkInfo() failed"));
return S_FALSE; // they canceled or this failed
}
// UpdateAndResolveLinkInfo() may have changed the path
SHGetPathFromIDList(this->pidl, szPath);
if (PathIsRoot(szPath))
{
// DebugMsg(DM_TRACE, "ShellLink::Resolve() root path %s", szPath);
// should be golden
}
else
{
// the above code did the retry logic (UI) if needed
hfind = FindFirstFile(szPath, &fd);
if (hfind != INVALID_HANDLE_VALUE)
{
FindClose(hfind);
SetFindData(this, &fd, szPath);
}
else
{
int id;
BOOL fFound = FALSE;
// this thing is a link that is broken
id = GetLastError();
DebugMsg(DM_TRACE, TEXT("ShellLink::Resolve() file not found %s(%d)"), szPath, id);
// Some error codes we will try to recover from by trying to get the network
// to restore the connection.
if (id == ERROR_BAD_NETPATH)
{
TCHAR szDrive[4];
szDrive[0] = szPath[0];
szDrive[1] = TEXT(':');
szDrive[2] = TEXT('\0');
if (WNetRestoreConnection(hwnd, szDrive) == WN_SUCCESS)
{
hfind = FindFirstFile(szPath, &fd);
if (hfind != INVALID_HANDLE_VALUE)
{
FindClose(hfind);
SetFindData(this, &fd, szPath);
fFound = TRUE;
}
}
}
if (!fFound)
{
GetFindData(this, &fd);
#ifdef ENABLE_TRACK
if (g_fNewTrack)
{
id = FindInFolder2( hwnd, fFlags, szPath, &fd,
dwTrackFlags, dwTickCountDeadline, this->ptracker );
}
else
{
#endif
id = FindInFolder( hwnd, fFlags, szPath, &fd );
#ifdef ENABLE_TRACK
}
#endif
if (id == IDOK)
{
DebugMsg(DM_TRACE, TEXT("ShellLink::Resolve() resolved to %s"), fd.cFileName);
// fd.cFileName comes back fully qualified
SetPIDLPath(this, NULL, fd.cFileName, &fd);
Assert(this->bDirty); // should be dirty now
}
else
{
// DebugMsg(DM_TRACE, TEXT("ShellLink::Resolve() failed"));
if ((id != IDCANCEL) && !(fFlags & SLR_NO_UI))
ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_LINKNOTFOUND), MAKEINTRESOURCE(IDS_LINKERROR),
MB_OK | MB_ICONEXCLAMATION, PathFindFileName(szPath));
Assert(!this->bDirty); // should not be dirty now
hres = S_FALSE;
}
}
}
}
//
// if the link is dirty update it.
//
if (this->bDirty && (fFlags & SLR_UPDATE))
CShellLink_Save(&this->pf, NULL, TRUE);
return hres;
} // ResolveLink
//
// Function: CShellLink_Resolve
//
// This routine determines the dwTickCountDeadline (the absolute time
// of the deadline), and calls ResolveLink().
//
// Check IPersistFile::IsDirty after calling this to see if the link info
// has changed as a result of this.
//
HRESULT CShellLink_Resolve(IShellLink *psl, HWND hwnd, DWORD fFlags)
{
CShellLink *this = IToClass(CShellLink, sl, psl);
DWORD dwTickCountDeadline = 0L;
return ResolveLink( this,
hwnd,
fFlags,
0L, // dwTrackFlags
0L,
0L ); // Reserved
}
#pragma data_seg(".text", "CODE")
IShellLinkVtbl c_ShellLink_Vtbl = {
CShellLink_QueryInterface, CShellLink_AddRef, CShellLink_Release,
CShellLink_GetPath,
CShellLink_GetIDList,
CShellLink_SetIDList,
CShellLink_GetDescription,
CShellLink_SetDescription,
CShellLink_GetWorkingDirectory,
CShellLink_SetWorkingDirectory,
CShellLink_GetArguments,
CShellLink_SetArguments,
CShellLink_GetHotkey,
CShellLink_SetHotkey,
CShellLink_GetShowCmd,
CShellLink_SetShowCmd,
CShellLink_GetIconLocation,
CShellLink_SetIconLocation,
CShellLink_SetRelativePath,
CShellLink_Resolve,
CShellLink_SetPath,
};
#pragma data_seg()
#ifdef UNICODE
//
// This section contains all of the methods for the ansi interface IShellLinkA
//
HRESULT CShellLinkA_QueryInterface(IShellLinkA *pslA, REFIID riid, LPVOID *ppvObj)
{
CShellLink *this = IToClass(CShellLink, slA, pslA);
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
}
ULONG CShellLinkA_AddRef(IShellLinkA *pslA)
{
CShellLink *this = IToClass(CShellLink, slA, pslA);
return CShellLink_AddRef(&this->sl);
}
ULONG CShellLinkA_Release(IShellLinkA *pslA)
{
CShellLink *this = IToClass(CShellLink, slA, pslA);
return CShellLink_Release(&this->sl);
}
HRESULT CShellLinkA_GetPath(IShellLinkA *pslA, LPSTR pszFile, int cchMaxPath, WIN32_FIND_DATAA *pfd, DWORD fFlags)
{
WCHAR szPath[MAX_PATH];
WIN32_FIND_DATA wfd;
HRESULT hr;
CShellLink *this = IToClass(CShellLink, slA, pslA);
VDATEINPUTBUF(pszFile, CHAR, cchMaxPath);
hr = CShellLink_GetPath(&this->sl, szPath, ARRAYSIZE(szPath), &wfd, fFlags);
if (pszFile)
{
WideCharToMultiByte(CP_ACP, 0,
szPath, -1,
pszFile, cchMaxPath,
NULL, NULL);
}
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;
WideCharToMultiByte(CP_ACP, 0,
wfd.cFileName, -1,
pfd->cFileName, ARRAYSIZE(pfd->cFileName),
NULL, NULL );
}
else
_fmemset(pfd, 0, SIZEOF(*pfd));
}
return hr;
}
HRESULT CShellLinkA_SetPath(IShellLinkA *pslA, LPCSTR pszPath)
{
WCHAR szPath[MAX_PATH];
CShellLink *this = IToClass(CShellLink, slA, pslA);
if (pszPath)
{
MultiByteToWideChar(CP_ACP, 0,
pszPath, -1,
szPath, ARRAYSIZE(szPath));
return CShellLink_SetPath(&this->sl, szPath);
}
else
{
return CShellLink_SetPath(&this->sl, NULL);
}
}
HRESULT CShellLinkA_SetRelativePath(IShellLinkA *pslA, LPCSTR pszPathRel, DWORD dwReserved)
{
WCHAR szPath[MAX_PATH];
CShellLink *this = IToClass(CShellLink, slA, pslA);
MultiByteToWideChar(CP_ACP, 0,
pszPathRel, -1,
szPath, ARRAYSIZE(szPath));
return CShellLink_SetRelativePath(&this->sl, szPath, dwReserved);
}
HRESULT CShellLinkA_GetIDList(IShellLinkA *pslA, LPITEMIDLIST *ppidl)
{
CShellLink *this = IToClass(CShellLink, slA, pslA);
return CShellLink_GetIDList(&this->sl, ppidl);
}
HRESULT CShellLinkA_SetIDList(IShellLinkA *pslA, LPCITEMIDLIST pidl)
{
CShellLink *this = IToClass(CShellLink, slA, pslA);
return CShellLink_SetIDList(&this->sl, pidl);
}
HRESULT CShellLinkA_GetDescription(IShellLinkA *pslA, LPSTR pszFile, int cchMaxPath)
{
CShellLink *this = IToClass(CShellLink, slA, pslA);
VDATEINPUTBUF(pszFile, CHAR, cchMaxPath);
if (!this->pszName)
*pszFile = 0;
else
WideCharToMultiByte(CP_ACP, 0,
this->pszName, -1,
pszFile, cchMaxPath,
NULL, NULL );
return NOERROR;
}
HRESULT CShellLinkA_SetDescription(IShellLinkA *pslA, LPCSTR pszFile)
{
WCHAR szName[MAX_PATH];
CShellLink *this = IToClass(CShellLink, slA, pslA);
MultiByteToWideChar(CP_ACP, 0,
pszFile, -1,
szName, ARRAYSIZE(szName));
return CShellLink_SetDescription(&this->sl, szName);
}
HRESULT CShellLinkA_GetWorkingDirectory(IShellLinkA *pslA, LPSTR pszDir, int cchMaxPath)
{
WCHAR szDir[MAX_PATH];
HRESULT hr;
CShellLink *this = IToClass(CShellLink, slA, pslA);
VDATEINPUTBUF(pszDir, CHAR, cchMaxPath);
hr = CShellLink_GetWorkingDirectory(&this->sl, szDir, ARRAYSIZE(szDir));
if (SUCCEEDED(hr))
{
WideCharToMultiByte(CP_ACP, 0,
szDir, -1,
pszDir, cchMaxPath,
NULL, NULL);
}
return hr;
}
HRESULT CShellLinkA_SetWorkingDirectory(IShellLinkA *pslA, LPCSTR pszDir)
{
WCHAR szDir[MAX_PATH];
CShellLink *this = IToClass(CShellLink, slA, pslA);
MultiByteToWideChar(CP_ACP, 0,
pszDir, -1,
szDir, ARRAYSIZE(szDir));
return CShellLink_SetWorkingDirectory(&this->sl, szDir);
}
HRESULT CShellLinkA_GetArguments(IShellLinkA *pslA, LPSTR pszArgs, int cchMaxPath)
{
WCHAR szArgs[MAX_PATH];
HRESULT hr;
CShellLink *this = IToClass(CShellLink, slA, pslA);
VDATEINPUTBUF(pszArgs, CHAR, cchMaxPath);
hr = CShellLink_GetArguments(&this->sl, szArgs, ARRAYSIZE(szArgs));
if (SUCCEEDED(hr))
{
WideCharToMultiByte(CP_ACP, 0,
szArgs, -1,
pszArgs, cchMaxPath,
NULL, NULL);
}
return hr;
}
HRESULT CShellLinkA_SetArguments(IShellLinkA *pslA, LPCSTR pszArgs)
{
WCHAR szArgs[MAX_PATH];
CShellLink *this = IToClass(CShellLink, slA, pslA);
MultiByteToWideChar(CP_ACP, 0,
pszArgs, -1,
szArgs, ARRAYSIZE(szArgs));
return CShellLink_SetArguments(&this->sl, szArgs);
}
HRESULT CShellLinkA_GetHotkey(IShellLinkA *pslA, WORD *pwHotkey)
{
CShellLink *this = IToClass(CShellLink, slA, pslA);
return CShellLink_GetHotkey(&this->sl, pwHotkey);
}
HRESULT CShellLinkA_SetHotkey(IShellLinkA *pslA, WORD wHotkey)
{
CShellLink *this = IToClass(CShellLink, slA, pslA);
return CShellLink_SetHotkey(&this->sl, wHotkey);
}
HRESULT CShellLinkA_GetShowCmd(IShellLinkA *pslA, int *piShowCmd)
{
CShellLink *this = IToClass(CShellLink, slA, pslA);
return CShellLink_GetShowCmd(&this->sl, piShowCmd);
}
HRESULT CShellLinkA_SetShowCmd(IShellLinkA *pslA, int iShowCmd)
{
CShellLink *this = IToClass(CShellLink, slA, pslA);
return CShellLink_SetShowCmd(&this->sl, iShowCmd);
}
HRESULT CShellLinkA_GetIconLocation(IShellLinkA *pslA, LPSTR pszPath, int cchMaxPath, int *piIcon)
{
WCHAR szPath[MAX_PATH];
HRESULT hr;
CShellLink *this = IToClass(CShellLink, slA, pslA);
VDATEINPUTBUF(pszPath, CHAR, cchMaxPath);
hr = CShellLink_GetIconLocation(&this->sl, szPath, ARRAYSIZE(szPath), piIcon);
if (SUCCEEDED(hr))
{
WideCharToMultiByte(CP_ACP, 0,
szPath, -1,
pszPath, cchMaxPath,
NULL, NULL);
}
return hr;
}
HRESULT CShellLinkA_SetIconLocation(IShellLinkA *pslA, LPCSTR pszPath, int iIcon)
{
WCHAR szPath[MAX_PATH];
LPWSTR pszPathW = szPath;
CShellLink *this = IToClass(CShellLink, slA, pslA);
if (pszPath) {
MultiByteToWideChar(CP_ACP, 0,
pszPath, -1,
szPath, ARRAYSIZE(szPath));
} else {
pszPathW = NULL;
}
return CShellLink_SetIconLocation(&this->sl, pszPathW, iIcon);
}
HRESULT CShellLinkA_Resolve(IShellLinkA *pslA, HWND hwnd, DWORD fFlags)
{
CShellLink *this = IToClass(CShellLink, slA, pslA);
return CShellLink_Resolve(&this->sl, hwnd, fFlags);
}
#pragma data_seg(".text", "CODE")
IShellLinkAVtbl c_ShellLinkA_Vtbl = {
CShellLinkA_QueryInterface,
CShellLinkA_AddRef,
CShellLinkA_Release,
CShellLinkA_GetPath,
CShellLinkA_GetIDList,
CShellLinkA_SetIDList,
CShellLinkA_GetDescription,
CShellLinkA_SetDescription,
CShellLinkA_GetWorkingDirectory,
CShellLinkA_SetWorkingDirectory,
CShellLinkA_GetArguments,
CShellLinkA_SetArguments,
CShellLinkA_GetHotkey,
CShellLinkA_SetHotkey,
CShellLinkA_GetShowCmd,
CShellLinkA_SetShowCmd,
CShellLinkA_GetIconLocation,
CShellLinkA_SetIconLocation,
CShellLinkA_SetRelativePath,
CShellLinkA_Resolve,
CShellLinkA_SetPath,
};
#pragma data_seg()
#endif // UNICODE
HRESULT CALLBACK CShellLink_CreateInstance(LPUNKNOWN punkOuter, REFIID riid, LPVOID *ppvOut)
{
HRESULT hres;
CShellLink *this;
*ppvOut = NULL;
// Make sure the linkinfo dll has been initialized
if (!LinkInfoDLL_Init()) {
return E_UNEXPECTED;
}
// does not support aggregation.
if (punkOuter)
return CLASS_E_NOAGGREGATION;
this = (CShellLink *)LocalAlloc(LPTR, SIZEOF(CShellLink));
if (!this)
return E_OUTOFMEMORY;
#ifdef ENABLE_TRACK
Tracker_InitCode();
this->ptracker = (struct CTracker *)LocalAlloc(LMEM_FIXED, sizeof(struct CTracker));
if (!this->ptracker)
{
LocalFree(this);
return(E_OUTOFMEMORY);
}
#endif
this->sl.lpVtbl = &c_ShellLink_Vtbl;
this->pf.lpVtbl = &c_PersistFile_Vtbl;
this->ps.lpVtbl = &c_PersistStream_Vtbl;
this->si.lpVtbl = &c_ShellExtInit_Vtbl;
////this->xi.lpVtbl = &c_ExtractIcon_Vtbl;
this->cm.lpVtbl = &c_ContextMenu_Vtbl;
this->dt.lpVtbl = &c_DropTarget_Vtbl;
#ifdef USE_DATA_OBJ
this->dobj.lpVtbl = &c_DataObj_Vtbl;
#endif
#ifdef UNICODE
this->slA.lpVtbl = &c_ShellLinkA_Vtbl;
#endif
#ifdef ENABLE_TRACK
this->slt.lpVtbl = &c_ShellLinkTracker_Vtbl;
#endif
this->cRef = 1;
Link_ResetPersistData(this);
//
// Note that the Release member will free the object, if QueryInterface
// failed.
//
hres = CShellLink_QueryInterface(&this->sl, riid, ppvOut);
CShellLink_Release(&this->sl);
// if we get called with something we don't expect we will
// hit this...
Assert(this->cRef == 1);
return hres; // S_OK or E_NOINTERFACE
}
#if 0
// BUGBUG: see if the retail build produces reasonable code, if not
// use something like this
#define OFF(class, itf) ((UINT)&(((class *)0)->itf))
__declspec( naked ) HRESULT CShellLink_PF_QueryInterface(IPersistFile *psf, REFIID riid, void **ppvObj)
{
_asm {
sub [esp+4], 4 // OFFSET(CShellLink, pf);
jmp CShellLink_QueryInterface
}
}
#endif
HRESULT CShellLink_PF_QueryInterface(IPersistFile *psf, REFIID riid, void **ppvObj)
{
CShellLink *this = IToClass(CShellLink, pf, psf);
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
}
ULONG CShellLink_PF_AddRef(IPersistFile *psf)
{
CShellLink *this = IToClass(CShellLink, pf, psf);
return CShellLink_AddRef(&this->sl);
}
ULONG CShellLink_PF_Release(IPersistFile *psf)
{
CShellLink *this = IToClass(CShellLink, pf, psf);
return CShellLink_Release(&this->sl);
}
HRESULT CShellLink_GetClassID(IPersistFile *psf, LPCLSID lpClassID)
{
CShellLink *this = IToClass(CShellLink, pf, psf);
*lpClassID = CLSID_ShellLink;
return S_OK;
}
HRESULT CShellLink_IsDirty(IPersistFile *psf)
{
CShellLink *this = IToClass(CShellLink, pf, psf);
// DebugMsg(DM_TRACE, "ShellLink::IsDirty -> %d", this->bDirty);
return this->bDirty ? S_OK : S_FALSE;
}
// link cache... when I load, save
typedef struct _PSCACHE {
struct _PSCACHE *pNext;
LPVOID pvData; // link data
UINT cbData;
TCHAR szPath[1]; // original filename
} PSCACHE;
#define PSCACHESIZE 4
// this must be SHARED so the link cache works when multiple processes
// dork with links
PSCACHE *g_pPSCache = NULL;
#if 0
void _DumpCache()
{
PSCACHE *ppsCache;
ENTERCRITICAL;
DebugMsg(DM_TRACE, TEXT("the link cache contains...."));
for (ppsCache = g_pPSCache; ppsCache; ppsCache = ppsCache->pNext)
DebugMsg(DM_TRACE, TEXT(" %s"), ppsCache->szPath);
LEAVECRITICAL;
}
#else
#define _DumpCache()
#endif
#ifdef YES_PSCACHE
PSCACHE *FindInCache(LPCTSTR pszPath, PSCACHE **ppsPrev)
{
PSCACHE *ppsCache;
*ppsPrev = NULL;
for (ppsCache = g_pPSCache; ppsCache; ppsCache = ppsCache->pNext)
{
if (!lstrcmpi(pszPath, ppsCache->szPath))
return ppsCache;
*ppsPrev = ppsCache;
}
return NULL;
}
PSCACHE *FindNthItem(int n)
{
PSCACHE *ppsCache;
int i;
for (ppsCache = g_pPSCache, i = 0; ppsCache && (i < n); ppsCache = ppsCache->pNext, i++)
;
return ppsCache;
}
void FreeCacheItem(PSCACHE *ppsCache)
{
if (ppsCache->pvData)
Free(ppsCache->pvData);
Free(ppsCache);
}
#endif // YES_PSCACHE
void PSCache_Term()
{
#ifdef YES_PSCACHE
_DumpCache();
#if 0 // dont need to do this on every process detatch
ENTERCRITICAL;
while (g_pPSCache)
{
PSCACHE *pNext = g_pPSCache->pNext;
FreeCacheItem(g_pPSCache);
g_pPSCache = pNext;
}
LEAVECRITICAL;
#endif
#else
return; // Do Nothing
#endif
}
#ifdef YES_PSCACHE
PSCACHE *AllocCacheItem(LPCTSTR pszPath)
{
PSCACHE *ppsCache = (void*)Alloc(SIZEOF(PSCACHE) + ((lstrlen(pszPath)+1) * SIZEOF(TCHAR)));
if (ppsCache)
{
Assert(ppsCache->pNext == NULL);
lstrcpy(ppsCache->szPath, pszPath);
return ppsCache;
}
return NULL;
}
HRESULT PSCacheSave(PSCACHE *ppsCache, IPersistStream *pps)
{
HRESULT hres = E_FAIL;
IStream *mem;
mem = CreateMemStream(NULL, 0);
if (mem == NULL)
return E_OUTOFMEMORY;
if (SUCCEEDED(pps->lpVtbl->Save(pps, mem, FALSE)))
{
const LARGE_INTEGER li = {0, 0};
ULARGE_INTEGER lu;
// get size of the mem stream.
mem->lpVtbl->Seek(mem, li, STREAM_SEEK_END, &lu);
mem->lpVtbl->Seek(mem, li, STREAM_SEEK_SET, NULL);
if (ppsCache->pvData)
Free(ppsCache->pvData);
ppsCache->cbData = lu.LowPart;
ppsCache->pvData = Alloc(ppsCache->cbData);
// now copy the stream to global memory
if (ppsCache->pvData != NULL)
hres = mem->lpVtbl->Read(mem, ppsCache->pvData, ppsCache->cbData, NULL);
}
mem->lpVtbl->Release(mem);
if (FAILED(hres))
{
DebugMsg(DM_TRACE, TEXT("Failed to save to Link cache"));
}
return hres;
}
#endif // YES_PSCACHE
HRESULT PSLoadThroughFileCache(IPersistStream *pps, LPCTSTR pszPath)
{
HRESULT hres;
PSCACHE *ppsCache, *ppsPrev;
#ifdef YES_PSCACHE
ENTERCRITICAL;
ppsCache = FindInCache(pszPath, &ppsPrev);
if (ppsCache)
{
IStream *mem;
Assert(ppsCache->pvData);
mem = CreateMemStream(ppsCache->pvData, ppsCache->cbData);
if (mem == NULL)
return E_OUTOFMEMORY;
// found the thing in the cache, load it.
// DebugMsg(DM_TRACE, "PSLoadThroughFileCache(%s) hit", pszPath);
hres = pps->lpVtbl->Load(pps, mem);
mem->lpVtbl->Release(mem);
AssertMsg(SUCCEEDED(hres), TEXT("load from cache stream failed"));
// move this item to the top of the queue
if (ppsPrev)
{
Assert(ppsCache != g_pPSCache);
ppsPrev->pNext = ppsCache->pNext;
ppsCache->pNext = g_pPSCache;
g_pPSCache = ppsCache;
}
else
{
Assert(ppsCache == g_pPSCache);
}
}
LEAVECRITICAL;
#else
ppsCache = NULL;
#endif
if (!ppsCache)
{
IStream *pstm = OpenFileStream(pszPath, OF_READ);
if (pstm)
{
hres = pps->lpVtbl->Load(pps, pstm);
pstm->lpVtbl->Release(pstm);
#define ENABLE_PS_CACHE 1
#if defined(ENABLE_PS_CACHE) && defined(YES_PSCACHE)
if (SUCCEEDED(hres))
{
// load was successful, put it in the cache
ppsCache = AllocCacheItem(pszPath);
if (ppsCache)
{
if (SUCCEEDED(PSCacheSave(ppsCache, pps)))
{
PSCACHE *ppsLast;
ENTERCRITICAL;
ppsLast = FindNthItem(PSCACHESIZE - 1);
if (ppsLast && ppsLast->pNext)
{
Assert(ppsLast->pNext->pNext == NULL);
FreeCacheItem(ppsLast->pNext);
ppsLast->pNext = NULL;
}
ppsCache->pNext = g_pPSCache;
g_pPSCache = ppsCache;
LEAVECRITICAL;
}
else
{
FreeCacheItem(ppsCache);
}
}
else
{
DebugMsg(DM_TRACE, TEXT("Failed to create cache item"));
}
}
#endif
}
else
hres = E_FAIL;
}
return hres;
}
// on saves make sure the cache contents are up to date
void PSUpdateFileCache(IPersistStream *pps, LPCTSTR pszPath)
{
#ifdef YES_PSCACHE
PSCACHE *ppsCache, *ppsPrev;
ENTERCRITICAL;
ppsCache = FindInCache(pszPath, &ppsPrev);
if (ppsCache)
{
if (pps)
DebugMsg(DM_TRACE, TEXT("Link: Update cached link %s"), pszPath);
else
DebugMsg(DM_TRACE, TEXT("Link: Flush cached link %s"), pszPath);
if (pps == NULL || FAILED(PSCacheSave(ppsCache, pps)))
{
if (pps)
DebugMsg(DM_TRACE, TEXT("Link: UpdateFileCache save failed %s"), pszPath);
if (ppsPrev)
ppsPrev->pNext = ppsCache->pNext;
else
g_pPSCache = ppsCache->pNext;
FreeCacheItem(ppsCache);
}
}
LEAVECRITICAL;
#else
return;
#endif // YES_PSCACHE
}
HRESULT Link_LoadFromPIF(CShellLink *this, LPCTSTR szPath)
{
IShellLink *psl = (IShellLink*)this;
int hPif;
PROPPRG ProgramProps;
LPITEMIDLIST pidl = NULL;
//
// Too noisy, use your private flag.
//
#ifdef YOUR_PRIVATE_DEBUG_FLAG
DebugMsg(DM_TRACE, TEXT("Link_LoadFromPIF(%s)"), szPath);
#endif
hPif = PifMgr_OpenProperties(szPath, NULL, 0, 0);
if (hPif == 0)
return E_FAIL;
_fmemset(&ProgramProps, 0, SIZEOF(ProgramProps));
if (!PifMgr_GetProperties(hPif,(LPSTR)MAKEINTATOM(GROUP_PRG), &ProgramProps, SIZEOF(ProgramProps), 0))
{
DebugMsg(DM_TRACE, TEXT("Link_LoadFromPIF PifMgr_GetProperties *failed*"));
return E_FAIL;
}
#if 0
DebugMsg(DM_TRACE, TEXT(" flPrg: %04X "), ProgramProps.flPrg);
DebugMsg(DM_TRACE, TEXT(" flPrgInit: %04X "), ProgramProps.flPrgInit);
DebugMsg(DM_TRACE, TEXT(" Title: %s "), ProgramProps.achTitle);
DebugMsg(DM_TRACE, TEXT(" CmdLine: %s "), ProgramProps.achCmdLine);
DebugMsg(DM_TRACE, TEXT(" WorkDir: %s "), ProgramProps.achWorkDir);
DebugMsg(DM_TRACE, TEXT(" HotKey: %04X "), ProgramProps.wHotKey);
DebugMsg(DM_TRACE, TEXT(" Icon: %s!%d "), ProgramProps.achIconFile, ProgramProps.wIconIndex);
DebugMsg(DM_TRACE, TEXT(" PIFFile: %s "), ProgramProps.achPIFFile);
#endif
#ifdef UNICODE
{
LPWSTR lpszTemp;
UINT cchTitle;
UINT cchWorkDir;
UINT cchCmdLine;
UINT cchIconFile;
UINT cchMax;
cchTitle = lstrlenA(ProgramProps.achTitle)+1;
cchWorkDir = lstrlenA(ProgramProps.achWorkDir)+1;
cchCmdLine = lstrlenA(ProgramProps.achCmdLine)+1;
cchIconFile = lstrlenA(ProgramProps.achIconFile)+1;
cchMax = cchTitle;
if ( cchWorkDir > cchMax ) cchMax = cchWorkDir;
if ( cchCmdLine > cchMax ) cchMax = cchCmdLine;
if ( cchIconFile > cchMax ) cchMax = cchIconFile;
cchMax *= SIZEOF(WCHAR); // For unicodizing
lpszTemp = alloca(cchMax); // For unicodizing
MultiByteToWideChar(CP_ACP, 0, ProgramProps.achTitle, cchTitle,
lpszTemp, cchMax);
CShellLink_SetDescription(psl, lpszTemp);
MultiByteToWideChar(CP_ACP, 0, ProgramProps.achWorkDir, cchWorkDir,
lpszTemp, cchMax);
CShellLink_SetWorkingDirectory(psl, lpszTemp);
CShellLink_SetHotkey(psl,ProgramProps.wHotKey);
MultiByteToWideChar(CP_ACP, 0, ProgramProps.achIconFile, cchIconFile,
lpszTemp, cchMax);
CShellLink_SetIconLocation(psl, lpszTemp, ProgramProps.wIconIndex);
MultiByteToWideChar(CP_ACP, 0, ProgramProps.achCmdLine, cchCmdLine,
lpszTemp, cchMax);
CShellLink_SetArguments(psl, PathGetArgs(lpszTemp));
PathRemoveArgs(lpszTemp);
// If this is a network path, we want to create a simple pidl
// instead of a full pidl to circumvent net hits
if (PathIsUNC(lpszTemp) || IsRemoteDrive(DRIVEID(lpszTemp)))
{
pidl = SHSimpleIDListFromPath( lpszTemp );
}
else
SetPIDLPath(this, NULL, lpszTemp, NULL);
}
#else
CShellLink_SetDescription(psl, ProgramProps.achTitle);
CShellLink_SetWorkingDirectory(psl, ProgramProps.achWorkDir);
CShellLink_SetArguments(psl, PathGetArgs(ProgramProps.achCmdLine));
CShellLink_SetHotkey(psl,ProgramProps.wHotKey);
CShellLink_SetIconLocation(psl, ProgramProps.achIconFile, ProgramProps.wIconIndex);
PathRemoveArgs(ProgramProps.achCmdLine);
// If this is a network path, we want to create a simple pidl
// instead of a full pidl to circumvent net hits
if (PathIsUNC(ProgramProps.achCmdLine) || IsRemoteDrive(DRIVEID(ProgramProps.achCmdLine)))
{
pidl = SHSimpleIDListFromPath( ProgramProps.achCmdLine );
}
else
SetPIDLPath(this, NULL, ProgramProps.achCmdLine, NULL);
#endif
// if a simple pidl was created, use it here...
if (pidl)
{
UpdateWorkingDir(this, pidl);
if (this->pidl)
ILFree(this->pidl);
this->pidl = pidl;
}
if (ProgramProps.flPrgInit & PRGINIT_MINIMIZED)
CShellLink_SetShowCmd(psl, SW_SHOWMINNOACTIVE);
else if (ProgramProps.flPrgInit & PRGINIT_MAXIMIZED)
CShellLink_SetShowCmd(psl, SW_SHOWMAXIMIZED);
else
CShellLink_SetShowCmd(psl, SW_SHOWNORMAL);
PifMgr_CloseProperties(hPif, 0);
this->bDirty = FALSE;
return S_OK;
}
BOOL RenameChangeExtension(LPTSTR pszPathSave, LPCTSTR pszExt, BOOL fMove)
{
TCHAR szPathSrc[MAX_PATH];
lstrcpy(szPathSrc, pszPathSave);
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;
}
HRESULT Link_SaveAsLink(CShellLink *this, LPCTSTR szPath)
{
HRESULT hres = E_FAIL;
IStream *pstm = OpenFileStream(szPath, OF_CREATE | OF_WRITE | OF_SHARE_DENY_WRITE);
if (pstm)
{
if (this->pszRelSource == NULL)
Link_SetRelativePath(this, szPath);
hres = CShellLink_PS_Save(&this->ps, pstm, TRUE);
if (SUCCEEDED(hres))
{
hres = pstm->lpVtbl->Commit(pstm, 0);
}
pstm->lpVtbl->Release(pstm);
if (SUCCEEDED(hres))
{
PSUpdateFileCache(&this->ps, szPath);
}
else
{
DeleteFile(szPath);
}
}
return hres;
}
// 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 Link_GetWorkingDir(CShellLink *psl, LPTSTR pszDir)
{
*pszDir = 0;
return psl->pszWorkingDir && psl->pszWorkingDir[0] &&
(ExpandEnvironmentStrings(psl->pszWorkingDir, pszDir, MAX_PATH) <= MAX_PATH);
}
HRESULT Link_SaveAsPIF(CShellLink *psl, LPCTSTR pszPath, BOOL fPath)
{
int hPif;
PROPPRG ProgramProps;
HRESULT hres;
TCHAR szDir[MAX_PATH];
TCHAR achPath[MAX_PATH];
//
// get filename and convert it to a short filename
//
if (fPath)
{
hres = CShellLink_GetPath(&psl->sl, achPath, ARRAYSIZE(achPath), NULL, 0);
PathGetShortPath(achPath);
Assert(!PathIsPif(achPath));
Assert(LOWORD(GetExeType(achPath)) == 0x5A4D);
Assert(PathIsPif(pszPath));
Assert(hres == S_OK);
}
else
{
lstrcpy(achPath, pszPath);
}
DebugMsg(DM_TRACE, TEXT("Link_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("Link_SaveToPIF: PifMgr_GetProperties *failed*"));
return E_FAIL;
}
// Set a title based on the link name.
if (psl->pszName && psl->pszName[0])
#ifdef UNICODE
{
WideCharToMultiByte(CP_ACP, 0, psl->pszName, -1,
ProgramProps.achTitle, SIZEOF(ProgramProps.achTitle),
NULL, NULL);
}
#else
lstrcpyn(ProgramProps.achTitle,psl->pszName,SIZEOF(ProgramProps.achTitle));
#endif
//
// if no work dir. is given default to the dir of the app.
//
if (Link_GetWorkingDir(psl, szDir))
{
#ifdef UNICODE
WCHAR szTemp[PIFDEFPATHSIZE];
GetShortPathName(szDir, szTemp, ARRAYSIZE(szTemp));
WideCharToMultiByte(CP_ACP, 0, szTemp, -1,
ProgramProps.achWorkDir, ARRAYSIZE(ProgramProps.achWorkDir),
NULL, NULL);
#else
GetShortPathName(szDir, ProgramProps.achWorkDir, ARRAYSIZE(ProgramProps.achWorkDir));
#endif
}
else if (fPath && !PathIsUNC(achPath))
{
#ifdef UNICODE
WCHAR szTemp[PIFDEFPATHSIZE];
lstrcpyn(szTemp, achPath, ARRAYSIZE(szTemp));
PathRemoveFileSpec(szTemp);
WideCharToMultiByte(CP_ACP, 0, szTemp, -1,
ProgramProps.achWorkDir, ARRAYSIZE(ProgramProps.achWorkDir),
NULL, NULL);
#else
lstrcpyn(ProgramProps.achWorkDir, achPath, ARRAYSIZE(ProgramProps.achWorkDir));
PathRemoveFileSpec(ProgramProps.achWorkDir);
#endif
}
// And for those network share points we need to quote blanks...
PathQuoteSpaces(achPath);
//
// add the args to build the full command line
//
if (psl->pszArgs && psl->pszArgs[0])
{
lstrcat(achPath, c_szSpace);
lstrcat(achPath, psl->pszArgs);
}
if (fPath)
#ifdef UNICODE
{
WideCharToMultiByte(CP_ACP, 0, achPath, -1,
ProgramProps.achCmdLine, ARRAYSIZE(ProgramProps.achCmdLine),
NULL, NULL);
}
#else
lstrcpyn(ProgramProps.achCmdLine, achPath, ARRAYSIZE(ProgramProps.achCmdLine));
#endif
if (psl->sld.iShowCmd == SW_SHOWMAXIMIZED)
ProgramProps.flPrgInit |= PRGINIT_MAXIMIZED;
if ((psl->sld.iShowCmd == SW_SHOWMINIMIZED) || (psl->sld.iShowCmd == SW_SHOWMINNOACTIVE))
ProgramProps.flPrgInit |= PRGINIT_MINIMIZED;
if (psl->sld.wHotkey)
ProgramProps.wHotKey = psl->sld.wHotkey;
if (psl->pszIconLocation && psl->pszIconLocation[0])
{
#ifdef UNICODE
WideCharToMultiByte(CP_ACP, 0, psl->pszIconLocation, -1,
ProgramProps.achIconFile, ARRAYSIZE(ProgramProps.achIconFile),
NULL, NULL);
#else
lstrcpyn(ProgramProps.achIconFile, psl->pszIconLocation, ARRAYSIZE(ProgramProps.achIconFile));
#endif
ProgramProps.wIconIndex = psl->sld.iIcon;
}
if (!PifMgr_SetProperties(hPif, (LPSTR)MAKEINTATOM(GROUP_PRG), &ProgramProps, SIZEOF(ProgramProps), 0))
{
DebugMsg(DM_TRACE, TEXT("Link_SaveToPIF: PifMgr_SetProperties *failed*"));
hres = E_FAIL;
} else {
hres = S_OK;
}
PifMgr_CloseProperties(hPif, 0);
psl->bDirty = FALSE;
return hres;
}
HRESULT Link_LoadFromFile(CShellLink *this, LPCTSTR pszPath)
{
HRESULT hres;
if (PathIsPif(pszPath))
hres = Link_LoadFromPIF(this, pszPath);
else
hres = PSLoadThroughFileCache(&this->ps, pszPath);
if (SUCCEEDED(hres))
{
TCHAR szPath[MAX_PATH];
if (this->pidl && SHGetPathFromIDList(this->pidl, szPath) && !lstrcmpi(szPath, pszPath))
{
DebugMsg(DM_TRACE, TEXT("Link points to itself, aaahhh!"));
hres = E_FAIL;
}
else
{
Str_SetPtr(&this->pszCurFile, pszPath);
}
}
Assert(!this->bDirty);
return hres;
}
HRESULT CShellLink_Load(IPersistFile *psf, LPCOLESTR pwszFile, DWORD grfMode)
{
CShellLink *this = IToClass(CShellLink, pf, psf);
TCHAR szPath[MAX_PATH];
OleStrToStrN(szPath, ARRAYSIZE(szPath), pwszFile, -1);
return Link_LoadFromFile(this, szPath);
}
HRESULT Link_SaveToFile(CShellLink *this, LPTSTR pszPathSave, BOOL fRemember)
{
HRESULT hres = E_FAIL;
BOOL bFileExisted;
BOOL fDosApp;
BOOL fFile;
TCHAR szPathSrc[MAX_PATH];
BOOL fWasSameFile = this->pszCurFile && (lstrcmpi(pszPathSave, this->pszCurFile) == 0);
CShellLink_GetPath(&this->sl, szPathSrc, ARRAYSIZE(szPathSrc), NULL, 0);
fFile = !(this->sld.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
fDosApp = fFile && LOWORD(GetExeType(szPathSrc)) == 0x5A4D;
bFileExisted = PathFileExists(pszPathSave);
//
// handle a link to link case. (or link to pif)
//
// BUGBUG we loose all new attributes, need to look into what
// a progman item to a .PIF file did in Win31. who set the hotkey,
// work dir etc. we definitly loose the icon.
//
if (fFile && (PathIsPif(szPathSrc) || PathIsLink(szPathSrc)))
{
if (RenameChangeExtension(pszPathSave, PathFindExtension(szPathSrc), fWasSameFile))
{
PSUpdateFileCache(NULL, pszPathSave); // flush cache
if (CopyFile(szPathSrc, pszPathSave, FALSE))
{
if (PathIsPif(pszPathSave))
hres = Link_SaveAsPIF(this, pszPathSave, FALSE);
else
hres = S_OK;
}
}
else
{
hres = E_FAIL;
}
}
//
// if the linked to file is a DOS app, we need to write a .PIF file
//
else if (fDosApp)
{
if (RenameChangeExtension(pszPathSave, c_szDotPif, fWasSameFile))
{
hres = Link_SaveAsPIF(this, pszPathSave, TRUE);
}
else
{
hres = E_FAIL;
}
}
//
// else write a link file
//
else
{
if (PathIsPif(pszPathSave))
{
if (!RenameChangeExtension(pszPathSave, c_szDotLnk, fWasSameFile))
{
hres = E_FAIL;
goto Update;
}
}
hres = Link_SaveAsLink(this, pszPathSave);
}
Update:
if (SUCCEEDED(hres))
{
// Knock out file close
SHChangeNotify(bFileExisted ? SHCNE_UPDATEITEM : SHCNE_CREATE, SHCNF_PATH, pszPathSave, NULL);
SHChangeNotify(SHCNE_FREESPACE, SHCNF_PATH, pszPathSave, NULL);
if (fRemember)
Str_SetPtr(&this->pszCurFile, pszPathSave);
}
return hres;
}
HRESULT CShellLink_Save(IPersistFile *psf, LPCOLESTR pwszFile, BOOL fRemember)
{
CShellLink *this = IToClass(CShellLink, pf, psf);
TCHAR szSavePath[MAX_PATH];
if (pwszFile == NULL)
{
if (this->pszCurFile == NULL)
return E_FAIL; // fail
lstrcpy(szSavePath, this->pszCurFile);
}
else
{
OleStrToStrN(szSavePath, ARRAYSIZE(szSavePath), pwszFile, -1);
}
return Link_SaveToFile(this, szSavePath, fRemember);
}
HRESULT CShellLink_SaveCompleted(IPersistFile *psf, LPCOLESTR pwszFile)
{
CShellLink *this = IToClass(CShellLink, pf, psf);
return S_OK;
}
HRESULT CShellLink_GetCurFile(IPersistFile *psf, LPOLESTR *lplpszFileName)
{
CShellLink *this = IToClass(CShellLink, pf, psf);
// actually we have this, but we have to return IMalloc memory...
// implement this when someone actually needs this.
*lplpszFileName = NULL;
return S_OK;
}
#pragma data_seg(".text", "CODE")
IPersistFileVtbl c_PersistFile_Vtbl = {
CShellLink_PF_QueryInterface, CShellLink_PF_AddRef, CShellLink_PF_Release,
CShellLink_GetClassID,
CShellLink_IsDirty,
CShellLink_Load,
CShellLink_Save,
CShellLink_SaveCompleted,
CShellLink_GetCurFile
};
#pragma data_seg()
HRESULT CShellLink_PS_QueryInterface(IPersistStream *pps, REFIID riid, void **ppvObj)
{
CShellLink *this = IToClass(CShellLink, ps, pps);
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
}
ULONG CShellLink_PS_AddRef(IPersistStream *pps)
{
CShellLink *this = IToClass(CShellLink, ps, pps);
return CShellLink_AddRef(&this->sl);
}
ULONG CShellLink_PS_Release(IPersistStream *pps)
{
CShellLink *this = IToClass(CShellLink, ps, pps);
return CShellLink_Release(&this->sl);
}
HRESULT CShellLink_PS_GetClassID(IPersistStream *pps, CLSID *pClassID)
{
CShellLink *this = IToClass(CShellLink, ps, pps);
*pClassID = CLSID_ShellLink;
return S_OK;
}
HRESULT CShellLink_PS_IsDirty(IPersistStream *pps)
{
CShellLink *this = IToClass(CShellLink, ps, pps);
// DebugMsg(DM_TRACE, "ShellLink::IsDirty -> %d", this->bDirty);
return this->bDirty ? S_OK : S_FALSE;
}
HRESULT LinkInfo_LoadFromStream(IStream *pstm, PLINKINFO *ppli)
{
DWORD dwSize;
ULONG cbBytesRead;
HRESULT hres;
if (*ppli)
{
LocalFree((HLOCAL)*ppli);
*ppli = NULL;
}
hres = pstm->lpVtbl->Read(pstm, &dwSize, SIZEOF(dwSize), &cbBytesRead); // size of data
if (SUCCEEDED(hres) && (cbBytesRead == SIZEOF(dwSize)))
{
if (dwSize >= SIZEOF(dwSize)) // must be at least this big
{
/* Yes. Read remainder of LinkInfo into local memory. */
PLINKINFO pli = (void*)LocalAlloc(LPTR, dwSize);
if (pli)
{
*(DWORD *)pli = dwSize; // Copy size
dwSize -= SIZEOF(dwSize); // Read remainder of LinkInfo
hres = pstm->lpVtbl->Read(pstm, ((DWORD *)pli) + 1, dwSize, &cbBytesRead);
if (SUCCEEDED(hres) && (cbBytesRead == dwSize))
*ppli = pli; // LinkInfo read successfully
else
LocalFree((HLOCAL)pli);
}
}
}
return hres;
}
#ifdef TEST_UNICODE_LINKS
#define Stream_WriteString(pstm, psz) Stream_WriteWideString(pstm, psz)
HRESULT Stream_WriteWideString(IStream *pstm, LPCTSTR psz)
{
USHORT cch = lstrlen(psz);
HRESULT hres;
WCHAR wszBuf[MAX_PATH];
StrToOleStr(wszBuf, psz);
hres = pstm->lpVtbl->Write(pstm, &cch, SIZEOF(cch), NULL);
if (SUCCEEDED(hres))
hres = pstm->lpVtbl->Write(pstm, wszBuf, cch * SIZEOF(WCHAR), NULL);
return hres;
}
#endif
#ifdef UNICODE
HRESULT Stream_ReadAnsiString(IStream *pstm, LPSTR pwsz, UINT cchBuf)
{
USHORT cch;
HRESULT hres = pstm->lpVtbl->Read(pstm, &cch, SIZEOF(cch), NULL); // size of data
if (SUCCEEDED(hres))
{
if (cch >= (USHORT)cchBuf)
{
DebugMsg(DM_TRACE, TEXT("truncating string read(%d to %d)"), cch, cchBuf);
cch = (USHORT)cchBuf - 1; // leave room for null terminator
}
hres = pstm->lpVtbl->Read(pstm, pwsz, cch, NULL);
if (SUCCEEDED(hres))
pwsz[cch] = 0; // add NULL terminator
}
return hres;
}
#else
HRESULT Stream_ReadWideString(IStream *pstm, LPWSTR pwsz, UINT cchBuf)
{
USHORT cch;
HRESULT hres;
VDATEINPUTBUF(pwsz, WCHAR, cchBuf);
hres = pstm->lpVtbl->Read(pstm, &cch, SIZEOF(cch), NULL); // size of data
if (SUCCEEDED(hres))
{
if (cch >= (USHORT)cchBuf)
{
DebugMsg(DM_TRACE, TEXT("truncating string read(%d to %d)"), cch, cchBuf);
cch = (USHORT)cchBuf - 1; // leave room for null terminator
}
hres = pstm->lpVtbl->Read(pstm, pwsz, cch * SIZEOF(WCHAR), NULL);
if (SUCCEEDED(hres))
pwsz[cch] = 0; // add NULL terminator
}
return hres;
}
#endif
HRESULT Str_SetFromStream(IStream *pstm, LPTSTR *ppsz, BOOL bWide)
{
TCHAR szBuf[MAX_PATH];
HRESULT hres;
if (bWide)
{
#ifdef UNICODE
hres = Stream_ReadStringBuffer(pstm,szBuf,ARRAYSIZE(szBuf));
#else
WCHAR wszBuf[MAX_PATH];
hres = Stream_ReadWideString(pstm, wszBuf, ARRAYSIZE(wszBuf));
if (SUCCEEDED(hres))
OleStrToStr(szBuf, wszBuf);
#endif
}
else
{
#ifdef UNICODE
CHAR szAnsiBuf[MAX_PATH];
hres = Stream_ReadAnsiString(pstm, szAnsiBuf, ARRAYSIZE(szAnsiBuf));
if (SUCCEEDED(hres))
MultiByteToWideChar(CP_ACP, 0,
szAnsiBuf, -1,
szBuf, ARRAYSIZE(szBuf));
#else
hres = Stream_ReadStringBuffer(pstm, szBuf, ARRAYSIZE(szBuf));
#endif
}
if (SUCCEEDED(hres))
Str_SetPtr(ppsz, szBuf);
return hres;
}
// this is to support future extensions of link files
//
// after all the known link data is read we will read
// in blocks of data of the following format:
// [cbBytes:DWORD] [ cbBytes of data], ...
//
//
void LoadExtraData(CShellLink *this, IStream *pstm)
{
ULONG cbBytes, uTotalSize, uSizeToRead;
DWORD cbSize;
HRESULT hres;
LPBYTE pTemp = NULL;
if (this->pExtraData)
{
LocalFree((HLOCAL)this->pExtraData);
this->pExtraData = NULL;
}
// an empty extra data is null terminated with a 0 DWORD
uTotalSize = 0;
while (TRUE)
{
hres = pstm->lpVtbl->Read(pstm, &cbSize, SIZEOF(cbSize), &cbBytes);
if (SUCCEEDED(hres) && (cbBytes == SIZEOF(cbSize)))
{
LPBYTE pReadData;
if (cbSize < SIZEOF(cbSize))
break;
if (pTemp)
{
pTemp = (LPBYTE)LocalReAlloc((HLOCAL)this->pExtraData,
uTotalSize + cbSize + SIZEOF(DWORD),
LMEM_ZEROINIT | LMEM_MOVEABLE
);
if (pTemp)
{
this->pExtraData = (LPSTR)pTemp;
}
}
else
{
pTemp = this->pExtraData = (void *)LocalAlloc( LPTR, uTotalSize + cbSize + SIZEOF(DWORD) );
}
if (!pTemp)
break;
uSizeToRead = cbSize - SIZEOF(cbSize);
pReadData = pTemp + uTotalSize;
DebugMsg(DM_TRACE, TEXT("Reading extra data size:%d"), cbSize);
hres = pstm->lpVtbl->Read(pstm, pReadData + SIZEOF(cbSize), uSizeToRead, &cbBytes);
if (SUCCEEDED(hres) && (cbBytes == uSizeToRead))
{
// got all of the extra data, comit it
*((UNALIGNED DWORD *)pReadData) = cbSize;
uTotalSize += cbSize;
}
else
break;
}
else
break;
}
}
HRESULT CShellLink_PS_Load(IPersistStream *pps, IStream *pstm)
{
CShellLink *this = IToClass(CShellLink, ps, pps);
ULONG cbBytes;
DWORD cbSize;
HRESULT hres;
Link_ResetPersistData(this); // clear out our state
hres = pstm->lpVtbl->Read(pstm, &cbSize, SIZEOF(cbSize), &cbBytes);
if (SUCCEEDED(hres))
{
if (cbBytes == SIZEOF(cbSize))
{
if (cbSize == SIZEOF(this->sld))
{
hres = pstm->lpVtbl->Read(pstm, (LPBYTE)&this->sld + SIZEOF(cbSize), SIZEOF(this->sld) - SIZEOF(cbSize), &cbBytes);
if (SUCCEEDED(hres) && cbBytes == (SIZEOF(this->sld) - SIZEOF(cbSize)) && IsEqualGUID(&this->sld.clsid, &CLSID_ShellLink))
{
#ifdef ENABLE_TRACK
EXP_TRACKER * lpData;
DWORD dwTrackerSize = 0;
#endif
this->sld.cbSize = SIZEOF(this->sld);
switch (this->sld.iShowCmd) {
case SW_SHOWNORMAL:
case SW_SHOWMINNOACTIVE:
case SW_SHOWMAXIMIZED:
break;
default:
DebugMsg(DM_TRACE, TEXT("Shortcut Load, mapping bogus ShowCmd: %d"), this->sld.iShowCmd);
this->sld.iShowCmd = SW_SHOWNORMAL;
break;
}
// read all of the members
if (this->sld.dwFlags & SLDF_HAS_ID_LIST)
{
hres = ILLoadFromStream(pstm, &this->pidl);
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_FORCE_NO_LINKINFO) && this->pli)
{
DebugMsg( DM_TRACE, TEXT("labotimizing link"));
LocalFree((HLOCAL)this->pli);
this->pli = NULL;
}
}
// BUGBUG: this part is not unicode ready, talk to daviddi
if (SUCCEEDED(hres) && (this->sld.dwFlags & (SLDF_HAS_LINK_INFO)))
{
hres = LinkInfo_LoadFromStream(pstm, &this->pli);
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_FORCE_NO_LINKINFO) && this->pli)
{
DebugMsg(DM_TRACE, TEXT("labotimizing link"));
LocalFree((HLOCAL)this->pli);
this->pli = NULL;
}
}
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_NAME))
hres = Str_SetFromStream(pstm, &this->pszName, this->sld.dwFlags & SLDF_UNICODE);
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_RELPATH))
hres = Str_SetFromStream(pstm, &this->pszRelPath, this->sld.dwFlags & SLDF_UNICODE);
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_WORKINGDIR))
hres = Str_SetFromStream(pstm, &this->pszWorkingDir, this->sld.dwFlags & SLDF_UNICODE);
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_ARGS))
hres = Str_SetFromStream(pstm, &this->pszArgs, this->sld.dwFlags & SLDF_UNICODE);
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_ICONLOCATION))
hres = Str_SetFromStream(pstm, &this->pszIconLocation, this->sld.dwFlags & SLDF_UNICODE);
if (SUCCEEDED(hres))
LoadExtraData(this, pstm);
#ifdef ENABLE_TRACK
if (g_fNewTrack)
{
// load the tracker from extra data
for( lpData = (EXP_TRACKER *)this->pExtraData;
lpData && lpData->cbSize && lpData->dwSignature!=EXP_TRACKER_SIG;
lpData = (EXP_TRACKER *) (((LPBYTE)lpData) + lpData->cbSize)
);
if (lpData && lpData->cbSize) {
if (SUCCEEDED(hres))
hres = Tracker_Load(this->ptracker,
lpData->abTracker,
lpData->cbSize - sizeof(EXP_TRACKER));
}
else
{
DebugMsg(DM_TRACE, TEXT("CShellLink_PS_Load: no EXP_TRACKER data found."));
}
}
#endif
if (SUCCEEDED(hres))
this->bDirty = FALSE;
}
else
{
DebugMsg(DM_TRACE, TEXT("failed to read link struct"));
hres = E_FAIL; // invalid file size
}
}
else
{
DebugMsg(DM_TRACE, TEXT("invalid length field in link:%d"), cbBytes);
hres = E_FAIL; // invalid file size
}
}
else if (cbBytes == 0)
{
// zero length file is ok
this->sld.cbSize = 0;
DebugMsg(DM_TRACE, TEXT("Empty link file, that's cool"));
}
else
{
hres = E_FAIL; // invalid file size
}
}
return hres;
}
HRESULT LinkInfo_SaveToStream(IStream *pstm, PCLINKINFO pcli)
{
DWORD dwSize;
ULONG cbBytes;
HRESULT hres;
dwSize = *(DWORD *)pcli; // Get LinkInfo size
hres = pstm->lpVtbl->Write(pstm, pcli, dwSize, &cbBytes);
if (SUCCEEDED(hres) && (cbBytes != dwSize))
hres = E_FAIL;
return hres;
}
HRESULT Link_SaveExtraData(CShellLink *this, IStream *pstm)
{
LPBYTE pData;
DWORD dwSize;
HRESULT hr = S_OK;
for (pData = (LPBYTE)this->pExtraData; pData; pData += dwSize)
{
ULONG cbBytes;
dwSize = *(UNALIGNED DWORD *)pData;
if (dwSize == 0)
break;
DebugMsg(DM_TRACE, TEXT("Writing extra data block, size:%d"), dwSize);
if (FAILED(hr = pstm->lpVtbl->Write(pstm, pData, dwSize, &cbBytes)))
break;
if (cbBytes != dwSize)
{
hr = STG_E_MEDIUMFULL;
break;
}
}
return(S_OK);
}
// 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 Link_AddExtraDataSection(CShellLink *this, DWORD UNALIGNED * lpData)
{
DWORD dwCurSize = 0;
LPBYTE lpTmp = NULL;
// Add data to the "Extra Data" section of the link.
if (!this->pExtraData) {
// allocation for block being passed in + DWORD marker
this->pExtraData = LocalAlloc( LPTR, SIZEOF( DWORD ) + *lpData );
if (this->pExtraData )
{
CopyMemory( this->pExtraData, lpData, *lpData );
this->bDirty = TRUE;
}
} else {
// find the end and add up the size as we go.
DWORD UNALIGNED * lpEntry;
for ( lpEntry = (DWORD UNALIGNED *)this->pExtraData;
lpEntry && *lpEntry!=0;
dwCurSize += *lpEntry,
lpEntry = (DWORD UNALIGNED *) (((LPBYTE)lpEntry)+ *lpEntry)
);
// size of new xtra data section is sum of all current sections +
// size of new block + sizeof DWORD marker.
lpTmp = (LPBYTE)LocalReAlloc( (HLOCAL)this->pExtraData,
dwCurSize + SIZEOF(DWORD) + *lpData,
LMEM_ZEROINIT | LMEM_MOVEABLE
);
if (lpTmp) {
this->pExtraData = lpTmp;
lpTmp += dwCurSize;
CopyMemory( lpTmp, lpData, *lpData );
*(DWORD UNALIGNED *)(lpTmp + *lpData) = 0;
this->bDirty = TRUE;
}
}
}
// This will remove the extra data section with the given signature.
void Link_RemoveExtraDataSection(CShellLink *this, DWORD dwSig)
{
LPEXP_SZ_LINK lpData = (LPEXP_SZ_LINK)this->pExtraData;
while( lpData && lpData->cbSize!=0 && (lpData->dwSignature!=dwSig) )
{
lpData = (LPEXP_SZ_LINK) (((LPBYTE)lpData) + lpData->cbSize);
}
if (lpData && (lpData->cbSize!=0))
{
LONG lNewSize;
LPVOID lpVoid;
LPEXP_SZ_LINK lpTmp;
DWORD dwSizeOfBlockToRemove;
DWORD dwSize = SIZEOF(DWORD); // There is a DWORD marker at the end
// of the xtra data section
dwSizeOfBlockToRemove = lpData->cbSize;
// Find how much xtra data exists past this section
for( lpTmp=(LPEXP_SZ_LINK)((LPBYTE)lpData + dwSizeOfBlockToRemove);
lpTmp && lpTmp->cbSize!=0;
dwSize += lpTmp->cbSize,
lpTmp = (LPEXP_SZ_LINK)(((LPBYTE)lpTmp) + lpTmp->cbSize)
);
// Move the remaining chunk to cover gap of this section,
// thus removing it
MoveMemory( lpData, (LPBYTE)lpData + lpData->cbSize, dwSize );
lNewSize = LocalSize( this->pExtraData ) - dwSizeOfBlockToRemove;
// Now, resize extra data block as appropriate
if (lNewSize > SIZEOF(DWORD))
{
if (NULL != (lpVoid = LocalReAlloc( (HLOCAL)this->pExtraData, lNewSize, LMEM_ZEROINIT | LMEM_MOVEABLE )))
{
this->pExtraData = lpVoid;
}
}
else
{
// We've removed the last section, delete the whole deal
LocalFree( (HLOCAL)this->pExtraData );
this->pExtraData = NULL;
}
this->bDirty = TRUE;
}
}
// This will return a given extra data section in a link, or NULL if the section
// with the requested signature doesn't exist.
LPVOID Link_ReadExtraDataSection(CShellLink *this, DWORD dwSig)
{
LPEXP_SZ_LINK lpData = (LPEXP_SZ_LINK)this->pExtraData;
LONG dwSize = SIZEOF(DWORD);
LPEXP_SZ_LINK lpTmp;
LPVOID lpSection = NULL;
// walk through data looking for given signature
while( lpData && lpData->cbSize && (lpData->dwSignature!=dwSig) )
{
lpData = (LPEXP_SZ_LINK)(((LPBYTE)lpData) + lpData->cbSize);
}
// if we found the section, create a buffer and copy over the data
if (lpData && lpData->cbSize)
{
lpSection = HeapAlloc( GetProcessHeap(), 0, lpData->cbSize );
if (lpSection)
CopyMemory( lpSection, lpData, lpData->cbSize );
}
return lpSection;
}
#ifdef ENABLE_TRACK
//
// Replaces the tracker extra data with current tracker state
//
HRESULT Link_AddTrackerData(CShellLink *this)
{
/*typedef struct {
DWORD cbSize; // Size of this extra data block
DWORD dwSignature; // signature of this extra data block
BYTE abTracker[ 1 ]; //
} EXP_TRACKER;*/
// get the size of the tracker and get some memory
ULONG ulSize = Tracker_GetSize(this->ptracker);
EXP_TRACKER *pExpTracker = (EXP_TRACKER *)LocalAlloc(LPTR, ulSize + sizeof(EXP_TRACKER));
if (!pExpTracker)
{
return(E_OUTOFMEMORY);
}
Link_RemoveExtraDataSection( this, EXP_TRACKER_SIG );
pExpTracker->cbSize = ulSize + sizeof(EXP_TRACKER);
pExpTracker->dwSignature = EXP_TRACKER_SIG;
Tracker_Save(this->ptracker, pExpTracker->abTracker);
Link_AddExtraDataSection(this, &pExpTracker->cbSize);
DebugMsg(DM_TRACE, TEXT("Link_AddTrackerData: EXP_TRACKER at %08X."), &pExpTracker->cbSize);
}
#endif
HRESULT CShellLink_PS_Save(IPersistStream *pps, IStream *pstm, BOOL fClearDirty)
{
CShellLink *this = IToClass(CShellLink, ps, pps);
ULONG cbBytes;
HRESULT hres;
this->sld.cbSize = SIZEOF(this->sld);
this->sld.clsid = CLSID_ShellLink;
// this->sld.dwFlags = 0;
// We do the following & instead of zeroing because the SLDF_HAS_EXP_SZ and
// SLDF_RUN_IN_SEPARATE are passed to us and are valid, the others can be
// reconstructed below, but these two can not, so we need to preserve it!
// (BUGBUG, this may change when we go to property stream storage for
// the xtra data -- RICKTU).
this->sld.dwFlags &= (SLDF_HAS_EXP_SZ | SLDF_RUN_IN_SEPARATE);
if (this->pszRelSource)
Link_SetRelativePath(this, this->pszRelSource);
#ifdef UNICODE
this->sld.dwFlags |= SLDF_UNICODE;
#endif
if (this->pidl)
this->sld.dwFlags |= SLDF_HAS_ID_LIST;
if (this->pli)
this->sld.dwFlags |= SLDF_HAS_LINK_INFO;
if (this->pszName && this->pszName[0])
this->sld.dwFlags |= SLDF_HAS_NAME;
if (this->pszRelPath && this->pszRelPath[0])
this->sld.dwFlags |= SLDF_HAS_RELPATH;
if (this->pszWorkingDir && this->pszWorkingDir[0])
this->sld.dwFlags |= SLDF_HAS_WORKINGDIR;
if (this->pszArgs && this->pszArgs[0])
this->sld.dwFlags |= SLDF_HAS_ARGS;
if (this->pszIconLocation && this->pszIconLocation[0])
this->sld.dwFlags |= SLDF_HAS_ICONLOCATION;
hres = pstm->lpVtbl->Write(pstm, &this->sld, SIZEOF(this->sld), &cbBytes);
if (SUCCEEDED(hres) && (cbBytes == SIZEOF(this->sld)))
{
if (this->pidl)
hres = ILSaveToStream(pstm, this->pidl);
if (SUCCEEDED(hres) && this->pli)
hres = LinkInfo_SaveToStream(pstm, this->pli);
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_NAME))
hres = Stream_WriteString(pstm, this->pszName);
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_RELPATH))
hres = Stream_WriteString(pstm, this->pszRelPath);
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_WORKINGDIR))
hres = Stream_WriteString(pstm, this->pszWorkingDir);
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_ARGS))
hres = Stream_WriteString(pstm, this->pszArgs);
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_ICONLOCATION))
hres = Stream_WriteString(pstm, this->pszIconLocation);
#ifdef ENABLE_TRACK
if (g_fNewTrack && SUCCEEDED(hres) && Tracker_IsDirty(this->ptracker))
hres = Link_AddTrackerData(this);
#endif
if (SUCCEEDED(hres))
hres = Link_SaveExtraData(this, pstm);
#ifdef TEST_EXTRA_DATA
if (bTestExtra && SUCCEEDED(hres))
{
DWORD dwSize;
// NULL block
dwSize = 4;
pstm->lpVtbl->Write(pstm, &dwSize, SIZEOF(dwSize), NULL);
// string block
dwSize = 6;
pstm->lpVtbl->Write(pstm, &dwSize, SIZEOF(dwSize), NULL);
pstm->lpVtbl->Write(pstm, TEXT("CG"), 2, NULL);
// string block
dwSize = 5;
pstm->lpVtbl->Write(pstm, &dwSize, SIZEOF(dwSize), NULL);
pstm->lpVtbl->Write(pstm, TEXT("X"), 1, NULL);
// string block
dwSize = 10;
pstm->lpVtbl->Write(pstm, &dwSize, SIZEOF(dwSize), NULL);
pstm->lpVtbl->Write(pstm, TEXT("123456"), 6, NULL);
}
#endif // TEST_EXTRA_DATA
if (SUCCEEDED(hres) && fClearDirty)
this->bDirty = FALSE;
}
else
{
DebugMsg(DM_TRACE, TEXT("Failed to write link"));
hres = E_FAIL;
}
return hres;
}
HRESULT CShellLink_GetSizeMax(IPersistStream *pps, ULARGE_INTEGER *pcbSize)
{
// CShellLink *this = IToClass(CShellLink, ps, pps);
pcbSize->LowPart = 16 * 1024; // 16k? who knows...
pcbSize->HighPart = 0;
return S_OK;
}
#pragma data_seg(".text", "CODE")
IPersistStreamVtbl c_PersistStream_Vtbl = {
CShellLink_PS_QueryInterface, CShellLink_PS_AddRef, CShellLink_PS_Release,
CShellLink_PS_GetClassID,
CShellLink_PS_IsDirty,
CShellLink_PS_Load,
CShellLink_PS_Save,
CShellLink_GetSizeMax
};
#pragma data_seg()
#ifdef ENABLE_TRACK
//+--------------------------------------------------
//
// Functions: IUnknown for IShellLinkTracker
//
// These routines simply forward the requests to
// the ShellLink Object.
//
//+--------------------------------------------------
HRESULT CShellLink_SLT_QueryInterface(IShellLinkTracker *pslt, REFIID riid, void **ppvObj)
{
CShellLink *this = IToClass(CShellLink, slt, pslt);
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
}
ULONG CShellLink_SLT_AddRef(IShellLinkTracker *pslt)
{
CShellLink *this = IToClass(CShellLink, slt, pslt);
return CShellLink_AddRef(&this->sl);
}
ULONG CShellLink_SLT_Release(IShellLinkTracker *pslt)
{
CShellLink *this = IToClass(CShellLink, slt, pslt);
return CShellLink_Release(&this->sl);
}
//+------------------------------------------------------------------
//
// Function: CShellLink_SLT_Initialize
//
// This function is a C=>C++ thunk routine.
//
//+------------------------------------------------------------------
HRESULT CShellLink_SLT_Initialize(IShellLinkTracker *pslt, DWORD dwTrackFlags)
{
CShellLink *this = IToClass(CShellLink, slt, pslt);
Tracker_SetCreationFlags( this->ptracker, dwTrackFlags );
return S_OK;
}
//+------------------------------------------------------------------
//
// Function: CShellLink_SLT_GetTrackFlags
//
// This function is a C=>C++ thunk routine.
//
//+------------------------------------------------------------------
HRESULT CShellLink_SLT_GetTrackFlags(IShellLinkTracker *pslt, DWORD *pdwTrackFlags)
{
CShellLink *this = IToClass(CShellLink, slt, pslt);
Tracker_GetCreationFlags( this->ptracker, pdwTrackFlags );
return S_OK;
}
//+------------------------------------------------------------------
//
// Function: CShellLink_SLT_Resolve
//
// Purpose: Provide the implementation for
// IShellLinkTracker::Resolve
//
// Inputs: [IShellLinkTracker*] psl
// - Pointer to the interface
// [HWND] hwnd
// - The parent window (which could be the desktop).
// [DWORD] fFlags
// - Flags from the SLR_FLAGS enumeration.
// [DWORD] dwTrackFlags
// - Restrictions on the link-tracking algorithm (TRACK_* flags).
// [DWORD] dwTickCountDeadline
// - Timeout, WRT GetTickCount(), on the link-tracking.
// 0 indicates infinity.
// [DWORD] dwReserved
// - Reserved for future expansion.
//
// Outputs: [HRESULT]
// - S_OK resolution was successful
// S_FALSE user canceled
//
// Algorithm: Call on Link_Resolve to perform the resolution.
//
// Notes: Check IPersistFile::IsDirty after calling this to see
// if the link info has changed.
//
//+------------------------------------------------------------------
HRESULT CShellLink_SLT_Resolve(IShellLinkTracker *pslt,
HWND hwnd,
DWORD fFlags,
DWORD dwTrackFlags,
DWORD dwTickCountDeadline,
DWORD dwReserved)
{
CShellLink *this = IToClass(CShellLink, slt, pslt);
return ResolveLink( this,
hwnd,
fFlags,
dwTrackFlags,
dwTickCountDeadline,
dwReserved );
} // CShellLink_SLT_Resolve
//+-------------------------------------------------
//
// The IShellLinkTracker VTable
//
//+-------------------------------------------------
#pragma data_seg(".text", "CODE")
IShellLinkTrackerVtbl c_ShellLinkTracker_Vtbl = {
CShellLink_SLT_QueryInterface, CShellLink_SLT_AddRef, CShellLink_SLT_Release,
CShellLink_SLT_Initialize,
CShellLink_SLT_GetTrackFlags,
CShellLink_SLT_Resolve
};
#pragma data_seg()
#endif // ENABLE_TRACK
HRESULT CShellLink_SI_QueryInterface(IShellExtInit *psei, REFIID riid, void **ppvObj)
{
CShellLink *this = IToClass(CShellLink, si, psei);
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
}
ULONG CShellLink_SI_AddRef(IShellExtInit *psei)
{
CShellLink *this = IToClass(CShellLink, si, psei);
return CShellLink_AddRef(&this->sl);
}
ULONG CShellLink_SI_Release(IShellExtInit *psei)
{
CShellLink *this = IToClass(CShellLink, si, psei);
return CShellLink_Release(&this->sl);
}
HRESULT CShellLink_Initialize(IShellExtInit *psei, LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
{
CShellLink *this = IToClass(CShellLink, si, psei);
HRESULT hres;
Assert(this->sld.iShowCmd == SW_SHOWNORMAL);
if (pdtobj)
{
STGMEDIUM medium;
FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
hres = pdtobj->lpVtbl->GetData(pdtobj, &fmte, &medium);
if (SUCCEEDED(hres))
{
TCHAR szPath[MAX_PATH];
DragQueryFile(medium.hGlobal, 0, szPath, ARRAYSIZE(szPath));
hres = Link_LoadFromFile(this, szPath);
SHReleaseStgMedium(&medium);
}
}
else
hres = E_FAIL;
return hres;
}
#pragma data_seg(".text", "CODE")
IShellExtInitVtbl c_ShellExtInit_Vtbl = {
CShellLink_SI_QueryInterface, CShellLink_SI_AddRef, CShellLink_SI_Release,
CShellLink_Initialize,
};
#pragma data_seg()
HRESULT CShellLink_CM_QueryInterface(IContextMenu2 *pcm, REFIID riid, void **ppvObj)
{
CShellLink *this = IToClass(CShellLink, cm, pcm);
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
}
ULONG CShellLink_CM_AddRef(IContextMenu2 *pcm)
{
CShellLink *this = IToClass(CShellLink, cm, pcm);
return CShellLink_AddRef(&this->sl);
}
ULONG CShellLink_CM_Release(IContextMenu2 *pcm)
{
CShellLink *this = IToClass(CShellLink, cm, pcm);
return CShellLink_Release(&this->sl);
}
HRESULT CShellLink_QueryContextMenu(IContextMenu2 *pcm, HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
CShellLink *this = IToClass(CShellLink, cm, pcm);
if (this->pcmTarget == NULL)
{
HRESULT hres = Link_GetUIObject(this, NULL, &IID_IContextMenu, &this->pcmTarget);
if (FAILED(hres))
return hres;
Assert(this->pcmTarget);
}
// save these if in case we need to rebuild the cm because the resolve change the
// target of the link
this->indexMenuSave = indexMenu;
this->idCmdFirstSave = idCmdFirst;
this->idCmdLastSave = idCmdLast;
this->uFlagsSave = uFlags;
return this->pcmTarget->lpVtbl->QueryContextMenu(this->pcmTarget, hmenu, indexMenu, idCmdFirst, idCmdLast, uFlags | CMF_VERBSONLY);
}
// BUGBUG: CANONICAL_VERB_NAME fixes some bugs where the implementaton of the
// context menu changes when the shortcut becomes dirty. but causes some
// others where the targtets don't implement cananonical verbs... so we leave
// this turned off for win95. we should fix this later.
HRESULT CShellLink_InvokeCommandAsync(IContextMenu2 *pcm, LPCMINVOKECOMMANDINFO pici)
{
CShellLink *this = IToClass(CShellLink, cm, pcm);
HRESULT hres;
#ifdef CANONICAL_VERB_NAME
TCHAR szVerb[32];
#endif
TCHAR szWorkingDir[MAX_PATH];
LPTSTR lpDirectory = NULL;
#ifdef UNICODE
CHAR szWorkingDirAnsi[MAX_PATH];
#endif
if (this->pcmTarget == NULL)
return E_FAIL;
#ifdef CANONICAL_VERB_NAME
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 the only case where this happens
// sepcifically we resolve from a D:\ -> \\SERVER\SHARE
if (HIWORD(pici->lpVerb) == 0 && (this->sld.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
this->pcmTarget->lpVtbl->GetCommandString(this->pcmTarget, LOWORD(pici->lpVerb), GCS_VERB, NULL, szVerb, ARRAYSIZE(szVerb));
#endif
Assert(!this->bDirty);
// BUGBUG: check for NOUI flag in pici
hres = CShellLink_Resolve(&this->sl, pici->hwnd, 0);
if (hres == S_OK)
{
if (this->bDirty)
{
// the context menu we have for this link is out of date, free it
this->pcmTarget->lpVtbl->Release(this->pcmTarget);
this->pcmTarget = NULL;
hres = Link_GetUIObject(this, NULL, &IID_IContextMenu, &this->pcmTarget);
if (SUCCEEDED(hres))
{
HMENU hmenu = CreatePopupMenu();
if (hmenu)
{
DebugMsg(DM_TRACE, TEXT("rebuilding cm after link resolve"));
hres = this->pcmTarget->lpVtbl->QueryContextMenu(this->pcmTarget,
hmenu, this->indexMenuSave, this->idCmdFirstSave, this->idCmdLastSave, this->uFlagsSave | CMF_VERBSONLY);
DestroyMenu(hmenu);
}
}
// don't really care if this fails...
CShellLink_Save(&this->pf, NULL, TRUE);
}
#ifdef CANONICAL_VERB_NAME
else
{
szVerb[0] = 0;
Assert(SUCCEEDED(hres));
}
#endif
if (SUCCEEDED(hres))
{
TCHAR szArgs[MAX_PATH];
TCHAR szExpArgs[MAX_PATH];
TCHAR szTitle[MAX_PATH];
CMINVOKECOMMANDINFOEX ici;
#ifdef UNICODE
CHAR szArgsAnsi[MAX_PATH];
#endif
if (pici->cbSize > SIZEOF(CMINVOKECOMMANDINFOEX))
{
hmemcpy(&ici,pici,SIZEOF(CMINVOKECOMMANDINFOEX));
// BUGBUG - We should probably alloc another use it to retain size
ici.cbSize = SIZEOF(CMINVOKECOMMANDINFOEX);
}
else
{
memset(&ici,0,SIZEOF(ici));
hmemcpy(&ici,pici,pici->cbSize);
ici.cbSize = SIZEOF(ici);
}
#ifdef CANONICAL_VERB_NAME
if (szVerb[0])
{
DebugMsg(DM_TRACE, TEXT("Mapping cmd %d to verb %s"), LOWORD(pici->lpVerb), szVerb);
ici.lpVerb = szVerb;
}
#endif
// build the args from those passed in cated on the end of the the link args
lstrcpyn(szArgs, this->pszArgs ? this->pszArgs : c_szNULL, ARRAYSIZE(szArgs));
if (ici.lpParameters)
{
int nArgLen = lstrlen(szArgs);
LPCTSTR lpParameters;
#ifdef UNICODE
WCHAR szParameters[MAX_PATH];
if (ici.cbSize < SIZEOF(CMINVOKECOMMANDINFOEX)
|| (ici.fMask & CMIC_MASK_UNICODE) != CMIC_MASK_UNICODE)
{
MultiByteToWideChar(CP_ACP, 0,
ici.lpParameters, -1,
szParameters, ARRAYSIZE(szParameters));
lpParameters = szParameters;
}
else
{
lpParameters = ici.lpParametersW;
}
#else
lpParameters = ici.lpParameters;
#endif
lstrcpyn(szArgs + nArgLen, c_szSpace, ARRAYSIZE(szArgs) - nArgLen - 1);
lstrcpyn(szArgs + nArgLen + 1, lpParameters, ARRAYSIZE(szArgs) - nArgLen - 2);
}
// Expand environment strings in szArgs
ExpandEnvironmentStrings( szArgs,
szExpArgs,
ARRAYSIZE(szExpArgs)
);
szExpArgs[ARRAYSIZE(szExpArgs)-1] = TEXT('\0');
#ifdef UNICODE
WideCharToMultiByte(CP_ACP, 0,
szExpArgs, -1,
szArgsAnsi, ARRAYSIZE(szArgsAnsi),
NULL, NULL);
ici.lpParameters = szArgsAnsi;
ici.lpParametersW = szExpArgs;
ici.fMask |= CMIC_MASK_UNICODE;
#else
ici.lpParameters = szExpArgs;
#endif
// if we have a working dir in the link over ride what is passed in
if (Link_GetWorkingDir(this, szWorkingDir))
{
if (PathIsDirectory(szWorkingDir))
lpDirectory = szWorkingDir;
#ifdef MYDIR_LIVES
// We need to test for
else if (lstrcmpi (szWorkingDir, c_szMyDirTag) == 0)
{
// we need to substitute in the current setting
// for the personal folder. Note if this is UNC this
// will only work for 32 bit applications. We may want
// to check this and possibly substitute a drive in
// for the user...
if (SHGetSpecialFolderPath(NULL, szWorkingDir, CSIDL_PERSONAL, TRUE))
{
if (PathIsDirectory(szWorkingDir))
lpDirectory = szWorkingDir;
}
}
#endif
if ( lpDirectory )
{
#ifdef UNICODE
WideCharToMultiByte(CP_ACP, 0,
lpDirectory, -1,
szWorkingDirAnsi, ARRAYSIZE(szWorkingDirAnsi),
NULL, NULL );
ici.lpDirectory = szWorkingDirAnsi;
ici.lpDirectoryW = lpDirectory;
#else
ici.lpDirectory = lpDirectory;
#endif
}
}
#ifdef WINNT
// set RUN IN SEPARATE VDM if needed
if (this->sld.dwFlags & SLDF_RUN_IN_SEPARATE)
{
ici.fMask |= CMIC_MASK_FLAG_SEP_VDM;
}
#endif
// and of course use our hotkey
if (this->sld.wHotkey)
{
ici.dwHotKey = this->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 = this->sld.iShowCmd;
}
#ifdef WINNT
//
// On NT we want to pass the title to the
// thing that we are about to start.
//
if (!(ici.fMask & CMIC_MASK_HASLINKNAME) && !(ici.fMask & CMIC_MASK_HASTITLE))
{
if (this->pszCurFile)
{
lstrcpyn(szTitle,PathFindFileName(this->pszCurFile),ARRAYSIZE(szTitle));
PathRemoveExtension(szTitle);
#ifdef UNICODE
ici.lpTitle = NULL; // Title is one or the other...
ici.lpTitleW = this->pszCurFile;
#else
ici.lpTitle = this->pszCurFile;
#endif
ici.fMask |= CMIC_MASK_HASLINKNAME;
}
}
#endif
Assert((ici.nShow > SW_HIDE) && (ici.nShow <= SW_MAX));
hres = this->pcmTarget->lpVtbl->InvokeCommand(this->pcmTarget,
(LPCMINVOKECOMMANDINFO)&ici);
}
}
return hres;
}
// Structure which encapsulates the paramters needed for InvokeCommand (so
// that we can pass both parameters though a single LPARAM in CreateThread)
typedef struct
{
IContextMenu2 * pcm;
CMINVOKECOMMANDINFOEX ici;
} ICMPARAMS;
#define ICM_BASE_SIZE (SIZEOF(ICMPARAMS) - SIZEOF(CMINVOKECOMMANDINFOEX))
// CShellLink_InvokeCommandWorker
//
// Runs as a separate thread, does the actual work of calling the "real"
// InvokeCommand
DWORD CShellLink_InvokeCommandWorker(LPVOID pVoid)
{
HRESULT hr;
ICMPARAMS * pParams = (ICMPARAMS *) pVoid;
hr = CShellLink_InvokeCommandAsync(pParams->pcm, (LPCMINVOKECOMMANDINFO) &pParams->ici);
pParams->pcm->lpVtbl->Release(pParams->pcm);
LocalFree(pParams);
return (DWORD) hr;
}
// CShellLink_InvokeCommand
//
// Function that spins a thread to do the real work, which has been moved into
// CShellLink_InvokeCommandSync.
HRESULT CShellLink_InvokeCommand(IContextMenu2 *pcm, LPCMINVOKECOMMANDINFO piciIn)
{
HRESULT hr = S_OK;
HANDLE hThread;
DWORD dwID;
DWORD cbSize;
DWORD cbBaseSize;
ICMPARAMS * pParams;
CHAR * pPos;
DWORD cchVerb,
cchParameters,
cchDirectory,
cchVerbW, // Last 3 are unused ifndef Unicode
cchParametersW,
cchDirectoryW;
LPCMINVOKECOMMANDINFOEX pici = (LPCMINVOKECOMMANDINFOEX) piciIn;
#ifdef UNICODE
WCHAR * pPosW;
const BOOL fUnicode = pici->cbSize >= SIZEOF(CMINVOKECOMMANDINFOEX) &&
(pici->fMask & CMIC_MASK_UNICODE) == CMIC_MASK_UNICODE;
#endif
if (0 == (piciIn->fMask & CMIC_MASK_ASYNCOK))
{
// Caller didn't indicate that Async startup was OK, so we call
// InvokeCommandAync SYNCHRONOUSLY
return CShellLink_InvokeCommandAsync(pcm, piciIn);
}
// Calc how much space we will need to duplicate the INVOKECOMMANDINFO
cbBaseSize = ICM_BASE_SIZE + max(piciIn->cbSize, sizeof(CMINVOKECOMMANDINFOEX));
// One byte slack in case of Unicode roundup for pPosW, below
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);
#ifdef UNICODE
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);
#endif
pParams = (ICMPARAMS *) LocalAlloc(LPTR, cbSize);
if (NULL == pParams)
{
return (hr = E_OUTOFMEMORY);
}
// Text data will start going in right after the structure
pPos = (CHAR *)((LPBYTE)pParams + cbBaseSize);
// Start with a copy of the static fields
CopyMemory(&pParams->ici, pici, pici->cbSize);
pcm->lpVtbl->AddRef(pcm);
pParams->pcm = pcm;
// 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;
#ifdef UNICODE
pPosW = (WCHAR *) ((DWORD)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;
#endif
// Pass all of the info off to the worker thread that will call the actual
// InvokeCommand API for us
hThread = CreateThread(NULL, 0, CShellLink_InvokeCommandWorker, (LPVOID) pParams, 0, &dwID);
if (NULL == hThread)
{
// Couldn't start the thread, so the onus is on us to clean up
pParams->pcm->lpVtbl->Release(pParams->pcm);
LocalFree(pParams);
hr = E_OUTOFMEMORY;
}
else
{
DECLAREWAITCURSOR;
SetWaitCursor();
// We give the async thread a little time to complete, during which we
// put up the busy cursor. This is solely to let the user see that
// some work is being done...
#define ASYNC_CMIC_TIMEOUT 750
if (WAIT_OBJECT_0 == WaitForSingleObject(hThread, ASYNC_CMIC_TIMEOUT))
{
// For consistency, we always return S_OK, but if you wanted to, you could
// return the actual return value from InvokeCommand in those cases where
// it completed in time like this:
//
// DWORD dwRetVal;
// if (GetExitCodeThread(hThread, &dwRetVal))
// {
// hr = (HRESULT) dwRetVal;
// }
}
CloseHandle(hThread);
ResetWaitCursor();
}
return hr;
}
HRESULT CShellLink_GetCommandString(IContextMenu2 *pcm, UINT idCmd, UINT wFlags, UINT *pmf, LPSTR pszName, UINT cchMax)
{
CShellLink *this = IToClass(CShellLink, cm, pcm);
VDATEINPUTBUF(pszName, TCHAR, cchMax);
if (this->pcmTarget)
return this->pcmTarget->lpVtbl->GetCommandString(this->pcmTarget, idCmd, wFlags, pmf, pszName, cchMax);
else
return E_FAIL;
}
HRESULT CShellLink_HandleMenuMsg(IContextMenu2 *pcm, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CShellLink *this = IToClass(CShellLink, cm, pcm);
IContextMenu2 *pcm2;
if (this->pcmTarget && SUCCEEDED(this->pcmTarget->lpVtbl->QueryInterface(this->pcmTarget, &IID_IContextMenu2, &pcm2)))
{
HRESULT hres = pcm2->lpVtbl->HandleMenuMsg(pcm2, uMsg, wParam, lParam);
pcm2->lpVtbl->Release(pcm2);
return hres;
}
return S_OK;
}
#pragma data_seg(".text", "CODE")
IContextMenu2Vtbl c_ContextMenu_Vtbl = {
CShellLink_CM_QueryInterface, CShellLink_CM_AddRef, CShellLink_CM_Release,
CShellLink_QueryContextMenu,
CShellLink_InvokeCommand,
CShellLink_GetCommandString,
CShellLink_HandleMenuMsg,
};
#pragma data_seg()
STDMETHODIMP CShellLink_DT_QueryInterface(IDropTarget *pdropt, REFIID riid, void **ppvObj)
{
CShellLink* this = IToClass(CShellLink, dt, pdropt);
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
}
STDMETHODIMP_(ULONG) CShellLink_DT_AddRef(IDropTarget *pdropt)
{
CShellLink* this = IToClass(CShellLink, dt, pdropt);
return CShellLink_AddRef(&this->sl);
}
STDMETHODIMP_(ULONG) CShellLink_DT_Release(IDropTarget *pdropt)
{
CShellLink* this = IToClass(CShellLink, dt, pdropt);
return CShellLink_Release(&this->sl);
}
STDMETHODIMP CShellLink_DragEnter(IDropTarget *pdropt, IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
CShellLink* this = IToClass(CShellLink, dt, pdropt);
Assert(this->pdtSrc);
this->grfKeyStateLast = grfKeyState;
return this->pdtSrc->lpVtbl->DragEnter(this->pdtSrc, pDataObj, grfKeyState, pt, pdwEffect);
}
STDMETHODIMP CShellLink_DragOver(IDropTarget *pdropt, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
CShellLink* this = IToClass(CShellLink, dt, pdropt);
Assert(this->pdtSrc);
if (!this->pdtSrc)
return E_UNEXPECTED;
this->grfKeyStateLast = grfKeyState;
return this->pdtSrc->lpVtbl->DragOver(this->pdtSrc, grfKeyState, pt, pdwEffect);
}
STDMETHODIMP CShellLink_DragLeave(IDropTarget *pdropt)
{
CShellLink* this = IToClass(CShellLink, dt, pdropt);
Assert(this->pdtSrc);
if (!this->pdtSrc)
return E_UNEXPECTED;
return this->pdtSrc->lpVtbl->DragLeave(this->pdtSrc);
}
extern HWND HKGetSetUIOwner(HWND hwndOwner, BOOL fSet);
STDMETHODIMP CShellLink_Drop(IDropTarget *pdropt, IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
CShellLink* this = IToClass(CShellLink, dt, pdropt);
HRESULT hres;
HWND hwndOwner = HKGetSetUIOwner(NULL, FALSE); // HACK
Assert(this->pdtSrc);
if (!this->pdtSrc)
return E_UNEXPECTED;
//
// We should leave from the un-resolved drop target.
//
this->pdtSrc->lpVtbl->DragLeave(this->pdtSrc);
hres = CShellLink_Resolve(&this->sl, hwndOwner, 0);
if (hres == S_OK)
{
IDropTarget *pdtSrcResolved;
hres = Link_GetUIObject(this, hwndOwner, &IID_IDropTarget, (LPVOID*)&pdtSrcResolved);
if (SUCCEEDED(hres))
{
DWORD dwEffectOrg = *pdwEffect;
pdtSrcResolved->lpVtbl->DragEnter(pdtSrcResolved, pDataObj, this->grfKeyStateLast, pt, pdwEffect);
*pdwEffect = dwEffectOrg; // restore it
hres = pdtSrcResolved->lpVtbl->DragOver(pdtSrcResolved, this->grfKeyStateLast, pt, pdwEffect);
if (SUCCEEDED(hres) && *pdwEffect)
{
*pdwEffect = dwEffectOrg; // restore it
hres = pdtSrcResolved->lpVtbl->Drop(pdtSrcResolved, pDataObj, grfKeyState, pt, pdwEffect);
}
else
{
hres = pdtSrcResolved->lpVtbl->DragLeave(pdtSrcResolved);
}
pdtSrcResolved->lpVtbl->Release(pdtSrcResolved);
}
}
else if (FAILED(hres) && (hres != HRESULT_FROM_WIN32(ERROR_CANCELLED)))
{
//
// We can safely assume that CShellLink_Resolve never fails
// on non-file system objects.
//
TCHAR szLinkSrc[MAX_PATH];
if (SHGetPathFromIDList(this->pidl, szLinkSrc))
{
ShellMessageBox(HINST_THISDLL,
hwndOwner,
MAKEINTRESOURCE(IDS_ENUMERR_PATHNOTFOUND),
MAKEINTRESOURCE(IDS_LINKERROR),
MB_OK | MB_ICONEXCLAMATION, NULL, szLinkSrc);
}
}
return hres;
}
#pragma data_seg(DATASEG_READONLY)
IDropTargetVtbl c_DropTarget_Vtbl =
{
CShellLink_DT_QueryInterface, CShellLink_DT_AddRef, CShellLink_DT_Release,
CShellLink_DragEnter,
CShellLink_DragOver,
CShellLink_DragLeave,
CShellLink_Drop,
};
#pragma data_seg()
HRESULT CShellLink_GetDropTarget(CShellLink *this, IDropTarget **ppdt)
{
IDropTarget *pdtSrc;
HRESULT hres;
*ppdt = NULL;
hres = Link_GetUIObject(this, NULL, &IID_IDropTarget, (LPVOID*)&pdtSrc);
if (SUCCEEDED(hres))
{
if (this->pdtSrc)
this->pdtSrc->lpVtbl->Release(pdtSrc);
this->pdtSrc = pdtSrc;
*ppdt = &this->dt;
CShellLink_AddRef(&this->sl);
}
return hres;
}