2 * PROJECT: ReactOS Zip Shell Extension
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
5 * COPYRIGHT: Copyright 2017 Mark Jansen (mark.jansen@reactos.org)
9 EXTERN_C HRESULT WINAPI SHCreateFileExtractIconW(LPCWSTR pszPath, DWORD dwFileAttributes, REFIID riid, void **ppv);
12 struct FolderViewColumns
20 static FolderViewColumns g_ColumnDefs[] =
22 { IDS_COL_NAME, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 25, LVCFMT_LEFT },
23 { IDS_COL_TYPE, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 20, LVCFMT_LEFT },
24 { IDS_COL_COMPRSIZE, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 10, LVCFMT_RIGHT },
25 { IDS_COL_PASSWORD, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 10, LVCFMT_LEFT },
26 { IDS_COL_SIZE, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 10, LVCFMT_RIGHT },
27 { IDS_COL_RATIO, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 10, LVCFMT_LEFT },
28 { IDS_COL_DATE_MOD, SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT, 15, LVCFMT_LEFT },
33 public CComCoClass<CZipFolder, &CLSID_ZipFolderStorageHandler>,
34 public CComObjectRootEx<CComMultiThreadModelNoCS>,
39 //public IPersistFile,
40 public IPersistFolder2,
45 CComHeapPtr<ITEMIDLIST> m_CurDir;
62 unzClose(m_UnzipFile);
66 // *** IZip methods ***
67 STDMETHODIMP_(unzFile) getZip()
71 m_UnzipFile = unzOpen2_64(m_ZipFile, &g_FFunc);
77 // *** IShellFolder2 methods ***
78 STDMETHODIMP GetDefaultSearchGUID(GUID *pguid)
83 STDMETHODIMP EnumSearches(IEnumExtraSearch **ppenum)
88 STDMETHODIMP GetDefaultColumn(DWORD dwRes, ULONG *pSort, ULONG *pDisplay)
93 STDMETHODIMP GetDefaultColumnState(UINT iColumn, DWORD *pcsFlags)
95 if (!pcsFlags || iColumn >= _countof(g_ColumnDefs))
97 *pcsFlags = g_ColumnDefs[iColumn].dwDefaultState;
100 STDMETHODIMP GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
105 // Adapted from CFileDefExt::GetFileTimeString
106 BOOL _GetFileTimeString(LPFILETIME lpFileTime, LPWSTR pwszResult, UINT cchResult)
110 if (!FileTimeToSystemTime(lpFileTime, &st))
113 size_t cchRemaining = cchResult;
114 LPWSTR pwszEnd = pwszResult;
115 int cchWritten = GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, pwszEnd, cchRemaining);
117 --cchWritten; // GetDateFormatW returns count with terminating zero
120 cchRemaining -= cchWritten;
121 pwszEnd += cchWritten;
123 StringCchCopyExW(pwszEnd, cchRemaining, L" ", &pwszEnd, &cchRemaining, 0);
125 cchWritten = GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, pwszEnd, cchRemaining);
127 --cchWritten; // GetTimeFormatW returns count with terminating zero
133 STDMETHODIMP GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd)
135 if (iColumn >= _countof(g_ColumnDefs))
138 psd->cxChar = g_ColumnDefs[iColumn].cxChar;
139 psd->fmt = g_ColumnDefs[iColumn].fmt;
143 return SHSetStrRet(&psd->str, _AtlBaseModule.GetResourceInstance(), g_ColumnDefs[iColumn].iResource);
146 PCUIDLIST_RELATIVE curpidl = ILGetNext(pidl);
147 if (curpidl->mkid.cb != 0)
149 DPRINT1("ERROR, unhandled PIDL!\n");
153 const ZipPidlEntry* zipEntry = _ZipFromIL(pidl);
158 bool isDir = zipEntry->ZipType == ZIP_PIDL_DIRECTORY;
161 case 0: /* Name, ReactOS specific? */
162 return GetDisplayNameOf(pidl, 0, &psd->str);
166 DWORD dwAttributes = isDir ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL;
167 ULONG_PTR firet = SHGetFileInfoA(zipEntry->Name, dwAttributes, &shfi, sizeof(shfi), SHGFI_USEFILEATTRIBUTES | SHGFI_TYPENAME);
170 return SHSetStrRet(&psd->str, shfi.szTypeName);
172 case 2: /* Compressed size */
175 ULONG64 Size = iColumn == 2 ? zipEntry->CompressedSize : zipEntry->UncompressedSize;
176 if (!StrFormatByteSizeW(Size, Buffer, _countof(Buffer)))
178 return SHSetStrRet(&psd->str, Buffer);
180 case 3: /* Password */
182 return SHSetStrRet(&psd->str, L"");
183 return SHSetStrRet(&psd->str, _AtlBaseModule.GetResourceInstance(), zipEntry->Password ? IDS_YES : IDS_NO);
187 if (zipEntry->UncompressedSize && !isDir)
188 ratio = 100 - (int)((zipEntry->CompressedSize*100)/zipEntry->UncompressedSize);
189 StringCchPrintfW(Buffer, _countof(Buffer), L"%d%%", ratio);
190 return SHSetStrRet(&psd->str, Buffer);
195 return SHSetStrRet(&psd->str, L"");
197 DosDateTimeToFileTime((WORD)(zipEntry->DosDate>>16), (WORD)zipEntry->DosDate, &ftLocal);
198 if (!_GetFileTimeString(&ftLocal, Buffer, _countof(Buffer)))
200 return SHSetStrRet(&psd->str, Buffer);
207 STDMETHODIMP MapColumnToSCID(UINT column, SHCOLUMNID *pscid)
213 // *** IShellFolder methods ***
214 STDMETHODIMP ParseDisplayName(HWND hwndOwner, LPBC pbc, LPOLESTR lpszDisplayName, ULONG *pchEaten, PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes)
219 STDMETHODIMP EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList)
221 return _CEnumZipContents_CreateInstance(this, dwFlags, m_ZipDir, IID_PPV_ARG(IEnumIDList, ppEnumIDList));
223 STDMETHODIMP BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
225 if (riid == IID_IShellFolder)
227 CStringA newZipDir = m_ZipDir;
228 PCUIDLIST_RELATIVE curpidl = pidl;
229 while (curpidl->mkid.cb)
231 const ZipPidlEntry* zipEntry = _ZipFromIL(curpidl);
236 newZipDir += zipEntry->Name;
239 curpidl = ILGetNext(curpidl);
241 return ShellObjectCreatorInit<CZipFolder>(m_ZipFile, newZipDir, m_CurDir, pidl, riid, ppvOut);
243 DbgPrint("%s(%S) UNHANDLED\n", __FUNCTION__, guid2string(riid));
246 STDMETHODIMP BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
251 STDMETHODIMP CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
253 const ZipPidlEntry* zipEntry1 = _ZipFromIL(pidl1);
254 const ZipPidlEntry* zipEntry2 = _ZipFromIL(pidl2);
256 if (!zipEntry1 || !zipEntry2)
260 if (zipEntry1->ZipType != zipEntry2->ZipType)
261 result = zipEntry1->ZipType - zipEntry2->ZipType;
263 result = stricmp(zipEntry1->Name, zipEntry2->Name);
265 if (!result && zipEntry1->ZipType == ZIP_PIDL_DIRECTORY)
267 PCUIDLIST_RELATIVE child1 = ILGetNext(pidl1);
268 PCUIDLIST_RELATIVE child2 = ILGetNext(pidl2);
270 if (child1->mkid.cb && child2->mkid.cb)
271 return CompareIDs(lParam, child1, child2);
272 else if (child1->mkid.cb)
274 else if (child2->mkid.cb)
278 return MAKE_COMPARE_HRESULT(result);
280 STDMETHODIMP CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID *ppvOut)
282 static const GUID UnknownIID = // {93F81976-6A0D-42C3-94DD-AA258A155470}
283 {0x93F81976, 0x6A0D, 0x42C3, {0x94, 0xDD, 0xAA, 0x25, 0x8A, 0x15, 0x54, 0x70}};
284 if (riid == IID_IShellView)
286 SFV_CREATE sfvparams = {sizeof(SFV_CREATE), this};
287 CComPtr<IShellFolderViewCB> pcb;
289 HRESULT hr = _CFolderViewCB_CreateInstance(IID_PPV_ARG(IShellFolderViewCB, &pcb));
290 if (FAILED_UNEXPECTEDLY(hr))
293 sfvparams.psfvcb = pcb;
294 hr = SHCreateShellFolderView(&sfvparams, (IShellView**)ppvOut);
298 if (riid == IID_IExplorerCommandProvider)
300 return _CExplorerCommandProvider_CreateInstance(this, riid, ppvOut);
302 if (UnknownIID != riid)
303 DbgPrint("%s(%S) UNHANDLED\n", __FUNCTION__, guid2string(riid));
306 STDMETHODIMP GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD *rgfInOut)
308 if (!rgfInOut || !cidl || !apidl)
313 //static DWORD dwFileAttrs = SFGAO_STREAM | SFGAO_HASPROPSHEET | SFGAO_CANDELETE | SFGAO_CANCOPY | SFGAO_CANMOVE;
314 //static DWORD dwFolderAttrs = SFGAO_FOLDER | SFGAO_DROPTARGET | SFGAO_HASPROPSHEET | SFGAO_CANDELETE | SFGAO_STORAGE | SFGAO_CANCOPY | SFGAO_CANMOVE;
315 static DWORD dwFileAttrs = SFGAO_STREAM;
316 static DWORD dwFolderAttrs = SFGAO_FOLDER | SFGAO_HASSUBFOLDER | SFGAO_BROWSABLE;
319 while (cidl > 0 && *apidl)
321 const ZipPidlEntry* zipEntry = _ZipFromIL(*apidl);
325 if (zipEntry->ZipType == ZIP_PIDL_FILE)
326 *rgfInOut |= dwFileAttrs;
328 *rgfInOut |= dwFolderAttrs;
339 *rgfInOut &= ~SFGAO_VALIDATE;
342 static HRESULT CALLBACK ZipFolderMenuCallback(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj,
343 UINT uMsg, WPARAM wParam, LPARAM lParam)
347 case DFM_MERGECONTEXTMENU:
348 DPRINT1("FIXME: Add menu items for DFM_MERGECONTEXTMENU\n");
350 case DFM_INVOKECOMMAND:
351 case DFM_INVOKECOMMANDEX:
352 case DFM_GETDEFSTATICID: // Required for Windows 7 to pick a default
357 STDMETHODIMP GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT * prgfInOut, LPVOID * ppvOut)
359 if ((riid == IID_IExtractIconA || riid == IID_IExtractIconW) && cidl == 1)
361 const ZipPidlEntry* zipEntry = _ZipFromIL(*apidl);
364 CComHeapPtr<WCHAR> pathW;
366 int len = MultiByteToWideChar(CP_ACP, 0, zipEntry->Name, -1, NULL, 0);
368 MultiByteToWideChar(CP_ACP, 0, zipEntry->Name, -1, pathW, len);
370 DWORD dwAttributes = (zipEntry->ZipType == ZIP_PIDL_DIRECTORY) ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL;
371 return SHCreateFileExtractIconW(pathW, dwAttributes, riid, ppvOut);
374 else if (riid == IID_IContextMenu && cidl >= 0)
376 const ZipPidlEntry* zipEntry = _ZipFromIL(*apidl);
381 if (zipEntry->ZipType == ZIP_PIDL_DIRECTORY)
383 LSTATUS res = RegOpenKeyExW(HKEY_CLASSES_ROOT, L"Folder", 0, KEY_READ | KEY_QUERY_VALUE, keys);
384 if (res != ERROR_SUCCESS)
388 return CDefFolderMenu_Create2(NULL, hwndOwner, cidl, apidl, this, ZipFolderMenuCallback, nkeys, keys, (IContextMenu**)ppvOut);
391 else if (riid == IID_IDataObject && cidl >= 1)
393 return CIDLData_CreateFromIDArray(m_CurDir, cidl, apidl, (IDataObject**)ppvOut);
396 DbgPrint("%s(%S) UNHANDLED\n", __FUNCTION__ , guid2string(riid));
397 return E_NOINTERFACE;
399 STDMETHODIMP GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET strRet)
404 PCUIDLIST_RELATIVE curpidl = ILGetNext(pidl);
405 if (curpidl->mkid.cb != 0)
407 DPRINT1("ERROR, unhandled PIDL!\n");
411 const ZipPidlEntry* zipEntry = _ZipFromIL(pidl);
415 return SHSetStrRet(strRet, (LPCSTR)zipEntry->Name);
417 STDMETHODIMP SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl, LPCOLESTR lpName, DWORD dwFlags, PITEMID_CHILD *pPidlOut)
423 //STDMETHODIMP CreateStream(LPCOLESTR pwcsName, DWORD grfMode, DWORD reserved1, DWORD reserved2, IStream **ppstm);
424 //STDMETHODIMP OpenStream(LPCOLESTR pwcsName, void *reserved1, DWORD grfMode, DWORD reserved2, IStream **ppstm);
425 //STDMETHODIMP CreateStorage(LPCOLESTR pwcsName, DWORD grfMode, DWORD dwStgFmt, DWORD reserved2, IStorage **ppstg);
426 //STDMETHODIMP OpenStorage(LPCOLESTR pwcsName, IStorage *pstgPriority, DWORD grfMode, SNB snbExclude, DWORD reserved, IStorage **ppstg);
427 //STDMETHODIMP CopyTo(DWORD ciidExclude, const IID *rgiidExclude, SNB snbExclude, IStorage *pstgDest);
428 //STDMETHODIMP MoveElementTo(LPCOLESTR pwcsName, IStorage *pstgDest, LPCOLESTR pwcsNewName, DWORD grfFlags);
429 //STDMETHODIMP Commit(DWORD grfCommitFlags);
430 //STDMETHODIMP Revert();
431 //STDMETHODIMP EnumElements(DWORD reserved1, void *reserved2, DWORD reserved3, IEnumSTATSTG **ppenum);
432 //STDMETHODIMP DestroyElement(LPCOLESTR pwcsName);
433 //STDMETHODIMP RenameElement(LPCOLESTR pwcsOldName, LPCOLESTR pwcsNewName);
434 //STDMETHODIMP SetElementTimes(LPCOLESTR pwcsName, const FILETIME *pctime, const FILETIME *patime, const FILETIME *pmtime);
435 //STDMETHODIMP SetClass(REFCLSID clsid);
436 //STDMETHODIMP SetStateBits(DWORD grfStateBits, DWORD grfMask);
437 //STDMETHODIMP Stat(STATSTG *pstatstg, DWORD grfStatFlag);
439 // *** IContextMenu methods ***
440 STDMETHODIMP GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT *pwReserved, LPSTR pszName, UINT cchMax)
448 return StringCchCopyA(pszName, cchMax, EXTRACT_VERBA);
450 return StringCchCopyW((LPWSTR)pszName, cchMax, EXTRACT_VERBW);
453 CStringA helpText(MAKEINTRESOURCEA(IDS_HELPTEXT));
454 return StringCchCopyA(pszName, cchMax, helpText);
458 CStringW helpText(MAKEINTRESOURCEA(IDS_HELPTEXT));
459 return StringCchCopyW((LPWSTR)pszName, cchMax, helpText);
468 STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pici)
470 if (!pici || pici->cbSize != sizeof(*pici))
473 if (pici->lpVerb == MAKEINTRESOURCEA(0) || (HIWORD(pici->lpVerb) && !strcmp(pici->lpVerb, EXTRACT_VERBA)))
475 BSTR ZipFile = m_ZipFile.AllocSysString();
476 InterlockedIncrement(&g_ModuleRefCnt);
479 HANDLE hThread = CreateThread(NULL, 0, s_ExtractProc, ZipFile, NULL, &tid);
482 CloseHandle(hThread);
488 STDMETHODIMP QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
492 CStringW menuText(MAKEINTRESOURCEW(IDS_MENUITEM));
494 InsertMenuW(hmenu, indexMenu++, MF_BYPOSITION | MF_SEPARATOR, 0, NULL);
496 InsertMenuW(hmenu, indexMenu++, MF_BYPOSITION | MF_STRING, idCmdFirst++, menuText);
499 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, Entries);
502 // *** IShellExtInit methods ***
503 STDMETHODIMP Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hkeyProgID)
505 FORMATETC etc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
508 HRESULT hr = pDataObj->GetData(&etc, &stg);
509 if (FAILED_UNEXPECTEDLY(hr))
514 HDROP hdrop = (HDROP)GlobalLock(stg.hGlobal);
517 UINT uNumFiles = DragQueryFileW(hdrop, 0xFFFFFFFF, NULL, 0);
520 WCHAR szFile[MAX_PATH * 2];
521 if (DragQueryFileW(hdrop, 0, szFile, _countof(szFile)))
523 CComHeapPtr<ITEMIDLIST> pidl;
524 hr = SHParseDisplayName(szFile, NULL, &pidl, 0, NULL);
525 if (!FAILED_UNEXPECTEDLY(hr))
527 hr = Initialize(pidl);
532 DbgPrint("Failed to query the file.\r\n");
537 DbgPrint("Invalid number of files: %d\r\n", uNumFiles);
539 GlobalUnlock(stg.hGlobal);
543 DbgPrint("Could not lock stg.hGlobal\r\n");
545 ReleaseStgMedium(&stg);
551 ////STDMETHODIMP GetClassID(CLSID *pclsid);
552 //STDMETHODIMP IsDirty();
553 //STDMETHODIMP Load(LPCOLESTR pszFileName, DWORD dwMode);
554 //STDMETHODIMP Save(LPCOLESTR pszFileName, BOOL fRemember);
555 //STDMETHODIMP SaveCompleted(LPCOLESTR pszFileName);
556 //STDMETHODIMP GetCurFile(LPOLESTR *ppszFileName);
558 //// *** IPersistFolder2 methods ***
559 STDMETHODIMP GetCurFolder(LPITEMIDLIST * pidl)
561 *pidl = ILClone(m_CurDir);
565 // *** IPersistFolder methods ***
566 STDMETHODIMP Initialize(LPCITEMIDLIST pidl)
568 WCHAR tmpPath[MAX_PATH];
570 if (SHGetPathFromIDListW(pidl, tmpPath))
573 m_CurDir.Attach(ILClone(pidl));
576 DbgPrint("%s() => Unable to parse pidl\n", __FUNCTION__);
580 // *** IPersist methods ***
581 STDMETHODIMP GetClassID(CLSID *lpClassId)
583 DbgPrint("%s\n", __FUNCTION__);
588 STDMETHODIMP Initialize(PCWSTR zipFile, PCSTR zipDir, PCUIDLIST_ABSOLUTE curDir, PCUIDLIST_RELATIVE pidl)
593 m_CurDir.Attach(ILCombine(curDir, pidl));
596 static DWORD WINAPI s_ExtractProc(LPVOID arg)
599 ZipFile.Attach((BSTR)arg);
601 _CZipExtract_runWizard(ZipFile);
603 InterlockedDecrement(&g_ModuleRefCnt);
608 DECLARE_NO_REGISTRY() // Handled manually because this object is exposed via multiple clsid's
609 DECLARE_NOT_AGGREGATABLE(CZipFolder)
611 DECLARE_PROTECT_FINAL_CONSTRUCT()
613 BEGIN_COM_MAP(CZipFolder)
614 COM_INTERFACE_ENTRY_IID(IID_IShellFolder2, IShellFolder2)
615 COM_INTERFACE_ENTRY_IID(IID_IShellFolder, IShellFolder)
616 // COM_INTERFACE_ENTRY_IID(IID_IStorage, IStorage)
617 COM_INTERFACE_ENTRY_IID(IID_IContextMenu, IContextMenu)
618 COM_INTERFACE_ENTRY_IID(IID_IShellExtInit, IShellExtInit)
619 //COM_INTERFACE_ENTRY_IID(IID_IPersistFile, IPersistFile)
620 COM_INTERFACE_ENTRY_IID(IID_IPersistFolder2, IPersistFolder2)
621 COM_INTERFACE_ENTRY_IID(IID_IPersistFolder, IPersistFolder)
622 COM_INTERFACE_ENTRY_IID(IID_IPersist, IPersist)