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 *pCreator = reinterpret_cast<CZipCreator *>(arg);
186     return pCreator->m_pimpl->JustDoIt();
187 }
188 
189 BOOL CZipCreator::runThread(CZipCreator *pCreator)
190 {
191     unsigned tid = 0;
192     HANDLE hThread = reinterpret_cast<HANDLE>(
193         _beginthreadex(NULL, 0, create_zip_function, pCreator, 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 pCreator;
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     else
357     {
358         WCHAR szFullPath[MAX_PATH];
359         GetFullPathNameW(strZipName, _countof(szFullPath), szFullPath, NULL);
360         SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, szFullPath, NULL);
361     }
362 
363     return err;
364 }
365