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 
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 
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
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
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
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 
164 CZipCreator::CZipCreator() : m_pimpl(new CZipCreatorImpl)
165 {
166     InterlockedIncrement(&g_ModuleRefCnt);
167 }
168 
169 CZipCreator::~CZipCreator()
170 {
171     InterlockedDecrement(&g_ModuleRefCnt);
172     delete m_pimpl;
173 }
174 
175 static unsigned __stdcall
176 create_zip_function(void *arg)
177 {
178     CZipCreator *pCreator = reinterpret_cast<CZipCreator *>(arg);
179     return pCreator->m_pimpl->JustDoIt();
180 }
181 
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 
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 
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