// Copyright (c) 2003-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//

#include <ezgzip.h>
#include "libzcore.h"

const TUint8 EZGZipFile::ID1 = 31;
const TUint8 EZGZipFile::ID2 = 139;

/**
Constructor
*/
EXPORT_C TEZGZipHeader::TEZGZipHeader() : iId1(EZGZipFile::ID1), iId2(EZGZipFile::ID2), iCompressionMethod(8), iFlags(0), iTime(0), 
	iExtraFlags(0), iOs(255), iXlen(0), iExtra(NULL), iFname(NULL), iComment(NULL), iCrc(0)
	{
	
	}

/**
Destructor
*/
EXPORT_C TEZGZipHeader::~TEZGZipHeader()
	{
	delete iExtra;
	delete iFname;
	delete iComment;
	}

/**
Constructor
*/
EXPORT_C TEZGZipTrailer::TEZGZipTrailer() : iCrc32(0), iSize(0)
	{

	}

/**
Constructor

@param aCrc the CRC to use for archive checking
@param aSize the size of the trailer
*/
EXPORT_C TEZGZipTrailer::TEZGZipTrailer(TInt32 aCrc, TInt32 aSize) : iCrc32(aCrc), iSize(aSize)
	{

	}

//--------------------------------------------------------------------------------------------------------

/**
Read the zip header from the specified zip file into the TEZGZipHeader object

@param aFile the zip file to read from
@param aHeader the target header object
@leave KEZlibErrBadGZipHeader invalid zip header
@leave ... Any of the system wide error codes
*/
EXPORT_C void EZGZipFile::ReadHeaderL(RFile &aFile, TEZGZipHeader &aHeader)
	{
	TInt obligatoryData = sizeof(aHeader.iId1) + sizeof(aHeader.iId2) + sizeof(aHeader.iCompressionMethod) +
		sizeof(aHeader.iFlags) + sizeof(aHeader.iTime) + sizeof(aHeader.iExtraFlags) + sizeof(aHeader.iOs);

	TPtr8 des(&aHeader.iId1,0,obligatoryData);
	TInt err = aFile.Read(des);
	if (err != KErrNone || (des.Size() != obligatoryData))
		User::Leave(KEZlibErrBadGZipHeader);

	if (aHeader.iId1 != ID1 || aHeader.iId2 != ID2)
		User::Leave(KEZlibErrBadGZipHeader);
	
	if (aHeader.iFlags & (1 << EFExtra)) // then the extra bit is set
		{
		des.Set(REINTERPRET_CAST(TUint8 *,&aHeader.iXlen),0,sizeof(aHeader.iXlen));
		err = aFile.Read(des);
		if (err != KErrNone || des.Size() != sizeof(aHeader.iXlen) || aHeader.iXlen < 0)
			User::Leave(KEZlibErrBadGZipHeader);
		
		aHeader.iExtra = HBufC8::NewMaxL(aHeader.iXlen);
		TPtr8 des = aHeader.iExtra->Des();
		err = aFile.Read(des);
		if (err != KErrNone || des.Size() != aHeader.iXlen)
			User::Leave(KEZlibErrBadGZipHeader);
		}
	
	if (aHeader.iFlags & (1 << EFName)) // then read in filename
		ReadStringIntoDescriptorL(aFile,&aHeader.iFname);

	if (aHeader.iFlags & (1 << EFComment)) // then read in comment
		ReadStringIntoDescriptorL(aFile,&aHeader.iComment);
			
	if (aHeader.iFlags & (1 << EFHcrc))
		{
		des.Set(REINTERPRET_CAST(TUint8*,&aHeader.iCrc),0,sizeof(aHeader.iCrc));
		err = aFile.Read(des);
		if (err != KErrNone || des.Size() != sizeof(aHeader.iCrc))
			User::Leave(KEZlibErrBadGZipHeader);
		}
	}

