windows-nt/Source/XPSP1/NT/shell/shell32/datautil.cpp
2020-09-26 16:20:57 +08:00

547 lines
17 KiB
C++

#include "shellprv.h"
#include "datautil.h"
#include "idlcomm.h"
STDAPI DataObj_SetDropTarget(IDataObject *pdtobj, const CLSID *pclsid)
{
return DataObj_SetBlob(pdtobj, g_cfTargetCLSID, pclsid, sizeof(*pclsid));
}
STDAPI DataObj_GetDropTarget(IDataObject *pdtobj, CLSID *pclsid)
{
return DataObj_GetBlob(pdtobj, g_cfTargetCLSID, pclsid, sizeof(*pclsid));
}
STDAPI_(UINT) DataObj_GetHIDACount(IDataObject *pdtobj)
{
STGMEDIUM medium = {0};
LPIDA pida = DataObj_GetHIDA(pdtobj, &medium);
if (pida)
{
UINT count = pida->cidl;
ASSERT(pida->cidl == HIDA_GetCount(medium.hGlobal));
HIDA_ReleaseStgMedium(pida, &medium);
return count;
}
return 0;
}
// PERFPERF
// This routine used to copy 512 bytes at a time, but that had a major negative perf impact.
// I have measured a 2-3x speedup in copy times by increasing this buffer size to 16k.
// Yes, its a lot of stack, but it is memory well spent. -saml
#define STREAM_COPY_BUF_SIZE 16384
#define STREAM_PROGRESS_INTERVAL (100*1024/STREAM_COPY_BUF_SIZE) // display progress after this many blocks
HRESULT StreamCopyWithProgress(IStream *pstmFrom, IStream *pstmTo, ULARGE_INTEGER cb, PROGRESSINFO * ppi)
{
BYTE buf[STREAM_COPY_BUF_SIZE];
ULONG cbRead;
HRESULT hr = S_OK;
ULARGE_INTEGER uliNewCompleted;
DWORD dwLastTickCount = 0;
if (ppi)
{
uliNewCompleted.QuadPart = ppi->uliBytesCompleted.QuadPart;
}
while (cb.QuadPart)
{
if (ppi && ppi->ppd)
{
DWORD dwTickCount = GetTickCount();
if ((dwTickCount - dwLastTickCount) > 1000)
{
EVAL(SUCCEEDED(ppi->ppd->SetProgress64(uliNewCompleted.QuadPart, ppi->uliBytesTotal.QuadPart)));
if (ppi->ppd->HasUserCancelled())
{
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
break;
}
dwLastTickCount = dwTickCount;
}
}
hr = pstmFrom->Read(buf, min(cb.LowPart, sizeof(buf)), &cbRead);
if (FAILED(hr) || (cbRead == 0))
{
// sometimes we are just done.
if (SUCCEEDED(hr))
hr = S_OK;
break;
}
if (ppi)
{
uliNewCompleted.QuadPart += (ULONGLONG) cbRead;
}
cb.QuadPart -= cbRead;
hr = pstmTo->Write(buf, cbRead, &cbRead);
if (FAILED(hr) || (cbRead == 0))
break;
}
return hr;
}
//
// APP COMPAT! Prior versions of the shell used IStream::CopyTo to copy
// the stream. New versions of the shell use IStream::Read to copy the
// stream so we can put up progress UI. WebFerret 3.0000 implements both
// IStream::Read and IStream::CopyTo, but their implementation of
// IStream::Read hangs the system. So we need to sniff at the data object
// and stream to see if it is WebFerret.
//
// WebFerret doesn't implement IPersist (so IPersist::GetClassID won't
// help) and they don't fill in the CLSID in the FILEDESCRIPTOR
// and it's an out-of-proc data object, so we have to go completely
// on circumstantial evidence.
//
STDAPI_(BOOL) IUnknown_SupportsInterface(IUnknown *punk, REFIID riid)
{
IUnknown *punkOut;
if (SUCCEEDED(punk->QueryInterface(riid, (void **)&punkOut)))
{
punkOut->Release();
return TRUE;
}
return FALSE;
}
STDAPI_(BOOL) DataObj_ShouldCopyWithProgress(IDataObject *pdtobj, IStream *pstm, PROGRESSINFO * ppi)
{
//
// Optimization: If there is no progress info, then don't waste your
// time with progress UI.
//
if (!ppi) return FALSE;
//
// How to detect a WebFerret IDataObject:
//
// The filegroup descriptor gives all objects as size zero.
// (Check this first since it is cheap and usually false)
// WebFerret app is running (look for their tooltip window).
// Their IDataObject doesn't support anything other than IUnknown
// (so we use IID_IAsyncOperation to detect shell data objects
// and IPersist to allow ISVs to override).
// Their IStream doesn't support IStream::Stat.
//
STATSTG stat;
if (ppi->uliBytesTotal.QuadPart == 0 &&
FindWindow(TEXT("VslToolTipWindow"), NULL) &&
!IUnknown_SupportsInterface(pdtobj, IID_IAsyncOperation) &&
!IUnknown_SupportsInterface(pdtobj, IID_IPersist) &&
pstm->Stat(&stat, STATFLAG_NONAME) == E_NOTIMPL)
{
return FALSE; // WebFerret!
}
// All test passed; go ahead and copy with progress UI
return TRUE;
}
STDAPI DataObj_SaveToFile(IDataObject *pdtobj, UINT cf, LONG lindex, LPCTSTR pszFile, FILEDESCRIPTOR *pfd, PROGRESSINFO * ppi)
{
STGMEDIUM medium = {0};
FORMATETC fmte;
HRESULT hr;
fmte.cfFormat = (CLIPFORMAT) cf;
fmte.ptd = NULL;
fmte.dwAspect = DVASPECT_CONTENT;
fmte.lindex = lindex;
fmte.tymed = TYMED_HGLOBAL | TYMED_ISTREAM | TYMED_ISTORAGE;
hr = pdtobj->GetData(&fmte, &medium);
if (SUCCEEDED(hr))
{
//
// if the destination file is system or read-only,
// clear those bits out so we can write anew.
//
DWORD dwTargetFileAttributes = GetFileAttributes(pszFile);
if (dwTargetFileAttributes != -1)
{
if (dwTargetFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY))
{
SetFileAttributes(pszFile, dwTargetFileAttributes & ~(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY));
}
}
DWORD dwSrcFileAttributes = 0;
if (pfd->dwFlags & FD_ATTRIBUTES)
{
// store the rest of the attributes if passed...
dwSrcFileAttributes = (pfd->dwFileAttributes & ~FILE_ATTRIBUTE_DIRECTORY);
}
switch (medium.tymed) {
case TYMED_HGLOBAL:
{
HANDLE hfile = CreateFile(pszFile, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, dwSrcFileAttributes, NULL);
if (hfile != INVALID_HANDLE_VALUE)
{
DWORD dwWrite;
// NTRAID89561-2000/02/25-raymondc: what about writes greater than 4 GB?
if (!WriteFile(hfile, GlobalLock(medium.hGlobal), (pfd->dwFlags & FD_FILESIZE) ? pfd->nFileSizeLow : (DWORD) GlobalSize(medium.hGlobal), &dwWrite, NULL))
hr = HRESULT_FROM_WIN32(GetLastError());
GlobalUnlock(medium.hGlobal);
if (pfd->dwFlags & (FD_CREATETIME | FD_ACCESSTIME | FD_WRITESTIME))
{
SetFileTime(hfile,
pfd->dwFlags & FD_CREATETIME ? &pfd->ftCreationTime : NULL,
pfd->dwFlags & FD_ACCESSTIME ? &pfd->ftLastAccessTime : NULL,
pfd->dwFlags & FD_WRITESTIME ? &pfd->ftLastWriteTime : NULL);
}
CloseHandle(hfile);
if (FAILED(hr))
EVAL(DeleteFile(pszFile));
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
break;
}
case TYMED_ISTREAM:
{
IStream *pstm;
hr = SHCreateStreamOnFile(pszFile, STGM_CREATE | STGM_WRITE | STGM_SHARE_DENY_WRITE, &pstm);
if (SUCCEEDED(hr))
{
//
// Per the SDK, IDataObject::GetData leaves the stream ptr at
// the end of the data in the stream. To copy the stream we
// first must reposition the stream ptr to the begining.
// We restore the stream ptr to it's original location when we're done.
//
// NOTE: In case the source stream doesn't support Seek(),
// attempt the copy even if the seek operation fails.
//
const LARGE_INTEGER ofsBegin = {0, 0};
ULARGE_INTEGER ofsOriginal = {0, 0};
HRESULT hrSeek = medium.pstm->Seek(ofsBegin, STREAM_SEEK_CUR, &ofsOriginal);
if (SUCCEEDED(hrSeek))
{
hrSeek = medium.pstm->Seek(ofsBegin, STREAM_SEEK_SET, NULL);
}
const ULARGE_INTEGER ul = {(UINT)-1, (UINT)-1}; // the whole thing
if (DataObj_ShouldCopyWithProgress(pdtobj, medium.pstm, ppi))
{
hr = StreamCopyWithProgress(medium.pstm, pstm, ul, ppi);
}
else
{
hr = medium.pstm->CopyTo(pstm, ul, NULL, NULL);
}
if (SUCCEEDED(hrSeek))
{
//
// Restore stream ptr in source to it's original location.
//
const LARGE_INTEGER ofs = { ofsOriginal.LowPart, (LONG)ofsOriginal.HighPart };
medium.pstm->Seek(ofs, STREAM_SEEK_SET, NULL);
}
pstm->Release();
if (FAILED(hr))
EVAL(DeleteFile(pszFile));
DebugMsg(TF_FSTREE, TEXT("IStream::CopyTo() -> %x"), hr);
}
break;
}
case TYMED_ISTORAGE:
{
WCHAR wszNewFile[MAX_PATH];
IStorage *pstg;
DebugMsg(TF_FSTREE, TEXT("got IStorage"));
SHTCharToUnicode(pszFile, wszNewFile, ARRAYSIZE(wszNewFile));
hr = StgCreateDocfile(wszNewFile,
STGM_DIRECT | STGM_READWRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE,
0, &pstg);
if (SUCCEEDED(hr))
{
hr = medium.pstg->CopyTo(0, NULL, NULL, pstg);
DebugMsg(TF_FSTREE, TEXT("IStorage::CopyTo() -> %x"), hr);
pstg->Commit(STGC_OVERWRITE);
pstg->Release();
if (FAILED(hr))
EVAL(DeleteFile(pszFile));
}
}
break;
default:
AssertMsg(FALSE, TEXT("got tymed that I didn't ask for %d"), medium.tymed);
}
if (SUCCEEDED(hr))
{
// in the HGLOBAL case we could take some shortcuts, so the attributes and
// file times were set earlier in the case statement.
// otherwise, we need to set the file times and attributes now.
if (medium.tymed != TYMED_HGLOBAL)
{
if (pfd->dwFlags & (FD_CREATETIME | FD_ACCESSTIME | FD_WRITESTIME))
{
// open with GENERIC_WRITE to let us set the file times,
// everybody else can open with SHARE_READ.
HANDLE hFile = CreateFile(pszFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
SetFileTime(hFile,
pfd->dwFlags & FD_CREATETIME ? &pfd->ftCreationTime : NULL,
pfd->dwFlags & FD_ACCESSTIME ? &pfd->ftLastAccessTime : NULL,
pfd->dwFlags & FD_WRITESTIME ? &pfd->ftLastWriteTime : NULL);
CloseHandle(hFile);
}
}
if (dwSrcFileAttributes)
{
SetFileAttributes(pszFile, dwSrcFileAttributes);
}
}
SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, pszFile, NULL);
SHChangeNotify(SHCNE_FREESPACE, SHCNF_PATH, pszFile, NULL);
}
ReleaseStgMedium(&medium);
}
return hr;
}
STDAPI DataObj_GetShellURL(IDataObject *pdtobj, STGMEDIUM *pmedium, LPCSTR *ppszURL)
{
FORMATETC fmte = {g_cfShellURL, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
HRESULT hr;
if (pmedium)
{
hr = pdtobj->GetData(&fmte, pmedium);
if (SUCCEEDED(hr))
*ppszURL = (LPCSTR)GlobalLock(pmedium->hGlobal);
}
else
hr = pdtobj->QueryGetData(&fmte); // query only
return hr;
}
STDAPI DataObj_GetOFFSETs(IDataObject *pdtobj, POINT *ppt)
{
STGMEDIUM medium = {0};
IDLData_InitializeClipboardFormats( );
ASSERT(g_cfOFFSETS);
FORMATETC fmt = {g_cfOFFSETS, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
ASSERT(ppt);
ppt->x = ppt->y = 0;
HRESULT hr = pdtobj->GetData(&fmt, &medium);
if (SUCCEEDED(hr))
{
POINT * pptTemp = (POINT *)GlobalLock(medium.hGlobal);
if (pptTemp)
{
*ppt = *pptTemp;
GlobalUnlock(medium.hGlobal);
}
else
hr = E_UNEXPECTED;
ReleaseStgMedium(&medium);
}
return hr;
}
STDAPI_(BOOL) DataObj_CanGoAsync(IDataObject *pdtobj)
{
BOOL fDoOpAsynch = FALSE;
IAsyncOperation * pao;
if (SUCCEEDED(pdtobj->QueryInterface(IID_PPV_ARG(IAsyncOperation, &pao))))
{
BOOL fIsOpAsync;
if (SUCCEEDED(pao->GetAsyncMode(&fIsOpAsync)) && fIsOpAsync)
{
fDoOpAsynch = SUCCEEDED(pao->StartOperation(NULL));
}
pao->Release();
}
return fDoOpAsynch;
}
//
// HACKHACK: (reinerf) - We used to always do async drag/drop operations on NT4 by cloning the
// dataobject. Some apps (WS_FTP 6.0) rely on the async nature in order for drag/drop to work since
// they stash the return value from DoDragDrop and look at it later when their copy hook is invoked
// by SHFileOperation(). So, we sniff the HDROP and if it has one path that contains "WS_FTPE\Notify"
// in it, then we do the operation async.
//
STDAPI_(BOOL) DataObj_GoAsyncForCompat(IDataObject *pdtobj)
{
BOOL bRet = FALSE;
STGMEDIUM medium;
FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
if (SUCCEEDED(pdtobj->GetData(&fmte, &medium)))
{
// is there only one path in the hdrop?
if (DragQueryFile((HDROP)medium.hGlobal, (UINT)-1, NULL, 0) == 1)
{
TCHAR szPath[MAX_PATH];
// is it the magical WS_FTP path ("%temp%\WS_FTPE\Notify") that WS_FTP sniffs
// for in their copy hook?
if (DragQueryFile((HDROP)medium.hGlobal, 0, szPath, ARRAYSIZE(szPath)) &&
StrStrI(szPath, TEXT("WS_FTPE\\Notify")))
{
// yes, we have to do an async operation for app compat
TraceMsg(TF_WARNING, "DataObj_GoAsyncForCompat: found WS_FTP HDROP, doing async drag-drop");
bRet = TRUE;
}
}
ReleaseStgMedium(&medium);
}
return bRet;
}
// use GlobalFree() to free the handle returned here
STDAPI DataObj_CopyHIDA(IDataObject *pdtobj, HIDA *phida)
{
*phida = NULL;
IDLData_InitializeClipboardFormats();
STGMEDIUM medium;
FORMATETC fmte = {g_cfHIDA, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
HRESULT hr = pdtobj->GetData(&fmte, &medium);
if (SUCCEEDED(hr))
{
SIZE_T cb = GlobalSize(medium.hGlobal);
*phida = (HIDA)GlobalAlloc(GPTR, cb);
if (*phida)
{
void *pv = GlobalLock(medium.hGlobal);
CopyMemory((void *)*phida, pv, cb);
GlobalUnlock(medium.hGlobal);
}
else
hr = E_OUTOFMEMORY;
ReleaseStgMedium(&medium);
}
return hr;
}
// Returns an IShellItem for the FIRST item in the data object
HRESULT DataObj_GetIShellItem(IDataObject *pdtobj, IShellItem** ppsi)
{
LPITEMIDLIST pidl;
HRESULT hr = PidlFromDataObject(pdtobj, &pidl);
if (SUCCEEDED(hr))
{
// at shome point should find out who is calling this
// can see if caller already as the info to create the ShellItem
hr = SHCreateShellItem(NULL, NULL, pidl, ppsi);
ILFree(pidl);
}
return hr;
}
STDAPI PathFromDataObject(IDataObject *pdtobj, LPTSTR pszPath, UINT cchPath)
{
FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
STGMEDIUM medium;
HRESULT hr = pdtobj->GetData(&fmte, &medium);
if (SUCCEEDED(hr))
{
if (DragQueryFile((HDROP)medium.hGlobal, 0, pszPath, cchPath))
hr = S_OK;
else
hr = E_FAIL;
ReleaseStgMedium(&medium);
}
return hr;
}
STDAPI PidlFromDataObject(IDataObject *pdtobj, LPITEMIDLIST *ppidlTarget)
{
HRESULT hr;
*ppidlTarget = NULL;
// If the data object has a HIDA, then use it. This allows us to
// access pidls inside data objects that aren't filesystem objects.
// (It's also faster than extracting the path and converting it back
// to a pidl. Difference: pidls for files on the desktop
// are returned in original form instead of being converted to
// a CSIDL_DESKTOPDIRECTORY-relative pidl. I think this is a good thing.)
STGMEDIUM medium;
LPIDA pida = DataObj_GetHIDA(pdtobj, &medium);
if (pida)
{
*ppidlTarget = HIDA_ILClone(pida, 0);
HIDA_ReleaseStgMedium(pida, &medium);
hr = *ppidlTarget ? S_OK : E_OUTOFMEMORY;
}
else
{
// No HIDA available; go for a filename
// This string is also used to store an URL in case it's an URL file
TCHAR szPath[MAX_URL_STRING];
hr = PathFromDataObject(pdtobj, szPath, ARRAYSIZE(szPath));
if (SUCCEEDED(hr))
{
*ppidlTarget = ILCreateFromPath(szPath);
hr = *ppidlTarget ? S_OK : E_OUTOFMEMORY;
}
}
return hr;
}