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