[SHELLEXT][ZIPFLDR] Implement ZIP creation (#2114)
[reactos.git] / dll / shellext / zipfldr / CZipCreater.cpp
1 /*
2 * PROJECT: ReactOS Zip Shell Extension
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Create a zip file
5 * COPYRIGHT: Copyright 2019 Mark Jansen (mark.jansen@reactos.org)
6 * Copyright 2019 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
7 */
8
9 #include "precomp.h"
10 #include "atlsimpcoll.h"
11 #include "minizip/zip.h"
12 #include "minizip/iowin32.h"
13 #include <process.h>
14
15 static CStringW DoGetZipName(LPCWSTR filename)
16 {
17 WCHAR szPath[MAX_PATH];
18 StringCbCopyW(szPath, sizeof(szPath), filename);
19 PathRemoveExtensionW(szPath);
20
21 CStringW ret = szPath;
22 ret += L".zip";
23
24 UINT i = 2;
25 while (PathFileExistsW(ret))
26 {
27 CStringW str;
28 str.Format(L" (%u).zip", i++);
29
30 ret = szPath;
31 ret += str;
32 }
33
34 return ret;
35 }
36
37 static CStringA DoGetAnsiName(LPCWSTR filename)
38 {
39 CHAR buf[MAX_PATH];
40 WideCharToMultiByte(CP_ACP, 0, filename, -1, buf, _countof(buf), NULL, NULL);
41 return buf;
42 }
43
44 static CStringW DoGetBaseName(LPCWSTR filename)
45 {
46 WCHAR szBaseName[MAX_PATH];
47 StringCbCopyW(szBaseName, sizeof(szBaseName), filename);
48 PathRemoveFileSpecW(szBaseName);
49 PathAddBackslashW(szBaseName);
50 return szBaseName;
51 }
52
53 static CStringA
54 DoGetNameInZip(const CStringW& basename, const CStringW& filename)
55 {
56 CStringW basenameI = basename, filenameI = filename;
57 basenameI.MakeUpper();
58 filenameI.MakeUpper();
59
60 CStringW ret;
61 if (filenameI.Find(basenameI) == 0)
62 ret = filename.Mid(basename.GetLength());
63 else
64 ret = filename;
65
66 ret.Replace(L'\\', L'/');
67
68 return DoGetAnsiName(ret);
69 }
70
71 static BOOL
72 DoReadAllOfFile(LPCWSTR filename, CSimpleArray<BYTE>& contents,
73 zip_fileinfo *pzi)
74 {
75 contents.RemoveAll();
76
77 HANDLE hFile = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ,
78 NULL, OPEN_EXISTING,
79 FILE_FLAG_SEQUENTIAL_SCAN, NULL);
80 if (hFile == INVALID_HANDLE_VALUE)
81 {
82 DPRINT1("%S: cannot open\n", filename);
83 return FALSE;
84 }
85
86 FILETIME ft, ftLocal;
87 ZeroMemory(pzi, sizeof(*pzi));
88 if (GetFileTime(hFile, NULL, NULL, &ft))
89 {
90 SYSTEMTIME st;
91 FileTimeToLocalFileTime(&ft, &ftLocal);
92 FileTimeToSystemTime(&ftLocal, &st);
93 pzi->tmz_date.tm_sec = st.wSecond;
94 pzi->tmz_date.tm_min = st.wMinute;
95 pzi->tmz_date.tm_hour = st.wHour;
96 pzi->tmz_date.tm_mday = st.wDay;
97 pzi->tmz_date.tm_mon = st.wMonth - 1;
98 pzi->tmz_date.tm_year = st.wYear;
99 }
100
101 const DWORD cbBuff = 0x7FFF;
102 LPBYTE pbBuff = reinterpret_cast<LPBYTE>(CoTaskMemAlloc(cbBuff));
103 if (!pbBuff)
104 {
105 DPRINT1("Out of memory\n");
106 CloseHandle(hFile);
107 return FALSE;
108 }
109
110 for (;;)
111 {
112 DWORD cbRead;
113 if (!ReadFile(hFile, pbBuff, cbBuff, &cbRead, NULL) || !cbRead)
114 break;
115
116 for (DWORD i = 0; i < cbRead; ++i)
117 contents.Add(pbBuff[i]);
118 }
119
120 CoTaskMemFree(pbBuff);
121 CloseHandle(hFile);
122
123 return TRUE;
124 }
125
126 static void
127 DoAddFilesFromItem(CSimpleArray<CStringW>& files, LPCWSTR item)
128 {
129 if (!PathIsDirectoryW(item))
130 {
131 files.Add(item);
132 return;
133 }
134
135 WCHAR szPath[MAX_PATH];
136 StringCbCopyW(szPath, sizeof(szPath), item);
137 PathAppendW(szPath, L"*");
138
139 WIN32_FIND_DATAW find;
140 HANDLE hFind = FindFirstFileW(szPath, &find);
141 if (hFind == INVALID_HANDLE_VALUE)
142 return;
143
144 do
145 {
146 if (wcscmp(find.cFileName, L".") == 0 ||
147 wcscmp(find.cFileName, L"..") == 0)
148 {
149 continue;
150 }
151
152 StringCbCopyW(szPath, sizeof(szPath), item);
153 PathAppendW(szPath, find.cFileName);
154
155 if (find.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
156 DoAddFilesFromItem(files, szPath);
157 else
158 files.Add(szPath);
159 } while (FindNextFileW(hFind, &find));
160
161 FindClose(hFind);
162 }
163
164 struct CZipCreatorImpl
165 {
166 CSimpleArray<CStringW> m_items;
167
168 unsigned JustDoIt();
169 };
170
171 CZipCreator::CZipCreator() : m_pimpl(new CZipCreatorImpl)
172 {
173 InterlockedIncrement(&g_ModuleRefCnt);
174 }
175
176 CZipCreator::~CZipCreator()
177 {
178 InterlockedDecrement(&g_ModuleRefCnt);
179 delete m_pimpl;
180 }
181
182 static unsigned __stdcall
183 create_zip_function(void *arg)
184 {
185 CZipCreator *pCreater = reinterpret_cast<CZipCreator *>(arg);
186 return pCreater->m_pimpl->JustDoIt();
187 }
188
189 BOOL CZipCreator::runThread(CZipCreator *pCreater)
190 {
191 unsigned tid = 0;
192 HANDLE hThread = reinterpret_cast<HANDLE>(
193 _beginthreadex(NULL, 0, create_zip_function, pCreater, 0, &tid));
194
195 if (hThread)
196 {
197 CloseHandle(hThread);
198 return TRUE;
199 }
200
201 DPRINT1("hThread == NULL\n");
202
203 CStringW strTitle(MAKEINTRESOURCEW(IDS_ERRORTITLE));
204 CStringW strText(MAKEINTRESOURCEW(IDS_CANTSTARTTHREAD));
205 MessageBoxW(NULL, strText, strTitle, MB_ICONERROR);
206
207 delete pCreater;
208 return FALSE;
209 }
210
211 void CZipCreator::DoAddItem(LPCWSTR pszFile)
212 {
213 // canonicalize path
214 WCHAR szPath[MAX_PATH];
215 GetFullPathNameW(pszFile, _countof(szPath), szPath, NULL);
216
217 m_pimpl->m_items.Add(szPath);
218 }
219
220 enum CZC_ERROR
221 {
222 CZCERR_ZEROITEMS = 1,
223 CZCERR_NOFILES,
224 CZCERR_CREATE,
225 CZCERR_READ
226 };
227
228 unsigned CZipCreatorImpl::JustDoIt()
229 {
230 // TODO: Show progress.
231
232 if (m_items.GetSize() <= 0)
233 {
234 DPRINT1("GetSize() <= 0\n");
235 return CZCERR_ZEROITEMS;
236 }
237
238 CSimpleArray<CStringW> files;
239 for (INT iItem = 0; iItem < m_items.GetSize(); ++iItem)
240 {
241 DoAddFilesFromItem(files, m_items[iItem]);
242 }
243
244 if (files.GetSize() <= 0)
245 {
246 DPRINT1("files.GetSize() <= 0\n");
247
248 CStringW strTitle(MAKEINTRESOURCEW(IDS_ERRORTITLE));
249 CStringW strText;
250 strText.Format(IDS_NOFILES, static_cast<LPCWSTR>(m_items[0]));
251 MessageBoxW(NULL, strText, strTitle, MB_ICONERROR);
252
253 return CZCERR_NOFILES;
254 }
255
256 zlib_filefunc64_def ffunc;
257 fill_win32_filefunc64W(&ffunc);
258
259 CStringW strZipName = DoGetZipName(m_items[0]);
260 zipFile zf = zipOpen2_64(strZipName, APPEND_STATUS_CREATE, NULL, &ffunc);
261 if (zf == 0)
262 {
263 DPRINT1("zf == 0\n");
264
265 int err = CZCERR_CREATE;
266
267 CStringW strTitle(MAKEINTRESOURCEW(IDS_ERRORTITLE));
268 CStringW strText;
269 strText.Format(IDS_CANTCREATEZIP, static_cast<LPCWSTR>(strZipName), err);
270 MessageBoxW(NULL, strText, strTitle, MB_ICONERROR);
271
272 return err;
273 }
274
275 // TODO: password
276 const char *password = NULL;
277 int zip64 = 1; // always zip64
278 zip_fileinfo zi;
279
280 int err = 0;
281 CStringW strTarget, strBaseName = DoGetBaseName(m_items[0]);
282 for (INT iFile = 0; iFile < files.GetSize(); ++iFile)
283 {
284 const CStringW& strFile = files[iFile];
285
286 CSimpleArray<BYTE> contents;
287 if (!DoReadAllOfFile(strFile, contents, &zi))
288 {
289 DPRINT1("DoReadAllOfFile failed\n");
290 err = CZCERR_READ;
291 strTarget = strFile;
292 break;
293 }
294
295 unsigned long crc = 0;
296 if (password)
297 {
298 // TODO: crc = ...;
299 }
300
301 CStringA strNameInZip = DoGetNameInZip(strBaseName, strFile);
302 err = zipOpenNewFileInZip3_64(zf,
303 strNameInZip,
304 &zi,
305 NULL,
306 0,
307 NULL,
308 0,
309 NULL,
310 Z_DEFLATED,
311 Z_DEFAULT_COMPRESSION,
312 0,
313 -MAX_WBITS,
314 DEF_MEM_LEVEL,
315 Z_DEFAULT_STRATEGY,
316 password,
317 crc,
318 zip64);
319 if (err)
320 {
321 DPRINT1("zipOpenNewFileInZip3_64\n");
322 break;
323 }
324
325 err = zipWriteInFileInZip(zf, contents.GetData(), contents.GetSize());
326 if (err)
327 {
328 DPRINT1("zipWriteInFileInZip\n");
329 break;
330 }
331
332 err = zipCloseFileInZip(zf);
333 if (err)
334 {
335 DPRINT1("zipCloseFileInZip\n");
336 break;
337 }
338 }
339
340 zipClose(zf, NULL);
341
342 if (err)
343 {
344 DeleteFileW(strZipName);
345
346 CStringW strTitle(MAKEINTRESOURCEW(IDS_ERRORTITLE));
347
348 CStringW strText;
349 if (err < 0)
350 strText.Format(IDS_CANTCREATEZIP, static_cast<LPCWSTR>(strZipName), err);
351 else
352 strText.Format(IDS_CANTREADFILE, static_cast<LPCWSTR>(strTarget));
353
354 MessageBoxW(NULL, strText, strTitle, MB_ICONERROR);
355 }
356
357 return err;
358 }