[PC98VID] Fix MSVC 2015 build for PC-98 target
[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-2019 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 CStringA m_Password;
16 bool m_DirectoryChanged;
17 unzFile uf;
18 public:
19 CZipExtract(PCWSTR Filename)
20 :m_DirectoryChanged(false)
21 ,uf(NULL)
22 {
23 m_Filename = Filename;
24 m_Directory = m_Filename;
25 PWSTR Dir = m_Directory.GetBuffer();
26 PathRemoveExtensionW(Dir);
27 m_Directory.ReleaseBuffer();
28 }
29
30 ~CZipExtract()
31 {
32 if (uf)
33 {
34 DPRINT1("WARNING: uf not closed!\n");
35 Close();
36 }
37 }
38
39 void Close()
40 {
41 if (uf)
42 unzClose(uf);
43 uf = NULL;
44 }
45
46 // *** IZip methods ***
47 STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject)
48 {
49 if (riid == IID_IUnknown)
50 {
51 *ppvObject = this;
52 AddRef();
53 return S_OK;
54 }
55 return E_NOINTERFACE;
56 }
57 STDMETHODIMP_(ULONG) AddRef(void)
58 {
59 return 2;
60 }
61 STDMETHODIMP_(ULONG) Release(void)
62 {
63 return 1;
64 }
65 STDMETHODIMP_(unzFile) getZip()
66 {
67 return uf;
68 }
69
70 class CExtractSettingsPage : public CPropertyPageImpl<CExtractSettingsPage>
71 {
72 private:
73 HANDLE m_hExtractionThread;
74 bool m_bExtractionThreadCancel;
75
76 CZipExtract* m_pExtract;
77 CStringA* m_pPassword;
78
79 public:
80 CExtractSettingsPage(CZipExtract* extract, CStringA* password)
81 :CPropertyPageImpl<CExtractSettingsPage>(MAKEINTRESOURCE(IDS_WIZ_TITLE))
82 ,m_hExtractionThread(NULL)
83 ,m_bExtractionThreadCancel(false)
84 ,m_pExtract(extract)
85 ,m_pPassword(password)
86 {
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;
90 }
91
92 int OnSetActive()
93 {
94 SetDlgItemTextW(IDC_DIRECTORY, m_pExtract->m_Directory);
95 m_pExtract->m_DirectoryChanged = false;
96 GetParent().CenterWindow(::GetDesktopWindow());
97 SetWizardButtons(PSWIZB_NEXT);
98 return 0;
99 }
100
101 int OnWizardNext()
102 {
103 if (m_hExtractionThread != NULL)
104 {
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)
111 {
112 return 0;
113 }
114 else
115 {
116 SetWindowLongPtr(DWLP_MSGRESULT, -1);
117 return TRUE;
118 }
119 }
120
121 /* We end up here if the user manually clicks Next: start extraction */
122 m_bExtractionThreadCancel = false;
123
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);
128 SetWizardButtons(0);
129
130 CStringW strExtracting(MAKEINTRESOURCEW(IDS_EXTRACTING));
131 SetDlgItemTextW(IDC_STATUSTEXT, strExtracting);
132
133 if (m_pExtract->m_DirectoryChanged)
134 UpdateDirectory();
135
136 m_pExtract->AddRef();
137
138 m_hExtractionThread = CreateThread(NULL, 0,
139 &CExtractSettingsPage::ExtractEntry,
140 this,
141 0, NULL);
142 if (!m_hExtractionThread)
143 {
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();
148
149 SetWindowLongPtr(DWLP_MSGRESULT, -1);
150
151 ::EnableWindow(GetDlgItem(IDC_BROWSE), TRUE);
152 ::EnableWindow(GetDlgItem(IDC_DIRECTORY), TRUE);
153 ::EnableWindow(GetDlgItem(IDC_PASSWORD), TRUE);
154 SetWizardButtons(PSWIZB_NEXT);
155 }
156 return TRUE;
157 }
158
159 static DWORD WINAPI ExtractEntry(LPVOID lpParam)
160 {
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;
165
166 pPage->SetWizardButtons(PSWIZB_NEXT);
167 if (!res)
168 {
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);
173
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);
178 }
179 SendMessageCallback(pPage->GetParent().m_hWnd, PSM_PRESSBUTTON, PSBTN_NEXT, 0, NULL, NULL);
180
181 return 0;
182 }
183
184 BOOL OnQueryCancel()
185 {
186 if (m_hExtractionThread != NULL)
187 {
188 /* Extraction will check the value of m_bExtractionThreadCancel between each file in the archive */
189 m_bExtractionThreadCancel = true;
190 return TRUE;
191 }
192 return FALSE;
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 CStringA Password;
251 if (_CZipAskPassword(m_hWnd, NULL, Password) == eAccept)
252 {
253 *m_pPassword = Password;
254 }
255 return 0;
256 }
257
258 void UpdateDirectory()
259 {
260 GetDlgItemText(IDC_DIRECTORY, m_pExtract->m_Directory);
261 m_pExtract->m_DirectoryChanged = false;
262 }
263
264 public:
265 enum { IDD = IDD_PROPPAGEDESTINATION };
266
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>)
272 END_MSG_MAP()
273 };
274
275
276 class CCompleteSettingsPage : public CPropertyPageImpl<CCompleteSettingsPage>
277 {
278 private:
279 CZipExtract* m_pExtract;
280
281 public:
282 CCompleteSettingsPage(CZipExtract* extract)
283 :CPropertyPageImpl<CCompleteSettingsPage>(MAKEINTRESOURCE(IDS_WIZ_TITLE))
284 , m_pExtract(extract)
285 {
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;
289 }
290
291
292 int OnSetActive()
293 {
294 SetWizardButtons(PSWIZB_FINISH);
295 CStringW Path = m_pExtract->m_Directory;
296 PWSTR Ptr = Path.GetBuffer(MAX_PATH);
297 RECT rc;
298 ::GetWindowRect(GetDlgItem(IDC_DESTDIR), &rc);
299 HDC dc = GetDC();
300 PathCompactPathW(dc, Ptr, rc.right - rc.left);
301 ReleaseDC(dc);
302 Path.ReleaseBuffer();
303 SetDlgItemTextW(IDC_DESTDIR, Path);
304 CheckDlgButton(IDC_SHOW_EXTRACTED, BST_CHECKED);
305 return 0;
306 }
307 BOOL OnWizardFinish()
308 {
309 if (IsDlgButtonChecked(IDC_SHOW_EXTRACTED) == BST_CHECKED)
310 {
311 ShellExecuteW(NULL, L"explore", m_pExtract->m_Directory, NULL, NULL, SW_SHOW);
312 }
313 return FALSE;
314 }
315
316 public:
317 enum { IDD = IDD_PROPPAGECOMPLETE };
318
319 BEGIN_MSG_MAP(CCompleteSettingsPage)
320 CHAIN_MSG_MAP(CPropertyPageImpl<CCompleteSettingsPage>)
321 END_MSG_MAP()
322 };
323
324
325 void runWizard()
326 {
327 PROPSHEETHEADERW psh = { sizeof(psh), 0 };
328 psh.dwFlags = PSH_WIZARD97 | PSH_HEADER;
329 psh.hInstance = _AtlBaseModule.GetResourceInstance();
330
331 CExtractSettingsPage extractPage(this, &m_Password);
332 CCompleteSettingsPage completePage(this);
333 HPROPSHEETPAGE hpsp[] =
334 {
335 extractPage.Create(),
336 completePage.Create()
337 };
338
339 psh.phpage = hpsp;
340 psh.nPages = _countof(hpsp);
341 psh.pszbmWatermark = MAKEINTRESOURCE(IDB_WATERMARK);
342 psh.pszbmHeader = MAKEINTRESOURCE(IDB_HEADER);
343
344 PropertySheetW(&psh);
345 }
346
347 bool Extract(HWND hDlg, HWND hProgress, const bool* bCancel)
348 {
349 unz_global_info64 gi;
350 uf = unzOpen2_64(m_Filename.GetString(), &g_FFunc);
351 int err = unzGetGlobalInfo64(uf, &gi);
352 if (err != UNZ_OK)
353 {
354 DPRINT1("ERROR, unzGetGlobalInfo64: 0x%x\n", err);
355 Close();
356 return false;
357 }
358
359 CZipEnumerator zipEnum;
360 if (!zipEnum.initialize(this))
361 {
362 DPRINT1("ERROR, zipEnum.initialize\n");
363 Close();
364 return false;
365 }
366
367 CWindow Progress(hProgress);
368 Progress.SendMessage(PBM_SETRANGE32, 0, gi.number_entry);
369 Progress.SendMessage(PBM_SETPOS, 0, 0);
370
371 BYTE Buffer[2048];
372 CStringA BaseDirectory = m_Directory;
373 CStringA Name;
374 CStringA Password = m_Password;
375 unz_file_info64 Info;
376 int CurrentFile = 0;
377 bool bOverwriteAll = false;
378 while (zipEnum.next(Name, Info))
379 {
380 if (*bCancel)
381 {
382 Close();
383 return false;
384 }
385
386 bool is_dir = Name.GetLength() > 0 && Name[Name.GetLength()-1] == '/';
387
388 char CombinedPath[MAX_PATH * 2] = { 0 };
389 PathCombineA(CombinedPath, BaseDirectory, Name);
390 CStringA FullPath = CombinedPath;
391 FullPath.Replace('/', '\\'); /* SHPathPrepareForWriteA does not handle '/' */
392 DWORD dwFlags = SHPPFW_DIRCREATE | (is_dir ? SHPPFW_NONE : SHPPFW_IGNOREFILENAME);
393 HRESULT hr = SHPathPrepareForWriteA(hDlg, NULL, FullPath, dwFlags);
394 if (FAILED_UNEXPECTEDLY(hr))
395 {
396 Close();
397 return false;
398 }
399 CurrentFile++;
400 if (is_dir)
401 continue;
402
403 if (Info.flag & MINIZIP_PASSWORD_FLAG)
404 {
405 eZipPasswordResponse Response = eAccept;
406 do
407 {
408 /* If there is a password set, try it */
409 if (!Password.IsEmpty())
410 {
411 err = unzOpenCurrentFilePassword(uf, Password);
412 if (err == UNZ_OK)
413 {
414 /* Try to read some bytes, because unzOpenCurrentFilePassword does not return failure */
415 char Buf[10];
416 err = unzReadCurrentFile(uf, Buf, sizeof(Buf));
417 unzCloseCurrentFile(uf);
418 if (err >= UNZ_OK)
419 {
420 /* 're'-open the file so that we can begin to extract */
421 err = unzOpenCurrentFilePassword(uf, Password);
422 break;
423 }
424 }
425 }
426 Response = _CZipAskPassword(hDlg, Name, Password);
427 } while (Response == eAccept);
428
429 if (Response == eSkip)
430 {
431 Progress.SendMessage(PBM_SETPOS, CurrentFile, 0);
432 continue;
433 }
434 else if (Response == eAbort)
435 {
436 Close();
437 return false;
438 }
439 }
440 else
441 {
442 err = unzOpenCurrentFile(uf);
443 }
444
445 if (err != UNZ_OK)
446 {
447 DPRINT1("ERROR, unzOpenCurrentFilePassword: 0x%x\n", err);
448 Close();
449 return false;
450 }
451
452 HANDLE hFile = CreateFileA(FullPath, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
453 if (hFile == INVALID_HANDLE_VALUE)
454 {
455 DWORD dwErr = GetLastError();
456 if (dwErr == ERROR_FILE_EXISTS)
457 {
458 bool bOverwrite = bOverwriteAll;
459 if (!bOverwriteAll)
460 {
461 eZipConfirmResponse Result = _CZipAskReplace(hDlg, FullPath);
462 switch (Result)
463 {
464 case eYesToAll:
465 bOverwriteAll = true;
466 case eYes:
467 bOverwrite = true;
468 break;
469 case eNo:
470 break;
471 case eCancel:
472 unzCloseCurrentFile(uf);
473 Close();
474 return false;
475 }
476 }
477
478 if (bOverwrite)
479 {
480 hFile = CreateFileA(FullPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
481 if (hFile == INVALID_HANDLE_VALUE)
482 {
483 dwErr = GetLastError();
484 }
485 }
486 else
487 {
488 unzCloseCurrentFile(uf);
489 continue;
490 }
491 }
492 if (hFile == INVALID_HANDLE_VALUE)
493 {
494 unzCloseCurrentFile(uf);
495 DPRINT1("ERROR, CreateFileA: 0x%x (%s)\n", dwErr, bOverwriteAll ? "Y" : "N");
496 Close();
497 return false;
498 }
499 }
500
501 do
502 {
503 if (*bCancel)
504 {
505 CloseHandle(hFile);
506 BOOL deleteResult = DeleteFileA(FullPath);
507 if (deleteResult == 0)
508 DPRINT1("ERROR, DeleteFileA: 0x%x\n", GetLastError());
509 Close();
510 return false;
511 }
512
513 err = unzReadCurrentFile(uf, Buffer, sizeof(Buffer));
514
515 if (err < 0)
516 {
517 DPRINT1("ERROR, unzReadCurrentFile: 0x%x\n", err);
518 break;
519 }
520 else if (err > 0)
521 {
522 DWORD dwWritten;
523 if (!WriteFile(hFile, Buffer, err, &dwWritten, NULL))
524 {
525 DPRINT1("ERROR, WriteFile: 0x%x\n", GetLastError());
526 break;
527 }
528 if (dwWritten != (DWORD)err)
529 {
530 DPRINT1("ERROR, WriteFile: dwWritten:%d err:%d\n", dwWritten, err);
531 break;
532 }
533 }
534
535 } while (err > 0);
536
537 /* Update Filetime */
538 FILETIME LocalFileTime;
539 DosDateTimeToFileTime((WORD)(Info.dosDate >> 16), (WORD)Info.dosDate, &LocalFileTime);
540 FILETIME FileTime;
541 LocalFileTimeToFileTime(&LocalFileTime, &FileTime);
542 SetFileTime(hFile, &FileTime, &FileTime, &FileTime);
543
544 /* Done */
545 CloseHandle(hFile);
546
547 if (err)
548 {
549 unzCloseCurrentFile(uf);
550 DPRINT1("ERROR, unzReadCurrentFile2: 0x%x\n", err);
551 Close();
552 return false;
553 }
554 else
555 {
556 err = unzCloseCurrentFile(uf);
557 if (err != UNZ_OK)
558 {
559 DPRINT1("ERROR(non-fatal), unzCloseCurrentFile: 0x%x\n", err);
560 }
561 }
562 Progress.SendMessage(PBM_SETPOS, CurrentFile, 0);
563 }
564
565 Close();
566 return true;
567 }
568 };
569
570
571 void _CZipExtract_runWizard(PCWSTR Filename)
572 {
573 CZipExtract extractor(Filename);
574 extractor.runWizard();
575 }
576