////////////////////////////////////////////////////////////////////////////////
// This source file is part of the ZipArchive library source distribution and
// is Copyrighted 2000 - 2014 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
#define ZIP_EXTRA_NTFS_TAG 0x0001

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_tModificationTime = m_tCreationTime = m_tLastAccessTime = (time_t)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;

	CZipExtraData* pExtra = m_aCentralExtraData.Lookup(ZIP_EXTRA_NTFS);
	if (pExtra != NULL && pExtra->m_data.GetSize() >= 32)
	{
		WORD temp;
		CBytesWriter::ReadBytes(temp, pExtra->m_data + 4);			
		if (temp == ZIP_EXTRA_NTFS_TAG)
		{
			CBytesWriter::ReadBytes(temp, pExtra->m_data + 6);			
			if (temp == 24)
			{
				m_tModificationTime = ReadFileTime(pExtra->m_data + 8);
				m_tCreationTime = ReadFileTime(pExtra->m_data + 16);
				m_tLastAccessTime = ReadFileTime(pExtra->m_data + 24);
			}
		}
	}


#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) != 0)
		{
			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::GetModificationTime()const
{
	if (m_tModificationTime != (time_t) 0)
	{
		return m_tModificationTime;
	}
	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);
}

void CZipFileHeader::SetModificationTime(const time_t& ttime, bool bFullResolution)
{
	if (bFullResolution)
	{
		m_tModificationTime = ttime;
	}	
	else
	{
		m_tModificationTime = m_tCreationTime = m_tLastAccessTime = (time_t)0;
	}
#if _MSC_VER >= 1400
	tm gts;
	tm* gt = &gts;
	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::WriteFileTime(const time_t& ttime, char* buffer)
{
	FILETIME ft;
	ZipPlatform::ConvertTimeToFileTime(ttime, ft);
	CBytesWriter::WriteBytes(buffer, ft.dwLowDateTime);
	CBytesWriter::WriteBytes(buffer + 4, ft.dwHighDateTime);	
}

time_t CZipFileHeader::ReadFileTime(const char* buffer)
{
	FILETIME ft = {0};
	CBytesWriter::ReadBytes(ft.dwLowDateTime, buffer);
	CBytesWriter::ReadBytes(ft.dwHighDateTime, buffer + 4);	
	time_t ret;
	return ZipPlatform::ConvertFileTimeToTime(ft, ret) ? ret : (time_t)0;
}


// write the header to the central dir
DWORD CZipFileHeader::Write(CZipStorage *pStorage)
{
	m_aCentralExtraData.RemoveInternalHeaders();
	if (m_tModificationTime != (time_t)0 || m_tCreationTime != (time_t)0 || m_tLastAccessTime != (time_t)0)
	{
		CZipExtraData* pExtra = m_aCentralExtraData.CreateNew(ZIP_EXTRA_NTFS);
		CZipAutoBuffer& buf = pExtra->m_data;
		buf.Allocate(32, true);
		// first 4 bytes are reserved
		WORD temp = ZIP_EXTRA_NTFS_TAG; // tag for the attribute
		CBytesWriter::WriteBytes((char*)(buf + 4), temp);
		temp = 3 * 8; // the size of the attribute
		CBytesWriter::WriteBytes((char*)(buf + 6), temp);

		WriteFileTime(m_tModificationTime, (char*)(buf + 8));
		WriteFileTime(m_tCreationTime, (char*)(buf + 16));
		WriteFileTime(m_tLastAccessTime, (char*)(buf + 24));
	}
	
	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;
			}
			if (data != NULL) // just in case
			{
				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::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;

	// 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, bool bInCentralOnly)
{
	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, bInCentralOnly);
		if (!bInCentralOnly)
		{
			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());
		// some zip files seem to report size greater than 0 for folders
		if ((m_uComprSize == 0 || m_uExternalAttr == 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_tModificationTime = header.m_tModificationTime;
	m_tCreationTime = header.m_tCreationTime;
	m_tLastAccessTime = header.m_tLastAccessTime;
	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;
	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.IsSetAny(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.IsSetAny(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;
}
