[SHELLEXT][MYDOCS][INF] Add mydocs.dll and .mydocs file extension (#2624)
[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();
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
342 PropertySheetW(&psh);
343 }
344
345 bool Extract(HWND hDlg, HWND hProgress, const bool* bCancel)
346 {
347 unz_global_info64 gi;
348 uf = unzOpen2_64(m_Filename.GetString(), &g_FFunc);
349 int err = unzGetGlobalInfo64(uf, &gi);
350 if (err != UNZ_OK)
351 {
352 DPRINT1("ERROR, unzGetGlobalInfo64: 0x%x\n", err);
353 Close();
354 return false;
355 }
356
357 CZipEnumerator zipEnum;
358 if (!zipEnum.initialize(this))
359 {
360 DPRINT1("ERROR, zipEnum.initialize\n");
361 Close();
362 return false;
363 }
364
365 CWindow Progress(hProgress);
366 Progress.SendMessage(PBM_SETRANGE32, 0, gi.number_entry);
367 Progress.SendMessage(PBM_SETPOS, 0, 0);
368
369 BYTE Buffer[2048];
370 CStringA BaseDirectory = m_Directory;
371 CStringA Name;
372 CStringA Password = m_Password;
373 unz_file_info64 Info;
374 int CurrentFile = 0;
375 bool bOverwriteAll = false;
376 while (zipEnum.next(Name, Info))
377 {
378 if (*bCancel)
379 {
380 Close();
381 return false;
382 }
383
384 bool is_dir = Name.GetLength() > 0 && Name[Name.GetLength()-1] == '/';
385
386 char CombinedPath[MAX_PATH * 2] = { 0 };
387 PathCombineA(CombinedPath, BaseDirectory, Name);
388 CStringA FullPath = CombinedPath;
389 FullPath.Replace('/', '\\'); /* SHPathPrepareForWriteA does not handle '/' */
390 DWORD dwFlags = SHPPFW_DIRCREATE | (is_dir ? SHPPFW_NONE : SHPPFW_IGNOREFILENAME);
391 HRESULT hr = SHPathPrepareForWriteA(hDlg, NULL, FullPath, dwFlags);
392 if (FAILED_UNEXPECTEDLY(hr))
393 {
394 Close();
395 return false;
396 }
397 CurrentFile++;
398 if (is_dir)
399 continue;
400
401 if (Info.flag & MINIZIP_PASSWORD_FLAG)
402 {
403 eZipPasswordResponse Response = eAccept;
404 do
405 {
406 /* If there is a password set, try it */
407 if (!Password.IsEmpty())
408 {
409 err = unzOpenCurrentFilePassword(uf, Password);
410 if (err == UNZ_OK)
411 {
412 /* Try to read some bytes, because unzOpenCurrentFilePassword does not return failure */
413 char Buf[10];
414 err = unzReadCurrentFile(uf, Buf, sizeof(Buf));
415 unzCloseCurrentFile(uf);
416 if (err >= UNZ_OK)
417 {
418 /* 're'-open the file so that we can begin to extract */
419 err = unzOpenCurrentFilePassword(uf, Password);
420 break;
421 }
422 }
423 }
424 Response = _CZipAskPassword(hDlg, Name, Password);
425 } while (Response == eAccept);
426
427 if (Response == eSkip)
428 {
429 Progress.SendMessage(PBM_SETPOS, CurrentFile, 0);
430 continue;
431 }
432 else if (Response == eAbort)
433 {
434 Close();
435 return false;
436 }
437 }
438 else
439 {
440 err = unzOpenCurrentFile(uf);
441 }
442
443 if (err != UNZ_OK)
444 {
445 DPRINT1("ERROR, unzOpenCurrentFilePassword: 0x%x\n", err);
446 Close();
447 return false;
448 }
449
450 HANDLE hFile = CreateFileA(FullPath, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
451 if (hFile == INVALID_HANDLE_VALUE)
452 {
453 DWORD dwErr = GetLastError();
454 if (dwErr == ERROR_FILE_EXISTS)
455 {
456 bool bOverwrite = bOverwriteAll;
457 if (!bOverwriteAll)
458 {
459 eZipConfirmResponse Result = _CZipAskReplace(hDlg, FullPath);
460 switch (Result)
461 {
462 case eYesToAll:
463 bOverwriteAll = true;
464 case eYes:
465 bOverwrite = true;
466 break;
467 case eNo:
468 break;
469 case eCancel:
470 unzCloseCurrentFile(uf);
471 Close();
472 return false;
473 }
474 }
475
476 if (bOverwrite)
477 {
478 hFile = CreateFileA(FullPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
479 if (hFile == INVALID_HANDLE_VALUE)
480 {
481 dwErr = GetLastError();
482 }
483 }
484 else
485 {
486 unzCloseCurrentFile(uf);
487 continue;
488 }
489 }
490 if (hFile == INVALID_HANDLE_VALUE)
491 {
492 unzCloseCurrentFile(uf);
493 DPRINT1("ERROR, CreateFileA: 0x%x (%s)\n", dwErr, bOverwriteAll ? "Y" : "N");
494 Close();
495 return false;
496 }
497 }
498
499 do
500 {
501 if (*bCancel)
502 {
503 CloseHandle(hFile);
504 BOOL deleteResult = DeleteFileA(FullPath);
505 if (deleteResult == 0)
506 DPRINT1("ERROR, DeleteFileA: 0x%x\n", GetLastError());
507 Close();
508 return false;
509 }
510
511 err = unzReadCurrentFile(uf, Buffer, sizeof(Buffer));
512
513 if (err < 0)
514 {
515 DPRINT1("ERROR, unzReadCurrentFile: 0x%x\n", err);
516 break;
517 }
518 else if (err > 0)
519 {
520 DWORD dwWritten;
521 if (!WriteFile(hFile, Buffer, err, &dwWritten, NULL))
522 {
523 DPRINT1("ERROR, WriteFile: 0x%x\n", GetLastError());
524 break;
525 }
526 if (dwWritten != (DWORD)err)
527 {
528 DPRINT1("ERROR, WriteFile: dwWritten:%d err:%d\n", dwWritten, err);
529 break;
530 }
531 }
532
533 } while (err > 0);
534
535 /* Update Filetime */
536 FILETIME LastAccessTime;
537 GetFileTime(hFile, NULL, &LastAccessTime, NULL);
538 FILETIME LocalFileTime;
539 DosDateTimeToFileTime((WORD)(Info.dosDate >> 16), (WORD)Info.dosDate, &LocalFileTime);
540 FILETIME FileTime;
541 LocalFileTimeToFileTime(&LocalFileTime, &FileTime);
542 SetFileTime(hFile, &FileTime, &LastAccessTime, &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