--- /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 ;
+ }
+