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-2019 Mark Jansen (mark.jansen@reactos.org)
16 bool m_DirectoryChanged
;
19 CZipExtract(PCWSTR Filename
)
20 :m_DirectoryChanged(false)
23 m_Filename
= Filename
;
24 m_Directory
= m_Filename
;
25 PWSTR Dir
= m_Directory
.GetBuffer();
26 PathRemoveExtensionW(Dir
);
27 m_Directory
.ReleaseBuffer();
34 DPRINT1("WARNING: uf not closed!\n");
46 // *** IZip methods ***
47 STDMETHODIMP
QueryInterface(REFIID riid
, void **ppvObject
)
49 if (riid
== IID_IUnknown
)
57 STDMETHODIMP_(ULONG
) AddRef(void)
61 STDMETHODIMP_(ULONG
) Release(void)
65 STDMETHODIMP_(unzFile
) getZip()
70 class CExtractSettingsPage
: public CPropertyPageImpl
<CExtractSettingsPage
>
73 HANDLE m_hExtractionThread
;
74 bool m_bExtractionThreadCancel
;
76 CZipExtract
* m_pExtract
;
77 CStringA
* m_pPassword
;
80 CExtractSettingsPage(CZipExtract
* extract
, CStringA
* password
)
81 :CPropertyPageImpl
<CExtractSettingsPage
>(MAKEINTRESOURCE(IDS_WIZ_TITLE
))
82 ,m_hExtractionThread(NULL
)
83 ,m_bExtractionThreadCancel(false)
85 ,m_pPassword(password
)
87 m_psp
.pszHeaderTitle
= MAKEINTRESOURCE(IDS_WIZ_DEST_TITLE
);
88 m_psp
.pszHeaderSubTitle
= MAKEINTRESOURCE(IDS_WIZ_DEST_SUBTITLE
);
89 m_psp
.dwFlags
|= PSP_USETITLE
| PSP_USEHEADERTITLE
| PSP_USEHEADERSUBTITLE
;
94 SetDlgItemTextW(IDC_DIRECTORY
, m_pExtract
->m_Directory
);
95 m_pExtract
->m_DirectoryChanged
= false;
96 GetParent().CenterWindow(::GetDesktopWindow());
97 SetWizardButtons(PSWIZB_NEXT
);
103 if (m_hExtractionThread
!= NULL
)
105 /* We enter here when extraction has finished, and go to next page if it succeeded */
106 WaitForSingleObject(m_hExtractionThread
, INFINITE
);
107 CloseHandle(m_hExtractionThread
);
108 m_hExtractionThread
= NULL
;
109 m_pExtract
->Release();
110 if (!m_bExtractionThreadCancel
)
116 SetWindowLongPtr(DWLP_MSGRESULT
, -1);
121 /* We end up here if the user manually clicks Next: start extraction */
122 m_bExtractionThreadCancel
= false;
124 /* Grey out every control during extraction to prevent user interaction */
125 ::EnableWindow(GetDlgItem(IDC_BROWSE
), FALSE
);
126 ::EnableWindow(GetDlgItem(IDC_DIRECTORY
), FALSE
);
127 ::EnableWindow(GetDlgItem(IDC_PASSWORD
), FALSE
);
130 CStringW
strExtracting(MAKEINTRESOURCEW(IDS_EXTRACTING
));
131 SetDlgItemTextW(IDC_STATUSTEXT
, strExtracting
);
133 if (m_pExtract
->m_DirectoryChanged
)
136 m_pExtract
->AddRef();
138 m_hExtractionThread
= CreateThread(NULL
, 0,
139 &CExtractSettingsPage::ExtractEntry
,
142 if (!m_hExtractionThread
)
144 /* Extraction thread creation failed, do not go to the next page */
145 DWORD err
= GetLastError();
146 DPRINT1("ERROR, m_hExtractionThread: CreateThread failed: 0x%x\n", err
);
147 m_pExtract
->Release();
149 SetWindowLongPtr(DWLP_MSGRESULT
, -1);
151 ::EnableWindow(GetDlgItem(IDC_BROWSE
), TRUE
);
152 ::EnableWindow(GetDlgItem(IDC_DIRECTORY
), TRUE
);
153 ::EnableWindow(GetDlgItem(IDC_PASSWORD
), TRUE
);
154 SetWizardButtons(PSWIZB_NEXT
);
159 static DWORD WINAPI
ExtractEntry(LPVOID lpParam
)
161 CExtractSettingsPage
* pPage
= (CExtractSettingsPage
*)lpParam
;
162 bool res
= pPage
->m_pExtract
->Extract(pPage
->m_hWnd
, pPage
->GetDlgItem(IDC_PROGRESS
), &(pPage
->m_bExtractionThreadCancel
));
163 /* Failing and cancelling extraction both mean we stay on the same property page */
164 pPage
->m_bExtractionThreadCancel
= !res
;
166 pPage
->SetWizardButtons(PSWIZB_NEXT
);
169 /* Extraction failed/cancelled: the page becomes interactive again */
170 ::EnableWindow(pPage
->GetDlgItem(IDC_BROWSE
), TRUE
);
171 ::EnableWindow(pPage
->GetDlgItem(IDC_DIRECTORY
), TRUE
);
172 ::EnableWindow(pPage
->GetDlgItem(IDC_PASSWORD
), TRUE
);
174 /* Reset the progress bar's appearance */
175 CWindow
Progress(pPage
->GetDlgItem(IDC_PROGRESS
));
176 Progress
.SendMessage(PBM_SETRANGE32
, 0, 1);
177 Progress
.SendMessage(PBM_SETPOS
, 0, 0);
179 SendMessageCallback(pPage
->GetParent().m_hWnd
, PSM_PRESSBUTTON
, PSBTN_NEXT
, 0, NULL
, NULL
);
186 if (m_hExtractionThread
!= NULL
)
188 /* Extraction will check the value of m_bExtractionThreadCancel between each file in the archive */
189 m_bExtractionThreadCancel
= true;
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
)
251 if (_CZipAskPassword(m_hWnd
, NULL
, Password
) == eAccept
)
253 *m_pPassword
= Password
;
258 void UpdateDirectory()
260 GetDlgItemText(IDC_DIRECTORY
, m_pExtract
->m_Directory
);
261 m_pExtract
->m_DirectoryChanged
= false;
265 enum { IDD
= IDD_PROPPAGEDESTINATION
};
267 BEGIN_MSG_MAP(CCompleteSettingsPage
)
268 COMMAND_ID_HANDLER(IDC_BROWSE
, OnBrowse
)
269 COMMAND_ID_HANDLER(IDC_PASSWORD
, OnPassword
)
270 COMMAND_HANDLER(IDC_DIRECTORY
, EN_CHANGE
, OnEnChangeDirectory
)
271 CHAIN_MSG_MAP(CPropertyPageImpl
<CExtractSettingsPage
>)
276 class CCompleteSettingsPage
: public CPropertyPageImpl
<CCompleteSettingsPage
>
279 CZipExtract
* m_pExtract
;
282 CCompleteSettingsPage(CZipExtract
* extract
)
283 :CPropertyPageImpl
<CCompleteSettingsPage
>(MAKEINTRESOURCE(IDS_WIZ_TITLE
))
284 , m_pExtract(extract
)
286 m_psp
.pszHeaderTitle
= MAKEINTRESOURCE(IDS_WIZ_COMPL_TITLE
);
287 m_psp
.pszHeaderSubTitle
= MAKEINTRESOURCE(IDS_WIZ_COMPL_SUBTITLE
);
288 m_psp
.dwFlags
|= PSP_USETITLE
| PSP_USEHEADERTITLE
| PSP_USEHEADERSUBTITLE
;
294 SetWizardButtons(PSWIZB_FINISH
);
295 CStringW Path
= m_pExtract
->m_Directory
;
296 PWSTR Ptr
= Path
.GetBuffer();
298 ::GetWindowRect(GetDlgItem(IDC_DESTDIR
), &rc
);
300 PathCompactPathW(dc
, Ptr
, rc
.right
- rc
.left
);
302 Path
.ReleaseBuffer();
303 SetDlgItemTextW(IDC_DESTDIR
, Path
);
304 CheckDlgButton(IDC_SHOW_EXTRACTED
, BST_CHECKED
);
307 BOOL
OnWizardFinish()
309 if (IsDlgButtonChecked(IDC_SHOW_EXTRACTED
) == BST_CHECKED
)
311 ShellExecuteW(NULL
, L
"explore", m_pExtract
->m_Directory
, NULL
, NULL
, SW_SHOW
);
317 enum { IDD
= IDD_PROPPAGECOMPLETE
};
319 BEGIN_MSG_MAP(CCompleteSettingsPage
)
320 CHAIN_MSG_MAP(CPropertyPageImpl
<CCompleteSettingsPage
>)
327 PROPSHEETHEADERW psh
= { sizeof(psh
), 0 };
328 psh
.dwFlags
= PSH_WIZARD97
| PSH_HEADER
;
329 psh
.hInstance
= _AtlBaseModule
.GetResourceInstance();
331 CExtractSettingsPage
extractPage(this, &m_Password
);
332 CCompleteSettingsPage
completePage(this);
333 HPROPSHEETPAGE hpsp
[] =
335 extractPage
.Create(),
336 completePage
.Create()
340 psh
.nPages
= _countof(hpsp
);
342 PropertySheetW(&psh
);
345 bool Extract(HWND hDlg
, HWND hProgress
, const bool* bCancel
)
347 unz_global_info64 gi
;
348 uf
= unzOpen2_64(m_Filename
.GetString(), &g_FFunc
);
349 int err
= unzGetGlobalInfo64(uf
, &gi
);
352 DPRINT1("ERROR, unzGetGlobalInfo64: 0x%x\n", err
);
357 CZipEnumerator zipEnum
;
358 if (!zipEnum
.initialize(this))
360 DPRINT1("ERROR, zipEnum.initialize\n");
365 CWindow
Progress(hProgress
);
366 Progress
.SendMessage(PBM_SETRANGE32
, 0, gi
.number_entry
);
367 Progress
.SendMessage(PBM_SETPOS
, 0, 0);
370 CStringA BaseDirectory
= m_Directory
;
372 CStringA Password
= m_Password
;
373 unz_file_info64 Info
;
375 bool bOverwriteAll
= false;
376 while (zipEnum
.next(Name
, Info
))
384 bool is_dir
= Name
.GetLength() > 0 && Name
[Name
.GetLength()-1] == '/';
386 char CombinedPath
[MAX_PATH
* 2] = { 0 };
387 PathCombineA(CombinedPath
, BaseDirectory
, Name
);
388 CStringA FullPath
= CombinedPath
;
389 FullPath
.Replace('/', '\\'); /* SHPathPrepareForWriteA does not handle '/' */
390 DWORD dwFlags
= SHPPFW_DIRCREATE
| (is_dir
? SHPPFW_NONE
: SHPPFW_IGNOREFILENAME
);
391 HRESULT hr
= SHPathPrepareForWriteA(hDlg
, NULL
, FullPath
, dwFlags
);
392 if (FAILED_UNEXPECTEDLY(hr
))
401 if (Info
.flag
& MINIZIP_PASSWORD_FLAG
)
403 eZipPasswordResponse Response
= eAccept
;
406 /* If there is a password set, try it */
407 if (!Password
.IsEmpty())
409 err
= unzOpenCurrentFilePassword(uf
, Password
);
412 /* Try to read some bytes, because unzOpenCurrentFilePassword does not return failure */
414 err
= unzReadCurrentFile(uf
, Buf
, sizeof(Buf
));
415 unzCloseCurrentFile(uf
);
418 /* 're'-open the file so that we can begin to extract */
419 err
= unzOpenCurrentFilePassword(uf
, Password
);
424 Response
= _CZipAskPassword(hDlg
, Name
, Password
);
425 } while (Response
== eAccept
);
427 if (Response
== eSkip
)
429 Progress
.SendMessage(PBM_SETPOS
, CurrentFile
, 0);
432 else if (Response
== eAbort
)
440 err
= unzOpenCurrentFile(uf
);
445 DPRINT1("ERROR, unzOpenCurrentFilePassword: 0x%x\n", err
);
450 HANDLE hFile
= CreateFileA(FullPath
, GENERIC_WRITE
, 0, NULL
, CREATE_NEW
, FILE_ATTRIBUTE_NORMAL
, NULL
);
451 if (hFile
== INVALID_HANDLE_VALUE
)
453 DWORD dwErr
= GetLastError();
454 if (dwErr
== ERROR_FILE_EXISTS
)
456 bool bOverwrite
= bOverwriteAll
;
459 eZipConfirmResponse Result
= _CZipAskReplace(hDlg
, FullPath
);
463 bOverwriteAll
= true;
470 unzCloseCurrentFile(uf
);
478 hFile
= CreateFileA(FullPath
, GENERIC_WRITE
, 0, NULL
, CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
479 if (hFile
== INVALID_HANDLE_VALUE
)
481 dwErr
= GetLastError();
486 unzCloseCurrentFile(uf
);
490 if (hFile
== INVALID_HANDLE_VALUE
)
492 unzCloseCurrentFile(uf
);
493 DPRINT1("ERROR, CreateFileA: 0x%x (%s)\n", dwErr
, bOverwriteAll
? "Y" : "N");
504 BOOL deleteResult
= DeleteFileA(FullPath
);
505 if (deleteResult
== 0)
506 DPRINT1("ERROR, DeleteFileA: 0x%x\n", GetLastError());
511 err
= unzReadCurrentFile(uf
, Buffer
, sizeof(Buffer
));
515 DPRINT1("ERROR, unzReadCurrentFile: 0x%x\n", err
);
521 if (!WriteFile(hFile
, Buffer
, err
, &dwWritten
, NULL
))
523 DPRINT1("ERROR, WriteFile: 0x%x\n", GetLastError());
526 if (dwWritten
!= (DWORD
)err
)
528 DPRINT1("ERROR, WriteFile: dwWritten:%d err:%d\n", dwWritten
, err
);
535 /* Update Filetime */
536 FILETIME LastAccessTime
;
537 GetFileTime(hFile
, NULL
, &LastAccessTime
, NULL
);
538 FILETIME LocalFileTime
;
539 DosDateTimeToFileTime((WORD
)(Info
.dosDate
>> 16), (WORD
)Info
.dosDate
, &LocalFileTime
);
541 LocalFileTimeToFileTime(&LocalFileTime
, &FileTime
);
542 SetFileTime(hFile
, &FileTime
, &LastAccessTime
, &FileTime
);
549 unzCloseCurrentFile(uf
);
550 DPRINT1("ERROR, unzReadCurrentFile2: 0x%x\n", err
);
556 err
= unzCloseCurrentFile(uf
);
559 DPRINT1("ERROR(non-fatal), unzCloseCurrentFile: 0x%x\n", err
);
562 Progress
.SendMessage(PBM_SETPOS
, CurrentFile
, 0);
571 void _CZipExtract_runWizard(PCWSTR Filename
)
573 CZipExtract
extractor(Filename
);
574 extractor
.runWizard();