// Copyright (c) 1998-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 "cimapatomparser.h"
#include "cimapatom.h"
#include "cimapcommand.h"
#include "imappaniccodes.h"
// Initial parser buffer size & granularity
const TInt KIOBufferSize=1280;
const TInt KIOBufferGranularity=256;
CImapAtomParser::CImapAtomParser(TBool aAllowParseAtTopLevel, TInt aLogId)
: iAllowParseAtTopLevel(aAllowParseAtTopLevel)
, iBufferSize(KIOBufferSize)
, iLogId(aLogId)
{}
CImapAtomParser::~CImapAtomParser()
{
// Dispose of buffer
delete iBuffer;
// Delete any atom tree that may still exist
delete iRootAtom;
// delete the atom array
iAtomArray.ResetAndDestroy();
iAtomStack.Reset();
}
// Create and call non-trivial contructor
CImapAtomParser *CImapAtomParser::NewL(TBool aAllowParseAtTopLevel, TInt aLogId)
{
CImapAtomParser* self=new (ELeave) CImapAtomParser(aAllowParseAtTopLevel, aLogId);
CleanupStack::PushL(self);
self->ConstructL();
CleanupStack::Pop();
return self;
}
// The non-trivial constructor
void CImapAtomParser::ConstructL()
{
// Get initial buffer
iBuffer=HBufC8::NewL(iBufferSize);
iRootAtom=CImapAtom::NewLC();
CleanupStack::Pop(iRootAtom);
iAtom=iRootAtom;
iNextIsChild=ETrue;
}
// Return the root atom
CImapAtom* CImapAtomParser::RootAtom()
{
// Return it
return(iRootAtom);
}
/**
@return a descriptor that points at the unparsed portion of the last line to be processed.
This pointer descriptor is only valid while the descriptor it points at is valid
This pointer descriptor will only be set after ProcessLineL() has returned
EFalse to indicate that no more data is expected.
*/
const TPtrC8& CImapAtomParser::UnparsedData()
{
return iUnparsedData;
}
/**
Returns the internal data buffer, transferring ownership of the buffer to the caller.
The internal data buffer pointer is set to NULL.
This method should only be called after parsing is complete.
@return the data buffer.
*/
HBufC8* CImapAtomParser::DetachBuffer()
// Note that the iBuffer pointer can change during parsing (via ReAllocL)
// So we only want to expose iBuffer after parsing.
{
// Check for internal programming error
__ASSERT_DEBUG(iParserState == EStateParseComplete, TImapServerPanic::ImapPanic(TImapServerPanic::EAtomParserInvalidParserState));
HBufC8* detachedBuffer = iBuffer;
iBuffer = NULL;
return detachedBuffer;
}
// Deal with the atom stack: this is used when building the atom tree
void CImapAtomParser::PushL(CImapAtom* aAtom)
{
iAtomStack.AppendL(aAtom);
}
CImapAtom* CImapAtomParser::PopL()
{
if (iAtomStack.Count() <= 0)
{
// Mismatched bracket
CImapCommand::CorruptDataL(iLogId);
}
TInt count = iAtomStack.Count();
CImapAtom* atom = iAtomStack[count-1];
iAtomStack.Remove(count-1);
return(atom);
}
// Add to the parsed buffer
void CImapAtomParser::BufferAppendL(const TChar aChar)
{
// Check for internal programming error
__ASSERT_DEBUG(iBuffer != NULL, TImapServerPanic::ImapPanic(TImapServerPanic::EAtomParserBufferIsNull));
// Does buffer need extending?
if (iBuffer->Length()==iBufferSize)
{
HBufC8 *oldbuffer=iBuffer;
const TText8 *oldbufptr=iBuffer->Ptr();
// Extend by granularity amount
iBufferSize+=KIOBufferGranularity;
iBuffer=iBuffer->ReAllocL(iBufferSize);
// Buffer moved?
if (iBuffer!=oldbuffer)
{
// Fixup buffer tree pointers
iRootAtom->FixupL(iBuffer,oldbufptr);
}
}
// Append the data
iBuffer->Des().Append(aChar);
}
// Add the last atom appended to the buffer to the tree.
void CImapAtomParser::AddAtomL()
{
// Check for internal programming error
__ASSERT_DEBUG(iBuffer != NULL, TImapServerPanic::ImapPanic(TImapServerPanic::EAtomParserBufferIsNull));
TPtrC8 bufptr(iBuffer->Ptr()+iAtomStart, iBuffer->Length()-iAtomStart);
// Make a new current atom
CImapAtom* newAtom=CImapAtom::NewLC();
iAtomArray.AppendL(newAtom);
CleanupStack::Pop(newAtom);
// Set pointers in it
newAtom->Set(bufptr, iParserQuoted);
// Add it as a child/sibling to the current atom
if (iNextIsChild)
{
iAtom->AddChild(newAtom);
}
else
{
iAtom->AddNext(newAtom);
}
// The next item should be a sibling
iNextIsChild=EFalse;
// Make new current
iAtom=newAtom;
}
/**
Receives a line of data.
If parsing completes before the end of the line, then aLine is set to point
at the octet immediately after the last parsed octet
@return ETrue if more data is expected.
*/
TBool CImapAtomParser::ProcessLineL(const TDesC8& aLine)
{
// Check for internal programming errors
__ASSERT_DEBUG(iParserState == EStateAtomWait, TImapServerPanic::ImapPanic(TImapServerPanic::EAtomParserInvalidParserState));
__ASSERT_DEBUG(iBuffer != NULL, TImapServerPanic::ImapPanic(TImapServerPanic::EAtomParserBufferIsNull));
// Process the buffer
TChar octet;
TInt lineLength = aLine.Length();
TBool bReachedTopLevel = EFalse;
TInt pos; // used after the loop
for(pos=0; pos<lineLength; ++pos)
{
// Octet to process
octet=aLine[pos];
switch(iParserState)
{
case EStateAtomWait:
switch(octet)
{
case '(':
{
// Make a new current atom
CImapAtom* newAtom=CImapAtom::NewLC();
iAtomArray.AppendL(newAtom);
CleanupStack::Pop(newAtom);
// Add it as a sibling to the current atom
if (iNextIsChild)
{
iAtom->AddChild(newAtom);
}
else
{
iAtom->AddNext(newAtom);
}
// The next item should be a child
iNextIsChild=ETrue;
// Push current atom onto atom stack, make new current
iAtom=newAtom;
PushL(iAtom);
// Store the open bracket in the buffer, so we can tell what it is
BufferAppendL(octet);
iAtom->Set(iBuffer->Right(1), EFalse);
break;
}
case ')':
// End of this nesting level: pop last atom off stack and
// make it the new current one
iAtom=PopL();
if (iAtomStack.Count() == 0)
{
bReachedTopLevel = ETrue;
}
// Any new atoms will be siblings, not children
iNextIsChild=EFalse;
break;
case '{':
// Start of a literal length
iLiteralLength=0;
iParserQuoted=EFalse;
iParserState=EStateLiteralLength;
break;
case ' ':
case '\r':
// Whitespace. Ignore! This state only happens with whitespace
// after a close )] or a endquote "
// Note that the CRLF will already have been stripped out by CImapSession
// so we are treating a CR on its own as whitespace
break;
case '\"':
// Quotes: we don't keep them, so the atom starts at the next
// character.
iAtomStart=iBuffer->Length();
iParserState=EStateInAtom;
iParserQuoted=ETrue;
iGotEscape=EFalse;
break;
default:
// Start new atom in buffer
iAtomStart=iBuffer->Length();
BufferAppendL(octet);
iParserState=EStateInAtom;
iParserQuoted=EFalse;
break;
}
break;
case EStateInAtom:
if (iParserQuoted)
{
// Look for another quote
if (octet=='\"')
{
// Just had an escape character?
if (iGotEscape)
{
// Add the character
BufferAppendL(octet);
iGotEscape=EFalse;
}
else
{
// It's the terminator: Add the atom, minus the quotes
AddAtomL();
iParserState=EStateAtomWait;
}
}
else
{
// Escape character?
if (!iGotEscape && octet=='\\')
{
// Got one
iGotEscape=ETrue;
}
else
{
// Add to buffer
BufferAppendL(octet);
iGotEscape=EFalse;
}
}
}
else
{
switch(octet)
{
case ' ':
case '\r':
// Whitespace: end of atom
// Note that the CRLF will already have been stripped out by CImapSession
// so we are treating a CR on its own as whitespace
{
AddAtomL();
// Either go back to looking for an atom, or a LF
//iParserState=(octet=='\r')?EStateWaitLF:EStateAtomWait;
iParserState = EStateAtomWait;
}
break;
case '(':
{
// Add this atom
AddAtomL();
// Make a new current atom
CImapAtom* newAtom=CImapAtom::NewLC();
iAtomArray.AppendL(newAtom);
CleanupStack::Pop(newAtom);
// Add it as a sibling to the current atom
if (iNextIsChild)
{
iAtom->AddChild(newAtom);
}
else
{
iAtom->AddNext(newAtom);
}
// The next item should be a child
iNextIsChild=ETrue;
// Push current atom onto atom stack, make new current
iAtom=newAtom;
PushL(iAtom);
// Store the open bracket in the buffer, so we can tell what it is
BufferAppendL(octet);
iAtom->Set(iBuffer->Right(1), EFalse);
iParserState=EStateAtomWait;
}
break;
case ')':
{
// Although these bytes usually indicate the end of an atom,
// they can also legitimately appear in a text field.
// If this is the end of an atom, then it must be a child or
// sibling atom in which case there will be an entry on the atom
// stack. If there is no entry on the atom stack, then this must
// be a text field so just add the octet to the buffer.
if (iAtomStack.Count() > 0)
{
// Add this atom
AddAtomL();
// End of this nesting level: pop last atom off stack and
// make it the new current one
iAtom=PopL();
if (iAtomStack.Count() == 0)
{
bReachedTopLevel = ETrue;
}
// Any new atoms will be siblings, not children
iNextIsChild=EFalse;
iParserState=EStateAtomWait;
}
else
{
BufferAppendL(octet);
}
}
break;
default:
{
// Add to buffer
BufferAppendL(octet);
}
break;
}
}
break;
case EStateLiteralLength:
// Digit?
if (octet.IsDigit())
{
// Add it to the total
iLiteralLength=(iLiteralLength*10)+(octet-(TChar)'0');
if (iLiteralLength <0)
{
User::Leave(KErrCorrupt);
}
}
else if (octet=='}')
{
// Need to skip CR, LF
iLiteralSkip=2;
iParserState=EStateLiteralSkip;
// Add the atom (with the length we know, but no data) to the
// structure now, so that the partial structure can be parsed.
iAtomStart=iBuffer->Length();
}
break;
case EStateLiteralSkip:
// Skipping...
if (--iLiteralSkip==0)
{
// Is literal 0 bytes long?
if (iLiteralLength==0)
{
// Nothing to follow
iParserState=EStateAtomWait;
}
else
{
// In some cases {nnn} would be present in the middle of FETCH header info ALSO
// so need to process the array further
// Eg: *43 FETCH (FLAGS (\Seen $LuukkuClean $NotJunk JunkRecorded) UID 6285 BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "ISO-8859-1") NIL NIL "7BIT" 4 0 NIL NIL NIL)("APPLICATION" "PDF" ("NAME" {25}
// Kopio_paperikirjeestä.pdf) NIL NIL "BASE64" 187346 NIL ("ATTACHMENT" ("FILENAME" {25}
// Kopio_paperikirjeestä.pdf)) NIL) "MIXED" ("BOUNDARY" "----=_Part_7226_1401000605.1221158570821") NIL NIL) BODY[HEADER.FIELDS (Received Date Subject From Priority X-MSMail-Priority X-Priority Importance)] {977}
// Checking for pos>=2, so that array is not out of bounds
if ((pos>=2) && (aLine[pos-2] != ']'))
{
iParserState=EStateAtomWait;
}
else
{
iParserState=EStateLiteralFetch;
// Add the atom (with the length we know, but no data) to the
// structure now, so that the partial structure can be parsed.
iAtomStart=iBuffer->Length();
AddAtomL();
}
}
}
break;
case EStateLiteralFetch:
// Fetching
{
// Internal programming error:
// This state should only be handled in ProcessLiteralBlockL()
__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::EAtomParserInvalidParserState));
}
break;
} // switch(iParserState)
if (bReachedTopLevel && !iAllowParseAtTopLevel)
{
break; // from for loop
}
}// for(...pos...)
// Exited early. Record the un-parsed data
if (bReachedTopLevel)
{
// skip the close-bracket that caused us to reach the top
++pos;
// and record anything that remains
if (pos < lineLength)
{
iUnparsedData.Set(aLine.Right(lineLength-pos));
}
}
// The whole buffer has been read now
if (iParserState == EStateInAtom)
{
AddAtomL();
iParserState = EStateAtomWait;
}
else if (iParserState == EStateLiteralLength)
{
// the line contained a '{' but did not have a matching '}'
CImapCommand::CorruptDataL(iLogId);
}
// Check for internal programming error
__ASSERT_DEBUG(iParserState == EStateAtomWait || iParserState == EStateLiteralFetch, TImapServerPanic::ImapPanic(TImapServerPanic::EAtomParserInvalidParserState));
TBool bWantMoreData = ETrue;
if (iParserState == EStateAtomWait)
{
bWantMoreData = EFalse;
iParserState = EStateParseComplete;
}
return bWantMoreData;
}
void CImapAtomParser::ProcessLiteralBlockL(const TDesC8& aLiteralBlock)
{
// Check for internal programming errors
__ASSERT_DEBUG(iParserState == EStateLiteralFetch, TImapServerPanic::ImapPanic(TImapServerPanic::EAtomParserInvalidParserState));
__ASSERT_DEBUG(aLiteralBlock.Length() == iLiteralLength, TImapServerPanic::ImapPanic(TImapServerPanic::EAtomParserLiteralBlockLengthMismatch));
__ASSERT_DEBUG(iBuffer != NULL, TImapServerPanic::ImapPanic(TImapServerPanic::EAtomParserBufferIsNull));
// BufferAppendL() appends a character, whereas this appends a whole string
if (iBuffer->Length() + iLiteralLength > iBufferSize)
{
HBufC8 *oldbuffer=iBuffer;
const TText8 *oldbufptr=iBuffer->Ptr();
// Extend by extra amount + round up by KIOBufferGranularity
iBufferSize += iLiteralLength;
iBufferSize += (KIOBufferGranularity - (iBufferSize % KIOBufferGranularity));
iBuffer=iBuffer->ReAllocL(iBufferSize);
// Buffer moved?
if (iBuffer!=oldbuffer)
{
// Fixup buffer tree pointers
iRootAtom->FixupL(iBuffer,oldbufptr);
}
}
iBuffer->Des().Append(aLiteralBlock);
AddAtomL();
// literal is always followed by a line, so we need to be waiting for the next atom
iParserState=EStateAtomWait;
}