/**
Write the zip header to the specified file

@param aFile the file to write to
@param aHeader the header object to write to the file
@leave ... Any of the system wide error codes
*/
EXPORT_C void EZGZipFile::WriteHeaderL(RFile &aFile, TEZGZipHeader &aHeader)
	{
	TInt obligatoryData = sizeof(aHeader.iId1) + sizeof(aHeader.iId2) + sizeof(aHeader.iCompressionMethod) +
		sizeof(aHeader.iFlags) + sizeof(aHeader.iTime) + sizeof(aHeader.iExtraFlags) + sizeof(aHeader.iOs);

	TPtrC8 des(&aHeader.iId1,obligatoryData);
	User::LeaveIfError(aFile.Write(des));
	TBuf8<1> null(1);
	null[0] = '\0';

	if (aHeader.iFlags & (1 << EFExtra)) // then the extra bit is set
		{
		des.Set(REINTERPRET_CAST(TUint8 *,&aHeader.iXlen),sizeof(aHeader.iXlen));
		User::LeaveIfError(aFile.Write(des));
		
		User::LeaveIfError(aFile.Write(*aHeader.iExtra));
		}
						
	if (aHeader.iFlags & (1 << EFName)) // then read in filename
		{
		User::LeaveIfError(aFile.Write(*aHeader.iFname));
		User::LeaveIfError(aFile.Write(null));
		}

	if (aHeader.iFlags & (1 << EFComment)) // then read in comment
		{
		User::LeaveIfError(aFile.Write(*aHeader.iComment));
		User::LeaveIfError(aFile.Write(null));
		}
	
	if (aHeader.iFlags & (1 << EFHcrc))
		{
		des.Set(REINTERPRET_CAST(TUint8*,&aHeader.iCrc),sizeof(aHeader.iCrc));
		User::LeaveIfError(aFile.Write(des));
		}
	}

void EZGZipFile::ReadStringIntoDescriptorL(RFile &aFile, HBufC8 **aDes)
	{
	TInt i, err;
	CArrayFixFlat<TUint8> *bytes = new (ELeave) CArrayFixFlat<TUint8>(16);
	CleanupStack::PushL(bytes);
	TBuf8<1> ch;
	while ((err = aFile.Read(ch)) == KErrNone)
		{
		if (ch.Length() != 1)
			User::Leave(KEZlibErrBadGZipHeader);
		else if (ch[0] != '\0')
			bytes->AppendL(*ch.Ptr());
		else
			break;
		}
	
	if (err != KErrNone)
		User::Leave(KEZlibErrBadGZipHeader);
	
	*aDes = HBufC8::NewMaxL(bytes->Count());
	
	for (i = 0; i < bytes->Count(); i++)
		(*aDes)->Des()[i] = (*bytes)[i];
	
	CleanupStack::PopAndDestroy(); // delete bytes
	}

/**
Read the zip trailer from the specified zip file into the TEZGZipTrailer object

@param aFile the zip file to read from
@param aTrailer the target trailer object
@leave KEZlibErrBadGZipTrailer invalid zip trailer
@leave ... Any of the system wide error codes
*/
EXPORT_C void EZGZipFile::ReadTrailerL(RFile &aFile, TEZGZipTrailer &aTrailer)
	{
	TPtr8 des(REINTERPRET_CAST(TUint8*,&aTrailer.iCrc32),0,sizeof(TEZGZipTrailer));

	TInt err = aFile.Read(des);
	if (err != KErrNone || des.Size() != sizeof(TEZGZipTrailer))
		User::Leave(KEZlibErrBadGZipTrailer);
	}

/**
Write the zip trailer to the specified file

@param aFile the file to write to
@param aTrailer the trailer object to write to the file
@leave ... Any of the system wide error codes
*/
EXPORT_C void EZGZipFile::WriteTrailerL(RFile &aFile, TEZGZipTrailer &aTrailer)
	{
	TPtrC8 des(REINTERPRET_CAST(TUint8*,&aTrailer.iCrc32),sizeof(TEZGZipTrailer));
	User::LeaveIfError(aFile.Write(des));
	}

/**
Find the zip trailer within the specified file, and read it into the TEZGZipTrailer object

@param aRfs file server session
@param aFname the file to read from
@param aTrailer the target trailer object
@leave KEZlibErrBadGZipHeader Invalid zip header
@leave ... Any of the system wide error codes
*/
EXPORT_C void EZGZipFile::LocateAndReadTrailerL(RFs &aRfs, const TDesC &aFname, TEZGZipTrailer &aTrailer)
	{
	TInt fileSize;

	RFile file;

	User::LeaveIfError(file.Open(aRfs,aFname,EFileStream | EFileRead | EFileShareAny));
	CleanupClosePushL(file);

	TUint8 magic[2];
	TPtr8 des(magic,0,sizeof(TUint8) * 2);
	User::LeaveIfError(file.Read(des));
	if (magic[0] != ID1 || magic[1] != ID2)
		User::Leave(KEZlibErrBadGZipHeader);

	User::LeaveIfError(file.Size(fileSize));
	TInt sizePos = fileSize - (sizeof (TInt32) * 2);
	User::LeaveIfError(file.Seek(ESeekStart,sizePos));

	des.Set(REINTERPRET_CAST(TUint8 *,&aTrailer),0,sizeof(TInt32)*2);
	
	User::LeaveIfError(file.Read(des));

	CleanupStack::PopAndDestroy();
	}

