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());
166 SetWizardButtons(PSWIZB_NEXT
);
172 ::EnableWindow(GetDlgItem(IDC_BROWSE
), FALSE
);
173 ::EnableWindow(GetDlgItem(IDC_DIRECTORY
), FALSE
);
174 ::EnableWindow(GetDlgItem(IDC_PASSWORD
), FALSE
);
177 if (m_pExtract
->m_DirectoryChanged
)
180 if (!m_pExtract
->Extract(m_hWnd
, GetDlgItem(IDC_PROGRESS
)))
182 /* Extraction failed, do not go to the next page */
183 SetWindowLongPtr(DWLP_MSGRESULT
, -1);
185 ::EnableWindow(GetDlgItem(IDC_BROWSE
), TRUE
);
186 ::EnableWindow(GetDlgItem(IDC_DIRECTORY
), TRUE
);
187 ::EnableWindow(GetDlgItem(IDC_PASSWORD
), FALSE
); /* Not supported for now */
188 SetWizardButtons(PSWIZB_NEXT
);
201 static INT CALLBACK
s_BrowseCallbackProc(HWND hWnd
, UINT uMsg
, LPARAM lp
, LPARAM pData
)
203 if (uMsg
== BFFM_INITIALIZED
)
205 browse_info
* info
= (browse_info
*)pData
;
207 dlg
.SendMessage(BFFM_SETSELECTION
, TRUE
, (LPARAM
)info
->Directory
);
208 dlg
.CenterWindow(info
->hWnd
);
213 LRESULT
OnBrowse(WORD wNotifyCode
, WORD wID
, HWND hWndCtl
, BOOL
& bHandled
)
215 BROWSEINFOW bi
= { m_hWnd
};
216 WCHAR path
[MAX_PATH
];
217 bi
.pszDisplayName
= path
;
218 bi
.lpfn
= s_BrowseCallbackProc
;
219 bi
.ulFlags
= BIF_NEWDIALOGSTYLE
| BIF_RETURNFSANCESTORS
| BIF_RETURNONLYFSDIRS
;
220 CStringW
title(MAKEINTRESOURCEW(IDS_WIZ_BROWSE_TITLE
));
221 bi
.lpszTitle
= title
;
223 if (m_pExtract
->m_DirectoryChanged
)
226 browse_info info
= { m_hWnd
, m_pExtract
->m_Directory
.GetString() };
227 bi
.lParam
= (LPARAM
)&info
;
229 CComHeapPtr
<ITEMIDLIST
> pidl
;
230 pidl
.Attach(SHBrowseForFolderW(&bi
));
232 WCHAR tmpPath
[MAX_PATH
];
233 if (pidl
&& SHGetPathFromIDListW(pidl
, tmpPath
))
235 m_pExtract
->m_Directory
= tmpPath
;
236 SetDlgItemTextW(IDC_DIRECTORY
, m_pExtract
->m_Directory
);
237 m_pExtract
->m_DirectoryChanged
= false;
242 LRESULT
OnEnChangeDirectory(WORD wNotifyCode
, WORD wID
, HWND hWndCtl
, BOOL
& bHandled
)
244 m_pExtract
->m_DirectoryChanged
= true;
248 LRESULT
OnPassword(WORD wNotifyCode
, WORD wID
, HWND hWndCtl
, BOOL
& bHandled
)
253 void UpdateDirectory()
255 GetDlgItemText(IDC_DIRECTORY
, m_pExtract
->m_Directory
);
256 m_pExtract
->m_DirectoryChanged
= false;
260 enum { IDD
= IDD_PROPPAGEDESTINATION
};
262 BEGIN_MSG_MAP(CCompleteSettingsPage
)
263 COMMAND_ID_HANDLER(IDC_BROWSE
, OnBrowse
)
264 COMMAND_ID_HANDLER(IDC_PASSWORD
, OnPassword
)
265 COMMAND_HANDLER(IDC_DIRECTORY
, EN_CHANGE
, OnEnChangeDirectory
)
266 CHAIN_MSG_MAP(CPropertyPageImpl
<CExtractSettingsPage
>)
271 class CCompleteSettingsPage
: public CPropertyPageImpl
<CCompleteSettingsPage
>
274 CZipExtract
* m_pExtract
;
277 CCompleteSettingsPage(CZipExtract
* extract
)
278 :CPropertyPageImpl
<CCompleteSettingsPage
>(MAKEINTRESOURCE(IDS_WIZ_TITLE
))
279 , m_pExtract(extract
)
281 m_psp
.pszHeaderTitle
= MAKEINTRESOURCE(IDS_WIZ_COMPL_TITLE
);
282 m_psp
.pszHeaderSubTitle
= MAKEINTRESOURCE(IDS_WIZ_COMPL_SUBTITLE
);
283 m_psp
.dwFlags
|= PSP_USETITLE
| PSP_USEHEADERTITLE
| PSP_USEHEADERSUBTITLE
;
289 SetWizardButtons(PSWIZB_FINISH
);
290 CStringW Path
= m_pExtract
->m_Directory
;
291 PWSTR Ptr
= Path
.GetBuffer();
293 ::GetWindowRect(GetDlgItem(IDC_DESTDIR
), &rc
);
295 PathCompactPathW(dc
, Ptr
, rc
.right
- rc
.left
);
297 Path
.ReleaseBuffer();
298 SetDlgItemTextW(IDC_DESTDIR
, Path
);
299 CheckDlgButton(IDC_SHOW_EXTRACTED
, BST_CHECKED
);
302 BOOL
OnWizardFinish()
304 if (IsDlgButtonChecked(IDC_SHOW_EXTRACTED
) == BST_CHECKED
)
306 ShellExecuteW(NULL
, L
"explore", m_pExtract
->m_Directory
, NULL
, NULL
, SW_SHOW
);
312 enum { IDD
= IDD_PROPPAGECOMPLETE
};
314 BEGIN_MSG_MAP(CCompleteSettingsPage
)
315 CHAIN_MSG_MAP(CPropertyPageImpl
<CCompleteSettingsPage
>)
322 PROPSHEETHEADERW psh
= { sizeof(psh
), 0 };
323 psh
.dwFlags
= PSH_WIZARD97
| PSH_HEADER
;
324 psh
.hInstance
= _AtlBaseModule
.GetResourceInstance();
326 CExtractSettingsPage
extractPage(this);
327 CCompleteSettingsPage
completePage(this);
328 HPROPSHEETPAGE hpsp
[] =
330 extractPage
.Create(),
331 completePage
.Create()
335 psh
.nPages
= _countof(hpsp
);
337 PropertySheetW(&psh
);
340 bool Extract(HWND hDlg
, HWND hProgress
)
342 unz_global_info64 gi
;
343 uf
= unzOpen2_64(m_Filename
.GetString(), &g_FFunc
);
344 int err
= unzGetGlobalInfo64(uf
, &gi
);
347 DPRINT1("ERROR, unzGetGlobalInfo64: 0x%x\n", err
);
352 CZipEnumerator zipEnum
;
353 if (!zipEnum
.initialize(this))
355 DPRINT1("ERROR, zipEnum.initialize\n");
360 CWindow
Progress(hProgress
);
361 Progress
.SendMessage(PBM_SETRANGE32
, 0, gi
.number_entry
);
362 Progress
.SendMessage(PBM_SETPOS
, 0, 0);
365 CStringA BaseDirectory
= m_Directory
;
367 unz_file_info64 Info
;
369 bool bOverwriteAll
= false;
370 while (zipEnum
.next(Name
, Info
))
372 bool is_dir
= Name
.GetLength() > 0 && Name
[Name
.GetLength()-1] == '/';
374 char CombinedPath
[MAX_PATH
* 2] = { 0 };
375 PathCombineA(CombinedPath
, BaseDirectory
, Name
);
376 CStringA FullPath
= CombinedPath
;
377 FullPath
.Replace('/', '\\'); /* SHPathPrepareForWriteA does not handle '/' */
378 DWORD dwFlags
= SHPPFW_DIRCREATE
| (is_dir
? SHPPFW_NONE
: SHPPFW_IGNOREFILENAME
);
379 HRESULT hr
= SHPathPrepareForWriteA(hDlg
, NULL
, FullPath
, dwFlags
);
380 if (FAILED_UNEXPECTEDLY(hr
))
389 const char* password
= NULL
;
390 /* FIXME: Process password, if required and not specified, prompt the user */
391 err
= unzOpenCurrentFilePassword(uf
, password
);
394 DPRINT1("ERROR, unzOpenCurrentFilePassword: 0x%x\n", err
);
399 HANDLE hFile
= CreateFileA(FullPath
, GENERIC_WRITE
, 0, NULL
, CREATE_NEW
, FILE_ATTRIBUTE_NORMAL
, NULL
);
400 if (hFile
== INVALID_HANDLE_VALUE
)
402 DWORD dwErr
= GetLastError();
403 if (dwErr
== ERROR_FILE_EXISTS
)
405 bool bOverwrite
= bOverwriteAll
;
408 CConfirmReplace::DialogResult Result
= CConfirmReplace::ShowDlg(hDlg
, FullPath
);
411 case CConfirmReplace::YesToAll
:
412 bOverwriteAll
= true;
413 case CConfirmReplace::Yes
:
416 case CConfirmReplace::No
:
418 case CConfirmReplace::Cancel
:
419 unzCloseCurrentFile(uf
);
427 hFile
= CreateFileA(FullPath
, GENERIC_WRITE
, 0, NULL
, CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
428 if (hFile
== INVALID_HANDLE_VALUE
)
430 dwErr
= GetLastError();
435 unzCloseCurrentFile(uf
);
439 if (hFile
== INVALID_HANDLE_VALUE
)
441 unzCloseCurrentFile(uf
);
442 DPRINT1("ERROR, CreateFileA: 0x%x (%s)\n", dwErr
, bOverwriteAll
? "Y" : "N");
450 err
= unzReadCurrentFile(uf
, Buffer
, sizeof(Buffer
));
454 DPRINT1("ERROR, unzReadCurrentFile: 0x%x\n", err
);
460 if (!WriteFile(hFile
, Buffer
, err
, &dwWritten
, NULL
))
462 DPRINT1("ERROR, WriteFile: 0x%x\n", GetLastError());
465 if (dwWritten
!= (DWORD
)err
)
467 DPRINT1("ERROR, WriteFile: dwWritten:%d err:%d\n", dwWritten
, err
);
474 /* Update Filetime */
475 FILETIME LastAccessTime
;
476 GetFileTime(hFile
, NULL
, &LastAccessTime
, NULL
);
477 FILETIME LocalFileTime
;
478 DosDateTimeToFileTime((WORD
)(Info
.dosDate
>> 16), (WORD
)Info
.dosDate
, &LocalFileTime
);
480 LocalFileTimeToFileTime(&LocalFileTime
, &FileTime
);
481 SetFileTime(hFile
, &FileTime
, &LastAccessTime
, &FileTime
);
488 unzCloseCurrentFile(uf
);
489 DPRINT1("ERROR, unzReadCurrentFile2: 0x%x\n", err
);
495 err
= unzCloseCurrentFile(uf
);
498 DPRINT1("ERROR(non-fatal), unzCloseCurrentFile: 0x%x\n", err
);
501 Progress
.SendMessage(PBM_SETPOS
, CurrentFile
, 0);
510 void _CZipExtract_runWizard(PCWSTR Filename
)
512 CZipExtract
extractor(Filename
);
513 extractor
.runWizard();