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