[ZIPFLDR] Enable / disable the correct wizard buttons
[reactos.git] / dll / shellext / zipfldr / CZipExtract.cpp
1 /*
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)
6 */
7
8 #include "precomp.h"
9
10 class CZipExtract :
11 public IZip
12 {
13 CStringW m_Filename;
14 CStringW m_Directory;
15 bool m_DirectoryChanged;
16 unzFile uf;
17 public:
18 CZipExtract(PCWSTR Filename)
19 :m_DirectoryChanged(false)
20 ,uf(NULL)
21 {
22 m_Filename = Filename;
23 m_Directory = m_Filename;
24 PWSTR Dir = m_Directory.GetBuffer();
25 PathRemoveExtensionW(Dir);
26 m_Directory.ReleaseBuffer();
27 }
28
29 ~CZipExtract()
30 {
31 if (uf)
32 {
33 DPRINT1("WARNING: uf not closed!\n");
34 Close();
35 }
36 }
37
38 void Close()
39 {
40 if (uf)
41 unzClose(uf);
42 uf = NULL;
43 }
44
45 // *** IZip methods ***
46 STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject)
47 {
48 if (riid == IID_IUnknown)
49 {
50 *ppvObject = this;
51 AddRef();
52 return S_OK;
53 }
54 return E_NOINTERFACE;
55 }
56 STDMETHODIMP_(ULONG) AddRef(void)
57 {
58 return 2;
59 }
60 STDMETHODIMP_(ULONG) Release(void)
61 {
62 return 1;
63 }
64 STDMETHODIMP_(unzFile) getZip()
65 {
66 return uf;
67 }
68
69 class CConfirmReplace : public CDialogImpl<CConfirmReplace>
70 {
71 private:
72 CStringA m_Filename;
73 public:
74 enum DialogResult
75 {
76 Yes,
77 YesToAll,
78 No,
79 Cancel
80 };
81
82 static DialogResult ShowDlg(HWND hDlg, PCSTR FullPath)
83 {
84 PCSTR Filename = PathFindFileNameA(FullPath);
85 CConfirmReplace confirm(Filename);
86 INT_PTR Result = confirm.DoModal(hDlg);
87 switch (Result)
88 {
89 case IDYES: return Yes;
90 case IDYESALL: return YesToAll;
91 default:
92 case IDNO: return No;
93 case IDCANCEL: return Cancel;
94 }
95 }
96
97 CConfirmReplace(const char* filename)
98 {
99 m_Filename = filename;
100 }
101
102 LRESULT OnInitDialog(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
103 {
104 CenterWindow(GetParent());
105
106 HICON hIcon = LoadIcon(NULL, IDI_EXCLAMATION);
107 SendDlgItemMessage(IDC_EXCLAMATION_ICON, STM_SETICON, (WPARAM)hIcon);
108
109 /* Our CString does not support FormatMessage yet */
110 CStringA message(MAKEINTRESOURCE(IDS_OVERWRITEFILE_TEXT));
111 CHeapPtr<CHAR, CLocalAllocator> formatted;
112
113 DWORD_PTR args[2] =
114 {
115 (DWORD_PTR)m_Filename.GetString(),
116 NULL
117 };
118
119 ::FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY,
120 message, 0, 0, (LPSTR)&formatted, 0, (va_list*)args);
121
122 ::SetDlgItemTextA(m_hWnd, IDC_MESSAGE, formatted);
123 return 0;
124 }
125
126 LRESULT OnButton(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
127 {
128 EndDialog(wID);
129 return 0;
130 }
131
132 public:
133 enum { IDD = IDD_CONFIRM_FILE_REPLACE };
134
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)
141 END_MSG_MAP()
142 };
143
144
145 class CExtractSettingsPage : public CPropertyPageImpl<CExtractSettingsPage>
146 {
147 private:
148 CZipExtract* m_pExtract;
149
150 public:
151 CExtractSettingsPage(CZipExtract* extract)
152 :CPropertyPageImpl<CExtractSettingsPage>(MAKEINTRESOURCE(IDS_WIZ_TITLE))
153 ,m_pExtract(extract)
154 {
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;
158 }
159
160 int OnSetActive()
161 {
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);
167 return 0;
168 }
169
170 int OnWizardNext()
171 {
172 ::EnableWindow(GetDlgItem(IDC_BROWSE), FALSE);
173 ::EnableWindow(GetDlgItem(IDC_DIRECTORY), FALSE);
174 ::EnableWindow(GetDlgItem(IDC_PASSWORD), FALSE);
175 SetWizardButtons(0);
176
177 if (m_pExtract->m_DirectoryChanged)
178 UpdateDirectory();
179
180 if (!m_pExtract->Extract(m_hWnd, GetDlgItem(IDC_PROGRESS)))
181 {
182 /* Extraction failed, do not go to the next page */
183 SetWindowLongPtr(DWLP_MSGRESULT, -1);
184
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);
189
190 return TRUE;
191 }
192 return 0;
193 }
194
195 struct browse_info
196 {
197 HWND hWnd;
198 LPCWSTR Directory;
199 };
200
201 static INT CALLBACK s_BrowseCallbackProc(HWND hWnd, UINT uMsg, LPARAM lp, LPARAM pData)
202 {
203 if (uMsg == BFFM_INITIALIZED)
204 {
205 browse_info* info = (browse_info*)pData;
206 CWindow dlg(hWnd);
207 dlg.SendMessage(BFFM_SETSELECTION, TRUE, (LPARAM)info->Directory);
208 dlg.CenterWindow(info->hWnd);
209 }
210 return 0;
211 }
212
213 LRESULT OnBrowse(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
214 {
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;
222
223 if (m_pExtract->m_DirectoryChanged)
224 UpdateDirectory();
225
226 browse_info info = { m_hWnd, m_pExtract->m_Directory.GetString() };
227 bi.lParam = (LPARAM)&info;
228
229 CComHeapPtr<ITEMIDLIST> pidl;
230 pidl.Attach(SHBrowseForFolderW(&bi));
231
232 WCHAR tmpPath[MAX_PATH];
233 if (pidl && SHGetPathFromIDListW(pidl, tmpPath))
234 {
235 m_pExtract->m_Directory = tmpPath;
236 SetDlgItemTextW(IDC_DIRECTORY, m_pExtract->m_Directory);
237 m_pExtract->m_DirectoryChanged = false;
238 }
239 return 0;
240 }
241
242 LRESULT OnEnChangeDirectory(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
243 {
244 m_pExtract->m_DirectoryChanged = true;
245 return 0;
246 }
247
248 LRESULT OnPassword(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
249 {
250 return 0;
251 }
252
253 void UpdateDirectory()
254 {
255 GetDlgItemText(IDC_DIRECTORY, m_pExtract->m_Directory);
256 m_pExtract->m_DirectoryChanged = false;
257 }
258
259 public:
260 enum { IDD = IDD_PROPPAGEDESTINATION };
261
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>)
267 END_MSG_MAP()
268 };
269
270
271 class CCompleteSettingsPage : public CPropertyPageImpl<CCompleteSettingsPage>
272 {
273 private:
274 CZipExtract* m_pExtract;
275
276 public:
277 CCompleteSettingsPage(CZipExtract* extract)
278 :CPropertyPageImpl<CCompleteSettingsPage>(MAKEINTRESOURCE(IDS_WIZ_TITLE))
279 , m_pExtract(extract)
280 {
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;
284 }
285
286
287 int OnSetActive()
288 {
289 SetWizardButtons(PSWIZB_FINISH);
290 CStringW Path = m_pExtract->m_Directory;
291 PWSTR Ptr = Path.GetBuffer();
292 RECT rc;
293 ::GetWindowRect(GetDlgItem(IDC_DESTDIR), &rc);
294 HDC dc = GetDC();
295 PathCompactPathW(dc, Ptr, rc.right - rc.left);
296 ReleaseDC(dc);
297 Path.ReleaseBuffer();
298 SetDlgItemTextW(IDC_DESTDIR, Path);
299 CheckDlgButton(IDC_SHOW_EXTRACTED, BST_CHECKED);
300 return 0;
301 }
302 BOOL OnWizardFinish()
303 {
304 if (IsDlgButtonChecked(IDC_SHOW_EXTRACTED) == BST_CHECKED)
305 {
306 ShellExecuteW(NULL, L"explore", m_pExtract->m_Directory, NULL, NULL, SW_SHOW);
307 }
308 return FALSE;
309 }
310
311 public:
312 enum { IDD = IDD_PROPPAGECOMPLETE };
313
314 BEGIN_MSG_MAP(CCompleteSettingsPage)
315 CHAIN_MSG_MAP(CPropertyPageImpl<CCompleteSettingsPage>)
316 END_MSG_MAP()
317 };
318
319
320 void runWizard()
321 {
322 PROPSHEETHEADERW psh = { sizeof(psh), 0 };
323 psh.dwFlags = PSH_WIZARD97 | PSH_HEADER;
324 psh.hInstance = _AtlBaseModule.GetResourceInstance();
325
326 CExtractSettingsPage extractPage(this);
327 CCompleteSettingsPage completePage(this);
328 HPROPSHEETPAGE hpsp[] =
329 {
330 extractPage.Create(),
331 completePage.Create()
332 };
333
334 psh.phpage = hpsp;
335 psh.nPages = _countof(hpsp);
336
337 PropertySheetW(&psh);
338 }
339
340 bool Extract(HWND hDlg, HWND hProgress)
341 {
342 unz_global_info64 gi;
343 uf = unzOpen2_64(m_Filename.GetString(), &g_FFunc);
344 int err = unzGetGlobalInfo64(uf, &gi);
345 if (err != UNZ_OK)
346 {
347 DPRINT1("ERROR, unzGetGlobalInfo64: 0x%x\n", err);
348 Close();
349 return false;
350 }
351
352 CZipEnumerator zipEnum;
353 if (!zipEnum.initialize(this))
354 {
355 DPRINT1("ERROR, zipEnum.initialize\n");
356 Close();
357 return false;
358 }
359
360 CWindow Progress(hProgress);
361 Progress.SendMessage(PBM_SETRANGE32, 0, gi.number_entry);
362 Progress.SendMessage(PBM_SETPOS, 0, 0);
363
364 BYTE Buffer[2048];
365 CStringA BaseDirectory = m_Directory;
366 CStringA Name;
367 unz_file_info64 Info;
368 int CurrentFile = 0;
369 bool bOverwriteAll = false;
370 while (zipEnum.next(Name, Info))
371 {
372 bool is_dir = Name.GetLength() > 0 && Name[Name.GetLength()-1] == '/';
373
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))
381 {
382 Close();
383 return false;
384 }
385 CurrentFile++;
386 if (is_dir)
387 continue;
388
389 const char* password = NULL;
390 /* FIXME: Process password, if required and not specified, prompt the user */
391 err = unzOpenCurrentFilePassword(uf, password);
392 if (err != UNZ_OK)
393 {
394 DPRINT1("ERROR, unzOpenCurrentFilePassword: 0x%x\n", err);
395 Close();
396 return false;
397 }
398
399 HANDLE hFile = CreateFileA(FullPath, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
400 if (hFile == INVALID_HANDLE_VALUE)
401 {
402 DWORD dwErr = GetLastError();
403 if (dwErr == ERROR_FILE_EXISTS)
404 {
405 bool bOverwrite = bOverwriteAll;
406 if (!bOverwriteAll)
407 {
408 CConfirmReplace::DialogResult Result = CConfirmReplace::ShowDlg(hDlg, FullPath);
409 switch (Result)
410 {
411 case CConfirmReplace::YesToAll:
412 bOverwriteAll = true;
413 case CConfirmReplace::Yes:
414 bOverwrite = true;
415 break;
416 case CConfirmReplace::No:
417 break;
418 case CConfirmReplace::Cancel:
419 unzCloseCurrentFile(uf);
420 Close();
421 return false;
422 }
423 }
424
425 if (bOverwrite)
426 {
427 hFile = CreateFileA(FullPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
428 if (hFile == INVALID_HANDLE_VALUE)
429 {
430 dwErr = GetLastError();
431 }
432 }
433 else
434 {
435 unzCloseCurrentFile(uf);
436 continue;
437 }
438 }
439 if (hFile == INVALID_HANDLE_VALUE)
440 {
441 unzCloseCurrentFile(uf);
442 DPRINT1("ERROR, CreateFileA: 0x%x (%s)\n", dwErr, bOverwriteAll ? "Y" : "N");
443 Close();
444 return false;
445 }
446 }
447
448 do
449 {
450 err = unzReadCurrentFile(uf, Buffer, sizeof(Buffer));
451
452 if (err < 0)
453 {
454 DPRINT1("ERROR, unzReadCurrentFile: 0x%x\n", err);
455 break;
456 }
457 else if (err > 0)
458 {
459 DWORD dwWritten;
460 if (!WriteFile(hFile, Buffer, err, &dwWritten, NULL))
461 {
462 DPRINT1("ERROR, WriteFile: 0x%x\n", GetLastError());
463 break;
464 }
465 if (dwWritten != (DWORD)err)
466 {
467 DPRINT1("ERROR, WriteFile: dwWritten:%d err:%d\n", dwWritten, err);
468 break;
469 }
470 }
471
472 } while (err > 0);
473
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);
479 FILETIME FileTime;
480 LocalFileTimeToFileTime(&LocalFileTime, &FileTime);
481 SetFileTime(hFile, &FileTime, &LastAccessTime, &FileTime);
482
483 /* Done.. */
484 CloseHandle(hFile);
485
486 if (err)
487 {
488 unzCloseCurrentFile(uf);
489 DPRINT1("ERROR, unzReadCurrentFile2: 0x%x\n", err);
490 Close();
491 return false;
492 }
493 else
494 {
495 err = unzCloseCurrentFile(uf);
496 if (err != UNZ_OK)
497 {
498 DPRINT1("ERROR(non-fatal), unzCloseCurrentFile: 0x%x\n", err);
499 }
500 }
501 Progress.SendMessage(PBM_SETPOS, CurrentFile, 0);
502 }
503
504 Close();
505 return true;
506 }
507 };
508
509
510 void _CZipExtract_runWizard(PCWSTR Filename)
511 {
512 CZipExtract extractor(Filename);
513 extractor.runWizard();
514 }
515