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 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 bool m_DirectoryChanged; 16 unzFile uf; 17 public: 18 CZipExtract(PCWSTR Filename) 19 :m_DirectoryChanged(false) 20 ,uf(NULL) 21 { 22 m_Filename = Filename; 23 m_Directory = m_Filename; 24 PWSTR Dir = m_Directory.GetBuffer(); 25 PathRemoveExtensionW(Dir); 26 m_Directory.ReleaseBuffer(); 27 } 28 29 ~CZipExtract() 30 { 31 if (uf) 32 { 33 DPRINT1("WARNING: uf not closed!\n"); 34 Close(); 35 } 36 } 37 38 void Close() 39 { 40 if (uf) 41 unzClose(uf); 42 uf = NULL; 43 } 44 45 // *** IZip methods *** 46 STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) 47 { 48 if (riid == IID_IUnknown) 49 { 50 *ppvObject = this; 51 AddRef(); 52 return S_OK; 53 } 54 return E_NOINTERFACE; 55 } 56 STDMETHODIMP_(ULONG) AddRef(void) 57 { 58 return 2; 59 } 60 STDMETHODIMP_(ULONG) Release(void) 61 { 62 return 1; 63 } 64 STDMETHODIMP_(unzFile) getZip() 65 { 66 return uf; 67 } 68 69 class CConfirmReplace : public CDialogImpl<CConfirmReplace> 70 { 71 private: 72 CStringA m_Filename; 73 public: 74 enum DialogResult 75 { 76 Yes, 77 YesToAll, 78 No, 79 Cancel 80 }; 81 82 static DialogResult ShowDlg(HWND hDlg, PCSTR FullPath) 83 { 84 PCSTR Filename = PathFindFileNameA(FullPath); 85 CConfirmReplace confirm(Filename); 86 INT_PTR Result = confirm.DoModal(hDlg); 87 switch (Result) 88 { 89 case IDYES: return Yes; 90 case IDYESALL: return YesToAll; 91 default: 92 case IDNO: return No; 93 case IDCANCEL: return Cancel; 94 } 95 } 96 97 CConfirmReplace(const char* filename) 98 { 99 m_Filename = filename; 100 } 101 102 LRESULT OnInitDialog(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) 103 { 104 CenterWindow(GetParent()); 105 106 HICON hIcon = LoadIcon(NULL, IDI_EXCLAMATION); 107 SendDlgItemMessage(IDC_EXCLAMATION_ICON, STM_SETICON, (WPARAM)hIcon); 108 109 /* Our CString does not support FormatMessage yet */ 110 CStringA message(MAKEINTRESOURCE(IDS_OVERWRITEFILE_TEXT)); 111 CHeapPtr<CHAR, CLocalAllocator> formatted; 112 113 DWORD_PTR args[2] = 114 { 115 (DWORD_PTR)m_Filename.GetString(), 116 NULL 117 }; 118 119 ::FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY, 120 message, 0, 0, (LPSTR)&formatted, 0, (va_list*)args); 121 122 ::SetDlgItemTextA(m_hWnd, IDC_MESSAGE, formatted); 123 return 0; 124 } 125 126 LRESULT OnButton(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) 127 { 128 EndDialog(wID); 129 return 0; 130 } 131 132 public: 133 enum { IDD = IDD_CONFIRM_FILE_REPLACE }; 134 135 BEGIN_MSG_MAP(CConfirmReplace) 136 MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) 137 COMMAND_ID_HANDLER(IDYES, OnButton) 138 COMMAND_ID_HANDLER(IDYESALL, OnButton) 139 COMMAND_ID_HANDLER(IDNO, OnButton) 140 COMMAND_ID_HANDLER(IDCANCEL, OnButton) 141 END_MSG_MAP() 142 }; 143 144 145 class CExtractSettingsPage : public CPropertyPageImpl<CExtractSettingsPage> 146 { 147 private: 148 CZipExtract* m_pExtract; 149 150 public: 151 CExtractSettingsPage(CZipExtract* extract) 152 :CPropertyPageImpl<CExtractSettingsPage>(MAKEINTRESOURCE(IDS_WIZ_TITLE)) 153 ,m_pExtract(extract) 154 { 155 m_psp.pszHeaderTitle = MAKEINTRESOURCE(IDS_WIZ_DEST_TITLE); 156 m_psp.pszHeaderSubTitle = MAKEINTRESOURCE(IDS_WIZ_DEST_SUBTITLE); 157 m_psp.dwFlags |= PSP_USETITLE | PSP_USEHEADERTITLE | PSP_USEHEADERSUBTITLE; 158 } 159 160 int OnSetActive() 161 { 162 SetDlgItemTextW(IDC_DIRECTORY, m_pExtract->m_Directory); 163 m_pExtract->m_DirectoryChanged = false; 164 ::EnableWindow(GetDlgItem(IDC_PASSWORD), FALSE); /* Not supported for now */ 165 GetParent().CenterWindow(::GetDesktopWindow()); 166 return 0; 167 } 168 169 int OnWizardNext() 170 { 171 ::EnableWindow(GetDlgItem(IDC_BROWSE), FALSE); 172 ::EnableWindow(GetDlgItem(IDC_DIRECTORY), FALSE); 173 ::EnableWindow(GetDlgItem(IDC_PASSWORD), FALSE); 174 175 if (m_pExtract->m_DirectoryChanged) 176 UpdateDirectory(); 177 178 if (!m_pExtract->Extract(m_hWnd, GetDlgItem(IDC_PROGRESS))) 179 { 180 /* Extraction failed, do not go to the next page */ 181 SetWindowLongPtr(DWLP_MSGRESULT, -1); 182 183 ::EnableWindow(GetDlgItem(IDC_BROWSE), TRUE); 184 ::EnableWindow(GetDlgItem(IDC_DIRECTORY), TRUE); 185 ::EnableWindow(GetDlgItem(IDC_PASSWORD), FALSE); /* Not supported for now */ 186 187 return TRUE; 188 } 189 return 0; 190 } 191 192 struct browse_info 193 { 194 HWND hWnd; 195 LPCWSTR Directory; 196 }; 197 198 static INT CALLBACK s_BrowseCallbackProc(HWND hWnd, UINT uMsg, LPARAM lp, LPARAM pData) 199 { 200 if (uMsg == BFFM_INITIALIZED) 201 { 202 browse_info* info = (browse_info*)pData; 203 CWindow dlg(hWnd); 204 dlg.SendMessage(BFFM_SETSELECTION, TRUE, (LPARAM)info->Directory); 205 dlg.CenterWindow(info->hWnd); 206 } 207 return 0; 208 } 209 210 LRESULT OnBrowse(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) 211 { 212 BROWSEINFOW bi = { m_hWnd }; 213 WCHAR path[MAX_PATH]; 214 bi.pszDisplayName = path; 215 bi.lpfn = s_BrowseCallbackProc; 216 bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS; 217 CStringW title(MAKEINTRESOURCEW(IDS_WIZ_BROWSE_TITLE)); 218 bi.lpszTitle = title; 219 220 if (m_pExtract->m_DirectoryChanged) 221 UpdateDirectory(); 222 223 browse_info info = { m_hWnd, m_pExtract->m_Directory.GetString() }; 224 bi.lParam = (LPARAM)&info; 225 226 CComHeapPtr<ITEMIDLIST> pidl; 227 pidl.Attach(SHBrowseForFolderW(&bi)); 228 229 WCHAR tmpPath[MAX_PATH]; 230 if (pidl && SHGetPathFromIDListW(pidl, tmpPath)) 231 { 232 m_pExtract->m_Directory = tmpPath; 233 SetDlgItemTextW(IDC_DIRECTORY, m_pExtract->m_Directory); 234 m_pExtract->m_DirectoryChanged = false; 235 } 236 return 0; 237 } 238 239 LRESULT OnEnChangeDirectory(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) 240 { 241 m_pExtract->m_DirectoryChanged = true; 242 return 0; 243 } 244 245 LRESULT OnPassword(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) 246 { 247 return 0; 248 } 249 250 void UpdateDirectory() 251 { 252 GetDlgItemText(IDC_DIRECTORY, m_pExtract->m_Directory); 253 m_pExtract->m_DirectoryChanged = false; 254 } 255 256 public: 257 enum { IDD = IDD_PROPPAGEDESTINATION }; 258 259 BEGIN_MSG_MAP(CCompleteSettingsPage) 260 COMMAND_ID_HANDLER(IDC_BROWSE, OnBrowse) 261 COMMAND_ID_HANDLER(IDC_PASSWORD, OnPassword) 262 COMMAND_HANDLER(IDC_DIRECTORY, EN_CHANGE, OnEnChangeDirectory) 263 CHAIN_MSG_MAP(CPropertyPageImpl<CExtractSettingsPage>) 264 END_MSG_MAP() 265 }; 266 267 268 class CCompleteSettingsPage : public CPropertyPageImpl<CCompleteSettingsPage> 269 { 270 private: 271 CZipExtract* m_pExtract; 272 273 public: 274 CCompleteSettingsPage(CZipExtract* extract) 275 :CPropertyPageImpl<CCompleteSettingsPage>(MAKEINTRESOURCE(IDS_WIZ_TITLE)) 276 , m_pExtract(extract) 277 { 278 m_psp.pszHeaderTitle = MAKEINTRESOURCE(IDS_WIZ_COMPL_TITLE); 279 m_psp.pszHeaderSubTitle = MAKEINTRESOURCE(IDS_WIZ_COMPL_SUBTITLE); 280 m_psp.dwFlags |= PSP_USETITLE | PSP_USEHEADERTITLE | PSP_USEHEADERSUBTITLE; 281 } 282 283 284 int OnSetActive() 285 { 286 SetWizardButtons(PSWIZB_FINISH); 287 CStringW Path = m_pExtract->m_Directory; 288 PWSTR Ptr = Path.GetBuffer(); 289 RECT rc; 290 ::GetWindowRect(GetDlgItem(IDC_DESTDIR), &rc); 291 HDC dc = GetDC(); 292 PathCompactPathW(dc, Ptr, rc.right - rc.left); 293 ReleaseDC(dc); 294 Path.ReleaseBuffer(); 295 SetDlgItemTextW(IDC_DESTDIR, Path); 296 CheckDlgButton(IDC_SHOW_EXTRACTED, BST_CHECKED); 297 return 0; 298 } 299 BOOL OnWizardFinish() 300 { 301 if (IsDlgButtonChecked(IDC_SHOW_EXTRACTED) == BST_CHECKED) 302 { 303 ShellExecuteW(NULL, L"explore", m_pExtract->m_Directory, NULL, NULL, SW_SHOW); 304 } 305 return FALSE; 306 } 307 308 public: 309 enum { IDD = IDD_PROPPAGECOMPLETE }; 310 311 BEGIN_MSG_MAP(CCompleteSettingsPage) 312 CHAIN_MSG_MAP(CPropertyPageImpl<CCompleteSettingsPage>) 313 END_MSG_MAP() 314 }; 315 316 317 void runWizard() 318 { 319 PROPSHEETHEADERW psh = { sizeof(psh), 0 }; 320 psh.dwFlags = PSH_WIZARD97 | PSH_HEADER; 321 psh.hInstance = _AtlBaseModule.GetResourceInstance(); 322 323 CExtractSettingsPage extractPage(this); 324 CCompleteSettingsPage completePage(this); 325 HPROPSHEETPAGE hpsp[] = 326 { 327 extractPage.Create(), 328 completePage.Create() 329 }; 330 331 psh.phpage = hpsp; 332 psh.nPages = _countof(hpsp); 333 334 PropertySheetW(&psh); 335 } 336 337 bool Extract(HWND hDlg, HWND hProgress) 338 { 339 unz_global_info64 gi; 340 uf = unzOpen2_64(m_Filename.GetString(), &g_FFunc); 341 int err = unzGetGlobalInfo64(uf, &gi); 342 if (err != UNZ_OK) 343 { 344 DPRINT1("ERROR, unzGetGlobalInfo64: 0x%x\n", err); 345 Close(); 346 return false; 347 } 348 349 CZipEnumerator zipEnum; 350 if (!zipEnum.initialize(this)) 351 { 352 DPRINT1("ERROR, zipEnum.initialize\n"); 353 Close(); 354 return false; 355 } 356 357 CWindow Progress(hProgress); 358 Progress.SendMessage(PBM_SETRANGE32, 0, gi.number_entry); 359 Progress.SendMessage(PBM_SETPOS, 0, 0); 360 361 BYTE Buffer[2048]; 362 CStringA BaseDirectory = m_Directory; 363 CStringA Name; 364 unz_file_info64 Info; 365 int CurrentFile = 0; 366 bool bOverwriteAll = false; 367 while (zipEnum.next(Name, Info)) 368 { 369 bool is_dir = Name.GetLength() > 0 && Name[Name.GetLength()-1] == '/'; 370 371 char CombinedPath[MAX_PATH * 2] = { 0 }; 372 PathCombineA(CombinedPath, BaseDirectory, Name); 373 CStringA FullPath = CombinedPath; 374 FullPath.Replace('/', '\\'); /* SHPathPrepareForWriteA does not handle '/' */ 375 DWORD dwFlags = SHPPFW_DIRCREATE | (is_dir ? SHPPFW_NONE : SHPPFW_IGNOREFILENAME); 376 HRESULT hr = SHPathPrepareForWriteA(hDlg, NULL, FullPath, dwFlags); 377 if (FAILED_UNEXPECTEDLY(hr)) 378 { 379 Close(); 380 return false; 381 } 382 CurrentFile++; 383 if (is_dir) 384 continue; 385 386 const char* password = NULL; 387 /* FIXME: Process password, if required and not specified, prompt the user */ 388 err = unzOpenCurrentFilePassword(uf, password); 389 if (err != UNZ_OK) 390 { 391 DPRINT1("ERROR, unzOpenCurrentFilePassword: 0x%x\n", err); 392 Close(); 393 return false; 394 } 395 396 HANDLE hFile = CreateFileA(FullPath, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); 397 if (hFile == INVALID_HANDLE_VALUE) 398 { 399 DWORD dwErr = GetLastError(); 400 if (dwErr == ERROR_FILE_EXISTS) 401 { 402 bool bOverwrite = bOverwriteAll; 403 if (!bOverwriteAll) 404 { 405 CConfirmReplace::DialogResult Result = CConfirmReplace::ShowDlg(hDlg, FullPath); 406 switch (Result) 407 { 408 case CConfirmReplace::YesToAll: 409 bOverwriteAll = true; 410 case CConfirmReplace::Yes: 411 bOverwrite = true; 412 break; 413 case CConfirmReplace::No: 414 break; 415 case CConfirmReplace::Cancel: 416 unzCloseCurrentFile(uf); 417 Close(); 418 return false; 419 } 420 } 421 422 if (bOverwrite) 423 { 424 hFile = CreateFileA(FullPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 425 if (hFile == INVALID_HANDLE_VALUE) 426 { 427 dwErr = GetLastError(); 428 } 429 } 430 else 431 { 432 unzCloseCurrentFile(uf); 433 continue; 434 } 435 } 436 if (hFile == INVALID_HANDLE_VALUE) 437 { 438 unzCloseCurrentFile(uf); 439 DPRINT1("ERROR, CreateFileA: 0x%x (%s)\n", dwErr, bOverwriteAll ? "Y" : "N"); 440 Close(); 441 return false; 442 } 443 } 444 445 do 446 { 447 err = unzReadCurrentFile(uf, Buffer, sizeof(Buffer)); 448 449 if (err < 0) 450 { 451 DPRINT1("ERROR, unzReadCurrentFile: 0x%x\n", err); 452 break; 453 } 454 else if (err > 0) 455 { 456 DWORD dwWritten; 457 if (!WriteFile(hFile, Buffer, err, &dwWritten, NULL)) 458 { 459 DPRINT1("ERROR, WriteFile: 0x%x\n", GetLastError()); 460 break; 461 } 462 if (dwWritten != (DWORD)err) 463 { 464 DPRINT1("ERROR, WriteFile: dwWritten:%d err:%d\n", dwWritten, err); 465 break; 466 } 467 } 468 469 } while (err > 0); 470 471 /* Update Filetime */ 472 FILETIME LastAccessTime; 473 GetFileTime(hFile, NULL, &LastAccessTime, NULL); 474 FILETIME LocalFileTime; 475 DosDateTimeToFileTime((WORD)(Info.dosDate >> 16), (WORD)Info.dosDate, &LocalFileTime); 476 FILETIME FileTime; 477 LocalFileTimeToFileTime(&LocalFileTime, &FileTime); 478 SetFileTime(hFile, &FileTime, &LastAccessTime, &FileTime); 479 480 /* Done.. */ 481 CloseHandle(hFile); 482 483 if (err) 484 { 485 unzCloseCurrentFile(uf); 486 DPRINT1("ERROR, unzReadCurrentFile2: 0x%x\n", err); 487 Close(); 488 return false; 489 } 490 else 491 { 492 err = unzCloseCurrentFile(uf); 493 if (err != UNZ_OK) 494 { 495 DPRINT1("ERROR(non-fatal), unzCloseCurrentFile: 0x%x\n", err); 496 } 497 } 498 Progress.SendMessage(PBM_SETPOS, CurrentFile, 0); 499 } 500 501 Close(); 502 return true; 503 } 504 }; 505 506 507 void _CZipExtract_runWizard(PCWSTR Filename) 508 { 509 CZipExtract extractor(Filename); 510 extractor.runWizard(); 511 } 512 513