1 
2 /*
3  * file system folder drop target
4  *
5  * Copyright 1997             Marcus Meissner
6  * Copyright 1998, 1999, 2002 Juergen Schmied
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  */
22 
23 #include <precomp.h>
24 
25 WINE_DEFAULT_DEBUG_CHANNEL (shell);
26 
SHELL_StripIllegalFsNameCharacters(_Inout_ LPWSTR Buf)27 static void SHELL_StripIllegalFsNameCharacters(_Inout_ LPWSTR Buf)
28 {
29     for (LPWSTR src = Buf, dst = src;;)
30     {
31         *dst = *src;
32         if (!*dst)
33             break;
34         else if (wcschr(INVALID_FILETITLE_CHARACTERSW, *dst))
35             src = CharNextW(src);
36         else
37             ++src, ++dst;
38     }
39 }
40 
41 static HRESULT
SHELL_LimitDropEffectToItemAttributes(_In_ IDataObject * pDataObject,_Inout_ PDWORD pdwEffect)42 SHELL_LimitDropEffectToItemAttributes(_In_ IDataObject *pDataObject, _Inout_ PDWORD pdwEffect)
43 {
44     DWORD att = *pdwEffect & (SFGAO_CANCOPY | SFGAO_CANMOVE | SFGAO_CANLINK); // DROPEFFECT maps perfectly to these SFGAO bits
45     HRESULT hr = SHGetAttributesFromDataObject(pDataObject, att, &att, NULL);
46     if (FAILED(hr))
47         return S_FALSE;
48     *pdwEffect &= ~(SFGAO_CANCOPY | SFGAO_CANMOVE | SFGAO_CANLINK) | att;
49     return hr;
50 }
51 
GetDefaultCopyMoveEffect()52 static void GetDefaultCopyMoveEffect()
53 {
54     // FIXME: When the source is on a different volume than the target, change default from move to copy
55 }
56 
57 /****************************************************************************
58  * CFSDropTarget::_CopyItems
59  *
60  * copies or moves items to this folder
61  */
_CopyItems(IShellFolder * pSFFrom,UINT cidl,LPCITEMIDLIST * apidl,BOOL bCopy)62 HRESULT CFSDropTarget::_CopyItems(IShellFolder * pSFFrom, UINT cidl,
63                                   LPCITEMIDLIST * apidl, BOOL bCopy)
64 {
65     HRESULT ret;
66     WCHAR wszDstPath[MAX_PATH + 1] = {0};
67     PWCHAR pwszSrcPathsList = (PWCHAR) HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR) * cidl + 1);
68     if (!pwszSrcPathsList)
69         return E_OUTOFMEMORY;
70 
71     PWCHAR pwszListPos = pwszSrcPathsList;
72     STRRET strretFrom;
73     SHFILEOPSTRUCTW fop;
74     BOOL bRenameOnCollision = FALSE;
75 
76     /* Build a double null terminated list of C strings from source paths */
77     for (UINT i = 0; i < cidl; i++)
78     {
79         ret = pSFFrom->GetDisplayNameOf(apidl[i], SHGDN_FORPARSING, &strretFrom);
80         if (FAILED(ret))
81             goto cleanup;
82 
83         ret = StrRetToBufW(&strretFrom, NULL, pwszListPos, MAX_PATH);
84         if (FAILED(ret))
85             goto cleanup;
86 
87         pwszListPos += lstrlenW(pwszListPos) + 1;
88     }
89     /* Append the final null. */
90     *pwszListPos = L'\0';
91 
92     /* Build a double null terminated target (this path) */
93     ret = StringCchCopyW(wszDstPath, MAX_PATH, m_sPathTarget);
94     if (FAILED(ret))
95         goto cleanup;
96 
97     wszDstPath[lstrlenW(wszDstPath) + 1] = UNICODE_NULL;
98 
99     /* Set bRenameOnCollision to TRUE if necesssary */
100     if (bCopy)
101     {
102         WCHAR szPath1[MAX_PATH], szPath2[MAX_PATH];
103         GetFullPathNameW(pwszSrcPathsList, _countof(szPath1), szPath1, NULL);
104         GetFullPathNameW(wszDstPath, _countof(szPath2), szPath2, NULL);
105         PathRemoveFileSpecW(szPath1);
106         if (_wcsicmp(szPath1, szPath2) == 0)
107             bRenameOnCollision = TRUE;
108     }
109 
110     ZeroMemory(&fop, sizeof(fop));
111     fop.hwnd = m_hwndSite;
112     fop.wFunc = bCopy ? FO_COPY : FO_MOVE;
113     fop.pFrom = pwszSrcPathsList;
114     fop.pTo = wszDstPath;
115     fop.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR;
116     if (bRenameOnCollision)
117         fop.fFlags |= FOF_RENAMEONCOLLISION;
118 
119     ret = S_OK;
120 
121     if (SHFileOperationW(&fop))
122     {
123         ERR("SHFileOperationW failed\n");
124         ret = E_FAIL;
125     }
126 
127 cleanup:
128     HeapFree(GetProcessHeap(), 0, pwszSrcPathsList);
129     return ret;
130 }
131 
CFSDropTarget()132 CFSDropTarget::CFSDropTarget():
133     m_cfShellIDList(0),
134     m_fAcceptFmt(FALSE),
135     m_sPathTarget(NULL),
136     m_hwndSite(NULL),
137     m_grfKeyState(0)
138 {
139 }
140 
Initialize(LPWSTR PathTarget)141 HRESULT CFSDropTarget::Initialize(LPWSTR PathTarget)
142 {
143     if (!PathTarget)
144         return E_UNEXPECTED;
145 
146     m_cfShellIDList = RegisterClipboardFormatW(CFSTR_SHELLIDLIST);
147     if (!m_cfShellIDList)
148         return E_FAIL;
149 
150     m_sPathTarget = (WCHAR *)SHAlloc((wcslen(PathTarget) + 1) * sizeof(WCHAR));
151     if (!m_sPathTarget)
152         return E_OUTOFMEMORY;
153 
154     wcscpy(m_sPathTarget, PathTarget);
155 
156     return S_OK;
157 }
158 
~CFSDropTarget()159 CFSDropTarget::~CFSDropTarget()
160 {
161     SHFree(m_sPathTarget);
162 }
163 
164 BOOL
_GetUniqueFileName(LPCWSTR pwszBasePath,LPCWSTR pwszExt,LPWSTR pwszTarget,BOOL bShortcut)165 CFSDropTarget::_GetUniqueFileName(LPCWSTR pwszBasePath, LPCWSTR pwszExt, LPWSTR pwszTarget, BOOL bShortcut)
166 {
167     WCHAR wszLink[40];
168 
169     if (!bShortcut)
170     {
171         if (!LoadStringW(shell32_hInstance, IDS_LNK_FILE, wszLink, _countof(wszLink)))
172             wszLink[0] = L'\0';
173     }
174 
175     if (!bShortcut)
176         swprintf(pwszTarget, L"%s%s%s", wszLink, pwszBasePath, pwszExt);
177     else
178         swprintf(pwszTarget, L"%s%s", pwszBasePath, pwszExt);
179 
180     for (UINT i = 2; PathFileExistsW(pwszTarget); ++i)
181     {
182         if (!bShortcut)
183             swprintf(pwszTarget, L"%s%s (%u)%s", wszLink, pwszBasePath, i, pwszExt);
184         else
185             swprintf(pwszTarget, L"%s (%u)%s", pwszBasePath, i, pwszExt);
186     }
187 
188     return TRUE;
189 }
190 
191 /****************************************************************************
192  * IDropTarget implementation
193  */
_QueryDrop(DWORD dwKeyState,LPDWORD pdwEffect)194 BOOL CFSDropTarget::_QueryDrop(DWORD dwKeyState, LPDWORD pdwEffect)
195 {
196     /* TODO Windows does different drop effects if dragging across drives.
197     i.e., it will copy instead of move if the directories are on different disks. */
198     GetDefaultCopyMoveEffect();
199 
200     DWORD dwEffect = m_dwDefaultEffect;
201 
202     *pdwEffect = DROPEFFECT_NONE;
203 
204     if (m_fAcceptFmt) { /* Does our interpretation of the keystate ... */
205         *pdwEffect = KeyStateToDropEffect (dwKeyState);
206 
207         if (*pdwEffect == DROPEFFECT_NONE)
208             *pdwEffect = dwEffect;
209 
210         /* ... matches the desired effect ? */
211         if (dwEffect & *pdwEffect) {
212             return TRUE;
213         }
214     }
215     return FALSE;
216 }
217 
_GetEffectFromMenu(IDataObject * pDataObject,POINTL pt,DWORD * pdwEffect,DWORD dwAvailableEffects)218 HRESULT CFSDropTarget::_GetEffectFromMenu(IDataObject *pDataObject, POINTL pt, DWORD *pdwEffect, DWORD dwAvailableEffects)
219 {
220     HMENU hmenu = LoadMenuW(shell32_hInstance, MAKEINTRESOURCEW(IDM_DRAGFILE));
221     if (!hmenu)
222         return E_OUTOFMEMORY;
223 
224     HMENU hpopupmenu = GetSubMenu(hmenu, 0);
225 
226     SHELL_LimitDropEffectToItemAttributes(pDataObject, &dwAvailableEffects);
227     if ((dwAvailableEffects & DROPEFFECT_COPY) == 0)
228         DeleteMenu(hpopupmenu, IDM_COPYHERE, MF_BYCOMMAND);
229     if ((dwAvailableEffects & DROPEFFECT_MOVE) == 0)
230         DeleteMenu(hpopupmenu, IDM_MOVEHERE, MF_BYCOMMAND);
231     if ((dwAvailableEffects & DROPEFFECT_LINK) == 0 && (dwAvailableEffects & (DROPEFFECT_COPY | DROPEFFECT_MOVE)))
232         DeleteMenu(hpopupmenu, IDM_LINKHERE, MF_BYCOMMAND);
233 
234     GetDefaultCopyMoveEffect();
235     if (*pdwEffect & dwAvailableEffects & DROPEFFECT_COPY)
236         SetMenuDefaultItem(hpopupmenu, IDM_COPYHERE, FALSE);
237     else if (*pdwEffect & dwAvailableEffects & DROPEFFECT_MOVE)
238         SetMenuDefaultItem(hpopupmenu, IDM_MOVEHERE, FALSE);
239     else if (dwAvailableEffects & DROPEFFECT_LINK)
240         SetMenuDefaultItem(hpopupmenu, IDM_LINKHERE, FALSE);
241 
242     /* FIXME: We need to support shell extensions here */
243 
244     /* We shouldn't use the site window here because the menu should work even when we don't have a site */
245     HWND hwndDummy = CreateWindowEx(0,
246                               WC_STATIC,
247                               NULL,
248                               WS_OVERLAPPED | WS_DISABLED | WS_CLIPSIBLINGS | WS_BORDER | SS_LEFT,
249                               pt.x,
250                               pt.y,
251                               1,
252                               1,
253                               NULL,
254                               NULL,
255                               NULL,
256                               NULL);
257 
258     SetForegroundWindow(hwndDummy); // Required for aborting by pressing Esc when dragging from Explorer to desktop
259     UINT uCommand = TrackPopupMenu(hpopupmenu,
260                                    TPM_LEFTALIGN | TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_NONOTIFY,
261                                    pt.x, pt.y, 0, hwndDummy, NULL);
262 
263     DestroyWindow(hwndDummy);
264 
265     if (uCommand == 0)
266         return S_FALSE;
267     else if (uCommand == IDM_COPYHERE)
268         *pdwEffect = DROPEFFECT_COPY;
269     else if (uCommand == IDM_MOVEHERE)
270         *pdwEffect = DROPEFFECT_MOVE;
271     else if (uCommand == IDM_LINKHERE)
272         *pdwEffect = DROPEFFECT_LINK;
273 
274     return S_OK;
275 }
276 
_RepositionItems(IShellFolderView * psfv,IDataObject * pdtobj,POINTL pt)277 HRESULT CFSDropTarget::_RepositionItems(IShellFolderView *psfv, IDataObject *pdtobj, POINTL pt)
278 {
279     CComPtr<IFolderView> pfv;
280     POINT ptDrag;
281     HRESULT hr = psfv->QueryInterface(IID_PPV_ARG(IFolderView, &pfv));
282     if (FAILED_UNEXPECTEDLY(hr))
283         return hr;
284 
285     hr = psfv->GetDragPoint(&ptDrag);
286     if (FAILED_UNEXPECTEDLY(hr))
287         return hr;
288 
289     PIDLIST_ABSOLUTE pidlFolder;
290     PUITEMID_CHILD *apidl;
291     UINT cidl;
292     hr = SH_GetApidlFromDataObject(pdtobj, &pidlFolder, &apidl, &cidl);
293     if (FAILED_UNEXPECTEDLY(hr))
294         return hr;
295 
296     CComHeapPtr<POINT> apt;
297     if (!apt.Allocate(cidl))
298     {
299         SHFree(pidlFolder);
300         _ILFreeaPidl(apidl, cidl);
301         return E_OUTOFMEMORY;
302     }
303 
304     for (UINT i = 0; i<cidl; i++)
305     {
306         pfv->GetItemPosition(apidl[i], &apt[i]);
307         apt[i].x += pt.x - ptDrag.x;
308         apt[i].y += pt.y - ptDrag.y;
309     }
310 
311     pfv->SelectAndPositionItems(cidl, apidl, apt, SVSI_SELECT);
312 
313     SHFree(pidlFolder);
314     _ILFreeaPidl(apidl, cidl);
315     return S_OK;
316 }
317 
DragEnter(IDataObject * pDataObject,DWORD dwKeyState,POINTL pt,DWORD * pdwEffect)318 HRESULT WINAPI CFSDropTarget::DragEnter(IDataObject *pDataObject,
319                                         DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
320 {
321     TRACE("(%p)->(DataObject=%p)\n", this, pDataObject);
322 
323     if (*pdwEffect == DROPEFFECT_NONE)
324         return S_OK;
325 
326     FORMATETC fmt;
327     FORMATETC fmt2;
328     m_fAcceptFmt = FALSE;
329 
330     InitFormatEtc (fmt, m_cfShellIDList, TYMED_HGLOBAL);
331     InitFormatEtc (fmt2, CF_HDROP, TYMED_HGLOBAL);
332 
333     if (SUCCEEDED(pDataObject->QueryGetData(&fmt)))
334         m_fAcceptFmt = TRUE;
335     else if (SUCCEEDED(pDataObject->QueryGetData(&fmt2)))
336         m_fAcceptFmt = TRUE;
337 
338     m_grfKeyState = dwKeyState;
339 
340 #define D_NONE DROPEFFECT_NONE
341 #define D_COPY DROPEFFECT_COPY
342 #define D_MOVE DROPEFFECT_MOVE
343 #define D_LINK DROPEFFECT_LINK
344     m_dwDefaultEffect = *pdwEffect;
345     switch (*pdwEffect & (D_COPY | D_MOVE | D_LINK))
346     {
347         case D_COPY | D_MOVE:
348             if (dwKeyState & MK_CONTROL)
349                 m_dwDefaultEffect = D_COPY;
350             else
351                 m_dwDefaultEffect = D_MOVE;
352             break;
353         case D_COPY | D_MOVE | D_LINK:
354             if ((dwKeyState & (MK_SHIFT | MK_CONTROL)) == (MK_SHIFT | MK_CONTROL))
355                 m_dwDefaultEffect = D_LINK;
356             else if ((dwKeyState & (MK_SHIFT | MK_CONTROL)) == MK_CONTROL)
357                 m_dwDefaultEffect = D_COPY;
358             else
359                 m_dwDefaultEffect = D_MOVE;
360             break;
361         case D_COPY | D_LINK:
362             if ((dwKeyState & (MK_SHIFT | MK_CONTROL)) == (MK_SHIFT | MK_CONTROL))
363                 m_dwDefaultEffect = D_LINK;
364             else
365                 m_dwDefaultEffect = D_COPY;
366             break;
367         case D_MOVE | D_LINK:
368             if ((dwKeyState & (MK_SHIFT | MK_CONTROL)) == (MK_SHIFT | MK_CONTROL))
369                 m_dwDefaultEffect = D_LINK;
370             else
371                 m_dwDefaultEffect = D_MOVE;
372             break;
373     }
374 
375     STGMEDIUM medium;
376     if (SUCCEEDED(pDataObject->GetData(&fmt2, &medium)))
377     {
378         WCHAR wstrFirstFile[MAX_PATH];
379         if (DragQueryFileW((HDROP)medium.hGlobal, 0, wstrFirstFile, _countof(wstrFirstFile)))
380         {
381             /* Check if the drive letter is different */
382             if (wstrFirstFile[0] != m_sPathTarget[0])
383             {
384                 m_dwDefaultEffect = DROPEFFECT_COPY;
385             }
386         }
387         ReleaseStgMedium(&medium);
388     }
389 
390     if (!m_fAcceptFmt)
391         *pdwEffect = DROPEFFECT_NONE;
392     else
393         *pdwEffect = m_dwDefaultEffect;
394 
395     return S_OK;
396 }
397 
DragOver(DWORD dwKeyState,POINTL pt,DWORD * pdwEffect)398 HRESULT WINAPI CFSDropTarget::DragOver(DWORD dwKeyState, POINTL pt,
399                                        DWORD *pdwEffect)
400 {
401     TRACE("(%p)\n", this);
402 
403     if (!pdwEffect)
404         return E_INVALIDARG;
405 
406     m_grfKeyState = dwKeyState;
407 
408     _QueryDrop(dwKeyState, pdwEffect);
409 
410     return S_OK;
411 }
412 
DragLeave()413 HRESULT WINAPI CFSDropTarget::DragLeave()
414 {
415     TRACE("(%p)\n", this);
416 
417     m_fAcceptFmt = FALSE;
418 
419     return S_OK;
420 }
421 
Drop(IDataObject * pDataObject,DWORD dwKeyState,POINTL pt,DWORD * pdwEffect)422 HRESULT WINAPI CFSDropTarget::Drop(IDataObject *pDataObject,
423                                    DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
424 {
425     TRACE("(%p) object dropped, effect %u\n", this, *pdwEffect);
426 
427     if (!pdwEffect)
428         return E_INVALIDARG;
429 
430     IUnknown_GetWindow(m_site, &m_hwndSite);
431 
432     DWORD dwAvailableEffects = *pdwEffect;
433 
434     _QueryDrop(dwKeyState, pdwEffect);
435 
436     TRACE("pdwEffect: 0x%x, m_dwDefaultEffect: 0x%x, dwAvailableEffects: 0x%x\n", *pdwEffect, m_dwDefaultEffect, dwAvailableEffects);
437 
438     if (m_grfKeyState & MK_RBUTTON)
439     {
440         HRESULT hr = _GetEffectFromMenu(pDataObject, pt, pdwEffect, dwAvailableEffects);
441         if (FAILED_UNEXPECTEDLY(hr) || hr == S_FALSE)
442             return hr;
443     }
444 
445     if (*pdwEffect == DROPEFFECT_MOVE && m_site)
446     {
447         CComPtr<IShellFolderView> psfv;
448         HRESULT hr = IUnknown_QueryService(m_site, SID_IFolderView, IID_PPV_ARG(IShellFolderView, &psfv));
449         if (SUCCEEDED(hr) && psfv->IsDropOnSource(this) == S_OK)
450         {
451             _RepositionItems(psfv, pDataObject, pt);
452             return S_OK;
453         }
454     }
455 
456     BOOL fIsOpAsync = FALSE;
457     CComPtr<IAsyncOperation> pAsyncOperation;
458 
459     if (SUCCEEDED(pDataObject->QueryInterface(IID_PPV_ARG(IAsyncOperation, &pAsyncOperation))))
460     {
461         if (SUCCEEDED(pAsyncOperation->GetAsyncMode(&fIsOpAsync)) && fIsOpAsync)
462         {
463             _DoDropData *data = static_cast<_DoDropData*>(HeapAlloc(GetProcessHeap(), 0, sizeof(_DoDropData)));
464             data->This = this;
465             // Need to maintain this class in case the window is closed or the class exists temporarily (when dropping onto a folder).
466             pDataObject->AddRef();
467             pAsyncOperation->StartOperation(NULL);
468             CoMarshalInterThreadInterfaceInStream(IID_IDataObject, pDataObject, &data->pStream);
469             this->AddRef();
470             data->dwKeyState = dwKeyState;
471             data->pt = pt;
472             // Need to dereference as pdweffect gets freed.
473             data->pdwEffect = *pdwEffect;
474             SHCreateThread(CFSDropTarget::_DoDropThreadProc, data, NULL, NULL);
475             return S_OK;
476         }
477     }
478     return this->_DoDrop(pDataObject, dwKeyState, pt, pdwEffect);
479 }
480 
481 HRESULT
482 WINAPI
SetSite(IUnknown * pUnkSite)483 CFSDropTarget::SetSite(IUnknown *pUnkSite)
484 {
485     m_site = pUnkSite;
486     return S_OK;
487 }
488 
489 HRESULT
490 WINAPI
GetSite(REFIID riid,void ** ppvSite)491 CFSDropTarget::GetSite(REFIID riid, void **ppvSite)
492 {
493     if (!m_site)
494         return E_FAIL;
495 
496     return m_site->QueryInterface(riid, ppvSite);
497 }
498 
_DoDrop(IDataObject * pDataObject,DWORD dwKeyState,POINTL pt,DWORD * pdwEffect)499 HRESULT CFSDropTarget::_DoDrop(IDataObject *pDataObject,
500                                DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
501 {
502     TRACE("(%p) performing drop, effect %u\n", this, *pdwEffect);
503     FORMATETC fmt;
504     FORMATETC fmt2;
505     STGMEDIUM medium;
506 
507     InitFormatEtc (fmt, m_cfShellIDList, TYMED_HGLOBAL);
508     InitFormatEtc (fmt2, CF_HDROP, TYMED_HGLOBAL);
509 
510     HRESULT hr;
511     bool bCopy = TRUE;
512     bool bLinking = FALSE;
513 
514     /* Figure out what drop operation we're doing */
515     if (pdwEffect)
516     {
517         TRACE("Current drop effect flag %i\n", *pdwEffect);
518         if ((*pdwEffect & DROPEFFECT_MOVE) == DROPEFFECT_MOVE)
519             bCopy = FALSE;
520         if ((*pdwEffect & DROPEFFECT_LINK) == DROPEFFECT_LINK)
521             bLinking = TRUE;
522     }
523 
524     if (SUCCEEDED(pDataObject->QueryGetData(&fmt)))
525     {
526         hr = pDataObject->GetData(&fmt, &medium);
527         TRACE("CFSTR_SHELLIDLIST\n");
528         if (FAILED(hr))
529         {
530             ERR("CFSTR_SHELLIDLIST failed\n");
531         }
532         /* lock the handle */
533         LPIDA lpcida = (LPIDA)GlobalLock(medium.hGlobal);
534         if (!lpcida)
535         {
536             ReleaseStgMedium(&medium);
537             return E_FAIL;
538         }
539 
540         /* convert the data into pidl */
541         LPITEMIDLIST pidl;
542         LPITEMIDLIST *apidl = _ILCopyCidaToaPidl(&pidl, lpcida);
543         if (!apidl)
544         {
545             ReleaseStgMedium(&medium);
546             return E_FAIL;
547         }
548 
549         CComPtr<IShellFolder> psfDesktop;
550         CComPtr<IShellFolder> psfFrom = NULL;
551 
552         /* Grab the desktop shell folder */
553         hr = SHGetDesktopFolder(&psfDesktop);
554         if (FAILED(hr))
555         {
556             ERR("SHGetDesktopFolder failed\n");
557             SHFree(pidl);
558             _ILFreeaPidl(apidl, lpcida->cidl);
559             ReleaseStgMedium(&medium);
560             return E_FAIL;
561         }
562 
563         /* Find source folder, this is where the clipboard data was copied from */
564         if (_ILIsDesktop(pidl))
565         {
566             /* use desktop shell folder */
567             psfFrom = psfDesktop;
568         }
569         else
570         {
571             hr = psfDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &psfFrom));
572             if (FAILED(hr))
573             {
574                 ERR("no IShellFolder\n");
575                 SHFree(pidl);
576                 _ILFreeaPidl(apidl, lpcida->cidl);
577                 ReleaseStgMedium(&medium);
578                 return E_FAIL;
579             }
580         }
581 
582         if (bLinking)
583         {
584             WCHAR wszNewLnk[MAX_PATH];
585 
586             TRACE("target path = %s\n", debugstr_w(m_sPathTarget));
587 
588             /* We need to create a link for each pidl in the copied items, so step through the pidls from the clipboard */
589             for (UINT i = 0; i < lpcida->cidl; i++)
590             {
591                 SFGAOF att = SHGetAttributes(psfFrom, apidl[i], SFGAO_FOLDER | SFGAO_STREAM | SFGAO_FILESYSTEM);
592                 CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlFull;
593                 hr = SHILCombine(pidl, apidl[i], &pidlFull);
594 
595                 WCHAR targetName[MAX_PATH];
596                 if (SUCCEEDED(hr))
597                 {
598                     // If the target is a file, we use SHGDN_FORPARSING because "NeverShowExt" will hide the ".lnk" extension.
599                     // If the target is a virtual item, we ask for the friendly name because SHGDN_FORPARSING will return a GUID.
600                     BOOL UseParsing = (att & (SFGAO_FILESYSTEM | SFGAO_FOLDER)) == SFGAO_FILESYSTEM;
601                     DWORD ShgdnFor = UseParsing ? SHGDN_FORPARSING : SHGDN_FOREDITING;
602                     hr = Shell_DisplayNameOf(psfFrom, apidl[i], ShgdnFor | SHGDN_INFOLDER, targetName, _countof(targetName));
603                 }
604                 if (FAILED_UNEXPECTEDLY(hr))
605                 {
606                     SHELL_ErrorBox(m_hwndSite, hr);
607                     break;
608                 }
609                 SHELL_StripIllegalFsNameCharacters(targetName);
610 
611                 WCHAR wszCombined[MAX_PATH + _countof(targetName)];
612                 PathCombineW(wszCombined, m_sPathTarget, targetName);
613 
614                 // Check to see if the source is a link
615                 BOOL fSourceIsLink = FALSE;
616                 if (!_wcsicmp(PathFindExtensionW(targetName), L".lnk") && (att & (SFGAO_FOLDER | SFGAO_STREAM)) != SFGAO_FOLDER)
617                 {
618                     fSourceIsLink = TRUE;
619                     PathRemoveExtensionW(wszCombined);
620                 }
621 
622                 // Create a pathname to save the new link.
623                 _GetUniqueFileName(wszCombined, L".lnk", wszNewLnk, TRUE);
624 
625                 CComPtr<IPersistFile> ppf;
626                 if (fSourceIsLink)
627                 {
628                     PWSTR pwszTargetFull;
629                     hr = SHGetNameFromIDList(pidlFull, SIGDN_DESKTOPABSOLUTEPARSING, &pwszTargetFull);
630                     if (!FAILED_UNEXPECTEDLY(hr))
631                     {
632                         hr = IShellLink_ConstructFromPath(pwszTargetFull, IID_PPV_ARG(IPersistFile, &ppf));
633                         FAILED_UNEXPECTEDLY(hr);
634                         SHFree(pwszTargetFull);
635                     }
636                 }
637                 else
638                 {
639                     CComPtr<IShellLinkW> pLink;
640                     hr = CShellLink::_CreatorClass::CreateInstance(NULL, IID_PPV_ARG(IShellLinkW, &pLink));
641                     if (!FAILED_UNEXPECTEDLY(hr))
642                     {
643                         hr = pLink->SetIDList(pidlFull);
644                         if (!FAILED_UNEXPECTEDLY(hr))
645                             hr = pLink->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
646 
647                         PWSTR pwszPath, pSep;
648                         if ((att & SFGAO_FILESYSTEM) && SUCCEEDED(SHGetNameFromIDList(pidlFull, SIGDN_FILESYSPATH, &pwszPath)))
649                         {
650                             if ((pSep = PathFindFileNameW(pwszPath)) > pwszPath)
651                             {
652                                 pSep[-1] = UNICODE_NULL;
653                                 pLink->SetWorkingDirectory(pwszPath);
654                             }
655                             SHFree(pwszPath);
656                         }
657                     }
658                 }
659                 if (SUCCEEDED(hr))
660                     hr = ppf->Save(wszNewLnk, !fSourceIsLink);
661                 if (FAILED_UNEXPECTEDLY(hr))
662                 {
663                     SHELL_ErrorBox(m_hwndSite, hr);
664                     break;
665                 }
666                 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, wszNewLnk, NULL);
667             }
668         }
669         else
670         {
671             hr = _CopyItems(psfFrom, lpcida->cidl, (LPCITEMIDLIST*)apidl, bCopy);
672         }
673 
674         SHFree(pidl);
675         _ILFreeaPidl(apidl, lpcida->cidl);
676         ReleaseStgMedium(&medium);
677     }
678     else if (SUCCEEDED(pDataObject->QueryGetData(&fmt2)))
679     {
680         FORMATETC fmt2;
681         InitFormatEtc (fmt2, CF_HDROP, TYMED_HGLOBAL);
682         if (SUCCEEDED(pDataObject->GetData(&fmt2, &medium)) /* && SUCCEEDED(pDataObject->GetData(&fmt2, &medium))*/)
683         {
684             WCHAR wszTargetPath[MAX_PATH + 1];
685             LPWSTR pszSrcList;
686 
687             wcscpy(wszTargetPath, m_sPathTarget);
688             //Double NULL terminate.
689             wszTargetPath[wcslen(wszTargetPath) + 1] = '\0';
690 
691             LPDROPFILES lpdf = (LPDROPFILES) GlobalLock(medium.hGlobal);
692             if (!lpdf)
693             {
694                 ERR("Error locking global\n");
695                 return E_FAIL;
696             }
697             pszSrcList = (LPWSTR) (((byte*) lpdf) + lpdf->pFiles);
698             ERR("Source file (just the first) = %s, target path = %s, bCopy: %d\n", debugstr_w(pszSrcList), debugstr_w(wszTargetPath), bCopy);
699 
700             SHFILEOPSTRUCTW op;
701             ZeroMemory(&op, sizeof(op));
702             op.pFrom = pszSrcList;
703             op.pTo = wszTargetPath;
704             op.hwnd = m_hwndSite;
705             op.wFunc = bCopy ? FO_COPY : FO_MOVE;
706             op.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR;
707             int res = SHFileOperationW(&op);
708             if (res)
709             {
710                 ERR("SHFileOperationW failed with 0x%x\n", res);
711                 hr = E_FAIL;
712             }
713 
714             return hr;
715         }
716         ERR("Error calling GetData\n");
717         hr = E_FAIL;
718     }
719     else
720     {
721         ERR("No viable drop format\n");
722         hr = E_FAIL;
723     }
724     return hr;
725 }
726 
_DoDropThreadProc(LPVOID lpParameter)727 DWORD WINAPI CFSDropTarget::_DoDropThreadProc(LPVOID lpParameter)
728 {
729     CoInitialize(NULL);
730     _DoDropData *data = static_cast<_DoDropData*>(lpParameter);
731     CComPtr<IDataObject> pDataObject;
732     HRESULT hr = CoGetInterfaceAndReleaseStream (data->pStream, IID_PPV_ARG(IDataObject, &pDataObject));
733 
734     if (SUCCEEDED(hr))
735     {
736         CComPtr<IAsyncOperation> pAsyncOperation;
737         hr = data->This->_DoDrop(pDataObject, data->dwKeyState, data->pt, &data->pdwEffect);
738         if (SUCCEEDED(pDataObject->QueryInterface(IID_PPV_ARG(IAsyncOperation, &pAsyncOperation))))
739         {
740             pAsyncOperation->EndOperation(hr, NULL, data->pdwEffect);
741         }
742     }
743     //Release the CFSFolder and data object holds in the copying thread.
744     data->This->Release();
745     //Release the parameter from the heap.
746     HeapFree(GetProcessHeap(), 0, data);
747     CoUninitialize();
748     return 0;
749 }
750 
CFSDropTarget_CreateInstance(LPWSTR sPathTarget,REFIID riid,LPVOID * ppvOut)751 HRESULT CFSDropTarget_CreateInstance(LPWSTR sPathTarget, REFIID riid, LPVOID * ppvOut)
752 {
753     return ShellObjectCreatorInit<CFSDropTarget>(sPathTarget, riid, ppvOut);
754 }
755