Source code of Windows XP (NT5)
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.

546 lines
17 KiB

  1. #include "shellprv.h"
  2. #include "datautil.h"
  3. #include "idlcomm.h"
  4. STDAPI DataObj_SetDropTarget(IDataObject *pdtobj, const CLSID *pclsid)
  5. {
  6. return DataObj_SetBlob(pdtobj, g_cfTargetCLSID, pclsid, sizeof(*pclsid));
  7. }
  8. STDAPI DataObj_GetDropTarget(IDataObject *pdtobj, CLSID *pclsid)
  9. {
  10. return DataObj_GetBlob(pdtobj, g_cfTargetCLSID, pclsid, sizeof(*pclsid));
  11. }
  12. STDAPI_(UINT) DataObj_GetHIDACount(IDataObject *pdtobj)
  13. {
  14. STGMEDIUM medium = {0};
  15. LPIDA pida = DataObj_GetHIDA(pdtobj, &medium);
  16. if (pida)
  17. {
  18. UINT count = pida->cidl;
  19. ASSERT(pida->cidl == HIDA_GetCount(medium.hGlobal));
  20. HIDA_ReleaseStgMedium(pida, &medium);
  21. return count;
  22. }
  23. return 0;
  24. }
  25. // PERFPERF
  26. // This routine used to copy 512 bytes at a time, but that had a major negative perf impact.
  27. // I have measured a 2-3x speedup in copy times by increasing this buffer size to 16k.
  28. // Yes, its a lot of stack, but it is memory well spent. -saml
  29. #define STREAM_COPY_BUF_SIZE 16384
  30. #define STREAM_PROGRESS_INTERVAL (100*1024/STREAM_COPY_BUF_SIZE) // display progress after this many blocks
  31. HRESULT StreamCopyWithProgress(IStream *pstmFrom, IStream *pstmTo, ULARGE_INTEGER cb, PROGRESSINFO * ppi)
  32. {
  33. BYTE buf[STREAM_COPY_BUF_SIZE];
  34. ULONG cbRead;
  35. HRESULT hr = S_OK;
  36. ULARGE_INTEGER uliNewCompleted;
  37. DWORD dwLastTickCount = 0;
  38. if (ppi)
  39. {
  40. uliNewCompleted.QuadPart = ppi->uliBytesCompleted.QuadPart;
  41. }
  42. while (cb.QuadPart)
  43. {
  44. if (ppi && ppi->ppd)
  45. {
  46. DWORD dwTickCount = GetTickCount();
  47. if ((dwTickCount - dwLastTickCount) > 1000)
  48. {
  49. EVAL(SUCCEEDED(ppi->ppd->SetProgress64(uliNewCompleted.QuadPart, ppi->uliBytesTotal.QuadPart)));
  50. if (ppi->ppd->HasUserCancelled())
  51. {
  52. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  53. break;
  54. }
  55. dwLastTickCount = dwTickCount;
  56. }
  57. }
  58. hr = pstmFrom->Read(buf, min(cb.LowPart, sizeof(buf)), &cbRead);
  59. if (FAILED(hr) || (cbRead == 0))
  60. {
  61. // sometimes we are just done.
  62. if (SUCCEEDED(hr))
  63. hr = S_OK;
  64. break;
  65. }
  66. if (ppi)
  67. {
  68. uliNewCompleted.QuadPart += (ULONGLONG) cbRead;
  69. }
  70. cb.QuadPart -= cbRead;
  71. hr = pstmTo->Write(buf, cbRead, &cbRead);
  72. if (FAILED(hr) || (cbRead == 0))
  73. break;
  74. }
  75. return hr;
  76. }
  77. //
  78. // APP COMPAT! Prior versions of the shell used IStream::CopyTo to copy
  79. // the stream. New versions of the shell use IStream::Read to copy the
  80. // stream so we can put up progress UI. WebFerret 3.0000 implements both
  81. // IStream::Read and IStream::CopyTo, but their implementation of
  82. // IStream::Read hangs the system. So we need to sniff at the data object
  83. // and stream to see if it is WebFerret.
  84. //
  85. // WebFerret doesn't implement IPersist (so IPersist::GetClassID won't
  86. // help) and they don't fill in the CLSID in the FILEDESCRIPTOR
  87. // and it's an out-of-proc data object, so we have to go completely
  88. // on circumstantial evidence.
  89. //
  90. STDAPI_(BOOL) IUnknown_SupportsInterface(IUnknown *punk, REFIID riid)
  91. {
  92. IUnknown *punkOut;
  93. if (SUCCEEDED(punk->QueryInterface(riid, (void **)&punkOut)))
  94. {
  95. punkOut->Release();
  96. return TRUE;
  97. }
  98. return FALSE;
  99. }
  100. STDAPI_(BOOL) DataObj_ShouldCopyWithProgress(IDataObject *pdtobj, IStream *pstm, PROGRESSINFO * ppi)
  101. {
  102. //
  103. // Optimization: If there is no progress info, then don't waste your
  104. // time with progress UI.
  105. //
  106. if (!ppi) return FALSE;
  107. //
  108. // How to detect a WebFerret IDataObject:
  109. //
  110. // The filegroup descriptor gives all objects as size zero.
  111. // (Check this first since it is cheap and usually false)
  112. // WebFerret app is running (look for their tooltip window).
  113. // Their IDataObject doesn't support anything other than IUnknown
  114. // (so we use IID_IAsyncOperation to detect shell data objects
  115. // and IPersist to allow ISVs to override).
  116. // Their IStream doesn't support IStream::Stat.
  117. //
  118. STATSTG stat;
  119. if (ppi->uliBytesTotal.QuadPart == 0 &&
  120. FindWindow(TEXT("VslToolTipWindow"), NULL) &&
  121. !IUnknown_SupportsInterface(pdtobj, IID_IAsyncOperation) &&
  122. !IUnknown_SupportsInterface(pdtobj, IID_IPersist) &&
  123. pstm->Stat(&stat, STATFLAG_NONAME) == E_NOTIMPL)
  124. {
  125. return FALSE; // WebFerret!
  126. }
  127. // All test passed; go ahead and copy with progress UI
  128. return TRUE;
  129. }
  130. STDAPI DataObj_SaveToFile(IDataObject *pdtobj, UINT cf, LONG lindex, LPCTSTR pszFile, FILEDESCRIPTOR *pfd, PROGRESSINFO * ppi)
  131. {
  132. STGMEDIUM medium = {0};
  133. FORMATETC fmte;
  134. HRESULT hr;
  135. fmte.cfFormat = (CLIPFORMAT) cf;
  136. fmte.ptd = NULL;
  137. fmte.dwAspect = DVASPECT_CONTENT;
  138. fmte.lindex = lindex;
  139. fmte.tymed = TYMED_HGLOBAL | TYMED_ISTREAM | TYMED_ISTORAGE;
  140. hr = pdtobj->GetData(&fmte, &medium);
  141. if (SUCCEEDED(hr))
  142. {
  143. //
  144. // if the destination file is system or read-only,
  145. // clear those bits out so we can write anew.
  146. //
  147. DWORD dwTargetFileAttributes = GetFileAttributes(pszFile);
  148. if (dwTargetFileAttributes != -1)
  149. {
  150. if (dwTargetFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY))
  151. {
  152. SetFileAttributes(pszFile, dwTargetFileAttributes & ~(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY));
  153. }
  154. }
  155. DWORD dwSrcFileAttributes = 0;
  156. if (pfd->dwFlags & FD_ATTRIBUTES)
  157. {
  158. // store the rest of the attributes if passed...
  159. dwSrcFileAttributes = (pfd->dwFileAttributes & ~FILE_ATTRIBUTE_DIRECTORY);
  160. }
  161. switch (medium.tymed) {
  162. case TYMED_HGLOBAL:
  163. {
  164. HANDLE hfile = CreateFile(pszFile, GENERIC_READ | GENERIC_WRITE,
  165. FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, dwSrcFileAttributes, NULL);
  166. if (hfile != INVALID_HANDLE_VALUE)
  167. {
  168. DWORD dwWrite;
  169. // NTRAID89561-2000/02/25-raymondc: what about writes greater than 4 GB?
  170. if (!WriteFile(hfile, GlobalLock(medium.hGlobal), (pfd->dwFlags & FD_FILESIZE) ? pfd->nFileSizeLow : (DWORD) GlobalSize(medium.hGlobal), &dwWrite, NULL))
  171. hr = HRESULT_FROM_WIN32(GetLastError());
  172. GlobalUnlock(medium.hGlobal);
  173. if (pfd->dwFlags & (FD_CREATETIME | FD_ACCESSTIME | FD_WRITESTIME))
  174. {
  175. SetFileTime(hfile,
  176. pfd->dwFlags & FD_CREATETIME ? &pfd->ftCreationTime : NULL,
  177. pfd->dwFlags & FD_ACCESSTIME ? &pfd->ftLastAccessTime : NULL,
  178. pfd->dwFlags & FD_WRITESTIME ? &pfd->ftLastWriteTime : NULL);
  179. }
  180. CloseHandle(hfile);
  181. if (FAILED(hr))
  182. EVAL(DeleteFile(pszFile));
  183. }
  184. else
  185. {
  186. hr = HRESULT_FROM_WIN32(GetLastError());
  187. }
  188. break;
  189. }
  190. case TYMED_ISTREAM:
  191. {
  192. IStream *pstm;
  193. hr = SHCreateStreamOnFile(pszFile, STGM_CREATE | STGM_WRITE | STGM_SHARE_DENY_WRITE, &pstm);
  194. if (SUCCEEDED(hr))
  195. {
  196. //
  197. // Per the SDK, IDataObject::GetData leaves the stream ptr at
  198. // the end of the data in the stream. To copy the stream we
  199. // first must reposition the stream ptr to the begining.
  200. // We restore the stream ptr to it's original location when we're done.
  201. //
  202. // NOTE: In case the source stream doesn't support Seek(),
  203. // attempt the copy even if the seek operation fails.
  204. //
  205. const LARGE_INTEGER ofsBegin = {0, 0};
  206. ULARGE_INTEGER ofsOriginal = {0, 0};
  207. HRESULT hrSeek = medium.pstm->Seek(ofsBegin, STREAM_SEEK_CUR, &ofsOriginal);
  208. if (SUCCEEDED(hrSeek))
  209. {
  210. hrSeek = medium.pstm->Seek(ofsBegin, STREAM_SEEK_SET, NULL);
  211. }
  212. const ULARGE_INTEGER ul = {(UINT)-1, (UINT)-1}; // the whole thing
  213. if (DataObj_ShouldCopyWithProgress(pdtobj, medium.pstm, ppi))
  214. {
  215. hr = StreamCopyWithProgress(medium.pstm, pstm, ul, ppi);
  216. }
  217. else
  218. {
  219. hr = medium.pstm->CopyTo(pstm, ul, NULL, NULL);
  220. }
  221. if (SUCCEEDED(hrSeek))
  222. {
  223. //
  224. // Restore stream ptr in source to it's original location.
  225. //
  226. const LARGE_INTEGER ofs = { ofsOriginal.LowPart, (LONG)ofsOriginal.HighPart };
  227. medium.pstm->Seek(ofs, STREAM_SEEK_SET, NULL);
  228. }
  229. pstm->Release();
  230. if (FAILED(hr))
  231. EVAL(DeleteFile(pszFile));
  232. DebugMsg(TF_FSTREE, TEXT("IStream::CopyTo() -> %x"), hr);
  233. }
  234. break;
  235. }
  236. case TYMED_ISTORAGE:
  237. {
  238. WCHAR wszNewFile[MAX_PATH];
  239. IStorage *pstg;
  240. DebugMsg(TF_FSTREE, TEXT("got IStorage"));
  241. SHTCharToUnicode(pszFile, wszNewFile, ARRAYSIZE(wszNewFile));
  242. hr = StgCreateDocfile(wszNewFile,
  243. STGM_DIRECT | STGM_READWRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE,
  244. 0, &pstg);
  245. if (SUCCEEDED(hr))
  246. {
  247. hr = medium.pstg->CopyTo(0, NULL, NULL, pstg);
  248. DebugMsg(TF_FSTREE, TEXT("IStorage::CopyTo() -> %x"), hr);
  249. pstg->Commit(STGC_OVERWRITE);
  250. pstg->Release();
  251. if (FAILED(hr))
  252. EVAL(DeleteFile(pszFile));
  253. }
  254. }
  255. break;
  256. default:
  257. AssertMsg(FALSE, TEXT("got tymed that I didn't ask for %d"), medium.tymed);
  258. }
  259. if (SUCCEEDED(hr))
  260. {
  261. // in the HGLOBAL case we could take some shortcuts, so the attributes and
  262. // file times were set earlier in the case statement.
  263. // otherwise, we need to set the file times and attributes now.
  264. if (medium.tymed != TYMED_HGLOBAL)
  265. {
  266. if (pfd->dwFlags & (FD_CREATETIME | FD_ACCESSTIME | FD_WRITESTIME))
  267. {
  268. // open with GENERIC_WRITE to let us set the file times,
  269. // everybody else can open with SHARE_READ.
  270. HANDLE hFile = CreateFile(pszFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
  271. if (hFile != INVALID_HANDLE_VALUE)
  272. {
  273. SetFileTime(hFile,
  274. pfd->dwFlags & FD_CREATETIME ? &pfd->ftCreationTime : NULL,
  275. pfd->dwFlags & FD_ACCESSTIME ? &pfd->ftLastAccessTime : NULL,
  276. pfd->dwFlags & FD_WRITESTIME ? &pfd->ftLastWriteTime : NULL);
  277. CloseHandle(hFile);
  278. }
  279. }
  280. if (dwSrcFileAttributes)
  281. {
  282. SetFileAttributes(pszFile, dwSrcFileAttributes);
  283. }
  284. }
  285. SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, pszFile, NULL);
  286. SHChangeNotify(SHCNE_FREESPACE, SHCNF_PATH, pszFile, NULL);
  287. }
  288. ReleaseStgMedium(&medium);
  289. }
  290. return hr;
  291. }
  292. STDAPI DataObj_GetShellURL(IDataObject *pdtobj, STGMEDIUM *pmedium, LPCSTR *ppszURL)
  293. {
  294. FORMATETC fmte = {g_cfShellURL, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  295. HRESULT hr;
  296. if (pmedium)
  297. {
  298. hr = pdtobj->GetData(&fmte, pmedium);
  299. if (SUCCEEDED(hr))
  300. *ppszURL = (LPCSTR)GlobalLock(pmedium->hGlobal);
  301. }
  302. else
  303. hr = pdtobj->QueryGetData(&fmte); // query only
  304. return hr;
  305. }
  306. STDAPI DataObj_GetOFFSETs(IDataObject *pdtobj, POINT *ppt)
  307. {
  308. STGMEDIUM medium = {0};
  309. IDLData_InitializeClipboardFormats( );
  310. ASSERT(g_cfOFFSETS);
  311. FORMATETC fmt = {g_cfOFFSETS, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  312. ASSERT(ppt);
  313. ppt->x = ppt->y = 0;
  314. HRESULT hr = pdtobj->GetData(&fmt, &medium);
  315. if (SUCCEEDED(hr))
  316. {
  317. POINT * pptTemp = (POINT *)GlobalLock(medium.hGlobal);
  318. if (pptTemp)
  319. {
  320. *ppt = *pptTemp;
  321. GlobalUnlock(medium.hGlobal);
  322. }
  323. else
  324. hr = E_UNEXPECTED;
  325. ReleaseStgMedium(&medium);
  326. }
  327. return hr;
  328. }
  329. STDAPI_(BOOL) DataObj_CanGoAsync(IDataObject *pdtobj)
  330. {
  331. BOOL fDoOpAsynch = FALSE;
  332. IAsyncOperation * pao;
  333. if (SUCCEEDED(pdtobj->QueryInterface(IID_PPV_ARG(IAsyncOperation, &pao))))
  334. {
  335. BOOL fIsOpAsync;
  336. if (SUCCEEDED(pao->GetAsyncMode(&fIsOpAsync)) && fIsOpAsync)
  337. {
  338. fDoOpAsynch = SUCCEEDED(pao->StartOperation(NULL));
  339. }
  340. pao->Release();
  341. }
  342. return fDoOpAsynch;
  343. }
  344. //
  345. // HACKHACK: (reinerf) - We used to always do async drag/drop operations on NT4 by cloning the
  346. // dataobject. Some apps (WS_FTP 6.0) rely on the async nature in order for drag/drop to work since
  347. // they stash the return value from DoDragDrop and look at it later when their copy hook is invoked
  348. // by SHFileOperation(). So, we sniff the HDROP and if it has one path that contains "WS_FTPE\Notify"
  349. // in it, then we do the operation async.
  350. //
  351. STDAPI_(BOOL) DataObj_GoAsyncForCompat(IDataObject *pdtobj)
  352. {
  353. BOOL bRet = FALSE;
  354. STGMEDIUM medium;
  355. FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  356. if (SUCCEEDED(pdtobj->GetData(&fmte, &medium)))
  357. {
  358. // is there only one path in the hdrop?
  359. if (DragQueryFile((HDROP)medium.hGlobal, (UINT)-1, NULL, 0) == 1)
  360. {
  361. TCHAR szPath[MAX_PATH];
  362. // is it the magical WS_FTP path ("%temp%\WS_FTPE\Notify") that WS_FTP sniffs
  363. // for in their copy hook?
  364. if (DragQueryFile((HDROP)medium.hGlobal, 0, szPath, ARRAYSIZE(szPath)) &&
  365. StrStrI(szPath, TEXT("WS_FTPE\\Notify")))
  366. {
  367. // yes, we have to do an async operation for app compat
  368. TraceMsg(TF_WARNING, "DataObj_GoAsyncForCompat: found WS_FTP HDROP, doing async drag-drop");
  369. bRet = TRUE;
  370. }
  371. }
  372. ReleaseStgMedium(&medium);
  373. }
  374. return bRet;
  375. }
  376. // use GlobalFree() to free the handle returned here
  377. STDAPI DataObj_CopyHIDA(IDataObject *pdtobj, HIDA *phida)
  378. {
  379. *phida = NULL;
  380. IDLData_InitializeClipboardFormats();
  381. STGMEDIUM medium;
  382. FORMATETC fmte = {g_cfHIDA, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  383. HRESULT hr = pdtobj->GetData(&fmte, &medium);
  384. if (SUCCEEDED(hr))
  385. {
  386. SIZE_T cb = GlobalSize(medium.hGlobal);
  387. *phida = (HIDA)GlobalAlloc(GPTR, cb);
  388. if (*phida)
  389. {
  390. void *pv = GlobalLock(medium.hGlobal);
  391. CopyMemory((void *)*phida, pv, cb);
  392. GlobalUnlock(medium.hGlobal);
  393. }
  394. else
  395. hr = E_OUTOFMEMORY;
  396. ReleaseStgMedium(&medium);
  397. }
  398. return hr;
  399. }
  400. // Returns an IShellItem for the FIRST item in the data object
  401. HRESULT DataObj_GetIShellItem(IDataObject *pdtobj, IShellItem** ppsi)
  402. {
  403. LPITEMIDLIST pidl;
  404. HRESULT hr = PidlFromDataObject(pdtobj, &pidl);
  405. if (SUCCEEDED(hr))
  406. {
  407. // at shome point should find out who is calling this
  408. // can see if caller already as the info to create the ShellItem
  409. hr = SHCreateShellItem(NULL, NULL, pidl, ppsi);
  410. ILFree(pidl);
  411. }
  412. return hr;
  413. }
  414. STDAPI PathFromDataObject(IDataObject *pdtobj, LPTSTR pszPath, UINT cchPath)
  415. {
  416. FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  417. STGMEDIUM medium;
  418. HRESULT hr = pdtobj->GetData(&fmte, &medium);
  419. if (SUCCEEDED(hr))
  420. {
  421. if (DragQueryFile((HDROP)medium.hGlobal, 0, pszPath, cchPath))
  422. hr = S_OK;
  423. else
  424. hr = E_FAIL;
  425. ReleaseStgMedium(&medium);
  426. }
  427. return hr;
  428. }
  429. STDAPI PidlFromDataObject(IDataObject *pdtobj, LPITEMIDLIST *ppidlTarget)
  430. {
  431. HRESULT hr;
  432. *ppidlTarget = NULL;
  433. // If the data object has a HIDA, then use it. This allows us to
  434. // access pidls inside data objects that aren't filesystem objects.
  435. // (It's also faster than extracting the path and converting it back
  436. // to a pidl. Difference: pidls for files on the desktop
  437. // are returned in original form instead of being converted to
  438. // a CSIDL_DESKTOPDIRECTORY-relative pidl. I think this is a good thing.)
  439. STGMEDIUM medium;
  440. LPIDA pida = DataObj_GetHIDA(pdtobj, &medium);
  441. if (pida)
  442. {
  443. *ppidlTarget = HIDA_ILClone(pida, 0);
  444. HIDA_ReleaseStgMedium(pida, &medium);
  445. hr = *ppidlTarget ? S_OK : E_OUTOFMEMORY;
  446. }
  447. else
  448. {
  449. // No HIDA available; go for a filename
  450. // This string is also used to store an URL in case it's an URL file
  451. TCHAR szPath[MAX_URL_STRING];
  452. hr = PathFromDataObject(pdtobj, szPath, ARRAYSIZE(szPath));
  453. if (SUCCEEDED(hr))
  454. {
  455. *ppidlTarget = ILCreateFromPath(szPath);
  456. hr = *ppidlTarget ? S_OK : E_OUTOFMEMORY;
  457. }
  458. }
  459. return hr;
  460. }