/**
@deprecated Interface is deprecated because it is unsafe as it may leave. It is available for backward compatibility reasons only.
@see EZGZipFile::IsGzipFileL
*/
EXPORT_C TBool EZGZipFile::IsGzipFile(RFs &aRfs, const TDesC &aFname)
	{
	TBool retBool = true;
	TRAPD(errCode, retBool = IsGzipFileL(aRfs, aFname));
	if(errCode != KErrNone)
		{
		retBool = false;
		}
	return retBool;
	}

/**
Determine if the given file is a valid zip file

@param aRfs file server session
@param aFname name of the file to check
@leave ... Any of the system wide error codes
@return ETrue if the file is valid zip file, EFalse otherwise
*/
EXPORT_C TBool EZGZipFile::IsGzipFileL(RFs &aRfs, const TDesC &aFname)
	{
	TUint8 ids[2];
	RFile file;

	User::LeaveIfError(file.Open(aRfs,aFname,EFileStream | EFileRead | EFileShareAny));
	CleanupClosePushL(file);

	TPtr8 des(ids,0,sizeof(TUint8) * 2);
	
	User::LeaveIfError(file.Read(des));
	CleanupStack::PopAndDestroy();
	return (ids[0] == ID1 && ids[1] == ID2);
	}

//--------------------------------------------------------------------------------------------------------


CEZFileToGzipBM::CEZFileToGzipBM(RFile &aInput, RFile &aOutput) : CEZFileBufferManager(aInput,aOutput)
	{
	iCrc = crc32_r(iCrc,NULL,0);
	}


CEZFileToGzipBM* CEZFileToGzipBM::NewLC(RFile &aInput, RFile &aOutput, TInt aBufferSize)
	{
	CEZFileToGzipBM *bm = new (ELeave) CEZFileToGzipBM(aInput,aOutput);
	CleanupStack::PushL(bm);
	bm->ConstructL(aBufferSize);
	return bm;
	}

CEZFileToGzipBM* CEZFileToGzipBM::NewL(RFile &aInput, RFile &aOutput, TInt aBufferSize)
	{
	CEZFileToGzipBM *bm = new (ELeave) CEZFileToGzipBM(aInput,aOutput);
	CleanupStack::PushL(bm);
	bm->ConstructL(aBufferSize);
	CleanupStack::Pop();
	return bm;
	}

void CEZFileToGzipBM::NeedInputL(CEZZStream &aZStream)
	{
	CEZFileBufferManager::NeedInputL(aZStream);
	iCrc = crc32_r(iCrc,iInputDescriptor.Ptr(),iInputDescriptor.Size());
	}

void CEZFileToGzipBM::InitializeL(CEZZStream &aZStream)
	{
	CEZFileBufferManager::InitializeL(aZStream);
	iCrc = crc32_r(iCrc,iInputDescriptor.Ptr(),iInputDescriptor.Size());
	}

//--------------------------------------------------------------------------------------------------------


CEZGzipToFileBM::CEZGzipToFileBM(RFile &aInput, RFile &aOutput) : CEZFileBufferManager(aInput,aOutput)
	{
	iCrc = crc32_r(iCrc,NULL,0);
	}

CEZGzipToFileBM* CEZGzipToFileBM::NewLC(RFile &aInput, RFile &aOutput, TInt aBufferSize)
	{
	CEZGzipToFileBM *bm = new (ELeave) CEZGzipToFileBM(aInput,aOutput);
	CleanupStack::PushL(bm);
	bm->ConstructL(aBufferSize);
	return bm;
	}

CEZGzipToFileBM* CEZGzipToFileBM::NewL(RFile &aInput, RFile &aOutput, TInt aBufferSize)
	{
	CEZGzipToFileBM *bm = new (ELeave) CEZGzipToFileBM(aInput,aOutput);
	CleanupStack::PushL(bm);
	bm->ConstructL(aBufferSize);
	CleanupStack::Pop();
	return bm;
	}

void CEZGzipToFileBM::NeedOutputL(CEZZStream &aZStream)
	{
	TPtrC8 od = aZStream.OutputDescriptor();
	iCrc = crc32_r(iCrc,od.Ptr(),od.Size());
	CEZFileBufferManager::NeedOutputL(aZStream);
	}

