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 LocalFileTime; 537 DosDateTimeToFileTime((WORD)(Info.dosDate >> 16), (WORD)Info.dosDate, &LocalFileTime); 538 FILETIME FileTime; 539 LocalFileTimeToFileTime(&LocalFileTime, &FileTime); 540 SetFileTime(hFile, &FileTime, &FileTime, &FileTime); 541 542 /* Done */ 543 CloseHandle(hFile); 544 545 if (err) 546 { 547 unzCloseCurrentFile(uf); 548 DPRINT1("ERROR, unzReadCurrentFile2: 0x%x\n", err); 549 Close(); 550 return false; 551 } 552 else 553 { 554 err = unzCloseCurrentFile(uf); 555 if (err != UNZ_OK) 556 { 557 DPRINT1("ERROR(non-fatal), unzCloseCurrentFile: 0x%x\n", err); 558 } 559 } 560 Progress.SendMessage(PBM_SETPOS, CurrentFile, 0); 561 } 562 563 Close(); 564 return true; 565 } 566 }; 567 568 569 void _CZipExtract_runWizard(PCWSTR Filename) 570 { 571 CZipExtract extractor(Filename); 572 extractor.runWizard(); 573 } 574 575