diff -r 000000000000 -r 72b543305e3a email/imap4mtm/imapsession/src/cimapcommand.cpp --- /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 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; iDes().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; iExists()) + { + 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 ; + } +