void CEZGzipToFileBM::FinalizeL(CEZZStream &aZStream)
	{
	TPtrC8 od = aZStream.OutputDescriptor();
	iCrc = crc32_r(iCrc,od.Ptr(),od.Size());
	CEZFileBufferManager::FinalizeL(aZStream);
	}


//--------------------------------------------------------------------------------------------------------
CEZGZipToFile::CEZGZipToFile() : iDecompressor(NULL), iBufferManager(NULL)
	{

	}

CEZGZipToFile::~CEZGZipToFile()
	{
	delete iDecompressor;
	delete iBufferManager;
	iGZipFile.Close();
	}

/**
Creates a new CEZGZipToFile object and leaves it on the CleanupStack

@param aRfs open file server session
@param aGzFileName name of the file to be de-compressed
@param aOutput the target file to hold the un-compressed data
@param aBufferSize required size of buffers
@return a pointer to the new CEZGZipToFile object, left on the CleanupStack
*/
EXPORT_C CEZGZipToFile* CEZGZipToFile::NewLC(RFs &aRfs, const TDesC &aGzFileName, RFile &aOutput, TInt aBufferSize)
	{
	CEZGZipToFile* dec = new (ELeave) CEZGZipToFile;
	CleanupStack::PushL(dec);
	dec->ConstructL(aRfs,aGzFileName,aOutput,aBufferSize);
	return dec;
	}

/**
Creates a new CEZGZipToFile object

@param aRfs open file server session
@param aGzFileName name of the file to be de-compressed
@param aOutput the target file to hold the un-compressed data
@param aBufferSize required size of buffers
@return a pointer to the new CEZGZipToFile object
*/
EXPORT_C CEZGZipToFile* CEZGZipToFile::NewL(RFs &aRfs, const TDesC &aGzFileName, RFile &aOutput, TInt aBufferSize)
	{
	CEZGZipToFile* dec = new (ELeave) CEZGZipToFile;
	CleanupStack::PushL(dec);
	dec->ConstructL(aRfs,aGzFileName,aOutput,aBufferSize);
	CleanupStack::Pop();
	return dec;
	}

/**
Quits the current de-compression operation and restarts with the specified arguments

@param aRfs open file server session
@param aGzFileName name of the file to be de-compressed
@param aOutput the target file to hold the un-compressed data
@param aBufferSize required size of buffers
@leave ... Any of the system wide error codes
*/
EXPORT_C void CEZGZipToFile::ResetL(RFs &aRfs, const TDesC &aGzFileName, RFile &aOutput, TInt aBufferSize)
	{
	delete iBufferManager;
	iBufferManager = NULL;
	InitialiseBufManL(aRfs,aGzFileName,aOutput,aBufferSize);
	iDecompressor->ResetL(*iBufferManager);
	}

/**
De-compresses the current zip file in stages.  The function needs to called again until the de-compression 
is finalised, in which case it will return EFalse - for example...

@code
while ( decompressor->InflateL() )
	{
	// No action required
	}
@endcode

@leave KEZlibErrBadGZipCrc Invalid CRC check
@leave ... Any of the system wide error codes
@return ETrue if the de-compression is not complete, and function must be called again
@return EFalse if the de-compression is finalised
*/
EXPORT_C TBool CEZGZipToFile::InflateL()
	{
	TBool keepGoing = iDecompressor->InflateL();

	if (!keepGoing)
		{
		if (iBufferManager->Crc() != iTrailer.iCrc32)
			{
			User::Leave(KEZlibErrBadGZipCrc);
				
			}
		iGZipFile.Close();
		}

	return keepGoing;
	}

void CEZGZipToFile::InitialiseBufManL(RFs &aRfs, const TDesC &aGzFileName, RFile &aOutput, TInt aBufferSize)
	{
	EZGZipFile::LocateAndReadTrailerL(aRfs,aGzFileName,iTrailer);
	User::LeaveIfError(iGZipFile.Open(aRfs,aGzFileName,EFileStream | EFileRead | EFileShareAny));
	EZGZipFile::ReadHeaderL(iGZipFile,iHeader);
	iBufferManager = CEZGzipToFileBM::NewL(iGZipFile,aOutput,aBufferSize);
	}

void CEZGZipToFile::ConstructL(RFs &aRfs, const TDesC &aGzFileName, RFile &aOutput, TInt aBufferSize)
	{
	InitialiseBufManL(aRfs,aGzFileName,aOutput,aBufferSize);

	// this is a special zlib modification to stop it choking when it can't find the normal zlib stream header.

	iDecompressor = CEZDecompressor::NewL(*iBufferManager,-CEZDecompressor::EMaxWBits);
	}


