mirror of
https://github.com/ApfelTeeSaft/reactos.git
synced 2026-08-26 19:33:31 +00:00
436 lines
12 KiB
C++
436 lines
12 KiB
C++
/*
|
|
* PROJECT: ReactOS Zip Shell Extension
|
|
* LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
|
|
* PURPOSE: Create a zip file
|
|
* COPYRIGHT: Copyright 2019 Mark Jansen ([email protected])
|
|
* Copyright 2019-2026 Katayama Hirofumi MZ ([email protected])
|
|
*/
|
|
|
|
#include "precomp.h"
|
|
#include "atlsimpcoll.h"
|
|
#include "minizip/zip.h"
|
|
#include "minizip/iowin32.h"
|
|
#include <process.h>
|
|
|
|
static inline DWORD CalculateNameCRC32(PCSTR name)
|
|
{
|
|
return crc32(0L, reinterpret_cast<const BYTE*>(name), strlen(name));
|
|
}
|
|
|
|
static CStringW DoGetZipName(PCWSTR filename)
|
|
{
|
|
WCHAR szPath[MAX_PATH];
|
|
StringCbCopyW(szPath, sizeof(szPath), filename);
|
|
PathRemoveExtensionW(szPath);
|
|
|
|
CStringW ret = szPath;
|
|
ret += L".zip";
|
|
|
|
UINT i = 2;
|
|
while (PathFileExistsW(ret))
|
|
{
|
|
CStringW str;
|
|
str.Format(L" (%u).zip", i++);
|
|
|
|
ret = szPath;
|
|
ret += str;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static CStringW DoGetBaseName(PCWSTR filename)
|
|
{
|
|
WCHAR szBaseName[MAX_PATH];
|
|
StringCbCopyW(szBaseName, sizeof(szBaseName), filename);
|
|
PathRemoveFileSpecW(szBaseName);
|
|
PathAddBackslashW(szBaseName);
|
|
return szBaseName;
|
|
}
|
|
|
|
static CStringA
|
|
DoGetNameInZip(const CStringW& basename, const CStringW& filename, UINT nCodePage)
|
|
{
|
|
CStringW basenameI = basename, filenameI = filename;
|
|
basenameI.MakeUpper();
|
|
filenameI.MakeUpper();
|
|
|
|
CStringW ret;
|
|
if (filenameI.Find(basenameI) == 0)
|
|
ret = filename.Mid(basename.GetLength());
|
|
else
|
|
ret = filename;
|
|
|
|
ret.Replace(L'\\', L'/');
|
|
|
|
return CStringA(CW2AEX<MAX_PATH>(ret, nCodePage));
|
|
}
|
|
|
|
static BOOL
|
|
DoGetFileTimeInfo(HANDLE hFile, zip_fileinfo* pzi)
|
|
{
|
|
FILETIME ft, ftLocal;
|
|
ZeroMemory(pzi, sizeof(*pzi));
|
|
if (GetFileTime(hFile, NULL, NULL, &ft))
|
|
{
|
|
SYSTEMTIME st;
|
|
FileTimeToLocalFileTime(&ft, &ftLocal);
|
|
FileTimeToSystemTime(&ftLocal, &st);
|
|
pzi->tmz_date.tm_sec = st.wSecond;
|
|
pzi->tmz_date.tm_min = st.wMinute;
|
|
pzi->tmz_date.tm_hour = st.wHour;
|
|
pzi->tmz_date.tm_mday = st.wDay;
|
|
pzi->tmz_date.tm_mon = st.wMonth - 1;
|
|
pzi->tmz_date.tm_year = st.wYear;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
DoAddFilesFromItem(CSimpleArray<CStringW>& files, PCWSTR item)
|
|
{
|
|
if (!PathIsDirectoryW(item))
|
|
{
|
|
files.Add(item);
|
|
return;
|
|
}
|
|
|
|
WCHAR szPath[MAX_PATH];
|
|
StringCbCopyW(szPath, sizeof(szPath), item);
|
|
PathAppendW(szPath, L"*");
|
|
|
|
WIN32_FIND_DATAW find;
|
|
HANDLE hFind = FindFirstFileW(szPath, &find);
|
|
if (hFind == INVALID_HANDLE_VALUE)
|
|
return;
|
|
|
|
do
|
|
{
|
|
if (wcscmp(find.cFileName, L".") == 0 ||
|
|
wcscmp(find.cFileName, L"..") == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
StringCbCopyW(szPath, sizeof(szPath), item);
|
|
PathAppendW(szPath, find.cFileName);
|
|
|
|
if (find.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
DoAddFilesFromItem(files, szPath);
|
|
else
|
|
files.Add(szPath);
|
|
} while (FindNextFileW(hFind, &find));
|
|
|
|
FindClose(hFind);
|
|
}
|
|
|
|
struct CZipCreatorImpl
|
|
{
|
|
CSimpleArray<CStringW> m_items;
|
|
CStringW m_ExistingZip;
|
|
CStringW m_TargetDir;
|
|
|
|
unsigned JustDoIt();
|
|
};
|
|
|
|
CZipCreator* CZipCreator::DoCreate(PCWSTR pszExistingZip, PCWSTR pszTargetDir)
|
|
{
|
|
CZipCreator* pCreator = new CZipCreator();
|
|
if (pszExistingZip)
|
|
pCreator->m_pimpl->m_ExistingZip = pszExistingZip;
|
|
if (pszTargetDir)
|
|
pCreator->m_pimpl->m_TargetDir = pszTargetDir;
|
|
return pCreator;
|
|
}
|
|
|
|
CZipCreator::CZipCreator() : m_pimpl(new CZipCreatorImpl)
|
|
{
|
|
InterlockedIncrement(&g_ModuleRefCnt);
|
|
}
|
|
|
|
CZipCreator::~CZipCreator()
|
|
{
|
|
InterlockedDecrement(&g_ModuleRefCnt);
|
|
delete m_pimpl;
|
|
}
|
|
|
|
static unsigned __stdcall
|
|
create_zip_function(void *arg)
|
|
{
|
|
CZipCreator *pCreator = reinterpret_cast<CZipCreator *>(arg);
|
|
unsigned result = pCreator->m_pimpl->JustDoIt();
|
|
|
|
if (result == 0 && pCreator->m_pidlNotify)
|
|
SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_IDLIST, (LPITEMIDLIST)pCreator->m_pidlNotify, NULL);
|
|
|
|
return result;
|
|
}
|
|
|
|
BOOL CZipCreator::runThread(CZipCreator *pCreator)
|
|
{
|
|
unsigned tid = 0;
|
|
HANDLE hThread = reinterpret_cast<HANDLE>(
|
|
_beginthreadex(NULL, 0, create_zip_function, pCreator, 0, &tid));
|
|
|
|
if (hThread)
|
|
{
|
|
CloseHandle(hThread);
|
|
return TRUE;
|
|
}
|
|
|
|
DPRINT1("hThread == NULL\n");
|
|
|
|
CStringW strTitle(MAKEINTRESOURCEW(IDS_ERRORTITLE));
|
|
CStringW strText(MAKEINTRESOURCEW(IDS_CANTSTARTTHREAD));
|
|
MessageBoxW(NULL, strText, strTitle, MB_ICONERROR);
|
|
|
|
delete pCreator;
|
|
return FALSE;
|
|
}
|
|
|
|
void CZipCreator::DoAddItem(PCWSTR pszFile)
|
|
{
|
|
// canonicalize path
|
|
WCHAR szPath[MAX_PATH];
|
|
GetFullPathNameW(pszFile, _countof(szPath), szPath, NULL);
|
|
|
|
m_pimpl->m_items.Add(szPath);
|
|
}
|
|
|
|
enum CZC_ERROR
|
|
{
|
|
CZCERR_ZEROITEMS = 1,
|
|
CZCERR_NOFILES,
|
|
CZCERR_CREATE,
|
|
CZCERR_READ
|
|
};
|
|
|
|
unsigned CZipCreatorImpl::JustDoIt()
|
|
{
|
|
// TODO: Show progress.
|
|
|
|
CStringW strZipName;
|
|
INT appendMode;
|
|
if (m_ExistingZip.IsEmpty())
|
|
{
|
|
strZipName = DoGetZipName(m_items[0]);
|
|
appendMode = APPEND_STATUS_CREATE;
|
|
}
|
|
else
|
|
{
|
|
strZipName = m_ExistingZip;
|
|
appendMode = APPEND_STATUS_ADDINZIP;
|
|
}
|
|
|
|
if (m_items.GetSize() <= 0)
|
|
{
|
|
DPRINT1("GetSize() <= 0\n");
|
|
return CZCERR_ZEROITEMS;
|
|
}
|
|
|
|
CSimpleArray<CStringW> files;
|
|
for (INT iItem = 0; iItem < m_items.GetSize(); ++iItem)
|
|
{
|
|
DoAddFilesFromItem(files, m_items[iItem]);
|
|
}
|
|
|
|
if (files.GetSize() <= 0)
|
|
{
|
|
DPRINT1("files.GetSize() <= 0\n");
|
|
|
|
CStringW strTitle(MAKEINTRESOURCEW(IDS_ERRORTITLE));
|
|
CStringW strText;
|
|
strText.Format(IDS_NOFILES, static_cast<PCWSTR>(m_items[0]));
|
|
MessageBoxW(NULL, strText, strTitle, MB_ICONERROR);
|
|
|
|
return CZCERR_NOFILES;
|
|
}
|
|
|
|
zlib_filefunc64_def ffunc;
|
|
ZeroMemory(&ffunc, sizeof(ffunc));
|
|
fill_win32_filefunc64W(&ffunc);
|
|
|
|
zipFile zf = zipOpen2_64(strZipName, appendMode, NULL, &ffunc);
|
|
if (zf == 0)
|
|
{
|
|
DPRINT1("zf == 0\n");
|
|
|
|
int err = CZCERR_CREATE;
|
|
|
|
CStringW strTitle(MAKEINTRESOURCEW(IDS_ERRORTITLE));
|
|
CStringW strText;
|
|
strText.Format(IDS_CANTCREATEZIP, static_cast<PCWSTR>(strZipName), err);
|
|
MessageBoxW(NULL, strText, strTitle, MB_ICONERROR);
|
|
|
|
return err;
|
|
}
|
|
|
|
// TODO: password
|
|
const char *password = NULL;
|
|
|
|
// Use dwAllocationGranularity for file mapping
|
|
SYSTEM_INFO si;
|
|
GetSystemInfo(&si);
|
|
const ULONGLONG dwChunkSize = si.dwAllocationGranularity * 256;
|
|
|
|
// The loop for creating a ZIP file
|
|
CStringW strTarget, strBaseName = DoGetBaseName(m_items[0]);
|
|
UINT nCodePage = GetZipCodePage(FALSE);
|
|
INT err = ZIP_OK;
|
|
for (INT iFile = 0; iFile < files.GetSize() && err == ZIP_OK; ++iFile)
|
|
{
|
|
const CStringW& strFile = files[iFile];
|
|
zip_fileinfo zi;
|
|
|
|
// Open the file for storing it into a ZIP file
|
|
HANDLE hFile = CreateFileW(strFile, GENERIC_READ, FILE_SHARE_READ, NULL,
|
|
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
DPRINT1("Cannot open file '%S'\n", (PCWSTR)strFile);
|
|
err = CZCERR_READ;
|
|
break;
|
|
}
|
|
|
|
// Load the file time info
|
|
DoGetFileTimeInfo(hFile, &zi);
|
|
|
|
// Get the file size
|
|
LARGE_INTEGER fileSize;
|
|
if (!GetFileSizeEx(hFile, &fileSize))
|
|
{
|
|
DPRINT1("Cannot get file size '%S'\n", (PCWSTR)strFile);
|
|
CloseHandle(hFile);
|
|
err = CZCERR_READ;
|
|
break;
|
|
}
|
|
|
|
unsigned long crc = 0;
|
|
if (password)
|
|
{
|
|
// TODO: crc = ...;
|
|
}
|
|
|
|
// Get filename info
|
|
CStringA strNameInZip = DoGetNameInZip(strBaseName, strFile, nCodePage);
|
|
CStringA strNameInZipUTF8 = DoGetNameInZip(strBaseName, strFile, CP_UTF8);
|
|
if (!m_TargetDir.IsEmpty())
|
|
{
|
|
CStringA strTargetDir = CStringA(CW2AEX<MAX_PATH>(m_TargetDir, nCodePage));
|
|
strNameInZip = strTargetDir + strNameInZip;
|
|
|
|
CStringA strTargetDirUTF8 = CStringA(CW2AEX<MAX_PATH>(m_TargetDir, CP_UTF8));
|
|
strNameInZipUTF8 = strTargetDirUTF8 + strNameInZipUTF8;
|
|
}
|
|
|
|
// Prepare additional field (UTF-8 name)
|
|
CSimpleArray<BYTE> extraField;
|
|
if (nCodePage != CP_UTF8 && strNameInZip != strNameInZipUTF8)
|
|
{
|
|
// Header
|
|
WORD headerID = EF_UNIPATH, dataSize = 1 + 4 + strNameInZipUTF8.GetLength();
|
|
extraField.Add(headerID & 0xFF);
|
|
extraField.Add((headerID >> 8) & 0xFF);
|
|
extraField.Add(dataSize & 0xFF);
|
|
extraField.Add((dataSize >> 8) & 0xFF);
|
|
extraField.Add(1); // Version
|
|
|
|
// CRC32
|
|
DWORD nameCRC = CalculateNameCRC32(strNameInZip);
|
|
extraField.Add(nameCRC & 0xFF);
|
|
extraField.Add((nameCRC >> 8) & 0xFF);
|
|
extraField.Add((nameCRC >> 16) & 0xFF);
|
|
extraField.Add((nameCRC >> 24) & 0xFF);
|
|
|
|
// UTF-8 name
|
|
for (INT ich = 0; ich < strNameInZipUTF8.GetLength(); ++ich)
|
|
extraField.Add(strNameInZipUTF8[ich]);
|
|
}
|
|
|
|
// Add a new file entry into a ZIP file
|
|
err = zipOpenNewFileInZip4_64(zf, strNameInZip, &zi,
|
|
(extraField.GetSize() ? extraField.GetData() : NULL),
|
|
extraField.GetSize(), NULL, 0, NULL, Z_DEFLATED,
|
|
Z_DEFAULT_COMPRESSION, 0, -MAX_WBITS, DEF_MEM_LEVEL,
|
|
Z_DEFAULT_STRATEGY, password, crc,
|
|
MINIZIP_COMPATIBLE_VERSION,
|
|
(nCodePage == CP_UTF8 ? MINIZIP_UTF8_FLAG : 0), 1);
|
|
if (err)
|
|
{
|
|
DPRINT1("zipOpenNewFileInZip3_64\n");
|
|
break;
|
|
}
|
|
|
|
if (fileSize.QuadPart > 0) // Not an empty file?
|
|
{
|
|
// Use a file mapping for quick file access and large file support
|
|
HANDLE hMapping = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
|
|
if (!hMapping)
|
|
{
|
|
DPRINT1("Cannot create file mapping\n");
|
|
err = CZCERR_READ;
|
|
CloseHandle(hFile);
|
|
break;
|
|
}
|
|
|
|
// The loop for reading and storing file contents
|
|
DWORD dwMapSize;
|
|
for (ULONGLONG offset = 0; offset < (ULONGLONG)fileSize.QuadPart && err == ZIP_OK;
|
|
offset += dwMapSize)
|
|
{
|
|
ULONGLONG dwDelta = fileSize.QuadPart - offset;
|
|
dwMapSize = (DWORD)min(dwChunkSize, dwDelta);
|
|
DWORD offsetLow = (DWORD)offset, offsetHigh = (DWORD)(offset >> 32);
|
|
PVOID pData = MapViewOfFile(hMapping, FILE_MAP_READ, offsetHigh, offsetLow,
|
|
dwMapSize);
|
|
if (!pData)
|
|
{
|
|
DPRINT1("Cannot map the view\n");
|
|
err = CZCERR_READ;
|
|
break;
|
|
}
|
|
|
|
err = zipWriteInFileInZip(zf, pData, dwMapSize);
|
|
UnmapViewOfFile(pData);
|
|
}
|
|
|
|
CloseHandle(hMapping);
|
|
}
|
|
|
|
CloseHandle(hFile);
|
|
|
|
zipCloseFileInZip(zf);
|
|
}
|
|
|
|
zipClose(zf, NULL);
|
|
|
|
if (err)
|
|
{
|
|
if (err && m_ExistingZip.IsEmpty())
|
|
DeleteFileW(strZipName);
|
|
|
|
CStringW strTitle(MAKEINTRESOURCEW(IDS_ERRORTITLE));
|
|
|
|
CStringW strText;
|
|
if (err < 0)
|
|
strText.Format(IDS_CANTCREATEZIP, static_cast<PCWSTR>(strZipName), err);
|
|
else
|
|
strText.Format(IDS_CANTREADFILE, static_cast<PCWSTR>(strTarget));
|
|
|
|
MessageBoxW(NULL, strText, strTitle, MB_ICONERROR);
|
|
}
|
|
else
|
|
{
|
|
WCHAR szFullPath[MAX_PATH];
|
|
GetFullPathNameW(strZipName, _countof(szFullPath), szFullPath, NULL);
|
|
|
|
if (m_ExistingZip.IsEmpty())
|
|
SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, szFullPath, NULL);
|
|
else
|
|
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, szFullPath, NULL);
|
|
}
|
|
|
|
return err;
|
|
}
|