Files
harbour-core/harbour/contrib/hbzlib/ziparchive.cpp
Luiz Rafael Culik 0f62fcf919 Added linux makefiles
2004-04-03 04:29:41 +00:00

3237 lines
81 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// $Workfile: ZipArchive.cpp $
// $Archive: /ZipArchive/ZipArchive.cpp $
// $Date$ $Author$
////////////////////////////////////////////////////////////////////////////////
// This source file is part of the ZipArchive library source distribution and
// is Copyright 2000-2003 by Tadeusz Dracz (http://www.artpol-software.com/)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// For the licensing details see the file License.txt
////////////////////////////////////////////////////////////////////////////////
#include "hbcomprs.h"
#include "stdafx.h"
#include "ziparchive.h"
// #include "ZipPathComponent.h"
#include "zipplatform.h"
#include "zipcompatibility.h"
#include <time.h>
#ifndef DEF_MEM_LEVEL
#if MAX_MEM_LEVEL >= 8
# define DEF_MEM_LEVEL 8
#else
# define DEF_MEM_LEVEL MAX_MEM_LEVEL
#endif
#endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
#define ZIP_COMPR_REPL_MASK 0xffffff00
#define ZIP_COMPR_REPL_SIGN 0x0100 // first 8 bits should be 00 (reserved for compression level), next 8 should be different from ff (to distinguish from -1)
const TCHAR CZipArchive::m_gszCopyright[] = {_T("ZipArchive library Copyright 2000 - 2003 Tadeusz Dracz")};
void CZipAddNewFileInfo::Defaults()
{
m_iSmartLevel = CZipArchive::zipsmSafeSmart;
m_iReplaceIndex = -1;
m_nBufSize = 65536;
m_iComprLevel = -1; // default
}
CZipArchive::CZipArchive()
{
m_bRemoveDriveLetter = true;
m_bIgnoreCRC =
m_bAutoFlush = false;
m_centralDir.m_pStorage= &m_storage;
// m_info.m_stream.zalloc = (alloc_func)_zliballoc;
// m_info.m_stream.zfree = (free_func)_zlibfree;
// m_bDetectZlibMemoryLeaks = true;
m_iFileOpened = nothing;
SetCaseSensitivity(ZipPlatform::GetSystemCaseSensitivity());
}
CZipArchive::~CZipArchive()
{
// Close(); // cannot be here: if an exception is thrown strange things can happen
EmptyPtrList();
}
void CZipArchive::Open(LPCTSTR szPathName, int iMode, int iVolumeSize)
{
if (!IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive already opened.\n"),__FILE__,__LINE__);
return;
}
m_storage.Open(szPathName, iMode, iVolumeSize);
OpenInternal(iMode);
}
void CZipArchive::Open(CZipMemFile& mf,int iMode)
{
if (!IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive already opened.\n"),__FILE__,__LINE__);
return;
}
if (iMode != zipOpen && iMode != zipOpenReadOnly && iMode != zipCreate)
{
TRACE(_T("%s(%i) : Mode not supported.\n"),__FILE__,__LINE__);
return;
}
m_storage.Open(mf, iMode);
OpenInternal(iMode);
}
void CZipArchive::OpenInternal(int iMode)
{
m_pszPassword.Release();
m_iFileOpened = nothing;
m_centralDir.Init();
m_iArchiveSystCompatib = ZipPlatform::GetSystemID();
m_szRootPath.Empty();
if ((iMode == zipOpen) ||(iMode == zipOpenReadOnly))
{
m_centralDir.Read();
// if there is at least one file, get system comp. from the first one
if (m_centralDir.IsValidIndex(0))
{
int iSystemComp = m_centralDir[0]->GetSystemCompatibility();
if (ZipCompatibility::IsPlatformSupported(iSystemComp))
m_iArchiveSystCompatib = iSystemComp;
}
}
}
bool CZipArchive::IsClosed(bool bArchive) const
{
return bArchive ?(m_storage.GetCurrentDisk() == -1):(!m_storage.m_pFile || m_storage.m_pFile->IsClosed());
}
void CZipArchive::ThrowError(int err, bool bZlib)
{
if (bZlib)
err = CZipException::ZlibErrToZip(err);
CZipException::Throw(err, IsClosed() ? _T("") : (LPCTSTR)m_storage.m_pFile->GetFilePath());
}
bool CZipArchive::GetFileInfo(CZipFileHeader & fhInfo, WORD uIndex) const
{
if (IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive is closed.\n"),__FILE__,__LINE__);
return false;
}
if (!m_centralDir.IsValidIndex(uIndex))
return false;
fhInfo = *(m_centralDir[uIndex]);
m_centralDir.ConvertFileName(true, false, &fhInfo);
return true;
}
int CZipArchive::FindFile(LPCTSTR lpszFileName, int iCaseSensitive, bool bFileNameOnly)
{
if (IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive is closed.\n"),__FILE__,__LINE__);
return (int)-1;
}
bool bCS;
bool bSporadically;
switch (iCaseSensitive)
{
case ffCaseSens:
bCS = true;
bSporadically = true;
break;
case ffNoCaseSens:
bCS = false;
bSporadically = true;
break;
default:
bCS = m_bCaseSensitive;
bSporadically = false;
}
return m_centralDir.FindFile(lpszFileName, bCS, bSporadically, bFileNameOnly);
}
bool CZipArchive::OpenFile(WORD uIndex)
{
if (!m_centralDir.IsValidIndex(uIndex))
{
ASSERT(FALSE);
return false;
}
if (m_storage.IsSpanMode() == 1)
{
TRACE(_T("%s(%i) : You cannot extract from the span in creation.\n"),__FILE__,__LINE__);
return false;
}
if (m_iFileOpened)
{
TRACE(_T("%s(%i) : A file already opened.\n"),__FILE__,__LINE__);
return false;
}
m_info.Init();
m_centralDir.OpenFile(uIndex);
if (CurrentFile()->IsEncrypted())
{
if (m_pszPassword.GetSize() == 0)
{
TRACE(_T("%s(%i) : Password not set for the encrypted file.\n"),__FILE__,__LINE__);
ThrowError(CZipException::badPassword);
}
CryptInitKeys();
if (!CryptCheck())
ThrowError(CZipException::badPassword);
}
else if (m_pszPassword.GetSize() != 0)
{
TRACE(_T("%s(%i) : Password set for a not encrypted file. Ignoring password.\n"),__FILE__,__LINE__);
}
WORD uMethod = CurrentFile()->m_uMethod;
if ((uMethod != 0) &&(uMethod != Z_DEFLATED))
ThrowError(CZipException::badZipFile);
if (uMethod == Z_DEFLATED)
{
// m_info.m_stream.opaque = m_bDetectZlibMemoryLeaks ? &m_list : 0;
int err = inflateInit2(&m_info.m_stream, -MAX_WBITS);
// * windowBits is passed < 0 to tell that there is no zlib header.
// * Note that in this case inflate *requires* an extra "dummy" byte
// * after the compressed stream in order to complete decompression and
// * return Z_STREAM_END.
CheckForError(err);
}
m_info.m_uComprLeft = CurrentFile()->m_uComprSize;
if (CurrentFile()->IsEncrypted())
m_info.m_uComprLeft -= ZIPARCHIVE_ENCR_HEADER_LEN;
m_info.m_uUncomprLeft = CurrentFile()->m_uUncomprSize;
m_info.m_uCrc32 = 0;
m_info.m_stream.total_out = 0;
m_info.m_stream.avail_in = 0;
m_iFileOpened = extract;
return true;
}
int CZipArchive::GetLocalExtraField(char *pBuf, int iSize)const
{
if (IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive is closed.\n"),__FILE__,__LINE__);
return -1;
}
if (m_iFileOpened != extract)
{
TRACE(_T("%s(%i) : A file must be opened to get the local extra field.\n"),__FILE__,__LINE__);
return -1;
}
int size = m_centralDir.m_pLocalExtraField.GetSize();
if (!pBuf|| !size)
return size;
if (iSize < size)
size = iSize;
memcpy(pBuf, m_centralDir.m_pLocalExtraField, size);
return size;
}
/*
void* CZipArchive::_zliballoc(void* opaque, UINT items, UINT size)
{
void* p = new char[size * items];
if (opaque)
{
CZipPtrList<void*>* list = (CZipPtrList<void*>*) opaque;
list->AddTail(p);
}
return p;
}
void CZipArchive::_zlibfree(void* opaque, void* address)
{
if (opaque)
{
CZipPtrList<void*>* list = (CZipPtrList<void*>*) opaque;
CZipPtrListIter iter = list->Find(address);
if (list->IteratorValid(iter))
list->RemoveAt(iter);
}
delete[] (char*) address;
}
*/
void CZipArchive::CheckForError(int iErr)
{
if ((iErr == Z_OK) ||(iErr == Z_NEED_DICT))
return;
ThrowError(iErr, true);
}
CZipFileHeader* CZipArchive::CurrentFile()
{
ASSERT(m_centralDir.m_pOpenedFile);
return m_centralDir.m_pOpenedFile;
}
DWORD CZipArchive::ReadFile(void *pBuf,
DWORD iSize)
{
if (m_iFileOpened != extract)
{
TRACE(_T("%s(%i) : Current file must be opened.\n"),__FILE__,__LINE__);
return 0;
}
if (!pBuf || !iSize)
return 0;
m_info.m_stream.next_out = (Bytef*)pBuf;
m_info.m_stream.avail_out = iSize > m_info.m_uUncomprLeft
? m_info.m_uUncomprLeft : iSize;
DWORD iRead = 0;
// may happen when the file is 0 sized
bool bForce = m_info.m_stream.avail_out == 0 && m_info.m_uComprLeft > 0;
while (m_info.m_stream.avail_out > 0 || (bForce && m_info.m_uComprLeft > 0))
{
if ((m_info.m_stream.avail_in == 0) &&
(m_info.m_uComprLeft >= 0)) // Also when there are zero bytes left!
{
DWORD uToRead = m_info.m_pBuffer.GetSize();
if (m_info.m_uComprLeft < uToRead)
uToRead = m_info.m_uComprLeft;
if (uToRead == 0)
{
uToRead = 1; // Add dummy byte at end of compressed data.
}
else
{
m_storage.Read(m_info.m_pBuffer, uToRead, false);
CryptDecodeBuffer(uToRead);
}
m_info.m_uComprLeft -= uToRead;
m_info.m_stream.next_in = (Bytef*)(char*)m_info.m_pBuffer;
m_info.m_stream.avail_in = uToRead;
}
if (CurrentFile()->m_uMethod == 0)
{
DWORD uToCopy = m_info.m_stream.avail_out < m_info.m_stream.avail_in
? m_info.m_stream.avail_out : m_info.m_stream.avail_in;
memcpy(m_info.m_stream.next_out, m_info.m_stream.next_in, uToCopy);
m_info.m_uCrc32 = crc32(m_info.m_uCrc32, m_info.m_stream.next_out, uToCopy);
m_info.m_uUncomprLeft -= uToCopy;
m_info.m_stream.avail_in -= uToCopy;
m_info.m_stream.avail_out -= uToCopy;
m_info.m_stream.next_out += uToCopy;
m_info.m_stream.next_in += uToCopy;
m_info.m_stream.total_out += uToCopy;
iRead += uToCopy;
}
else
{
DWORD uTotal = m_info.m_stream.total_out;
Bytef* pOldBuf = m_info.m_stream.next_out;
int err = inflate(&m_info.m_stream, Z_SYNC_FLUSH);
DWORD uToCopy = m_info.m_stream.total_out - uTotal;
m_info.m_uCrc32 = crc32(m_info.m_uCrc32, pOldBuf, uToCopy);
m_info.m_uUncomprLeft -= uToCopy;
iRead += uToCopy;
if (err == Z_STREAM_END)
return iRead;
CheckForError(err);
}
}
return iRead;
}
void CZipArchive::Close(int iAfterException, bool bUpdateTimeStamp)
{
// if after an exception - the archive may be closed, but the file may be opened
if (IsClosed() && (!iAfterException || IsClosed(false)))
{
TRACE(_T("%s(%i) : ZipArchive is already closed.\n"),__FILE__,__LINE__);
return;
}
if (m_iFileOpened == extract)
CloseFile(NULL, iAfterException != afNoException);
if (m_iFileOpened == compress)
CloseNewFile(iAfterException != afNoException);
if (iAfterException != afAfterException && !IsClosed(false)) // in disk spanning when user aborts
WriteCentralDirectory(false); // we will flush in CZipStorage::Close
time_t tNewestTime = 0;
if (bUpdateTimeStamp)
{
int iSize = m_centralDir.m_headers.GetSize();
for (int i = 0; i< iSize; i++)
{
time_t tFileInZipTime = m_centralDir[i]->GetTime();
if (tFileInZipTime > tNewestTime)
tNewestTime = tFileInZipTime;
}
}
m_centralDir.Clear();
CZipString szFileName = m_storage.Close(iAfterException == afAfterException);
if (bUpdateTimeStamp && !szFileName.IsEmpty())
ZipPlatform::SetFileModTime(szFileName, tNewestTime);
}
void CZipArchive::WriteCentralDirectory(bool bFlush)
{
m_centralDir.Write(GetCallback(cbSave));
if (bFlush)
m_storage.Flush();
}
void CZipArchive::SetCallback(CZipActionCallback* pCallback, int iWhich)
{
CallbackType cbs[] = {cbAdd, cbAddTmp, cbAddStore,cbExtract,cbDeleteCnt,cbDelete,cbTest,cbSave, cbGetFromArchive, cbRename, cbReplace};
int iCount = sizeof(cbs)/sizeof(CallbackType);
for (int i = 0; i < iCount; i++)
{
CallbackType iCallback = cbs[i];
if (iWhich & iCallback)
m_callbacks.Set(pCallback, iCallback);
}
}
void CZipArchive::SetAdvanced(int iWriteBuffer, int iGeneralBuffer, int iSearchBuffer)
{
if (!IsClosed())
{
TRACE(_T("%s(%i) : Set this options before opening the archive.\n"),__FILE__,__LINE__);
return;
}
m_storage.m_iWriteBufferSize = iWriteBuffer < 1024 ? 1024 : iWriteBuffer;
m_info.m_iBufferSize = iGeneralBuffer < 1024 ? 1024 : iGeneralBuffer;
m_centralDir.m_iBufferSize = iSearchBuffer < 1024 ? 1024 : iSearchBuffer;
}
int CZipArchive::CloseFile(CZipFile &file)
{
CZipString temp = file.GetFilePath();
file.Close();
return CloseFile(temp);
}
int CZipArchive::CloseFile(LPCTSTR lpszFilePath, bool bAfterException)
{
if (m_iFileOpened != extract)
{
TRACE(_T("%s(%i) : No opened file.\n"),__FILE__,__LINE__);
return false;
}
int iRet = 1;
if (!bAfterException)
{
if (m_info.m_uUncomprLeft == 0)
{
if (!m_bIgnoreCRC && m_info.m_uCrc32 != CurrentFile()->m_uCrc32)
ThrowError(CZipException::badCrc);
}
else
iRet = -1;
if (CurrentFile()->m_uMethod == Z_DEFLATED)
inflateEnd(&m_info.m_stream);
if (lpszFilePath)
{
if (!ZipPlatform::SetFileModTime(lpszFilePath, CurrentFile()->GetTime())
||!ZipPlatform::SetFileAttr(lpszFilePath, CurrentFile()->GetSystemAttr()))
iRet = -2;
}
}
m_centralDir.CloseFile(bAfterException);
m_iFileOpened = nothing;
m_info.ReleaseBuf();
EmptyPtrList();
return iRet;
}
bool CZipArchive::OpenNewFile(CZipFileHeader & header,
int iLevel,
LPCTSTR lpszFilePath, DWORD uInternal)
{
if (IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive is closed.\n"),__FILE__,__LINE__);
return false;
}
if (m_iFileOpened)
{
TRACE(_T("%s(%i) : A file already opened.\n"),__FILE__,__LINE__);
return false;
}
if (m_storage.IsSpanMode() == -1)
{
TRACE(_T("%s(%i) : You cannot add files to the existing disk spannig archive.\n"),__FILE__,__LINE__);
return false;
}
if (GetCount() ==(WORD)USHRT_MAX)
{
TRACE(_T("%s(%i) : Maximum file count inside archive reached.\n"),__FILE__,__LINE__);
return false;
}
DWORD uAttr = 0; // ..compiler
time_t ttime;
if (lpszFilePath)
{
if (!ZipPlatform::GetFileAttr(lpszFilePath, uAttr))
// do not continue - if the file was a directory then not recognizing it will cause
// serious errors (need uAttr to recognize it)
return false;
if (!ZipPlatform::GetFileModTime(lpszFilePath, ttime))
ttime = time(NULL);
}
m_info.Init();
if (lpszFilePath)
{
header.SetTime(ttime);
SetFileHeaderAttr(header, uAttr); // set system compatibility as well
}
else
header.SetSystemCompatibility(m_iArchiveSystCompatib);
CZipString szFileName = header.GetFileName();
bool bIsDirectory = header.IsDirectory();
if (bIsDirectory)
{
int iNameLen = szFileName.GetLength();
if (!iNameLen || !CZipPathComponent::IsSeparator(szFileName[iNameLen-1]))
{
szFileName += CZipPathComponent::m_cSeparator;
header.SetFileName(szFileName);
}
}
if (szFileName.IsEmpty())
{
szFileName.Format(_T("file%i"), GetCount());
header.SetFileName(szFileName);
}
// make sure that all slashes are correct (as the current system default)
// because AddNewFile calls InsertFindFastElement if necessary and
// the find array keeps all the files already converted to the current system standards
// we do not perform Oem->Ansi here, because who would pass oem values here?
//
ZipCompatibility::SlashBackslashChg(header.m_pszFileName, true);
bool bEncrypted = m_pszPassword.GetSize() != 0;
#ifdef _DEBUG
if (bIsDirectory && bEncrypted)
TRACE(_T("%s(%i) : Encrypting a directory. It's pointless.\n\
Clear the password before adding a directory.\n"),__FILE__,__LINE__);
#endif
int iReplaceIndex = -1;
bool bReplace = (iLevel & 0xffff) == ZIP_COMPR_REPL_SIGN;
if (bReplace)
{
int iMask = ZIP_COMPR_REPL_MASK;
iReplaceIndex = (iLevel & iMask) >> 16;
iLevel = (char)(iLevel & ~iMask);
ASSERT(iLevel == 0);
}
else
uInternal = 0;
if (iLevel < -1 || iLevel > 9)
iLevel = -1;
if (!header.PrepareData(iLevel, m_storage.IsSpanMode() == 1, bEncrypted))
ThrowError(CZipException::tooLongFileName);
if (bReplace)
{
uInternal += header.GetSize(true);
if (header.IsEncrypted())
uInternal += ZIPARCHIVE_ENCR_HEADER_LEN;
if (header.IsDataDescr())
uInternal += ZIPARCHIVE_DATADESCRIPTOR_LEN + 4; // CZipCentralDir::CloseNewFile
}
m_centralDir.AddNewFile(header, iReplaceIndex);
if (bReplace)
MakeSpaceForReplace(iReplaceIndex, uInternal, szFileName);
// this ensures the conversion will take place anyway (must take because we are going
// to write the local header in a moment
m_centralDir.ConvertFileName(false, m_centralDir.m_bConvertAfterOpen);
CurrentFile()->WriteLocal(m_storage);
// we have written the local header, but if we keep filenames not converted
// in memory , we have to restore the non-converted value
if (m_centralDir.m_bConvertAfterOpen)
CurrentFile()->SetFileName(szFileName);
if (bEncrypted)
{
CZipAutoBuffer buf(ZIPARCHIVE_ENCR_HEADER_LEN);
// use pseudo-crc since we don't know it yet
CryptCryptHeader((long)header.m_uModTime << 16, buf);
m_storage.Write(buf, ZIPARCHIVE_ENCR_HEADER_LEN, false);
}
m_info.m_uComprLeft = 0;
m_info.m_stream.avail_in = (uInt)0;
m_info.m_stream.avail_out = (uInt)m_info.m_pBuffer.GetSize();
m_info.m_stream.next_out = (Bytef*)(char*)m_info.m_pBuffer;
m_info.m_stream.total_in = 0;
m_info.m_stream.total_out = 0;
if (bIsDirectory && (CurrentFile()->m_uMethod != 0))
CurrentFile()->m_uMethod = 0;
if (CurrentFile()->m_uMethod == Z_DEFLATED)
{
// m_info.m_stream.opaque = m_bDetectZlibMemoryLeaks ? &m_list : 0;
int err = deflateInit2(&m_info.m_stream, iLevel,
Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
CheckForError(err);
}
m_iFileOpened = compress;
return true;
}
bool CZipArchive::ExtractFile(WORD uIndex,
LPCTSTR lpszPath,
bool bFullPath,
LPCTSTR lpszNewName,
DWORD nBufSize)
{
if (!nBufSize && !lpszPath)
return false;
CZipFileHeader header;
GetFileInfo(header, uIndex); // to ensure that slash and oem conversions take place
CZipString szFileNameInZip = (LPCTSTR)header.GetFileName();
CZipString szFile = PredictExtractedFileName(szFileNameInZip, lpszPath, bFullPath, lpszNewName);
CZipActionCallback* pCallback = GetCallback(cbExtract);
if (pCallback)
pCallback->Init(szFileNameInZip, szFile);
if (header.IsDirectory())
{
if (pCallback)
pCallback->SetTotal(0); // in case of calling LeftToDo afterwards
ZipPlatform::ForceDirectory(szFile);
ZipPlatform::SetFileAttr(szFile, header.GetSystemAttr());
if (pCallback)
pCallback->CallbackEnd();
return true;
}
else
{
if (pCallback)
pCallback->SetTotal(header.m_uUncomprSize);
if (!OpenFile(uIndex))
return false;
CZipPathComponent zpc(szFile);
ZipPlatform::ForceDirectory(zpc.GetFilePath());
CZipFile f(szFile, CZipFile::modeWrite |
CZipFile::modeCreate | CZipFile::shareDenyWrite);
DWORD iRead;
CZipAutoBuffer buf(nBufSize);
int iAborted = 0;
do
{
iRead = ReadFile(buf, buf.GetSize());
if (iRead)
{
f.Write(buf, iRead);
if (pCallback)
if (!(*pCallback)(iRead))
{
if (iRead == buf.GetSize() && ReadFile(buf, 1) != 0) // test one byte if there is something left
iAborted = CZipException::abortedAction;
else
iAborted = CZipException::abortedSafely; // we did it!
break;
}
}
}
while (iRead == buf.GetSize());
bool bRet = CloseFile(f) == 1;
if (!bRet && iAborted == CZipException::abortedSafely)
iAborted = CZipException::abortedAction; // sorry, finished, but not successfull
if (pCallback)
pCallback->CallbackEnd();
if (iAborted)
CZipException::Throw(iAborted, szFile); // throw to distuingiush from other return codes
return bRet;
}
}
bool CZipArchive::ExtractFile(WORD uIndex,
CZipMemFile& mf,
DWORD nBufSize)
{
if (!nBufSize)
return false;
CZipFileHeader header;
GetFileInfo(header, uIndex); // to ensure that slash and oem conversions take place
CZipActionCallback* pCallback = GetCallback(cbExtract);
if (pCallback)
{
pCallback->Init(header.GetFileName());
pCallback->SetTotal(header.m_uUncomprSize);
}
if (header.IsDirectory() || !OpenFile(uIndex))
return false;
CZipAutoBuffer buf(nBufSize);
mf.SeekToEnd();
DWORD iRead;
int iAborted = 0;
do
{
iRead = ReadFile(buf, buf.GetSize());
if (iRead)
{
mf.Write(buf, iRead);
if (pCallback)
if (!(*pCallback)(iRead))
{
if (iRead == buf.GetSize() && ReadFile(buf, 1) != 0) // test one byte if there is something left
iAborted = CZipException::abortedAction;
else
iAborted = CZipException::abortedSafely; // we did it!
break;
}
}
}
while (iRead == buf.GetSize());
bool bRet = CloseFile() == 1;
if (!bRet && iAborted == CZipException::abortedSafely)
iAborted = CZipException::abortedAction; // sorry, finished, but not successfull
if (pCallback)
pCallback->CallbackEnd();
if (iAborted)
CZipException::Throw(iAborted); // throw to distuingiush from other return codes
return bRet;
}
void CZipArchive::SetExtraField(const char *pBuf, WORD iSize)
{
if (m_iFileOpened != compress)
{
TRACE(_T("%s(%i) : A new file must be opened.\n"),__FILE__,__LINE__);
return;
}
if (!pBuf || !iSize)
return;
CurrentFile()->m_pExtraField.Allocate(iSize);
memcpy(CurrentFile()->m_pExtraField, pBuf, iSize);
}
bool CZipArchive::WriteNewFile(const void *pBuf, DWORD iSize)
{
if (m_iFileOpened != compress)
{
TRACE(_T("%s(%i) : A new file must be opened.\n"),__FILE__,__LINE__);
return false;
}
m_info.m_stream.next_in = (Bytef*)pBuf;
m_info.m_stream.avail_in = iSize;
CurrentFile()->m_uCrc32 = crc32(CurrentFile()->m_uCrc32, (Bytef*)pBuf, iSize);
while (m_info.m_stream.avail_in > 0)
{
if (m_info.m_stream.avail_out == 0)
{
CryptEncodeBuffer();
m_storage.Write(m_info.m_pBuffer, m_info.m_uComprLeft, false);
m_info.m_uComprLeft = 0;
m_info.m_stream.avail_out = m_info.m_pBuffer.GetSize();
m_info.m_stream.next_out = (Bytef*)(char*)m_info.m_pBuffer;
}
if (CurrentFile()->m_uMethod == Z_DEFLATED)
{
DWORD uTotal = m_info.m_stream.total_out;
int err = deflate(&m_info.m_stream, Z_NO_FLUSH);
CheckForError(err);
m_info.m_uComprLeft += m_info.m_stream.total_out - uTotal;
}
else
{
DWORD uToCopy = (m_info.m_stream.avail_in < m_info.m_stream.avail_out)
? m_info.m_stream.avail_in : m_info.m_stream.avail_out;
memcpy(m_info.m_stream.next_out, m_info.m_stream.next_in, uToCopy);
m_info.m_stream.avail_in -= uToCopy;
m_info.m_stream.avail_out -= uToCopy;
m_info.m_stream.next_in += uToCopy;
m_info.m_stream.next_out += uToCopy;
m_info.m_stream.total_in += uToCopy;
m_info.m_stream.total_out += uToCopy;
m_info.m_uComprLeft += uToCopy;
}
}
return true;
}
bool CZipArchive::CloseNewFile(bool bAfterException)
{
if (m_iFileOpened != compress)
{
TRACE(_T("%s(%i) : A new file must be opened.\n"),__FILE__,__LINE__);
return false;
}
m_info.m_stream.avail_in = 0;
if (!bAfterException)
{
int err = Z_OK;
if (CurrentFile()->m_uMethod == Z_DEFLATED)
while (err == Z_OK)
{
if (m_info.m_stream.avail_out == 0)
{
CryptEncodeBuffer();
m_storage.Write(m_info.m_pBuffer, m_info.m_uComprLeft, false);
m_info.m_uComprLeft = 0;
m_info.m_stream.avail_out = m_info.m_pBuffer.GetSize();
m_info.m_stream.next_out = (Bytef*)(char*)m_info.m_pBuffer;
}
DWORD uTotal = m_info.m_stream.total_out;
err = deflate(&m_info.m_stream, Z_FINISH);
m_info.m_uComprLeft += m_info.m_stream.total_out - uTotal;
}
if (err == Z_STREAM_END)
err = Z_OK;
CheckForError(err);
if (m_info.m_uComprLeft > 0)
{
CryptEncodeBuffer();
m_storage.Write(m_info.m_pBuffer, m_info.m_uComprLeft, false);
}
if (CurrentFile()->m_uMethod == Z_DEFLATED)
{
err = deflateEnd(&m_info.m_stream);
CheckForError(err);
}
// it may be increased by the encrypted header size
CurrentFile()->m_uComprSize += m_info.m_stream.total_out;
CurrentFile()->m_uUncomprSize = m_info.m_stream.total_in;
m_centralDir.CloseNewFile();
}
else
m_centralDir.m_pOpenedFile = NULL;
m_iFileOpened = nothing;
m_info.ReleaseBuf();
EmptyPtrList();
if (m_bAutoFlush && !bAfterException)
Flush();
return true;
}
void CZipArchive::DeleteFile(WORD uIndex)
{
CZipWordArray indexes;
indexes.Add(uIndex);
DeleteFiles(indexes);
}
void CZipArchive::GetIndexes(const CZipStringArray &aNames, CZipWordArray& aIndexes)
{
if (IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive is closed.\n"),__FILE__,__LINE__);
return;
}
int iSize = aNames.GetSize();
for (WORD i = 0; i < iSize; i++)
{
int idx = FindFile(aNames[i], ffDefault, false);
if (idx != -1)
aIndexes.Add((WORD)idx);
}
}
void CZipArchive::DeleteFiles(const CZipStringArray &aNames)
{
CZipWordArray indexes;
GetIndexes(aNames, indexes);
DeleteFiles(indexes);
}
void CZipArchive::DeleteFiles(CZipWordArray &aIndexes)
{
if (IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive is closed.\n"),__FILE__,__LINE__);
return;
}
if (m_storage.IsSpanMode())
{
TRACE(_T("%s(%i) : You cannot delete files from the disk spannig archive.\n"),__FILE__,__LINE__);
return;
}
if (m_iFileOpened)
{
TRACE(_T("%s(%i) : You cannot delete files if there is a file opened.\n"),__FILE__,__LINE__);
return;
}
CZipActionCallback* pCallback = GetCallback(cbDeleteCnt);
if (pCallback)
pCallback->Init();
int uSize = aIndexes.GetSize();
if (!uSize)
{
TRACE(_T("%s(%i) : The indekses array is empty.\n"),__FILE__,__LINE__);
return;
}
// remove all - that's easy so don't waste the time
if (uSize == GetCount())
{
pCallback = GetCallback(cbDelete);
if (pCallback)
{
// do it right and sent the notification
pCallback->Init();
pCallback->SetTotal(uSize);
}
m_centralDir.RemoveFromDisk();
m_storage.m_pFile->SetLength(m_centralDir.GetBytesBefore());
m_centralDir.RemoveAll();
if (m_bAutoFlush)
Flush();
if (pCallback)
pCallback->CallbackEnd();
return;
}
aIndexes.Sort(true);
CZipArray<CZipDeleteInfo> aInfo;
int iDelIndex = 0;
int iStep = 0; // for the compiler
if (pCallback)
{
pCallback->SetTotal(GetCount());
iStep = CZipActionCallback::m_iStep; // we don't want to wait forever
}
int i;
int uMaxDelIndex = aIndexes[uSize - 1];
for (i = aIndexes[0]; i < GetCount(); i++)
{
CZipFileHeader* pHeader = m_centralDir[i];
bool bDelete;
if (i <= uMaxDelIndex && i == aIndexes[iDelIndex])
{
iDelIndex++;
bDelete = true;
}
else
bDelete = false;
aInfo.Add(CZipDeleteInfo(pHeader, bDelete));
if (pCallback && (!(i % iStep)))
if (!(*pCallback)(iStep))
ThrowError(CZipException::abortedSafely);
}
ASSERT(iDelIndex == uSize);
uSize = aInfo.GetSize();
if (!uSize) // it is possible
return;
// now we start deleting (not safe to break)
pCallback = GetCallback(cbDelete);
if (pCallback)
pCallback->Init();
m_centralDir.RemoveFromDisk();
DWORD uTotalToMoveBytes = 0, uLastOffset = m_storage.m_pFile->GetLength() - m_centralDir.GetBytesBefore();
// count the number of bytes to move
for (i = uSize - 1; i >=0 ; i--)
{
const CZipDeleteInfo& di = aInfo[i];
if (!di.m_bDelete)
uTotalToMoveBytes += uLastOffset - di.m_pHeader->m_uOffset;
uLastOffset = di.m_pHeader->m_uOffset;
}
if (pCallback)
pCallback->CallbackEnd();
if (pCallback)
pCallback->SetTotal(uTotalToMoveBytes);
m_info.Init();
DWORD uMoveBy = 0, uOffsetStart = 0;
for (i = 0; i < uSize; i++)
{
const CZipDeleteInfo& di = aInfo[i];
if (di.m_bDelete)
{
// next hole
DWORD uTemp = di.m_pHeader->m_uOffset;
m_centralDir.RemoveFile(di.m_pHeader); // first remove
if (uOffsetStart)
{
// copy the files over a previous holes
MovePackedFiles(uOffsetStart, uTemp, uMoveBy, pCallback);
uOffsetStart = 0; // never be at the beginning, because the first file is always to be deleted
}
if (i == uSize - 1)
uTemp = (m_storage.m_pFile->GetLength() - m_centralDir.GetBytesBefore()) - uTemp;
else
uTemp = aInfo[i+1].m_pHeader->m_uOffset - uTemp;
uMoveBy += uTemp;
}
else
{
if (uOffsetStart == 0) // find contiuos area to move
uOffsetStart = di.m_pHeader->m_uOffset;
di.m_pHeader->m_uOffset -= uMoveBy;
}
}
if (uOffsetStart)
MovePackedFiles(uOffsetStart,
m_storage.m_pFile->GetLength() - m_centralDir.GetBytesBefore(),
uMoveBy, pCallback);
m_info.ReleaseBuf();
if (uMoveBy) // just in case
m_storage.m_pFile->SetLength(m_storage.m_pFile->GetLength() - uMoveBy);
if (pCallback)
pCallback->CallbackEnd();
if (m_bAutoFlush)
Flush();
}
bool CZipArchive::AddNewFile(LPCTSTR lpszFilePath,
int iComprLevel,
bool bFullPath,
int iSmartLevel,
unsigned long nBufSize)
{
CZipAddNewFileInfo zanfi (lpszFilePath, bFullPath);
zanfi.m_iComprLevel = iComprLevel;
zanfi.m_iSmartLevel = zipsmSafeSmart;
zanfi.m_nBufSize = nBufSize;
return AddNewFile(zanfi);
}
bool CZipArchive::AddNewFileDrv(LPCTSTR lpszFilePath,
int iComprLevel,
bool bFullPath,
int iSmartLevel,
unsigned long nBufSize)
{
CZipAddNewFileInfo zanfi (lpszFilePath, bFullPath);
zanfi.m_iComprLevel = iComprLevel;
zanfi.m_iSmartLevel = zipsmSafeSmart;
zanfi.m_nBufSize = nBufSize;
return AddNewFileDrv(zanfi);
}
bool CZipArchive::AddNewFile(LPCTSTR lpszFilePath,
LPCTSTR lpszFileNameInZip,
int iComprLevel,
int iSmartLevel,
unsigned long nBufSize)
{
CZipAddNewFileInfo zanfi(lpszFilePath, lpszFileNameInZip);
zanfi.m_iComprLevel = iComprLevel;
zanfi.m_iSmartLevel = zipsmSafeSmart;
zanfi.m_nBufSize = nBufSize;
return AddNewFile(zanfi);
}
bool CZipArchive::AddNewFile(CZipMemFile& mf,
LPCTSTR lpszFileNameInZip,
int iComprLevel,
int iSmartLevel,
unsigned long nBufSize)
{
CZipAddNewFileInfo zanfi(&mf, lpszFileNameInZip);
zanfi.m_iComprLevel = iComprLevel;
zanfi.m_iSmartLevel = zipsmSafeSmart;
zanfi.m_nBufSize = nBufSize;
return AddNewFile(zanfi);
}
bool CZipArchive::AddNewFileDrv(CZipAddNewFileInfo& info)
{
// no need for ASSERT and TRACE here - it will be done by OpenNewFile
if (!m_info.m_iBufferSize)
return false;
CZipPathComponent::RemoveSeparators(info.m_szFilePath);
if (!info.m_szFilePath.IsEmpty()) // it may be empty after removing sep.
{
if (info.m_szFileNameInZip.IsEmpty())
{
CZipPathComponent zpc(info.m_szFilePath);
info.m_szFileNameInZip = info.m_bFullPath ? zpc.GetNoDrive() : TrimRootPath(zpc);
}
}
else if (!info.m_pFile)
return false;
bool bSpan = GetSpanMode() != 0;
// checking the iReplace index
if (!UpdateReplaceIndex(info.m_iReplaceIndex, info.m_szFileNameInZip))
return false;
bool bReplace = info.m_iReplaceIndex >= 0;
DWORD uAttr;
time_t ttime;
if (info.m_pFile)
{
uAttr = ZipPlatform::GetDefaultAttributes();
ttime = time(NULL);
}
else
{
if (!ZipPlatform::GetFileAttr(info.m_szFilePath, uAttr))
return false; // we don't know whether it is a file or a directory
if (!ZipPlatform::GetFileModTime(info.m_szFilePath, ttime))
ttime = time(NULL);
}
CZipFileHeader header;
header.SetFileName(info.m_szFileNameInZip);
if (ZipPlatform::GetSystemID() != ZipCompatibility::zcUnix)
uAttr |= ZipCompatibility::ConvertToSystem(uAttr, ZipPlatform::GetSystemID(), ZipCompatibility::zcUnix); // make it readable under Unix as well, since it stores its attributes in HIWORD(uAttr)
SetFileHeaderAttr(header, uAttr);
header.SetTime(ttime);
bool bInternal = (info.m_iSmartLevel & zipsmInternal01) != 0;
CZipActionCallback* pCallback = NULL;
if (!bInternal)
{
pCallback = GetCallback(cbAdd);
if (pCallback)
pCallback->Init(info.m_szFileNameInZip, info.m_szFilePath);
}
if (header.IsDirectory()) // will never be when m_pFile is not NULL, so we don't check it
{
ASSERT(!info.m_pFile); // should never happened
ASSERT(!bInternal);
if (pCallback)
pCallback->SetTotal(0); // in case of calling LeftToDo afterwards
// clear password for a directory
bool bRet = false;
CZipSmClrPass smcp;
if (info.m_iSmartLevel & zipsmCPassDir)
smcp.ClearPasswordSmartly(this);
bRet = OpenNewFile(header, bReplace ? (info.m_iReplaceIndex << 16) | ZIP_COMPR_REPL_SIGN : 0);
CloseNewFile();
if (pCallback)
pCallback->CallbackEnd();
return bRet;
}
CZipSmClrPass smcp;
bool bIsCompression = info.m_iComprLevel != 0;
bool bEff = (info.m_iSmartLevel & zipsmCheckForEff)&& bIsCompression;
bool bCheckForZeroSized = (info.m_iSmartLevel & zipsmCPFile0) && !GetPassword().IsEmpty();
bool bCheckForSmallFiles = (info.m_iSmartLevel & zipsmNotCompSmall) && bIsCompression;
DWORD iFileSize = DWORD(-1);
bool bNeedTempArchive = (bEff && bSpan) || (bReplace && bIsCompression);
if (bCheckForSmallFiles || bCheckForZeroSized || bNeedTempArchive)
{
if (info.m_pFile)
iFileSize = info.m_pFile->GetLength();
else
{
if (!ZipPlatform::GetFileSize(info.m_szFilePath, iFileSize) && bEff)
bEff = false; // the file size is needed only when eff. in span mode
}
if (iFileSize != DWORD(-1))
{
if (bCheckForZeroSized && iFileSize == 0)
smcp.ClearPasswordSmartly(this);
if (bCheckForSmallFiles && iFileSize < 5)
info.m_iComprLevel = 0;
}
}
bool bEffInMem = bEff && (info.m_iSmartLevel & zipsmMemoryFlag);
CZipString szTempFileName;
if (bNeedTempArchive && (bEffInMem ||
!(szTempFileName = ZipPlatform::GetTmpFileName(
m_szTempPath.IsEmpty() ? NULL : (LPCTSTR)m_szTempPath, iFileSize)
).IsEmpty()))
{
CZipMemFile* pmf = NULL;
CZipArchive zip;
try
{
// compress first to a temporary file, if ok - copy the data, if not - add storing
if (bEffInMem)
{
pmf = new CZipMemFile;
zip.Open(*pmf, zipCreate);
}
else
zip.Open(szTempFileName, zipCreate);
zip.SetRootPath(m_szRootPath);
zip.SetPassword(GetPassword());
zip.SetSystemCompatibility(m_iArchiveSystCompatib);
zip.SetCallback(pCallback, cbAdd);
// create a temporary file
int iTempReplaceIndex = info.m_iReplaceIndex;
info.m_iSmartLevel = zipsmLazy;
info.m_iReplaceIndex = -1;
if (!zip.AddNewFile(info))
throw false;
info.m_iReplaceIndex = iTempReplaceIndex;
// this may also happen when bReplace, but not in span mode
if (bEff)
{
CZipFileHeader fh;
zip.GetFileInfo(fh, 0);
if (!fh.CompressionEfficient())
{
info.m_iComprLevel = 0;
info.m_iSmartLevel = zipsmInternal01;
// compression is pointless, store instead
throw AddNewFile(info);
}
}
m_info.Init();
throw GetFromArchive(zip, 0, info.m_iReplaceIndex, true, GetCallback(cbAddTmp));
}
catch (bool bRet)
{
zip.Close(!bRet); // that doesn't really matter how it will be closed
if (pmf)
delete pmf;
if (!bEffInMem)
ZipPlatform::RemoveFile(szTempFileName, false);
m_info.ReleaseBuf();
return bRet;
}
catch (...)
{
zip.Close(true);
if (pmf)
delete pmf;
if (!bEffInMem)
ZipPlatform::RemoveFile(szTempFileName, false);
m_info.ReleaseBuf();
throw;
}
}
// try to open before adding
CZipFile f;
CZipAbstractFile *pf;
if (info.m_pFile)
pf = info.m_pFile;
else
{
if (!f.Open(info.m_szFilePath, CZipFile::modeRead | CZipFile::shareDenyWrite, false))
{
if (pCallback)
pCallback->CallbackEnd();
return false;
}
pf = &f;
}
ASSERT(pf);
// call init before opening (in case of exception we have the names)
iFileSize = pf->GetLength();
bool bRet;
if (bReplace)
{
ASSERT(!bIsCompression);
bRet = OpenNewFile(header, (info.m_iReplaceIndex << 16) | ZIP_COMPR_REPL_SIGN , NULL, iFileSize);
}
else
bRet = OpenNewFile(header, info.m_iComprLevel);
if (!bRet)
{
if (pCallback)
pCallback->CallbackEnd();
return false;
}
if (bInternal)
{
// we do it here, because if in OpenNewFile is replacing
// then we get called cbReplace callback before and it would
// overwrite callback information written in pCallback->Init
pCallback = GetCallback(cbAddStore);
if (pCallback)
pCallback->Init(info.m_szFileNameInZip, info.m_szFilePath);
}
if (pCallback)
pCallback->SetTotal(iFileSize);
CZipAutoBuffer buf(info.m_nBufSize);
DWORD iRead;
int iAborted = 0;
do
{
iRead = pf->Read(buf, info.m_nBufSize);
if (iRead)
{
WriteNewFile(buf, iRead);
if (pCallback)
if (!(*pCallback)(iRead))
{
// todo: we could remove here the bytes of the file partially added if not disk-spanning
if (iRead == buf.GetSize() && pf->Read(buf, 1) != 0) // test one byte if there is something left
{
if (!m_storage.IsSpanMode() && !bReplace)
{
RemoveLast(true);
CloseNewFile(true);
iAborted = CZipException::abortedSafely;
}
else
iAborted = CZipException::abortedAction;
}
else
{
iAborted = CZipException::abortedSafely; // we did it!
CloseNewFile();
}
break;
}
}
}
while (iRead == buf.GetSize());
if (!iAborted)
CloseNewFile();
if (pCallback)
pCallback->CallbackEnd();
if (iAborted)
CZipException::Throw(iAborted); // throw to distuinguish from other return codes
if (bEff)
{
// remove the last file and add it without the compression if needed
if (!info.m_pFile)
f.Close();
buf.Release();
if (RemoveLast())
{
info.m_iComprLevel = 0;
info.m_iSmartLevel = zipsmInternal01;
return AddNewFile(info);
}
}
return true;
}
bool CZipArchive::AddNewFile(CZipAddNewFileInfo& info)
{
// no need for ASSERT and TRACE here - it will be done by OpenNewFile
if (!m_info.m_iBufferSize)
return false;
CZipPathComponent::RemoveSeparators(info.m_szFilePath);
if (!info.m_szFilePath.IsEmpty()) // it may be empty after removing sep.
{
if (info.m_szFileNameInZip.IsEmpty())
{
CZipPathComponent zpc(info.m_szFilePath);
if (info.m_bFullPath)
{
if (m_bRemoveDriveLetter)
info.m_szFileNameInZip = zpc.GetNoDrive();
}
else
info.m_szFileNameInZip = TrimRootPath(zpc);
}
}
else if (!info.m_pFile)
return false;
bool bSpan = GetSpanMode() != 0;
// checking the iReplace index
if (!UpdateReplaceIndex(info.m_iReplaceIndex, info.m_szFileNameInZip))
return false;
bool bReplace = info.m_iReplaceIndex >= 0;
DWORD uAttr;
time_t ttime;
if (info.m_pFile)
{
uAttr = ZipPlatform::GetDefaultAttributes();
ttime = time(NULL);
}
else
{
if (!ZipPlatform::GetFileAttr(info.m_szFilePath, uAttr))
return false; // we don't know whether it is a file or a directory
if (!ZipPlatform::GetFileModTime(info.m_szFilePath, ttime))
ttime = time(NULL);
}
CZipFileHeader header;
header.SetFileName(info.m_szFileNameInZip);
if (ZipPlatform::GetSystemID() != ZipCompatibility::zcUnix)
uAttr |= ZipCompatibility::ConvertToSystem(uAttr, ZipPlatform::GetSystemID(), ZipCompatibility::zcUnix); // make it readable under Unix as well, since it stores its attributes in HIWORD(uAttr)
SetFileHeaderAttr(header, uAttr);
header.SetTime(ttime);
bool bInternal = (info.m_iSmartLevel & zipsmInternal01) != 0;
CZipActionCallback* pCallback = NULL;
if (!bInternal)
{
pCallback = GetCallback(cbAdd);
if (pCallback)
pCallback->Init(info.m_szFileNameInZip, info.m_szFilePath);
}
if (header.IsDirectory()) // will never be when m_pFile is not NULL, so we don't check it
{
ASSERT(!info.m_pFile); // should never happened
ASSERT(!bInternal);
if (pCallback)
pCallback->SetTotal(0); // in case of calling LeftToDo afterwards
// clear password for a directory
bool bRet = false;
CZipSmClrPass smcp;
if (info.m_iSmartLevel & zipsmCPassDir)
smcp.ClearPasswordSmartly(this);
bRet = OpenNewFile(header, bReplace ? (info.m_iReplaceIndex << 16) | ZIP_COMPR_REPL_SIGN : 0);
CloseNewFile();
if (pCallback)
pCallback->CallbackEnd();
return bRet;
}
CZipSmClrPass smcp;
bool bIsCompression = info.m_iComprLevel != 0;
bool bEff = (info.m_iSmartLevel & zipsmCheckForEff)&& bIsCompression;
bool bCheckForZeroSized = (info.m_iSmartLevel & zipsmCPFile0) && !GetPassword().IsEmpty();
bool bCheckForSmallFiles = (info.m_iSmartLevel & zipsmNotCompSmall) && bIsCompression;
DWORD iFileSize = DWORD(-1);
bool bNeedTempArchive = (bEff && bSpan) || (bReplace && bIsCompression);
if (bCheckForSmallFiles || bCheckForZeroSized || bNeedTempArchive)
{
if (info.m_pFile)
iFileSize = info.m_pFile->GetLength();
else
{
if (!ZipPlatform::GetFileSize(info.m_szFilePath, iFileSize) && bEff)
bEff = false; // the file size is needed only when eff. in span mode
}
if (iFileSize != DWORD(-1))
{
if (bCheckForZeroSized && iFileSize == 0)
smcp.ClearPasswordSmartly(this);
if (bCheckForSmallFiles && iFileSize < 5)
info.m_iComprLevel = 0;
}
}
bool bEffInMem = bEff && (info.m_iSmartLevel & zipsmMemoryFlag);
CZipString szTempFileName;
if (bNeedTempArchive && (bEffInMem ||
!(szTempFileName = ZipPlatform::GetTmpFileName(
m_szTempPath.IsEmpty() ? NULL : (LPCTSTR)m_szTempPath, iFileSize)
).IsEmpty()))
{
CZipMemFile* pmf = NULL;
CZipArchive zip;
try
{
// compress first to a temporary file, if ok - copy the data, if not - add storing
if (bEffInMem)
{
pmf = new CZipMemFile;
zip.Open(*pmf, zipCreate);
}
else
zip.Open(szTempFileName, zipCreate);
zip.SetRootPath(m_szRootPath);
zip.SetPassword(GetPassword());
zip.SetSystemCompatibility(m_iArchiveSystCompatib);
zip.SetCallback(pCallback, cbAdd);
// create a temporary file
int iTempReplaceIndex = info.m_iReplaceIndex;
info.m_iSmartLevel = zipsmLazy;
info.m_iReplaceIndex = -1;
if (!zip.AddNewFile(info))
throw false;
info.m_iReplaceIndex = iTempReplaceIndex;
// this may also happen when bReplace, but not in span mode
if (bEff)
{
CZipFileHeader fh;
zip.GetFileInfo(fh, 0);
if (!fh.CompressionEfficient())
{
info.m_iComprLevel = 0;
info.m_iSmartLevel = zipsmInternal01;
// compression is pointless, store instead
throw AddNewFile(info);
}
}
m_info.Init();
throw GetFromArchive(zip, 0, info.m_iReplaceIndex, true, GetCallback(cbAddTmp));
}
catch (bool bRet)
{
zip.Close(!bRet); // that doesn't really matter how it will be closed
if (pmf)
delete pmf;
if (!bEffInMem)
ZipPlatform::RemoveFile(szTempFileName, false);
m_info.ReleaseBuf();
return bRet;
}
catch (...)
{
zip.Close(true);
if (pmf)
delete pmf;
if (!bEffInMem)
ZipPlatform::RemoveFile(szTempFileName, false);
m_info.ReleaseBuf();
throw;
}
}
// try to open before adding
CZipFile f;
CZipAbstractFile *pf;
if (info.m_pFile)
pf = info.m_pFile;
else
{
// cannot be shareDenyWrite
// from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/base/creating_and_opening_files.asp :
// If you specify the GENERIC_READ and GENERIC_WRITE access modes along with the FILE_SHARE_READ and FILE_SHARE_WRITE sharing modes in your first call to CreateFile. If you specify the GENERIC_READ and GENERIC_WRITE access modes and the FILE_SHARE_READ sharing mode only in your second call to CreateFile, the function will fail with a sharing violation because the read-only sharing mode specified in the second call conflicts with the read/write access that has been granted in the first call.
if (!f.Open(info.m_szFilePath, CZipFile::modeRead | CZipFile::shareDenyNone, false))
{
if (pCallback)
pCallback->CallbackEnd();
return false;
}
pf = &f;
}
ASSERT(pf);
// call init before opening (in case of exception we have the names)
iFileSize = pf->GetLength();
bool bRet;
if (bReplace)
{
ASSERT(!bIsCompression);
bRet = OpenNewFile(header, (info.m_iReplaceIndex << 16) | ZIP_COMPR_REPL_SIGN , NULL, iFileSize);
}
else
bRet = OpenNewFile(header, info.m_iComprLevel);
if (!bRet)
{
if (pCallback)
pCallback->CallbackEnd();
return false;
}
if (bInternal)
{
// we do it here, because if in OpenNewFile is replacing
// then we get called cbReplace callback before and it would
// overwrite callback information written in pCallback->Init
pCallback = GetCallback(cbAddStore);
if (pCallback)
pCallback->Init(info.m_szFileNameInZip, info.m_szFilePath);
}
if (pCallback)
pCallback->SetTotal(iFileSize);
CZipAutoBuffer buf(info.m_nBufSize);
DWORD iRead;
int iAborted = 0;
do
{
iRead = pf->Read(buf, info.m_nBufSize);
if (iRead)
{
WriteNewFile(buf, iRead);
if (pCallback)
if (!(*pCallback)(iRead))
{
// todo: we could remove here the bytes of the file partially added if not disk-spanning
if (iRead == buf.GetSize() && pf->Read(buf, 1) != 0) // test one byte if there is something left
{
if (!m_storage.IsSpanMode() && !bReplace)
{
RemoveLast(true);
CloseNewFile(true);
iAborted = CZipException::abortedSafely;
}
else
iAborted = CZipException::abortedAction;
}
else
{
iAborted = CZipException::abortedSafely; // we did it!
CloseNewFile();
}
break;
}
}
}
while (iRead == buf.GetSize());
if (!iAborted)
CloseNewFile();
if (pCallback)
pCallback->CallbackEnd();
if (iAborted)
CZipException::Throw(iAborted); // throw to distuinguish from other return codes
if (bEff)
{
// remove the last file and add it without the compression if needed
if (!info.m_pFile)
f.Close();
buf.Release();
if (RemoveLast())
{
info.m_iComprLevel = 0;
info.m_iSmartLevel = zipsmInternal01;
return AddNewFile(info);
}
}
return true;
}
bool CZipArchive::RemoveLast(bool bRemoveAnyway)
{
int iIndex = GetCount() - 1;
if (iIndex < 0)
return false;
CZipFileHeader* pHeader = m_centralDir[iIndex];
if (!bRemoveAnyway && pHeader->CompressionEfficient())
return false;
m_centralDir.RemoveLastFile(pHeader, iIndex);
return true;
}
CZipString CZipArchive::GetArchivePath() const
{
if (IsClosed(false))
{
TRACE(_T("%s(%i) : ZipArchive is closed.\n"),__FILE__,__LINE__);
return _T("");
}
return m_storage.m_pFile->GetFilePath();
}
CZipString CZipArchive::GetGlobalComment() const
{
if (IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive is closed.\n"),__FILE__,__LINE__);
return _T("");
}
CZipString temp;
return SingleToWide(m_centralDir.m_pszComment, temp) != -1 ? (LPCTSTR)temp : _T("");
}
bool CZipArchive::SetGlobalComment(LPCTSTR lpszComment)
{
if (IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive is closed.\n"),__FILE__,__LINE__);
return false;
}
if (m_storage.IsSpanMode() == -1)
{
TRACE(_T("%s(%i) : You cannot modify the global comment of the existing disk spanning archive.\n"),__FILE__,__LINE__);
return false;
}
WideToSingle(lpszComment, m_centralDir.m_pszComment);
m_centralDir.RemoveFromDisk();
if (m_bAutoFlush)
Flush();
return true;
}
int CZipArchive::GetCurrentDisk() const
{
return m_storage.GetCurrentDisk() + 1;
}
bool CZipArchive::SetFileComment(WORD uIndex, LPCTSTR lpszComment)
{
if (IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive is closed.\n"),__FILE__,__LINE__);
return false;
}
if (m_storage.IsSpanMode() == -1)
{
TRACE(_T("%s(%i) : You cannot modify the file comment in the existing disk spanning archive.\n"),__FILE__,__LINE__);
return false;
}
if (!m_centralDir.IsValidIndex(uIndex))
{
ASSERT(FALSE);
return false;
}
m_centralDir[uIndex]->SetComment(lpszComment);
m_centralDir.RemoveFromDisk();
if (m_bAutoFlush)
Flush();
return true;
}
void CZipArchive::CryptInitKeys()
{
ASSERT(m_pszPassword.GetSize());
m_keys[0] = 305419896L;
m_keys[1] = 591751049L;
m_keys[2] = 878082192L;
for (DWORD i = 0; i < m_pszPassword.GetSize(); i++)
CryptUpdateKeys(m_pszPassword[i]);
}
void CZipArchive::CryptUpdateKeys(char c)
{
m_keys[0] = CryptCRC32(m_keys[0], c);
m_keys[1] += m_keys[0] & 0xff;
m_keys[1] = m_keys[1] * 134775813L + 1;
c = char(m_keys[1] >> 24);
m_keys[2] = CryptCRC32(m_keys[2], c);
}
bool CZipArchive::CryptCheck()
{
CZipAutoBuffer buf(ZIPARCHIVE_ENCR_HEADER_LEN);
m_storage.Read(buf, ZIPARCHIVE_ENCR_HEADER_LEN, false);
BYTE b = 0;
for (int i = 0; i < ZIPARCHIVE_ENCR_HEADER_LEN; i++)
{
b = buf[i]; // only temporary
CryptDecode((char&)b);
}
// check the last byte
return CurrentFile()->IsDataDescr() ?
(BYTE(CurrentFile()->m_uModTime >> 8) == b) : (BYTE(CurrentFile()->m_uCrc32 >> 24) == b);
}
char CZipArchive::CryptDecryptByte()
{
int temp = (m_keys[2] & 0xffff) | 2;
return (char)(((temp * (temp ^ 1)) >> 8) & 0xff);
}
void CZipArchive::CryptDecode(char &c)
{
c ^= CryptDecryptByte();
CryptUpdateKeys(c);
}
bool CZipArchive::SetPassword(LPCTSTR lpszPassword)
{
if (m_iFileOpened != nothing)
{
TRACE(_T("%s(%i) : You cannot change the password when the file is opened.\n"),__FILE__,__LINE__);
return false; // it's important not to change the password when the file inside archive is opened
}
if (IsClosed())
{
TRACE(_T("%s(%i) : Setting the password for a closed archive has no effect.\n"),__FILE__,__LINE__);
}
if (lpszPassword)
{
int iLen = WideToSingle(lpszPassword, m_pszPassword);
if (iLen == -1)
return false;
for (size_t i = 0; (int)i < iLen; i++)
if (m_pszPassword[i] <= 0)
{
m_pszPassword.Release();
TRACE(_T("%s(%i) : The password contains forbidden characters. Password cleared.\n"),__FILE__,__LINE__);
return false;
}
}
else
m_pszPassword.Release();
return true;
}
CZipString CZipArchive::GetPassword()const
{
CZipString temp;
CZipArchive::SingleToWide(m_pszPassword, temp);
return temp;
}
DWORD CZipArchive::CryptCRC32(DWORD l, char c)
{
const DWORD *CRC_TABLE = get_crc_table();
return CRC_TABLE[(l ^ c) & 0xff] ^ (l >> 8);
}
void CZipArchive::CryptCryptHeader(long iCrc, CZipAutoBuffer &buf)
{
CryptInitKeys();
srand(UINT(time(NULL)));
// genereate pseudo-random sequence
char c;
for (int i = 0; i < ZIPARCHIVE_ENCR_HEADER_LEN - 2; i++)
{
int t1 = rand();
c = (char)(t1 >> 6);
if (!c)
c = (char)t1;
CryptEncode(c);
buf[i] = c;
}
c = (char)((iCrc >> 16) & 0xff);
CryptEncode(c);
buf[ZIPARCHIVE_ENCR_HEADER_LEN - 2] = c;
c = (char)((iCrc >> 24) & 0xff);
CryptEncode(c);
buf[ZIPARCHIVE_ENCR_HEADER_LEN - 1] = c;
}
void CZipArchive::CryptEncode(char &c)
{
char t = CryptDecryptByte();
CryptUpdateKeys(c);
c ^= t;
}
void CZipArchive::CryptEncodeBuffer()
{
if (CurrentFile()->IsEncrypted())
for (DWORD i = 0; i < m_info.m_uComprLeft; i++)
CryptEncode(m_info.m_pBuffer[i]);
}
void CZipArchive::CloseFileAfterTestFailed()
{
if (m_iFileOpened != extract)
{
TRACE(_T("%s(%i) : No file opened.\n"),__FILE__,__LINE__);
return;
}
m_info.ReleaseBuf();
m_centralDir.Clear(false);
m_iFileOpened = nothing;
}
bool CZipArchive::TestFile(WORD uIndex, DWORD uBufSize)
{
if (m_storage.IsSpanMode() == 1)
{
TRACE(_T("%s(%i) : You cannot test the spanning archive in creation.\n"),__FILE__,__LINE__);
return false;
}
if (!uBufSize)
return false;
CZipFileHeader* pHeader = m_centralDir[uIndex];
CZipActionCallback* pCallback = GetCallback(cbTest);
if (pCallback)
{
pCallback->Init(m_centralDir.GetProperHeaderFileName(pHeader));
}
if (pHeader->IsDirectory())
{
if (pCallback)
pCallback->SetTotal(0);
// we do not test whether the password for the encrypted directory
// is correct, since it seems to be senseless (anyway password
// encrypted directories should be avoided - it adds 12 bytes)
DWORD iSize = pHeader->m_uComprSize;
if ((iSize != 0 || iSize != pHeader->m_uUncomprSize)
// different treating compressed directories
&& !(pHeader->IsEncrypted() && iSize == 12 && !pHeader->m_uUncomprSize))
CZipException::Throw(CZipException::dirWithSize);
if (pCallback)
pCallback->CallbackEnd();
return true;
}
else
{
try
{
if (pCallback)
pCallback->SetTotal(pHeader->m_uUncomprSize);
if (!OpenFile(uIndex))
return false;
CZipAutoBuffer buf(uBufSize);
DWORD iRead;
int iAborted = 0;
do
{
iRead = ReadFile(buf, buf.GetSize());
if (pCallback && iRead)
if (!(*pCallback)(iRead))
{
if (iRead == buf.GetSize() && ReadFile(buf, 1) != 0) // test one byte if there is something left
iAborted = CZipException::abortedAction;
else
iAborted = CZipException::abortedSafely; // we did it!
break;
}
}
while (iRead == buf.GetSize());
bool bRet = CloseFile() != -1;
if (!bRet && iAborted == CZipException::abortedSafely)
iAborted = CZipException::abortedAction; // sorry, finished, but not successfull
if (pCallback)
pCallback->CallbackEnd();
if (iAborted)
CZipException::Throw(iAborted); // throw to distuingiush from other return codes
if (bRet)
return true;
else
CZipException::Throw(CZipException::badZipFile);
return false; // to satisfy the compiler and eliminate warning
}
catch(...)
{
CloseFileAfterTestFailed();
throw;
}
}
}
int CZipArchive::WideToSingle(LPCTSTR lpWide, CZipAutoBuffer &szSingle)
{
#ifdef _UNICODE
return ZipPlatform::WideToSingle(lpWide, szSingle);
#else
size_t iLen = strlen(lpWide);
// if not UNICODE just copy
// iLen does not include the NULL character
szSingle.Allocate(iLen);
memcpy(szSingle, lpWide, iLen);
return iLen;
#endif
}
int CZipArchive::SingleToWide(const CZipAutoBuffer &szSingle, CZipString& szWide)
{
#ifdef _UNICODE
return ZipPlatform::SingleToWide(szSingle, szWide);
#else // if not UNICODE just copy
int singleLen = szSingle.GetSize();
// iLen does not include the NULL character
memcpy(szWide.GetBuffer(singleLen),szSingle.GetBuffer(), singleLen);
szWide.ReleaseBuffer(singleLen);
return singleLen;
#endif
}
void CZipArchive::CryptDecodeBuffer(DWORD uCount)
{
if (CurrentFile()->IsEncrypted())
for (DWORD i = 0; i < uCount; i++)
CryptDecode(m_info.m_pBuffer[i]);
}
void CZipArchive::EmptyPtrList()
{
if (m_list.GetCount())
{
// if some memory hasn't been freed due to an error in zlib, so free it now
CZipPtrListIter iter = m_list.GetHeadPosition();
while (m_list.IteratorValid(iter))
delete[] (char*) m_list.GetNext(iter);
}
m_list.RemoveAll();
}
void CZipArchive::SetFileHeaderAttr(CZipFileHeader& header, DWORD uAttr)
{
header.SetSystemCompatibility(m_iArchiveSystCompatib);
header.SetSystemAttr(uAttr);
}
void CZipArchive::EnableFindFast(bool bEnable)
{
if (IsClosed())
{
TRACE(_T("%s(%i) : Set it after opening the archive.\n"),__FILE__,__LINE__);
return;
}
m_centralDir.EnableFindFast(bEnable, m_bCaseSensitive);
}
bool CZipArchive::SetSystemCompatibility(int iSystemComp)
{
if (IsClosed())
{
TRACE(_T("%s(%i) : Set it after opening the archive.\n"),__FILE__,__LINE__);
return false;
}
if (m_iFileOpened == compress)
{
TRACE(_T("%s(%i) : Set it before opening a file inside archive.\n"),__FILE__,__LINE__);
return false;
}
if (!ZipCompatibility::IsPlatformSupported(iSystemComp))
return false;
m_iArchiveSystCompatib = iSystemComp;
return true;
}
void CZipArchive::SetRootPath(LPCTSTR szPath)
{
if (IsClosed())
{
TRACE(_T("%s(%i) : Set it after opening the archive.\n"),__FILE__,__LINE__);
return;
}
if (m_iFileOpened != nothing)
{
TRACE(_T("%s(%i) : Set it before opening a file inside archive.\n"),__FILE__,__LINE__);
return;
}
if (szPath)
{
m_szRootPath = szPath;
CZipPathComponent::RemoveSeparators(m_szRootPath);
}
else
m_szRootPath.Empty();
}
CZipString CZipArchive::TrimRootPath(CZipPathComponent &zpc)const
{
if (m_szRootPath.IsEmpty())
return zpc.GetFileName();
CZipString szPath = zpc.GetFullPath();
return RemovePathBeginning(m_szRootPath, szPath, m_pZipCompare) ? szPath : zpc.GetFileName();
}
bool CZipArchive::RemovePathBeginning(LPCTSTR lpszBeginning, CZipString& szPath, ZIPSTRINGCOMPARE pCompareFunction)
{
CZipString szBeginning(lpszBeginning);
CZipPathComponent::RemoveSeparators(szBeginning);
int iRootPathLength = szBeginning.GetLength();
if (iRootPathLength && szPath.GetLength() >= iRootPathLength &&
(szPath.Left(iRootPathLength).*pCompareFunction)(szBeginning) == 0)
{
// the beginning is the same
if (szPath.GetLength() == iRootPathLength)
{
szPath.Empty();
return true;
}
// is the end of m_szPathRoot only a beginning of a directory name?
// check for a separator
// we know the length is larger, so we can write:
if (CZipPathComponent::IsSeparator(szPath[iRootPathLength]))
{
szPath = szPath.Mid(iRootPathLength);
CZipPathComponent::RemoveSeparatorsLeft(szPath);
return true;
}
}
return false;
}
void CZipArchive::SetTempPath(LPCTSTR lpszPath, bool bForce)
{
m_szTempPath = lpszPath;
if (lpszPath && bForce)
ZipPlatform::ForceDirectory(lpszPath);
CZipPathComponent::RemoveSeparators(m_szTempPath);
}
CZipString CZipArchive::PredictFileNameInZip(LPCTSTR lpszFilePath,
bool bFullPath, int iWhat, bool bExactly)const
{
CZipString sz = lpszFilePath;
if (sz.IsEmpty())
return _T("");
bool bAppend;
switch (iWhat)
{
case prFile:
bAppend = false;
break;
case prDir:
bAppend = true;
break;
default:
bAppend = CZipPathComponent::IsSeparator(sz[sz.GetLength() - 1]);
}
// remove for CZipPathComponent treating last name as a file even if dir
CZipPathComponent::RemoveSeparators(sz);
CZipPathComponent zpc(sz);
if (bFullPath)
{
if (m_bRemoveDriveLetter)
sz = zpc.GetNoDrive();
}
else
sz = TrimRootPath(zpc);
if (bAppend && !sz.IsEmpty())
CZipPathComponent::AppendSeparator(sz);
CZipFileHeader fh; // create a temporary object to convert
fh.SetFileName(sz);
if (bExactly)
{
fh.SetSystemCompatibility(m_iArchiveSystCompatib);
ZipCompatibility::FileNameUpdate(fh, false);
}
else
{
fh.SetSystemCompatibility(-1); // non existing system to prevent ansi oem conversion
ZipCompatibility::FileNameUpdate(fh, true);// update only path separators
}
return fh.GetFileName();
}
CZipString CZipArchive::PredictExtractedFileName(LPCTSTR lpszFileNameInZip, LPCTSTR lpszPath, bool bFullPath, LPCTSTR lpszNewName)const
{
CZipString szFile = lpszPath;
CZipString sz = lpszNewName ? lpszNewName : lpszFileNameInZip;
if (sz.IsEmpty())
return szFile;
if (!szFile.IsEmpty())
CZipPathComponent::AppendSeparator(szFile);
// remove for CZipPathComponent treating last name as a file even if dir
CZipPathComponent::RemoveSeparators(sz);
CZipPathComponent zpc(sz);
szFile += bFullPath ? (m_bRemoveDriveLetter ? zpc.GetNoDrive() : sz)
: TrimRootPath(zpc);
return szFile;
}
void CZipArchive::SetAutoFlush(bool bAutoFlush)
{
if (IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive not yet opened.\n"),__FILE__,__LINE__);
return;
}
if (m_storage.IsSpanMode() != 0)
{
TRACE(_T("%s(%i) : Cannot set auto-flush for the disk spanning archive.\n"),__FILE__,__LINE__);
return;
}
m_bAutoFlush = bAutoFlush;
}
void CZipArchive::Flush()
{
if (IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive not yet opened.\n"),__FILE__,__LINE__);
return;
}
if (m_storage.IsSpanMode() < 0)
{
TRACE(_T("%s(%i) : Cannot flush an existing disk spanning archive.\n"),__FILE__,__LINE__);
return;
}
WriteCentralDirectory();
m_storage.FlushFile();
if (m_storage.IsSpanMode() > 0) // try to finalize disk-spanning archive without closing it
m_storage.FinalizeSpan();
}
void CZipArchive::GetCentralDirInfo(CZipCentralDir::Info& info)const
{
if (IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive not yet opened.\n"),__FILE__,__LINE__);
return;
}
m_centralDir.GetInfo(info);
if (GetSpanMode() > 0)
info.m_uDiskEntriesNo = m_storage.GetCurrentDisk();
}
bool CZipArchive::CWildcard::IsPatternValid(LPCTSTR lpszPattern, int* iErrorType)
{
try
{
/* loop through pattern to EOS */
while (*lpszPattern)
{
/* determine pattern type */
switch (*lpszPattern)
{
/* check literal escape, it cannot be at end of pattern */
case _T('\\'):
if (!*++lpszPattern)
throw patternEsc;
lpszPattern++;
break;
/* the [..] construct must be well formed */
case _T('['):
lpszPattern++;
/* if the next character is ']' then bad pattern */
if (*lpszPattern == _T(']'))
throw patternEmpty;
/* if end of pattern here then bad pattern */
if (!*lpszPattern)
throw patternClose;
/* loop to end of [..] construct */
while (*lpszPattern != _T(']'))
{
/* check for literal escape */
if (*lpszPattern == _T('\\'))
{
lpszPattern++;
/* if end of pattern here then bad pattern */
if (!*lpszPattern++)
throw patternEsc;
}
else lpszPattern++;
/* if end of pattern here then bad pattern */
if (!*lpszPattern)
throw patternClose;
/* if this a range */
if (*lpszPattern == _T('-'))
{
/* we must have an end of range */
if (!*++lpszPattern || *lpszPattern == ']')
throw patternRange;
else
{
/* check for literal escape */
if (*lpszPattern == _T('\\'))
lpszPattern++;
/* if end of pattern here
then bad pattern */
if (!*lpszPattern++)
throw patternEsc;
}
}
}
break;
/* all other characters are valid pattern elements */
case '*':
case '?':
default:
lpszPattern++; /* "normal" character */
break;
}
}
throw patternValid;
}
catch (int i)
{
if (iErrorType)
*iErrorType = i;
return i == patternValid;
}
}
bool CZipArchive::CWildcard::IsPattern(LPCTSTR lpszPattern)
{
while (*lpszPattern)
{
switch (*lpszPattern++)
{
case _T('?'):
case _T('*'):
case _T('['):
case _T('\\'):
return true;
}
}
return false;
}
bool CZipArchive::CWildcard::IsMatch(LPCTSTR lpszText, int *iRetCode)
{
CZipString sz;
if (!m_bCaseSensitive)
{
sz = lpszText;
sz.MakeLower();
lpszText = (LPCTSTR)sz;
}
int i = Match((LPCTSTR)m_szPattern, lpszText);
if (iRetCode)
*iRetCode = i;
return i == matchValid;
}
int CZipArchive::CWildcard::MatchAfterStar(LPCTSTR p, LPCTSTR t)
{
int iMatch = matchNone;
TCHAR nextp;
/* pass over existing ? and * in pattern */
while ( *p == _T('?') || *p == _T('*') )
{
/* take one char for each ? and + */
if (*p == _T('?'))
{
/* if end of text then no match */
if (!*t++)
return matchAbort;
}
/* move to next char in pattern */
p++;
}
/* if end of pattern we have matched regardless of text left */
if (!*p)
return matchValid;
/* get the next character to match which must be a literal or '[' */
nextp = *p;
if (nextp == _T('\\'))
{
nextp = p[1];
/* if end of text then we have a bad pattern */
if (!nextp)
return matchPattern;
}
/* Continue until we run out of text or definite result seen */
do
{
/* a precondition for matching is that the next character
in the pattern match the next character in the text or that
the next pattern char is the beginning of a range. Increment
text pointer as we go here */
if (nextp == *t || nextp == _T('['))
iMatch = Match(p, t);
/* if the end of text is reached then no iMatch */
if (!*t++)
iMatch = matchAbort;
} while ( iMatch != matchValid &&
iMatch != matchAbort &&
iMatch != matchPattern);
/* return result */
return iMatch;
}
int CZipArchive::CWildcard::Match(LPCTSTR lpszPattern, LPCTSTR lpszText)
{
TCHAR range_start, range_end; /* start and end in range */
bool bInvert; /* is this [..] or [!..] */
bool bMemberMatch; /* have I matched the [..] construct? */
bool bLoop; /* should I terminate? */
for ( ; *lpszPattern; lpszPattern++, lpszText++)
{
/* if this is the end of the text
then this is the end of the match */
if (!*lpszText)
{
if ( *lpszPattern == _T('*') && *++lpszPattern == _T('\0') )
return matchValid;
else
return matchAbort;
}
/* determine and react to pattern type */
switch (*lpszPattern)
{
case _T('?'): /* single any character match */
break;
case _T('*'): /* multiple any character match */
return MatchAfterStar (lpszPattern, lpszText);
/* [..] construct, single member/exclusion character match */
case _T('['):
{
/* move to beginning of range */
lpszPattern++;
/* check if this is a member match or exclusion match */
bInvert = false;
if (*lpszPattern == _T('!') || *lpszPattern == _T('^'))
{
bInvert = true;
lpszPattern++;
}
/* if closing bracket here or at range start then we have a
malformed pattern */
if (*lpszPattern == _T(']'))
return matchPattern;
bMemberMatch = false;
bLoop = true;
while (bLoop)
{
/* if end of construct then bLoop is done */
if (*lpszPattern == _T(']'))
{
bLoop = false;
continue;
}
/* matching a '!', '^', '-', '\' or a ']' */
if (*lpszPattern == _T('\\'))
range_start = range_end = *++lpszPattern;
else
range_start = range_end = *lpszPattern;
/* if end of pattern then bad pattern (Missing ']') */
if (!*lpszPattern)
return matchPattern;
/* check for range bar */
if (*++lpszPattern == _T('-'))
{
/* get the range end */
range_end = *++lpszPattern;
/* if end of pattern or construct
then bad pattern */
if (range_end == _T('\0') || range_end == _T(']'))
return matchPattern;
/* special character range end */
if (range_end == _T('\\'))
{
range_end = *++lpszPattern;
/* if end of text then
we have a bad pattern */
if (!range_end)
return matchPattern;
}
/* move just beyond this range */
lpszPattern++;
}
/* if the text character is in range then match found.
make sure the range letters have the proper
relationship to one another before comparison */
if (range_start < range_end)
{
if (*lpszText >= range_start && *lpszText <= range_end)
{
bMemberMatch = true;
bLoop = false;
}
}
else
{
if (*lpszText >= range_end && *lpszText <= range_start)
{
bMemberMatch = true;
bLoop = false;
}
}
}
/* if there was a match in an exclusion set then no match */
/* if there was no match in a member set then no match */
if ((bInvert && bMemberMatch) || !(bInvert || bMemberMatch))
return matchRange;
/* if this is not an exclusion then skip the rest of
the [...] construct that already matched. */
if (bMemberMatch)
{
while (*lpszPattern != _T(']'))
{
/* bad pattern (Missing ']') */
if (!*lpszPattern)
return matchPattern;
/* skip exact match */
if (*lpszPattern == _T('\\'))
{
lpszPattern++;
/* if end of text then
we have a bad pattern */
if (!*lpszPattern)
return matchPattern;
}
/* move to next pattern char */
lpszPattern++;
}
}
break;
}
case _T('\\'): /* next character is quoted and must match exactly */
/* move pattern pointer to quoted char and fall through */
lpszPattern++;
/* if end of text then we have a bad pattern */
if (!*lpszPattern)
return matchPattern;
/* must match this character exactly */
default:
if (*lpszPattern != *lpszText)
return matchPattern;
}
}
/* if end of text not reached then the pattern fails */
if (*lpszText)
return matchEnd;
else
return matchValid;
}
void CZipArchive::FindMatches(LPCTSTR lpszPattern, CZipWordArray &ar, bool bFullPath) const
{
if (IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive is closed.\n"),__FILE__,__LINE__);
return;
}
// ar.RemoveAll(); don't do this
int iCount = GetCount();
CWildcard wc(lpszPattern, m_bCaseSensitive);
for (int i = 0; i < iCount; i++)
{
const CZipFileHeader* pHeader = m_centralDir[i];
CZipString sz = m_centralDir.GetProperHeaderFileName(pHeader);
if (!bFullPath)
{
CZipPathComponent::RemoveSeparators(sz);
CZipPathComponent zpc(sz);
sz = zpc.GetFileName();
}
if (wc.IsMatch(sz))
ar.Add(i);
}
}
int CZipArchive::WillBeDuplicated(LPCTSTR lpszFilePath, bool bFullPath, bool bFileNameOnly , int iWhat)
{
CZipString szFile;
// we predict with bExactly set to false, because FindFile converts all filanames anyway
if (bFileNameOnly)
{
CZipPathComponent zpc(lpszFilePath);
szFile = PredictFileNameInZip(zpc.GetFileName(), false, iWhat);
}
else
szFile = PredictFileNameInZip(lpszFilePath, bFullPath, iWhat);
return FindFile(szFile, ffDefault, bFileNameOnly);
}
// it'll get up to the next file or to the end of file (bad if zip corrupted or not-ordered by offsett or redundant bytes added)
bool CZipArchive::GetFromArchive(CZipArchive& zip, WORD uIndex, int iReplaceIndex, bool bKeepSystComp, CZipActionCallback* pCallback)
{
if (IsClosed() || zip.IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive is closed.\n"),__FILE__,__LINE__);
return false;
}
if (m_iFileOpened || zip.m_iFileOpened)
{
TRACE(_T("%s(%i) : You cannot get files from another archive if there is a file opened.\n"),__FILE__,__LINE__);
return false;
}
if (zip.m_storage.IsSpanMode())
{
TRACE(_T("%s(%i) : You cannot get files from the disk spannig archive.\n"),__FILE__,__LINE__);
return false;
}
if (m_storage.IsSpanMode() == -1)
{
TRACE(_T("%s(%i) : You cannot add files to the existing disk spannig archive.\n"),__FILE__,__LINE__);
return false;
}
ASSERT(m_info.m_pBuffer.GetSize() > 0);
bool bIsSpan = m_storage.IsSpanMode() == 1;
CZipFileHeader fh;
if (!zip.GetFileInfo(fh, uIndex))
return false;
CZipAbstractFile* pFile = zip.m_storage.m_pFile;
DWORD uEndOffset;
if (uIndex < zip.GetCount() - 1)
{
CZipFileHeader fhTemp;
if (!zip.GetFileInfo(fhTemp, uIndex+1))
return false;
uEndOffset = fhTemp.m_uOffset;
}
else
{
CZipCentralDir::Info info;
zip.m_centralDir.GetInfo(info);
if (info.m_bOnDisk)
uEndOffset = info.m_uOffset;
else
uEndOffset = pFile->GetLength();
}
uEndOffset += zip.m_centralDir.GetBytesBefore();
DWORD uStartOffset = zip.m_centralDir.GetBytesBefore() + fh.m_uOffset + fh.GetSize(true);
DWORD uTotalToMove = uEndOffset - uStartOffset, uTotalMoved = 0;
DWORD uPredictedSize = fh.m_uComprSize +
(fh.IsDataDescr() ? ZIPARCHIVE_DATADESCRIPTOR_LEN : 0);
if (uTotalToMove > uPredictedSize + 4/* may be or may be not a signature*/)
uTotalToMove = uPredictedSize + 4;
else if (uTotalToMove < uPredictedSize)
ThrowError(CZipException::badZipFile);
// conversion stuff
CZipString szFileNameConverted, szFileName;
bool bConvertSystem = !bKeepSystComp && fh.GetSystemCompatibility() != m_iArchiveSystCompatib;
// GetFileInfo always converts the filename regardless of zip.m_centralDir.m_bConvertAfterOpen value
szFileNameConverted = fh.GetFileName();
if (bConvertSystem)
{
DWORD uAttr = fh.GetSystemAttr();
fh.SetSystemCompatibility(m_iArchiveSystCompatib);
fh.SetSystemAttr(uAttr);
}
ZipCompatibility::FileNameUpdate(fh, false);
szFileName = fh.GetFileName();
bool bNeedDataDescr = bIsSpan && !fh.IsDataDescr();
if (bNeedDataDescr)
fh.m_uFlag |= 8; // data descriptor present
// needed by InsertFindFastElement
if (m_centralDir.IsFindFastEnabled())
fh.SetFileName(szFileNameConverted);
if (!UpdateReplaceIndex(iReplaceIndex, szFileNameConverted))
return false;
bool bReplace = iReplaceIndex >= 0;
int iCallbackType = 0;
if (pCallback)
iCallbackType = pCallback->m_iType;
// if the same callback is applied to cbReplace, then the previous information about the type will be lost
CZipFileHeader* pHeader = m_centralDir.AddNewFile(fh, iReplaceIndex); // must be converted when adding because of InsertFastElement
if (bReplace)
MakeSpaceForReplace(iReplaceIndex, uTotalToMove + fh.GetSize(true), szFileNameConverted);
if (pCallback)
{
pCallback->m_iType = iCallbackType;
pCallback->Init(szFileNameConverted, zip.GetArchivePath());
pCallback->SetTotal(fh.m_uComprSize);
}
if (m_centralDir.IsFindFastEnabled())
pHeader->SetFileName(szFileName);
// must be written as not converted
pHeader->WriteLocal(m_storage);
// made a correction to what was set in WriteLocal
pHeader->m_uOffset -= m_centralDir.GetBytesBefore();
// we keep in converted in memory
if (m_centralDir.m_bConvertAfterOpen)
pHeader->SetFileName(szFileNameConverted);
// skip reading the local file header
pFile->Seek(uStartOffset, CZipAbstractFile::begin);
DWORD uPack = uTotalToMove > m_info.m_pBuffer.GetSize() ? m_info.m_pBuffer.GetSize() : uTotalToMove;
char* buf = (char*)m_info.m_pBuffer;
DWORD size_read;
int iAborted = 0;
bool bBreak = false;
if (uPack)
do
{
size_read = pFile->Read(buf, uPack);
if (!size_read)
break;
if (uTotalMoved + size_read > uTotalToMove)
{
size_read = uTotalToMove - uTotalMoved;
if (!size_read) // this is for protection
break;
bBreak = true;
}
m_storage.Write(buf, size_read, false);
uTotalMoved += size_read;
if (pCallback)
if (!(*pCallback)(size_read))
{
if (uTotalToMove != uTotalMoved)
{
if (!bIsSpan && !bReplace)
{
m_centralDir.RemoveLastFile();
iAborted = CZipException::abortedSafely;
}
else
iAborted = CZipException::abortedAction;
}
else
iAborted = CZipException::abortedSafely; // we did it!
break;
}
}
while (!bBreak);
if (iAborted)
CZipException::Throw(iAborted); // throw to distuingiush from other return codes
// copying from non-span to span or from span without data description to span
// so add the data descriptor
m_centralDir.m_pOpenedFile = NULL;
if (bNeedDataDescr && uTotalMoved == uTotalToMove)
{
const int iToWrite = ZIPARCHIVE_DATADESCRIPTOR_LEN + 4;
CZipAutoBuffer buf(iToWrite);
memcpy(buf, m_storage.m_gszExtHeaderSignat, 4);
pHeader->GetCrcAndSizes(buf + 4);
m_storage.Write(buf, iToWrite, true);
}
m_storage.Flush();
if (uTotalMoved < uTotalToMove)
ThrowError(CZipException::badZipFile);
if (pCallback)
pCallback->CallbackEnd();
return true;
}
bool CZipArchive::GetFromArchive(CZipArchive& zip, CZipWordArray &aIndexes, bool bKeepSystComp)
{
aIndexes.Sort(true);
int iFiles = aIndexes.GetSize();
m_info.Init();
try
{
for (int i = 0; i < iFiles; i++)
{
int iFileIndex = aIndexes[i];
if (!m_centralDir.IsValidIndex(iFileIndex))
if (!GetFromArchive(zip, iFileIndex, -1, bKeepSystComp, GetCallback(cbGetFromArchive)))
{
m_info.ReleaseBuf();
return false;
}
}
}
catch (...)
{
m_info.ReleaseBuf();
throw;
}
m_info.ReleaseBuf();
if (m_bAutoFlush)
Flush();
return true;
}
bool CZipArchive::RenameFile(WORD uIndex, LPCTSTR lpszNewName)
{
if (IsClosed())
{
TRACE(_T("%s(%i) : ZipArchive is closed.\n"),__FILE__,__LINE__);
return false;
}
if (m_storage.IsSpanMode())
{
TRACE(_T("%s(%i) : You cannot rename files in the disk spannig archive.\n"),__FILE__,__LINE__);
return false;
}
if (m_iFileOpened)
{
TRACE(_T("%s(%i) : You cannot rename a file if there is a file opened.\n"),__FILE__,__LINE__);
return false;
}
CZipFileHeader fh, fhNew;
if (!GetFileInfo(fh, uIndex))
return false;
CZipString szNewName(lpszNewName);
if (fh.IsDirectory())
CZipPathComponent::AppendSeparator(szNewName);
else
CZipPathComponent::RemoveSeparators(szNewName);
if (fh.GetFileName().Collate(szNewName) == 0)
return true;
fhNew.SetSystemCompatibility(m_iArchiveSystCompatib);
fhNew.SetFileName(szNewName);
ZipCompatibility::FileNameUpdate(fhNew, false);
ZipCompatibility::FileNameUpdate(fh, false); // in case the conversion changes the filename size
WORD uFileNameLen = fh.GetFileNameSize();
WORD uNewFileNameLen = fhNew.GetFileNameSize();
int iDelta = uNewFileNameLen - uFileNameLen;
int iOffset = 0;
CZipAutoBuffer buf, *pBuf;
m_centralDir.RemoveFromDisk(); // does m_storage.Flush();
if (iDelta != 0)
{
// we need to make more or less space
m_info.Init();
DWORD uStartOffset = fh.m_uOffset + 30 + uFileNameLen;
DWORD uFileLen = m_storage.m_pFile->GetLength();
DWORD uEndOffset = uFileLen - m_centralDir.GetBytesBefore();
CZipActionCallback* pCallback = GetCallback(cbRename);
if (pCallback)
{
// do it right and sent the notification
pCallback->Init(fh.GetFileName(), GetArchivePath());
pCallback->SetTotal(uEndOffset - uStartOffset);
}
bool bForward = iDelta > 0;
if (bForward)
m_storage.m_pFile->SetLength(uFileLen + iDelta); // ensure the seek is correct
MovePackedFiles(uStartOffset, uEndOffset, abs(iDelta), pCallback, bForward);
if (pCallback)
pCallback->CallbackEnd();
if (!bForward)
m_storage.m_pFile->SetLength(uFileLen + iDelta); // delta < 0; shrink the file
m_info.ReleaseBuf();
int iSize = GetCount();
for (int i = uIndex + 1; i < iSize; i++)
m_centralDir[i]->m_uOffset += iDelta;
buf.Allocate(4+uNewFileNameLen);
WORD uExtraFieldSize = fh.GetExtraFieldSize();
memcpy(buf, &uNewFileNameLen, 2);
memcpy(buf + 2, &uExtraFieldSize, 2); // to write everything at once
memcpy(buf + 4, fhNew.m_pszFileName, uNewFileNameLen);
pBuf = &buf;
iOffset = -4;
}
else
pBuf = &fhNew.m_pszFileName;
m_storage.m_pFile->Seek(m_centralDir.GetBytesBefore() + fh.m_uOffset + 30 + iOffset, CZipAbstractFile::begin);
m_storage.m_pFile->Write(buf, buf.GetSize());
m_centralDir.RenameFile(uIndex, szNewName);
if (m_bAutoFlush)
Flush();
return true;
}
bool CZipArchive::UpdateReplaceIndex(int& iReplaceIndex, LPCTSTR lpszNewFileName)
{
if (iReplaceIndex == -2)
iReplaceIndex = FindFile(lpszNewFileName);
if (iReplaceIndex < 0)
{
if (iReplaceIndex != -1)
iReplaceIndex = -1;
return true;
}
if (GetSpanMode()!=0)
{
TRACE(_T("%s(%i) : You cannot replace files in a disk-spanning archive.\n"),__FILE__,__LINE__);
return false;
}
if (!m_centralDir.IsValidIndex(iReplaceIndex))
{
TRACE(_T("%s(%i) : Not valid replace index.\n"),__FILE__,__LINE__);
return false;
}
if (iReplaceIndex == GetCount() - 1) // replacing last file in the archive
{
RemoveLast(true);
iReplaceIndex = -1;
}
return true;
}
void CZipArchive::MakeSpaceForReplace(int iReplaceIndex, DWORD uTotal, LPCTSTR lpszFileName)
{
ASSERT(iReplaceIndex < GetCount() - 1);
DWORD uReplaceStart = m_storage.m_pFile->GetPosition() - m_centralDir.GetBytesBefore();
DWORD uReplaceEnd = m_centralDir.m_headers[iReplaceIndex + 1]->m_uOffset;
DWORD uReplaceTotal = uReplaceEnd - uReplaceStart;
int iDelta = uTotal - uReplaceTotal;
if (iDelta != 0)
{
// m_info.Init(); don't - the calling functions will
CZipActionCallback* pCallback = GetCallback(CZipArchive::cbReplace);
DWORD uFileLen = m_storage.m_pFile->GetLength();
DWORD uUpperLimit = uFileLen - m_centralDir.GetBytesBefore(); // will be added in MovePackedFiles
if (pCallback)
{
pCallback->Init(lpszFileName, GetArchivePath());
pCallback->SetTotal(uUpperLimit - uReplaceEnd);
}
bool bForward = iDelta > 0;
if (bForward)
m_storage.m_pFile->SetLength(uFileLen + iDelta); // ensure the seek is correct
MovePackedFiles(uReplaceEnd, uUpperLimit, abs(iDelta), pCallback, bForward);
if (!bForward)
m_storage.m_pFile->SetLength(uFileLen + iDelta); // delta < 0; shrink the file
m_storage.m_pFile->Seek(uReplaceStart, CZipAbstractFile::begin);
int iSize = m_centralDir.m_headers.GetSize();
for (int i = iReplaceIndex + 1; i < iSize; i++)
m_centralDir.m_headers[i]->m_uOffset += iDelta;
if (pCallback)
pCallback->CallbackEnd();
}
}
void CZipArchive::MovePackedFiles(DWORD uStartOffset, DWORD uEndOffset, DWORD uMoveBy, CZipActionCallback* pCallback, bool bForward)
{
ASSERT(m_info.m_pBuffer.GetSize() > 0);
uStartOffset += m_centralDir.GetBytesBefore();
uEndOffset += m_centralDir.GetBytesBefore();
DWORD uTotalToMove = uEndOffset - uStartOffset;
DWORD uPack = uTotalToMove > m_info.m_pBuffer.GetSize() ? m_info.m_pBuffer.GetSize() : uTotalToMove;
char* buf = (char*)m_info.m_pBuffer;
DWORD size_read;
bool bBreak = false;
do
{
if (uEndOffset - uStartOffset < uPack)
{
uPack = uEndOffset - uStartOffset;
if (!uPack)
break;
bBreak = true;
}
DWORD uPosition = bForward ? uEndOffset - uPack : uStartOffset;
m_storage.m_pFile->Seek(uPosition, CZipAbstractFile::begin);
size_read = m_storage.m_pFile->Read(buf, uPack);
if (!size_read)
break;
if (bForward)
uPosition += uMoveBy;
else
uPosition -= uMoveBy;
m_storage.m_pFile->Seek(uPosition, CZipAbstractFile::begin);
m_storage.m_pFile->Write(buf, size_read);
if (bForward)
uEndOffset -= size_read;
else
uStartOffset += size_read;
if (pCallback)
if (!(*pCallback)(size_read))
ThrowError(CZipException::abortedAction);
}
while (!bBreak);
if (uEndOffset != uStartOffset)
ThrowError(CZipException::internal);
}