//--------------------------------------------------------------------------------------------------------


CEZFileToGZip::CEZFileToGZip() : iCompressor(NULL), iBufferManager(NULL)
	{

	}

CEZFileToGZip::~CEZFileToGZip()
	{
	delete iCompressor;
	delete iBufferManager;
	iGZipFile.Close();
	}

/**
Creates a new CEZFileToGZip object and leaves it on the CleanupStack

@param aRfs open file server session
@param aGzFileName the name of the target zip file
@param aInput the file to compress
@param aBufferSize required size of buffers
@return a pointer to the new CEZFileToGZip object, left on the CleanupStack
*/
EXPORT_C CEZFileToGZip* CEZFileToGZip::NewLC(RFs &aRfs, const TDesC &aGzFileName, RFile &aInput, TInt aBufferSize)
	{
	CEZFileToGZip* com = new (ELeave) CEZFileToGZip;
	CleanupStack::PushL(com);
	com->ConstructL(aRfs,aGzFileName,aInput,aBufferSize);
	return com;
	}

/**
Creates a new CEZFileToGZip object and leaves it on the CleanupStack

@param aRfs open file server session
@param aGzFileName the name of the target zip file
@param aInput the file to compress
@param aBufferSize required size of buffers
@return a pointer to the new CEZFileToGZip object, left on the CleanupStack
*/
EXPORT_C CEZFileToGZip* CEZFileToGZip::NewL(RFs &aRfs, const TDesC &aGzFileName, RFile &aInput, TInt aBufferSize)
	{
	CEZFileToGZip* com = new (ELeave) CEZFileToGZip;
	CleanupStack::PushL(com);
	com->ConstructL(aRfs,aGzFileName,aInput,aBufferSize);
	CleanupStack::Pop();
	return com;
	}

/**
Quits the current compression operation and restarts with the specified arguments

@param aRfs open file server session
@param aGzFileName the name of the target zip file
@param aInput the file to compress
@param aBufferSize required size of buffers
@leave ... Any of the system wide error codes
*/
EXPORT_C void CEZFileToGZip::ResetL(RFs &aRfs, const TDesC &aGzFileName, RFile &aInput, TInt aBufferSize)
	{
	delete iBufferManager;
	iBufferManager = NULL;
	InitialiseBufManL(aRfs,aGzFileName,aInput,aBufferSize);
	iCompressor->ResetL(*iBufferManager);
	}

/**
Compresses the current file in stages.  The function needs to called again until the compression 
is finalised, in which case it will return EFalse - for example...

@code
while ( compressor->DeflateL() )
	{
	// No action required
	}
@endcode

@leave ... Any of the system wide error codes
@return ETrue if the compression is not complete, and function must be called again
@return EFalse if the compression is finalised
*/
EXPORT_C TBool CEZFileToGZip::DeflateL()
	{
	TBool keepgoing = iCompressor->DeflateL();
	if (!keepgoing)
		{
		TEZGZipTrailer trailer(iBufferManager->Crc(), iUncompressedDataSize);
		EZGZipFile::WriteTrailerL(iGZipFile, trailer);
		iGZipFile.Close();
		}

	return keepgoing;
	}

void CEZFileToGZip::InitialiseBufManL(RFs &aRfs, const TDesC &aGzFileName, RFile &aInput, TInt aBufferSize)
	{
	User::LeaveIfError(aInput.Size(iUncompressedDataSize));
	TInt err;

	err = iGZipFile.Create(aRfs, aGzFileName,EFileStream | EFileWrite | EFileShareExclusive);
	if (err == KErrAlreadyExists)
		User::LeaveIfError(iGZipFile.Open(aRfs,aGzFileName,EFileStream | EFileWrite | EFileShareExclusive));
	else 
		User::LeaveIfError(err);
	
	EZGZipFile::WriteHeaderL(iGZipFile,iHeader);
	iBufferManager = CEZFileToGzipBM::NewL(aInput,iGZipFile,aBufferSize);
	}

void CEZFileToGZip::ConstructL(RFs &aRfs, const TDesC &aGzFileName, RFile &aInput, TInt aBufferSize)
	{
	InitialiseBufManL(aRfs,aGzFileName,aInput,aBufferSize);

	// this is a special zlib modification to stop zlib writing out its normal zlib stream header.

	iCompressor = CEZCompressor::NewL(*iBufferManager,CEZCompressor::EDefaultCompression,-CEZCompressor::EMaxWBits);
	}
