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 577