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         CStringW m_OldStatus;
79 
80     public:
81         CExtractSettingsPage(CZipExtract* extract, CStringA* password)
82             :CPropertyPageImpl<CExtractSettingsPage>(MAKEINTRESOURCE(IDS_WIZ_TITLE))
83             ,m_hExtractionThread(NULL)
84             ,m_bExtractionThreadCancel(false)
85             ,m_pExtract(extract)
86             ,m_pPassword(password)
87         {
88             m_psp.pszHeaderTitle = MAKEINTRESOURCE(IDS_WIZ_DEST_TITLE);
89             m_psp.pszHeaderSubTitle = MAKEINTRESOURCE(IDS_WIZ_DEST_SUBTITLE);
90             m_psp.dwFlags |= PSP_USETITLE | PSP_USEHEADERTITLE | PSP_USEHEADERSUBTITLE;
91         }
92 
93         int OnSetActive()
94         {
95             SetDlgItemTextW(IDC_DIRECTORY, m_pExtract->m_Directory);
96             m_pExtract->m_DirectoryChanged = false;
97             GetParent().CenterWindow(::GetDesktopWindow());
98             SetWizardButtons(PSWIZB_NEXT);
99             return 0;
100         }
101 
102         int OnWizardNext()
103         {
104             if (m_hExtractionThread != NULL)
105             {
106                 /* We enter here when extraction has finished, and go to next page if it succeeded */
107                 WaitForSingleObject(m_hExtractionThread, INFINITE);
108                 CloseHandle(m_hExtractionThread);
109                 m_hExtractionThread = NULL;
110                 m_pExtract->Release();
111                 if (!m_bExtractionThreadCancel)
112                 {
113                     return 0;
114                 }
115                 else
116                 {
117                     SetWindowLongPtr(DWLP_MSGRESULT, -1);
118                     return TRUE;
119                 }
120             }
121 
122             /* We end up here if the user manually clicks Next: start extraction */
123             m_bExtractionThreadCancel = false;
124 
125             /* Grey out every control during extraction to prevent user interaction */
126             ::EnableWindow(GetDlgItem(IDC_BROWSE), FALSE);
127             ::EnableWindow(GetDlgItem(IDC_DIRECTORY), FALSE);
128             ::EnableWindow(GetDlgItem(IDC_PASSWORD), FALSE);
129             SetWizardButtons(0);
130 
131             ::GetWindowTextW(GetDlgItem(IDC_STATUSTEXT), m_OldStatus.GetBuffer(MAX_PATH), MAX_PATH);
132             m_OldStatus.ReleaseBuffer();
133             CStringW strExtracting(MAKEINTRESOURCEW(IDS_EXTRACTING));
134             SetDlgItemTextW(IDC_STATUSTEXT, strExtracting);
135 
136             if (m_pExtract->m_DirectoryChanged)
137                 UpdateDirectory();
138 
139             m_pExtract->AddRef();
140 
141             m_hExtractionThread = CreateThread(NULL, 0,
142                                                &CExtractSettingsPage::ExtractEntry,
143                                                this,
144                                                0, NULL);
145             if (!m_hExtractionThread)
146             {
147                 /* Extraction thread creation failed, do not go to the next page */
148                 DWORD err = GetLastError();
149                 DPRINT1("ERROR, m_hExtractionThread: CreateThread failed: 0x%x\n", err);
150                 m_pExtract->Release();
151 
152                 SetWindowLongPtr(DWLP_MSGRESULT, -1);
153 
154                 ::EnableWindow(GetDlgItem(IDC_BROWSE), TRUE);
155                 ::EnableWindow(GetDlgItem(IDC_DIRECTORY), TRUE);
156                 ::EnableWindow(GetDlgItem(IDC_PASSWORD), TRUE);
157                 SetWizardButtons(PSWIZB_NEXT);
158             }
159             return TRUE;
160         }
161 
162         void WizardReset()
163         {
164             SetDlgItemTextW(IDC_STATUSTEXT, m_OldStatus);
165         }
166 
167         static DWORD WINAPI ExtractEntry(LPVOID lpParam)
168         {
169             CExtractSettingsPage* pPage = (CExtractSettingsPage*)lpParam;
170             bool res = pPage->m_pExtract->Extract(pPage->m_hWnd, pPage->GetDlgItem(IDC_PROGRESS), &(pPage->m_bExtractionThreadCancel));
171             /* Failing and cancelling extraction both mean we stay on the same property page */
172             pPage->m_bExtractionThreadCancel = !res;
173 
174             pPage->SetWizardButtons(PSWIZB_NEXT);
175             if (!res)
176             {
177                 /* Extraction failed/cancelled: the page becomes interactive again */
178                 ::EnableWindow(pPage->GetDlgItem(IDC_BROWSE), TRUE);
179                 ::EnableWindow(pPage->GetDlgItem(IDC_DIRECTORY), TRUE);
180                 ::EnableWindow(pPage->GetDlgItem(IDC_PASSWORD), TRUE);
181 
182                 /* Reset the progress bar's appearance */
183                 CWindow Progress(pPage->GetDlgItem(IDC_PROGRESS));
184                 Progress.SendMessage(PBM_SETRANGE32, 0, 1);
185                 Progress.SendMessage(PBM_SETPOS, 0, 0);
186                 pPage->WizardReset();
187             }
188             SendMessageCallback(pPage->GetParent().m_hWnd, PSM_PRESSBUTTON, PSBTN_NEXT, 0, NULL, NULL);
189 
190             return 0;
191         }
192 
193         BOOL OnQueryCancel()
194         {
195             if (m_hExtractionThread != NULL)
196             {
197                 /* Extraction will check the value of m_bExtractionThreadCancel between each file in the archive */
198                 m_bExtractionThreadCancel = true;
199                 return TRUE;
200             }
201             return FALSE;
202         }
203 
204         struct browse_info
205         {
206             HWND hWnd;
207             LPCWSTR Directory;
208         };
209 
210         static INT CALLBACK s_BrowseCallbackProc(HWND hWnd, UINT uMsg, LPARAM lp, LPARAM pData)
211         {
212             if (uMsg == BFFM_INITIALIZED)
213             {
214                 browse_info* info = (browse_info*)pData;
215                 CWindow dlg(hWnd);
216                 dlg.SendMessage(BFFM_SETSELECTION, TRUE, (LPARAM)info->Directory);
217                 dlg.CenterWindow(info->hWnd);
218             }
219             return 0;
220         }
221 
222         LRESULT OnBrowse(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
223         {
224             BROWSEINFOW bi = { m_hWnd };
225             WCHAR path[MAX_PATH];
226             bi.pszDisplayName = path;
227             bi.lpfn = s_BrowseCallbackProc;
228             bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
229             CStringW title(MAKEINTRESOURCEW(IDS_WIZ_BROWSE_TITLE));
230             bi.lpszTitle = title;
231 
232             if (m_pExtract->m_DirectoryChanged)
233                 UpdateDirectory();
234 
235             browse_info info = { m_hWnd, m_pExtract->m_Directory.GetString() };
236             bi.lParam = (LPARAM)&info;
237 
238             CComHeapPtr<ITEMIDLIST> pidl;
239             pidl.Attach(SHBrowseForFolderW(&bi));
240 
241             WCHAR tmpPath[MAX_PATH];
242             if (pidl && SHGetPathFromIDListW(pidl, tmpPath))
243             {
244                 m_pExtract->m_Directory = tmpPath;
245                 SetDlgItemTextW(IDC_DIRECTORY, m_pExtract->m_Directory);
246                 m_pExtract->m_DirectoryChanged = false;
247             }
248             return 0;
249         }
250 
251         LRESULT OnEnChangeDirectory(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
252         {
253             m_pExtract->m_DirectoryChanged = true;
254             return 0;
255         }
256 
257         LRESULT OnPassword(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
258         {
259             CStringA Password;
260             if (_CZipAskPassword(m_hWnd, NULL, Password) == eAccept)
261             {
262                 *m_pPassword = Password;
263             }
264             return 0;
265         }
266 
267         void UpdateDirectory()
268         {
269             GetDlgItemText(IDC_DIRECTORY, m_pExtract->m_Directory);
270             m_pExtract->m_DirectoryChanged = false;
271         }
272 
273     public:
274         enum { IDD = IDD_PROPPAGEDESTINATION };
275 
276         BEGIN_MSG_MAP(CCompleteSettingsPage)
277             COMMAND_ID_HANDLER(IDC_BROWSE, OnBrowse)
278             COMMAND_ID_HANDLER(IDC_PASSWORD, OnPassword)
279             COMMAND_HANDLER(IDC_DIRECTORY, EN_CHANGE, OnEnChangeDirectory)
280             CHAIN_MSG_MAP(CPropertyPageImpl<CExtractSettingsPage>)
281         END_MSG_MAP()
282     };
283 
284 
285     class CCompleteSettingsPage : public CPropertyPageImpl<CCompleteSettingsPage>
286     {
287     private:
288         CZipExtract* m_pExtract;
289 
290     public:
291         CCompleteSettingsPage(CZipExtract* extract)
292             :CPropertyPageImpl<CCompleteSettingsPage>(MAKEINTRESOURCE(IDS_WIZ_TITLE))
293             , m_pExtract(extract)
294         {
295             m_psp.pszHeaderTitle = MAKEINTRESOURCE(IDS_WIZ_COMPL_TITLE);
296             m_psp.pszHeaderSubTitle = MAKEINTRESOURCE(IDS_WIZ_COMPL_SUBTITLE);
297             m_psp.dwFlags |= PSP_USETITLE | PSP_USEHEADERTITLE | PSP_USEHEADERSUBTITLE;
298         }
299 
300 
301         int OnSetActive()
302         {
303             SetWizardButtons(PSWIZB_FINISH);
304             CStringW Path = m_pExtract->m_Directory;
305             PWSTR Ptr = Path.GetBuffer(MAX_PATH);
306             RECT rc;
307             ::GetWindowRect(GetDlgItem(IDC_DESTDIR), &rc);
308             HDC dc = GetDC();
309             PathCompactPathW(dc, Ptr, rc.right - rc.left);
310             ReleaseDC(dc);
311             Path.ReleaseBuffer();
312             SetDlgItemTextW(IDC_DESTDIR, Path);
313             CheckDlgButton(IDC_SHOW_EXTRACTED, BST_CHECKED);
314             return 0;
315         }
316         BOOL OnWizardFinish()
317         {
318             if (IsDlgButtonChecked(IDC_SHOW_EXTRACTED) == BST_CHECKED)
319             {
320                 ShellExecuteW(NULL, L"explore", m_pExtract->m_Directory, NULL, NULL, SW_SHOW);
321             }
322             return FALSE;
323         }
324 
325     public:
326         enum { IDD = IDD_PROPPAGECOMPLETE };
327 
328         BEGIN_MSG_MAP(CCompleteSettingsPage)
329             CHAIN_MSG_MAP(CPropertyPageImpl<CCompleteSettingsPage>)
330         END_MSG_MAP()
331     };
332 
333 
334     /* NOTE: This callback is needed to set large icon correctly. */
335     static INT CALLBACK s_PropSheetCallbackProc(HWND hwndDlg, UINT uMsg, LPARAM lParam)
336     {
337         if (uMsg == PSCB_INITIALIZED)
338         {
339             HICON hIcon = LoadIconW(_AtlBaseModule.GetResourceInstance(), MAKEINTRESOURCEW(IDI_ZIPFLDR));
340             CWindow dlg(hwndDlg);
341             dlg.SetIcon(hIcon, TRUE);
342         }
343 
344         return 0;
345     }
346 
347     void runWizard()
348     {
349         PROPSHEETHEADERW psh = { sizeof(psh), 0 };
350         psh.dwFlags = PSH_WIZARD97 | PSH_HEADER | PSH_USEICONID | PSH_USECALLBACK;
351         psh.hInstance = _AtlBaseModule.GetResourceInstance();
352 
353         CExtractSettingsPage extractPage(this, &m_Password);
354         CCompleteSettingsPage completePage(this);
355         HPROPSHEETPAGE hpsp[] =
356         {
357             extractPage.Create(),
358             completePage.Create()
359         };
360 
361         psh.phpage = hpsp;
362         psh.nPages = _countof(hpsp);
363         psh.pszIcon = MAKEINTRESOURCE(IDI_ZIPFLDR);
364         psh.pszbmWatermark = MAKEINTRESOURCE(IDB_WATERMARK);
365         psh.pszbmHeader = MAKEINTRESOURCE(IDB_HEADER);
366         psh.pfnCallback = s_PropSheetCallbackProc;
367 
368         PropertySheetW(&psh);
369     }
370 
371     eZipExtractError ExtractSingle(
372         HWND hDlg,
373         LPCSTR FullPath,
374         bool is_dir,
375         unz_file_info64* Info,
376         CStringA Name,
377         CStringA Password,
378         bool* bOverwriteAll,
379         const bool* bCancel,
380         int* ErrorCode
381     )
382     {
383         int err;
384         BYTE Buffer[2048];
385         DWORD dwFlags = SHPPFW_DIRCREATE | (is_dir ? SHPPFW_NONE : SHPPFW_IGNOREFILENAME);
386         HRESULT hr = SHPathPrepareForWriteA(hDlg, NULL, FullPath, dwFlags);
387         if (FAILED_UNEXPECTEDLY(hr))
388         {
389             *ErrorCode = hr;
390             return eDirectoryError;
391         }
392         if (is_dir)
393             return eNoError;
394 
395         if (Info->flag & MINIZIP_PASSWORD_FLAG)
396         {
397             eZipPasswordResponse Response = eAccept;
398             do
399             {
400                 /* If there is a password set, try it */
401                 if (!Password.IsEmpty())
402                 {
403                     err = unzOpenCurrentFilePassword(uf, Password);
404                     if (err == UNZ_OK)
405                     {
406                         /* Try to read some bytes, because unzOpenCurrentFilePassword does not return failure */
407                         char Buf[10];
408                         err = unzReadCurrentFile(uf, Buf, sizeof(Buf));
409                         unzCloseCurrentFile(uf);
410                         if (err >= UNZ_OK)
411                         {
412                             /* 're'-open the file so that we can begin to extract */
413                             err = unzOpenCurrentFilePassword(uf, Password);
414                             break;
415                         }
416                     }
417                 }
418                 Response = _CZipAskPassword(hDlg, Name, Password);
419             } while (Response == eAccept);
420 
421             if (Response == eSkip)
422             {
423                 return eNoError;
424             }
425             else if (Response == eAbort)
426             {
427                 return eExtractAbort;
428             }
429         }
430         else
431         {
432             err = unzOpenCurrentFile(uf);
433         }
434 
435         if (err != UNZ_OK)
436         {
437             DPRINT1("ERROR, unzOpenCurrentFilePassword: 0x%x\n", err);
438             *ErrorCode = err;
439             return eOpenError;
440         }
441 
442         HANDLE hFile = CreateFileA(FullPath, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
443         if (hFile == INVALID_HANDLE_VALUE)
444         {
445             DWORD dwErr = GetLastError();
446             if (dwErr == ERROR_FILE_EXISTS)
447             {
448                 bool bOverwrite = *bOverwriteAll;
449                 if (!*bOverwriteAll)
450                 {
451                     eZipConfirmResponse Result = _CZipAskReplace(hDlg, FullPath);
452                     switch (Result)
453                     {
454                     case eYesToAll:
455                         *bOverwriteAll = true;
456                         /* fall through */
457                     case eYes:
458                         bOverwrite = true;
459                         break;
460                     case eNo:
461                         break;
462                     case eCancel:
463                         unzCloseCurrentFile(uf);
464                         return eExtractAbort;
465                     }
466                 }
467 
468                 if (bOverwrite)
469                 {
470                     hFile = CreateFileA(FullPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
471                     if (hFile == INVALID_HANDLE_VALUE)
472                     {
473                         dwErr = GetLastError();
474                     }
475                 }
476                 else
477                 {
478                     unzCloseCurrentFile(uf);
479                     return eNoError;
480                 }
481             }
482             if (hFile == INVALID_HANDLE_VALUE)
483             {
484                 unzCloseCurrentFile(uf);
485                 DPRINT1("ERROR, CreateFileA: 0x%x (%s)\n", dwErr, *bOverwriteAll ? "Y" : "N");
486                 *ErrorCode = dwErr;
487                 return eFileError;
488             }
489         }
490 
491         do
492         {
493             if (*bCancel)
494             {
495                 CloseHandle(hFile);
496                 BOOL deleteResult = DeleteFileA(FullPath);
497                 if (!deleteResult)
498                     DPRINT1("ERROR, DeleteFileA: 0x%x\n", GetLastError());
499                 return eExtractAbort;
500             }
501 
502             err = unzReadCurrentFile(uf, Buffer, sizeof(Buffer));
503 
504             if (err < 0)
505             {
506                 DPRINT1("ERROR, unzReadCurrentFile: 0x%x\n", err);
507                 break;
508             }
509             else if (err > 0)
510             {
511                 DWORD dwWritten;
512                 if (!WriteFile(hFile, Buffer, err, &dwWritten, NULL))
513                 {
514                     DPRINT1("ERROR, WriteFile: 0x%x\n", GetLastError());
515                     break;
516                 }
517                 if (dwWritten != (DWORD)err)
518                 {
519                     DPRINT1("ERROR, WriteFile: dwWritten:%d err:%d\n", dwWritten, err);
520                     break;
521                 }
522             }
523 
524         } while (err > 0);
525 
526         /* Update Filetime */
527         FILETIME LocalFileTime;
528         DosDateTimeToFileTime((WORD)(Info->dosDate >> 16), (WORD)Info->dosDate, &LocalFileTime);
529         FILETIME FileTime;
530         LocalFileTimeToFileTime(&LocalFileTime, &FileTime);
531         SetFileTime(hFile, &FileTime, &FileTime, &FileTime);
532 
533         /* Done */
534         CloseHandle(hFile);
535 
536         if (err)
537         {
538             unzCloseCurrentFile(uf);
539             DPRINT1("ERROR, unzReadCurrentFile2: 0x%x\n", err);
540             *ErrorCode = err;
541             return eUnpackError;
542         }
543         else
544         {
545             err = unzCloseCurrentFile(uf);
546             if (err != UNZ_OK)
547             {
548                 DPRINT1("ERROR(non-fatal), unzCloseCurrentFile: 0x%x\n", err);
549             }
550         }
551         return eNoError;
552     }
553 
554     bool Extract(HWND hDlg, HWND hProgress, const bool* bCancel)
555     {
556         unz_global_info64 gi;
557         uf = unzOpen2_64(m_Filename.GetString(), &g_FFunc);
558         int err = unzGetGlobalInfo64(uf, &gi);
559         if (err != UNZ_OK)
560         {
561             DPRINT1("ERROR, unzGetGlobalInfo64: 0x%x\n", err);
562             Close();
563             return false;
564         }
565 
566         CZipEnumerator zipEnum;
567         if (!zipEnum.initialize(this))
568         {
569             DPRINT1("ERROR, zipEnum.initialize\n");
570             Close();
571             return false;
572         }
573 
574         CWindow Progress(hProgress);
575         Progress.SendMessage(PBM_SETRANGE32, 0, gi.number_entry);
576         Progress.SendMessage(PBM_SETPOS, 0, 0);
577 
578         CStringA BaseDirectory = m_Directory;
579         CStringA Name;
580         CStringA Password = m_Password;
581         unz_file_info64 Info;
582         int CurrentFile = 0;
583         bool bOverwriteAll = false;
584         while (zipEnum.next(Name, Info))
585         {
586             if (*bCancel)
587             {
588                 Close();
589                 return false;
590             }
591 
592             bool is_dir = Name.GetLength() > 0 && Name[Name.GetLength()-1] == '/';
593 
594             char CombinedPath[MAX_PATH * 2] = { 0 };
595             PathCombineA(CombinedPath, BaseDirectory, Name);
596             CStringA FullPath = CombinedPath;
597             FullPath.Replace('/', '\\');    /* SHPathPrepareForWriteA does not handle '/' */
598         Retry:
599             eZipExtractError Result = ExtractSingle(hDlg, FullPath, is_dir, &Info, Name, Password, &bOverwriteAll, bCancel, &err);
600             if (Result != eDirectoryError)
601                 CurrentFile++;
602             switch (Result)
603             {
604                 case eNoError:
605                     break;
606 
607                 case eExtractAbort:
608                 case eUnpackError:
609                 {
610                     Close();
611                     return false;
612                 }
613 
614                 case eDirectoryError:
615                 {
616                     char StrippedPath[MAX_PATH] = { 0 };
617 
618                     StrCpyNA(StrippedPath, FullPath, _countof(StrippedPath));
619                     if (!is_dir)
620                         PathRemoveFileSpecA(StrippedPath);
621                     PathStripPathA(StrippedPath);
622                     if (ShowExtractError(hDlg, (LPCSTR)&StrippedPath, err, eDirectoryError) == IDRETRY)
623                         goto Retry;
624                     Close();
625                     return false;
626                 }
627 
628                 case eFileError:
629                 {
630                     int Result = ShowExtractError(hDlg, FullPath, err, eFileError);
631                     switch (Result)
632                     {
633                     case IDABORT:
634                         Close();
635                         return false;
636                     case IDRETRY:
637                         CurrentFile--;
638                         goto Retry;
639                     case IDIGNORE:
640                         break;
641                     }
642                     break;
643                 }
644 
645                 case eOpenError:
646                 {
647                     if (err == UNZ_BADZIPFILE &&
648                         Info.compression_method != 0 &&
649                         Info.compression_method != Z_DEFLATED &&
650                         Info.compression_method != Z_BZIP2ED)
651                     {
652                         if (ShowExtractError(hDlg, FullPath, Info.compression_method, eOpenError) == IDYES)
653                             break;
654                     }
655                     Close();
656                     return false;
657                 }
658             }
659             if (Result == eNoError && is_dir)
660                 continue;
661             Progress.SendMessage(PBM_SETPOS, CurrentFile, 0);
662         }
663 
664         Close();
665         return true;
666     }
667 
668     int ShowExtractError(HWND hDlg, LPCSTR path, int Error, eZipExtractError ErrorType)
669     {
670         CStringA strTitle(MAKEINTRESOURCEW(IDS_ERRORTITLE));
671         CStringA strErr, strText;
672         PSTR Win32ErrorString;
673 
674         if (ErrorType == eFileError || ErrorType == eOpenError)
675             strText.LoadString(IDS_CANTEXTRACTFILE);
676         else
677             strText.LoadString(GetModuleHandleA("shell32.dll"), 128); // IDS_CREATEFOLDER_DENIED
678 
679         strText.FormatMessage(strText.GetString(), path);
680 
681         if (ErrorType == eFileError || HRESULT_FACILITY(Error) == FACILITY_WIN32)
682         {
683             if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
684                                NULL, ErrorType == eFileError ? Error : HRESULT_CODE(Error), 0,
685                                (PSTR)&Win32ErrorString, 0, NULL) != 0)
686             {
687                 strErr.SetString(Win32ErrorString);
688                 LocalFree(Win32ErrorString);
689             }
690         }
691         if (ErrorType == eOpenError)
692             strErr.Format(IDS_DECOMPRESSERROR, Error);
693         else if (strErr.GetLength() == 0)
694             strErr.Format(IDS_UNKNOWNERROR, Error);
695 
696         strText.Append("\r\n\r\n" + strErr);
697 
698         UINT mbFlags = MB_ICONWARNING;
699         if (ErrorType == eDirectoryError)
700             mbFlags |= MB_RETRYCANCEL;
701         else if (ErrorType == eFileError)
702             mbFlags |= MB_ABORTRETRYIGNORE;
703         else if (ErrorType == eOpenError)
704             mbFlags |= MB_YESNO;
705 
706         return MessageBoxA(hDlg, strText, strTitle, mbFlags);
707     }
708 };
709 
710 
711 void _CZipExtract_runWizard(PCWSTR Filename)
712 {
713     CZipExtract extractor(Filename);
714     extractor.runWizard();
715 }
716 
717