1 /*
2  * Trash virtual folder support. The trashing engine is implemented in trash.c
3  *
4  * Copyright (C) 2006 Mikolaj Zalewski
5  * Copyright (C) 2009 Andrew Hill
6  * Copyright (C) 2018 Russell Johnson
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 #include <mmsystem.h>
26 #include <ntquery.h>
27 
28 WINE_DEFAULT_DEBUG_CHANNEL(CRecycleBin);
29 
30 typedef struct
31 {
32     int column_name_id;
33     const GUID *fmtId;
34     DWORD pid;
35     int pcsFlags;
36     int fmt;
37     int cxChars;
38 } columninfo;
39 
40 static const columninfo RecycleBinColumns[] =
41 {
42     {IDS_SHV_COLUMN_NAME,     &FMTID_Storage,   PID_STG_NAME,        SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT,  LVCFMT_LEFT,  25},
43     {IDS_SHV_COLUMN_DELFROM,  &FMTID_Displaced, PID_DISPLACED_FROM,  SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT,  LVCFMT_LEFT,  35},
44     {IDS_SHV_COLUMN_DELDATE,  &FMTID_Displaced, PID_DISPLACED_DATE,  SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT,  15},
45     {IDS_SHV_COLUMN_SIZE,     &FMTID_Storage,   PID_STG_SIZE,        SHCOLSTATE_TYPE_INT | SHCOLSTATE_ONBYDEFAULT,  LVCFMT_RIGHT, 10},
46     {IDS_SHV_COLUMN_TYPE,     &FMTID_Storage,   PID_STG_STORAGETYPE, SHCOLSTATE_TYPE_INT | SHCOLSTATE_ONBYDEFAULT,  LVCFMT_LEFT,  15},
47     {IDS_SHV_COLUMN_MODIFIED, &FMTID_Storage,   PID_STG_WRITETIME,   SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT,  15},
48     /* {"creation time",  &FMTID_Storage,   PID_STG_CREATETIME, SHCOLSTATE_TYPE_DATE, LVCFMT_LEFT,  20}, */
49     /* {"attribs",        &FMTID_Storage,   PID_STG_ATTRIBUTES, SHCOLSTATE_TYPE_STR,  LVCFMT_LEFT,  20}, */
50 };
51 
52 #define COLUMN_NAME    0
53 #define COLUMN_DELFROM 1
54 #define COLUMN_DATEDEL 2
55 #define COLUMN_SIZE    3
56 #define COLUMN_TYPE    4
57 #define COLUMN_MTIME   5
58 
59 #define COLUMNS_COUNT  6
60 
61 // The ROS Recycle Bin PIDL format starts with a NT4/2000 Unicode FS PIDL followed by
62 // BBITEMDATA and BBITEMFOOTER. This makes it compatible with SHChangeNotify listeners.
63 #include "pshpack1.h"
64 #define BBITEMFILETYPE (PT_FS | PT_FS_UNICODE_FLAG | PT_FS_FILE_FLAG)
65 #define BBITEMFOLDERTYPE (PT_FS | PT_FS_UNICODE_FLAG | PT_FS_FOLDER_FLAG)
66 struct BBITEMDATA
67 {
68     FILETIME DeletionTime;
69 #ifdef COLUMN_FATTS
70     WORD AttribsHi; // Nobody needs this yet
71 #endif
72     WORD RecycledPathOffset;
73     WCHAR OriginalLocation[ANYSIZE_ARRAY];
74     // ... @RecycledPathOffset WCHAR RecycledPath[ANYSIZE_ARRAY];
75 };
76 struct BBITEMFOOTER
77 {
78     enum { ENDSIG = MAKEWORD('K', 'I') }; // "Killed item". MUST have the low bit set so _ILGetFileStructW returns NULL.
79     WORD DataSize;
80     WORD EndSignature;
81 };
82 #include "poppack.h"
83 
IsFolder(LPCITEMIDLIST pidl)84 static inline BOOL IsFolder(LPCITEMIDLIST pidl)
85 {
86     return _ILGetFSType(pidl) & PT_FS_FOLDER_FLAG;
87 }
88 
ValidateItem(LPCITEMIDLIST pidl)89 static BBITEMDATA* ValidateItem(LPCITEMIDLIST pidl)
90 {
91     const UINT minstringsize = sizeof(L"X") + sizeof(""); // PT_FS strings
92     const UINT minfs = sizeof(WORD) + FIELD_OFFSET(PIDLDATA, u.file.szNames) + minstringsize;
93     const UINT mindatasize = FIELD_OFFSET(BBITEMDATA, OriginalLocation) + (sizeof(L"C:\\X") * 2);
94     const UINT minsize = minfs + mindatasize + sizeof(BBITEMFOOTER);
95     const BYTE type = _ILGetType(pidl);
96     if ((type == BBITEMFILETYPE || type == BBITEMFOLDERTYPE) && pidl->mkid.cb >= minsize)
97     {
98         BBITEMFOOTER *pEnd = (BBITEMFOOTER*)((BYTE*)pidl + pidl->mkid.cb - sizeof(BBITEMFOOTER));
99         if (pEnd->EndSignature == BBITEMFOOTER::ENDSIG && pEnd->DataSize >= mindatasize)
100             return (BBITEMDATA*)((BYTE*)pEnd - pEnd->DataSize);
101     }
102     return NULL;
103 }
104 
CreateItem(LPCWSTR pszTrash,LPCWSTR pszOrig,const DELETED_FILE_INFO & Details)105 static LPITEMIDLIST CreateItem(LPCWSTR pszTrash, LPCWSTR pszOrig, const DELETED_FILE_INFO &Details)
106 {
107     const BOOL folder = Details.Attributes & FILE_ATTRIBUTE_DIRECTORY;
108     LPCWSTR pszName = PathFindFileNameW(pszTrash);
109     SIZE_T ofsName = (SIZE_T)(pszName - pszTrash);
110     SIZE_T cchName = wcslen(pszName) + 1, cbName = cchName * sizeof(WCHAR);
111     SIZE_T cbFSNames = cbName + sizeof("") + 1; // Empty short name + 1 for WORD alignment
112     SIZE_T cbFS = sizeof(WORD) + FIELD_OFFSET(PIDLDATA, u.file.szNames) + cbFSNames;
113     SIZE_T cchTrash = ofsName + cchName, cbTrash = cchTrash * sizeof(WCHAR);
114     SIZE_T cchOrig = wcslen(pszOrig) + 1, cbOrig = cchOrig * sizeof(WCHAR);
115     SIZE_T cbData = FIELD_OFFSET(BBITEMDATA, OriginalLocation) + cbOrig + cbTrash;
116     SIZE_T cb = cbFS + cbData + sizeof(BBITEMFOOTER);
117     if (cb > 0xffff)
118         return NULL;
119     LPITEMIDLIST pidl = (LPITEMIDLIST)SHAlloc(cb + sizeof(WORD));
120     if (!pidl)
121         return pidl;
122 
123     pidl->mkid.cb = cb;
124     pidl->mkid.abID[0] = folder ? BBITEMFOLDERTYPE : BBITEMFILETYPE;
125     ILGetNext(pidl)->mkid.cb = 0; // Terminator
126     FileStruct &fsitem = ((PIDLDATA*)pidl->mkid.abID)->u.file;
127     fsitem.dummy = 0;
128     C_ASSERT(sizeof(RECYCLEBINFILESIZETYPE) <= sizeof(fsitem.dwFileSize));
129     fsitem.dwFileSize = Details.FileSize;
130     fsitem.uFileAttribs = LOWORD(Details.Attributes);
131     FileTimeToDosDateTime(&Details.LastModification, &fsitem.uFileDate, &fsitem.uFileTime);
132     CopyMemory(fsitem.szNames, pszName, cbName);
133     LPSTR pszShort = const_cast<LPSTR>(&fsitem.szNames[cbName]);
134     pszShort[0] = '\0';
135     pszShort[1] = '\0'; // Fill alignment padding (for ILIsEqual memcmp)
136 
137     BBITEMFOOTER *footer = (BBITEMFOOTER*)((BYTE*)pidl + cb - sizeof(BBITEMFOOTER));
138     footer->DataSize = cbData;
139     footer->EndSignature = BBITEMFOOTER::ENDSIG;
140 
141     BBITEMDATA *data = (BBITEMDATA*)((BYTE*)footer - footer->DataSize);
142     data->DeletionTime = Details.DeletionTime;
143 #ifdef COLUMN_FATTS
144     data->AttribsHi = HIWORD(Details.Attributes);
145 #endif
146     data->RecycledPathOffset = FIELD_OFFSET(BBITEMDATA, OriginalLocation) + cbOrig;
147     CopyMemory(data->OriginalLocation, pszOrig, cbOrig);
148     CopyMemory((BYTE*)data + data->RecycledPathOffset, pszTrash, cbTrash);
149 
150     assert(!(((SIZE_T)&fsitem.szNames) & 1)); // WORD aligned please
151     C_ASSERT(!(FIELD_OFFSET(BBITEMDATA, OriginalLocation) & 1)); // WORD aligned please
152     assert(!(((SIZE_T)data) & 1)); // WORD aligned please
153     assert(_ILGetFSType(pidl));
154     assert(_ILIsPidlSimple(pidl));
155     assert(*(WORD*)((BYTE*)pidl + pidl->mkid.cb - sizeof(WORD)) & 1); // ENDSIG bit
156     assert(_ILGetFileStructW(pidl) == NULL); // Our custom footer is incompatible with WinXP pidl data
157     assert(ValidateItem(pidl) == data);
158     return pidl;
159 }
160 
GetItemFileSize(LPCITEMIDLIST pidl)161 static inline UINT GetItemFileSize(LPCITEMIDLIST pidl)
162 {
163     return _ILGetFSType(pidl) ? ((PIDLDATA*)pidl->mkid.abID)->u.file.dwFileSize : 0;
164 }
165 
GetItemOriginalFullPath(const BBITEMDATA & Data)166 static inline LPCWSTR GetItemOriginalFullPath(const BBITEMDATA &Data)
167 {
168     return Data.OriginalLocation;
169 }
170 
GetItemOriginalFolder(const BBITEMDATA & Data,LPWSTR & Out)171 static HRESULT GetItemOriginalFolder(const BBITEMDATA &Data, LPWSTR &Out)
172 {
173     HRESULT hr = SHStrDupW(GetItemOriginalFullPath(Data), &Out);
174     if (SUCCEEDED(hr))
175         PathRemoveFileSpecW(Out);
176     return hr;
177 }
178 
GetItemOriginalFileName(const BBITEMDATA & Data)179 static LPCWSTR GetItemOriginalFileName(const BBITEMDATA &Data)
180 {
181     return PathFindFileNameW(GetItemOriginalFullPath(Data));
182 }
183 
GetItemRecycledFullPath(const BBITEMDATA & Data)184 static inline LPCWSTR GetItemRecycledFullPath(const BBITEMDATA &Data)
185 {
186     return (LPCWSTR)((BYTE*)&Data + Data.RecycledPathOffset);
187 }
188 
GetItemRecycledFileName(LPCITEMIDLIST pidl,const BBITEMDATA & Data)189 static inline LPCWSTR GetItemRecycledFileName(LPCITEMIDLIST pidl, const BBITEMDATA &Data)
190 {
191     C_ASSERT(BBITEMFILETYPE & PT_FS_UNICODE_FLAG);
192     return (LPCWSTR)((LPPIDLDATA)pidl->mkid.abID)->u.file.szNames;
193 }
194 
GetItemDriveNumber(LPCITEMIDLIST pidl)195 static int GetItemDriveNumber(LPCITEMIDLIST pidl)
196 {
197     if (BBITEMDATA *pData = ValidateItem(pidl))
198         return PathGetDriveNumberW(GetItemRecycledFullPath(*pData));
199     WCHAR buf[MAX_PATH];
200     return _ILSimpleGetTextW(pidl, buf, _countof(buf)) ? PathGetDriveNumberW(buf) : -1;
201 }
202 
GetItemTypeName(PCUITEMID_CHILD pidl,const BBITEMDATA & Data,SHFILEINFOW & shfi)203 static HRESULT GetItemTypeName(PCUITEMID_CHILD pidl, const BBITEMDATA &Data, SHFILEINFOW &shfi)
204 {
205     LPCWSTR path = GetItemRecycledFullPath(Data);
206     UINT attribs = ((PIDLDATA*)pidl->mkid.abID)->u.file.uFileAttribs;
207     if (SHGetFileInfoW(path, attribs, &shfi, sizeof(shfi), SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES))
208         return S_OK;
209     shfi.szTypeName[0] = UNICODE_NULL;
210     return E_FAIL;
211 }
212 
GetRecycleBinFileHandleFromItem(const BBITEMDATA & Data)213 static HDELFILE GetRecycleBinFileHandleFromItem(const BBITEMDATA &Data)
214 {
215     RECYCLEBINFILEIDENTITY identity = { Data.DeletionTime, GetItemRecycledFullPath(Data) };
216     return GetRecycleBinFileHandle(NULL, &identity);
217 }
218 
219 /*
220  * Recycle Bin folder
221  */
222 
GetDefaultRecycleDriveNumber()223 static UINT GetDefaultRecycleDriveNumber()
224 {
225     int drive = 0;
226     WCHAR buf[MAX_PATH];
227     if (GetWindowsDirectoryW(buf, _countof(buf)))
228         drive = PathGetDriveNumberW(buf);
229     return max(0, drive);
230 }
231 
GetGlobalRecycleBinPath()232 static inline LPCWSTR GetGlobalRecycleBinPath()
233 {
234     return NULL;
235 }
236 
IsRecycleBinEmpty(IShellFolder * pSF)237 static BOOL IsRecycleBinEmpty(IShellFolder *pSF)
238 {
239     CComPtr<IEnumIDList> spEnumFiles;
240     HRESULT hr = pSF->EnumObjects(NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &spEnumFiles);
241     CComHeapPtr<ITEMIDLIST> spPidl;
242     ULONG itemcount;
243     return FAILED(hr) || spEnumFiles->Next(1, &spPidl, &itemcount) != S_OK;
244 }
245 
CRecycleBin_ChangeNotifyBBItem(_In_ LONG Event,_In_opt_ LPCITEMIDLIST BBItem)246 static void CRecycleBin_ChangeNotifyBBItem(_In_ LONG Event, _In_opt_ LPCITEMIDLIST BBItem)
247 {
248     LPITEMIDLIST pidlFolder = SHCloneSpecialIDList(NULL, CSIDL_BITBUCKET, FALSE);
249     if (!pidlFolder)
250         return;
251     if (BBItem)
252     {
253         assert(ValidateItem(BBItem));
254         if (LPITEMIDLIST pidlFull = ILCombine(pidlFolder, BBItem))
255         {
256             // Send notification for [Desktop][RecycleBin][BBItem]
257             // FIXME: Windows monitors each RecycleBin FS folder on every drive
258             //        instead of manually sending these?
259             SHChangeNotify(Event, SHCNF_IDLIST, pidlFull, NULL);
260             ILFree(pidlFull);
261         }
262     }
263     else
264     {
265         SHChangeNotify(Event, SHCNF_IDLIST, pidlFolder, NULL);
266     }
267     ILFree(pidlFolder);
268 }
269 
CRecycleBin_NotifyRecycled(LPCWSTR OrigPath,const WIN32_FIND_DATAW * pFind,const RECYCLEBINFILEIDENTITY * pFI)270 EXTERN_C void CRecycleBin_NotifyRecycled(LPCWSTR OrigPath, const WIN32_FIND_DATAW *pFind,
271                                          const RECYCLEBINFILEIDENTITY *pFI)
272 {
273     DELETED_FILE_INFO info;
274     info.LastModification = pFind->ftLastWriteTime;
275     info.DeletionTime = pFI->DeletionTime;
276     info.FileSize = pFind->nFileSizeLow;
277     info.Attributes = pFind->dwFileAttributes;
278     if (LPITEMIDLIST pidl = CreateItem(pFI->RecycledFullPath, OrigPath, info))
279     {
280         CRecycleBin_ChangeNotifyBBItem(IsFolder(pidl) ? SHCNE_MKDIR : SHCNE_CREATE, pidl);
281         ILFree(pidl);
282     }
283 }
284 
CRecycleBin_NotifyRemovedFromRecycleBin(LPCITEMIDLIST BBItem)285 static void CRecycleBin_NotifyRemovedFromRecycleBin(LPCITEMIDLIST BBItem)
286 {
287     CRecycleBin_ChangeNotifyBBItem(IsFolder(BBItem) ? SHCNE_RMDIR : SHCNE_DELETE, BBItem);
288 
289     CComHeapPtr<ITEMIDLIST> pidlBB(SHCloneSpecialIDList(NULL, CSIDL_BITBUCKET, FALSE));
290     CComPtr<IShellFolder> pSF;
291     if (pidlBB && SUCCEEDED(SHBindToObject(NULL, pidlBB, IID_PPV_ARG(IShellFolder, &pSF))))
292     {
293         if (IsRecycleBinEmpty(pSF))
294             SHUpdateRecycleBinIcon();
295     }
296 }
297 
CRecyclerExtractIcon_CreateInstance(IShellFolder & FSFolder,LPCITEMIDLIST pidl,REFIID riid,LPVOID * ppvOut)298 static HRESULT CRecyclerExtractIcon_CreateInstance(
299     IShellFolder &FSFolder, LPCITEMIDLIST pidl, REFIID riid, LPVOID *ppvOut)
300 {
301     HRESULT hr = FSFolder.GetUIObjectOf(NULL, 1, &pidl, riid, NULL, ppvOut);
302     if (SUCCEEDED(hr))
303         return hr;
304 
305     // In case the search fails we use a default icon
306     ERR("Recycler could not retrieve the icon, this shouldn't happen\n");
307 
308     if (IsFolder(pidl))
309         return SHELL_CreateFallbackExtractIconForFolder(riid, ppvOut);
310     else
311         return SHELL_CreateFallbackExtractIconForNoAssocFile(riid, ppvOut);
312 }
313 
314 class CRecycleBinItemContextMenu :
315     public CComObjectRootEx<CComMultiThreadModelNoCS>,
316     public IContextMenu2
317 {
318     private:
319         LPITEMIDLIST                        apidl;
320     public:
321         CRecycleBinItemContextMenu();
322         ~CRecycleBinItemContextMenu();
323         HRESULT WINAPI Initialize(LPCITEMIDLIST pidl);
324 
325         // IContextMenu
326         STDMETHOD(QueryContextMenu)(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) override;
327         STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO lpcmi) override;
328         STDMETHOD(GetCommandString)(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen) override;
329 
330         // IContextMenu2
331         STDMETHOD(HandleMenuMsg)(UINT uMsg, WPARAM wParam, LPARAM lParam) override;
332 
333         BEGIN_COM_MAP(CRecycleBinItemContextMenu)
334         COM_INTERFACE_ENTRY_IID(IID_IContextMenu, IContextMenu)
335         COM_INTERFACE_ENTRY_IID(IID_IContextMenu2, IContextMenu2)
336         END_COM_MAP()
337 };
338 
339 class CRecycleBinEnum :
340     public CEnumIDListBase
341 {
342     public:
343         CRecycleBinEnum();
344         ~CRecycleBinEnum();
345         HRESULT WINAPI Initialize(DWORD dwFlags);
346         BOOL CBEnumRecycleBin(IN HDELFILE hDeletedFile);
CBEnumRecycleBin(IN PVOID Context,IN HDELFILE hDeletedFile)347         static BOOL CALLBACK CBEnumRecycleBin(IN PVOID Context, IN HDELFILE hDeletedFile)
348         {
349             return static_cast<CRecycleBinEnum*>(Context)->CBEnumRecycleBin(hDeletedFile);
350         }
351 
352         BEGIN_COM_MAP(CRecycleBinEnum)
353         COM_INTERFACE_ENTRY_IID(IID_IEnumIDList, IEnumIDList)
354         END_COM_MAP()
355 };
356 
CRecycleBinEnum()357 CRecycleBinEnum::CRecycleBinEnum()
358 {
359 }
360 
~CRecycleBinEnum()361 CRecycleBinEnum::~CRecycleBinEnum()
362 {
363 }
364 
Initialize(DWORD dwFlags)365 HRESULT WINAPI CRecycleBinEnum::Initialize(DWORD dwFlags)
366 {
367     LPCWSTR szDrive = GetGlobalRecycleBinPath();
368     if (dwFlags & SHCONTF_NONFOLDERS)
369     {
370         TRACE("Starting Enumeration\n");
371 
372         if (!EnumerateRecycleBinW(szDrive, CBEnumRecycleBin, this))
373         {
374             WARN("Error: EnumerateCRecycleBinW failed\n");
375             return E_FAIL;
376         }
377     }
378     else
379     {
380         // do nothing
381     }
382     return S_OK;
383 }
384 
CBEnumRecycleBin(IN HDELFILE hDeletedFile)385 BOOL CRecycleBinEnum::CBEnumRecycleBin(IN HDELFILE hDeletedFile)
386 {
387     LPITEMIDLIST pidl = NULL;
388     DELETED_FILE_INFO info;
389     IRecycleBinFile *pRBF = IRecycleBinFileFromHDELFILE(hDeletedFile);
390     BOOL ret = SUCCEEDED(pRBF->GetInfo(&info));
391     if (ret)
392     {
393         pidl = CreateItem(info.RecycledFullPath.String, info.OriginalFullPath.String, info);
394         ret = pidl != NULL;
395         FreeRecycleBinString(&info.OriginalFullPath);
396         FreeRecycleBinString(&info.RecycledFullPath);
397     }
398     if (pidl)
399     {
400         ret = AddToEnumList(pidl);
401         if (!ret)
402             ILFree(pidl);
403     }
404     CloseRecycleBinHandle(hDeletedFile);
405     return ret;
406 }
407 
408 /**************************************************************************
409 * IContextMenu2 Bitbucket Item Implementation
410 */
411 
CRecycleBinItemContextMenu()412 CRecycleBinItemContextMenu::CRecycleBinItemContextMenu()
413 {
414     apidl = NULL;
415 }
416 
~CRecycleBinItemContextMenu()417 CRecycleBinItemContextMenu::~CRecycleBinItemContextMenu()
418 {
419     ILFree(apidl);
420 }
421 
Initialize(LPCITEMIDLIST pidl)422 HRESULT WINAPI CRecycleBinItemContextMenu::Initialize(LPCITEMIDLIST pidl)
423 {
424     apidl = ILClone(pidl);
425     if (apidl == NULL)
426         return E_OUTOFMEMORY;
427     return S_OK;
428 }
429 
430 enum { IDC_BB_RESTORE = 1, IDC_BB_CUT, IDC_BB_DELETE, IDC_BB_PROPERTIES };
431 static const CMVERBMAP g_BBItemVerbMap[] =
432 {
433     { "undelete", IDC_BB_RESTORE },
434     { "cut", IDC_BB_CUT },
435     { "delete", IDC_BB_DELETE },
436     { "properties", IDC_BB_PROPERTIES },
437     { NULL }
438 };
439 
QueryContextMenu(HMENU hMenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags)440 HRESULT WINAPI CRecycleBinItemContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
441 {
442     UINT idHigh = 0, id;
443 
444     TRACE("(%p)->(hmenu=%p indexmenu=%x cmdfirst=%x cmdlast=%x flags=%x )\n", this, hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
445 
446     id = idCmdFirst + IDC_BB_RESTORE;
447     if (_InsertMenuItemW(hMenu, indexMenu, TRUE, id, MFT_STRING, MAKEINTRESOURCEW(IDS_RESTORE), 0))
448     {
449         idHigh = max(idHigh, id);
450         indexMenu++;
451     }
452     id = idCmdFirst + IDC_BB_CUT;
453     if (_InsertMenuItemW(hMenu, indexMenu, TRUE, id, MFT_STRING, MAKEINTRESOURCEW(IDS_CUT), MFS_DISABLED))
454     {
455         idHigh = max(idHigh, id);
456         if (_InsertMenuItemW(hMenu, indexMenu++, TRUE, -1, MFT_SEPARATOR, NULL, 0))
457             indexMenu++;
458     }
459     id = idCmdFirst + IDC_BB_DELETE;
460     if (_InsertMenuItemW(hMenu, indexMenu, TRUE, id, MFT_STRING, MAKEINTRESOURCEW(IDS_DELETE), 0))
461     {
462         idHigh = max(idHigh, id);
463         if (_InsertMenuItemW(hMenu, indexMenu++, TRUE, -1, MFT_SEPARATOR, NULL, 0))
464             indexMenu++;
465     }
466     id = idCmdFirst + IDC_BB_PROPERTIES;
467     if (_InsertMenuItemW(hMenu, indexMenu, TRUE, id, MFT_STRING, MAKEINTRESOURCEW(IDS_PROPERTIES), 0))
468     {
469         idHigh = max(idHigh, id);
470         if (_InsertMenuItemW(hMenu, indexMenu++, TRUE, -1, MFT_SEPARATOR, NULL, 0))
471             indexMenu++;
472     }
473     return idHigh ? MAKE_HRESULT(SEVERITY_SUCCESS, 0, idHigh - idCmdFirst + 1) : S_OK;
474 }
475 
ConfirmDelete(LPCMINVOKECOMMANDINFO lpcmi,UINT cidl,LPCITEMIDLIST pidl,const BBITEMDATA & Data)476 static BOOL ConfirmDelete(LPCMINVOKECOMMANDINFO lpcmi, UINT cidl, LPCITEMIDLIST pidl, const BBITEMDATA &Data)
477 {
478     if (lpcmi->fMask & CMIC_MASK_FLAG_NO_UI)
479     {
480         return TRUE;
481     }
482     else if (cidl == 1)
483     {
484         const UINT ask = IsFolder(pidl) ? ASK_DELETE_FOLDER : ASK_DELETE_FILE;
485         return SHELL_ConfirmYesNoW(lpcmi->hwnd, ask, GetItemOriginalFileName(Data));
486     }
487     WCHAR buf[MAX_PATH];
488     wsprintfW(buf, L"%d", cidl);
489     return SHELL_ConfirmYesNoW(lpcmi->hwnd, ASK_DELETE_MULTIPLE_ITEM, buf);
490 }
491 
InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)492 HRESULT WINAPI CRecycleBinItemContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
493 {
494     TRACE("(%p)->(invcom=%p verb=%p wnd=%p)\n", this, lpcmi, lpcmi->lpVerb, lpcmi->hwnd);
495 
496     int CmdId = SHELL_MapContextMenuVerbToCmdId(lpcmi, g_BBItemVerbMap);
497 
498     // Handle DefView accelerators
499     if ((SIZE_T)lpcmi->lpVerb == FCIDM_SHVIEW_CUT)
500         CmdId = IDC_BB_CUT;
501     if ((SIZE_T)lpcmi->lpVerb == FCIDM_SHVIEW_DELETE)
502         CmdId = IDC_BB_DELETE;
503     if ((SIZE_T)lpcmi->lpVerb == FCIDM_SHVIEW_PROPERTIES)
504         CmdId = IDC_BB_PROPERTIES;
505 
506     if (CmdId == IDC_BB_RESTORE || CmdId == IDC_BB_DELETE)
507     {
508         BBITEMDATA *pData = ValidateItem(apidl);
509         if (!pData && FAILED_UNEXPECTEDLY(E_FAIL))
510             return E_FAIL;
511         HDELFILE hDelFile = GetRecycleBinFileHandleFromItem(*pData);
512         if (!hDelFile && FAILED_UNEXPECTEDLY(E_FAIL))
513             return E_FAIL;
514 
515         HRESULT hr = S_FALSE;
516         if (CmdId == IDC_BB_RESTORE)
517             hr = RestoreFileFromRecycleBin(hDelFile) ? S_OK : E_FAIL;
518         else if (ConfirmDelete(lpcmi, 1, apidl, *pData))
519             hr = DeleteFileInRecycleBin(hDelFile) ? S_OK : E_FAIL;
520 
521         if (hr == S_OK)
522             CRecycleBin_NotifyRemovedFromRecycleBin(apidl);
523 
524         CloseRecycleBinHandle(hDelFile);
525         return hr;
526     }
527     else if (CmdId == IDC_BB_CUT)
528     {
529         FIXME("implement cut\n");
530         SHELL_ErrorBox(lpcmi->hwnd, ERROR_NOT_SUPPORTED);
531         return E_NOTIMPL;
532     }
533     else if (CmdId == IDC_BB_PROPERTIES)
534     {
535         FIXME("implement properties\n");
536         SHELL_ErrorBox(lpcmi->hwnd, ERROR_NOT_SUPPORTED);
537         return E_NOTIMPL;
538     }
539     return E_UNEXPECTED;
540 }
541 
GetCommandString(UINT_PTR idCommand,UINT uFlags,UINT * lpReserved,LPSTR lpszName,UINT uMaxNameLen)542 HRESULT WINAPI CRecycleBinItemContextMenu::GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen)
543 {
544     TRACE("(%p)->(idcom=%lx flags=%x %p name=%p len=%x)\n", this, idCommand, uFlags, lpReserved, lpszName, uMaxNameLen);
545     return SHELL_GetCommandStringImpl(idCommand, uFlags, lpszName, uMaxNameLen, g_BBItemVerbMap);
546 }
547 
HandleMenuMsg(UINT uMsg,WPARAM wParam,LPARAM lParam)548 HRESULT WINAPI CRecycleBinItemContextMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
549 {
550     TRACE("CRecycleBin_IContextMenu2Item_HandleMenuMsg (%p)->(msg=%x wp=%lx lp=%lx)\n", this, uMsg, wParam, lParam);
551 
552     return E_NOTIMPL;
553 }
554 
CRecycleBin()555 CRecycleBin::CRecycleBin()
556 {
557     pidl = NULL;
558     ZeroMemory(m_pFSFolders, sizeof(m_pFSFolders));
559 }
560 
~CRecycleBin()561 CRecycleBin::~CRecycleBin()
562 {
563     SHFree(pidl);
564     for (SIZE_T i = 0; i < _countof(m_pFSFolders); ++i)
565     {
566         if (m_pFSFolders[i])
567             m_pFSFolders[i]->Release();
568     }
569 }
570 
GetFSFolderForItem(LPCITEMIDLIST pidl)571 IShellFolder* CRecycleBin::GetFSFolderForItem(LPCITEMIDLIST pidl)
572 {
573     int drive = GetItemDriveNumber(pidl);
574     if (drive < 0)
575         drive = GetDefaultRecycleDriveNumber();
576     if ((UINT)drive >= _countof(m_pFSFolders) && FAILED_UNEXPECTEDLY(E_FAIL))
577         return NULL;
578 
579     if (!m_pFSFolders[drive])
580     {
581         HRESULT hr;
582         PERSIST_FOLDER_TARGET_INFO pfti = {};
583         if (FAILED_UNEXPECTEDLY(hr = GetRecycleBinPathFromDriveNumber(drive, pfti.szTargetParsingName)))
584             return NULL;
585         pfti.dwAttributes = FILE_ATTRIBUTE_DIRECTORY;
586         pfti.csidl = -1;
587         CComHeapPtr<ITEMIDLIST> pidlRoot;
588         pidlRoot.Attach(SHELL32_CreateSimpleIDListFromPath(pfti.szTargetParsingName, pfti.dwAttributes));
589         if (!pidlRoot && FAILED_UNEXPECTEDLY(E_FAIL))
590             return NULL;
591         IShellFolder *psf;
592         hr = SHELL32_CoCreateInitSF(pidlRoot, &pfti, NULL, &CLSID_ShellFSFolder, IID_PPV_ARG(IShellFolder, &psf));
593         if (FAILED(hr))
594             return NULL;
595         m_pFSFolders[drive] = psf; // Reference count is 1 (for the m_pFSFolders cache)
596     }
597     m_pFSFolders[drive]->AddRef(); // AddRef for the caller
598     return m_pFSFolders[drive];
599 }
600 
601 /*************************************************************************
602  * RecycleBin IPersistFolder2 interface
603  */
604 
GetClassID(CLSID * pClassID)605 HRESULT WINAPI CRecycleBin::GetClassID(CLSID *pClassID)
606 {
607     TRACE("(%p, %p)\n", this, pClassID);
608     if (pClassID == NULL)
609         return E_INVALIDARG;
610     *pClassID = GetClassID();
611     return S_OK;
612 }
613 
Initialize(PCIDLIST_ABSOLUTE pidl)614 HRESULT WINAPI CRecycleBin::Initialize(PCIDLIST_ABSOLUTE pidl)
615 {
616     TRACE("(%p, %p)\n", this, pidl);
617 
618     SHFree((LPVOID)this->pidl);
619     this->pidl = ILClone(pidl);
620     if (this->pidl == NULL)
621         return E_OUTOFMEMORY;
622     return S_OK;
623 }
624 
GetCurFolder(PIDLIST_ABSOLUTE * ppidl)625 HRESULT WINAPI CRecycleBin::GetCurFolder(PIDLIST_ABSOLUTE *ppidl)
626 {
627     TRACE("\n");
628     return SHILClone((LPCITEMIDLIST)pidl, ppidl);
629 }
630 
631 /*************************************************************************
632  * RecycleBin IShellFolder2 interface
633  */
634 
ParseDisplayName(HWND hwnd,LPBC pbc,LPOLESTR pszDisplayName,ULONG * pchEaten,PIDLIST_RELATIVE * ppidl,ULONG * pdwAttributes)635 HRESULT WINAPI CRecycleBin::ParseDisplayName(HWND hwnd, LPBC pbc,
636         LPOLESTR pszDisplayName, ULONG *pchEaten, PIDLIST_RELATIVE *ppidl,
637         ULONG *pdwAttributes)
638 {
639     FIXME("stub\n");
640     return E_NOTIMPL; // FIXME: Parse "D<Drive><UniqueId>.ext"
641 }
642 
EnumObjects(HWND hwndOwner,DWORD dwFlags,LPENUMIDLIST * ppEnumIDList)643 HRESULT WINAPI CRecycleBin::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList)
644 {
645     return ShellObjectCreatorInit<CRecycleBinEnum>(dwFlags, IID_PPV_ARG(IEnumIDList, ppEnumIDList));
646 }
647 
BindToObject(PCUIDLIST_RELATIVE pidl,LPBC pbc,REFIID riid,void ** ppv)648 HRESULT WINAPI CRecycleBin::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbc, REFIID riid, void **ppv)
649 {
650     FIXME("(%p, %p, %p, %s, %p) - stub\n", this, pidl, pbc, debugstr_guid(&riid), ppv);
651     return E_NOTIMPL;
652 }
653 
BindToStorage(PCUIDLIST_RELATIVE pidl,LPBC pbc,REFIID riid,void ** ppv)654 HRESULT WINAPI CRecycleBin::BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbc, REFIID riid, void **ppv)
655 {
656     FIXME("(%p, %p, %p, %s, %p) - stub\n", this, pidl, pbc, debugstr_guid(&riid), ppv);
657     return E_NOTIMPL;
658 }
659 
CompareCanonical(const BBITEMDATA & Data1,const BBITEMDATA & Data2)660 static HRESULT CompareCanonical(const BBITEMDATA &Data1, const BBITEMDATA &Data2)
661 {
662     // This assumes two files with the same original path cannot be deleted at
663     // the same time (within the FAT/NTFS FILETIME resolution).
664     int result = CompareFileTime(&Data1.DeletionTime, &Data2.DeletionTime);
665     if (result == 0)
666         result = _wcsicmp(GetItemOriginalFullPath(Data1), GetItemOriginalFullPath(Data2));
667     return MAKE_COMPARE_HRESULT(result);
668 }
669 
CompareIDs(LPARAM lParam,PCUIDLIST_RELATIVE pidl1,PCUIDLIST_RELATIVE pidl2)670 HRESULT WINAPI CRecycleBin::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
671 {
672     UINT column = UINT(lParam & SHCIDS_COLUMNMASK);
673     if (column >= COLUMNS_COUNT || !_ILGetFSType(pidl1) || !_ILGetFSType(pidl2))
674         return E_INVALIDARG;
675     BBITEMDATA *pData1 = ValidateItem(pidl1), *pData2 = ValidateItem(pidl2);
676     if ((!pData1 || !pData2) && column != COLUMN_NAME)
677         return E_INVALIDARG;
678 
679     LPCWSTR pName1, pName2;
680     FILETIME ft1, ft2;
681     SHFILEINFOW shfi1, shfi2;
682     int result;
683     HRESULT hr = CFSFolder::CompareSortFoldersFirst(pidl1, pidl2);
684     if (SUCCEEDED(hr))
685         return hr;
686     switch (column)
687     {
688         case COLUMN_NAME:
689             if (pData1 && pData2)
690             {
691                 if (lParam & SHCIDS_CANONICALONLY)
692                     return CompareCanonical(*pData1, *pData2);
693                 pName1 = GetItemOriginalFileName(*pData1);
694                 pName2 = GetItemOriginalFileName(*pData2);
695                 result = CFSFolder::CompareUiStrings(pName1, pName2);
696             }
697             else
698             {
699                 // We support comparing names even for non-Recycle items because
700                 // SHChangeNotify can broadcast regular FS items.
701                 if (IShellFolder *pSF = GetFSFolderForItem(pidl1))
702                 {
703                     hr = pSF->CompareIDs(lParam, pidl1, pidl2);
704                     pSF->Release();
705                     return hr;
706                 }
707                 return E_INVALIDARG;
708             }
709             break;
710         case COLUMN_DELFROM:
711             if (SUCCEEDED(hr = GetItemOriginalFolder(*pData1, const_cast<LPWSTR&>(pName1))))
712             {
713                 if (SUCCEEDED(hr = GetItemOriginalFolder(*pData2, const_cast<LPWSTR&>(pName2))))
714                 {
715                     result = CFSFolder::CompareUiStrings(pName1, pName2);
716                     SHFree(const_cast<LPWSTR>(pName2));
717                 }
718                 SHFree(const_cast<LPWSTR>(pName1));
719             }
720             return SUCCEEDED(hr) ? MAKE_COMPARE_HRESULT(result) : hr;
721         case COLUMN_DATEDEL:
722             result = CompareFileTime(&pData1->DeletionTime, &pData2->DeletionTime);
723             break;
724         case COLUMN_SIZE:
725             result = GetItemFileSize(pidl1) - GetItemFileSize(pidl2);
726             break;
727         case COLUMN_TYPE:
728             GetItemTypeName(pidl1, *pData1, shfi1);
729             GetItemTypeName(pidl2, *pData2, shfi2);
730             result = CFSFolder::CompareUiStrings(shfi1.szTypeName, shfi2.szTypeName);
731             break;
732         case COLUMN_MTIME:
733             _ILGetFileDateTime(pidl1, &ft1);
734             _ILGetFileDateTime(pidl2, &ft2);
735             result = CompareFileTime(&ft1, &ft2);
736             break;
737     }
738     return MAKE_COMPARE_HRESULT(result);
739 }
740 
CreateViewObject(HWND hwndOwner,REFIID riid,void ** ppv)741 HRESULT WINAPI CRecycleBin::CreateViewObject(HWND hwndOwner, REFIID riid, void **ppv)
742 {
743     CComPtr<IShellView> pShellView;
744     HRESULT hr = E_NOINTERFACE;
745 
746     TRACE("(%p, %p, %s, %p)\n", this, hwndOwner, debugstr_guid(&riid), ppv);
747 
748     if (!ppv)
749         return hr;
750     *ppv = NULL;
751 
752     if (IsEqualIID (riid, IID_IDropTarget))
753     {
754         hr = CRecyclerDropTarget_CreateInstance(riid, ppv);
755     }
756     else if (IsEqualIID (riid, IID_IContextMenu) || IsEqualIID (riid, IID_IContextMenu2))
757     {
758         m_IsBackgroundMenu = true;
759         hr = this->QueryInterface(riid, ppv);
760     }
761     else if (IsEqualIID (riid, IID_IShellView))
762     {
763         SFV_CREATE sfvparams = { sizeof(SFV_CREATE), this };
764         hr = SHCreateShellFolderView(&sfvparams, (IShellView**)ppv);
765     }
766     else
767         return hr;
768 
769     TRACE ("-- (%p)->(interface=%p)\n", this, ppv);
770     return hr;
771 
772 }
773 
GetAttributesOf(UINT cidl,PCUITEMID_CHILD_ARRAY apidl,SFGAOF * rgfInOut)774 HRESULT WINAPI CRecycleBin::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl,
775         SFGAOF *rgfInOut)
776 {
777     TRACE("(%p, %d, {%p, ...}, {%x})\n", this, cidl, apidl ? apidl[0] : NULL, (unsigned int)*rgfInOut);
778     HRESULT hr = S_OK;
779     const SFGAOF ThisFolder = SFGAO_FOLDER | SFGAO_HASPROPSHEET | SFGAO_DROPTARGET | SFGAO_CANRENAME | SFGAO_CANLINK;
780     if (!cidl)
781     {
782         *rgfInOut &= ThisFolder;
783         if (SHRestricted(REST_BITBUCKNOPROP))
784             *rgfInOut &= ~SFGAO_HASPROPSHEET;
785         return hr;
786     }
787     SFGAOF remain = SFGAO_LINK & *rgfInOut;
788     *rgfInOut &= remain | SFGAO_HASPROPSHEET | SFGAO_CANDELETE | SFGAO_FILESYSTEM; // TODO: SFGAO_CANMOVE
789     for (UINT i = 0; (*rgfInOut & remain) && i < cidl && SUCCEEDED(hr); ++i)
790     {
791         if (IShellFolder* pSF = GetFSFolderForItem(apidl[i]))
792         {
793             hr = pSF->GetAttributesOf(1, &apidl[i], rgfInOut);
794             pSF->Release();
795         }
796     }
797     return hr;
798 }
799 
GetUIObjectOf(HWND hwndOwner,UINT cidl,PCUITEMID_CHILD_ARRAY apidl,REFIID riid,UINT * prgfInOut,void ** ppv)800 HRESULT WINAPI CRecycleBin::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl,
801         REFIID riid, UINT *prgfInOut, void **ppv)
802 {
803     LPVOID pObj = NULL;
804     HRESULT hr = E_INVALIDARG;
805 
806     TRACE ("(%p)->(%p,%u,apidl=%p, %p %p)\n", this,
807            hwndOwner, cidl, apidl, prgfInOut, ppv);
808 
809     if (!ppv)
810         return hr;
811 
812     *ppv = NULL;
813     assert(!cidl || (apidl && apidl[0]));
814 
815     if ((IsEqualIID (riid, IID_IContextMenu) || IsEqualIID(riid, IID_IContextMenu2)) && (cidl >= 1))
816     {
817         // FIXME: Handle multiple items
818         hr = ShellObjectCreatorInit<CRecycleBinItemContextMenu>(apidl[0], riid, &pObj);
819     }
820     else if((IsEqualIID(riid, IID_IExtractIconA) || IsEqualIID(riid, IID_IExtractIconW)) && (cidl == 1))
821     {
822         if (IShellFolder *pSF = GetFSFolderForItem(apidl[0]))
823         {
824             hr = CRecyclerExtractIcon_CreateInstance(*pSF, apidl[0], riid, &pObj);
825             pSF->Release();
826         }
827     }
828     else
829         hr = E_NOINTERFACE;
830 
831     if (SUCCEEDED(hr) && !pObj)
832         hr = E_OUTOFMEMORY;
833 
834     *ppv = pObj;
835     TRACE ("(%p)->hr=0x%08x\n", this, hr);
836     return hr;
837 }
838 
GetDisplayNameOf(PCUITEMID_CHILD pidl,SHGDNF uFlags,STRRET * pName)839 HRESULT WINAPI CRecycleBin::GetDisplayNameOf(PCUITEMID_CHILD pidl, SHGDNF uFlags, STRRET *pName)
840 {
841     TRACE("(%p, %p, %x, %p)\n", this, pidl, (unsigned int)uFlags, pName);
842     const BBITEMDATA *pData = ValidateItem(pidl);
843     if (!pData)
844         return E_INVALIDARG;
845 
846     if (IS_SHGDN_FOR_PARSING(uFlags))
847     {
848         LPCWSTR pszName = GetItemRecycledFullPath(*pData);
849         if (uFlags & SHGDN_INFOLDER)
850             pszName = PathFindFileNameW(pszName);
851         pName->pOleStr = SHStrDupW(pszName);
852     }
853     else
854     {
855         if (uFlags & SHGDN_INFOLDER)
856             pName->pOleStr = SHStrDupW(GetItemOriginalFileName(*pData));
857         else
858             pName->pOleStr = SHStrDupW(GetItemOriginalFullPath(*pData));
859     }
860 
861     if (pName->pOleStr)
862     {
863         pName->uType = STRRET_WSTR;
864         if (!IsFolder(pidl))
865             SHELL_FS_ProcessDisplayFilename(pName->pOleStr, uFlags);
866         return S_OK;
867     }
868     pName->uType = STRRET_CSTR;
869     pName->cStr[0] = '\0';
870     return E_OUTOFMEMORY;
871 }
872 
SetNameOf(HWND hwnd,PCUITEMID_CHILD pidl,LPCOLESTR pszName,SHGDNF uFlags,PITEMID_CHILD * ppidlOut)873 HRESULT WINAPI CRecycleBin::SetNameOf(HWND hwnd, PCUITEMID_CHILD pidl, LPCOLESTR pszName,
874                                       SHGDNF uFlags, PITEMID_CHILD *ppidlOut)
875 {
876     TRACE("\n");
877     return E_FAIL; /* not supported */
878 }
879 
GetDefaultSearchGUID(GUID * pguid)880 HRESULT WINAPI CRecycleBin::GetDefaultSearchGUID(GUID *pguid)
881 {
882     FIXME("stub\n");
883     return E_NOTIMPL;
884 }
885 
EnumSearches(IEnumExtraSearch ** ppEnum)886 HRESULT WINAPI CRecycleBin::EnumSearches(IEnumExtraSearch **ppEnum)
887 {
888     FIXME("stub\n");
889     *ppEnum = NULL;
890     return E_NOTIMPL;
891 }
892 
GetDefaultColumn(DWORD dwReserved,ULONG * pSort,ULONG * pDisplay)893 HRESULT WINAPI CRecycleBin::GetDefaultColumn(DWORD dwReserved, ULONG *pSort, ULONG *pDisplay)
894 {
895     TRACE("(%p, %x, %p, %p)\n", this, (unsigned int)dwReserved, pSort, pDisplay);
896     if (pSort)
897         *pSort = 0;
898     if (pDisplay)
899         *pDisplay = 0;
900     return S_OK;
901 }
902 
GetDefaultColumnState(UINT iColumn,SHCOLSTATEF * pcsFlags)903 HRESULT WINAPI CRecycleBin::GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *pcsFlags)
904 {
905     TRACE("(%p, %d, %p)\n", this, iColumn, pcsFlags);
906     if (iColumn >= COLUMNS_COUNT)
907         return E_INVALIDARG;
908     *pcsFlags = RecycleBinColumns[iColumn].pcsFlags;
909     return S_OK;
910 }
911 
GetDetailsEx(PCUITEMID_CHILD pidl,const SHCOLUMNID * pscid,VARIANT * pv)912 HRESULT WINAPI CRecycleBin::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
913 {
914     FIXME("stub\n");
915     return E_NOTIMPL;
916 }
917 
GetDetailsOf(PCUITEMID_CHILD pidl,UINT iColumn,LPSHELLDETAILS pDetails)918 HRESULT WINAPI CRecycleBin::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, LPSHELLDETAILS pDetails)
919 {
920     HRESULT hr;
921     FILETIME ft;
922     SHFILEINFOW shfi;
923     WCHAR buffer[MAX_PATH];
924 
925     TRACE("(%p, %p, %d, %p)\n", this, pidl, iColumn, pDetails);
926     if (iColumn >= COLUMNS_COUNT)
927         return E_FAIL;
928 
929     if (pidl == NULL)
930     {
931         pDetails->fmt = RecycleBinColumns[iColumn].fmt;
932         pDetails->cxChar = RecycleBinColumns[iColumn].cxChars;
933         return SHSetStrRet(&pDetails->str, RecycleBinColumns[iColumn].column_name_id);
934     }
935 
936     if (iColumn == COLUMN_NAME)
937         return GetDisplayNameOf(pidl, SHGDN_NORMAL | SHGDN_INFOLDER, &pDetails->str);
938 
939     const BBITEMDATA *pData = ValidateItem(pidl);
940     if (!pData && FAILED_UNEXPECTEDLY(E_INVALIDARG))
941         return E_INVALIDARG;
942 
943     switch (iColumn)
944     {
945         case COLUMN_DATEDEL:
946             CFSFolder::FormatDateTime(pData->DeletionTime, buffer, _countof(buffer));
947             break;
948         case COLUMN_DELFROM:
949             if (SUCCEEDED(hr = GetItemOriginalFolder(*pData, pDetails->str.pOleStr)))
950                 pDetails->str.uType = STRRET_WSTR;
951             return hr;
952         case COLUMN_SIZE:
953             *buffer = UNICODE_NULL;
954             if (!IsFolder(pidl))
955                 CFSFolder::FormatSize(GetItemFileSize(pidl), buffer, _countof(buffer));
956             break;
957         case COLUMN_MTIME:
958             _ILGetFileDateTime(pidl, &ft);
959             CFSFolder::FormatDateTime(ft, buffer, _countof(buffer));
960             break;
961         case COLUMN_TYPE:
962             GetItemTypeName(pidl, *pData, shfi);
963             return SHSetStrRet(&pDetails->str, shfi.szTypeName);
964         default:
965             return E_FAIL;
966     }
967     return SHSetStrRet(&pDetails->str, buffer);
968 }
969 
MapColumnToSCID(UINT iColumn,SHCOLUMNID * pscid)970 HRESULT WINAPI CRecycleBin::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid)
971 {
972     TRACE("(%p, %d, %p)\n", this, iColumn, pscid);
973     if (iColumn >= COLUMNS_COUNT)
974         return E_INVALIDARG;
975     pscid->fmtid = *RecycleBinColumns[iColumn].fmtId;
976     pscid->pid = RecycleBinColumns[iColumn].pid;
977     return S_OK;
978 }
979 
980 /*************************************************************************
981  * RecycleBin IContextMenu interface
982  */
983 
984 enum { IDC_EMPTYRECYCLEBIN = 1, IDC_PROPERTIES };
985 static const CMVERBMAP g_BBFolderVerbMap[] =
986 {
987     { "empty", IDC_EMPTYRECYCLEBIN },
988     { "properties", IDC_PROPERTIES },
989     { NULL }
990 };
991 
QueryContextMenu(HMENU hMenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags)992 HRESULT WINAPI CRecycleBin::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
993 {
994     TRACE("QueryContextMenu %p %p %u %u %u %u\n", this, hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags );
995 
996     if (!hMenu)
997         return E_INVALIDARG;
998 
999     UINT idHigh = 0, id;
1000 
1001     WORD state = IsRecycleBinEmpty(this) ? MFS_DISABLED : MFS_ENABLED;
1002     id = idCmdFirst + IDC_EMPTYRECYCLEBIN;
1003     if (_InsertMenuItemW(hMenu, indexMenu, TRUE, id, MFT_STRING, MAKEINTRESOURCEW(IDS_EMPTY_BITBUCKET), state))
1004     {
1005         idHigh = max(idHigh, id);
1006         if (m_IsBackgroundMenu && !SHRestricted(REST_BITBUCKNOPROP))
1007         {
1008             id = idCmdFirst + IDC_PROPERTIES;
1009             if (_InsertMenuItemW(hMenu, ++indexMenu, TRUE, id, MFT_STRING, MAKEINTRESOURCEW(IDS_PROPERTIES), 0))
1010             {
1011                 idHigh = max(idHigh, id);
1012                 _InsertMenuItemW(hMenu, indexMenu++, TRUE, -1, MFT_SEPARATOR, NULL, 0);
1013             }
1014         }
1015     }
1016     return idHigh ? MAKE_HRESULT(SEVERITY_SUCCESS, 0, idHigh - idCmdFirst + 1) : S_OK;
1017 }
1018 
InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)1019 HRESULT WINAPI CRecycleBin::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
1020 {
1021     TRACE("%p %p verb %p\n", this, lpcmi, lpcmi ? lpcmi->lpVerb : NULL);
1022     int CmdId = SHELL_MapContextMenuVerbToCmdId(lpcmi, g_BBFolderVerbMap);
1023     if (CmdId == IDC_EMPTYRECYCLEBIN)
1024     {
1025         HRESULT hr = SHEmptyRecycleBinW(lpcmi->hwnd, NULL, 0);
1026         TRACE("result %x\n", hr);
1027         if (hr != S_OK)
1028             return hr;
1029 #if 0   // This is a nasty hack because lpcmi->hwnd might not be a shell browser.
1030         // Not required with working SHChangeNotify.
1031         CComPtr<IShellView> pSV;
1032         LPSHELLBROWSER lpSB = (LPSHELLBROWSER)SendMessage(lpcmi->hwnd, CWM_GETISHELLBROWSER, 0, 0);
1033         if (lpSB && SUCCEEDED(lpSB->QueryActiveShellView(&pSV)))
1034             pSV->Refresh();
1035 #endif
1036         return hr;
1037     }
1038     else if (CmdId == IDC_PROPERTIES)
1039     {
1040         return SHELL_ShowItemIDListProperties((LPITEMIDLIST)CSIDL_BITBUCKET);
1041     }
1042     return E_INVALIDARG;
1043 }
1044 
GetCommandString(UINT_PTR idCommand,UINT uFlags,UINT * lpReserved,LPSTR lpszName,UINT uMaxNameLen)1045 HRESULT WINAPI CRecycleBin::GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen)
1046 {
1047     TRACE("%p %lu %u %p %p %u\n", this, idCommand, uFlags, lpReserved, lpszName, uMaxNameLen);
1048     return SHELL_GetCommandStringImpl(idCommand, uFlags, lpszName, uMaxNameLen, g_BBFolderVerbMap);
1049 }
1050 
1051 /*************************************************************************
1052  * RecycleBin IShellPropSheetExt interface
1053  */
1054 
AddPages(LPFNSVADDPROPSHEETPAGE pfnAddPage,LPARAM lParam)1055 HRESULT WINAPI CRecycleBin::AddPages(LPFNSVADDPROPSHEETPAGE pfnAddPage, LPARAM lParam)
1056 {
1057     extern HRESULT RecycleBin_AddPropSheetPages(LPFNSVADDPROPSHEETPAGE pfnAddPage, LPARAM lParam);
1058     return RecycleBin_AddPropSheetPages(pfnAddPage, lParam);
1059 }
1060 
ReplacePage(EXPPS uPageID,LPFNSVADDPROPSHEETPAGE pfnReplaceWith,LPARAM lParam)1061 HRESULT WINAPI CRecycleBin::ReplacePage(EXPPS uPageID, LPFNSVADDPROPSHEETPAGE pfnReplaceWith, LPARAM lParam)
1062 {
1063     FIXME("%p %lu %p %lu\n", this, uPageID, pfnReplaceWith, lParam);
1064 
1065     return E_NOTIMPL;
1066 }
1067 
1068 /*************************************************************************
1069  * RecycleBin IShellExtInit interface
1070  */
1071 
Initialize(PCIDLIST_ABSOLUTE pidlFolder,IDataObject * pdtobj,HKEY hkeyProgID)1072 HRESULT WINAPI CRecycleBin::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
1073 {
1074     TRACE("%p %p %p %p\n", this, pidlFolder, pdtobj, hkeyProgID );
1075     m_IsBackgroundMenu = false;
1076     return S_OK;
1077 }
1078 
1079 /**
1080  * Tests whether a file can be trashed
1081  * @param wszPath Path to the file to be trash
1082  * @returns TRUE if the file can be trashed, FALSE otherwise
1083  */
1084 BOOL
TRASH_CanTrashFile(LPCWSTR wszPath)1085 TRASH_CanTrashFile(LPCWSTR wszPath)
1086 {
1087     LONG ret;
1088     DWORD dwNukeOnDelete, dwType, VolSerialNumber, MaxComponentLength;
1089     DWORD FileSystemFlags, dwSize, dwDisposition;
1090     HKEY hKey;
1091     WCHAR szBuffer[10];
1092     WCHAR szKey[150] = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\BitBucket\\Volume\\";
1093 
1094     if (wszPath[1] != L':')
1095     {
1096         /* path is UNC */
1097         return FALSE;
1098     }
1099 
1100     // Copy and retrieve the root path from get given string
1101     WCHAR wszRootPathName[MAX_PATH];
1102     StringCbCopyW(wszRootPathName, sizeof(wszRootPathName), wszPath);
1103     PathStripToRootW(wszRootPathName);
1104 
1105     // Test to see if the drive is fixed (non removable)
1106     if (GetDriveTypeW(wszRootPathName) != DRIVE_FIXED)
1107     {
1108         /* no bitbucket on removable media */
1109         return FALSE;
1110     }
1111 
1112     if (!GetVolumeInformationW(wszRootPathName, NULL, 0, &VolSerialNumber, &MaxComponentLength, &FileSystemFlags, NULL, 0))
1113     {
1114         ERR("GetVolumeInformationW failed with %u wszRootPathName=%s\n", GetLastError(), debugstr_w(wszRootPathName));
1115         return FALSE;
1116     }
1117 
1118     swprintf(szBuffer, L"%04X-%04X", LOWORD(VolSerialNumber), HIWORD(VolSerialNumber));
1119     wcscat(szKey, szBuffer);
1120 
1121     if (RegCreateKeyExW(HKEY_CURRENT_USER, szKey, 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &hKey, &dwDisposition) != ERROR_SUCCESS)
1122     {
1123         ERR("RegCreateKeyExW failed\n");
1124         return FALSE;
1125     }
1126 
1127     if (dwDisposition  & REG_CREATED_NEW_KEY)
1128     {
1129         /* per default move to bitbucket */
1130         dwNukeOnDelete = 0;
1131         RegSetValueExW(hKey, L"NukeOnDelete", 0, REG_DWORD, (LPBYTE)&dwNukeOnDelete, sizeof(DWORD));
1132         /* per default unlimited size */
1133         dwSize = -1;
1134         RegSetValueExW(hKey, L"MaxCapacity", 0, REG_DWORD, (LPBYTE)&dwSize, sizeof(DWORD));
1135     }
1136     else
1137     {
1138         dwSize = sizeof(dwNukeOnDelete);
1139         ret = RegQueryValueExW(hKey, L"NukeOnDelete", NULL, &dwType, (LPBYTE)&dwNukeOnDelete, &dwSize);
1140         if (ret != ERROR_SUCCESS)
1141         {
1142             dwNukeOnDelete = 0;
1143             if (ret == ERROR_FILE_NOT_FOUND)
1144             {
1145                 /* restore key and enable bitbucket */
1146                 RegSetValueExW(hKey, L"NukeOnDelete", 0, REG_DWORD, (LPBYTE)&dwNukeOnDelete, sizeof(DWORD));
1147             }
1148         }
1149     }
1150     BOOL bCanTrash = !dwNukeOnDelete;
1151     // FIXME: Check if bitbucket is full (CORE-13743)
1152     RegCloseKey(hKey);
1153     return bCanTrash;
1154 }
1155 
1156 BOOL
TRASH_TrashFile(LPCWSTR wszPath)1157 TRASH_TrashFile(LPCWSTR wszPath)
1158 {
1159     TRACE("(%s)\n", debugstr_w(wszPath));
1160     return DeleteFileToRecycleBin(wszPath);
1161 }
1162 
TRASH_PlayEmptyRecycleBinSound()1163 static void TRASH_PlayEmptyRecycleBinSound()
1164 {
1165     CRegKey regKey;
1166     CHeapPtr<WCHAR> pszValue;
1167     CHeapPtr<WCHAR> pszSndPath;
1168     DWORD dwType, dwSize;
1169     LONG lError;
1170 
1171     lError = regKey.Open(HKEY_CURRENT_USER,
1172                          L"AppEvents\\Schemes\\Apps\\Explorer\\EmptyRecycleBin\\.Current",
1173                          KEY_READ);
1174     if (lError != ERROR_SUCCESS)
1175         return;
1176 
1177     lError = regKey.QueryValue(NULL, &dwType, NULL, &dwSize);
1178     if (lError != ERROR_SUCCESS)
1179         return;
1180 
1181     if (!pszValue.AllocateBytes(dwSize))
1182         return;
1183 
1184     lError = regKey.QueryValue(NULL, &dwType, pszValue, &dwSize);
1185     if (lError != ERROR_SUCCESS)
1186         return;
1187 
1188     if (dwType == REG_EXPAND_SZ)
1189     {
1190         dwSize = ExpandEnvironmentStringsW(pszValue, NULL, 0);
1191         if (dwSize == 0)
1192             return;
1193 
1194         if (!pszSndPath.Allocate(dwSize))
1195             return;
1196 
1197         if (ExpandEnvironmentStringsW(pszValue, pszSndPath, dwSize) == 0)
1198             return;
1199     }
1200     else if (dwType == REG_SZ)
1201     {
1202         /* The type is REG_SZ, no need to expand */
1203         pszSndPath.Attach(pszValue.Detach());
1204     }
1205     else
1206     {
1207         /* Invalid type */
1208         return;
1209     }
1210 
1211     PlaySoundW(pszSndPath, NULL, SND_FILENAME | SND_ASYNC | SND_NODEFAULT);
1212 }
1213 
1214 /*************************************************************************
1215  * SHUpdateCRecycleBinIcon                                [SHELL32.@]
1216  *
1217  * Undocumented
1218  */
SHUpdateRecycleBinIcon(void)1219 EXTERN_C HRESULT WINAPI SHUpdateRecycleBinIcon(void)
1220 {
1221     FIXME("stub\n");
1222 
1223     // HACK! This dwItem2 should be the icon index in the system image list that has changed.
1224     // FIXME: Call SHMapPIDLToSystemImageListIndex
1225     DWORD dwItem2 = -1;
1226 
1227     SHChangeNotify(SHCNE_UPDATEIMAGE, SHCNF_DWORD, NULL, &dwItem2);
1228     return S_OK;
1229 }
1230 
1231 /*************************************************************************
1232  *              SHEmptyRecycleBinA (SHELL32.@)
1233  */
SHEmptyRecycleBinA(HWND hwnd,LPCSTR pszRootPath,DWORD dwFlags)1234 HRESULT WINAPI SHEmptyRecycleBinA(HWND hwnd, LPCSTR pszRootPath, DWORD dwFlags)
1235 {
1236     LPWSTR szRootPathW = NULL;
1237     int len;
1238     HRESULT hr;
1239 
1240     TRACE("%p, %s, 0x%08x\n", hwnd, debugstr_a(pszRootPath), dwFlags);
1241 
1242     if (pszRootPath)
1243     {
1244         len = MultiByteToWideChar(CP_ACP, 0, pszRootPath, -1, NULL, 0);
1245         if (len == 0)
1246             return HRESULT_FROM_WIN32(GetLastError());
1247         szRootPathW = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
1248         if (!szRootPathW)
1249             return E_OUTOFMEMORY;
1250         if (MultiByteToWideChar(CP_ACP, 0, pszRootPath, -1, szRootPathW, len) == 0)
1251         {
1252             HeapFree(GetProcessHeap(), 0, szRootPathW);
1253             return HRESULT_FROM_WIN32(GetLastError());
1254         }
1255     }
1256 
1257     hr = SHEmptyRecycleBinW(hwnd, szRootPathW, dwFlags);
1258     HeapFree(GetProcessHeap(), 0, szRootPathW);
1259 
1260     return hr;
1261 }
1262 
SHEmptyRecycleBinW(HWND hwnd,LPCWSTR pszRootPath,DWORD dwFlags)1263 HRESULT WINAPI SHEmptyRecycleBinW(HWND hwnd, LPCWSTR pszRootPath, DWORD dwFlags)
1264 {
1265     WCHAR szBuffer[MAX_PATH];
1266     DWORD count;
1267     LONG ret;
1268     IShellFolder *pDesktop, *pRecycleBin;
1269     PIDLIST_ABSOLUTE pidlRecycleBin;
1270     PITEMID_CHILD pidl;
1271     HRESULT hr = S_OK;
1272     LPENUMIDLIST penumFiles;
1273     STRRET StrRet;
1274 
1275     TRACE("%p, %s, 0x%08x\n", hwnd, debugstr_w(pszRootPath), dwFlags);
1276 
1277     if (!(dwFlags & SHERB_NOCONFIRMATION))
1278     {
1279         hr = SHGetDesktopFolder(&pDesktop);
1280         if (FAILED(hr))
1281             return hr;
1282         hr = SHGetFolderLocation(NULL, CSIDL_BITBUCKET, NULL, 0, &pidlRecycleBin);
1283         if (FAILED(hr))
1284         {
1285             pDesktop->Release();
1286             return hr;
1287         }
1288         hr = pDesktop->BindToObject(pidlRecycleBin, NULL, IID_PPV_ARG(IShellFolder, &pRecycleBin));
1289         CoTaskMemFree(pidlRecycleBin);
1290         pDesktop->Release();
1291         if (FAILED(hr))
1292             return hr;
1293         hr = pRecycleBin->EnumObjects(hwnd, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN, &penumFiles);
1294         if (FAILED(hr))
1295         {
1296             pRecycleBin->Release();
1297             return hr;
1298         }
1299 
1300         count = 0;
1301         if (hr != S_FALSE)
1302         {
1303             while (penumFiles->Next(1, &pidl, NULL) == S_OK)
1304             {
1305                 count++;
1306                 pRecycleBin->GetDisplayNameOf(pidl, SHGDN_NORMAL | SHGDN_INFOLDER, &StrRet);
1307                 StrRetToBuf(&StrRet, pidl, szBuffer, _countof(szBuffer));
1308                 CoTaskMemFree(pidl);
1309             }
1310             penumFiles->Release();
1311         }
1312         pRecycleBin->Release();
1313 
1314         switch (count)
1315         {
1316             case 0:
1317                 /* no files, don't need confirmation */
1318                 break;
1319 
1320             case 1:
1321                 /* we have only one item inside the bin, so show a message box with its name */
1322                 if (ShellMessageBoxW(shell32_hInstance, hwnd, MAKEINTRESOURCEW(IDS_DELETEITEM_TEXT), MAKEINTRESOURCEW(IDS_EMPTY_BITBUCKET),
1323                                    MB_ICONEXCLAMATION | MB_YESNO | MB_DEFBUTTON2, szBuffer) == IDNO)
1324                 {
1325                     return S_OK;
1326                 }
1327                 break;
1328 
1329             default:
1330                 /* we have more than one item, so show a message box with the count of the items */
1331                 StringCbPrintfW(szBuffer, sizeof(szBuffer), L"%u", count);
1332                 if (ShellMessageBoxW(shell32_hInstance, hwnd, MAKEINTRESOURCEW(IDS_DELETEMULTIPLE_TEXT), MAKEINTRESOURCEW(IDS_EMPTY_BITBUCKET),
1333                                    MB_ICONEXCLAMATION | MB_YESNO | MB_DEFBUTTON2, szBuffer) == IDNO)
1334                 {
1335                     return S_OK;
1336                 }
1337                 break;
1338         }
1339     }
1340 
1341     if (dwFlags & SHERB_NOPROGRESSUI)
1342     {
1343         ret = EmptyRecycleBinW(pszRootPath);
1344     }
1345     else
1346     {
1347        /* FIXME
1348         * show a progress dialog
1349         */
1350         ret = EmptyRecycleBinW(pszRootPath);
1351     }
1352 
1353     if (!ret)
1354         return HRESULT_FROM_WIN32(GetLastError());
1355 
1356     CRecycleBin_ChangeNotifyBBItem(SHCNE_UPDATEDIR, NULL);
1357     if (!(dwFlags & SHERB_NOSOUND))
1358     {
1359         TRASH_PlayEmptyRecycleBinSound();
1360     }
1361     return S_OK;
1362 }
1363 
SHQueryRecycleBinA(LPCSTR pszRootPath,LPSHQUERYRBINFO pSHQueryRBInfo)1364 HRESULT WINAPI SHQueryRecycleBinA(LPCSTR pszRootPath, LPSHQUERYRBINFO pSHQueryRBInfo)
1365 {
1366     LPWSTR szRootPathW = NULL;
1367     int len;
1368     HRESULT hr;
1369 
1370     TRACE("%s, %p\n", debugstr_a(pszRootPath), pSHQueryRBInfo);
1371 
1372     if (pszRootPath)
1373     {
1374         len = MultiByteToWideChar(CP_ACP, 0, pszRootPath, -1, NULL, 0);
1375         if (len == 0)
1376             return HRESULT_FROM_WIN32(GetLastError());
1377         szRootPathW = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
1378         if (!szRootPathW)
1379             return E_OUTOFMEMORY;
1380         if (MultiByteToWideChar(CP_ACP, 0, pszRootPath, -1, szRootPathW, len) == 0)
1381         {
1382             HeapFree(GetProcessHeap(), 0, szRootPathW);
1383             return HRESULT_FROM_WIN32(GetLastError());
1384         }
1385     }
1386 
1387     hr = SHQueryRecycleBinW(szRootPathW, pSHQueryRBInfo);
1388     HeapFree(GetProcessHeap(), 0, szRootPathW);
1389 
1390     return hr;
1391 }
1392 
SHQueryRecycleBinW(LPCWSTR pszRootPath,LPSHQUERYRBINFO pSHQueryRBInfo)1393 HRESULT WINAPI SHQueryRecycleBinW(LPCWSTR pszRootPath, LPSHQUERYRBINFO pSHQueryRBInfo)
1394 {
1395     TRACE("%s, %p\n", debugstr_w(pszRootPath), pSHQueryRBInfo);
1396 
1397     if (!pszRootPath || (pszRootPath[0] == 0) ||
1398         !pSHQueryRBInfo || (pSHQueryRBInfo->cbSize < sizeof(SHQUERYRBINFO)))
1399     {
1400         return E_INVALIDARG;
1401     }
1402 
1403     pSHQueryRBInfo->i64Size = 0;
1404     pSHQueryRBInfo->i64NumItems = 0;
1405 
1406     CComPtr<IRecycleBin> spRecycleBin;
1407     HRESULT hr;
1408     if (FAILED_UNEXPECTEDLY((hr = GetDefaultRecycleBin(pszRootPath, &spRecycleBin))))
1409         return hr;
1410 
1411     CComPtr<IRecycleBinEnumList> spEnumList;
1412     hr = spRecycleBin->EnumObjects(&spEnumList);
1413     if (!SUCCEEDED(hr))
1414         return hr;
1415 
1416     while (TRUE)
1417     {
1418         CComPtr<IRecycleBinFile> spFile;
1419         hr = spEnumList->Next(1, &spFile, NULL);
1420         if (hr == S_FALSE)
1421             return S_OK;
1422 
1423         if (FAILED_UNEXPECTEDLY(hr))
1424             return hr;
1425 
1426         ULARGE_INTEGER Size = {};
1427         if (FAILED_UNEXPECTEDLY((hr = spFile->GetFileSize(&Size))))
1428             return hr;
1429 
1430         pSHQueryRBInfo->i64Size += Size.QuadPart;
1431         pSHQueryRBInfo->i64NumItems++;
1432     }
1433 
1434     return S_OK;
1435 }
1436