#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; }