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