a95f8ba10849939f77be0599490c68f9df6ab1fa
[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 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 CExtractSettingsPage : public CPropertyPageImpl<CExtractSettingsPage>
70 {
71 private:
72 CZipExtract* m_pExtract;
73
74 public:
75 CExtractSettingsPage(CZipExtract* extract)
76 :CPropertyPageImpl<CExtractSettingsPage>(MAKEINTRESOURCE(IDS_WIZ_TITLE))
77 ,m_pExtract(extract)
78 {
79 m_psp.pszHeaderTitle = MAKEINTRESOURCE(IDS_WIZ_DEST_TITLE);
80 m_psp.pszHeaderSubTitle = MAKEINTRESOURCE(IDS_WIZ_DEST_SUBTITLE);
81 m_psp.dwFlags |= PSP_USETITLE | PSP_USEHEADERTITLE | PSP_USEHEADERSUBTITLE;
82 }
83
84 int OnSetActive()
85 {
86 SetDlgItemTextW(IDC_DIRECTORY, m_pExtract->m_Directory);
87 m_pExtract->m_DirectoryChanged = false;
88 ::EnableWindow(GetDlgItem(IDC_PASSWORD), FALSE); /* Not supported for now */
89 GetParent().CenterWindow(::GetDesktopWindow());
90 SetWizardButtons(PSWIZB_NEXT);
91 return 0;
92 }
93
94 int OnWizardNext()
95 {
96 ::EnableWindow(GetDlgItem(IDC_BROWSE), FALSE);
97 ::EnableWindow(GetDlgItem(IDC_DIRECTORY), FALSE);
98 ::EnableWindow(GetDlgItem(IDC_PASSWORD), FALSE);
99 SetWizardButtons(0);
100
101 if (m_pExtract->m_DirectoryChanged)
102 UpdateDirectory();
103
104 if (!m_pExtract->Extract(m_hWnd, GetDlgItem(IDC_PROGRESS)))
105 {
106 /* Extraction failed, do not go to the next page */
107 SetWindowLongPtr(DWLP_MSGRESULT, -1);
108
109 ::EnableWindow(GetDlgItem(IDC_BROWSE), TRUE);
110 ::EnableWindow(GetDlgItem(IDC_DIRECTORY), TRUE);
111 ::EnableWindow(GetDlgItem(IDC_PASSWORD), FALSE); /* Not supported for now */
112 SetWizardButtons(PSWIZB_NEXT);
113
114 return TRUE;
115 }
116 return 0;
117 }
118
119 struct browse_info
120 {
121 HWND hWnd;
122 LPCWSTR Directory;
123 };
124
125 static INT CALLBACK s_BrowseCallbackProc(HWND hWnd, UINT uMsg, LPARAM lp, LPARAM pData)
126 {
127 if (uMsg == BFFM_INITIALIZED)
128 {
129 browse_info* info = (browse_info*)pData;
130 CWindow dlg(hWnd);
131 dlg.SendMessage(BFFM_SETSELECTION, TRUE, (LPARAM)info->Directory);
132 dlg.CenterWindow(info->hWnd);
133 }
134 return 0;
135 }
136
137 LRESULT OnBrowse(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
138 {
139 BROWSEINFOW bi = { m_hWnd };
140 WCHAR path[MAX_PATH];
141 bi.pszDisplayName = path;
142 bi.lpfn = s_BrowseCallbackProc;
143 bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
144 CStringW title(MAKEINTRESOURCEW(IDS_WIZ_BROWSE_TITLE));
145 bi.lpszTitle = title;
146
147 if (m_pExtract->m_DirectoryChanged)
148 UpdateDirectory();
149
150 browse_info info = { m_hWnd, m_pExtract->m_Directory.GetString() };
151 bi.lParam = (LPARAM)&info;
152
153 CComHeapPtr<ITEMIDLIST> pidl;
154 pidl.Attach(SHBrowseForFolderW(&bi));
155
156 WCHAR tmpPath[MAX_PATH];
157 if (pidl && SHGetPathFromIDListW(pidl, tmpPath))
158 {
159 m_pExtract->m_Directory = tmpPath;
160 SetDlgItemTextW(IDC_DIRECTORY, m_pExtract->m_Directory);
161 m_pExtract->m_DirectoryChanged = false;
162 }
163 return 0;
164 }
165
166 LRESULT OnEnChangeDirectory(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
167 {
168 m_pExtract->m_DirectoryChanged = true;
169 return 0;
170 }
171
172 LRESULT OnPassword(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
173 {
174 return 0;
175 }
176
177 void UpdateDirectory()
178 {
179 GetDlgItemText(IDC_DIRECTORY, m_pExtract->m_Directory);
180 m_pExtract->m_DirectoryChanged = false;
181 }
182
183 public:
184 enum { IDD = IDD_PROPPAGEDESTINATION };
185
186 BEGIN_MSG_MAP(CCompleteSettingsPage)
187 COMMAND_ID_HANDLER(IDC_BROWSE, OnBrowse)
188 COMMAND_ID_HANDLER(IDC_PASSWORD, OnPassword)
189 COMMAND_HANDLER(IDC_DIRECTORY, EN_CHANGE, OnEnChangeDirectory)
190 CHAIN_MSG_MAP(CPropertyPageImpl<CExtractSettingsPage>)
191 END_MSG_MAP()
192 };
193
194
195 class CCompleteSettingsPage : public CPropertyPageImpl<CCompleteSettingsPage>
196 {
197 private:
198 CZipExtract* m_pExtract;
199
200 public:
201 CCompleteSettingsPage(CZipExtract* extract)
202 :CPropertyPageImpl<CCompleteSettingsPage>(MAKEINTRESOURCE(IDS_WIZ_TITLE))
203 , m_pExtract(extract)
204 {
205 m_psp.pszHeaderTitle = MAKEINTRESOURCE(IDS_WIZ_COMPL_TITLE);
206 m_psp.pszHeaderSubTitle = MAKEINTRESOURCE(IDS_WIZ_COMPL_SUBTITLE);
207 m_psp.dwFlags |= PSP_USETITLE | PSP_USEHEADERTITLE | PSP_USEHEADERSUBTITLE;
208 }
209
210
211 int OnSetActive()
212 {
213 SetWizardButtons(PSWIZB_FINISH);
214 CStringW Path = m_pExtract->m_Directory;
215 PWSTR Ptr = Path.GetBuffer();
216 RECT rc;
217 ::GetWindowRect(GetDlgItem(IDC_DESTDIR), &rc);
218 HDC dc = GetDC();
219 PathCompactPathW(dc, Ptr, rc.right - rc.left);
220 ReleaseDC(dc);
221 Path.ReleaseBuffer();
222 SetDlgItemTextW(IDC_DESTDIR, Path);
223 CheckDlgButton(IDC_SHOW_EXTRACTED, BST_CHECKED);
224 return 0;
225 }
226 BOOL OnWizardFinish()
227 {
228 if (IsDlgButtonChecked(IDC_SHOW_EXTRACTED) == BST_CHECKED)
229 {
230 ShellExecuteW(NULL, L"explore", m_pExtract->m_Directory, NULL, NULL, SW_SHOW);
231 }
232 return FALSE;
233 }
234
235 public:
236 enum { IDD = IDD_PROPPAGECOMPLETE };
237
238 BEGIN_MSG_MAP(CCompleteSettingsPage)
239 CHAIN_MSG_MAP(CPropertyPageImpl<CCompleteSettingsPage>)
240 END_MSG_MAP()
241 };
242
243
244 void runWizard()
245 {
246 PROPSHEETHEADERW psh = { sizeof(psh), 0 };
247 psh.dwFlags = PSH_WIZARD97 | PSH_HEADER;
248 psh.hInstance = _AtlBaseModule.GetResourceInstance();
249
250 CExtractSettingsPage extractPage(this);
251 CCompleteSettingsPage completePage(this);
252 HPROPSHEETPAGE hpsp[] =
253 {
254 extractPage.Create(),
255 completePage.Create()
256 };
257
258 psh.phpage = hpsp;
259 psh.nPages = _countof(hpsp);
260
261 PropertySheetW(&psh);
262 }
263
264 bool Extract(HWND hDlg, HWND hProgress)
265 {
266 unz_global_info64 gi;
267 uf = unzOpen2_64(m_Filename.GetString(), &g_FFunc);
268 int err = unzGetGlobalInfo64(uf, &gi);
269 if (err != UNZ_OK)
270 {
271 DPRINT1("ERROR, unzGetGlobalInfo64: 0x%x\n", err);
272 Close();
273 return false;
274 }
275
276 CZipEnumerator zipEnum;
277 if (!zipEnum.initialize(this))
278 {
279 DPRINT1("ERROR, zipEnum.initialize\n");
280 Close();
281 return false;
282 }
283
284 CWindow Progress(hProgress);
285 Progress.SendMessage(PBM_SETRANGE32, 0, gi.number_entry);
286 Progress.SendMessage(PBM_SETPOS, 0, 0);
287
288 BYTE Buffer[2048];
289 CStringA BaseDirectory = m_Directory;
290 CStringA Name;
291 unz_file_info64 Info;
292 int CurrentFile = 0;
293 bool bOverwriteAll = false;
294 while (zipEnum.next(Name, Info))
295 {
296 bool is_dir = Name.GetLength() > 0 && Name[Name.GetLength()-1] == '/';
297
298 char CombinedPath[MAX_PATH * 2] = { 0 };
299 PathCombineA(CombinedPath, BaseDirectory, Name);
300 CStringA FullPath = CombinedPath;
301 FullPath.Replace('/', '\\'); /* SHPathPrepareForWriteA does not handle '/' */
302 DWORD dwFlags = SHPPFW_DIRCREATE | (is_dir ? SHPPFW_NONE : SHPPFW_IGNOREFILENAME);
303 HRESULT hr = SHPathPrepareForWriteA(hDlg, NULL, FullPath, dwFlags);
304 if (FAILED_UNEXPECTEDLY(hr))
305 {
306 Close();
307 return false;
308 }
309 CurrentFile++;
310 if (is_dir)
311 continue;
312
313 const char* password = NULL;
314 /* FIXME: Process password, if required and not specified, prompt the user */
315 err = unzOpenCurrentFilePassword(uf, password);
316 if (err != UNZ_OK)
317 {
318 DPRINT1("ERROR, unzOpenCurrentFilePassword: 0x%x\n", err);
319 Close();
320 return false;
321 }
322
323 HANDLE hFile = CreateFileA(FullPath, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
324 if (hFile == INVALID_HANDLE_VALUE)
325 {
326 DWORD dwErr = GetLastError();
327 if (dwErr == ERROR_FILE_EXISTS)
328 {
329 bool bOverwrite = bOverwriteAll;
330 if (!bOverwriteAll)
331 {
332 eZipConfirmResponse Result = _CZipAskReplace(hDlg, FullPath);
333 switch (Result)
334 {
335 case eYesToAll:
336 bOverwriteAll = true;
337 case eYes:
338 bOverwrite = true;
339 break;
340 case eNo:
341 break;
342 case eCancel:
343 unzCloseCurrentFile(uf);
344 Close();
345 return false;
346 }
347 }
348
349 if (bOverwrite)
350 {
351 hFile = CreateFileA(FullPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
352 if (hFile == INVALID_HANDLE_VALUE)
353 {
354 dwErr = GetLastError();
355 }
356 }
357 else
358 {
359 unzCloseCurrentFile(uf);
360 continue;
361 }
362 }
363 if (hFile == INVALID_HANDLE_VALUE)
364 {
365 unzCloseCurrentFile(uf);
366 DPRINT1("ERROR, CreateFileA: 0x%x (%s)\n", dwErr, bOverwriteAll ? "Y" : "N");
367 Close();
368 return false;
369 }
370 }
371
372 do
373 {
374 err = unzReadCurrentFile(uf, Buffer, sizeof(Buffer));
375
376 if (err < 0)
377 {
378 DPRINT1("ERROR, unzReadCurrentFile: 0x%x\n", err);
379 break;
380 }
381 else if (err > 0)
382 {
383 DWORD dwWritten;
384 if (!WriteFile(hFile, Buffer, err, &dwWritten, NULL))
385 {
386 DPRINT1("ERROR, WriteFile: 0x%x\n", GetLastError());
387 break;
388 }
389 if (dwWritten != (DWORD)err)
390 {
391 DPRINT1("ERROR, WriteFile: dwWritten:%d err:%d\n", dwWritten, err);
392 break;
393 }
394 }
395
396 } while (err > 0);
397
398 /* Update Filetime */
399 FILETIME LastAccessTime;
400 GetFileTime(hFile, NULL, &LastAccessTime, NULL);
401 FILETIME LocalFileTime;
402 DosDateTimeToFileTime((WORD)(Info.dosDate >> 16), (WORD)Info.dosDate, &LocalFileTime);
403 FILETIME FileTime;
404 LocalFileTimeToFileTime(&LocalFileTime, &FileTime);
405 SetFileTime(hFile, &FileTime, &LastAccessTime, &FileTime);
406
407 /* Done.. */
408 CloseHandle(hFile);
409
410 if (err)
411 {
412 unzCloseCurrentFile(uf);
413 DPRINT1("ERROR, unzReadCurrentFile2: 0x%x\n", err);
414 Close();
415 return false;
416 }
417 else
418 {
419 err = unzCloseCurrentFile(uf);
420 if (err != UNZ_OK)
421 {
422 DPRINT1("ERROR(non-fatal), unzCloseCurrentFile: 0x%x\n", err);
423 }
424 }
425 Progress.SendMessage(PBM_SETPOS, CurrentFile, 0);
426 }
427
428 Close();
429 return true;
430 }
431 };
432
433
434 void _CZipExtract_runWizard(PCWSTR Filename)
435 {
436 CZipExtract extractor(Filename);
437 extractor.runWizard();
438 }
439