// Copyright (c) 2006-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 "cstoreutilities.h"
#include <miuthdr.h>
#include <barsread.h>

// The maximum number of octets to be uuencoded on each line is 45
const TInt KUuDecodedLineLength = 45;	
// Initial size of buffer in which the "Real name <user@host>" strings
// are built before they're put into the CImHeader and the size by
// which to increment the buffer when necessary
const TInt KImapAddressSizeInc=256;
const TInt KKiloByteSize = 1024;
const TInt KBufferExtension = 4;
/**
Constructor.
*/
CStoreUtilities::CStoreUtilities(TImEncodingType aEncodingType,TUint aCharsetId,RFs& aFs)
	:iEncodingType(aEncodingType),iCharsetId(aCharsetId),iFs(aFs)
	{
	}
	
/**
Factory constructors.
Ownership aHeader is taken.
*/	
CStoreUtilities* CStoreUtilities::NewL(TImEncodingType aEncodingType,TUint aCharsetId,RFs& aFs)
	{
	CStoreUtilities* self=new(ELeave) CStoreUtilities(aEncodingType,aCharsetId,aFs);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop();
	return self;
	}
	
void CStoreUtilities::ConstructL()
	{
	// Create converter objects
	iCharacterConverter=CCnvCharacterSetConverter::NewL();
	iCharConv=CImConvertCharconv::NewL(*iCharacterConverter, iFs);	
	}

/**
Destructor.
*/	
CStoreUtilities::~CStoreUtilities()
	{
	delete iCharConv;
	delete iCharacterConverter;
	delete iFooterString;
	delete iPartialLine;
	}

/**
Checks if the descriptor contains the UUE begin header
Extracts the file name if it is
*/	
TBool CStoreUtilities::CheckUUEStartL(const TDesC8& aSourceLine)
	{
	
	TInt sourceLength = aSourceLine.Length();
	if(sourceLength < KImcvUueStart().Length()+3) // can't be "begin ###", it's not long enough; 3=length of ###
		return EFalse;

	if(!aSourceLine.Left(KImcvUueStart().Length()).CompareF(KImcvUueStart)) // start of line might be UUE boundary
		{
		// we also need to check that the next three chars are numbers - Unix file access code
		const TUint8* sourceLinePtr = aSourceLine.Ptr();
		TInt length=KImcvUueStart().Length();// this defines length as 6 ie. "b e g i n  " 
		if( TChar(sourceLinePtr[length]).IsDigit() && 
			TChar(sourceLinePtr[length+1]).IsDigit() && 
			TChar(sourceLinePtr[length+2]).IsDigit() )
			{
			// Found 'begin ###' at the start of a line - assume this is a UUencode header
			// The attachment name in this header is ignored. We use the value from the MIME header
			return ETrue;
			}
		}
		
	return EFalse;
	}
	
/**
Appends data to partial line object extening the destination buffer as appropirate 
@param aBufferPtr the destination bufer
@param aText the data to be appended
*/	
	
void CStoreUtilities::AppendExtendL(HBufC8** aBufferPtr, const TDesC8& aText)
	{
	HBufC8 *buffer = *aBufferPtr;
	TInt32 space = buffer->Des().MaxLength() - (buffer->Des().Length() + aText.Length());
	if (space < 0)
		{
		TInt32 inc = (-space) + KImapAddressSizeInc - ((-space) % KImapAddressSizeInc);
		TInt32 newSize = buffer->Des().MaxLength() + inc;
		HBufC8 *newBuf = buffer->ReAllocL(newSize);
		*aBufferPtr = buffer = newBuf;
		}
	buffer->Des().Append(aText);
	}
	
	
