////////////////////////////////////////////////////////////////////////////////
|
// This source file is part of the ZipArchive library source distribution and
|
// is Copyrighted 2000 - 2011 by Artpol Software - Tadeusz Dracz
|
//
|
// 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 refer to the License.txt file.
|
//
|
// Web Site: http://www.artpol-software.com
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
#include "stdafx.h"
|
//#include <string>
|
#include "ZipCentralDir.h"
|
#include "ZipArchive.h"
|
#include "ZipFileMapping.h"
|
#include "ZipPlatform.h"
|
#include "BytesWriter.h"
|
|
|
#define CENTRAL_DIR_END_SIZE 22
|
|
// needed also for checking when Zip64 is disabled
|
#define CENTRAL_DIR_END64_LOCATOR_SIZE 20
|
|
using namespace ZipArchiveLib;
|
|
char CZipCentralDir::m_gszSignature[] = {0x50, 0x4b, 0x05, 0x06};
|
char CZipCentralDir::m_gszSignature64Locator[] = {0x50, 0x4b, 0x06, 0x07};
|
|
|
|
CZipCentralDir::CZipCentralDir()
|
{
|
m_pInfo = NULL;
|
m_pHeaders = NULL;
|
m_pFindArray = NULL;
|
m_pOpenedFile = NULL;
|
m_iIgnoredChecks = 0;
|
m_specialFlags = CZipArchive::sfNone;
|
InitUnicode();
|
}
|
|
void CZipCentralDir::InitUnicode()
|
{
|
#ifdef _ZIP_UNICODE_CUSTOM
|
m_iUnicodeMode = CZipArchive::umCustom;
|
#else
|
m_iUnicodeMode = CZipArchive::umNone;
|
#endif
|
}
|
|
void CZipCentralDir::InitOnCreate(CZipArchive* pArchive)
|
{
|
m_pArchive = pArchive;
|
m_pStorage = pArchive->GetStorage();
|
}
|
|
void CZipCentralDir::Init(CZipCentralDir* pSource)
|
{
|
m_pOpenedFile = NULL;
|
m_iIgnoredChecks = CZipArchive::checkIgnoredByDefault;
|
// just in case
|
DestroySharedData();
|
if (pSource != NULL)
|
{
|
#ifdef _ZIP_USE_LOCKING
|
pSource->LockAccess();
|
#endif
|
m_pInfo = pSource->m_pInfo;
|
m_pInfo->m_iReference++;
|
m_pHeaders = pSource->m_pHeaders;
|
m_pFindArray = pSource->m_pFindArray;
|
#ifdef _ZIP_USE_LOCKING
|
// points to the same object now
|
UnlockAccess();
|
#endif
|
|
m_pStorage->UpdateSegmMode(m_pInfo->m_uLastVolume);
|
m_pStorage->m_uBytesBeforeZip = pSource->m_pStorage->m_uBytesBeforeZip;
|
|
}
|
else
|
CreateSharedData();
|
}
|
|
CZipCentralDir::~CZipCentralDir()
|
{
|
DestroySharedData();
|
}
|
|
void CZipCentralDir::Read()
|
{
|
if (!m_pStorage)
|
{
|
ASSERT(FALSE);
|
return;
|
}
|
|
ZIP_FILE_USIZE location = LocateSignature();
|
|
if (location == CZipStorage::SignatureNotFound)
|
ThrowError(CZipException::cdirNotFound);
|
|
bool isBinary = m_pStorage->IsBinarySplit();
|
if (!isBinary)
|
{
|
m_pInfo->m_uEndOffset = (ZIP_SIZE_TYPE)location;
|
m_pStorage->m_pFile->SafeSeek((ZIP_FILE_USIZE)(m_pInfo->m_uEndOffset + 4));
|
}
|
else
|
{
|
ZIP_FILE_USIZE uPos = m_pStorage->m_pFile->GetPosition();
|
ASSERT(uPos > location);
|
m_pStorage->SeekInBinary(-(ZIP_FILE_SIZE)(uPos - location) + 4);
|
m_pInfo->m_uEndOffset = m_pStorage->GetPosition() - 4;
|
}
|
|
CZipAutoBuffer buf(CENTRAL_DIR_END_SIZE - 4);
|
|
// we can skip the signature, we already know it is good - it was found
|
m_pStorage->Read(buf, CENTRAL_DIR_END_SIZE - 4, true);
|
|
WORD uCommentSize;
|
CBytesWriter::ReadBytes(m_pInfo->m_uLastVolume, buf, 2);
|
CBytesWriter::ReadBytes(m_pInfo->m_uVolumeWithCD, buf + 2, 2);
|
CBytesWriter::ReadBytes(m_pInfo->m_uVolumeEntriesNo,buf + 4, 2);
|
CBytesWriter::ReadBytes(m_pInfo->m_uEntriesNumber,buf + 6, 2);
|
CBytesWriter::ReadBytes(m_pInfo->m_uSize, buf + 8, 4);
|
CBytesWriter::ReadBytes(m_pInfo->m_uOffset, buf + 12, 4);
|
CBytesWriter::ReadBytes(uCommentSize, buf + 16);
|
buf.Release();
|
|
if (uCommentSize)
|
{
|
m_pInfo->m_pszComment.Allocate(uCommentSize);
|
m_pStorage->Read(m_pInfo->m_pszComment, uCommentSize, true);
|
}
|
if (m_pInfo->NeedsZip64())
|
{
|
if (isBinary || m_pInfo->m_uEndOffset >= CENTRAL_DIR_END64_LOCATOR_SIZE)
|
{
|
if (isBinary)
|
m_pStorage->SeekInBinary(-CENTRAL_DIR_END_SIZE - uCommentSize - CENTRAL_DIR_END64_LOCATOR_SIZE);
|
else
|
m_pStorage->m_pFile->SafeSeek((ZIP_FILE_USIZE)(m_pInfo->m_uEndOffset) - CENTRAL_DIR_END64_LOCATOR_SIZE);
|
char buf[4];
|
m_pStorage->Read(buf, 4, true);
|
if (memcmp(buf, m_gszSignature64Locator, 4) == 0)
|
ThrowError(CZipException::noZip64);
|
}
|
// when the zip64 locator is not found, try to treat this archive as normal
|
}
|
|
// if m_uLastVolume is not zero, it is enough to say that it is a multi-volume archive unless it is a binary split archive
|
if (IsConsistencyCheckOn(CZipArchive::checkVolumeEntries))
|
if (!((!m_pInfo->m_uLastVolume && (m_pInfo->m_uEntriesNumber == m_pInfo->m_uVolumeEntriesNo) && !m_pInfo->m_uVolumeWithCD)
|
|| m_pInfo->m_uLastVolume))
|
ThrowError(CZipException::badZipFile);
|
|
m_pStorage->UpdateSegmMode(m_pInfo->m_uLastVolume);
|
|
if (!m_pStorage->IsSegmented() && !m_pInfo->CheckIfOK_1())
|
ThrowError(CZipException::badZipFile);
|
|
if (m_pStorage->m_uBytesBeforeZip == 0 && m_pInfo->m_uLastVolume == 0)
|
m_pStorage->m_uBytesBeforeZip = m_pInfo->CalculateBytesBeforeZip();
|
|
if (!m_pInfo->CheckIfOK_2())
|
ThrowError(CZipException::badZipFile);
|
|
m_pInfo->m_bInArchive = true;
|
m_pStorage->ChangeVolume(m_pInfo->m_uVolumeWithCD);
|
|
if (!m_pInfo->m_uSize)
|
return;
|
|
ReadHeaders();
|
}
|
|
|
void CZipCentralDir::ThrowError(int err) const
|
{
|
CZipException::Throw(err, m_pStorage->m_pFile->GetFilePath());
|
}
|
|
|
void CZipCentralDir::ReadHeaders()
|
{
|
if (!m_pStorage->IsBinarySplit())
|
m_pStorage->Seek(m_pInfo->m_uOffset);
|
else
|
m_pStorage->SeekInBinary(m_pInfo->m_uOffset, true);
|
|
RemoveHeaders(); //just in case
|
for (ZIP_INDEX_TYPE i = 0; i < m_pInfo->m_uEntriesNumber; i++)
|
{
|
CZipFileHeader* pHeader = new CZipFileHeader(this);
|
m_pHeaders->Add(pHeader);
|
|
if (!pHeader->Read(true))
|
ThrowError(CZipException::badZipFile);
|
}
|
|
if (m_specialFlags.IsSetAny(CZipArchive::sfExhaustiveRead))
|
{
|
ZIP_FILE_USIZE uPosition = m_pStorage->GetPosition();
|
// different offset, or different parts
|
if (uPosition != m_pInfo->m_uEndOffset || m_pStorage->IsSegmented() && !m_pStorage->IsBinarySplit() && m_pStorage->GetCurrentVolume() != m_pInfo->m_uLastVolume)
|
for(;;)
|
{
|
CZipAutoBuffer buf(4);
|
m_pStorage->Read(buf, 4, true);
|
if (!CZipFileHeader::VerifySignature(buf))
|
break;
|
CZipFileHeader* pHeader = new CZipFileHeader(this);
|
m_pHeaders->Add(pHeader);
|
if (!pHeader->Read(false))
|
ThrowError(CZipException::badZipFile);
|
}
|
}
|
|
// this is necessary when removing data descriptors, CZipArchive::MakeSpaceForReplace, deleting, replacing or encrypting files
|
// sort always, to yield the same results in requesting files by index regardless of the reason for opening
|
m_pHeaders->Sort(CompareHeaders);
|
RebuildFindFastArray();
|
}
|
|
void CZipCentralDir::Close()
|
{
|
m_pOpenedFile = NULL;
|
DestroySharedData();
|
// do not reference it anymore
|
m_pInfo = NULL;
|
m_pHeaders = NULL;
|
m_pFindArray = NULL;
|
m_specialFlags = CZipArchive::sfNone;
|
InitUnicode();
|
}
|
|
bool CZipCentralDir::IsValidIndex(ZIP_INDEX_TYPE uIndex)const
|
{
|
return uIndex < (ZIP_INDEX_TYPE)m_pHeaders->GetSize() && uIndex != ZIP_FILE_INDEX_UNSPECIFIED;
|
}
|
|
void CZipCentralDir::OpenFile(ZIP_INDEX_TYPE uIndex)
|
{
|
CZipFileHeader* pOpenedFile = (*this)[uIndex];
|
if (!pOpenedFile->ReadLocal(this))
|
ThrowError(CZipException::badZipFile);
|
m_pOpenedFile = pOpenedFile;
|
}
|
|
|
void CZipCentralDir::CloseFile(bool skipCheckingDataDescriptor)
|
{
|
if (!m_pOpenedFile)
|
return;
|
if (!skipCheckingDataDescriptor && IsConsistencyCheckOn(CZipArchive::checkDataDescriptor)
|
&& !m_pOpenedFile->CheckDataDescriptor(m_pStorage))
|
ThrowError(CZipException::badZipFile);
|
m_pOpenedFile = NULL;
|
}
|
|
// add new header using the argument as a template
|
CZipFileHeader* CZipCentralDir::AddNewFile(const CZipFileHeader & header, ZIP_INDEX_TYPE uReplaceIndex, int iLevel, bool bRichHeaderTemplateCopy)
|
{
|
// copy some of the template data
|
m_pOpenedFile = NULL;
|
ZIP_INDEX_TYPE uIndex;
|
CZipFileHeader* pHeader = new CZipFileHeader(this);
|
try
|
{
|
pHeader->m_uMethod = header.m_uMethod;
|
pHeader->m_uModDate = header.m_uModDate;
|
pHeader->m_uModTime = header.m_uModTime;
|
|
|
pHeader->m_uExternalAttr = header.m_uExternalAttr;
|
pHeader->m_uLocalComprSize = header.m_uLocalComprSize;
|
pHeader->m_uLocalUncomprSize = header.m_uLocalUncomprSize;
|
pHeader->m_uLocalHeaderSize = header.m_uLocalHeaderSize;
|
pHeader->m_fileName = header.m_fileName;
|
pHeader->m_comment = header.m_comment;
|
pHeader->m_aLocalExtraData = header.m_aLocalExtraData;
|
// local will be removed in a moment in PrepareData
|
pHeader->m_aCentralExtraData = header.m_aCentralExtraData;
|
pHeader->m_aCentralExtraData.RemoveInternalHeaders();
|
pHeader->m_iSystemCompatibility = header.m_iSystemCompatibility;
|
pHeader->m_uEncryptionMethod = header.m_uEncryptionMethod;
|
pHeader->UpdateStringsFlags(false);
|
#ifdef _ZIP_UNICODE_CUSTOM
|
// current settings
|
pHeader->m_stringSettings = GetStringStoreSettings();
|
#endif
|
|
// set only when adding a new file, not in PrepareData (which may be called under different circumstances)
|
// we need the proper encryption method to be set already
|
RemoveFromDisk();
|
|
bool bReplace = IsValidIndex(uReplaceIndex);
|
|
pHeader->PrepareData(iLevel, m_pStorage->IsSegmented());
|
|
if (bRichHeaderTemplateCopy)
|
{
|
// call here, because PrepareData will zero them
|
pHeader->m_uCrc32 = header.m_uCrc32;
|
pHeader->m_uComprSize = header.m_uComprSize;
|
pHeader->m_uUncomprSize = header.m_uUncomprSize;
|
}
|
|
// now that everything is all right, we can add the new file
|
if (bReplace)
|
{
|
// PrepareStringBuffers was called in CZipArchive::OpenNewFile
|
// the local extra field is updated if needed, so we can check the lengths
|
if (!pHeader->CheckLengths(true))
|
ThrowError(CZipException::tooLongData);
|
|
CZipFileHeader* pfh = (*m_pHeaders)[(ZIP_ARRAY_SIZE_TYPE)uReplaceIndex];
|
m_pStorage->Seek(pfh->m_uOffset);
|
RemoveFile(pfh, uReplaceIndex, false);
|
m_pHeaders->InsertAt((ZIP_ARRAY_SIZE_TYPE)uReplaceIndex, pHeader);
|
m_pOpenedFile = pHeader;
|
uIndex = uReplaceIndex;
|
}
|
else
|
{
|
uIndex = (ZIP_INDEX_TYPE)m_pHeaders->Add(pHeader);
|
m_pOpenedFile = pHeader;
|
m_pStorage->m_pFile->SeekToEnd();
|
}
|
}
|
catch(...)
|
{
|
// otherwise it is added to the collection and will be auto-deleted
|
if (pHeader != NULL && m_pOpenedFile == NULL)
|
delete pHeader;
|
throw;
|
}
|
|
if (m_pInfo->m_bFindFastEnabled)
|
InsertFindFastElement(pHeader, uIndex); // GetCount > 0, because we have just added a header
|
m_pInfo->m_iLastIndexAdded = uIndex;
|
return pHeader;
|
}
|
|
|
void CZipCentralDir::SetComment(LPCTSTR lpszComment, UINT codePage)
|
{
|
ZipCompatibility::ConvertStringToBuffer(lpszComment, m_pInfo->m_pszComment, codePage);
|
RemoveFromDisk();
|
}
|
|
|
void CZipCentralDir::RemoveFromDisk()
|
{
|
if (m_pInfo->m_bInArchive)
|
{
|
ASSERT(!m_pStorage->IsSegmented()); // you can't add files to an existing segmented archive or to delete them from it
|
m_pStorage->m_pFile->SetLength((ZIP_FILE_USIZE)(m_pStorage->m_uBytesBeforeZip + m_pInfo->m_uOffset));
|
m_pInfo->m_bInArchive = false;
|
}
|
else
|
m_pStorage->Flush(); // if remove from disk is requested, then the archive modification will follow, so flush the buffers
|
}
|
|
|
void CZipCentralDir::CloseNewFile()
|
{
|
m_pOpenedFile->OnNewFileClose(m_pStorage);
|
m_pOpenedFile = NULL;
|
}
|
|
void CZipCentralDir::Write()
|
{
|
if (m_pInfo->m_bInArchive)
|
return;
|
|
m_pInfo->m_uEntriesNumber = (ZIP_INDEX_TYPE)m_pHeaders->GetSize();
|
|
if (!m_pStorage->IsSegmented())
|
{
|
m_pStorage->Flush();
|
m_pStorage->m_pFile->SeekToEnd();
|
}
|
// else
|
// we are at the end already
|
|
m_pInfo->m_uSize = 0;
|
bool bDontAllowVolumeChange = false;
|
|
if (m_pStorage->IsSegmented())
|
{
|
// segmentation signature at the beginning (4 bytes) + the size of the data descr. for each file
|
ZIP_SIZE_TYPE uSize = GetSize(true);
|
// if there is a segmented archive in creation and it is only one-volume,
|
// (current volume number is 0 so far, no bytes has been written so we know they are
|
// all in the buffer) make sure that it will be after writing central dir
|
// and make it a not segmented archive
|
if (m_pStorage->GetCurrentVolume() == 0)
|
{
|
// calculate the size of data descriptors already in the buffer or on the disk
|
// (they will be removed in the not segmented archive).
|
ZIP_SIZE_TYPE uToGrow = uSize - 4;
|
for (ZIP_INDEX_TYPE i = 0; i < m_pInfo->m_uEntriesNumber; i++)
|
{
|
CZipFileHeader* pHeader = (*this)[i];
|
if (pHeader->NeedsDataDescriptor())
|
{
|
if (!pHeader->IsEncrypted())
|
uToGrow -= 4; // remove the signature only
|
}
|
else
|
uToGrow -= pHeader->GetDataDescriptorSize(true);
|
}
|
|
ZIP_SIZE_TYPE uVolumeFree = m_pStorage->VolumeLeft();
|
|
if (uVolumeFree >= uToGrow)
|
// lets make sure it will be one-volume archive
|
{
|
// can the operation be done only in the buffer?
|
if (!m_pStorage->m_uBytesWritten && // no bytes on the disk yet
|
(m_pStorage->GetFreeInBuffer() >= uToGrow)) // is the buffer big enough?
|
{
|
RemoveDataDescr(true);
|
bDontAllowVolumeChange = true; // if a volume change occurs somehow, we'll throw an error later
|
}
|
else
|
{
|
m_pStorage->Flush();
|
if (RemoveDataDescr(false))
|
bDontAllowVolumeChange = true; // if a volume change occurs somehow, we'll throw an error later
|
}
|
}
|
}
|
|
// make sure that in a segmented archive, the whole central directory will fit on the single volume
|
if (!bDontAllowVolumeChange && !m_pStorage->IsBinarySplit())
|
m_pStorage->AssureFree(uSize);
|
}
|
|
try
|
{
|
WriteHeaders(bDontAllowVolumeChange || !m_pStorage->IsSegmented());
|
|
WriteCentralEnd();
|
|
if (bDontAllowVolumeChange)
|
{
|
if (m_pStorage->GetCurrentVolume() != 0)
|
ThrowError(CZipException::badZipFile);
|
}
|
}
|
catch (...)
|
{
|
if (bDontAllowVolumeChange)
|
{
|
m_pStorage->FinalizeSegm();
|
m_pInfo->m_uLastVolume = 0;
|
}
|
throw;
|
}
|
m_pInfo->m_bInArchive = true;
|
}
|
|
void CZipCentralDir::WriteHeaders(bool bOneDisk)
|
{
|
CZipActionCallback* pCallback = m_pArchive->GetCallback(CZipActionCallback::cbSave);
|
m_pInfo->m_uVolumeEntriesNo = 0;
|
bool binarySplit = m_pStorage->IsBinarySplit();
|
if (binarySplit)
|
{
|
m_pStorage->AssureFree(1);
|
m_pInfo->m_uVolumeWithCD = 0;
|
}
|
else
|
m_pInfo->m_uVolumeWithCD = m_pStorage->GetCurrentVolume();
|
|
m_pInfo->m_uOffset = m_pStorage->GetPosition();
|
|
if (!m_pInfo->m_uEntriesNumber)
|
return;
|
|
ZIP_VOLUME_TYPE uDisk = m_pStorage->GetCurrentVolume();
|
|
if (pCallback)
|
{
|
pCallback->Init();
|
pCallback->SetTotal(m_pInfo->m_uEntriesNumber);
|
}
|
|
int iAborted = 0;
|
if (m_pInfo->m_uEntriesNumber > 0)
|
{
|
ZIP_INDEX_TYPE uLast = (ZIP_INDEX_TYPE)(m_pInfo->m_uEntriesNumber - 1);
|
ZIP_INDEX_TYPE i = 0;
|
for (;;)
|
{
|
CZipFileHeader* pHeader = (*this)[i];
|
|
m_pInfo->m_uSize += pHeader->Write(m_pStorage);
|
|
if (!binarySplit && m_pStorage->GetCurrentVolume() != uDisk)
|
{
|
m_pInfo->m_uVolumeEntriesNo = 1;
|
uDisk = m_pStorage->GetCurrentVolume();
|
// update the information about the offset and starting volume if the
|
// first header was written in the new volume
|
if (i == 0)
|
{
|
m_pInfo->m_uOffset = 0;
|
m_pInfo->m_uVolumeWithCD = uDisk;
|
}
|
}
|
else
|
m_pInfo->m_uVolumeEntriesNo++;
|
|
if (pCallback)
|
{
|
bool ret, last;
|
if (i == uLast)
|
{
|
ret = pCallback->RequestLastCallback(1);
|
last = true;
|
}
|
else
|
{
|
ret = pCallback->RequestCallback();
|
last = false;
|
}
|
|
if (ret)
|
{
|
if (last)
|
break;
|
}
|
else
|
{
|
if (bOneDisk)
|
{
|
ASSERT(!m_pStorage->IsSegmented());
|
// if segmented, would need to m_pStorage->Flush(), but the headers can span multiple volumes
|
m_pStorage->EmptyWriteBuffer();
|
// remove saved part from the volume
|
m_pStorage->m_pFile->SetLength((ZIP_FILE_USIZE)(m_pStorage->m_uBytesBeforeZip + m_pInfo->m_uOffset));
|
// We can now abort safely
|
iAborted = CZipException::abortedSafely;
|
}
|
else
|
iAborted = CZipException::abortedAction;
|
break;
|
}
|
}
|
else if (i == uLast)
|
break;
|
|
i++;
|
}
|
}
|
|
if (pCallback)
|
{
|
pCallback->CallbackEnd();
|
if (iAborted)
|
ThrowError(iAborted);
|
}
|
}
|
|
|
void CZipCentralDir::WriteCentralEnd()
|
{
|
ZIP_SIZE_TYPE uSize = CENTRAL_DIR_END_SIZE + m_pInfo->m_pszComment.GetSize();
|
|
CZipAutoBuffer buf((DWORD)uSize);
|
char* pBuf = buf;
|
ZIP_VOLUME_TYPE uDisk = m_pStorage->GetCurrentVolume();
|
if (m_pStorage->IsSegmented())
|
{
|
// update the volume number
|
if (m_pStorage->IsBinarySplit())
|
{
|
m_pStorage->AssureFree(1);
|
m_pInfo->m_uLastVolume = 0;
|
}
|
else
|
{
|
m_pStorage->AssureFree(uSize);
|
m_pInfo->m_uLastVolume = m_pStorage->GetCurrentVolume();
|
}
|
}
|
if (m_pInfo->m_uLastVolume != uDisk && !m_pStorage->IsBinarySplit())
|
m_pInfo->m_uVolumeEntriesNo = 0;
|
|
|
WORD uCommentSize = (WORD)m_pInfo->m_pszComment.GetSize();
|
memcpy(pBuf, m_gszSignature, 4);
|
CBytesWriter::WriteBytes(pBuf + 4, CBytesWriter::WriteSafeU16(m_pInfo->m_uLastVolume));
|
CBytesWriter::WriteBytes(pBuf + 6, CBytesWriter::WriteSafeU16(m_pInfo->m_uVolumeWithCD));
|
CBytesWriter::WriteBytes(pBuf + 8, CBytesWriter::WriteSafeU16(m_pInfo->m_uVolumeEntriesNo));
|
CBytesWriter::WriteBytes(pBuf + 10, CBytesWriter::WriteSafeU16(m_pInfo->m_uEntriesNumber));
|
CBytesWriter::WriteBytes(pBuf + 12, CBytesWriter::WriteSafeU32(m_pInfo->m_uSize));
|
CBytesWriter::WriteBytes(pBuf + 16, CBytesWriter::WriteSafeU32(m_pInfo->m_uOffset));
|
CBytesWriter::WriteBytes(pBuf + 20, uCommentSize);
|
memcpy(pBuf + 22, m_pInfo->m_pszComment, uCommentSize);
|
if (uSize > (DWORD)(-1))
|
CZipException::Throw(CZipException::internalError);
|
m_pStorage->Write(buf, (DWORD)uSize, true);
|
}
|
|
void CZipCentralDir::RemoveAll()
|
{
|
m_pInfo->m_iLastIndexAdded = ZIP_FILE_INDEX_UNSPECIFIED;
|
ClearFindFastArray();
|
RemoveHeaders();
|
}
|
|
ZIP_INDEX_TYPE CZipCentralDir::RemoveFindFastElement(CZipFileHeader* pHeader, bool bShift)
|
{
|
// FindFileNameIndex(pHeader->GetFileName()) will not work as the file might have been just renamed
|
ZIP_ARRAY_SIZE_TYPE count = m_pFindArray->GetSize();
|
for (ZIP_ARRAY_SIZE_TYPE i = 0; i < count; i++)
|
{
|
CZipFindFast* pFindFast = (*m_pFindArray)[i];
|
if (pFindFast->m_pHeader == pHeader)
|
{
|
ZIP_INDEX_TYPE uElementIndex = pFindFast->m_uIndex;
|
delete pFindFast;
|
m_pFindArray->RemoveAt((ZIP_ARRAY_SIZE_TYPE)i);
|
// shift down the indexes
|
if (bShift)
|
{
|
ZIP_INDEX_TYPE uSize = (ZIP_INDEX_TYPE)m_pFindArray->GetSize();
|
for (ZIP_INDEX_TYPE j = 0; j < uSize; j++)
|
{
|
if ((*m_pFindArray)[(ZIP_ARRAY_SIZE_TYPE)j]->m_uIndex > uElementIndex)
|
(*m_pFindArray)[(ZIP_ARRAY_SIZE_TYPE)j]->m_uIndex--;
|
}
|
}
|
return uElementIndex;
|
}
|
}
|
ASSERT(FALSE);
|
return ZIP_FILE_INDEX_NOT_FOUND;
|
}
|
|
void CZipCentralDir::RemoveFile(CZipFileHeader* pHeader, ZIP_INDEX_TYPE uIndex, bool bShift)
|
{
|
if (uIndex == ZIP_FILE_INDEX_UNSPECIFIED)
|
{
|
// we need to know the index to remove
|
ZIP_INDEX_TYPE uCount = (ZIP_INDEX_TYPE)m_pHeaders->GetSize();
|
for (ZIP_INDEX_TYPE i = 0; i < uCount; i++)
|
if (pHeader == (*m_pHeaders)[(ZIP_ARRAY_SIZE_TYPE)i])
|
{
|
uIndex = i;
|
break;
|
}
|
}
|
ASSERT(uIndex != ZIP_FILE_INDEX_UNSPECIFIED || pHeader);
|
if (!pHeader)
|
pHeader = (*m_pHeaders)[(ZIP_ARRAY_SIZE_TYPE)uIndex];
|
|
if (m_pInfo->m_bFindFastEnabled)
|
{
|
RemoveFindFastElement(pHeader, bShift);
|
}
|
|
if (uIndex != ZIP_FILE_INDEX_UNSPECIFIED)
|
{
|
delete pHeader;
|
m_pHeaders->RemoveAt((ZIP_ARRAY_SIZE_TYPE)uIndex);
|
if (m_pInfo->m_iLastIndexAdded != ZIP_FILE_INDEX_UNSPECIFIED)
|
{
|
if (m_pInfo->m_iLastIndexAdded == uIndex)
|
m_pInfo->m_iLastIndexAdded = ZIP_FILE_INDEX_UNSPECIFIED;
|
else if (m_pInfo->m_iLastIndexAdded > uIndex)
|
m_pInfo->m_iLastIndexAdded--;
|
}
|
}
|
}
|
|
|
void CZipCentralDir::RemoveLastFile(CZipFileHeader* pHeader, ZIP_INDEX_TYPE uIndex)
|
{
|
if (uIndex == ZIP_FILE_INDEX_UNSPECIFIED)
|
{
|
if (m_pHeaders->GetSize() == 0)
|
return;
|
uIndex = (ZIP_VOLUME_TYPE)(m_pHeaders->GetSize() - 1);
|
}
|
if (!pHeader)
|
pHeader = (*m_pHeaders)[(ZIP_ARRAY_SIZE_TYPE)uIndex];
|
ZIP_SIZE_TYPE uNewSize = pHeader->m_uOffset + m_pStorage->m_uBytesBeforeZip;
|
// then remove
|
RemoveFile(pHeader, uIndex);
|
|
m_pStorage->Flush();
|
m_pStorage->m_pFile->SetLength((ZIP_FILE_USIZE)uNewSize);
|
m_pInfo->m_bInArchive = false; // it is true when AutoFlush is set to true
|
}
|
|
|
ZIP_SIZE_TYPE CZipCentralDir::GetSize(bool bWhole) const
|
{
|
ZIP_SIZE_TYPE uTotal = CENTRAL_DIR_END_SIZE + m_pInfo->m_pszComment.GetSize();
|
ZIP_INDEX_TYPE uCount = (ZIP_INDEX_TYPE)m_pHeaders->GetSize();
|
if (bWhole)
|
{
|
for (ZIP_INDEX_TYPE i = 0; i < uCount; i++)
|
{
|
const CZipFileHeader* pHeader = (*m_pHeaders)[(ZIP_ARRAY_SIZE_TYPE)i];
|
uTotal += pHeader->GetSize();
|
}
|
}
|
|
|
return uTotal;
|
}
|
|
bool CZipCentralDir::RemoveDataDescr(bool bFromBuffer)
|
{
|
// this will not work if there are bytes before zip
|
CZipFileMapping fm;
|
char* pFile = NULL ;
|
ZIP_SIZE_TYPE uSize;
|
if (bFromBuffer)
|
{
|
uSize = m_pStorage->m_uBytesInWriteBuffer;
|
pFile = m_pStorage->m_pWriteBuffer;
|
}
|
else
|
{
|
uSize = (ZIP_SIZE_TYPE)m_pStorage->m_pFile->GetLength();
|
// we cannot use CZipMemFile in multi-volume archive
|
// so it must be CZipFile
|
if (!fm.CreateMapping(static_cast<CZipFile*>(m_pStorage->m_pFile)))
|
return false;
|
pFile = fm.GetMappedMemory();
|
}
|
|
ZIP_SIZE_TYPE uOffsetToChange = 4;
|
ZIP_SIZE_TYPE uPosInBuffer = 0;
|
WORD uExtraHeaderLen;
|
ZIP_INDEX_TYPE uCount = (ZIP_INDEX_TYPE)m_pHeaders->GetSize();
|
for (ZIP_INDEX_TYPE i = 0; i < uCount; i++)
|
{
|
CZipFileHeader* pHeader = (*m_pHeaders)[(ZIP_ARRAY_SIZE_TYPE)i];
|
char* pSource = pFile + pHeader->m_uOffset;
|
|
if (pHeader->NeedsDataDescriptor())
|
uExtraHeaderLen = (WORD)(pHeader->IsEncrypted() ? 0 : 4);
|
else
|
{
|
uExtraHeaderLen = pHeader->GetDataDescriptorSize(true);
|
// removing data descriptor
|
pHeader->m_uFlag &= ~8;
|
// update local header:
|
// write modified flag in the local header
|
CBytesWriter::WriteBytes(pSource + 6, pHeader->m_uFlag);
|
pHeader->WriteSmallDataDescriptor(pSource + 14, false);
|
}
|
|
ZIP_SIZE_TYPE uToCopy = (i == (uCount - 1) ? uSize : (*m_pHeaders)[(ZIP_ARRAY_SIZE_TYPE)(i + 1)]->m_uOffset)
|
- pHeader->m_uOffset - uExtraHeaderLen;
|
if (uToCopy > 0)
|
// TODO: [postponed] the size_t limit on uToCopy, but creating such a big segment is unlikely (at least at the moment of writing)
|
memmove(pFile + uPosInBuffer, pSource, (size_t)uToCopy);
|
|
uPosInBuffer += uToCopy;
|
pHeader->m_uOffset -= uOffsetToChange;
|
uOffsetToChange += uExtraHeaderLen;
|
}
|
|
if (bFromBuffer)
|
m_pStorage->m_uBytesInWriteBuffer = (DWORD)uPosInBuffer;
|
else
|
{
|
m_pStorage->m_uBytesWritten = uPosInBuffer;
|
fm.RemoveMapping();
|
m_pStorage->m_pFile->SetLength((ZIP_FILE_USIZE)uPosInBuffer);
|
}
|
return true;
|
}
|
|
void CZipCentralDir::RemoveHeaders()
|
{
|
ZIP_INDEX_TYPE uCount = (ZIP_INDEX_TYPE)m_pHeaders->GetSize();
|
for (ZIP_INDEX_TYPE i = 0; i < uCount; i++)
|
delete (*m_pHeaders)[(ZIP_ARRAY_SIZE_TYPE)i];
|
m_pHeaders->RemoveAll();
|
}
|
|
void CZipCentralDir::BuildFindFastArray( bool bCaseSensitive )
|
{
|
ClearFindFastArray();
|
m_pInfo->m_bCaseSensitive = bCaseSensitive;
|
// for later
|
m_pInfo->m_pCompare = GetCZipStrCompFunc(bCaseSensitive);
|
ZIP_INDEX_TYPE uCount = (ZIP_INDEX_TYPE)m_pHeaders->GetSize();
|
|
for (ZIP_INDEX_TYPE i = 0; i < uCount; i++)
|
m_pFindArray->Add(new CZipFindFast((*m_pHeaders)[(ZIP_ARRAY_SIZE_TYPE)i], i));
|
|
m_pFindArray->Sort(bCaseSensitive ? CompareFindFastCollate : CompareFindFastCollateNoCase);
|
}
|
|
void CZipCentralDir::EnableFindFast(bool bEnable, bool bCaseSensitive)
|
{
|
if (m_pInfo->m_bFindFastEnabled == bEnable)
|
return;
|
m_pInfo->m_bFindFastEnabled = bEnable;
|
if (bEnable)
|
BuildFindFastArray(bCaseSensitive);
|
else
|
m_pFindArray->RemoveAll();
|
}
|
|
ZIP_INDEX_TYPE CZipCentralDir::FindFile(LPCTSTR lpszFileName, bool bCaseSensitive, bool bSporadically, bool bFileNameOnly)
|
{
|
if (!m_pInfo->m_bFindFastEnabled)
|
EnableFindFast(true, bSporadically ? !bCaseSensitive : bCaseSensitive);
|
ZIP_INDEX_TYPE uResult = ZIP_FILE_INDEX_NOT_FOUND;
|
if (bFileNameOnly)
|
{
|
// a non-effective search (treat an array as unsorted)
|
|
// set the proper compare function
|
ZIPSTRINGCOMPARE pCompare = bCaseSensitive == m_pInfo->m_bCaseSensitive ? m_pInfo->m_pCompare : GetCZipStrCompFunc(bCaseSensitive);
|
|
ZIP_INDEX_TYPE uSize = (ZIP_INDEX_TYPE)m_pFindArray->GetSize();
|
for (ZIP_INDEX_TYPE i = 0; i < uSize; i++)
|
{
|
CZipString sz = (*m_pFindArray)[(ZIP_ARRAY_SIZE_TYPE)i]->m_pHeader->GetFileName();
|
CZipPathComponent::RemoveSeparators(sz); // to find a dir
|
CZipPathComponent zpc(sz);
|
sz = zpc.GetFileName();
|
if ((sz.*pCompare)(lpszFileName) == 0)
|
{
|
uResult = i;
|
break;
|
}
|
}
|
}
|
else if (bCaseSensitive == m_pInfo->m_bCaseSensitive)
|
uResult = FindFileNameIndex(lpszFileName);
|
else
|
{
|
if (bSporadically)
|
{
|
// a non-effective search (treat an array as unsorted)
|
// do not use m_pInfo->m_pCompare, as it may be shared
|
ZIPSTRINGCOMPARE pCompare = GetCZipStrCompFunc(bCaseSensitive);
|
ZIP_INDEX_TYPE uSize = (ZIP_INDEX_TYPE)m_pFindArray->GetSize();
|
for (ZIP_INDEX_TYPE i = 0; i < uSize; i++)
|
if (((*m_pFindArray)[(ZIP_ARRAY_SIZE_TYPE)i]->m_pHeader->GetFileName().*pCompare)(lpszFileName) == 0)
|
{
|
uResult = i;
|
break;
|
}
|
}
|
else
|
{
|
BuildFindFastArray(bCaseSensitive);
|
uResult = FindFileNameIndex(lpszFileName);
|
}
|
}
|
return uResult == ZIP_FILE_INDEX_NOT_FOUND ? ZIP_FILE_INDEX_NOT_FOUND : (*m_pFindArray)[(ZIP_ARRAY_SIZE_TYPE)uResult]->m_uIndex;
|
}
|
|
ZIP_INDEX_TYPE CZipCentralDir::InsertFindFastElement(CZipFileHeader* pHeader, ZIP_INDEX_TYPE uIndex)
|
{
|
CZipString fileName = pHeader->GetFileName();
|
ZIP_ARRAY_SIZE_TYPE uSize = m_pFindArray->GetSize();
|
|
// Our initial binary search range encompasses the entire array of filenames:
|
ZIP_ARRAY_SIZE_TYPE start = 0;
|
ZIP_ARRAY_SIZE_TYPE end = uSize;
|
|
// Keep halving our search range until we find the right place
|
// to insert the new element:
|
while ( start < end )
|
{
|
// Find the midpoint of the search range:
|
ZIP_ARRAY_SIZE_TYPE midpoint = ( start + end ) / 2;
|
|
// Compare the filename with the filename at the midpoint of the current search range:
|
int result = CompareElement(fileName, (ZIP_INDEX_TYPE)midpoint);
|
|
// If our filename is larger, it must fall in the first half of the search range:
|
if ( result > 0 )
|
{
|
end = midpoint;
|
}
|
|
// If it's smaller, it must fall in the last half:
|
else if ( result < 0 )
|
{
|
start = midpoint + 1;
|
}
|
|
// If they're equal, we can go ahead and insert here:
|
else
|
{
|
start = midpoint;
|
break;
|
}
|
}
|
m_pFindArray->InsertAt(start, new CZipFindFast(pHeader, uIndex == ZIP_FILE_INDEX_UNSPECIFIED ? (ZIP_INDEX_TYPE)uSize : uIndex /* just in case */));
|
return (ZIP_INDEX_TYPE)start;
|
}
|
|
ZIP_INDEX_TYPE CZipCentralDir::FindFileNameIndex(LPCTSTR lpszFileName) const
|
{
|
ZIP_ARRAY_SIZE_TYPE start = 0;
|
|
ZIP_ARRAY_SIZE_TYPE end = m_pFindArray->GetSize();
|
if (end == 0)
|
return ZIP_FILE_INDEX_NOT_FOUND;
|
else
|
end--;
|
|
// Keep halving our search range until we find the given element:
|
while ( start <= end )
|
{
|
// Find the midpoint of the search range:
|
ZIP_ARRAY_SIZE_TYPE midpoint = ( start + end ) / 2;
|
|
// Compare the given filename with the filename at the midpoint of the search range:
|
int result = CompareElement(lpszFileName, (ZIP_INDEX_TYPE)midpoint);
|
|
// If our filename is smaller, it must fall in the first half of the search range:
|
if ( result > 0 )
|
{
|
if (midpoint == 0)
|
return ZIP_FILE_INDEX_NOT_FOUND;
|
end = midpoint - 1;
|
}
|
|
// If it's larger, it must fall in the last half:
|
else if ( result < 0 )
|
{
|
start = midpoint + 1;
|
}
|
|
// If they're equal, return the result:
|
else
|
{
|
return (ZIP_INDEX_TYPE)midpoint;
|
}
|
}
|
|
// Signal failure:
|
return ZIP_FILE_INDEX_NOT_FOUND;
|
}
|
|
void CZipCentralDir::GetComment(CZipString& szComment) const
|
{
|
ZipCompatibility::ConvertBufferToString(szComment, m_pInfo->m_pszComment, ZipCompatibility::GetDefaultCommentCodePage(m_pArchive->GetSystemCompatibility()));
|
}
|
|
#ifdef _ZIP_UNICODE_CUSTOM
|
|
const CZipStringStoreSettings& CZipCentralDir::GetStringStoreSettings() const
|
{
|
return m_pArchive->GetStringStoreSettings();
|
}
|
|
#endif
|
|
bool CZipCentralDir::OnFileNameChange(CZipFileHeader* pHeader)
|
{
|
bool result;
|
if (m_pArchive->GetCommitMode() == CZipArchive::cmOnChange)
|
result = m_pArchive->CommitChanges();
|
else
|
result = m_pArchive->CanModify();
|
|
if (result && m_pInfo->m_bFindFastEnabled)
|
{
|
InsertFindFastElement(pHeader, RemoveFindFastElement(pHeader, false));
|
}
|
return result;
|
}
|
|
bool CZipCentralDir::OnFileCentralChange()
|
{
|
if (!m_pArchive->CanModify(true))
|
{
|
return false;
|
}
|
|
RemoveFromDisk();
|
m_pArchive->Finalize(true);
|
return true;
|
}
|
|
void CZipCentralDir::CreateSharedData()
|
{
|
m_pInfo = new CInfo();
|
m_pInfo->Init();
|
m_pHeaders = new CZipArray<CZipFileHeader*>();
|
m_pFindArray = new CZipArray<CZipFindFast*>();
|
}
|
|
bool CZipCentralDir::IsAnyFileModified() const
|
{
|
ZIP_INDEX_TYPE uCount = (ZIP_INDEX_TYPE)m_pHeaders->GetSize();
|
for (ZIP_INDEX_TYPE i = 0; i < uCount; i++)
|
if ((*m_pHeaders)[(ZIP_ARRAY_SIZE_TYPE)i]->IsModified())
|
return true;
|
return false;
|
}
|
|
void CZipCentralDir::DestroySharedData()
|
{
|
if (!m_pInfo)
|
return;
|
#ifdef _ZIP_USE_LOCKING
|
LockAccess();
|
try
|
{
|
#endif
|
m_pInfo->m_iReference--;
|
if (m_pInfo->m_iReference <= 0) // <= is just in case instead of ==
|
{
|
|
if (m_pHeaders != NULL)
|
{
|
RemoveHeaders();
|
delete m_pHeaders;
|
m_pHeaders = NULL;
|
}
|
|
if (m_pFindArray != NULL)
|
{
|
ClearFindFastArray();
|
delete m_pFindArray;
|
m_pFindArray = NULL;
|
}
|
#ifdef _ZIP_USE_LOCKING
|
UnlockAccess();
|
#endif
|
delete m_pInfo;
|
m_pInfo = NULL;
|
}
|
#ifdef _ZIP_USE_LOCKING
|
}
|
catch(...)
|
{
|
UnlockAccess();
|
throw;
|
}
|
UnlockAccess();
|
#endif
|
}
|
|
|
ZIP_FILE_USIZE CZipCentralDir::LocateSignature()
|
{
|
return m_pStorage->LocateSignature(m_gszSignature, 0xFFFF + CENTRAL_DIR_END_SIZE);
|
}
|