1 /*
2  * PROJECT:     Recycle bin management
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Enumerates contents of a MS Windows 2000/XP/2003 recyclebin
5  * COPYRIGHT:   Copyright 2006-2007 Hervé Poussineau (hpoussin@reactos.org)
6  *              Copyright 2024 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
7  */
8 
9 #include "recyclebin_private.h"
10 #include <atlstr.h>
11 #include <shlwapi.h>
12 #include <strsafe.h>
13 
14 class RecycleBin5File : public IRecycleBinFile
15 {
16 public:
17     RecycleBin5File();
18     virtual ~RecycleBin5File();
19 
20     HRESULT Init(
21         _In_ IRecycleBin5 *prb,
22         _In_ LPCWSTR Folder,
23         _In_ PDELETED_FILE_RECORD pDeletedFile);
24 
25     /* IUnknown methods */
26     STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) override;
27     STDMETHODIMP_(ULONG) AddRef() override;
28     STDMETHODIMP_(ULONG) Release() override;
29 
30     /* IRecycleBinFile methods */
31     STDMETHODIMP IsEqualIdentity(const RECYCLEBINFILEIDENTITY *pFI) override
32     {
33         RECYCLEBINFILEIDENTITY self = { m_deletedFile.DeletionTime, m_FullName };
34         return RecycleBinGeneric_IsEqualFileIdentity(pFI, &self) ? S_OK : S_FALSE;
35     }
36     STDMETHODIMP GetInfo(PDELETED_FILE_INFO pInfo) override;
37     STDMETHODIMP GetLastModificationTime(FILETIME *pLastModificationTime) override;
38     STDMETHODIMP GetDeletionTime(FILETIME *pDeletionTime) override;
39     STDMETHODIMP GetFileSize(ULARGE_INTEGER *pFileSize) override;
40     STDMETHODIMP GetPhysicalFileSize(ULARGE_INTEGER *pPhysicalFileSize) override;
41     STDMETHODIMP GetAttributes(DWORD *pAttributes) override;
42     STDMETHODIMP GetFileName(SIZE_T BufferSize, LPWSTR Buffer, SIZE_T *RequiredSize) override;
43     STDMETHODIMP Delete() override;
44     STDMETHODIMP Restore() override;
45 
46 protected:
47     LONG m_ref;
48     IRecycleBin5 *m_recycleBin;
49     DELETED_FILE_RECORD m_deletedFile;
50     LPWSTR m_FullName;
51 };
52 
53 STDMETHODIMP RecycleBin5File::QueryInterface(REFIID riid, void **ppvObject)
54 {
55     TRACE("(%p, %s, %p)\n", this, debugstr_guid(&riid), ppvObject);
56 
57     if (!ppvObject)
58         return E_POINTER;
59 
60     if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IRecycleBinFile))
61     {
62         *ppvObject = static_cast<IRecycleBinFile *>(this);
63     }
64     else
65     {
66         *ppvObject = NULL;
67         return E_NOINTERFACE;
68     }
69 
70     AddRef();
71     return S_OK;
72 }
73 
74 STDMETHODIMP_(ULONG) RecycleBin5File::AddRef()
75 {
76     TRACE("(%p)\n", this);
77     return InterlockedIncrement(&m_ref);
78 }
79 
80 RecycleBin5File::~RecycleBin5File()
81 {
82     TRACE("(%p)\n", this);
83     m_recycleBin->Release();
84     SHFree(m_FullName);
85 }
86 
87 STDMETHODIMP_(ULONG) RecycleBin5File::Release()
88 {
89     TRACE("(%p)\n", this);
90     ULONG refCount = InterlockedDecrement(&m_ref);
91     if (refCount == 0)
92         delete this;
93     return refCount;
94 }
95 
96 STDMETHODIMP RecycleBin5File::GetInfo(PDELETED_FILE_INFO pInfo)
97 {
98     HRESULT hr = S_OK;
99     ULARGE_INTEGER uli;
100     if (FAILED(GetLastModificationTime(&pInfo->LastModification)))
101         ZeroMemory(&pInfo->LastModification, sizeof(pInfo->LastModification));
102     pInfo->DeletionTime = m_deletedFile.DeletionTime;
103     C_ASSERT(sizeof(pInfo->FileSize) <= sizeof(UINT));
104     pInfo->FileSize = FAILED(GetFileSize(&uli)) ? INVALID_FILE_SIZE : uli.LowPart;
105     if (FAILED(hr = GetAttributes(&pInfo->Attributes)))
106         pInfo->Attributes = 0;
107     InitializeRecycleBinStringRef(&pInfo->OriginalFullPath, m_deletedFile.FileNameW);
108     InitializeRecycleBinStringRef(&pInfo->RecycledFullPath, m_FullName);
109     return hr;
110 }
111 
112 STDMETHODIMP RecycleBin5File::GetLastModificationTime(FILETIME *pLastModificationTime)
113 {
114     TRACE("(%p, %p)\n", this, pLastModificationTime);
115 
116     DWORD dwAttributes = ::GetFileAttributesW(m_FullName);
117     if (dwAttributes == INVALID_FILE_ATTRIBUTES)
118         return HResultFromWin32(GetLastError());
119 
120     HANDLE hFile;
121     hFile = CreateFileW(m_FullName,
122                         GENERIC_READ,
123                         FILE_SHARE_READ |
124                             ((dwAttributes & FILE_ATTRIBUTE_DIRECTORY) ? (FILE_SHARE_WRITE | FILE_SHARE_DELETE) : 0),
125                         NULL,
126                         OPEN_EXISTING,
127                         (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) ? FILE_FLAG_BACKUP_SEMANTICS : 0,
128                         NULL);
129     if (hFile == INVALID_HANDLE_VALUE)
130         return HResultFromWin32(GetLastError());
131 
132     HRESULT hr;
133     if (GetFileTime(hFile, NULL, NULL, pLastModificationTime))
134         hr = S_OK;
135     else
136         hr = HResultFromWin32(GetLastError());
137     CloseHandle(hFile);
138     return hr;
139 }
140 
141 STDMETHODIMP RecycleBin5File::GetDeletionTime(FILETIME *pDeletionTime)
142 {
143     TRACE("(%p, %p)\n", this, pDeletionTime);
144     *pDeletionTime = m_deletedFile.DeletionTime;
145     return S_OK;
146 }
147 
148 STDMETHODIMP RecycleBin5File::GetFileSize(ULARGE_INTEGER *pFileSize)
149 {
150     TRACE("(%p, %p)\n", this, pFileSize);
151 
152     DWORD dwAttributes = GetFileAttributesW(m_FullName);
153     if (dwAttributes == INVALID_FILE_ATTRIBUTES)
154         return HRESULT_FROM_WIN32(GetLastError());
155     if (dwAttributes & FILE_ATTRIBUTE_DIRECTORY)
156     {
157         pFileSize->QuadPart = 0;
158         return S_OK;
159     }
160 
161     HANDLE hFile = CreateFileW(m_FullName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
162     if (hFile == INVALID_HANDLE_VALUE)
163         return HRESULT_FROM_WIN32(GetLastError());
164     pFileSize->u.LowPart = ::GetFileSize(hFile, &pFileSize->u.HighPart);
165 
166     HRESULT hr;
167     if (pFileSize->u.LowPart != INVALID_FILE_SIZE)
168         hr = S_OK;
169     else
170         hr = HRESULT_FROM_WIN32(GetLastError());
171 
172     CloseHandle(hFile);
173     return hr;
174 }
175 
176 STDMETHODIMP RecycleBin5File::GetPhysicalFileSize(ULARGE_INTEGER *pPhysicalFileSize)
177 {
178     TRACE("(%p, %p)\n", this, pPhysicalFileSize);
179     pPhysicalFileSize->u.HighPart = 0;
180     pPhysicalFileSize->u.LowPart = m_deletedFile.dwPhysicalFileSize;
181     return S_OK;
182 }
183 
184 STDMETHODIMP RecycleBin5File::GetAttributes(DWORD *pAttributes)
185 {
186     DWORD dwAttributes;
187 
188     TRACE("(%p, %p)\n", this, pAttributes);
189 
190     dwAttributes = GetFileAttributesW(m_FullName);
191     if (dwAttributes == INVALID_FILE_ATTRIBUTES)
192         return HResultFromWin32(GetLastError());
193 
194     *pAttributes = dwAttributes;
195     return S_OK;
196 }
197 
198 STDMETHODIMP RecycleBin5File::GetFileName(SIZE_T BufferSize, LPWSTR Buffer, SIZE_T *RequiredSize)
199 {
200     DWORD dwRequired;
201 
202     TRACE("(%p, %u, %p, %p)\n", this, BufferSize, Buffer, RequiredSize);
203 
204     dwRequired = (DWORD)(wcslen(m_deletedFile.FileNameW) + 1) * sizeof(WCHAR);
205     if (RequiredSize)
206         *RequiredSize = dwRequired;
207 
208     if (BufferSize == 0 && !Buffer)
209         return S_OK;
210 
211     if (BufferSize < dwRequired)
212         return E_OUTOFMEMORY;
213     CopyMemory(Buffer, m_deletedFile.FileNameW, dwRequired);
214     return S_OK;
215 }
216 
217 STDMETHODIMP RecycleBin5File::Delete()
218 {
219     TRACE("(%p)\n", this);
220     return m_recycleBin->Delete(m_FullName, &m_deletedFile);
221 }
222 
223 STDMETHODIMP RecycleBin5File::Restore()
224 {
225     TRACE("(%p)\n", this);
226     return m_recycleBin->Restore(m_FullName, &m_deletedFile);
227 }
228 
229 RecycleBin5File::RecycleBin5File()
230     : m_ref(1)
231     , m_recycleBin(NULL)
232     , m_FullName(NULL)
233 {
234     ZeroMemory(&m_deletedFile, sizeof(m_deletedFile));
235 }
236 
237 HRESULT
238 RecycleBin5File::Init(
239     _In_ IRecycleBin5 *prb,
240     _In_ LPCWSTR Folder,
241     _In_ PDELETED_FILE_RECORD pDeletedFile)
242 {
243     m_recycleBin = prb;
244     m_recycleBin->AddRef();
245     m_deletedFile = *pDeletedFile;
246 
247     WCHAR szUniqueId[32];
248     StringCchPrintfW(szUniqueId, _countof(szUniqueId), L"%lu", pDeletedFile->dwRecordUniqueId);
249 
250     CStringW strFullName(Folder);
251     strFullName += L"\\D";
252     strFullName += (WCHAR)(L'a' + pDeletedFile->dwDriveNumber);
253     strFullName += szUniqueId;
254     strFullName += PathFindExtensionW(pDeletedFile->FileNameW);
255     if (GetFileAttributesW(strFullName) == INVALID_FILE_ATTRIBUTES)
256         return E_FAIL;
257 
258     return SHStrDup(strFullName, &m_FullName);
259 }
260 
261 static HRESULT
262 RecycleBin5File_Constructor(
263     _In_ IRecycleBin5 *prb,
264     _In_ LPCWSTR Folder,
265     _In_ PDELETED_FILE_RECORD pDeletedFile,
266     _Out_ IRecycleBinFile **ppFile)
267 {
268     if (!ppFile)
269         return E_POINTER;
270 
271     *ppFile = NULL;
272 
273     RecycleBin5File *pThis = new RecycleBin5File();
274     if (!pThis)
275         return E_OUTOFMEMORY;
276 
277     HRESULT hr = pThis->Init(prb, Folder, pDeletedFile);
278     if (FAILED(hr))
279     {
280         delete pThis;
281         return hr;
282     }
283 
284     *ppFile = static_cast<IRecycleBinFile *>(pThis);
285     return S_OK;
286 }
287 
288 class RecycleBin5Enum : public IRecycleBinEnumList
289 {
290 public:
291     RecycleBin5Enum();
292     virtual ~RecycleBin5Enum();
293 
294     HRESULT Init(
295         _In_ IRecycleBin5 *prb,
296         _In_ HANDLE hInfo,
297         _In_ HANDLE hInfoMapped,
298         _In_ LPCWSTR pszPrefix);
299 
300     /* IUnknown methods */
301     STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) override;
302     STDMETHODIMP_(ULONG) AddRef() override;
303     STDMETHODIMP_(ULONG) Release() override;
304 
305     /* IRecycleBinEnumList methods */
306     STDMETHODIMP Next(DWORD celt, IRecycleBinFile **rgelt, DWORD *pceltFetched) override;
307     STDMETHODIMP Skip(DWORD celt) override;
308     STDMETHODIMP Reset() override;
309 
310 protected:
311     LONG m_ref;
312     IRecycleBin5 *m_recycleBin;
313     HANDLE m_hInfo;
314     INFO2_HEADER *m_pInfo;
315     DWORD m_dwCurrent;
316     LPWSTR m_pszPrefix;
317 };
318 
319 STDMETHODIMP RecycleBin5Enum::QueryInterface(REFIID riid, void **ppvObject)
320 {
321     TRACE("(%p, %s, %p)\n", this, debugstr_guid(&riid), ppvObject);
322 
323     if (!ppvObject)
324         return E_POINTER;
325 
326     if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IRecycleBinEnumList))
327         *ppvObject = static_cast<IRecycleBinEnumList *>(this);
328     else
329     {
330         *ppvObject = NULL;
331         return E_NOINTERFACE;
332     }
333 
334     AddRef();
335     return S_OK;
336 }
337 
338 STDMETHODIMP_(ULONG) RecycleBin5Enum::AddRef()
339 {
340     TRACE("(%p)\n", this);
341     return InterlockedIncrement(&m_ref);
342 }
343 
344 RecycleBin5Enum::~RecycleBin5Enum()
345 {
346     TRACE("(%p)\n", this);
347 
348     m_recycleBin->OnClosing(this);
349     UnmapViewOfFile(m_pInfo);
350     m_recycleBin->Release();
351     SHFree(m_pszPrefix);
352 }
353 
354 STDMETHODIMP_(ULONG) RecycleBin5Enum::Release()
355 {
356     TRACE("(%p)\n", this);
357 
358     ULONG refCount = InterlockedDecrement(&m_ref);
359     if (refCount == 0)
360         delete this;
361     return refCount;
362 }
363 
364 STDMETHODIMP RecycleBin5Enum::Next(DWORD celt, IRecycleBinFile **rgelt, DWORD *pceltFetched)
365 {
366     HRESULT hr;
367 
368     TRACE("(%p, %u, %p, %p)\n", this, celt, rgelt, pceltFetched);
369 
370     if (!rgelt)
371         return E_POINTER;
372     if (!pceltFetched && celt > 1)
373         return E_INVALIDARG;
374 
375     ULARGE_INTEGER FileSize;
376     FileSize.u.LowPart = GetFileSize(m_hInfo, &FileSize.u.HighPart);
377     if (FileSize.u.LowPart == 0)
378         return HResultFromWin32(GetLastError());
379 
380     DWORD dwEntries =
381         (DWORD)((FileSize.QuadPart - sizeof(INFO2_HEADER)) / sizeof(DELETED_FILE_RECORD));
382 
383     DWORD iEntry = m_dwCurrent, fetched = 0;
384     PDELETED_FILE_RECORD pDeletedFile = (PDELETED_FILE_RECORD)(m_pInfo + 1) + iEntry;
385     for (; iEntry < dwEntries && fetched < celt; ++iEntry)
386     {
387         hr = RecycleBin5File_Constructor(m_recycleBin, m_pszPrefix, pDeletedFile, &rgelt[fetched]);
388         if (SUCCEEDED(hr))
389             fetched++;
390         pDeletedFile++;
391     }
392 
393     m_dwCurrent = iEntry;
394     if (pceltFetched)
395         *pceltFetched = fetched;
396     if (fetched == celt)
397         return S_OK;
398     else
399         return S_FALSE;
400 }
401 
402 STDMETHODIMP RecycleBin5Enum::Skip(DWORD celt)
403 {
404     TRACE("(%p, %u)\n", this, celt);
405     m_dwCurrent += celt;
406     return S_OK;
407 }
408 
409 STDMETHODIMP RecycleBin5Enum::Reset()
410 {
411     TRACE("(%p)\n", this);
412     m_dwCurrent = 0;
413     return S_OK;
414 }
415 
416 RecycleBin5Enum::RecycleBin5Enum()
417     : m_ref(1)
418     , m_recycleBin(NULL)
419     , m_hInfo(NULL)
420     , m_pInfo(NULL)
421     , m_dwCurrent(0)
422     , m_pszPrefix(NULL)
423 {
424 }
425 
426 HRESULT
427 RecycleBin5Enum::Init(
428     _In_ IRecycleBin5 *prb,
429     _In_ HANDLE hInfo,
430     _In_ HANDLE hInfoMapped,
431     _In_ LPCWSTR pszPrefix)
432 {
433     m_recycleBin = prb;
434     m_recycleBin->AddRef();
435 
436     HRESULT hr = SHStrDup(pszPrefix, &m_pszPrefix);
437     if (FAILED(hr))
438         return hr;
439 
440     m_hInfo = hInfo;
441     m_pInfo = (PINFO2_HEADER)MapViewOfFile(hInfoMapped, FILE_MAP_READ, 0, 0, 0);
442     if (!m_pInfo)
443         return HResultFromWin32(GetLastError());
444 
445     if (m_pInfo->dwVersion != 5 || m_pInfo->dwRecordSize != sizeof(DELETED_FILE_RECORD))
446         return E_FAIL;
447 
448     return S_OK;
449 }
450 
451 EXTERN_C
452 HRESULT
453 RecycleBin5Enum_Constructor(
454     _In_ IRecycleBin5 *prb,
455     _In_ HANDLE hInfo,
456     _In_ HANDLE hInfoMapped,
457     _In_ LPCWSTR szPrefix,
458     _Out_ IUnknown **ppUnknown)
459 {
460     if (!ppUnknown)
461         return E_POINTER;
462 
463     *ppUnknown = NULL;
464 
465     RecycleBin5Enum *pThis = new RecycleBin5Enum();
466     if (!pThis)
467         return E_OUTOFMEMORY;
468 
469     HRESULT hr = pThis->Init(prb, hInfo, hInfoMapped, szPrefix);
470     if (FAILED(hr))
471     {
472         delete pThis;
473         return hr;
474     }
475 
476     *ppUnknown = static_cast<IRecycleBinEnumList *>(pThis);
477     return S_OK;
478 }
479