///////////////////////////////////////////////////////////////////////////////
// $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 "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 = m_bDetectZlibMemoryLeaks = 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_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::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::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);
	
}