void CStoreUtilities::AttachFooterInfoL(TInt32 aBodyPartRemainingSize,CImapSettings& aImapSettings,HBufC8*& aDecodedData)
	{
	const HBufC8* buf= aImapSettings.DefaultPartialMessageFooter();

	_LIT(KIntegerDirective,"%d");
	const TInt KNoOfDigitsInMailSize = 10; // maximum length for no of digits for remaining body parts size
	TResourceReader reader;
	reader.SetBuffer(buf);
	// Check if %d is not found in the resource string (To avoid problems due to localisation)
	if(buf->Find((TDesC8&)KIntegerDirective) == KErrNotFound)
		{
		iFooterString = (reader.ReadTPtrC()).AllocL();
		}
	else
		{
		HBufC* resourceBuf = (reader.ReadTPtrC()).AllocL();
		CleanupStack::PushL(resourceBuf);
		iFooterString = HBufC::NewL(resourceBuf->Length()+KNoOfDigitsInMailSize);
		iFooterString->Des().Format(*resourceBuf,(aBodyPartRemainingSize / KKiloByteSize));
		CleanupStack::PopAndDestroy(resourceBuf);
		}	
	//caller will have left aDecodedData on the cleanupstack
	CleanupStack::Pop(aDecodedData); 
	TInt newSize = aDecodedData->Size() + iFooterString->Size();
	aDecodedData = aDecodedData->ReAlloc(newSize);
	CleanupStack::PushL(aDecodedData);
	aDecodedData->Des().Append(*iFooterString);
	delete iFooterString;
	iFooterString = NULL;
			
	}
	

// convert text from its charset and write to richtext store. aText
// can span multiple and partial lines
void CStoreUtilities::WriteToBodyL(const TDesC8& aText,CRichText* aMessageBody)
	{
	TInt pos = aMessageBody->DocumentLength();

	// Add bits of body text, converting along the way, till no characters left
	// .. to convert.

	// Convert text before writing to body.
	TInt rem = 0;

	// there will be a max of one output char per input byte
	HBufC16* text16=HBufC16::NewLC(aText.Length());
	TPtr16 ptr16=text16->Des();
	
	TBool preparedToConvert = iCharConv->PrepareToConvertToFromOurCharsetL(iCharsetId);
	
	if (!preparedToConvert)
		{
		ptr16.Copy(aText);
		aMessageBody->InsertL(pos, ptr16);
		}
	else
		{
		TInt unconvertedChars, firstPos; // not used 
		rem = iCharConv->ConvertToOurCharsetL(aText, ptr16, 
											  unconvertedChars, firstPos);
		if (rem < 0) // error
			{
			// Copy unconverted characters.
			ptr16.Copy(aText);
			aMessageBody->InsertL(pos, ptr16);
			}
		else if (rem && rem < iLeftOver.MaxLength())
			{
			iLeftOver.Copy(aText.Right(rem));	
			}

		// convert CRLF to ELineBreak
		TInt start = 0;
		TInt length = ptr16.Length();
		
		if (length>0)
			{
			TInt i;
			for (i=1; i<length; i++)
				{
				if (ptr16[i-1] == KImcvCR && ptr16[i] == KImcvLF)
					{
					ptr16[i-1] = CEditableText::ELineBreak;

					// write this line to body
					TPtrC ptr = ptr16.Mid(start, i-start);
					aMessageBody->InsertL(pos, ptr);
					pos += ptr.Length();
					start = i+1;
					}
				}

			if (start != i)
				{
				TPtrC ptr = ptr16.Mid(start, i-start);
				aMessageBody->InsertL(pos, ptr);
				}
			}
		}

	CleanupStack::PopAndDestroy(text16);
	}
	
