[ADVAPI32] SEH-protext the calls to service control handlers
[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 return 0;
167 }
168
169 int OnWizardNext()
170 {
171 ::EnableWindow(GetDlgItem(IDC_BROWSE), FALSE);
172 ::EnableWindow(GetDlgItem(IDC_DIRECTORY), FALSE);
173 ::EnableWindow(GetDlgItem(IDC_PASSWORD), FALSE);
174
175 if (m_pExtract->m_DirectoryChanged)
176 UpdateDirectory();
177
178 if (!m_pExtract->Extract(m_hWnd, GetDlgItem(IDC_PROGRESS)))
179 {
180 /* Extraction failed, do not go to the next page */
181 SetWindowLongPtr(DWLP_MSGRESULT, -1);
182
183 ::EnableWindow(GetDlgItem(IDC_BROWSE), TRUE);
184 ::EnableWindow(GetDlgItem(IDC_DIRECTORY), TRUE);
185 ::EnableWindow(GetDlgItem(IDC_PASSWORD), FALSE); /* Not supported for now */
186
187 return TRUE;
188 }
189 return 0;
190 }
191
192 struct browse_info
193 {
194 HWND hWnd;
195 LPCWSTR Directory;
196 };
197
198 static INT CALLBACK s_BrowseCallbackProc(HWND hWnd, UINT uMsg, LPARAM lp, LPARAM pData)
199 {
200 if (uMsg == BFFM_INITIALIZED)
201 {
202 browse_info* info = (browse_info*)pData;
203 CWindow dlg(hWnd);
204 dlg.SendMessage(BFFM_SETSELECTION, TRUE, (LPARAM)info->Directory);
205 dlg.CenterWindow(info->hWnd);
206 }
207 return 0;
208 }
209
210 LRESULT OnBrowse(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
211 {
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;
219
220 if (m_pExtract->m_DirectoryChanged)
221 UpdateDirectory();
222
223 browse_info info = { m_hWnd, m_pExtract->m_Directory.GetString() };
224 bi.lParam = (LPARAM)&info;
225
226 CComHeapPtr<ITEMIDLIST> pidl;
227 pidl.Attach(SHBrowseForFolderW(&bi));
228
229 WCHAR tmpPath[MAX_PATH];
230 if (pidl && SHGetPathFromIDListW(pidl, tmpPath))
231 {
232 m_pExtract->m_Directory = tmpPath;
233 SetDlgItemTextW(IDC_DIRECTORY, m_pExtract->m_Directory);
234 m_pExtract->m_DirectoryChanged = false;
235 }
236 return 0;
237 }
238
239 LRESULT OnEnChangeDirectory(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
240 {
241 m_pExtract->m_DirectoryChanged = true;
242 return 0;
243 }
244
245 LRESULT OnPassword(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
246 {
247 return 0;
248 }
249
250 void UpdateDirectory()
251 {
252 GetDlgItemText(IDC_DIRECTORY, m_pExtract->m_Directory);
253 m_pExtract->m_DirectoryChanged = false;
254 }
255
256 public:
257 enum { IDD = IDD_PROPPAGEDESTINATION };
258
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>)
264 END_MSG_MAP()
265 };
266
267
268 class CCompleteSettingsPage : public CPropertyPageImpl<CCompleteSettingsPage>
269 {
270 private:
271 CZipExtract* m_pExtract;
272
273 public:
274 CCompleteSettingsPage(CZipExtract* extract)
275 :CPropertyPageImpl<CCompleteSettingsPage>(MAKEINTRESOURCE(IDS_WIZ_TITLE))
276 , m_pExtract(extract)
277 {
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;
281 }
282
283
284 int OnSetActive()
285 {
286 SetWizardButtons(PSWIZB_FINISH);
287 CStringW Path = m_pExtract->m_Directory;
288 PWSTR Ptr = Path.GetBuffer();
289 RECT rc;
290 ::GetWindowRect(GetDlgItem(IDC_DESTDIR), &rc);
291 HDC dc = GetDC();
292 PathCompactPathW(dc, Ptr, rc.right - rc.left);
293 ReleaseDC(dc);
294 Path.ReleaseBuffer();
295 SetDlgItemTextW(IDC_DESTDIR, Path);
296 CheckDlgButton(IDC_SHOW_EXTRACTED, BST_CHECKED);
297 return 0;
298 }
299 BOOL OnWizardFinish()
300 {
301 if (IsDlgButtonChecked(IDC_SHOW_EXTRACTED) == BST_CHECKED)
302 {
303 ShellExecuteW(NULL, L"explore", m_pExtract->m_Directory, NULL, NULL, SW_SHOW);
304 }
305 return FALSE;
306 }
307
308 public:
309 enum { IDD = IDD_PROPPAGECOMPLETE };
310
311 BEGIN_MSG_MAP(CCompleteSettingsPage)
312 CHAIN_MSG_MAP(CPropertyPageImpl<CCompleteSettingsPage>)
313 END_MSG_MAP()
314 };
315
316
317 void runWizard()
318 {
319 PROPSHEETHEADERW psh = { sizeof(psh), 0 };
320 psh.dwFlags = PSH_WIZARD97 | PSH_HEADER;
321 psh.hInstance = _AtlBaseModule.GetResourceInstance();
322
323 CExtractSettingsPage extractPage(this);
324 CCompleteSettingsPage completePage(this);
325 HPROPSHEETPAGE hpsp[] =
326 {
327 extractPage.Create(),
328 completePage.Create()
329 };
330
331 psh.phpage = hpsp;
332 psh.nPages = _countof(hpsp);
333
334 PropertySheetW(&psh);
335 }
336
337 bool Extract(HWND hDlg, HWND hProgress)
338 {
339 unz_global_info64 gi;
340 uf = unzOpen2_64(m_Filename.GetString(), &g_FFunc);
341 int err = unzGetGlobalInfo64(uf, &gi);
342 if (err != UNZ_OK)
343 {
344 DPRINT1("ERROR, unzGetGlobalInfo64: 0x%x\n", err);
345 Close();
346 return false;
347 }
348
349 CZipEnumerator zipEnum;
350 if (!zipEnum.initialize(this))
351 {
352 DPRINT1("ERROR, zipEnum.initialize\n");
353 Close();
354 return false;
355 }
356
357 CWindow Progress(hProgress);
358 Progress.SendMessage(PBM_SETRANGE32, 0, gi.number_entry);
359 Progress.SendMessage(PBM_SETPOS, 0, 0);
360
361 BYTE Buffer[2048];
362 CStringA BaseDirectory = m_Directory;
363 CStringA Name;
364 unz_file_info64 Info;
365 int CurrentFile = 0;
366 bool bOverwriteAll = false;
367 while (zipEnum.next(Name, Info))
368 {
369 bool is_dir = Name.GetLength() > 0 && Name[Name.GetLength()-1] == '/';
370
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))
378 {
379 Close();
380 return false;
381 }
382 CurrentFile++;
383 if (is_dir)
384 continue;
385
386 const char* password = NULL;
387 /* FIXME: Process password, if required and not specified, prompt the user */
388 err = unzOpenCurrentFilePassword(uf, password);
389 if (err != UNZ_OK)
390 {
391 DPRINT1("ERROR, unzOpenCurrentFilePassword: 0x%x\n", err);
392 Close();
393 return false;
394 }
395
396 HANDLE hFile = CreateFileA(FullPath, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
397 if (hFile == INVALID_HANDLE_VALUE)
398 {
399 DWORD dwErr = GetLastError();
400 if (dwErr == ERROR_FILE_EXISTS)
401 {
402 bool bOverwrite = bOverwriteAll;
403 if (!bOverwriteAll)
404 {
405 CConfirmReplace::DialogResult Result = CConfirmReplace::ShowDlg(hDlg, FullPath);
406 switch (Result)
407 {
408 case CConfirmReplace::YesToAll:
409 bOverwriteAll = true;
410 case CConfirmReplace::Yes:
411 bOverwrite = true;
412 break;
413 case CConfirmReplace::No:
414 break;
415 case CConfirmReplace::Cancel:
416 unzCloseCurrentFile(uf);
417 Close();
418 return false;
419 }
420 }
421
422 if (bOverwrite)
423 {
424 hFile = CreateFileA(FullPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
425 if (hFile == INVALID_HANDLE_VALUE)
426 {
427 dwErr = GetLastError();
428 }
429 }
430 else
431 {
432 unzCloseCurrentFile(uf);
433 continue;
434 }
435 }
436 if (hFile == INVALID_HANDLE_VALUE)
437 {
438 unzCloseCurrentFile(uf);
439 DPRINT1("ERROR, CreateFileA: 0x%x (%s)\n", dwErr, bOverwriteAll ? "Y" : "N");
440 Close();
441 return false;
442 }
443 }
444
445 do
446 {
447 err = unzReadCurrentFile(uf, Buffer, sizeof(Buffer));
448
449 if (err < 0)
450 {
451 DPRINT1("ERROR, unzReadCurrentFile: 0x%x\n", err);
452 break;
453 }
454 else if (err > 0)
455 {
456 DWORD dwWritten;
457 if (!WriteFile(hFile, Buffer, err, &dwWritten, NULL))
458 {
459 DPRINT1("ERROR, WriteFile: 0x%x\n", GetLastError());
460 break;
461 }
462 if (dwWritten != (DWORD)err)
463 {
464 DPRINT1("ERROR, WriteFile: dwWritten:%d err:%d\n", dwWritten, err);
465 break;
466 }
467 }
468
469 } while (err > 0);
470
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);
476 FILETIME FileTime;
477 LocalFileTimeToFileTime(&LocalFileTime, &FileTime);
478 SetFileTime(hFile, &FileTime, &LastAccessTime, &FileTime);
479
480 /* Done.. */
481 CloseHandle(hFile);
482
483 if (err)
484 {
485 unzCloseCurrentFile(uf);
486 DPRINT1("ERROR, unzReadCurrentFile2: 0x%x\n", err);
487 Close();
488 return false;
489 }
490 else
491 {
492 err = unzCloseCurrentFile(uf);
493 if (err != UNZ_OK)
494 {
495 DPRINT1("ERROR(non-fatal), unzCloseCurrentFile: 0x%x\n", err);
496 }
497 }
498 Progress.SendMessage(PBM_SETPOS, CurrentFile, 0);
499 }
500
501 Close();
502 return true;
503 }
504 };
505
506
507 void _CZipExtract_runWizard(PCWSTR Filename)
508 {
509 CZipExtract extractor(Filename);
510 extractor.runWizard();
511 }
512