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