// Decode and store received data
HBufC8* CStoreUtilities::DecodeL(const TPtrC8& aBodyData, const TBool aEndOfStream)
	{
	// Somewhere to store decoded data, at least as long as source (plus anything we have left
	// in the partial line buffer which may now get consumed)
	TInt outputbuffersize=aBodyData.Length()+KBufferExtension;
	if (iPartialLine)
		outputbuffersize+=iPartialLine->Des().Length();

	HBufC8* decoded=HBufC8::NewL(outputbuffersize);
	TPtr8 decoded_ptr=decoded->Des();

	// Which decoder are we using?
	switch(iEncodingType)
		{
		case EEncodingTypeNone:
		case EEncodingType7Bit:
		case EEncodingType8Bit:
		case EEncodingTypeBinary:
		case EEncodingTypeUnknown:
			// Nothing to do, just copy data
			decoded->Des().Append(aBodyData);
			break;

		case EEncodingTypeBASE64:
			// Decode Base64 data: just filter it through decoder, it
			// ignores line breaks anyway.
			iB64Decoder.Decode(aBodyData,decoded_ptr);
			break;

		case EEncodingTypeUU:
			{
			TPtrC8 bodydata=aBodyData;

			// Got a partial buffer?
			if (!iPartialLine)
				{
				// Allocate buffer
				iPartialLine=HBufC8::NewL(KUuDecodedLineLength);
				iUUDecoding = EFalse;
				}
			
			// Decode UUEncoded data: line by line
			TBool decodeEnded = EFalse;
			TInt position=0;
			while ( bodydata.Length() && !decodeEnded )
				{
				// Find() returns the start of "\r\n". The decoding algorithm
				// requires that the encoded line contains the "\r\n".
				TInt lineEnd = bodydata.Find( _L8("\r\n") );
				if (lineEnd != KErrNotFound)
					{
					lineEnd = lineEnd + 2;
					AppendExtendL( &iPartialLine, bodydata.Left( lineEnd ));
				
					bodydata.Set( bodydata.Mid( lineEnd ) );
				
					// Check for a well-formated  begin-tag
					if ( CheckUUEStartL( iPartialLine->Des() ) )
						{
						iUUDecoding = ETrue;
						}
					else if ( iPartialLine->Compare( KImcvUueEnd ) != 0 && iUUDecoding )
						{
						// Every malformatted string is decoded as an empty string 
						// with length 0. Appending such a string is harmless.
						TPtr8 destination((unsigned char*)decoded_ptr.Ptr()+position,0,outputbuffersize-position);
						iUUDecoder.Decode(*iPartialLine,destination);
						position+=destination.Length();			
						}
					else if ( iUUDecoding )
						{
						decodeEnded = ETrue;
						iUUDecoding = EFalse;	
						}
					
					iPartialLine->Des().Zero();
					}
				else
					{
					AppendExtendL( &iPartialLine, bodydata);
					
					// advance to end of bodydata
  					bodydata.Set(bodydata.Ptr()+bodydata.Length(), 0);
					}
				}
			decoded->Des().SetLength(position);	
			break;
			}

		case EEncodingTypeQP:
			{
			TPtrC8 bodydata=aBodyData;

			// Got a partial buffer?
			if (!iPartialLine)
				{
				// Allocate buffer
				iPartialLine=HBufC8::NewL(256);
				}

			// Build buffer to decode: basically, QP decoder wants CRLF terminated
			// lines, so we build them in the iPartialLine buffer. There may be
			// stuff already there from previous data packet - so we just append.
			TInt position=0;
			while(bodydata.Length())
				{
				// Find a line break
				TInt lineend=bodydata.Find(_L8("\r\n"));

				// No break?
				if (lineend==KErrNotFound && !aEndOfStream)
					{
					// Stick it all in the partialline buffer, we should get a CRLF
					// soon...
					AppendExtendL( &iPartialLine,bodydata);
					break;
					}
				else
					{
					if (lineend==KErrNotFound)
						{
						// Append whole thing left to buffer
						AppendExtendL( &iPartialLine,bodydata);

						// advance to end of bodydata
						bodydata.Set(bodydata.Ptr()+bodydata.Length(), 0);
						}
					else
						{
						// Append to buffer up to that point (including the \r\n)
						AppendExtendL( &iPartialLine,bodydata.Left(lineend+2));

						// Remove from the buffer we're working on (including the \r\n)
						bodydata.Set(bodydata.Ptr()+lineend+2,bodydata.Length()-lineend-2);
						}

					// Decode & skip on in buffer
					TPtr8 destination((unsigned char*)decoded_ptr.Ptr()+position,0,outputbuffersize-position);
					iQPDecoder.Decode(*iPartialLine,destination);
					position+=destination.Length();
					iPartialLine->Des().Zero();
					}
				}

			// Update decoded
			decoded->Des().SetLength(position);
			break;
			}
		}

	// put back any partially converted data
	if (iLeftOver.Length())
		{
		decoded->Des().Insert(0, iLeftOver);
		iLeftOver.SetLength(0);
		}
		
	return decoded;
	}

