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