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