2 * PROJECT: ReactOS Zip Shell Extension
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Zip extraction
5 * COPYRIGHT: Copyright 2017 Mark Jansen (mark.jansen@reactos.org)
17 CZipExtract(PCWSTR Filename
)
20 m_Filename
= Filename
;
21 m_Directory
= m_Filename
;
22 PWSTR Dir
= m_Directory
.GetBuffer();
23 PathRemoveExtensionW(Dir
);
24 m_Directory
.ReleaseBuffer();
31 DPRINT1("WARNING: uf not closed!\n");
43 // *** IZip methods ***
44 STDMETHODIMP
QueryInterface(REFIID riid
, void **ppvObject
)
46 if (riid
== IID_IUnknown
)
54 STDMETHODIMP_(ULONG
) AddRef(void)
58 STDMETHODIMP_(ULONG
) Release(void)
62 STDMETHODIMP_(unzFile
) getZip()
67 class CConfirmReplace
: public CDialogImpl
<CConfirmReplace
>
80 static DialogResult
ShowDlg(HWND hDlg
, PCSTR FullPath
)
82 PCSTR Filename
= PathFindFileNameA(FullPath
);
83 CConfirmReplace
confirm(Filename
);
84 INT_PTR Result
= confirm
.DoModal(hDlg
);
87 case IDYES
: return Yes
;
88 case IDYESALL
: return YesToAll
;
91 case IDCANCEL
: return Cancel
;
95 CConfirmReplace(const char* filename
)
97 m_Filename
= filename
;
100 LRESULT
OnInitDialog(UINT nMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
102 CenterWindow(GetParent());
104 HICON hIcon
= LoadIcon(NULL
, IDI_EXCLAMATION
);
105 SendDlgItemMessage(IDC_EXCLAMATION_ICON
, STM_SETICON
, (WPARAM
)hIcon
);
107 /* Our CString does not support FormatMessage yet */
108 CStringA
message(MAKEINTRESOURCE(IDS_OVERWRITEFILE_TEXT
));
109 CHeapPtr
<CHAR
, CLocalAllocator
> formatted
;
113 (DWORD_PTR
)m_Filename
.GetString(),
117 ::FormatMessageA(FORMAT_MESSAGE_FROM_STRING
| FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_ARGUMENT_ARRAY
,
118 message
, 0, 0, (LPSTR
)&formatted
, 0, (va_list*)args
);
120 ::SetDlgItemTextA(m_hWnd
, IDC_MESSAGE
, formatted
);
124 LRESULT
OnButton(WORD wNotifyCode
, WORD wID
, HWND hWndCtl
, BOOL
& bHandled
)
131 enum { IDD
= IDD_CONFIRM_FILE_REPLACE
};
133 BEGIN_MSG_MAP(CConfirmReplace
)
134 MESSAGE_HANDLER(WM_INITDIALOG
, OnInitDialog
)
135 COMMAND_ID_HANDLER(IDYES
, OnButton
)
136 COMMAND_ID_HANDLER(IDYESALL
, OnButton
)
137 COMMAND_ID_HANDLER(IDNO
, OnButton
)
138 COMMAND_ID_HANDLER(IDCANCEL
, OnButton
)
143 class CExtractSettingsPage
: public CPropertyPageImpl
<CExtractSettingsPage
>
146 CZipExtract
* m_pExtract
;
149 CExtractSettingsPage(CZipExtract
* extract
)
150 :CPropertyPageImpl
<CExtractSettingsPage
>(MAKEINTRESOURCE(IDS_WIZ_TITLE
))
153 m_psp
.pszHeaderTitle
= MAKEINTRESOURCE(IDS_WIZ_DEST_TITLE
);
154 m_psp
.pszHeaderSubTitle
= MAKEINTRESOURCE(IDS_WIZ_DEST_SUBTITLE
);
155 m_psp
.dwFlags
|= PSP_USETITLE
| PSP_USEHEADERTITLE
| PSP_USEHEADERSUBTITLE
;
160 SetDlgItemTextW(IDC_DIRECTORY
, m_pExtract
->m_Directory
);
161 ::EnableWindow(GetDlgItem(IDC_PASSWORD
), FALSE
); /* Not supported for now */
162 GetParent().CenterWindow(::GetDesktopWindow());
168 ::EnableWindow(GetDlgItem(IDC_BROWSE
), FALSE
);
169 ::EnableWindow(GetDlgItem(IDC_DIRECTORY
), FALSE
);
170 ::EnableWindow(GetDlgItem(IDC_PASSWORD
), FALSE
);
172 if (!m_pExtract
->Extract(m_hWnd
, GetDlgItem(IDC_PROGRESS
)))
174 /* Extraction failed, do not go to the next page */
175 SetWindowLongPtr(DWLP_MSGRESULT
, -1);
177 ::EnableWindow(GetDlgItem(IDC_BROWSE
), TRUE
);
178 ::EnableWindow(GetDlgItem(IDC_DIRECTORY
), TRUE
);
179 ::EnableWindow(GetDlgItem(IDC_PASSWORD
), FALSE
); /* Not supported for now */
192 static INT CALLBACK
s_BrowseCallbackProc(HWND hWnd
, UINT uMsg
, LPARAM lp
, LPARAM pData
)
194 if (uMsg
== BFFM_INITIALIZED
)
196 browse_info
* info
= (browse_info
*)pData
;
198 dlg
.SendMessage(BFFM_SETSELECTION
, TRUE
, (LPARAM
)info
->Directory
);
199 dlg
.CenterWindow(info
->hWnd
);
204 LRESULT
OnBrowse(WORD wNotifyCode
, WORD wID
, HWND hWndCtl
, BOOL
& bHandled
)
206 BROWSEINFOW bi
= { m_hWnd
};
207 WCHAR path
[MAX_PATH
];
208 bi
.pszDisplayName
= path
;
209 bi
.lpfn
= s_BrowseCallbackProc
;
210 bi
.ulFlags
= BIF_NEWDIALOGSTYLE
| BIF_RETURNFSANCESTORS
| BIF_RETURNONLYFSDIRS
;
211 CStringW
title(MAKEINTRESOURCEW(IDS_WIZ_BROWSE_TITLE
));
212 bi
.lpszTitle
= title
;
214 browse_info info
= { m_hWnd
, m_pExtract
->m_Directory
.GetString() };
215 bi
.lParam
= (LPARAM
)&info
;
217 CComHeapPtr
<ITEMIDLIST
> pidl
;
218 pidl
.Attach(SHBrowseForFolderW(&bi
));
220 WCHAR tmpPath
[MAX_PATH
];
221 if (pidl
&& SHGetPathFromIDListW(pidl
, tmpPath
))
223 m_pExtract
->m_Directory
= tmpPath
;
224 SetDlgItemTextW(IDC_DIRECTORY
, m_pExtract
->m_Directory
);
229 LRESULT
OnPassword(WORD wNotifyCode
, WORD wID
, HWND hWndCtl
, BOOL
& bHandled
)
235 enum { IDD
= IDD_PROPPAGEDESTIONATION
};
237 BEGIN_MSG_MAP(CCompleteSettingsPage
)
238 COMMAND_ID_HANDLER(IDC_BROWSE
, OnBrowse
)
239 COMMAND_ID_HANDLER(IDC_PASSWORD
, OnPassword
)
240 CHAIN_MSG_MAP(CPropertyPageImpl
<CExtractSettingsPage
>)
245 class CCompleteSettingsPage
: public CPropertyPageImpl
<CCompleteSettingsPage
>
248 CZipExtract
* m_pExtract
;
251 CCompleteSettingsPage(CZipExtract
* extract
)
252 :CPropertyPageImpl
<CCompleteSettingsPage
>(MAKEINTRESOURCE(IDS_WIZ_TITLE
))
253 , m_pExtract(extract
)
255 m_psp
.pszHeaderTitle
= MAKEINTRESOURCE(IDS_WIZ_COMPL_TITLE
);
256 m_psp
.pszHeaderSubTitle
= MAKEINTRESOURCE(IDS_WIZ_COMPL_SUBTITLE
);
257 m_psp
.dwFlags
|= PSP_USETITLE
| PSP_USEHEADERTITLE
| PSP_USEHEADERSUBTITLE
;
263 SetWizardButtons(PSWIZB_FINISH
);
264 CStringW Path
= m_pExtract
->m_Directory
;
265 PWSTR Ptr
= Path
.GetBuffer();
267 ::GetWindowRect(GetDlgItem(IDC_DESTDIR
), &rc
);
269 PathCompactPathW(dc
, Ptr
, rc
.right
- rc
.left
);
271 Path
.ReleaseBuffer();
272 SetDlgItemTextW(IDC_DESTDIR
, Path
);
273 CheckDlgButton(IDC_SHOW_EXTRACTED
, BST_CHECKED
);
276 BOOL
OnWizardFinish()
278 if (IsDlgButtonChecked(IDC_SHOW_EXTRACTED
) == BST_CHECKED
)
280 ShellExecuteW(NULL
, L
"explore", m_pExtract
->m_Directory
, NULL
, NULL
, SW_SHOW
);
286 enum { IDD
= IDD_PROPPAGECOMPLETE
};
288 BEGIN_MSG_MAP(CCompleteSettingsPage
)
289 CHAIN_MSG_MAP(CPropertyPageImpl
<CCompleteSettingsPage
>)
296 PROPSHEETHEADERW psh
= { sizeof(psh
), 0 };
297 psh
.dwFlags
= PSH_WIZARD97
| PSH_HEADER
;
298 psh
.hInstance
= _AtlBaseModule
.GetResourceInstance();
300 CExtractSettingsPage
extractPage(this);
301 CCompleteSettingsPage
completePage(this);
302 HPROPSHEETPAGE hpsp
[] =
304 extractPage
.Create(),
305 completePage
.Create()
309 psh
.nPages
= _countof(hpsp
);
311 PropertySheetW(&psh
);
314 bool Extract(HWND hDlg
, HWND hProgress
)
316 unz_global_info64 gi
;
317 uf
= unzOpen2_64(m_Filename
.GetString(), &g_FFunc
);
318 int err
= unzGetGlobalInfo64(uf
, &gi
);
321 DPRINT1("ERROR, unzGetGlobalInfo64: 0x%x\n", err
);
325 CZipEnumerator zipEnum
;
326 if (!zipEnum
.initialize(this))
328 DPRINT1("ERROR, zipEnum.initialize\n");
332 CWindow
Progress(hProgress
);
333 Progress
.SendMessage(PBM_SETRANGE32
, 0, gi
.number_entry
);
334 Progress
.SendMessage(PBM_SETPOS
, 0, 0);
337 CStringA BaseDirectory
= m_Directory
;
339 unz_file_info64 Info
;
341 bool bOverwriteAll
= false;
342 while (zipEnum
.next(Name
, Info
))
344 bool is_dir
= Name
.GetLength() > 0 && Name
[Name
.GetLength()-1] == '/';
346 char CombinedPath
[MAX_PATH
* 2] = { 0 };
347 PathCombineA(CombinedPath
, BaseDirectory
, Name
);
348 CStringA FullPath
= CombinedPath
;
349 FullPath
.Replace('/', '\\'); /* SHPathPrepareForWriteA does not handle '/' */
350 DWORD dwFlags
= SHPPFW_DIRCREATE
| (is_dir
? SHPPFW_NONE
: SHPPFW_IGNOREFILENAME
);
351 HRESULT hr
= SHPathPrepareForWriteA(hDlg
, NULL
, FullPath
, dwFlags
);
352 if (FAILED_UNEXPECTEDLY(hr
))
360 const char* password
= NULL
;
361 /* FIXME: Process password, if required and not specified, prompt the user */
362 err
= unzOpenCurrentFilePassword(uf
, password
);
365 DPRINT1("ERROR, unzOpenCurrentFilePassword: 0x%x\n", err
);
369 HANDLE hFile
= CreateFileA(FullPath
, GENERIC_WRITE
, 0, NULL
, CREATE_NEW
, FILE_ATTRIBUTE_NORMAL
, NULL
);
370 if (hFile
== INVALID_HANDLE_VALUE
)
372 DWORD dwErr
= GetLastError();
373 if (dwErr
== ERROR_FILE_EXISTS
)
375 bool bOverwrite
= bOverwriteAll
;
378 CConfirmReplace::DialogResult Result
= CConfirmReplace::ShowDlg(hDlg
, FullPath
);
381 case CConfirmReplace::YesToAll
:
382 bOverwriteAll
= true;
383 case CConfirmReplace::Yes
:
386 case CConfirmReplace::No
:
388 case CConfirmReplace::Cancel
:
395 hFile
= CreateFileA(FullPath
, GENERIC_WRITE
, 0, NULL
, CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
396 if (hFile
== INVALID_HANDLE_VALUE
)
398 dwErr
= GetLastError();
403 unzCloseCurrentFile(uf
);
407 if (hFile
== INVALID_HANDLE_VALUE
)
409 unzCloseCurrentFile(uf
);
410 DPRINT1("ERROR, CreateFileA: 0x%x (%s)\n", dwErr
, bOverwriteAll
? "Y" : "N");
417 err
= unzReadCurrentFile(uf
, Buffer
, sizeof(Buffer
));
421 DPRINT1("ERROR, unzReadCurrentFile: 0x%x\n", err
);
427 if (!WriteFile(hFile
, Buffer
, err
, &dwWritten
, NULL
))
429 DPRINT1("ERROR, WriteFile: 0x%x\n", GetLastError());
432 if (dwWritten
!= (DWORD
)err
)
434 DPRINT1("ERROR, WriteFile: dwWritten:%d err:%d\n", dwWritten
, err
);
441 /* Update Filetime */
442 FILETIME LastAccessTime
;
443 GetFileTime(hFile
, NULL
, &LastAccessTime
, NULL
);
444 FILETIME LocalFileTime
;
445 DosDateTimeToFileTime((WORD
)(Info
.dosDate
>> 16), (WORD
)Info
.dosDate
, &LocalFileTime
);
447 LocalFileTimeToFileTime(&LocalFileTime
, &FileTime
);
448 SetFileTime(hFile
, &FileTime
, &LastAccessTime
, &FileTime
);
455 unzCloseCurrentFile(uf
);
456 DPRINT1("ERROR, unzReadCurrentFile2: 0x%x\n", err
);
461 err
= unzCloseCurrentFile(uf
);
464 DPRINT1("ERROR(non-fatal), unzCloseCurrentFile: 0x%x\n", err
);
467 Progress
.SendMessage(PBM_SETPOS
, CurrentFile
, 0);
476 void _CZipExtract_runWizard(PCWSTR Filename
)
478 CZipExtract
extractor(Filename
);
479 extractor
.runWizard();