xref: /reactos/dll/shellext/cabview/folder.cpp (revision 63bb46a2)
1 /*
2  * PROJECT:     ReactOS CabView Shell Extension
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Shell folder implementation
5  * COPYRIGHT:   Copyright 2024 Whindmar Saksit <whindsaks@proton.me>
6  */
7 
8 #include "cabview.h"
9 #include "util.h"
10 
11 enum FOLDERCOLUMNS
12 {
13     COL_NAME,   // PKEY_ItemNameDisplay
14     COL_SIZE,   // PKEY_Size
15     COL_TYPE,   // PKEY_ItemTypeText
16     COL_MDATE,  // PKEY_DateModified
17     COL_PATH,   // PKEY_?: Archive-relative path
18     COL_ATT,    // PKEY_FileAttributes
19     COLCOUNT
20 };
21 
22 static const struct FOLDERCOLUMN
23 {
24     BYTE TextId;
25     BYTE LvcFmt;
26     BYTE LvcChars;
27     BYTE ColFlags;
28     const GUID *pkg;
29     BYTE pki;
30 } g_Columns[] =
31 {
32     { IDS_COL_NAME, LVCFMT_LEFT, 20, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_NAME },
33     { IDS_COL_SIZE, LVCFMT_RIGHT, 16, SHCOLSTATE_TYPE_INT | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_SIZE },
34     { IDS_COL_TYPE, LVCFMT_LEFT, 20, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_STORAGETYPE },
35     { IDS_COL_MDATE, LVCFMT_LEFT, 20, SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_WRITETIME },
36     { IDS_COL_PATH, LVCFMT_LEFT, 30, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, &CLSID_CabFolder, 0 },
37     { IDS_COL_ATT, LVCFMT_RIGHT, 10, SHCOLSTATE_TYPE_STR, &FMTID_Storage, PID_STG_ATTRIBUTES },
38 };
39 
40 #include <pshpack1.h>
41 struct CABITEM
42 {
43     WORD cb;
44     WORD Unknown; // Not sure what Windows uses this for, we always store 0
45     UINT Size;
46     WORD Date, Time; // DOS
47     WORD Attrib;
48     WORD NameOffset;
49     WCHAR Path[ANYSIZE_ARRAY];
50 
51 #if FLATFOLDER
IsFolderCABITEM52     inline bool IsFolder() const { return false; }
53 #else
IsFolderCABITEM54     inline BOOL IsFolder() const { return Attrib & FILE_ATTRIBUTE_DIRECTORY; }
55 #endif
56     enum { FSATTS = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM |
57                     FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_DIRECTORY };
GetFSAttributesCABITEM58     WORD GetFSAttributes() const { return Attrib & FSATTS; }
GetNameCABITEM59     LPCWSTR GetName() const { return Path + NameOffset; }
60 
ValidateCABITEM61     template<class PIDL> static CABITEM* Validate(PIDL pidl)
62     {
63         CABITEM *p = (CABITEM*)pidl;
64         return p && p->cb > FIELD_OFFSET(CABITEM, Path[1]) && p->Unknown == 0 ? p : NULL;
65     }
66 };
67 #include <poppack.h>
68 
CreateItem(LPCWSTR Path,UINT Attrib,UINT Size,UINT DateTime)69 static CABITEM* CreateItem(LPCWSTR Path, UINT Attrib, UINT Size, UINT DateTime)
70 {
71     const SIZE_T len = lstrlenW(Path), cb = FIELD_OFFSET(CABITEM, Path[len + 1]);
72     if (cb > 0xffff)
73         return NULL;
74     CABITEM *p = (CABITEM*)SHAlloc(cb + sizeof(USHORT));
75     if (p)
76     {
77         p->cb = (USHORT)cb;
78         p->Unknown = 0;
79         p->Size = Size;
80         p->Attrib = Attrib;
81         p->Date = HIWORD(DateTime);
82         p->Time = LOWORD(DateTime);
83         p->NameOffset = 0;
84         for (UINT i = 0;; ++i)
85         {
86             WCHAR c = Path[i];
87             if (c == L':') // Don't allow absolute paths
88                 c = L'_';
89             if (c == L'/') // Normalize
90                 c = L'\\';
91             if (c == '\\')
92                 p->NameOffset = i + 1;
93             p->Path[i] = c;
94             if (!c)
95                 break;
96         }
97         ((SHITEMID*)((BYTE*)p + cb))->cb = 0;
98     }
99     return p;
100 }
101 
CreateItem(LPCSTR Path,UINT Attrib,UINT Size=0,UINT DateTime=0)102 static CABITEM* CreateItem(LPCSTR Path, UINT Attrib, UINT Size = 0, UINT DateTime = 0)
103 {
104     WCHAR buf[MAX_PATH * 2];
105     UINT codepage = (Attrib & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP;
106     if (MultiByteToWideChar(codepage, 0, Path, -1, buf, _countof(buf)))
107         return CreateItem(buf, Attrib, Size, DateTime);
108     return NULL;
109 }
110 
ItemMenuCallback(IShellFolder * psf,HWND hwnd,IDataObject * pdtobj,UINT uMsg,WPARAM wParam,LPARAM lParam)111 static HRESULT CALLBACK ItemMenuCallback(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj,
112                                          UINT uMsg, WPARAM wParam, LPARAM lParam)
113 {
114     enum { IDC_EXTRACT, IDC_EXTRACTALL };
115     HRESULT hr = E_NOTIMPL;
116     const BOOL Background = !pdtobj;
117 
118     switch (uMsg)
119     {
120         case DFM_MODIFYQCMFLAGS:
121         {
122             *((UINT*)lParam) = wParam | CMF_NOVERBS | (Background ? 0 : CMF_VERBSONLY);
123             return S_OK;
124         }
125 
126         case DFM_MERGECONTEXTMENU:
127         {
128             QCMINFO &qcmi = *(QCMINFO*)lParam;
129             UINT pos = qcmi.indexMenu, id = 0;
130             if (Background || SUCCEEDED(hr = InsertMenuItem(qcmi, pos, id, IDC_EXTRACT, IDS_EXTRACT, MFS_DEFAULT)))
131             {
132                 hr = InsertMenuItem(qcmi, pos, id, IDC_EXTRACTALL, IDS_EXTRACTALL);
133                 if (SUCCEEDED(hr) && !Background)
134                 {
135                     --pos;
136                     InsertMenuItem(qcmi, pos, id, 0, -1); // Separator
137                 }
138             }
139             if (SUCCEEDED(hr))
140             {
141                 qcmi.idCmdFirst = id + 1;
142                 hr = S_FALSE; // Don't add verbs
143             }
144             break;
145         }
146 
147         case DFM_INVOKECOMMAND:
148         {
149             hr = S_FALSE;
150             CCabFolder *pCabFolder = static_cast<CCabFolder*>(psf);
151             switch (wParam)
152             {
153                 case IDC_EXTRACT:
154                 case IDC_EXTRACTALL:
155                     hr = pCabFolder->ExtractFilesUI(hwnd, wParam == IDC_EXTRACT ? pdtobj : NULL);
156                     break;
157             }
158             break;
159         }
160     }
161     return hr;
162 }
163 
FolderBackgroundMenuCallback(IShellFolder * psf,HWND hwnd,IDataObject * pdtobj,UINT uMsg,WPARAM wParam,LPARAM lParam)164 static HRESULT CALLBACK FolderBackgroundMenuCallback(IShellFolder *psf, HWND hwnd,
165                                                      IDataObject *pdtobj, UINT uMsg,
166                                                      WPARAM wParam, LPARAM lParam)
167 {
168     return ItemMenuCallback(psf, hwnd, NULL, uMsg, wParam, lParam);
169 }
170 
FindNamedItem(PCUITEMID_CHILD pidl) const171 int CEnumIDList::FindNamedItem(PCUITEMID_CHILD pidl) const
172 {
173     CABITEM *needle = (CABITEM*)pidl;
174     for (ULONG i = 0, c = GetCount(); i < c; ++i)
175     {
176         CABITEM *item = (CABITEM*)DPA_FastGetPtr(m_Items, i);
177         if (!lstrcmpiW(needle->Path, item->Path))
178             return i;
179     }
180     return -1;
181 }
182 
183 struct FILLCALLBACKDATA
184 {
185     CEnumIDList *pEIDL;
186     SHCONTF ContF;
187 };
188 
EnumFillCallback(EXTRACTCALLBACKMSG msg,const EXTRACTCALLBACKDATA & ecd,LPVOID cookie)189 static HRESULT CALLBACK EnumFillCallback(EXTRACTCALLBACKMSG msg, const EXTRACTCALLBACKDATA &ecd, LPVOID cookie)
190 {
191     FILLCALLBACKDATA &data = *(FILLCALLBACKDATA*)cookie;
192 
193     switch ((UINT)msg)
194     {
195         case ECM_FILE:
196         {
197             const FDINOTIFICATION &fdin = *ecd.pfdin;
198             HRESULT hr = S_FALSE;
199             SFGAOF attr = MapFSToSFAttributes(fdin.attribs & CABITEM::FSATTS);
200             if (IncludeInEnumIDList(data.ContF, attr))
201             {
202                 UINT datetime = MAKELONG(fdin.time, fdin.date);
203                 CABITEM *item = CreateItem(fdin.psz1, fdin.attribs, fdin.cb, datetime);
204                 if (!item)
205                     return E_OUTOFMEMORY;
206                 if (FAILED(hr = data.pEIDL->Append((LPCITEMIDLIST)item)))
207                     SHFree(item);
208             }
209             return SUCCEEDED(hr) ? S_FALSE : hr; // Never extract
210         }
211     }
212     return E_NOTIMPL;
213 }
214 
Fill(LPCWSTR path,HWND hwnd,SHCONTF contf)215 HRESULT CEnumIDList::Fill(LPCWSTR path, HWND hwnd, SHCONTF contf)
216 {
217     FILLCALLBACKDATA data = { this, contf };
218     return ExtractCabinet(path, NULL, EnumFillCallback, &data);
219 }
220 
Fill(PCIDLIST_ABSOLUTE pidl,HWND hwnd,SHCONTF contf)221 HRESULT CEnumIDList::Fill(PCIDLIST_ABSOLUTE pidl, HWND hwnd, SHCONTF contf)
222 {
223     WCHAR path[MAX_PATH];
224     if (SHGetPathFromIDListW(pidl, path))
225         return Fill(path, hwnd, contf);
226     return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
227 }
228 
GetDefaultColumn(DWORD dwRes,ULONG * pSort,ULONG * pDisplay)229 IFACEMETHODIMP CCabFolder::GetDefaultColumn(DWORD dwRes, ULONG *pSort, ULONG *pDisplay)
230 {
231     if (pSort)
232         *pSort = COL_NAME;
233     if (pDisplay)
234         *pDisplay = COL_NAME;
235     return S_OK;
236 }
237 
GetDefaultColumnState(UINT iColumn,SHCOLSTATEF * pcsFlags)238 IFACEMETHODIMP CCabFolder::GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *pcsFlags)
239 {
240     if (!pcsFlags || iColumn >= _countof(g_Columns))
241         return E_INVALIDARG;
242     *pcsFlags = g_Columns[iColumn].ColFlags;
243     return S_OK;
244 }
245 
GetDisplayNameOf(PCUITEMID_CHILD pidl,DWORD dwFlags,LPSTRRET pName)246 IFACEMETHODIMP CCabFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET pName)
247 {
248     CABITEM *item = CABITEM::Validate(pidl);
249     if (!item || !pName)
250         return E_INVALIDARG;
251 
252     if (dwFlags & SHGDN_FORPARSING)
253     {
254         if (dwFlags & SHGDN_INFOLDER)
255             return StrTo(FLATFOLDER ? item->Path : item->GetName(), *pName);
256 
257         WCHAR parent[MAX_PATH];
258         if (!SHGetPathFromIDListW(m_CurDir, parent))
259             return E_FAIL;
260         UINT cch = lstrlenW(parent) + 1 + lstrlenW(item->Path) + 1;
261         pName->uType = STRRET_WSTR;
262         pName->pOleStr = (LPWSTR)SHAlloc(cch * sizeof(WCHAR));
263         if (!pName->pOleStr)
264             return E_OUTOFMEMORY;
265         lstrcpyW(pName->pOleStr, parent);
266         PathAppendW(pName->pOleStr, item->Path);
267         return S_OK;
268     }
269 
270     SHFILEINFO fi;
271     DWORD attr = item->IsFolder() ? FILE_ATTRIBUTE_DIRECTORY : 0;
272     UINT flags = SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES;
273     if (SHGetFileInfo(item->GetName(), attr, &fi, sizeof(fi), flags))
274         return StrTo(fi.szDisplayName, *pName);
275     return StrTo(item->GetName(), *pName);
276 }
277 
GetItemDetails(PCUITEMID_CHILD pidl,UINT iColumn,SHELLDETAILS * psd,VARIANT * pv)278 HRESULT CCabFolder::GetItemDetails(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd, VARIANT *pv)
279 {
280     HRESULT hr = E_FAIL;
281     STRRET *psr = &psd->str, srvar;
282     CABITEM *item = CABITEM::Validate(pidl);
283     if (!item)
284         return E_INVALIDARG;
285 
286     switch (iColumn)
287     {
288         case COL_NAME:
289         {
290             hr = GetDisplayNameOf(pidl, SHGDN_NORMAL | SHGDN_INFOLDER, pv ? &srvar : psr);
291             return SUCCEEDED(hr) && pv ? StrRetToVariantBSTR(&srvar, *pv) : hr;
292         }
293 
294         case COL_SIZE:
295         {
296             UINT data = item->Size;
297             if (pv)
298             {
299                 V_VT(pv) = VT_UI4;
300                 V_UI4(pv) = data;
301             }
302             else
303             {
304                 psr->uType = STRRET_CSTR;
305                 StrFormatByteSizeA(data, psr->cStr, _countof(psr->cStr));
306             }
307             return S_OK;
308         }
309 
310         case COL_TYPE:
311         {
312             SHFILEINFO fi;
313             LPCWSTR data = fi.szTypeName;
314             DWORD attr = item->GetFSAttributes();
315             UINT flags = SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES;
316             if (SHGetFileInfo(item->GetName(), attr, &fi, sizeof(fi), flags))
317                 return pv ? StrTo(data, *pv) : StrTo(data, *psr);
318             break;
319         }
320 
321         case COL_MDATE:
322         {
323             if (pv)
324             {
325                 if (DosDateTimeToVariantTime(item->Date, item->Time, &V_DATE(pv)))
326                 {
327                     V_VT(pv) = VT_DATE;
328                     return S_OK;
329                 }
330             }
331             else
332             {
333                 FILETIME utc, loc;
334                 if (DosDateTimeToFileTime(item->Date, item->Time, &utc) && FileTimeToLocalFileTime(&utc, &loc))
335                 {
336                     psr->uType = STRRET_CSTR;
337                     if (SHFormatDateTimeA(&loc, NULL, psr->cStr, _countof(psr->cStr)))
338                     {
339                         return S_OK;
340                     }
341                 }
342             }
343             break;
344         }
345 
346         case COL_PATH:
347         {
348             UINT len = item->NameOffset ? item->NameOffset - 1 : 0;
349             return pv ? StrTo(item->Path, len, *pv) : StrTo(item->Path, len, *psr);
350         }
351 
352         case COL_ATT:
353         {
354             UINT data = item->GetFSAttributes();
355             if (pv)
356             {
357                 V_VT(pv) = VT_UI4;
358                 V_UI4(pv) = data;
359             }
360             else
361             {
362                 UINT i = 0;
363                 psr->uType = STRRET_CSTR;
364                 if (data & FILE_ATTRIBUTE_READONLY) psr->cStr[i++] = 'R';
365                 if (data & FILE_ATTRIBUTE_HIDDEN) psr->cStr[i++] = 'H';
366                 if (data & FILE_ATTRIBUTE_SYSTEM) psr->cStr[i++] = 'S';
367                 if (data & FILE_ATTRIBUTE_ARCHIVE) psr->cStr[i++] = 'A';
368                 psr->cStr[i++] = '\0';
369             }
370             return S_OK;
371         }
372     }
373     return hr;
374 }
375 
GetDetailsEx(PCUITEMID_CHILD pidl,const SHCOLUMNID * pscid,VARIANT * pv)376 IFACEMETHODIMP CCabFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
377 {
378     if (!pscid || !pv)
379         return E_INVALIDARG;
380 
381     CABITEM *item;
382     int col = MapSCIDToColumn(*pscid);
383     if (col >= 0)
384     {
385         return GetItemDetails(pidl, col, NULL, pv);
386     }
387     else if ((item = CABITEM::Validate(pidl)) == NULL)
388     {
389         return E_INVALIDARG;
390     }
391     else if (IsEqual(*pscid, FMTID_ShellDetails, PID_FINDDATA))
392     {
393         WIN32_FIND_DATA wfd;
394         ZeroMemory(&wfd, sizeof(wfd));
395         wfd.dwFileAttributes = item->GetFSAttributes();
396         wfd.nFileSizeLow = item->Size;
397         DosDateTimeToFileTime(item->Date, item->Time, &wfd.ftLastWriteTime);
398         lstrcpyn(wfd.cFileName, item->GetName(), MAX_PATH);
399         return InitVariantFromBuffer(&wfd, sizeof(wfd), pv);
400     }
401     return E_FAIL;
402 }
403 
GetDetailsOf(PCUITEMID_CHILD pidl,UINT iColumn,SHELLDETAILS * psd)404 IFACEMETHODIMP CCabFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd)
405 {
406     if (!psd || iColumn >= _countof(g_Columns))
407     {
408         return E_INVALIDARG;
409     }
410     else if (!pidl)
411     {
412         psd->fmt = g_Columns[iColumn].LvcFmt;
413         psd->cxChar = g_Columns[iColumn].LvcChars;
414         WCHAR buf[MAX_PATH];
415         if (LoadStringW(_AtlBaseModule.GetResourceInstance(), g_Columns[iColumn].TextId, buf, _countof(buf)))
416             return StrTo(buf, psd->str);
417         return E_FAIL;
418     }
419     return GetItemDetails(pidl, iColumn, psd, NULL);
420 }
421 
MapSCIDToColumn(const SHCOLUMNID & scid)422 int CCabFolder::MapSCIDToColumn(const SHCOLUMNID &scid)
423 {
424     for (UINT i = 0; i < _countof(g_Columns); ++i)
425     {
426         if (g_Columns[i].pkg && IsEqual(scid, *g_Columns[i].pkg, g_Columns[i].pki))
427             return i;
428     }
429     return -1;
430 }
431 
MapColumnToSCID(UINT column,SHCOLUMNID * pscid)432 IFACEMETHODIMP CCabFolder::MapColumnToSCID(UINT column, SHCOLUMNID *pscid)
433 {
434     if (column < _countof(g_Columns) && g_Columns[column].pkg)
435     {
436         pscid->fmtid = *g_Columns[column].pkg;
437         pscid->pid = g_Columns[column].pki;
438         return S_OK;
439     }
440     return E_FAIL;
441 }
442 
EnumObjects(HWND hwndOwner,DWORD dwFlags,LPENUMIDLIST * ppEnumIDList)443 IFACEMETHODIMP CCabFolder::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList)
444 {
445     CEnumIDList *p = CEnumIDList::CreateInstance();
446     *ppEnumIDList = static_cast<LPENUMIDLIST>(p);
447     return p ? p->Fill(m_CurDir, hwndOwner, dwFlags) : E_OUTOFMEMORY;
448 }
449 
BindToObject(PCUIDLIST_RELATIVE pidl,LPBC pbcReserved,REFIID riid,LPVOID * ppvOut)450 IFACEMETHODIMP CCabFolder::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
451 {
452     UNIMPLEMENTED;
453     return E_NOTIMPL;
454 }
455 
CompareID(LPARAM lParam,PCUITEMID_CHILD pidl1,PCUITEMID_CHILD pidl2)456 HRESULT CCabFolder::CompareID(LPARAM lParam, PCUITEMID_CHILD pidl1, PCUITEMID_CHILD pidl2)
457 {
458     CABITEM *p1 = (CABITEM*)pidl1, *p2 = (CABITEM*)pidl2;
459     HRESULT hr = S_OK;
460     int ret = 0;
461 
462     if (lParam & (SHCIDS_ALLFIELDS | SHCIDS_CANONICALONLY))
463     {
464         ret = lstrcmpiW(p1->Path, p2->Path);
465         if (ret && (lParam & SHCIDS_ALLFIELDS))
466         {
467             for (UINT i = 0; ret && SUCCEEDED(hr) && i < COLCOUNT; ++i)
468             {
469                 hr = (i == COL_NAME) ? 0 : CompareID(i, pidl1, pidl2);
470                 ret = (short)HRESULT_CODE(hr);
471             }
472         }
473     }
474     else
475     {
476         UINT col = lParam & SHCIDS_COLUMNMASK;
477         switch (col)
478         {
479             case COL_NAME:
480                 ret = StrCmpLogicalW(p1->GetName(), p2->GetName());
481                 break;
482 
483             case COL_SIZE:
484                 ret = p1->Size - p2->Size;
485                 break;
486 
487             case COL_MDATE:
488                 ret = MAKELONG(p1->Time, p1->Date) - MAKELONG(p2->Time, p2->Date);
489                 break;
490 
491             default:
492             {
493                 if (col < COLCOUNT)
494                 {
495                     PWSTR str1, str2;
496                     if (SUCCEEDED(hr = ::GetDetailsOf(*this, pidl1, col, str1)))
497                     {
498                         if (SUCCEEDED(hr = ::GetDetailsOf(*this, pidl2, col, str2)))
499                         {
500                             ret = StrCmpLogicalW(str1, str2);
501                             SHFree(str2);
502                         }
503                         SHFree(str1);
504                     }
505                 }
506                 else
507                 {
508                     hr = E_INVALIDARG;
509                 }
510             }
511         }
512     }
513     return SUCCEEDED(hr) ? MAKE_COMPARE_HRESULT(ret) : hr;
514 }
515 
CompareIDs(LPARAM lParam,PCUIDLIST_RELATIVE pidl1,PCUIDLIST_RELATIVE pidl2)516 IFACEMETHODIMP CCabFolder::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
517 {
518     C_ASSERT(FLATFOLDER);
519     if (!pidl1 || !ILIsSingle(pidl1) || !pidl2 || !ILIsSingle(pidl2))
520         return E_UNEXPECTED;
521 
522     return CompareID(lParam, pidl1, pidl2);
523 }
524 
CreateViewObject(HWND hwndOwner,REFIID riid,LPVOID * ppv)525 IFACEMETHODIMP CCabFolder::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID *ppv)
526 {
527     if (riid == IID_IShellView)
528     {
529         SFV_CREATE sfvc = { sizeof(SFV_CREATE), static_cast<IShellFolder*>(this) };
530         return SHCreateShellFolderView(&sfvc, (IShellView**)ppv);
531     }
532     if (riid == IID_IContextMenu)
533     {
534         LPFNDFMCALLBACK func = FolderBackgroundMenuCallback;
535         IContextMenu **ppCM = (IContextMenu**)ppv;
536         return CDefFolderMenu_Create2(m_CurDir, hwndOwner, 0, NULL, this, func, 0, NULL, ppCM);
537     }
538     return E_NOINTERFACE;
539 }
540 
GetAttributesOf(UINT cidl,PCUITEMID_CHILD_ARRAY apidl,SFGAOF * rgfInOut)541 IFACEMETHODIMP CCabFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, SFGAOF *rgfInOut)
542 {
543     if (!cidl)
544     {
545         const SFGAOF ThisFolder = (SFGAO_FOLDER | SFGAO_BROWSABLE | SFGAO_CANLINK);
546         *rgfInOut = *rgfInOut & ThisFolder;
547         return S_OK;
548     }
549     else if (!apidl)
550     {
551         return E_INVALIDARG;
552     }
553     HRESULT hr = S_OK;
554     const SFGAOF filemask = SFGAO_READONLY | SFGAO_HIDDEN | SFGAO_SYSTEM | SFGAO_ISSLOW;
555     SFGAOF remain = *rgfInOut & filemask, validate = *rgfInOut & SFGAO_VALIDATE;
556     CComPtr<CEnumIDList> list;
557     for (UINT i = 0; i < cidl && (remain || validate); ++i)
558     {
559         CABITEM *item = CABITEM::Validate(apidl[i]);
560         if (!item)
561         {
562             hr = E_INVALIDARG;
563             break;
564         }
565         else if (validate)
566         {
567             if (!list && FAILED_UNEXPECTEDLY(hr = CreateEnum(&list)))
568                 return hr;
569             if (list->FindNamedItem((PCUITEMID_CHILD)item) == -1)
570                 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
571         }
572         SFGAOF att = MapFSToSFAttributes(item->GetFSAttributes()) | SFGAO_ISSLOW;
573         remain &= att & ~(FLATFOLDER ? SFGAO_FOLDER : 0);
574     }
575     *rgfInOut = remain;
576     return hr;
577 }
578 
GetUIObjectOf(HWND hwndOwner,UINT cidl,PCUITEMID_CHILD_ARRAY apidl,REFIID riid,UINT * prgfInOut,LPVOID * ppvOut)579 IFACEMETHODIMP CCabFolder::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT *prgfInOut, LPVOID *ppvOut)
580 {
581     HRESULT hr = E_NOINTERFACE;
582     if (riid == IID_IExtractIconA || riid == IID_IExtractIconW)
583     {
584         if (cidl != 1)
585             return E_INVALIDARG;
586         CABITEM *item = CABITEM::Validate(apidl[0]);
587         if (!item)
588             return E_INVALIDARG;
589 
590         DWORD attr = item->GetFSAttributes();
591         return SHCreateFileExtractIconW(item->GetName(), attr, riid, ppvOut);
592     }
593     else if (riid == IID_IContextMenu && cidl)
594     {
595         LPFNDFMCALLBACK func = ItemMenuCallback;
596         IContextMenu **ppCM = (IContextMenu**)ppvOut;
597         return CDefFolderMenu_Create2(NULL, hwndOwner, cidl, apidl, this, func, 0, NULL, ppCM);
598     }
599     else if (riid == IID_IDataObject && cidl)
600     {
601         // Note: This IDataObject is only compatible with IContextMenu, it cannot handle drag&drop of virtual items!
602         return CIDLData_CreateFromIDArray(m_CurDir, cidl, apidl, (IDataObject**)ppvOut);
603     }
604     return hr;
605 }
606 
MessageSFVCB(UINT uMsg,WPARAM wParam,LPARAM lParam)607 IFACEMETHODIMP CCabFolder::MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam)
608 {
609     switch (uMsg)
610     {
611         case SFVM_WINDOWCREATED:
612             m_ShellViewWindow = (HWND)wParam;
613             return S_OK;
614         case SFVM_WINDOWCLOSING:
615             m_ShellViewWindow = NULL;
616             return S_OK;
617     }
618     return E_NOTIMPL;
619 }
620 
GetIconOf(PCUITEMID_CHILD pidl,UINT flags,int * pIconIndex)621 IFACEMETHODIMP CCabFolder::GetIconOf(PCUITEMID_CHILD pidl, UINT flags, int *pIconIndex)
622 {
623     if (CABITEM *item = CABITEM::Validate(pidl))
624     {
625         int index = MapPIDLToSystemImageListIndex(this, pidl, flags);
626         if (index == -1 && item->IsFolder())
627             index = (flags & GIL_OPENICON) ? SIID_FOLDEROPEN : SIID_FOLDER;
628         if (index != -1)
629         {
630             *pIconIndex = index;
631             return S_OK;
632         }
633     }
634     return S_FALSE;
635 }
636 
GetFsPathFromIDList(PCIDLIST_ABSOLUTE pidl,PWSTR pszPath)637 static HRESULT GetFsPathFromIDList(PCIDLIST_ABSOLUTE pidl, PWSTR pszPath)
638 {
639     BOOL ret = SHGetPathFromIDListW(pidl, pszPath);
640     if (!ret && ILIsEmpty(pidl))
641         ret = SHGetSpecialFolderPathW(NULL, pszPath, CSIDL_DESKTOPDIRECTORY, TRUE);
642     return ret ? S_OK : HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
643 }
644 
FolderBrowseCallback(HWND hwnd,UINT uMsg,LPARAM lParam,LPARAM lpData)645 static int CALLBACK FolderBrowseCallback(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
646 {
647     WCHAR buf[MAX_PATH];
648     switch (uMsg)
649     {
650         case BFFM_INITIALIZED:
651         {
652             if (LoadStringW(_AtlBaseModule.GetResourceInstance(), IDS_EXTRACT, buf, _countof(buf)))
653             {
654                 // Remove leading and trailing dots
655                 WCHAR *s = buf, *e = s + lstrlenW(s);
656                 while (*s == '.') ++s;
657                 while (e > s && e[-1] == '.') *--e = UNICODE_NULL;
658                 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)s);
659                 SendMessageW(GetDlgItem(hwnd, IDOK), WM_SETTEXT, 0, (LPARAM)s);
660             }
661             if (lpData)
662             {
663                 SendMessageW(hwnd, BFFM_SETEXPANDED, FALSE, lpData);
664                 SendMessageW(hwnd, BFFM_SETSELECTION, FALSE, lpData);
665             }
666             break;
667         }
668 
669         case BFFM_SELCHANGED:
670         {
671             SFGAOF wanted = SFGAO_FILESYSTEM | SFGAO_FOLDER, query = wanted | SFGAO_STREAM;
672             PCIDLIST_ABSOLUTE pidl = (PCIDLIST_ABSOLUTE)lParam;
673             BOOL enable = ILIsEmpty(pidl); // Allow the desktop
674             PCUITEMID_CHILD child;
675             IShellFolder *pSF;
676             if (SUCCEEDED(SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &pSF), &child)))
677             {
678                 SFGAOF attrib = query;
679                 if (SUCCEEDED(pSF->GetAttributesOf(1, &child, &attrib)))
680                     enable = (attrib & query) == wanted;
681                 pSF->Release();
682             }
683             if (enable)
684             {
685                 // We don't trust .zip folders, check the file-system to make sure
686                 UINT attrib = SUCCEEDED(GetFsPathFromIDList(pidl, buf)) ? GetFileAttributesW(buf) : 0;
687                 enable = (attrib & FILE_ATTRIBUTE_DIRECTORY) && attrib != INVALID_FILE_ATTRIBUTES;
688             }
689             PostMessageW(hwnd, BFFM_ENABLEOK, 0, enable);
690             break;
691         }
692     }
693     return 0;
694 }
695 
696 struct EXTRACTFILESDATA
697 {
698     CCabFolder *pLifetimeCF;
699     HWND hWndOwner;
700     CIDA *pCIDA;
701     STGMEDIUM cidamedium;
702     IDataObject *pDO;
703     IStream *pMarshalDO;
704     IProgressDialog *pPD;
705     UINT cabfiles, completed;
706     WCHAR path[MAX_PATH], cab[MAX_PATH];
707 };
708 
GetUiOwner(const EXTRACTFILESDATA & data)709 static HWND GetUiOwner(const EXTRACTFILESDATA &data)
710 {
711     HWND hWnd;
712     if (SUCCEEDED(IUnknown_GetWindow(data.pPD, &hWnd)) && IsWindowVisible(hWnd))
713         return hWnd;
714     return IsWindowVisible(data.hWndOwner) ? data.hWndOwner : NULL;
715 }
716 
ExtractFilesCallback(EXTRACTCALLBACKMSG msg,const EXTRACTCALLBACKDATA & ecd,LPVOID cookie)717 static HRESULT CALLBACK ExtractFilesCallback(EXTRACTCALLBACKMSG msg, const EXTRACTCALLBACKDATA &ecd, LPVOID cookie)
718 {
719     EXTRACTFILESDATA &data = *(EXTRACTFILESDATA*)cookie;
720     switch ((UINT)msg)
721     {
722         case ECM_BEGIN:
723         {
724             data.cabfiles = (UINT)(SIZE_T)ecd.pfdin->hf;
725             return S_OK;
726         }
727 
728         case ECM_FILE:
729         {
730             if (data.pPD && data.pPD->HasUserCancelled())
731                 return HRESULT_FROM_WIN32(ERROR_CANCELLED);
732             HRESULT hr = data.pCIDA ? S_FALSE : S_OK; // Filtering or all items?
733             if (hr != S_OK)
734             {
735                 CABITEM *needle = CreateItem(ecd.pfdin->psz1, ecd.pfdin->attribs);
736                 if (!needle)
737                     return E_OUTOFMEMORY;
738                 for (UINT i = 0; i < data.pCIDA->cidl && hr == S_FALSE; ++i)
739                 {
740                     C_ASSERT(FLATFOLDER);
741                     LPCITEMIDLIST pidlChild = ILFindLastID(HIDA_GetPIDLItem(data.pCIDA, i));
742                     CABITEM *haystack = CABITEM::Validate(pidlChild);
743                     if (!haystack && FAILED_UNEXPECTEDLY(hr = E_FAIL))
744                         break;
745                     if (!lstrcmpiW(needle->Path, haystack->Path))
746                     {
747                         if (data.pPD)
748                             data.pPD->SetLine(1, needle->Path, TRUE, NULL);
749                         hr = S_OK; // Found it in the list of files to extract
750                     }
751                 }
752                 SHFree(needle);
753             }
754             if (data.pPD)
755                 data.pPD->SetProgress(data.completed++, data.cabfiles);
756             return hr;
757         }
758 
759         case ECM_PREPAREPATH:
760         {
761             UINT flags = SHPPFW_DIRCREATE | SHPPFW_IGNOREFILENAME;
762             return SHPathPrepareForWriteW(GetUiOwner(data), NULL, ecd.Path, flags);
763         }
764 
765         case ECM_ERROR:
766         {
767             return ErrorBox(GetUiOwner(data), ecd.hr);
768         }
769     }
770     return E_NOTIMPL;
771 }
772 
Free(EXTRACTFILESDATA & data)773 static void Free(EXTRACTFILESDATA &data)
774 {
775     if (data.pPD)
776     {
777         data.pPD->StopProgressDialog();
778         data.pPD->Release();
779     }
780     CDataObjectHIDA::DestroyCIDA(data.pCIDA, data.cidamedium);
781     IUnknown_Set((IUnknown**)&data.pDO, NULL);
782     IUnknown_Set((IUnknown**)&data.pMarshalDO, NULL);
783     IUnknown_Set((IUnknown**)&data.pLifetimeCF, NULL);
784     SHFree(&data);
785 }
786 
ExtractFilesThread(LPVOID pParam)787 static DWORD CALLBACK ExtractFilesThread(LPVOID pParam)
788 {
789     EXTRACTFILESDATA &data = *(EXTRACTFILESDATA*)pParam;
790     HRESULT hr = S_OK;
791     if (SUCCEEDED(SHCoCreateInstance(NULL, &CLSID_ProgressDialog, NULL, IID_PPV_ARG(IProgressDialog, &data.pPD))))
792     {
793         // TODO: IActionProgress SPACTION_COPYING
794         if (SUCCEEDED(data.pPD->StartProgressDialog(data.hWndOwner, NULL, PROGDLG_NOTIME, NULL)))
795         {
796             data.pPD->SetTitle(data.cab);
797             data.pPD->SetLine(2, data.path, TRUE, NULL);
798             data.pPD->SetAnimation(GetModuleHandleW(L"SHELL32"), 161);
799             data.pPD->SetProgress(0, 0);
800         }
801     }
802     if (data.pMarshalDO)
803     {
804         hr = CoGetInterfaceAndReleaseStream(data.pMarshalDO, IID_PPV_ARG(IDataObject, &data.pDO));
805         data.pMarshalDO = NULL;
806         if (SUCCEEDED(hr))
807             hr = CDataObjectHIDA::CreateCIDA(data.pDO, &data.pCIDA, data.cidamedium);
808     }
809     if (SUCCEEDED(hr))
810     {
811         ExtractCabinet(data.cab, data.path, ExtractFilesCallback, &data);
812     }
813     Free(data);
814     return 0;
815 }
816 
ExtractFilesUI(HWND hWnd,IDataObject * pDO)817 HRESULT CCabFolder::ExtractFilesUI(HWND hWnd, IDataObject *pDO)
818 {
819     if (!IsWindowVisible(hWnd) && IsWindowVisible(m_ShellViewWindow))
820         hWnd = m_ShellViewWindow;
821 
822     EXTRACTFILESDATA *pData = (EXTRACTFILESDATA*)SHAlloc(sizeof(*pData));
823     if (!pData)
824         return E_OUTOFMEMORY;
825     ZeroMemory(pData, sizeof(*pData));
826     pData->hWndOwner = hWnd;
827     pData->pLifetimeCF = this;
828     pData->pLifetimeCF->AddRef();
829 
830     HRESULT hr = GetFsPathFromIDList(m_CurDir, pData->cab);
831     if (SUCCEEDED(hr) && pDO)
832     {
833         hr = CoMarshalInterThreadInterfaceInStream(IID_IDataObject, pDO, &pData->pMarshalDO);
834     }
835     if (SUCCEEDED(hr))
836     {
837         hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
838         LPITEMIDLIST pidlInitial = ILClone(m_CurDir);
839         ILRemoveLastID(pidlInitial); // Remove the "name.cab" part (we can't extract into ourselves)
840         UINT bif = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;
841         BROWSEINFO bi = { hWnd, NULL, NULL, pData->cab, bif, FolderBrowseCallback, (LPARAM)pidlInitial };
842         if (PIDLIST_ABSOLUTE folder = SHBrowseForFolderW(&bi))
843         {
844             hr = GetFsPathFromIDList(folder, pData->path);
845             ILFree(folder);
846             if (SUCCEEDED(hr))
847             {
848                 UINT ctf = CTF_COINIT | CTF_PROCESS_REF | CTF_THREAD_REF | CTF_FREELIBANDEXIT;
849                 hr = SHCreateThread(ExtractFilesThread, pData, ctf, NULL) ? S_OK : E_OUTOFMEMORY;
850             }
851         }
852         ILFree(pidlInitial);
853     }
854     if (hr != S_OK)
855         Free(*pData);
856     return hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) ? S_OK : hr;
857 }
858