email/imap4mtm/imapsession/src/cimapcommand.cpp
changeset 0 72b543305e3a
child 9 1d7827e39b52
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/email/imap4mtm/imapsession/src/cimapcommand.cpp	Thu Dec 17 08:44:11 2009 +0200
@@ -0,0 +1,1236 @@
+// 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 "cimapcommand.h"
+#include "cimapsessionconsts.h"
+#include "cimapfolderinfo.h"
+#include "cimaputils.h"
+#include "cimapcharconv.h"
+#include "cimaplogger.h"
+
+static const TInt KDefaultNumberOfTaggedResponsesExpected = 1;
+
+CImapCommand::CImapCommand(CImapFolderInfo* aSelectedFolderData, TInt aLogId)
+	: iSelectedFolderData(aSelectedFolderData)
+	, iLogId(aLogId)
+	{}
+
+CImapCommand::~CImapCommand()
+	{
+	delete iOutputBuffer;
+	}
+
+void CImapCommand::SendDataCnfL()
+	{}
+
+CImapCommand::TParseState CImapCommand::ParseBlockL(const TDesC8& aData)
+	{
+#ifdef __IMAP_LOGGING
+	// Log the first few bytes only - but enough to tell the difference between untagged FETCH's
+	TPtrC8 truncatedData = aData.Left(30);
+	__LOG_FORMAT((iLogId, "CImapCommand::ParseBlockL(): [%S] %d octets", &truncatedData, aData.Length()));
+#endif //__IMAP_LOGGING
+
+	__ASSERT_DEBUG(iParseState != ECommandComplete, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidParseState2));
+	iUnparsedData.Set(aData);
+	iLiteralBytesExpected = 0; // Initialise to zero, and only peek in states that read a line.
+	
+	switch (iParseState)
+		{
+		case EWaitStartResponse:
+			{
+			if (Flushing())
+				{
+				ParseStartResponseFlushL(aData);
+				}
+			else
+				{
+				ParseLineL(aData);
+				}
+			break;
+			}
+		case EWaitLineParse:
+			{
+			ParseLineL(aData);
+			}
+			break;
+		case EWaitLiteralParse:
+			{
+			ParseLiteralL(aData);
+			}
+			break;		
+		case EWaitLiteralIngore:
+			{
+			__LOG_TEXT(iLogId, "CImapCommand Ignore Literal");
+			// Ignore the literal.
+			// A literal is always followed by a line (as part of the same response)
+			SetParseState(EWaitLineIgnore);
+			}
+			break;
+		case EWaitLineIgnore:
+			{
+			__LOG_TEXT(iLogId, "CImapCommand Ignore Line following Literal");
+			// Ignore the line, but check whether it indicates that another literal is on its way.
+			PeekLiteralRequest(aData);
+			
+			TParseState newParseState = (iLiteralBytesExpected > 0) 
+				? EWaitLiteralIngore	// a literal has been indicated on the line.  We need to ignore that too!
+				: EWaitStartResponse;	// we are not expecting any more data for this response, so wait for the next one.
+			SetParseState(newParseState);
+			}
+			break;
+		default:
+			{
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidParseState3));
+			}
+		}
+
+	if (iCompleteOnAnyResponse && iParseState==EWaitStartResponse)
+		{
+		__LOG_TEXT(iLogId, "CImapCommand - Complete on Any Response");
+		__ASSERT_DEBUG(iResponseCode == EImapResponseNone, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidResponseCode));
+		
+		// Simulate command completion so that the session closes this command.		
+		iResponseCode = EImapResponseOk;
+		SetParseState(ECommandComplete);
+		}
+
+	// check some post-conditions: is session getting consistent data?
+	
+	// (iParseState == ECommandComplete) implies (iResponseCode != EImapResponseNone)
+	__ASSERT_DEBUG(iParseState == ECommandComplete ? iResponseCode != EImapResponseNone : ETrue, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandMismatchedParseStateAndResponseCode));
+	
+	// (iParseState != ECommandComplete) implies (iResponseCode == EImapResponseNone)
+	__ASSERT_DEBUG(iParseState != ECommandComplete ? iResponseCode == EImapResponseNone : ETrue, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandMismatchedParseStateAndResponseCode));
+				
+	return iParseState;
+	}
+
+/**
+Parses incoming line data.
+@param aLine the line to parse.
+*/
+void CImapCommand::ParseLineL(const TDesC8& aLine)
+	{
+
+	// Should only be calling this method when parsing a line.
+	__ASSERT_DEBUG(iParseState == EWaitStartResponse || iParseState == EWaitLineParse, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidParseState4));
+	
+	PeekLiteralRequest(aLine);
+				
+	TParseBlockResult parseBlockResult = DoParseBlockL(aLine);
+	
+	if(parseBlockResult ==  ECompleteUntagged && iLiteralBytesExpected != 0)
+		return;
+	
+	switch (parseBlockResult)
+		{
+		case ECompleteTagged:
+			// No more data expected
+			__ASSERT_DEBUG(iLiteralBytesExpected == 0, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandResponseLiteralDataNotZero));
+			__ASSERT_DEBUG(iResponseCode != EImapResponseNone, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidResponseCode));
+			
+			SetParseState(ECommandComplete);
+			break;
+		case ECompleteUntagged:
+			// Wait for the next response line to be received
+			__ASSERT_DEBUG(iLiteralBytesExpected == 0, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandResponseLiteralDataNotZero));
+			
+			SetParseState(EWaitStartResponse);
+			break;
+		case EResponseIncomplete:
+			// Literal data is expected - this needs to be delivered to the subclass
+			__ASSERT_DEBUG(iLiteralBytesExpected > 0, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandResponseLiteralDataNotNonZero));
+			
+			SetParseState(EWaitLiteralParse);
+			break;
+		case ENotRecognised:
+			if (iParseState == EWaitLineParse)
+				{
+				__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidParseState5));
+				}
+			else // EWaitStartResponse
+				{
+				// The line was not understood by the subclass, so try the generic parsing
+				parseBlockResult = ParseUnhandledBlockL(aLine);
+				__ASSERT_DEBUG(parseBlockResult != EResponseIncomplete, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidParseBlockResult)); // Not expected or currently supported
+				
+				// If we're swallowing an unsupported response, then we may need to swallow a literal block next
+				TParseState newParseState = (iLiteralBytesExpected > 0) 
+					? EWaitLiteralIngore	// a literal has been indicated on the line.  We need to ignore that too!
+					: EWaitStartResponse;	// we are not expecting any more data for this response	
+				SetParseState(newParseState);
+				}
+			break;
+		default:
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidParseBlockResult));
+			break;
+		}
+	}
+	
+/**
+Parses incoming literal data.
+@param aLiteralBlock the literal block to parse.
+*/
+void CImapCommand::ParseLiteralL(const TDesC8& aLiteralBlock)
+	{
+	// Should only be calling this method when parsing a literal blcok.
+	__ASSERT_DEBUG(iParseState == EWaitLiteralParse, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidParseState6));
+	
+	TParseBlockResult parseBlockResult = DoParseBlockL(aLiteralBlock);
+	switch (parseBlockResult)
+		{
+		case ECompleteTagged:					
+		case ECompleteUntagged:
+			// Literals are always followed by a line, so the response can't be complete yet.
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidParseBlockResult));
+			break;
+		case EResponseIncomplete:
+			SetParseState(EWaitLineParse);
+			break;
+		case ENotRecognised: 
+			// Subclass asked for the literal block, so it ought to have parsed it.
+			// Fall through to default __ASSERT_DEBUG below.
+		default:
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidParseBlockResult));
+			break;
+		}
+	}
+
+/**
+During cancel this method is called whenever the start of a new response is received.
+It doesn't do any parsing, other than to detect the closing tagged response, 
+which completes the command and means there is no more data to receive.
+@param aLine the line containing the start of a new response.
+*/
+void CImapCommand::ParseStartResponseFlushL(const TDesC8& aLine)
+	{
+	__LOG_TEXT(iLogId, "CImapCommand::ParseStartResponseFlushL()");
+	
+	// should only call this method when parsing an incoming response but not cancelling
+	__ASSERT_DEBUG(iParseState == EWaitStartResponse, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidParseState7));
+	__ASSERT_DEBUG(iTaggedResponsesToFlush > 0, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandNoTaggedResponsesToFlush));
+
+	TInt tagId = 0;
+	switch (GetTagTypeL(tagId))
+		{
+		case ETagged:
+			{
+			--iTaggedResponsesToFlush;
+			if (iTaggedResponsesToFlush == 0)
+				{
+				// Don't check the tagId during cancel.
+				iResponseCode = GetResponseStateCode();
+				SetParseState(ECommandComplete);	
+				}
+			else
+				{
+				__LOG_FORMAT((iLogId, "CImapCommand - Still have %d tagged response(s) to flush", iTaggedResponsesToFlush));
+				}			
+			}
+			break;
+		case EUntagged:
+			{
+			// Ignore the untagged response, but check whether it indicates that another literal is on its way.
+			PeekLiteralRequest(aLine);
+			TParseState newParseState = (iLiteralBytesExpected > 0) 
+				? EWaitLiteralIngore	// a literal has been indicated on the line.  We need to ignore that too!
+				: EWaitStartResponse;	// we are not expecting any more data for this response, so wait for the next one.
+			SetParseState(newParseState);
+			}
+			break;
+		case EContinuation:
+			{
+			// Not exepecting a continuation response
+			CorruptDataL(); 
+			}
+			break;
+		}
+	}
+
+/**
+Looks for {nnn} at the end of the line, determine whether literal bytes are expected.
+If literal bytes are expected, then iLiteralBytesExpected is updated to store the
+number of bytes expected.
+@return KErrNone if literal bytes are expected, otherwise a system-wide error code.
+*/
+TInt CImapCommand::PeekLiteralRequest(const TDesC8& aLine)
+	{
+	// Minimum of 3 chars needed for literal specifier: {n}
+	TInt lineLength = aLine.Length();
+	if (lineLength < 3)
+		{
+		return KErrNotFound; // no request found.
+		}
+		
+	// Last char MUST be a }
+	if (aLine[lineLength-1] != '}')
+		{
+		return KErrNotFound; // no request found.
+		}
+	
+	// Reverse-Find matching '{'
+	TPtrC8 ptrLiteralRequest;
+	for (TInt i=lineLength-1; i>=0; --i)
+		{
+		if (aLine[i] == '{')
+			{
+			// Found matching brace.  Point at its contents only!
+			ptrLiteralRequest.Set(aLine.Mid(i+1, lineLength-i-2));
+			break; // from for loop
+			}
+		}
+	
+	if (ptrLiteralRequest.Length() == 0)
+		{
+		return KErrNotFound; // No matching brace found
+		}
+	
+	// Extract the value
+	TLex8 lex(ptrLiteralRequest);
+	TInt err = lex.Val(iLiteralBytesExpected);
+#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)			
+	//	If message body is downloaded using FETCH BINARY. The number of bytes received for previous fetch is recorded here.
+	//	This valued is used to make sure that there are no extra body fetch command are issued 
+	iPreviousLiteralBytesReceived = iLiteralBytesExpected;
+#endif
+	
+#ifdef __IMAP_LOGGING
+	if (iLiteralBytesExpected > 0)
+		{
+		__LOG_FORMAT((iLogId, "CImapCommand - Found Literal Request: %d", iLiteralBytesExpected));
+		}	
+#endif //__IMAP_LOGGING
+
+	return err;
+	}
+
+/**
+This is called when the session has processed all the blocks in its input buffer, but the 
+command is not yet complete (so it is expecting more data).
+This allows subclasses of CImapCommand to commit any bulk operations while waiting for the next set of data.
+By default, this method does nothing.
+*/
+void CImapCommand::WaitingForMoreDataL()
+	{}
+
+/**
+Call this method to stop fully parsing the command.
+Instead, discard all incoming server data until no more data is expected for this command.
+By default this means that the tagged response has been received.
+This method can be overridden.
+*/
+void CImapCommand::Cancel()
+	{
+	__LOG_TEXT(iLogId, "CImapCommand::Cancel()");
+	EnterFlushingState();
+	}
+
+void CImapCommand::EnterFlushingState()
+	{
+	iFlushing = ETrue;
+	iTaggedResponsesToFlush = NumberOfTaggedResponsesExpected();
+	
+	__LOG_FORMAT((iLogId, "CImapCommand::EnterFlushingState() - %d tagged response(s) to flush", iTaggedResponsesToFlush));
+	
+	if (iParseState == EWaitLiteralParse)
+		{
+		SetParseState(EWaitLiteralIngore);
+		}
+	else if (iParseState == EWaitLineParse)
+		{
+		SetParseState(EWaitLineIgnore);
+		}
+	}
+
+TBool CImapCommand::Flushing() const
+	{
+	return iFlushing;
+	}
+
+/**
+This method tells the session whether the Flush operation is able to complete
+the command straight away.
+By default, a command that has not already been completed (and so destoyed)
+cannot be completed early by Flush, as it is still waiting for the tagged
+response.
+This method can be overridden by commands that can be completed earlier, such
+as commands that do not wait for a tagged response.
+@return EFalse.
+*/
+TBool CImapCommand::CanCompleteFlushNow() const
+	{
+	// Any command that has completed should have been destroyed before this
+	// method is called.
+	__ASSERT_DEBUG(iParseState != ECommandComplete, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidParseState8));
+	__ASSERT_DEBUG(Flushing(), TImapServerPanic::ImapPanic(TImapServerPanic::ECommandNotFlushing));
+	
+	return EFalse;
+	}
+
+/**
+Returns the number of tagged responses that are currently expected.
+This default implementation always returns one, as most commands on send one request
+and expect one tagged response.
+Pipelined commands that send more than one request at a time should override this method.
+@return one.
+*/
+TInt CImapCommand::NumberOfTaggedResponsesExpected() const
+	{
+	return KDefaultNumberOfTaggedResponsesExpected;
+	}
+
+/**
+This API will return the result returned by the remote server to the issued IMAP command.
+
+@return The IMAP result code extracted from the incoming response or EImapResponseNone 
+		if the response has not yet been parsed.
+*/ 
+CImapCommand::TResponseCode CImapCommand::ResponseCode() const
+	{
+	return iResponseCode;
+	}
+
+/**
+@return the number of bytes 
+*/
+TInt CImapCommand::LiteralBytesExpected() const
+	{
+	return iLiteralBytesExpected;
+	}
+
+/**
+Static method that should a be called by all classes internal to CImapSession whenever they
+detect corrupt data - e.g. missing fields - that means that they can no longer process the 
+incoming data.
+
+This method will always leave with KErrCorrupt.
+*/
+#ifdef __IMAP_LOGGING
+void CImapCommand::CorruptDataL(TInt aLogId)
+#else
+void CImapCommand::CorruptDataL(TInt /*aLogId*/)
+#endif //__IMAP_LOGGING
+// static method
+	{
+	__LOG_TEXT(aLogId, "CImapCommand::CorruptDataL() !!!");
+	User::Leave(KErrImapCorrupt);
+	}
+
+/**
+Non-static version of CorruptDataL() that provides the current log id
+*/
+void CImapCommand::CorruptDataL()
+	{
+	CorruptDataL(iLogId);
+	}
+
+/**
+Static method that stores each part of the supplied data into an array based on 
+the delimiter value supplied in the aDelimiter argument.
+
+@param aDelimiter 	The character that delimits the end of one part and the begining of the next.
+@param aData 		The data that is to be split.
+@param aOutDelimitedPartsList Output array that recieves the list of parts.
+@param aMaxParts 	If greater than zero, this stops the search when aOutDelimitedPartsList 
+					has the specified number of parts.
+*/
+void CImapCommand::GetDelimitedPartsL(TChar aDelimiter, const TDesC8& aData, RDesParts& aOutDelimitedPartsList, TInt aMaxParts /*=0*/)
+// static method
+	{
+	aOutDelimitedPartsList.Reset();
+	TInt dataLength = aData.Length();
+	
+	if (dataLength == 0)
+		{
+		// no data to parse, so nothing to do.
+		return;
+		}
+	
+	TInt currentPos = 0;
+	TPtrC8 currentSegment(aData);
+	
+	for(;;)
+		{
+		TInt found = currentSegment.Locate(aDelimiter);
+		if (found == KErrNotFound)
+			{
+			aOutDelimitedPartsList.AppendL(currentSegment);
+			break;
+			}
+		
+		currentSegment.Set(currentSegment.Left(found));
+		aOutDelimitedPartsList.AppendL(currentSegment);
+		
+		// Check if whether have enough parts yet
+		if (aMaxParts > 0 && aOutDelimitedPartsList.Count() >= aMaxParts)
+			{
+			break;
+			}
+		
+		// Move past the delimiter and set next segment
+		currentPos += (found+1);
+		if (currentPos<dataLength)
+			{
+			currentSegment.Set(aData.Mid(currentPos));
+			}
+		else
+			{
+			// No more data
+			break;
+			}	
+		}
+	}
+
+/**
+Static method that calculates the length in characters of the given tag id
+
+@param aTagId The tag id whose length in characters is to be calculated
+@return The length in characters of the given tag id
+*/
+TInt CImapCommand::TagLength(TInt aTagId)
+// static method
+	{
+	// Count how many characters would be needed to represent aTagId as a string.
+	// Do this by count how many times aTagId can be divided by 10.
+	
+	TInt tagLength = 1; // values 0 to 9 have a length of 1 character
+	
+	while (aTagId > 9)
+		{
+		aTagId = aTagId / 10;
+		++tagLength;
+		}
+		
+	return tagLength;
+	}
+
+/**
+Encodes a Unicode mailbox name by:
+	- Converting it into ImapUtf7 format
+	- Quoting it if necessary
+@param aMailboxName the mailbox name in Unicode format.
+@return the mailbox name in ImapUtf7, quoted if necessary.
+*/
+HBufC8* CImapCommand::EncodeMailboxNameForSendL(const TDesC16& aMailboxName)
+// static method
+	{
+	// According to section 9 of RFC3501, mailbox is an astring.
+	// 	
+	// That means that if it cannot be represented as an atom, then it must be 
+	// represented as either a quoted string or literal.
+	// We can rule out literal as explained below.
+	//
+	// An atom is a string that contains any 7-bit character other than "atom-specials"
+	//
+	// atom-specials consists of 
+	//
+	// 	"(" ")" "{" "%" "*" DQUOTE "\" "]" SP CTL
+	//
+	//
+	// The ImapUtf7 encoding has the following characteristics (from RFC3501).
+	//
+	// "printable US-ASCII characters, except for "&", represent 
+	// themselves; that is, characters with octet values 0x20-0x25 and 0x27-0x7e.
+	// All other characters (octet values 0x00-0x1f and 0x7f-0xff) are
+	// represented in modified BASE64, with a further modification from
+	// [UTF-7] that "," is used instead of "/". Modified BASE64 MUST NOT be
+	// used to represent any printing US-ASCII character which can represent
+	// itself.""
+	//
+	// "&" means start "Modified BASE64" block
+	// "-" means end "Modified BASE64" block
+	// "&-" represents the & character
+	//
+	// "Modified BASE64" can contain the following characters
+	// "a" to "z", "A" to "Z", "0" to "9" and then "+" "," and "=".  The last is the pad character.
+	//
+	//
+	// So an Imap-UTF7 mailbox string can always be sent as quoted (and hence no need for literal)
+	// because it conforms to the RFC3501 definition of a quoted string, which 
+	// may only contain 7-bit US-ASCII characters other than NUL, CR and LF (which are encoded as Modified BASE64)
+	//
+	//
+	// To decide whether to send the encoded mailbox as an atom or as quoted, we need to test whether
+	// the encoded string contains printable atom-specials (CTL characters including CR and LF have 
+	// already been dealt with by the ImapUtf7 encoding)
+	//
+	// Usefully, the set of atom-specials and the set of characters used in Modified BASE64 
+	// DO NOT intersect.  That makes it safe to search for (and escape) atom-specials in an 
+	// encoded ImapUtf7 string without inadvertantly finding or modifying a BASE64 encoding.
+	//
+	// Note that section 5.1 of RFC3501 allows mailbox names to include all the atom-specials, including
+	// list wildcards such as "*" and "%".  So we need to search for all printable atom-specials, not a subset.
+
+	if (aMailboxName.Length()==0)
+	    {
+	    // aMailboxName is an empty string so just return an empty string.
+	    _LIT8(KEmptyString, "\"\"");
+	    HBufC8* hBuf = KEmptyString().AllocL();
+	    return hBuf;
+	    }
+
+	// Convert into ImapUtf7.
+	HBufC8* mailboxNameUtf7 = CImapUtils::GetRef().Charconv().ConvertFromUnicodeToImapUtf7L(aMailboxName);
+	CleanupStack::PushL(mailboxNameUtf7);
+	
+	// Search for printable atom-specials and quote the mailbox if any are found
+	TInt countQuotedSpecials = 0;
+	if (CheckForPrintableAtomSpecial(*mailboxNameUtf7, countQuotedSpecials))
+		{		
+		// Make enough room to escape each of the quoted-specials and to insert the start and end quote
+		TInt mailboxNameLength = mailboxNameUtf7->Length() + countQuotedSpecials + 2;
+		HBufC8 *expandedBuffer = mailboxNameUtf7->ReAllocL(mailboxNameLength);
+		
+		// Been moved due to realloc?
+		if (expandedBuffer != mailboxNameUtf7)
+			{
+			// Update mailboxNameUtf7 with the new address
+			mailboxNameUtf7 = expandedBuffer;
+			expandedBuffer = NULL;
+			
+			// Update the cleanup stack with the new address of mailboxNameUtf7
+			CleanupStack::Pop();
+			CleanupStack::PushL(mailboxNameUtf7);
+			}
+
+		// Insert the opening DQUOTE
+		mailboxNameUtf7->Des().Insert(0, KImapTxtDoubleQuote);
+		
+		// Perform escaping if there are any quoted-specials
+		if (countQuotedSpecials > 0)
+			{
+			// Use the length of the newly expanded buffer, 
+			// but do not include the first or last characters, which are non-escaped DQUOTE's
+			// Hence start at index 1, and reduce the length by 2
+			mailboxNameLength -= 2;
+			for(TInt i=1; i<mailboxNameLength; ++i)
+				{
+				if ((*mailboxNameUtf7)[i]=='\\' || (*mailboxNameUtf7)[i]=='\"')
+					{
+					// We have found one of the quoted-specials, this needs to be escaped
+					mailboxNameUtf7->Des().Insert(i, KImapTxtEscape);
+					++i;
+					}
+				}	
+			}
+			
+		// Append the closing DQUOTE
+		mailboxNameUtf7->Des().Append(KImapTxtDoubleQuote);
+		}
+	
+	CleanupStack::Pop(mailboxNameUtf7);
+	return mailboxNameUtf7;
+	}
+
+/**
+Checks whether the given string contains printable atom-specials.
+An atom special is one of "(" ")" "{" "%" "*" DQUOTE "\" "]" SP
+@param aString The string that is to be checked
+@param aCountQuotedSpecials Output parameter that counts the number of quoted-specials characters that need escaping
+@return ETrue is any of the atom-specials are present in the string
+*/
+TBool CImapCommand::CheckForPrintableAtomSpecial(const TDesC8& aString, TInt& aCountQuotedSpecials)
+// static method
+	{
+	aCountQuotedSpecials = 0;
+	TBool foundPrintableAtomSpecial = EFalse;
+
+	TInt stringLength = aString.Length();
+	for(TInt i=0; i<stringLength; ++i)
+		{
+		if (aString[i]=='(' || aString[i]==')' || aString[i]=='{' || aString[i]==' ' ||	// <<< specific printable atom-specials
+			aString[i]=='%' || aString[i]=='*' || 										// <<< list-wildcards
+			aString[i]==']')															// <<< resp-specials
+			{
+			foundPrintableAtomSpecial = ETrue;
+			}
+		else if (aString[i]=='\\' || aString[i]=='\"')									// <<< quoted-specials
+			{
+			foundPrintableAtomSpecial = ETrue;
+			++aCountQuotedSpecials;
+			}
+		}
+		
+	return foundPrintableAtomSpecial;
+	}
+
+/**
+Helper that is to be called by subclasses when they receive some data that is not specific to the class.
+This indicates that the data is probably unilateral notification data, such as EXISTS and RECENT
+This method will parse and handle the notification.
+@param aLine The line to be parsed and handled (not including CRLF).
+@return Whether the data was successfully parsed, recognised, etc
+*/
+CImapCommand::TParseBlockResult CImapCommand::ParseUnhandledBlockL(const TDesC8& aLine)
+	{
+	TParseBlockResult result = ENotRecognised;	
+	
+	iUnparsedData.Set(aLine);
+	
+	// Check the tag type.  This method should only be called for untagged resposnes.
+	TInt tagId = 0;
+	TTagType tagType = GetTagTypeL(tagId);
+	__ASSERT_DEBUG(tagType == EUntagged, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidTagType));
+	
+	// Looking for one of
+	//
+	// number "EXISTS"
+	// number "RECENT"
+	// nz-number "EXPUNGE"
+	//
+	// Note that untagged "BYE" is ignored.
+	// This can occur during LOGOUT or prior to the server terminating the connection.
+	// For LOGOUT, we wait for the tagged OK.
+	// And transport handler detects server termination - so we don't need to respond to the early warning.
+	
+	TPtrC8 numberPart = GetNextPart();
+	TLex8 desToInt(numberPart);
+	
+	TInt messageCountOrId = 0; // This is a message count for all except EXPUNGE where it is a message id
+	TInt err = desToInt.Val(messageCountOrId);
+	
+	if (err == KErrNone)
+		{
+		TPtrC8 secondPart = GetNextPart();
+		
+		if((secondPart.CompareF(KImapTxtExpunge) == 0))
+			{
+			__LOG_FORMAT((iLogId, "CImapCommand::ParseUnhandledBlockL() - Found EXPUNGE: %d", messageCountOrId));
+			if (iSelectedFolderData != NULL && iSelectedFolderData->Exists())
+				{
+				iSelectedFolderData->AddExpungedMessageL(messageCountOrId);
+				}
+			result = ECompleteUntagged;
+			}
+		else if((secondPart.CompareF(KImapTxtExists) == 0))
+			{
+			__LOG_FORMAT((iLogId, "CImapCommand::ParseUnhandledBlockL() - Found EXISTS: %d", messageCountOrId));
+			if (iSelectedFolderData != NULL)
+				{
+				iSelectedFolderData->SetExists(messageCountOrId);	
+				}			
+			result = ECompleteUntagged;
+			}
+		else if((secondPart.CompareF(KImapTxtRecent) == 0))
+			{
+			__LOG_FORMAT((iLogId, "CImapCommand::ParseUnhandledBlockL() - Found RECENT: %d", messageCountOrId));
+			if (iSelectedFolderData != NULL)
+				{
+				iSelectedFolderData->SetRecent(messageCountOrId);
+				}			
+			result = ECompleteUntagged;
+			}
+		else if ((secondPart.CompareF(KImapTxtFetch) == 0))
+			{
+			// If a FETCH has been requested, then the FETCH response will be handled by the subclass.
+			// So getting here means that the FETCH was unsolicited.
+			// When means that it is LIKELY that this is a FETCH FLAGS response.
+			// We don't need to parse the resposne fully (a complex task), but instead, 
+			// just record the notification that flags have changed for a message.
+			// This will cause a resync of flags when the Protocol Controller next attempts to go IDLE.
+			__LOG_FORMAT((iLogId, "CImapCommand::ParseUnhandledBlockL() - Found unsolicited FETCH: %d", messageCountOrId));
+			SetMessageFlagsChanged();
+			result = ECompleteUntagged;
+			}
+		}
+	
+#ifdef __IMAP_LOGGING
+	if (result == ENotRecognised)
+		{
+		__LOG_TEXT(iLogId, "CImapCommand::ParseUnhandledBlockL() - Ignoring Unhandled Response");
+		}	
+#endif //__IMAP_LOGGING
+
+	return result;
+	}
+
+/**
+Helper function to return the tag of the response
+@param aTagId Will contain the Tag if response is tagged.
+@return Returns the tag type as an enumeration.
+*/
+CImapCommand::TTagType CImapCommand::GetTagTypeL(TInt& aTagId)
+	{
+	TPtrC8 nextPart = GetNextPart() ;
+	if(nextPart.Find(KImapTxtContinuation) >= 0)
+	 	{
+	 	__LOG_TEXT(iLogId, "CImapCommand::GetTagTypeL() - Found Continuation");
+	 	return EContinuation ;
+	 	}
+	else if(nextPart.Find(KImapTxtUntagged) >= 0)
+		{
+		// Don't log this, as it's the most common, and causes bloat!
+		return EUntagged;
+		}
+	else 
+		{
+		TLex8 desToInt(nextPart);
+		if (desToInt.Val(aTagId) != KErrNone)
+			{
+			// We were expecting a numeric tag id here.
+			__LOG_TEXT(iLogId, "CImapCommand::GetTagTypeL() - Non-numeric tag id");
+			CorruptDataL();
+			}
+		__LOG_FORMAT((iLogId, "CImapCommand::GetTagTypeL() - Found Tagged: %d", aTagId));
+		}
+	return ETagged;
+	}
+
+/**
+Helper method to return the next part of the response from server.
+@return nextpart from the iUnparsedData
+		KNullDesC8 if no more data to return.
+*/
+TPtrC8 CImapCommand::GetNextPart()
+	{
+	TPtrC8 nextPart = iUnparsedData;
+	
+	// If the first character is a space, then skip it.	
+	if (iUnparsedData.Length() > 0)
+		{
+		if (iUnparsedData[0] == ' ')
+			{
+			iUnparsedData.Set(iUnparsedData.Mid(1));
+			}
+		}
+		
+	if (iUnparsedData.Length() > 0)
+		{
+		TInt length = iUnparsedData.Length();
+		// Some server may send n FETCH (UID n BODY[1]<0> "Message Body" BODY[1.MIME] {nnn}
+		// So extract the body from this response.
+		if(iUnparsedData[0] == '"')
+			{
+			for (TInt i=length-1; i>=1; --i)
+				{
+				if (iUnparsedData[i] == '"')
+					{
+					// Found matching brace.  Point at its contents only!
+					nextPart.Set(iUnparsedData.Mid(0, i + 1));
+					iUnparsedData.Set(iUnparsedData.Mid(i+1));
+					if(iUnparsedData.Length() > 0)
+						{
+						if (iUnparsedData[0] == ' ')
+							{
+							iUnparsedData.Set(iUnparsedData.Mid(1));
+							}
+						}
+					break; // from for loop
+					}
+				}
+			}
+		else
+			{
+			TInt offset = iUnparsedData.Locate(' ');
+			if (offset != KErrNotFound)
+				{
+				nextPart.Set(iUnparsedData.Left(offset));
+				if(iUnparsedData.Length() > offset + 1)
+					{
+					iUnparsedData.Set(iUnparsedData.Mid(offset + 1));
+					}
+				else
+					{
+					iUnparsedData.Set(KNullDesC8);
+					}
+				}
+			else
+				{
+				iUnparsedData.Set(KNullDesC8);
+				}
+			}
+		}
+	return 	nextPart;
+	}
+
+/**
+Helper method to return the next part of the response from server.
+The Unparsed Data pointer is not updated by this routine
+@return nextpart from the iUnparsedData
+		KNullDesC8 if no more data to return.
+*/
+TPtrC8 CImapCommand::PeekNextPart()
+	{
+	TPtrC8 nextPart = iUnparsedData;
+
+	while ((nextPart.Length() > 0) && (nextPart[0] == ' '))
+		{
+		nextPart.Set(nextPart.Mid(1));
+		}
+
+	if (nextPart.Length() > 0)
+		{
+		TInt offset = nextPart.Locate(' ');
+		if (offset != KErrNotFound)
+			{
+			nextPart.Set(nextPart.Left(offset));
+			}
+		}
+	else
+		{
+		nextPart.Set(KNullDesC8);
+		}
+
+	return nextPart;
+	}
+
+/**
+Remainder()
+@return the remaining unparsed data
+*/
+TPtrC8 CImapCommand::Remainder()
+	{
+	return iUnparsedData;
+	}
+
+/**
+Helper method to parse the "["resp-text-code"]" type response from server.
+iUnparsedData is updated to next part of response only if [] is found.
+@return resp-text-code without the square brackets([]).
+        KNullDesC8 if "[" not found.
+*/
+TPtrC8 CImapCommand::GetResponseTextCodeL()
+	{
+	if(iUnparsedData.Length()==0)
+		{
+		iUnparsedData.Set(KNullDesC8);
+		return iUnparsedData;	
+		}
+		
+	TPtrC8 response = iUnparsedData;
+	if(response[0] == '[')
+		{
+		TInt offset = iUnparsedData.Locate(']');
+		// No closing "]" ,possibly data may be corrupt.
+		if(offset== KErrNotFound)
+			{
+			__LOG_TEXT(iLogId, "CImapCommand::GetResponseTextCodeL() - No Closing ']'");
+			CorruptDataL();
+			}
+		
+		response.Set(iUnparsedData.Mid(1,offset-1));
+		if (iUnparsedData.Length() > offset + 1)
+			{
+			// Move to next non-space part after "]".
+			iUnparsedData.Set(iUnparsedData.Mid(offset+1));
+			}
+		else	
+			{
+			iUnparsedData.Set(KNullDesC8);
+			}
+			
+		__LOG_FORMAT((iLogId, "CImapCommand::GetResponseTextCodeL() - Found %S", &response));
+		}
+	else
+		{
+		__LOG_TEXT(iLogId, "CImapCommand::GetResponseTextCodeL() - Not Found");
+		response.Set(KNullDesC8);
+		}
+	
+	return response;	
+	}
+	
+/**
+Helper method to return the state of the server response.
+iUnparsedData is updated to next part of response only if OK/NO/BAD is found.
+@return EImapResponseOk if OK is returned by server
+		EImapResponseNo if NO is returned by server
+		EImapResponseBad if BAD is returned by server
+		EImapResponseNone in all other cases. 
+*/	
+CImapCommand::TResponseCode CImapCommand::GetResponseStateCode()	
+	{
+	TResponseCode result = EImapResponseNone;
+	TInt offset = 0;
+	TBool update = EFalse;
+	offset = iUnparsedData.Locate(' ');
+
+	TPtrC8 nextPart = iUnparsedData;
+	if (offset != KErrNotFound)
+		{
+		nextPart.Set(iUnparsedData.Left(offset));
+		}
+		
+	if(nextPart.CompareF(KImapTxtOk) == 0) 
+		{
+		__LOG_TEXT(iLogId, "CImapCommand::GetResponseStateCode() - Found \"OK\"");
+	 	result = EImapResponseOk ;
+	 	update = ETrue;
+	 	}
+	else if(nextPart.CompareF(KImapTxtNo) == 0) 
+		{
+		__LOG_TEXT(iLogId, "CImapCommand::GetResponseStateCode() - Found \"NO\"");
+		result = EImapResponseNo;
+		update = ETrue;
+		}
+	else if(nextPart.CompareF(KImapTxtBad) == 0) 
+		{
+		__LOG_TEXT(iLogId, "CImapCommand::GetResponseStateCode() - Found \"BAD\"");
+	 	result = EImapResponseBad;
+	 	update = ETrue;
+		}
+		
+	if(update)
+		{
+		if(iUnparsedData.Length() > offset + 1)
+			{
+			iUnparsedData.Set(iUnparsedData.Mid(offset+1));		
+			}
+		else
+			{
+			iUnparsedData.Set(KNullDesC8);
+			}
+			
+		}
+	return result;
+	}
+
+void CImapCommand::SetParseState(TParseState aParseState)
+	{
+	if (aParseState == iParseState)
+		{
+		// Nothing to do.
+		return;
+		}
+		
+#ifdef __IMAP_LOGGING
+
+	_LIT8(KTxtWaitStartResponse, "EWaitStartResponse");
+	_LIT8(KTxtWaitLiteralParse, "EWaitLiteralParse");
+	_LIT8(KTxtWaitLineParse, "EWaitLineParse");
+	_LIT8(KTxtWaitLiteralIngore, "EWaitLiteralIngore");
+	_LIT8(KTxtWaitLineIgnore, "EWaitLineIgnore");
+	_LIT8(KTxtCommandComplete, "ECommandComplete");
+	_LIT8(KTxtParseStateUnknown, "Unknown");
+	
+	TPtrC8 ptrOldCommandParseState(KTxtParseStateUnknown);
+	TPtrC8 ptrNewCommandParseState(KTxtParseStateUnknown);
+	
+	switch(iParseState)
+		{
+		case EWaitStartResponse:	ptrOldCommandParseState.Set(KTxtWaitStartResponse);		break;
+		case EWaitLiteralParse:		ptrOldCommandParseState.Set(KTxtWaitLiteralParse);		break;
+		case EWaitLineParse:		ptrOldCommandParseState.Set(KTxtWaitLineParse);			break;
+		case EWaitLiteralIngore:	ptrOldCommandParseState.Set(KTxtWaitLiteralIngore);		break;
+		case EWaitLineIgnore:		ptrOldCommandParseState.Set(KTxtWaitLineIgnore);		break;
+		case ECommandComplete:		ptrOldCommandParseState.Set(KTxtCommandComplete);		break;
+		}
+		
+	switch(aParseState)
+		{
+		case EWaitStartResponse:	ptrNewCommandParseState.Set(KTxtWaitStartResponse);		break;
+		case EWaitLiteralParse:		ptrNewCommandParseState.Set(KTxtWaitLiteralParse);		break;
+		case EWaitLineParse:		ptrNewCommandParseState.Set(KTxtWaitLineParse);			break;
+		case EWaitLiteralIngore:	ptrNewCommandParseState.Set(KTxtWaitLiteralIngore);		break;
+		case EWaitLineIgnore:		ptrNewCommandParseState.Set(KTxtWaitLineIgnore);		break;
+		case ECommandComplete:		ptrNewCommandParseState.Set(KTxtCommandComplete);		break;
+		}
+	
+	_LIT8(KLogFormat, "CImapCommand::iParseState %S ==> %S");
+	__LOG_FORMAT((iLogId, KLogFormat, &ptrOldCommandParseState, &ptrNewCommandParseState));
+	
+#endif //__IMAP_LOGGING
+
+	iParseState = aParseState;
+	}
+
+CImapCommand::TParseState CImapCommand::ParseState() const
+	{
+	return iParseState;
+	}
+
+void CImapCommand::SetMessageFlagsChanged()
+	{
+	if (iSelectedFolderData != NULL)
+		{
+		__LOG_TEXT(iLogId, "CImapCommand - SetMessageFlagsChanged");
+		iSelectedFolderData->SetMessageFlagsChanged(ETrue);
+		}
+	}
+
+//
+//
+//
+
+CImapCommandEx::CImapCommandEx(CImapFolderInfo* aSelectedFolderData, TInt aLogId)
+	: CImapCommand(aSelectedFolderData, aLogId)
+	{}
+
+CImapCommand::TParseBlockResult CImapCommandEx::DoParseBlockL(const TDesC8& /*aData*/)
+	{
+	TParseBlockResult result = ENotRecognised;
+	
+	switch (ParseState())
+		{
+		case EWaitStartResponse:
+			{
+			result = ParseStartResponseL();
+			}
+			break;
+		case EWaitLiteralParse:
+			{
+			__LOG_TEXT(iLogId, "CImapCommand - calling ParseLiteralBlockL()");
+			ParseLiteralBlockL();
+			result = EResponseIncomplete;
+			}
+			break;
+		case EWaitLineParse:
+			{
+			__LOG_TEXT(iLogId, "CImapCommand - calling ParseLineFollowingLiteralL()");
+			TBool wantMoreData = ParseLineFollowingLiteralL();
+			result = wantMoreData ? EResponseIncomplete : ECompleteUntagged;
+			}
+			break;
+		default:
+			{
+			// This method should not be called for any other parse state
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidParseState9));
+			}
+			break;
+		}
+		
+	return result;
+	}
+	
+CImapCommand::TParseBlockResult CImapCommandEx::ParseStartResponseL()
+	{
+	TParseBlockResult result = ENotRecognised;
+	
+	TInt tagId = 0;
+	switch (GetTagTypeL(tagId))
+		{
+		case ETagged:
+			{
+			__LOG_TEXT(iLogId, "CImapCommand - calling ParseTaggedResponseL()");
+			TBool commandComplete = ParseTaggedResponseL(tagId);
+			
+			// If this tagged response does not complete the command (e.g. during fetch body)
+			// then treat it as if it were an untagged resopnse.
+			result = commandComplete ? ECompleteTagged : ECompleteUntagged;
+			}
+			break;
+		case EUntagged:
+			{
+			__LOG_TEXT(iLogId, "CImapCommand - calling ParseUntaggedResponseL()");
+			result = ParseUntaggedResponseL();
+			}
+			break;
+		case EContinuation:
+			{
+			__LOG_TEXT(iLogId, "CImapCommand - calling ParseContinuationResponseL()");
+			ParseContinuationResponseL();
+			result = ECompleteUntagged;
+			}
+			break;
+		default:
+			{
+			// unexpected tag type value
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ECommandInvalidTagType));
+			CorruptDataL();
+			break;
+			}
+		}
+		
+	return result;
+	}
+	
+/**
+Calls the Default handler with no further processing.
+@see BaseParseTaggedResponseL()
+@param aTagId The incoming tag id.
+@return ETrue - indicating that the command is completed by this tagged response.
+*/
+TBool CImapCommandEx::ParseTaggedResponseL(TInt aTagId)
+	{
+	BaseParseTaggedResponseL(aTagId);
+	return ETrue;
+	}
+
+/**
+Default handler for a tagged response.
+Records the response status code (OK/NO/BAD)
+And checks that the incoming tag id matches the sent tag id.
+Can be called by subclasses.
+@param aTagId The incoming tag id.
+*/	
+void CImapCommandEx::BaseParseTaggedResponseL(TInt aTagId)
+	{
+	// Check the tag id
+	if (aTagId != iTagId)
+		{
+		// Unexpected tag id
+		CorruptDataL();
+		}
+
+	// Fetch and check the response code
+	iResponseCode = GetResponseStateCode();
+	if (iResponseCode == EImapResponseNone)
+		{
+		// Was expecting one of OK/NO/BAD, but didn't get it.  This is a parse error.
+		CorruptDataL();
+		}
+	}
+
+/**
+Default handler for a continuation response.
+Always leaves with KErrImapCorrupt, because by default commands do not expect continuation responses.
+*/
+void CImapCommandEx::ParseContinuationResponseL()
+	{
+	CorruptDataL();
+	}
+
+/**
+Default handler for an untagged response.
+Always treats the response as unrecognised, causing ParseUnhandledBlockL() to be called later.
+This default is used by commands that expect no specific responses.
+@return ENotRecognised
+*/
+CImapCommand::TParseBlockResult CImapCommandEx::ParseUntaggedResponseL()
+	{	
+	return ENotRecognised;
+	}
+
+/**
+Default handler for a block of literal data.
+Always leaves with KErrImapCorrupt, because by default commands do not expect literal data.
+*/
+void CImapCommandEx::ParseLiteralBlockL()
+	{
+	CorruptDataL();
+	}
+
+/**
+Default handler for a line following a literal block.
+Always leaves with KErrImapCorrupt, because by default commands do not expect literal data, let alone lines following literal data.
+@return because this implementation of the method always leaves, no value can ever be returned.
+*/
+TBool CImapCommandEx::ParseLineFollowingLiteralL()
+	{
+	CorruptDataL();
+	return EFalse;
+	}
+
+/***
+To get the present TagId of the Command object.
+@return the TagId of the Command object.
+*/	
+TInt CImapCommand::GetTagId() 
+	{
+	return iTagId ;
+	}
+