////////////////////////////////////////////////////////////////////////////////
|
// 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 "ZipFileHeader.h"
|
#include "ZipAutoBuffer.h"
|
#include "ZipArchive.h"
|
#include "ZipPlatform.h"
|
#include "ZipCompatibility.h"
|
#include <time.h>
|
|
#include "ZipCrc32Cryptograph.h"
|
#include "BytesWriter.h"
|
#include "zlib/zlib.h"
|
|
#define FILEHEADERSIZE 46
|
#define LOCALFILEHEADERSIZE 30
|
#ifdef _ZIP_UNICODE_CUSTOM
|
#define ZIP_EXTRA_ZARCH_NAME_VER 1
|
#endif
|
|
using namespace ZipArchiveLib;
|
|
char CZipFileHeader::m_gszSignature[] = {0x50, 0x4b, 0x01, 0x02};
|
char CZipFileHeader::m_gszLocalSignature[] = {0x50, 0x4b, 0x03, 0x04};
|
CZipFileHeader::CZipFileHeader()
|
{
|
Initialize(NULL);
|
SetSystemCompatibility(ZipPlatform::GetSystemID());
|
}
|
|
CZipFileHeader::CZipFileHeader(CZipCentralDir* pCentralDir)
|
{
|
Initialize(pCentralDir);
|
}
|
|
CZipFileHeader::~CZipFileHeader()
|
{
|
|
}
|
|
void CZipFileHeader::Initialize(CZipCentralDir* pCentralDir)
|
{
|
m_uExternalAttr = 0;//ZipPlatform::GetDefaultAttributes();
|
m_uModDate = m_uModTime = 0;
|
m_uMethod = CZipCompressor::methodDeflate;
|
m_uVersionMadeBy = 0;
|
m_uCrc32 = 0;
|
// initialize to 0, because on 64 bit platform unsigned long is 8 byte and we are copying only 4 bytes in Read()
|
m_uComprSize = m_uUncomprSize = m_uOffset = 0;
|
m_uLocalFileNameSize = 0;
|
m_uLocalComprSize = m_uLocalUncomprSize = 0;
|
m_uLocalHeaderSize = 0;
|
m_uVolumeStart = 0;
|
m_uEncryptionMethod = CZipCryptograph::encNone;
|
m_bIgnoreCrc32 = false;
|
m_uFlag = 0;
|
m_pCentralDir = pCentralDir;
|
m_state = sfNone;
|
}
|
|
bool CZipFileHeader::Read(bool bReadSignature)
|
{
|
m_state = sfNone;
|
|
CZipStorage *pStorage = m_pCentralDir->GetStorage();
|
WORD uFileNameSize, uCommentSize, uExtraFieldSize;
|
CZipAutoBuffer buf(FILEHEADERSIZE);
|
if (bReadSignature)
|
{
|
pStorage->Read(buf, FILEHEADERSIZE, true);
|
if (!VerifySignature(buf))
|
return false;
|
}
|
else
|
pStorage->Read((char*)buf + 4, FILEHEADERSIZE - 4, true);
|
|
WORD uVersionMadeBy;
|
CBytesWriter::ReadBytes(uVersionMadeBy, buf + 4);
|
CBytesWriter::ReadBytes(m_uVersionNeeded, buf + 6);
|
CBytesWriter::ReadBytes(m_uFlag, buf + 8);
|
CBytesWriter::ReadBytes(m_uMethod, buf + 10);
|
CBytesWriter::ReadBytes(m_uModTime, buf + 12);
|
CBytesWriter::ReadBytes(m_uModDate, buf + 14);
|
CBytesWriter::ReadBytes(m_uCrc32, buf + 16);
|
CBytesWriter::ReadBytes(m_uComprSize, buf + 20, 4);
|
CBytesWriter::ReadBytes(m_uUncomprSize, buf + 24, 4);
|
CBytesWriter::ReadBytes(uFileNameSize, buf + 28);
|
CBytesWriter::ReadBytes(uExtraFieldSize, buf + 30);
|
CBytesWriter::ReadBytes(uCommentSize, buf + 32);
|
CBytesWriter::ReadBytes(m_uVolumeStart, buf + 34, 2);
|
CBytesWriter::ReadBytes(m_uInternalAttr, buf + 36);
|
CBytesWriter::ReadBytes(m_uExternalAttr, buf + 38);
|
CBytesWriter::ReadBytes(m_uOffset, buf + 42, 4);
|
buf.Release();
|
|
m_uVersionMadeBy = uVersionMadeBy & 0xFF;
|
SetSystemCompatibility((uVersionMadeBy & 0xFF00) >> 8);
|
|
// we may need to modify this later
|
m_uEncryptionMethod = (BYTE)((m_uFlag & (WORD) 1) != 0 ? CZipCryptograph::encStandard : CZipCryptograph::encNone);
|
|
ZIP_VOLUME_TYPE uCurDsk = pStorage->GetCurrentVolume();
|
m_fileName.m_buffer.Allocate(uFileNameSize); // don't add NULL at the end
|
pStorage->Read(m_fileName.m_buffer, uFileNameSize, true);
|
|
#ifdef _ZIP_UNICODE_CUSTOM
|
const CZipStringStoreSettings& stringStoreSettings = m_pCentralDir->GetStringStoreSettings();
|
|
if (stringStoreSettings.IsStandardNameCodePage())
|
m_stringSettings.SetDefaultNameCodePage(GetSystemCompatibility());
|
else
|
m_stringSettings.m_uNameCodePage = stringStoreSettings.m_uNameCodePage;
|
|
if (!stringStoreSettings.IsStandardCommentCodePage())
|
m_stringSettings.m_uCommentCodePage = stringStoreSettings.m_uCommentCodePage;
|
#endif
|
|
if (!m_aCentralExtraData.Read(pStorage, uExtraFieldSize))
|
return false;
|
#if defined _ZIP_UNICODE || defined _ZIP_UNICODE_CUSTOM || defined _ZIP_AES || defined _ZIP_ZIP64
|
CZipExtraData* pExtra = NULL ;
|
#endif
|
|
|
#ifdef _ZIP_UNICODE_CUSTOM
|
if (m_state == sfNone)
|
{
|
pExtra = m_aCentralExtraData.Lookup(ZIP_EXTRA_ZARCH_NAME);
|
if (pExtra != NULL)
|
{
|
m_state = sfCustomUnicode;
|
|
WORD uExtraDataSize = (WORD)pExtra->m_data.GetSize();
|
int offset = 1;
|
if (offset > uExtraDataSize)
|
return false;
|
if (pExtra->m_data[0] <= ZIP_EXTRA_ZARCH_NAME_VER) // else don't parse it
|
{
|
offset++;
|
if (offset > uExtraDataSize)
|
return false;
|
BYTE flag = pExtra->m_data[1];
|
bool bReadCommentCp = (flag & 4) != 0;
|
if ((flag & 1) != 0)
|
{
|
// code page present
|
if (offset + 4 > uExtraDataSize)
|
return false;
|
// avoid warnings
|
DWORD temp;
|
CBytesWriter::ReadBytes(temp, pExtra->m_data + offset);
|
m_stringSettings.m_uNameCodePage = temp;
|
offset += 4;
|
}
|
|
if ((flag & 3) == 3)
|
{
|
m_stringSettings.m_bStoreNameInExtraData = true;
|
int iFileNameSize = pExtra->m_data.GetSize() - 2 - 4;
|
if (bReadCommentCp)
|
iFileNameSize -= 4;
|
// code page present
|
if (offset + iFileNameSize > uExtraDataSize || iFileNameSize <= 0)
|
return false;
|
// just in case
|
m_fileName.ClearString();
|
m_fileName.m_buffer.Allocate(iFileNameSize);
|
memcpy(m_fileName.m_buffer, pExtra->m_data + offset, iFileNameSize);
|
offset += iFileNameSize;
|
}
|
else
|
m_stringSettings.m_bStoreNameInExtraData = false;
|
|
if (bReadCommentCp)
|
{
|
// code page present
|
if (offset + 4 > uExtraDataSize)
|
return false;
|
DWORD temp;
|
CBytesWriter::ReadBytes(temp, pExtra->m_data + offset);
|
m_stringSettings.m_uCommentCodePage = temp;
|
// offset += 4;
|
}
|
}
|
}
|
else if (m_pCentralDir->GetUnicodeMode() == CZipArchive::umCustom)
|
{
|
m_state = sfCustomUnicode;
|
}
|
}
|
#endif
|
|
|
|
if (uCommentSize)
|
{
|
m_comment.m_buffer.Allocate(uCommentSize);
|
pStorage->Read(m_comment.m_buffer, uCommentSize, true);
|
}
|
|
m_aCentralExtraData.RemoveInternalHeaders();
|
|
return pStorage->GetCurrentVolume() == uCurDsk || pStorage->IsBinarySplit(); // check that the whole header is in one volume
|
}
|
|
|
time_t CZipFileHeader::GetTime()const
|
{
|
struct tm atm;
|
atm.tm_sec = (m_uModTime & ~0xFFE0) << 1;
|
atm.tm_min = (m_uModTime & ~0xF800) >> 5;
|
atm.tm_hour = m_uModTime >> 11;
|
|
atm.tm_mday = m_uModDate & ~0xFFE0;
|
atm.tm_mon = ((m_uModDate & ~0xFE00) >> 5) - 1;
|
atm.tm_year = (m_uModDate >> 9) + 80;
|
atm.tm_isdst = -1;
|
return mktime(&atm);
|
}
|
|
// write the header to the central dir
|
DWORD CZipFileHeader::Write(CZipStorage *pStorage)
|
{
|
m_aCentralExtraData.RemoveInternalHeaders();
|
|
WORD uMethod = m_uMethod;
|
|
PrepareStringBuffers();
|
|
if (!CheckLengths(false))
|
CZipException::Throw(CZipException::tooLongData);
|
|
#ifdef _ZIP_UNICODE_CUSTOM
|
if (m_state == sfCustomUnicode)
|
{
|
if (m_stringSettings.m_bStoreNameInExtraData)
|
{
|
if (!m_fileName.HasString() && m_fileName.HasBuffer())
|
GetFileName(false); // don't clear the buffer, it will be needed in a moment
|
ASSERT(m_fileName.HasString());
|
ASSERT(m_fileName.HasBuffer());
|
if (m_fileName.GetString().GetLength() == 0)
|
m_stringSettings.m_bStoreNameInExtraData = false;
|
}
|
|
if (!m_stringSettings.IsStandard(GetSystemCompatibility()))
|
{
|
CZipExtraData* pExtra = m_aCentralExtraData.CreateNew(ZIP_EXTRA_ZARCH_NAME);
|
bool bWriteCommentCp = !m_stringSettings.IsStandardCommentCodePage(GetSystemCompatibility());
|
|
BYTE flag = 0;
|
int offset = 2;
|
char* data = NULL;
|
if (m_stringSettings.m_bStoreNameInExtraData)
|
{
|
CZipAutoBuffer buffer;
|
// m_fileName.m_buffer contains CP_ACP page, we don't check if the current page is CP_ACP - too large dependency on PrepareStringBuffers
|
ZipCompatibility::ConvertStringToBuffer(m_fileName.GetString(), buffer, m_stringSettings.m_uNameCodePage);
|
int size = 2 + 4 + buffer.GetSize();
|
if (bWriteCommentCp)
|
size += 4;
|
pExtra->m_data.Allocate(size);
|
data = (char*)pExtra->m_data;
|
CBytesWriter::WriteBytes(data + offset, (DWORD)m_stringSettings.m_uNameCodePage);
|
offset += 4;
|
memcpy(data + offset, buffer, buffer.GetSize());
|
offset += buffer.GetSize();
|
flag = 3;
|
}
|
else if (!m_stringSettings.IsStandardNameCodePage(GetSystemCompatibility()))
|
{
|
int size = 2 + 4;
|
if (bWriteCommentCp)
|
size += 4;
|
pExtra->m_data.Allocate(size);
|
data = (char*)pExtra->m_data;
|
CBytesWriter::WriteBytes(data + offset, (DWORD)m_stringSettings.m_uNameCodePage);
|
offset += 4;
|
flag = 1;
|
}
|
|
if (bWriteCommentCp)
|
{
|
if (!pExtra->m_data.IsAllocated())
|
{
|
pExtra->m_data.Allocate(2 + 4);
|
data = (char*)pExtra->m_data;
|
}
|
ASSERT(data);
|
CBytesWriter::WriteBytes(data + offset, (DWORD)m_stringSettings.m_uCommentCodePage);
|
flag |= 4;
|
}
|
|
data[0] = ZIP_EXTRA_ZARCH_NAME_VER;
|
data[1] = flag;
|
}
|
}
|
#endif
|
|
WORD uFileNameSize = (WORD)m_fileName.GetBufferSize(), uCommentSize = m_comment.GetBufferSize(),
|
uExtraFieldSize = (WORD)m_aCentralExtraData.GetTotalSize();
|
DWORD uSize = FILEHEADERSIZE + uFileNameSize + uCommentSize + uExtraFieldSize;
|
CZipAutoBuffer buf(uSize);
|
char* dest = (char*)buf;
|
memcpy(dest, m_gszSignature, 4);
|
WORD uVersionMadeBy = (int)m_uVersionMadeBy & 0xFF;
|
uVersionMadeBy |= (WORD)(m_iSystemCompatibility << 8);
|
CBytesWriter::WriteBytes(dest + 4, uVersionMadeBy);
|
CBytesWriter::WriteBytes(dest + 6, m_uVersionNeeded);
|
CBytesWriter::WriteBytes(dest + 8, m_uFlag);
|
CBytesWriter::WriteBytes(dest + 10, uMethod);
|
CBytesWriter::WriteBytes(dest + 12, m_uModTime);
|
CBytesWriter::WriteBytes(dest + 14, m_uModDate);
|
WriteCrc32(dest + 16);
|
CBytesWriter::WriteBytes(dest + 20, CBytesWriter::WriteSafeU32(m_uComprSize));
|
CBytesWriter::WriteBytes(dest + 24, CBytesWriter::WriteSafeU32(m_uUncomprSize));
|
CBytesWriter::WriteBytes(dest + 28, uFileNameSize);
|
CBytesWriter::WriteBytes(dest + 30, uExtraFieldSize);
|
CBytesWriter::WriteBytes(dest + 32, uCommentSize);
|
CBytesWriter::WriteBytes(dest + 34, CBytesWriter::WriteSafeU16(m_uVolumeStart));
|
CBytesWriter::WriteBytes(dest + 36, m_uInternalAttr);
|
CBytesWriter::WriteBytes(dest + 38, m_uExternalAttr);
|
CBytesWriter::WriteBytes(dest + 42, CBytesWriter::WriteSafeU32(m_uOffset));
|
|
memcpy(dest + 46, m_fileName.m_buffer, uFileNameSize);
|
|
if (uExtraFieldSize)
|
m_aCentralExtraData.Write(dest + 46 + uFileNameSize);
|
|
if (uCommentSize)
|
memcpy(dest + 46 + uFileNameSize + uExtraFieldSize, m_comment.m_buffer, uCommentSize);
|
|
pStorage->Write(dest, uSize, true);
|
|
// remove to avoid miscalculations in GetSize()
|
m_aCentralExtraData.RemoveInternalHeaders();
|
ClearFileName();
|
return uSize;
|
}
|
|
bool CZipFileHeader::ReadLocal(CZipCentralDir* pCentralDir)
|
{
|
CZipStorage* pStorage = pCentralDir->GetStorage();
|
pStorage->ChangeVolume(m_uVolumeStart);
|
bool isBinary = pStorage->IsBinarySplit();
|
if (isBinary)
|
pStorage->SeekInBinary(m_uOffset, true);
|
else
|
pStorage->Seek(m_uOffset);
|
|
char buf[LOCALFILEHEADERSIZE];
|
|
pStorage->Read(buf, LOCALFILEHEADERSIZE, true);
|
if (memcmp(buf, m_gszLocalSignature, 4) != 0)
|
return false;
|
|
bool bIsDataDescr = (((WORD)*(buf + 6)) & 8) != 0;
|
|
WORD uTemp;
|
CBytesWriter::ReadBytes(uTemp, buf + 6);
|
// do not compare the whole flag - the bits reserved by PKWARE may differ
|
// in local and central headers
|
if (pCentralDir->IsConsistencyCheckOn(CZipArchive::checkLocalFlag)
|
&& (uTemp & 0xf) != (m_uFlag & 0xf))
|
return false;
|
|
// method
|
WORD uMethod;
|
CBytesWriter::ReadBytes(uMethod, buf + 8);
|
|
// this may be different in the local header (it may contain disk name for example)
|
CBytesWriter::ReadBytes(m_uLocalFileNameSize, buf + 26);
|
WORD uExtraFieldSize;
|
CBytesWriter::ReadBytes(uExtraFieldSize, buf + 28);
|
ZIP_VOLUME_TYPE uCurDsk = pStorage->GetCurrentVolume();
|
// skip reading the local file name
|
|
if (isBinary)
|
pStorage->SeekInBinary(m_uLocalFileNameSize);
|
else
|
pStorage->m_pFile->Seek(m_uLocalFileNameSize, CZipAbstractFile::current);
|
|
m_uLocalHeaderSize = LOCALFILEHEADERSIZE + m_uLocalFileNameSize + uExtraFieldSize;
|
if (!m_aLocalExtraData.Read(pStorage, uExtraFieldSize))
|
return false;
|
|
CBytesWriter::ReadBytes(m_uLocalComprSize, buf + 18, 4);
|
CBytesWriter::ReadBytes(m_uLocalUncomprSize, buf + 22, 4);
|
|
|
if (uMethod == CZipCompressor::methodWinZipAes && IsEncrypted())
|
CZipException::Throw(CZipException::noAES);
|
|
if (pCentralDir->IsConsistencyCheckOn(CZipArchive::checkLocalMethod)
|
&& uMethod != m_uMethod )
|
return false;
|
|
if (!bIsDataDescr && pCentralDir->IsConsistencyCheckOn(CZipArchive::checkLocalCRC | CZipArchive::checkLocalSizes))
|
{
|
// read all at once - probably faster than checking and reading separately
|
DWORD uCrc32;
|
CBytesWriter::ReadBytes(uCrc32, buf + 14);
|
if (pCentralDir->IsConsistencyCheckOn(CZipArchive::checkLocalCRC)
|
&& uCrc32 != m_uCrc32)
|
return false;
|
|
if (pCentralDir->IsConsistencyCheckOn(CZipArchive::checkLocalSizes)
|
// do not check, if local compressed size is 0 - this usually means, that some archiver
|
// could not update the compressed size after compression
|
&& ( m_uLocalComprSize != 0 && m_uLocalComprSize != m_uComprSize || m_uLocalUncomprSize != m_uUncomprSize))
|
return false;
|
}
|
return pStorage->GetCurrentVolume() == uCurDsk || isBinary; // check that the whole header is in one volume
|
}
|
|
void CZipFileHeader::SetTime(const time_t & ttime)
|
{
|
#if _MSC_VER >= 1400
|
tm gts;
|
tm* gt = >s;
|
localtime_s(gt, &ttime);
|
#else
|
tm* gt = localtime(&ttime);
|
#endif
|
WORD year, month, day, hour, min, sec;
|
if (gt == NULL)
|
{
|
year = 0;
|
month = day = 1;
|
hour = min = sec = 0;
|
}
|
else
|
{
|
year = (WORD)(gt->tm_year + 1900);
|
if (year <= 1980)
|
year = 0;
|
else
|
year -= 1980;
|
month = (WORD)gt->tm_mon + 1;
|
day = (WORD)gt->tm_mday;
|
hour = (WORD)gt->tm_hour;
|
min = (WORD)gt->tm_min;
|
sec = (WORD)gt->tm_sec;
|
}
|
|
m_uModDate = (WORD) (day + ( month << 5) + (year << 9));
|
m_uModTime = (WORD) ((sec >> 1) + (min << 5) + (hour << 11));
|
}
|
|
void CZipFileHeader::ConvertFileName(CZipAutoBuffer& buffer) const
|
{
|
if (!m_fileName.HasString())
|
return;
|
CZipString temp = m_fileName.GetString();
|
ZipCompatibility::SlashBackslashChg(temp, false);
|
UINT codePage;
|
#ifdef _ZIP_UNICODE_CUSTOM
|
if (m_state.IsSetAny(sfCustomUnicode))
|
{
|
if (m_stringSettings.m_bStoreNameInExtraData)
|
{
|
codePage = GetDefaultFileNameCodePage();
|
|
}
|
else
|
{
|
codePage = m_stringSettings.m_uNameCodePage;
|
}
|
}
|
else
|
#endif
|
{
|
codePage = GetDefaultFileNameCodePage();
|
}
|
|
ZipCompatibility::ConvertStringToBuffer(temp, buffer, codePage);
|
}
|
|
void CZipFileHeader::ConvertFileName(CZipString& szFileName) const
|
{
|
if (!m_fileName.HasBuffer())
|
return;
|
UINT codePage;
|
#ifdef _ZIP_UNICODE_CUSTOM
|
if (m_state.IsSetAny(sfCustomUnicode))
|
{
|
codePage = m_stringSettings.m_uNameCodePage;
|
}
|
else
|
#endif
|
{
|
codePage = GetDefaultFileNameCodePage();
|
}
|
ZipCompatibility::ConvertBufferToString(szFileName, m_fileName.m_buffer, codePage);
|
// some archives may have an invalid path separator stored
|
ZipCompatibility::NormalizePathSeparators(szFileName);
|
}
|
|
void CZipFileHeader::ConvertComment(CZipAutoBuffer& buffer) const
|
{
|
if (!m_comment.HasString())
|
return;
|
UINT codePage;
|
#ifdef _ZIP_UNICODE_CUSTOM
|
if (m_state.IsSetAny(sfCustomUnicode))
|
{
|
codePage = m_stringSettings.m_uCommentCodePage;
|
}
|
else
|
#endif
|
{
|
codePage = GetDefaultCommentCodePage();
|
}
|
|
ZipCompatibility::ConvertStringToBuffer(m_comment.GetString(), buffer, codePage);
|
}
|
|
void CZipFileHeader::ConvertComment(CZipString& szComment) const
|
{
|
if (!m_comment.HasBuffer())
|
return;
|
UINT codePage;
|
#ifdef _ZIP_UNICODE_CUSTOM
|
if (m_state.IsSetAny(sfCustomUnicode))
|
{
|
codePage = m_stringSettings.m_uCommentCodePage;
|
}
|
else
|
#endif
|
{
|
codePage = GetDefaultCommentCodePage();
|
}
|
ZipCompatibility::ConvertBufferToString(szComment, m_comment.m_buffer, codePage);
|
}
|
|
// write the local header
|
void CZipFileHeader::WriteLocal(CZipStorage *pStorage)
|
{
|
m_aLocalExtraData.RemoveInternalLocalHeaders();
|
if (IsDataDescriptor())
|
{
|
m_uLocalComprSize = 0;
|
// write, if we know it - WinZip 9.0 in segmented mode with AES encryption will
|
// complain otherwise (this seems like a bug, because the data descriptor is present and
|
// local descriptor should be discarded)
|
if (!IsWinZipAesEncryption())
|
m_uLocalUncomprSize = 0;
|
}
|
else
|
{
|
}
|
|
WORD uMethod = m_uMethod;
|
|
PrepareStringBuffers();
|
// this check was already performed, if a file was replaced
|
if (!CheckLengths(true))
|
m_pCentralDir->ThrowError(CZipException::tooLongData);
|
m_uLocalFileNameSize = (WORD)m_fileName.GetBufferSize();
|
DWORD uExtraFieldSize = m_aLocalExtraData.GetTotalSize();
|
m_uLocalHeaderSize = LOCALFILEHEADERSIZE + uExtraFieldSize + m_uLocalFileNameSize;
|
CZipAutoBuffer buf(m_uLocalHeaderSize);
|
char* dest = (char*) buf;
|
memcpy(dest, m_gszLocalSignature, 4);
|
|
CBytesWriter::WriteBytes(dest + 4, m_uVersionNeeded);
|
CBytesWriter::WriteBytes(dest + 6, m_uFlag);
|
CBytesWriter::WriteBytes(dest + 8, uMethod);
|
CBytesWriter::WriteBytes(dest + 10, m_uModTime);
|
CBytesWriter::WriteBytes(dest + 12, m_uModDate);
|
WriteSmallDataDescriptor(dest + 14);
|
CBytesWriter::WriteBytes(dest + 26, m_uLocalFileNameSize);
|
CBytesWriter::WriteBytes(dest + 28, (WORD)uExtraFieldSize);
|
memcpy(dest + 30, m_fileName.m_buffer, m_uLocalFileNameSize);
|
|
if (uExtraFieldSize)
|
m_aLocalExtraData.Write(dest + 30 + m_uLocalFileNameSize);
|
|
// possible volume change before writing to the file in the segmented archive
|
// so write the local header first
|
pStorage->Write(dest, m_uLocalHeaderSize, true);
|
|
m_uVolumeStart = pStorage->IsBinarySplit() ? 0 : pStorage->GetCurrentVolume();
|
m_uOffset = pStorage->GetPosition() - m_uLocalHeaderSize;
|
|
m_aLocalExtraData.RemoveInternalLocalHeaders();
|
ClearFileName();
|
}
|
|
|
WORD CZipFileHeader::GetDataDescriptorSize(bool bConsiderSignature) const
|
{
|
if (IsDataDescriptor())
|
{
|
|
WORD size = 12;
|
return (WORD)(bConsiderSignature ? size + 4 : size);
|
}
|
else
|
return 0;
|
}
|
|
bool CZipFileHeader::NeedsDataDescriptor() const
|
{
|
return m_uEncryptionMethod == CZipCryptograph::encStandard;
|
}
|
|
void CZipFileHeader::PrepareData(int iLevel, bool bSegm)
|
{
|
// could be == 1, but the way below it works for PredictMaximumFileSizeInArchive when used on an existing segmented archive
|
m_uInternalAttr = 0;
|
|
// version made by
|
|
m_uVersionMadeBy = (unsigned char)0x14;
|
|
|
m_uCrc32 = 0;
|
m_uComprSize = 0;
|
m_uUncomprSize = 0;
|
|
m_uFlag = 0;
|
if (m_uMethod == CZipCompressor::methodDeflate)
|
{
|
switch (iLevel)
|
{
|
case 1:
|
m_uFlag |= 6;
|
break;
|
case 2:
|
m_uFlag |= 4;
|
break;
|
case 8:
|
case 9:
|
m_uFlag |= 2;
|
break;
|
}
|
}
|
|
UpdateFlag(bSegm);
|
|
AdjustLocalComprSize();
|
|
m_uVersionNeeded = 0;
|
if (m_uVersionNeeded == 0)
|
m_uVersionNeeded = IsDirectory() ? 0xa : 0x14; // 1.0 or 2.0
|
}
|
|
|
void CZipFileHeader::GetCrcAndSizes(char * pBuffer)const
|
{
|
WriteCrc32(pBuffer);
|
CBytesWriter::WriteBytes(pBuffer + 4, m_uComprSize, 4);
|
CBytesWriter::WriteBytes(pBuffer + 8, m_uUncomprSize, 4);
|
}
|
|
bool CZipFileHeader::CheckDataDescriptor(CZipStorage* pStorage) const
|
{
|
if (!IsDataDescriptor())
|
return true;
|
|
const int sizeOfSize = 4;
|
|
const int size = 4 + 2 * sizeOfSize; // crc and two sizes
|
|
CZipAutoBuffer buf(size + 4);
|
pStorage->Read(buf, size, false);
|
char* pBuf = NULL ;
|
|
// when an archive is segmented, files that are divided between volume have bit 3 of flag set
|
// which tell about the presence of the data descriptor after the compressed data
|
// This signature may be in a segmented archive that is one volume only
|
// (it is detected as a not segmented archive)
|
if (memcmp(buf, CZipStorage::m_gszExtHeaderSignat, 4) == 0) // there is a signature
|
{
|
pStorage->Read((char*)buf + size, 4, false);
|
pBuf = (char*)buf + 4;
|
}
|
else
|
pBuf = buf;
|
|
DWORD uCrc32 = 0;
|
ZIP_SIZE_TYPE uCompressed = 0, uUncompressed = 0;
|
|
CBytesWriter::ReadBytes(uCrc32, pBuf);
|
CBytesWriter::ReadBytes(uCompressed, pBuf + 4, sizeOfSize);
|
CBytesWriter::ReadBytes(uUncompressed, pBuf + 4 + sizeOfSize, sizeOfSize);
|
return uCrc32 == m_uCrc32 && uCompressed == m_uComprSize && uUncompressed == m_uUncomprSize;
|
}
|
|
DWORD CZipFileHeader::GetSize()const
|
{
|
DWORD uSize = FILEHEADERSIZE + PredictFileNameSize() + PredictCommentSize();
|
uSize += m_aCentralExtraData.GetTotalSize();
|
|
#ifdef _ZIP_UNICODE_CUSTOM
|
if (m_state.IsSetAny(sfCustomUnicode))
|
{
|
if (m_stringSettings.m_bStoreNameInExtraData)
|
{
|
CZipString temp;
|
if (m_fileName.HasString())
|
temp = m_fileName.GetString();
|
else
|
ConvertFileName(temp);
|
if (temp.GetLength() > 0)
|
{
|
uSize += 4 + 2 + 4; // headerID, size + version, flag + filename code page
|
CZipAutoBuffer buffer;
|
ZipCompatibility::ConvertStringToBuffer(temp, buffer, m_stringSettings.m_uNameCodePage);
|
uSize += buffer.GetSize();
|
if (!m_stringSettings.IsStandardCommentCodePage(GetSystemCompatibility()))
|
uSize += 4;
|
}
|
}
|
}
|
#endif
|
return uSize;
|
}
|
|
DWORD CZipFileHeader::GetLocalSize(bool bReal)const
|
{
|
if (bReal)
|
return m_uLocalHeaderSize;
|
|
DWORD uSize = LOCALFILEHEADERSIZE + m_aLocalExtraData.GetTotalSize() + PredictFileNameSize();
|
return uSize;
|
}
|
|
bool CZipFileHeader::SetComment(LPCTSTR lpszComment)
|
{
|
if (m_pCentralDir)
|
{
|
// update the lpszFileName to make sure the renaming is necessary
|
GetComment();
|
CZipString newComment(lpszComment);
|
if (!UpdateCommentFlags(&newComment))
|
{
|
if (m_comment.GetString().Collate(newComment) == 0)
|
return true;
|
}
|
// just in case
|
m_comment.ClearBuffer();
|
|
bool ret;
|
CZipString oldComment= m_comment.GetString();
|
m_comment.SetString(lpszComment);
|
ret = m_pCentralDir->OnFileCentralChange();
|
if (!ret)
|
m_comment.SetString(oldComment);
|
|
return ret;
|
}
|
else
|
{
|
m_comment.ClearBuffer();
|
m_comment.SetString(lpszComment);
|
return true;
|
}
|
}
|
|
const CZipString& CZipFileHeader::GetComment(bool bClearBuffer)
|
{
|
if (m_comment.HasString())
|
{
|
return m_comment.GetString();
|
}
|
m_comment.AllocateString();
|
ConvertComment(m_comment.GetString());
|
if (bClearBuffer)
|
m_comment.ClearBuffer();
|
return m_comment.GetString();
|
}
|
|
int CZipFileHeader::GetCompressionLevel() const
|
{
|
if (m_uMethod == CZipCompressor::methodStore)
|
return CZipCompressor::levelStore;
|
else if ((m_uFlag & (WORD) 6) != 0)
|
return 1;
|
else if ((m_uFlag & (WORD) 4) != 0)
|
return 2;
|
else if ((m_uFlag & (WORD) 2) != 0)
|
return CZipCompressor::levelBest;
|
else
|
return CZipCompressor::levelDefault;
|
}
|
|
bool CZipFileHeader::SetFileName(LPCTSTR lpszFileName)
|
{
|
CZipString newFileName(lpszFileName);
|
if (!IsDirectory() || newFileName.GetLength() != 1 || !CZipPathComponent::IsSeparator(newFileName[0]))
|
// do not remove from directories where only path separator is present
|
CZipPathComponent::RemoveSeparatorsLeft(newFileName);
|
if (m_pCentralDir)
|
{
|
// update the lpszFileName to make sure the renaming is necessary
|
GetFileName();
|
|
if (!UpdateFileNameFlags(&newFileName, true))
|
{
|
if (IsDirectory())
|
CZipPathComponent::AppendSeparator(newFileName);
|
else
|
CZipPathComponent::RemoveSeparators(newFileName);
|
|
if (m_fileName.GetString().Collate(newFileName) == 0)
|
return true;
|
}
|
// just in case
|
m_fileName.ClearBuffer();
|
|
bool ret;
|
CZipString oldFileName = m_fileName.GetString();
|
m_fileName.SetString(lpszFileName);
|
ret = m_pCentralDir->OnFileNameChange(this);
|
if (ret)
|
{
|
SetModified();
|
}
|
else
|
m_fileName.SetString(oldFileName);
|
|
return ret;
|
}
|
else
|
{
|
m_fileName.ClearBuffer();
|
m_fileName.SetString(newFileName);
|
return true;
|
}
|
}
|
|
const CZipString& CZipFileHeader::GetFileName(bool bClearBuffer)
|
{
|
if (m_fileName.HasString())
|
{
|
return m_fileName.GetString();
|
}
|
m_fileName.AllocateString();
|
ConvertFileName(m_fileName.GetString());
|
// don't keep it in memory
|
if (bClearBuffer)
|
{
|
m_fileName.ClearBuffer();
|
}
|
return m_fileName.GetString();
|
}
|
|
bool CZipFileHeader::IsDirectory()
|
{
|
return ZipPlatform::IsDirectory(GetSystemAttr());
|
}
|
|
DWORD CZipFileHeader::GetSystemAttr()
|
{
|
if (ZipCompatibility::IsPlatformSupported(GetSystemCompatibility()))
|
{
|
DWORD uAttr = GetSystemCompatibility() == ZipCompatibility::zcUnix ? (m_uExternalAttr >> 16) : (m_uExternalAttr & 0xFFFF);
|
DWORD uConvertedAttr = ZipCompatibility::ConvertToSystem(uAttr, GetSystemCompatibility(), ZipPlatform::GetSystemID());
|
if (m_uComprSize == 0 && !ZipPlatform::IsDirectory(uConvertedAttr) && CZipPathComponent::HasEndingSeparator(GetFileName()))
|
// can happen, a folder can have attributes set and no dir attribute (Python modules)
|
// TODO: [postponed] fix and cache after reading from central dir, but avoid calling GetFileName() there to keep lazy name conversion
|
return ZipPlatform::GetDefaultDirAttributes() | uConvertedAttr;
|
else
|
{
|
#ifdef _ZIP_SYSTEM_LINUX
|
// converting from Windows attributes may create a not readable linux directory
|
if (GetSystemCompatibility() != ZipCompatibility::zcUnix && ZipPlatform::IsDirectory(uConvertedAttr))
|
return ZipPlatform::GetDefaultDirAttributes();
|
#endif
|
return uConvertedAttr;
|
}
|
}
|
else
|
return CZipPathComponent::HasEndingSeparator(GetFileName()) ? ZipPlatform::GetDefaultDirAttributes() : ZipPlatform::GetDefaultAttributes();
|
}
|
|
bool CZipFileHeader::SetSystemAttr(DWORD uAttr)
|
{
|
// The high-word should no be set in attributes,
|
// It will be overwritten by Unix attributes, which are stored in high-word.
|
ASSERT((uAttr & 0xFFFF0000) == 0);
|
// make it readable under Unix as well, since it stores its attributes in HIWORD(uAttr)
|
DWORD uNewAtrr = ZipCompatibility::ConvertToSystem(uAttr, ZipPlatform::GetSystemID(), GetSystemCompatibility());
|
if (GetSystemCompatibility() == ZipCompatibility::zcUnix)
|
{
|
uNewAtrr <<= 16;
|
if (ZipPlatform::IsDirectory(uAttr))
|
uNewAtrr |= 0x10; // make it recognizable under other systems (all use 0x10 for directory)
|
}
|
else
|
// make it readable under linux
|
uNewAtrr |= (ZipCompatibility::ConvertToSystem(uAttr, ZipPlatform::GetSystemID(), ZipCompatibility::zcUnix) << 16);
|
|
if (uNewAtrr != m_uExternalAttr)
|
{
|
if (m_pCentralDir)
|
{
|
if (!m_pCentralDir->OnFileCentralChange())
|
{
|
return false;
|
}
|
}
|
m_uExternalAttr = uNewAtrr;
|
}
|
return true;
|
}
|
|
CZipFileHeader& CZipFileHeader::operator=(const CZipFileHeader& header)
|
{
|
m_uVersionMadeBy = header.m_uVersionMadeBy;
|
m_uVersionNeeded = header.m_uVersionNeeded;
|
m_iSystemCompatibility = header.m_iSystemCompatibility;
|
m_uFlag = header.m_uFlag;
|
m_uMethod = header.m_uMethod;
|
m_uModTime = header.m_uModTime;
|
m_uModDate = header.m_uModDate;
|
m_uCrc32 = header.m_uCrc32;
|
m_uComprSize = header.m_uComprSize;
|
m_uUncomprSize = header.m_uUncomprSize;
|
m_uVolumeStart = header.m_uVolumeStart;
|
m_uInternalAttr = header.m_uInternalAttr;
|
m_uLocalComprSize = header.m_uLocalComprSize;
|
m_uLocalHeaderSize = header.m_uLocalHeaderSize;
|
m_uLocalUncomprSize = header.m_uLocalUncomprSize;
|
m_uExternalAttr = header.m_uExternalAttr;
|
m_uLocalFileNameSize = header.m_uLocalFileNameSize;;
|
m_uOffset = header.m_uOffset;
|
m_aLocalExtraData = header.m_aLocalExtraData;
|
m_aCentralExtraData = header.m_aCentralExtraData;
|
m_uEncryptionMethod = header.m_uEncryptionMethod;
|
m_fileName = header.m_fileName;
|
m_comment = header.m_comment;
|
m_state = header.m_state;
|
|
#ifdef _ZIP_UNICODE_CUSTOM
|
m_stringSettings = header.m_stringSettings;
|
#endif
|
m_pCentralDir = header.m_pCentralDir;
|
|
return *this;
|
}
|
|
void CZipFileHeader::WriteSmallDataDescriptor(char* pDest, bool bLocal)
|
{
|
WriteCrc32(pDest);
|
if (bLocal)
|
{
|
CBytesWriter::WriteBytes(pDest + 4, m_uLocalComprSize, 4);
|
CBytesWriter::WriteBytes(pDest + 8, m_uLocalUncomprSize, 4);
|
}
|
else
|
{
|
CBytesWriter::WriteBytes(pDest + 4, m_uComprSize, 4);
|
CBytesWriter::WriteBytes(pDest + 8, m_uUncomprSize, 4);
|
}
|
}
|
|
|
void CZipFileHeader::WriteDataDescriptor(CZipStorage* pStorage)
|
{
|
if (!IsDataDescriptor())
|
return;
|
bool signature = NeedsSignatureInDataDescriptor(pStorage);
|
CZipAutoBuffer buf;
|
buf.Allocate(GetDataDescriptorSize(signature));
|
char* pBuf = NULL ;
|
if (signature)
|
{
|
memcpy(buf, CZipStorage::m_gszExtHeaderSignat, 4);
|
pBuf = (char*)buf + 4;
|
}
|
else
|
pBuf = buf;
|
WriteCrc32(pBuf);
|
CBytesWriter::WriteBytes(pBuf + 4, m_uComprSize, 4);
|
CBytesWriter::WriteBytes(pBuf + 8, m_uUncomprSize, 4);
|
pStorage->Write(buf, buf.GetSize(), true);
|
}
|
|
void CZipFileHeader::UpdateLocalHeader(CZipStorage* pStorage)
|
{
|
if (pStorage->IsSegmented() || IsDataDescriptor())
|
// there is nothing to fix
|
return;
|
pStorage->Flush();
|
ZIP_FILE_USIZE uPos = pStorage->m_pFile->GetPosition();
|
// update crc and sizes, the sizes may already be all right,
|
// but 8 more bytes won't make a difference, we need to update crc32 anyway
|
CZipAutoBuffer buf(12);
|
m_uLocalComprSize = CBytesWriter::WriteSafeU32(m_uComprSize);
|
m_uLocalUncomprSize = CBytesWriter::WriteSafeU32(m_uUncomprSize);
|
WriteSmallDataDescriptor(buf);
|
pStorage->Seek(m_uOffset + 14);
|
pStorage->m_pFile->Write(buf, 12);
|
|
pStorage->m_pFile->SafeSeek(uPos);
|
}
|
|
void CZipFileHeader::WriteCrc32(char* pBuf) const
|
{
|
DWORD uCrc = m_bIgnoreCrc32 ? 0 : m_uCrc32;
|
CBytesWriter::WriteBytes(pBuf, uCrc);
|
}
|
|
|
void CZipFileHeader::ClearFileName()
|
{
|
#ifdef _ZIP_UNICODE_CUSTOM
|
if (m_state.IsSetAny(sfCustomUnicode) && m_stringSettings.m_bStoreNameInExtraData)
|
// we are keeping m_pszFileName, clear the buffer, we need the original, when writing extra header in central directory (can happen many times - better performance)
|
// and when accessing the filename
|
m_fileName.ClearBuffer();
|
else
|
#endif
|
m_fileName.ClearString();
|
}
|
|
bool CZipFileHeader::UpdateFileNameFlags(const CZipString* szNewFileName, bool bAllowRemoveCDir)
|
{
|
#if defined _ZIP_UNICODE || defined _ZIP_UNICODE_CUSTOM
|
CBitFlag iMode = m_pCentralDir->GetUnicodeMode();
|
#endif
|
// move the buffer to the name, to ensure it is converted properly now
|
GetComment();
|
bool centralDirChanged = false;
|
bool changed = false;
|
#ifdef _ZIP_UNICODE_CUSTOM
|
bool isCustom = iMode == CZipArchive::umCustom;
|
const CZipStringStoreSettings& stringStoreSettings = m_pCentralDir->GetStringStoreSettings();
|
if (m_state.ChangeWithCheck(sfCustomUnicode, isCustom))
|
{
|
// the mode changed
|
if (isCustom)
|
{
|
// changed to custom
|
m_stringSettings.m_bStoreNameInExtraData = stringStoreSettings.m_bStoreNameInExtraData;
|
m_stringSettings.m_uNameCodePage = stringStoreSettings.m_uNameCodePage;
|
if (!m_stringSettings.m_bStoreNameInExtraData && m_stringSettings.m_uNameCodePage != GetDefaultFileNameCodePage())
|
// the local filename needs to be rewritten, the name code page has changed
|
changed = true;
|
}
|
else
|
{
|
// changed from custom
|
if (!m_stringSettings.m_bStoreNameInExtraData && m_stringSettings.m_uNameCodePage != GetDefaultFileNameCodePage())
|
changed = true;
|
|
m_stringSettings.m_bStoreNameInExtraData = stringStoreSettings.m_bStoreNameInExtraData;
|
m_stringSettings.m_uNameCodePage = stringStoreSettings.m_uNameCodePage;
|
}
|
centralDirChanged = true;
|
}
|
else if (isCustom)
|
{
|
if (stringStoreSettings.m_bStoreNameInExtraData != m_stringSettings.m_bStoreNameInExtraData)
|
{
|
if (m_stringSettings.m_bStoreNameInExtraData)
|
{
|
changed |= (stringStoreSettings.m_uNameCodePage == GetDefaultFileNameCodePage());
|
}
|
else
|
{
|
changed |= (m_stringSettings.m_uNameCodePage == GetDefaultFileNameCodePage());
|
}
|
}
|
else if (!m_stringSettings.m_bStoreNameInExtraData)
|
changed |= (stringStoreSettings.m_uNameCodePage != m_stringSettings.m_uNameCodePage);
|
|
m_stringSettings.m_bStoreNameInExtraData = stringStoreSettings.m_bStoreNameInExtraData;
|
m_stringSettings.m_uNameCodePage = stringStoreSettings.m_uNameCodePage;
|
}
|
#endif
|
if (changed || centralDirChanged)
|
{
|
m_comment.ClearBuffer();
|
}
|
// remove the central directory in case the filename did not changed, but the comment flags changed
|
if (!changed && centralDirChanged && bAllowRemoveCDir && m_pCentralDir && m_comment.HasString())
|
m_pCentralDir->OnFileCentralChange();
|
return changed;
|
}
|
|
bool CZipFileHeader::UpdateCommentFlags(const CZipString* szNewComment)
|
{
|
#if defined _ZIP_UNICODE || defined _ZIP_UNICODE_CUSTOM
|
CBitFlag iMode = m_pCentralDir->GetUnicodeMode();
|
#endif
|
bool changed = false;
|
#ifdef _ZIP_UNICODE_CUSTOM
|
bool isCustom = iMode == CZipArchive::umCustom;
|
changed |= m_state.ChangeWithCheck(sfCustomUnicode, isCustom);
|
if (isCustom)
|
{
|
const CZipStringStoreSettings& stringStoreSettings = m_pCentralDir->GetStringStoreSettings();
|
changed |= (m_stringSettings.m_uCommentCodePage != stringStoreSettings.m_uCommentCodePage);
|
m_stringSettings.m_uCommentCodePage = stringStoreSettings.m_uCommentCodePage;
|
}
|
#endif
|
return changed;
|
}
|