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)
15 bool m_DirectoryChanged
;
18 CZipExtract(PCWSTR Filename
)
19 :m_DirectoryChanged(false)
22 m_Filename
= Filename
;
23 m_Directory
= m_Filename
;
24 PWSTR Dir
= m_Directory
.GetBuffer();
25 PathRemoveExtensionW(Dir
);
26 m_Directory
.ReleaseBuffer();
33 DPRINT1("WARNING: uf not closed!\n");
45 // *** IZip methods ***
46 STDMETHODIMP
QueryInterface(REFIID riid
, void **ppvObject
)
48 if (riid
== IID_IUnknown
)
56 STDMETHODIMP_(ULONG
) AddRef(void)
60 STDMETHODIMP_(ULONG
) Release(void)
64 STDMETHODIMP_(unzFile
) getZip()
69 class CConfirmReplace
: public CDialogImpl
<CConfirmReplace
>
82 static DialogResult
ShowDlg(HWND hDlg
, PCSTR FullPath
)
84 PCSTR Filename
= PathFindFileNameA(FullPath
);
85 CConfirmReplace
confirm(Filename
);
86 INT_PTR Result
= confirm
.DoModal(hDlg
);
89 case IDYES
: return Yes
;
90 case IDYESALL
: return YesToAll
;
93 case IDCANCEL
: return Cancel
;
97 CConfirmReplace(const char* filename
)
99 m_Filename
= filename
;
102 LRESULT
OnInitDialog(UINT nMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
104 CenterWindow(GetParent());
106 HICON hIcon
= LoadIcon(NULL
, IDI_EXCLAMATION
);
107 SendDlgItemMessage(IDC_EXCLAMATION_ICON
, STM_SETICON
, (WPARAM
)hIcon
);
109 /* Our CString does not support FormatMessage yet */
110 CStringA
message(MAKEINTRESOURCE(IDS_OVERWRITEFILE_TEXT
));
111 CHeapPtr
<CHAR
, CLocalAllocator
> formatted
;
115 (DWORD_PTR
)m_Filename
.GetString(),
119 ::FormatMessageA(FORMAT_MESSAGE_FROM_STRING
| FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_ARGUMENT_ARRAY
,
120 message
, 0, 0, (LPSTR
)&formatted
, 0, (va_list*)args
);
122 ::SetDlgItemTextA(m_hWnd
, IDC_MESSAGE
, formatted
);
126 LRESULT
OnButton(WORD wNotifyCode
, WORD wID
, HWND hWndCtl
, BOOL
& bHandled
)
133 enum { IDD
= IDD_CONFIRM_FILE_REPLACE
};
135 BEGIN_MSG_MAP(CConfirmReplace
)
136 MESSAGE_HANDLER(WM_INITDIALOG
, OnInitDialog
)
137 COMMAND_ID_HANDLER(IDYES
, OnButton
)
138 COMMAND_ID_HANDLER(IDYESALL
, OnButton
)
139 COMMAND_ID_HANDLER(IDNO
, OnButton
)
140 COMMAND_ID_HANDLER(IDCANCEL
, OnButton
)
145 class CExtractSettingsPage
: public CPropertyPageImpl
<CExtractSettingsPage
>
148 CZipExtract
* m_pExtract
;
151 CExtractSettingsPage(CZipExtract
* extract
)
152 :CPropertyPageImpl
<CExtractSettingsPage
>(MAKEINTRESOURCE(IDS_WIZ_TITLE
))
155 m_psp
.pszHeaderTitle
= MAKEINTRESOURCE(IDS_WIZ_DEST_TITLE
);
156 m_psp
.pszHeaderSubTitle
= MAKEINTRESOURCE(IDS_WIZ_DEST_SUBTITLE
);
157 m_psp
.dwFlags
|= PSP_USETITLE
| PSP_USEHEADERTITLE
| PSP_USEHEADERSUBTITLE
;
162 SetDlgItemTextW(IDC_DIRECTORY
, m_pExtract
->m_Directory
);
163 m_pExtract
->m_DirectoryChanged
= false;
164 ::EnableWindow(GetDlgItem(IDC_PASSWORD
), FALSE
); /* Not supported for now */
165 GetParent().CenterWindow(::GetDesktopWindow());
171 ::EnableWindow(GetDlgItem(IDC_BROWSE
), FALSE
);
172 ::EnableWindow(GetDlgItem(IDC_DIRECTORY
), FALSE
);
173 ::EnableWindow(GetDlgItem(IDC_PASSWORD
), FALSE
);
175 if (m_pExtract
->m_DirectoryChanged
)
178 if (!m_pExtract
->Extract(m_hWnd
, GetDlgItem(IDC_PROGRESS
)))
180 /* Extraction failed, do not go to the next page */
181 SetWindowLongPtr(DWLP_MSGRESULT
, -1);
183 ::EnableWindow(GetDlgItem(IDC_BROWSE
), TRUE
);
184 ::EnableWindow(GetDlgItem(IDC_DIRECTORY
), TRUE
);
185 ::EnableWindow(GetDlgItem(IDC_PASSWORD
), FALSE
); /* Not supported for now */
198 static INT CALLBACK
s_BrowseCallbackProc(HWND hWnd
, UINT uMsg
, LPARAM lp
, LPARAM pData
)
200 if (uMsg
== BFFM_INITIALIZED
)
202 browse_info
* info
= (browse_info
*)pData
;
204 dlg
.SendMessage(BFFM_SETSELECTION
, TRUE
, (LPARAM
)info
->Directory
);
205 dlg
.CenterWindow(info
->hWnd
);
210 LRESULT
OnBrowse(WORD wNotifyCode
, WORD wID
, HWND hWndCtl
, BOOL
& bHandled
)
212 BROWSEINFOW bi
= { m_hWnd
};
213 WCHAR path
[MAX_PATH
];
214 bi
.pszDisplayName
= path
;
215 bi
.lpfn
= s_BrowseCallbackProc
;
216 bi
.ulFlags
= BIF_NEWDIALOGSTYLE
| BIF_RETURNFSANCESTORS
| BIF_RETURNONLYFSDIRS
;
217 CStringW
title(MAKEINTRESOURCEW(IDS_WIZ_BROWSE_TITLE
));
218 bi
.lpszTitle
= title
;
220 if (m_pExtract
->m_DirectoryChanged
)
223 browse_info info
= { m_hWnd
, m_pExtract
->m_Directory
.GetString() };
224 bi
.lParam
= (LPARAM
)&info
;
226 CComHeapPtr
<ITEMIDLIST
> pidl
;
227 pidl
.Attach(SHBrowseForFolderW(&bi
));
229 WCHAR tmpPath
[MAX_PATH
];
230 if (pidl
&& SHGetPathFromIDListW(pidl
, tmpPath
))
232 m_pExtract
->m_Directory
= tmpPath
;
233 SetDlgItemTextW(IDC_DIRECTORY
, m_pExtract
->m_Directory
);
234 m_pExtract
->m_DirectoryChanged
= false;
239 LRESULT
OnEnChangeDirectory(WORD wNotifyCode
, WORD wID
, HWND hWndCtl
, BOOL
& bHandled
)
241 m_pExtract
->m_DirectoryChanged
= true;
245 LRESULT
OnPassword(WORD wNotifyCode
, WORD wID
, HWND hWndCtl
, BOOL
& bHandled
)
250 void UpdateDirectory()
252 GetDlgItemText(IDC_DIRECTORY
, m_pExtract
->m_Directory
);
253 m_pExtract
->m_DirectoryChanged
= false;
257 enum { IDD
= IDD_PROPPAGEDESTINATION
};
259 BEGIN_MSG_MAP(CCompleteSettingsPage
)
260 COMMAND_ID_HANDLER(IDC_BROWSE
, OnBrowse
)
261 COMMAND_ID_HANDLER(IDC_PASSWORD
, OnPassword
)
262 COMMAND_HANDLER(IDC_DIRECTORY
, EN_CHANGE
, OnEnChangeDirectory
)
263 CHAIN_MSG_MAP(CPropertyPageImpl
<CExtractSettingsPage
>)
268 class CCompleteSettingsPage
: public CPropertyPageImpl
<CCompleteSettingsPage
>
271 CZipExtract
* m_pExtract
;
274 CCompleteSettingsPage(CZipExtract
* extract
)
275 :CPropertyPageImpl
<CCompleteSettingsPage
>(MAKEINTRESOURCE(IDS_WIZ_TITLE
))
276 , m_pExtract(extract
)
278 m_psp
.pszHeaderTitle
= MAKEINTRESOURCE(IDS_WIZ_COMPL_TITLE
);
279 m_psp
.pszHeaderSubTitle
= MAKEINTRESOURCE(IDS_WIZ_COMPL_SUBTITLE
);
280 m_psp
.dwFlags
|= PSP_USETITLE
| PSP_USEHEADERTITLE
| PSP_USEHEADERSUBTITLE
;
286 SetWizardButtons(PSWIZB_FINISH
);
287 CStringW Path
= m_pExtract
->m_Directory
;
288 PWSTR Ptr
= Path
.GetBuffer();
290 ::GetWindowRect(GetDlgItem(IDC_DESTDIR
), &rc
);
292 PathCompactPathW(dc
, Ptr
, rc
.right
- rc
.left
);
294 Path
.ReleaseBuffer();
295 SetDlgItemTextW(IDC_DESTDIR
, Path
);
296 CheckDlgButton(IDC_SHOW_EXTRACTED
, BST_CHECKED
);
299 BOOL
OnWizardFinish()
301 if (IsDlgButtonChecked(IDC_SHOW_EXTRACTED
) == BST_CHECKED
)
303 ShellExecuteW(NULL
, L
"explore", m_pExtract
->m_Directory
, NULL
, NULL
, SW_SHOW
);
309 enum { IDD
= IDD_PROPPAGECOMPLETE
};
311 BEGIN_MSG_MAP(CCompleteSettingsPage
)
312 CHAIN_MSG_MAP(CPropertyPageImpl
<CCompleteSettingsPage
>)
319 PROPSHEETHEADERW psh
= { sizeof(psh
), 0 };
320 psh
.dwFlags
= PSH_WIZARD97
| PSH_HEADER
;
321 psh
.hInstance
= _AtlBaseModule
.GetResourceInstance();
323 CExtractSettingsPage
extractPage(this);
324 CCompleteSettingsPage
completePage(this);
325 HPROPSHEETPAGE hpsp
[] =
327 extractPage
.Create(),
328 completePage
.Create()
332 psh
.nPages
= _countof(hpsp
);
334 PropertySheetW(&psh
);
337 bool Extract(HWND hDlg
, HWND hProgress
)
339 unz_global_info64 gi
;
340 uf
= unzOpen2_64(m_Filename
.GetString(), &g_FFunc
);
341 int err
= unzGetGlobalInfo64(uf
, &gi
);
344 DPRINT1("ERROR, unzGetGlobalInfo64: 0x%x\n", err
);
349 CZipEnumerator zipEnum
;
350 if (!zipEnum
.initialize(this))
352 DPRINT1("ERROR, zipEnum.initialize\n");
357 CWindow
Progress(hProgress
);
358 Progress
.SendMessage(PBM_SETRANGE32
, 0, gi
.number_entry
);
359 Progress
.SendMessage(PBM_SETPOS
, 0, 0);
362 CStringA BaseDirectory
= m_Directory
;
364 unz_file_info64 Info
;
366 bool bOverwriteAll
= false;
367 while (zipEnum
.next(Name
, Info
))
369 bool is_dir
= Name
.GetLength() > 0 && Name
[Name
.GetLength()-1] == '/';
371 char CombinedPath
[MAX_PATH
* 2] = { 0 };
372 PathCombineA(CombinedPath
, BaseDirectory
, Name
);
373 CStringA FullPath
= CombinedPath
;
374 FullPath
.Replace('/', '\\'); /* SHPathPrepareForWriteA does not handle '/' */
375 DWORD dwFlags
= SHPPFW_DIRCREATE
| (is_dir
? SHPPFW_NONE
: SHPPFW_IGNOREFILENAME
);
376 HRESULT hr
= SHPathPrepareForWriteA(hDlg
, NULL
, FullPath
, dwFlags
);
377 if (FAILED_UNEXPECTEDLY(hr
))
386 const char* password
= NULL
;
387 /* FIXME: Process password, if required and not specified, prompt the user */
388 err
= unzOpenCurrentFilePassword(uf
, password
);
391 DPRINT1("ERROR, unzOpenCurrentFilePassword: 0x%x\n", err
);
396 HANDLE hFile
= CreateFileA(FullPath
, GENERIC_WRITE
, 0, NULL
, CREATE_NEW
, FILE_ATTRIBUTE_NORMAL
, NULL
);
397 if (hFile
== INVALID_HANDLE_VALUE
)
399 DWORD dwErr
= GetLastError();
400 if (dwErr
== ERROR_FILE_EXISTS
)
402 bool bOverwrite
= bOverwriteAll
;
405 CConfirmReplace::DialogResult Result
= CConfirmReplace::ShowDlg(hDlg
, FullPath
);
408 case CConfirmReplace::YesToAll
:
409 bOverwriteAll
= true;
410 case CConfirmReplace::Yes
:
413 case CConfirmReplace::No
:
415 case CConfirmReplace::Cancel
:
416 unzCloseCurrentFile(uf
);
424 hFile
= CreateFileA(FullPath
, GENERIC_WRITE
, 0, NULL
, CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
425 if (hFile
== INVALID_HANDLE_VALUE
)
427 dwErr
= GetLastError();
432 unzCloseCurrentFile(uf
);
436 if (hFile
== INVALID_HANDLE_VALUE
)
438 unzCloseCurrentFile(uf
);
439 DPRINT1("ERROR, CreateFileA: 0x%x (%s)\n", dwErr
, bOverwriteAll
? "Y" : "N");
447 err
= unzReadCurrentFile(uf
, Buffer
, sizeof(Buffer
));
451 DPRINT1("ERROR, unzReadCurrentFile: 0x%x\n", err
);
457 if (!WriteFile(hFile
, Buffer
, err
, &dwWritten
, NULL
))
459 DPRINT1("ERROR, WriteFile: 0x%x\n", GetLastError());
462 if (dwWritten
!= (DWORD
)err
)
464 DPRINT1("ERROR, WriteFile: dwWritten:%d err:%d\n", dwWritten
, err
);
471 /* Update Filetime */
472 FILETIME LastAccessTime
;
473 GetFileTime(hFile
, NULL
, &LastAccessTime
, NULL
);
474 FILETIME LocalFileTime
;
475 DosDateTimeToFileTime((WORD
)(Info
.dosDate
>> 16), (WORD
)Info
.dosDate
, &LocalFileTime
);
477 LocalFileTimeToFileTime(&LocalFileTime
, &FileTime
);
478 SetFileTime(hFile
, &FileTime
, &LastAccessTime
, &FileTime
);
485 unzCloseCurrentFile(uf
);
486 DPRINT1("ERROR, unzReadCurrentFile2: 0x%x\n", err
);
492 err
= unzCloseCurrentFile(uf
);
495 DPRINT1("ERROR(non-fatal), unzCloseCurrentFile: 0x%x\n", err
);
498 Progress
.SendMessage(PBM_SETPOS
, CurrentFile
, 0);
507 void _CZipExtract_runWizard(PCWSTR Filename
)
509 CZipExtract
extractor(Filename
);
510 extractor
.runWizard();