--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/email/pop3andsmtpmtm/imapservermtm/src/IMAPSESS.CPP Mon May 03 12:29:07 2010 +0300
@@ -0,0 +1,10210 @@
+// Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+//
+// Initial Contributors:
+// Nokia Corporation - initial contribution.
+//
+// Contributors:
+//
+// Description:
+// Deal with a connection to an IMAP4rev1 server
+// This involves logging in, issuing commands and parsing responses into
+// suitable return formats as necessary, and updating the message server's
+// database.
+//
+//
+
+#include <msvstd.h>
+#include <miuthdr.h>
+#include <miut_err.h>
+#include <msventry.h>
+#include <imcvutil.h>
+
+#include <txtetext.h>
+
+#include <cmsvbodytext.h>
+#include <txtrich.h>
+#include <imcvtext.h>
+#include <imcvsend.h>
+#include <imcm.rsg> // resource definition for IMCV
+#include <barsread.h> // TResourceReader
+#include <msvapi.h>
+
+#include "impsmtm.h"
+
+#include "imapsess.h"
+#include "impspan.h"
+#include "fldindex.h"
+#include <imapset.h>
+#include <miutlog.h>
+#include <msvstore.h>
+#include <commdb.h>
+#include <commdbconnpref.h>
+#include <mmsvattachmentmanager.h>
+#include <mmsvattachmentmanagersync.h>
+#include <cemailaccounts.h>
+#ifdef __WINS__
+#include <e32wins.h> // for maxfilename lengths
+#include <msvapi.h>
+#endif
+
+#ifdef _DEBUG
+#define LOG_COMMANDS(a) a
+#define DBG(a) a
+#define PRINTING
+#else
+#define LOG_COMMANDS(a)
+#define DBG(a)
+#undef PRINTING
+#endif
+
+#include "cimapcanceltimer.h"
+#include "mimapsessionobserver.h"
+
+// For the "UID SEARCH" command, the reply is of the form:
+// "* SEARCH 1234547890 1234567891 ......."
+// : :
+// |---9---| :
+// |---11----|
+//
+// Here the untagged headers is 9 characters and each UID part may be up to 11 characters.
+// So if we wanted to limit the number of replies recieved in a single search so that it is
+// no larger is size than a single body fetch part....
+
+// Maximum number of UIDs acceptable in a single reply.
+const TInt KImapUidSearchSize=(5120-9)/11;
+
+// Initial size of buffer in which the "Real name <user@host>" strings
+// are built before they're put into the CImHeader and the size by
+// which to increment the buffer when necessary
+const TInt KImapAddressSizeInc=256;
+
+// Initial size of the buffer containing the UID Search list
+const TInt KUidListStringSize=256;
+
+// Illegal UID we use for marker
+const TUint KIllegalUID = 0xffffffff;
+
+// Idle time when waiting for a cancelled fetch to complete (microseconds)
+const TInt KImapFetchCancelIdleTime = 10000000; // 10 seconds
+
+// Idle time (seconds) when waiting for a DONE command to complete
+// when coming out of IMAP IDLE state.
+const TInt KImapDoneInactivityTimeSeconds = 5;
+
+// IMAP text
+_LIT8(KIMAP_UNTAGGED, "*");
+_LIT8(KIMAP_CONTINUATION, "+");
+_LIT8(KIMAP_BODY, "BODY");
+_LIT8(KIMAP_BODYPEEK, "BODY.PEEK");
+_LIT8(KIMAP_BODYSTRUCTURE, "BODYSTRUCTURE");
+_LIT8(KIMAP_BYE, "BYE");
+_LIT8(KIMAP_CAPABILITY, "CAPABILITY");
+_LIT8(KIMAP_EXISTS, "EXISTS");
+_LIT8(KIMAP_EXPUNGE, "EXPUNGE");
+_LIT8(KIMAP_FETCH, "FETCH");
+_LIT8(KIMAP_FLAGS, "FLAGS");
+_LIT8(KIMAP_HEADERFIELDS, "HEADER.FIELDS");
+
+_LIT8(KIMAP_ALERT, "ALERT");
+_LIT8(KIMAP_LIST, "LIST");
+_LIT8(KIMAP_LSUB, "LSUB");
+_LIT8(KIMAP_NIL, "NIL");
+_LIT8(KIMAP_NO, "NO");
+_LIT8(KIMAP_PREAUTH, "PREAUTH");
+_LIT8(KIMAP_READWRITE, "READ-WRITE");
+_LIT8(KIMAP_READONLY, "READ-ONLY");
+_LIT8(KIMAP_RECENT, "RECENT");
+_LIT8(KIMAP_RFC822SIZE, "RFC822.SIZE");
+_LIT8(KIMAP_UID, "UID");
+_LIT8(KIMAP_UIDVALIDITY, "UIDVALIDITY");
+_LIT8(KIMAP_UIDNEXT, "UIDNEXT");
+_LIT8(KIMAP_MIME, "MIME");
+_LIT8(KIMAP_SEARCH, "SEARCH");
+
+// IMAP capabilities
+_LIT8(KIMAP_VERSION, "IMAP4rev1");
+_LIT8(KIMAP_STARTTLS, "STARTTLS");
+_LIT8(KIMAP_LOGINDISABLED, "LOGINDISABLED");
+_LIT8(KIMAP_IDLE, "IDLE");
+
+// IMAP commands (trailing spaces, except for those which take no params)
+_LIT8(KIMAPC_CLOSE, "CLOSE");
+_LIT8(KIMAPC_LOGOUT, "LOGOUT");
+_LIT8(KIMAPC_SUBSCRIBE, "SUBSCRIBE ");
+_LIT8(KIMAPC_UNSUBSCRIBE, "UNSUBSCRIBE ");
+_LIT8(KIMAPC_IDLE, "IDLE");
+_LIT8(KIMAPC_DONE, "DONE");
+
+// IMAP flags
+_LIT8(KIMAPFLAG_NOSELECT, "\\Noselect");
+_LIT8(KIMAPFLAG_NOINFERIORS, "\\Noinferiors");
+_LIT8(KIMAPFLAG_ANSWERED, "\\Answered");
+_LIT8(KIMAPFLAG_DELETED, "\\Deleted");
+_LIT8(KIMAPFLAG_DRAFT, "\\Draft");
+_LIT8(KIMAPFLAG_FLAGGED, "\\Flagged");
+_LIT8(KIMAPFLAG_RECENT, "\\Recent");
+_LIT8(KIMAPFLAG_SEEN, "\\Seen");
+_LIT8(KIMAPFLAG_UNREAD, "\\Unread");
+
+// MIME message types
+_LIT8(KMIME_MESSAGE, "MESSAGE");
+_LIT8(KMIME_RFC822, "RFC822");
+_LIT8(KMIME_TEXT, "TEXT");
+_LIT8(KMIME_HTML, "HTML");
+_LIT8(KMIME_XVCARD, "X-VCARD");
+_LIT8(KMIME_VCALENDAR, "X-VCALENDAR");
+_LIT8(KMIME_ICALENDAR, "CALENDAR");
+_LIT8(KMIME_NAME, "NAME");
+_LIT8(KMIME_NAME_RFC2231, "NAME*");
+_LIT8(KMIME_FILENAME, "FILENAME");
+_LIT8(KMIME_FILENAME_RFC2231, "FILENAME*");
+_LIT8(KMIME_ATTACHMENT, "ATTACHMENT");
+_LIT8(KMIME_DELIVERY_STATUS, "DELIVERY-STATUS");
+_LIT8(KMIME_ALTERNATIVE, "ALTERNATIVE");
+_LIT8(KMIME_RELATED, "RELATED");
+_LIT8(KMIME_IMAGE, "IMAGE");
+_LIT8(KMIME_AUDIO, "AUDIO");
+_LIT8(KMIME_VIDEO, "VIDEO");
+_LIT8(KMIME_APPLICATION, "APPLICATION");
+
+// Encoding types
+_LIT8(KMIME_BASE64, "BASE64");
+
+// Multipart types
+_LIT8(KMIME_MIXED, "MIXED");
+
+// for first stage download (to view in folder list)
+_LIT8(KImapFetchSmallHeaderToEnd, "%d UID FETCH %d:* (UID FLAGS BODYSTRUCTURE BODY.PEEK[HEADER.FIELDS (Received Date Subject From %S)])\r\n");
+_LIT8(KImapFetchSmallHeaderRange,"%d UID FETCH %d:%d (UID FLAGS BODYSTRUCTURE BODY.PEEK[HEADER.FIELDS (Received Date Subject From %S)])\r\n");
+_LIT8(KImapFetchSmallHeaderRangeRefined,"%d UID FETCH %S (UID FLAGS BODYSTRUCTURE BODY.PEEK[HEADER.FIELDS (Received Date Subject From %S)])\r\n");
+
+// for second stage download (to view when downloading whole email)
+_LIT8(KImapFetchLargeHeader, "%d UID FETCH %d (UID FLAGS BODYSTRUCTURE BODY.PEEK[HEADER.FIELDS (Received Date Subject From Reply-to To Cc Bcc Message-ID %S)])\r\n");
+_LIT8(KImapFetchLargeHeaderRange, "%d UID FETCH %d:* (UID FLAGS BODYSTRUCTURE BODY.PEEK[HEADER.FIELDS (Received Date Subject From Reply-to To Cc Bcc Message-ID %S)])\r\n");
+
+// Use %S so we can specify a refined FETCH based on a previous refined SEARCH
+_LIT8(KImapFetchLargeHeaderRangeRefined, "%d UID FETCH %S (UID FLAGS BODYSTRUCTURE BODY.PEEK[HEADER.FIELDS (Received Date Subject From Reply-to To Cc Bcc Message-ID %S)])\r\n");
+// Constants for fetching body
+_LIT8(KImapFetchBodyPeek, "%d UID FETCH %d (BODY.PEEK[%S]<%d.%d>)\r\n");
+_LIT8(KImapFetchBody, "%d UID FETCH %d (BODY[%S]<%d.%d>)\r\n");
+_LIT8(KImapFetchMimeBodyPeek, "%d UID FETCH %u (BODY.PEEK[%S]<0.%d> BODY.PEEK[%S.MIME])\r\n");
+_LIT8(KImapFetchMimeBody, "%d UID FETCH %u (BODY[%S]<0.%d> BODY[%S.MIME])\r\n");
+
+// Constants for sending commands
+_LIT8(KImapCommand, "%S\r\n");
+
+// The maximum number of octets to be uuencoded on each line is 45
+const TInt KUuDecodedLineLength = 45;
+
+const TInt KBodyTextChunkSizeBytes = 512;
+const TInt KKiloByteSize = 1024;
+// UIDs are 32bit integers, which takes maximum 10 decimal digits.
+// KMaxUint32Chars is used to check whether the string containg the UID
+// list has allocated enough memory when creating it.
+const TInt KMaxUint32Chars = 10;
+
+// Processing to remove illegal characters from a filename
+LOCAL_C void StripIllegalCharactersFromFileName(TDes16& aName)
+ {
+ TInt length=aName.Length();
+ for(TInt index=0; index < length; index++)
+ {
+ //parse extracted filename and replace any illegal chars with a default
+ TUint charr=(TUint)aName[index];
+ if( charr == '*' || charr == '\\' || charr == '<' || charr == '>' ||
+ charr == ':' || charr == '"' || charr == '/' || charr == '|' ||
+ charr == '?' || charr < ' ')
+ {
+ aName[index] = KImcvDefaultChar;
+ }
+ }
+ }
+
+
+// We don't do the async notifications yet: they make everything a bit more wobbly
+// SJM actually I guess we do do them now!
+#define ASYNC_NOTIFICATIONS
+
+// do we automatically set the iRelatedId of each object to itself? No
+// for now as it confuses the offline handling code which assumes it
+// is a shadow entry if iRelatedId is set.
+#define SET_RELATED_ID 0
+
+// This is very nasty but necessary as the flag returning functions
+// return 0 or not-zero not 0 or 1
+
+#define FIXBOOL(a) (a?ETrue:EFalse)
+
+// Directory structure
+CImImap4DirStruct::CImImap4DirStruct()
+ {
+ }
+
+CImImap4DirStruct::~CImImap4DirStruct()
+ {
+ // Get rid of leaf
+ delete iLeafname;
+ }
+
+void CImImap4DirStruct::SetLeafnameL(const TDesC& aName)
+ {
+ // Make buffer, set it
+ iLeafname=HBufC::NewL(aName.Length());
+ *iLeafname=aName;
+ }
+
+TPtrC CImImap4DirStruct::Leafname()
+ {
+ // Return it
+ return(*iLeafname);
+ }
+
+CImImap4Session::CImImap4Session(MImapSessionObserver& aObserver) // construct high-priority active object
+ : CMsgActive(1), iState(EImapStateDisconnected), iCancelledTag(-1),
+ iSecurityState(EUnknown), iCharset(KCharacterSetIdentifierUtf8),
+ iObserver(aObserver),iPrimarySession(NULL)
+ {
+ __DECLARE_NAME(_S("CImImap4Session"));
+ }
+
+CImImap4Session* CImImap4Session::NewLC(TInt aId, MImapSessionObserver& aObserver)
+
+ {
+ CImImap4Session* self=new (ELeave) CImImap4Session(aObserver);
+
+ CleanupStack::PushL(self);
+ self->ConstructL(aId);
+ return self;
+ }
+
+CImImap4Session* CImImap4Session::NewL(TInt aId, MImapSessionObserver& aObserver)
+ {
+ CImImap4Session* self=NewLC(aId, aObserver);
+ CleanupStack::Pop();
+ return self;
+ }
+
+void CImImap4Session::ConstructL(TInt aId)
+ {
+ // Add to active scheduler
+ CActiveScheduler::Add(this);
+
+ // Get FS
+ User::LeaveIfError(iFs.Connect());
+
+ // Get somewhere to store service settings
+ iServiceSettings=new (ELeave) CImImap4Settings;
+
+ // Get an IO processor
+ iImapIO=CImapIO::NewL(aId);
+
+ // Message selection
+ iSelection=new (ELeave) CMsvEntrySelection;
+
+ // List of messages to delete on folder close
+ iDeletedUids=new (ELeave) CArrayFixFlat<TMsvId>(8);
+
+ // List of messages to fetch in single fetch operation
+ iFetchList=new (ELeave) CArrayFixFlat<TMsvId>(8);
+
+ // List of messages who's Seen status has changed
+ iSetSeenList=new (ELeave) CArrayFixFlat<TMsvId>(8);
+ iClearSeenList=new (ELeave) CArrayFixFlat<TMsvId>(8);
+
+ RResourceFile resFile;
+ OpenResourceFileL(resFile,iFs); // NB leaves if file not found
+
+ // make sure the resource file will be closed if anything goes wrong
+ // CloseResourceFile is declared in IMCVDLL.H and defined in IMCVDLL.CPP
+ TCleanupItem close(CloseResourceFile,&resFile);
+ CleanupStack::PushL(close);
+
+ // Read iStore8BitData flag.
+ HBufC8* buf = resFile.AllocReadLC( STORE_8BIT_BODY_TEXT );
+ TResourceReader reader;
+ reader.SetBuffer(buf);
+ iStore8BitData = reader.ReadInt8();
+ CleanupStack::PopAndDestroy(buf);
+
+ buf=resFile.AllocReadLC(DEFAULT_ATTACHMENT_NAME);
+ reader.SetBuffer(buf);
+ iDefaultAttachmentName=reader.ReadTPtrC().AllocL();
+ CleanupStack::PopAndDestroy(2, &resFile); // buf, resFile (Close resfile)
+
+ if (!iStore8BitData)
+ {
+ // CRichText bits
+ iParaLayer=CParaFormatLayer::NewL();
+ iCharLayer=CCharFormatLayer::NewL();
+ }
+
+ // Create converter objects
+ iCharacterConverter=CCnvCharacterSetConverter::NewL();
+ iCharConv=CImConvertCharconv::NewL(*iCharacterConverter, iFs);
+ iHeaderConverter=CImConvertHeader::NewL(*iCharConv);
+
+ // we assume that this message is MIME as we have no way of
+ // detecting otherwise (without sending a new FETCH to the
+ // server to get the MIME-Version).
+ iHeaderConverter->SetMessageType(ETrue);
+
+ // Get timer to defeat IMSK timeout
+ iDummyRead = CImImap4SessionDummyRead::NewL(*this, *iImapIO, Priority());
+
+ iIdleRead = CImImap4SessionIdleRead::NewL(*this, Priority());
+
+ // List of message UIDs in a folder
+ iSearchList=new (ELeave) CArrayFixFlat<TUint32>(8); // For granularity of 8, 32 bytes are allocated each time.
+ iSyncLimit = KImImapSynchroniseAll;
+ // set up progress types
+ iProgress.iType=EImap4GenericProgressType;
+ iCurrentDrive = MessageServer::CurrentDriveL(iFs);
+
+ iIdleTimerExpired = EFalse;
+
+ iIdleTimer = CIdleTimeoutTimer::NewL(*this);
+ iReissueIdle = EFalse;
+ iDisconnectAfterIdleStopped = EFalse;
+ iFetchPartialMail=EFalse;
+ iCaf = new (ELeave) CImCaf(iFs);
+
+ iIsICalendar = EFalse;
+ iIsVCalendar = EFalse;
+
+ iUidString = HBufC8::NewL(KUidListStringSize);
+
+ iCancelTimer = CImapCancelTimer::NewL(*this);
+ }
+
+CImImap4Session::~CImImap4Session()
+ {
+ Cancel(); // make sure we're cancelled
+
+ delete iDummyRead;
+ delete iIdleRead;
+
+ // No settings
+ delete iServiceSettings;
+ delete iPrefs;
+
+ // Get rid of connection
+ delete iImapIO;
+
+ // Get rid of message body bits
+ delete iBodyBuf;
+ delete iMessageBody;
+ delete iBodyText;
+ delete iParaLayer;
+ delete iCharLayer;
+
+ // Message selection
+ delete iSelection;
+
+ // List of messages to delete
+ delete iDeletedUids;
+
+ // List of messages to fetch
+ delete iFetchList;
+
+ // List of seen flags to set/clear
+ delete iSetSeenList;
+ delete iClearSeenList;
+
+ // Partial line: used when doing Q-P decoding, as it works on a line at a time.
+ delete iPartialLine;
+
+ // Any attachment info (ie: there was a fetch in progress)
+ delete iAttachmentFile;
+ delete iAttachmentFullPath;
+ delete iAttachmentMimeInfo;
+ delete iDefaultAttachmentName;
+ delete iFooterString;
+
+ // Any message sizer left over (ie: we've been deleted in the middle of
+ // an append operation)
+ delete iMessageSizer;
+ delete iMessageSender;
+ delete iLineBuffer;
+
+ // Characterset conversion
+ delete iHeaderConverter;
+ delete iCharConv;
+ delete iCharacterConverter;
+
+ // CMsvServerEntry used for moves
+ delete iMoveEntry;
+
+ //cached TMsvEntry data
+ delete iCachedEntryData;
+
+ // List of message UIDs in a folder
+ delete iSearchList;
+
+ // Selection passed during synchronisation
+ delete iSynchronisationSelection;
+
+ delete iIdleTimer;
+ delete iCaf;
+ delete iUidString;
+ iFs.Close();
+
+ delete iCancelTimer;
+
+ //Delete Username and Password
+ delete iUsername;
+ delete iPassword;
+
+ // Note: iList is owned by a caller, not us - we don't delete it
+ }
+
+// Logging calls: passed through to ImapIO
+void CImImap4Session::LogText(const TDesC8& aString)
+ {
+ // Log the text
+ iImapIO->LogText(aString);
+ }
+
+void CImImap4Session::LogText(TRefByValue<const TDesC8> aFmt,...)
+ {
+ VA_LIST list;
+ VA_START(list,aFmt);
+ TBuf8<1024> aBuf;
+ //handles the data over flow panics. returns immediately without performing any action.
+ TDes8OverflowHandler overFlowHandler;
+
+ aBuf.AppendFormatList(aFmt,list, &overFlowHandler);
+ LogText(aBuf);
+ }
+
+// Do setentry, leave if there is an error
+void CImImap4Session::SetEntryL(const TMsvId aId)
+ {
+#ifdef PRINTING
+ TInt error=iEntry->SetEntry(aId);
+ if (error)
+ LogText(_L8("SetEntryL(%x) returned %d"),aId,error);
+ User::LeaveIfError(error);
+#else
+ User::LeaveIfError(iEntry->SetEntry(aId));
+#endif
+ }
+
+// Change entry, leave if error
+void CImImap4Session::ChangeEntryL(const TMsvEntry& aEntry)
+ {
+#ifdef PRINTING
+ TInt error=iEntry->ChangeEntry(aEntry);
+ if (error)
+ LogText(_L8("ChangeEntryL(%x) returned %d"),aEntry.Id(),error);
+ User::LeaveIfError(error);
+#else
+ User::LeaveIfError(iEntry->ChangeEntry(aEntry));
+#endif
+ }
+
+// Change entry in bulk mode (i.e. no index file commit), leave if error
+void CImImap4Session::ChangeEntryBulkL(const TMsvEntry& aEntry)
+ {
+#ifdef PRINTING
+ TInt error=iEntry->ChangeEntryBulk(aEntry);
+ if (error)
+ LogText(_L8("ChangeEntryL(%x) returned %d"),aEntry.Id(),error);
+ User::LeaveIfError(error);
+#else
+ User::LeaveIfError(iEntry->ChangeEntryBulk(aEntry));
+#endif
+ }
+// Get children, leave if error
+void CImImap4Session::GetChildrenL(CMsvEntrySelection& aSelection)
+ {
+#ifdef PRINTING
+ TInt error=iEntry->GetChildren(aSelection);
+ if (error)
+ LogText(_L8("GetChildrenL() returned %d"),error);
+ User::LeaveIfError(error);
+#else
+ User::LeaveIfError(iEntry->GetChildren(aSelection));
+#endif
+ }
+
+// This can be called after Select() has completed to find out if the
+// folder has changed in any way since the last sync
+TBool CImImap4Session::FolderChanged() const
+ {
+ return iMailboxReceivedExists ||
+ iMailboxReceivedExpunge ||
+ iMailboxReceivedFlags;
+ }
+
+TBool CImImap4Session::ImapIdleSupported() const
+ {
+ return iUseIdleCommand && iCapabilityIdleSupport;
+ }
+
+TBool CImImap4Session::IsIdling() const
+ {
+ return(iState==EImapStateIdling);
+ }
+
+// Transfers the current selection into the iFolderIndex, and sorts it by
+// UID.
+void CImImap4Session::MakeSortedFolderIndexL(TBool aUseCachedEntryData)
+ {
+
+ TInt noofchildren=iSelection->Count();
+
+ // Reset folder index
+ iFolderIndex.SetSizeL(noofchildren);
+ TInt a=0;
+
+ if(!aUseCachedEntryData)
+ { //can't rely on iCachedEntryData
+ TMsvEntry* entryPtr;
+ TMsvId id;
+ for(a=0;a<noofchildren;a++)
+ {
+ // Save UID/TMsvId of this entry
+ id=(*iSelection)[a];
+ User::LeaveIfError(iEntry->GetEntryFromId(id,entryPtr));
+ iFolderIndex[a].iUid=((TMsvEmailEntry)(*entryPtr)).UID();
+ iFolderIndex[a].iMsvId=id;
+ }
+ }
+ else
+ {
+ for(a=0;a<noofchildren;a++)
+ {
+ // Save UID/TMsvId of this entry
+ iFolderIndex[a].iUid=(*iCachedEntryData)[a].iUid;
+ iFolderIndex[a].iMsvId=(*iSelection)[a];
+ }
+ }
+
+ // Sort it by UID
+ iFolderIndex.Sort();
+
+ // Check for any duplicate UIDs (ie, a dud netscape server)
+ TMsvEntry* entryPtr;
+ TMsvEntry* nextEntryPtr;
+ for(a=1;a<noofchildren;a++)
+ {
+ if(iFolderIndex[a].iUid!=0 && iFolderIndex[a].iUid==iFolderIndex[a-1].iUid)
+ {
+ if(!aUseCachedEntryData)
+ {
+ // get the TMsvEntry for the message/folder
+ User::LeaveIfError(iEntry->GetEntryFromId(iFolderIndex[a].iMsvId,entryPtr));
+ User::LeaveIfError(iEntry->GetEntryFromId(iFolderIndex[a-1].iMsvId,nextEntryPtr));
+ // check if type of TMsvEntry and type of next TMsvEntry are both Messages
+ if( entryPtr->iType.iUid == nextEntryPtr->iType.iUid && entryPtr->iType.iUid == KUidMsvMessageEntryValue)
+ {
+ User::Leave(KErrCorrupt);
+ }
+ }
+ else
+ {
+ User::Leave(KErrCorrupt);
+ }
+ }
+
+ }
+
+#ifdef PRINTING
+ LogText(_L8("MakeSortedFolderIndex done: index list follows. children=%d"),noofchildren);
+ for(a=0;a<noofchildren;a++)
+ LogText(_L8(" MsvId=%8x UID=%d"),iFolderIndex[a].iMsvId,iFolderIndex[a].iUid);
+#endif
+ }
+
+HBufC* CImImap4Session::DoUnModUTF7LC(TDesC8& aBuffer)
+ {
+ iCharConv->PrepareToConvertToFromOurCharsetL(KCharacterSetIdentifierImapUtf7);
+
+ // unicode version won't be longer than the original (in chars)
+ HBufC* text=HBufC::NewL(aBuffer.Length());
+ CleanupStack::PushL(text);
+
+ //LogText(_L8("DoUnModUTF7LC: from %S"),&aBuffer);
+
+ TInt numUC, indexUC;
+ TPtr des = text->Des();
+ iCharConv->ConvertToOurCharsetL(aBuffer, des, numUC, indexUC);
+
+ //LogText(_L8("DoUnModUTF7LC: to %S len %d numUC %d"),&des, des.Length(), numUC);
+
+ return text;
+ }
+
+// Enquote a string (being sent as a string literal) if required
+void CImImap4Session::DoQuoteL(HBufC8*& aBuffer)
+ {
+ // Null string? Nothing to do
+ if (!aBuffer->Length() || !aBuffer->Des().Length()) return;
+
+ // Anything needing quoting in there?
+ if (aBuffer->Des().Locate('\\')==KErrNotFound &&
+ aBuffer->Des().Locate('\"')==KErrNotFound) return;
+
+ // Run through string, inserting quote characters as needed
+ for(TInt a=0;a<aBuffer->Des().Length();a++)
+ {
+ if (aBuffer->Des()[a]=='\\' || aBuffer->Des()[a]=='\"')
+ {
+ HBufC8 *newbuf=aBuffer->ReAllocL(aBuffer->Des().Length()+1);
+
+ // Been moved due to realloc?
+ if (newbuf!=aBuffer)
+ {
+ // In all cases when DoQuoteL() is called, the buffer is on the top of
+ // the cleanup stack: change this to indicate the correct entry
+ CleanupStack::Pop();
+ CleanupStack::PushL(aBuffer=newbuf);
+ }
+
+ aBuffer->Des().Insert(a,_L8("\\"));
+ a++;
+ }
+ }
+ }
+
+TInt CImImap4Session::FindFilename(const CImMimeHeader& aMimeInfo, TPtrC8& aFilename)
+ {
+ // Look in content-type list
+ const CDesC8Array& ctype=aMimeInfo.ContentTypeParams();
+
+ DBG((LogText(_L8("FindFilename: Checking %d entries in content-type list"),
+ ctype.Count())));
+
+ TInt tuple=0;
+ while(tuple<ctype.Count())
+ {
+#ifdef PRINTING
+ TPtrC8 t1=ctype[tuple],t2=ctype[tuple+1];
+ LogText(_L8(" %S %S"),&t1,&t2);
+#endif
+ // Look for "name xxx"
+ if (ctype[tuple].CompareF(KMIME_NAME)==0)
+ {
+ // Got it: report that we found it
+ aFilename.Set(ctype[tuple+1]);
+ TBuf8<KMaxFileName>buf(aFilename);
+ buf.Trim();
+ if(buf.Length()==0)
+ {
+ return(KErrNotFound);
+ }
+
+ return(KErrNone);
+ }
+ else if (ctype[tuple].CompareF(KMIME_NAME_RFC2231)==0)
+ {
+ // Got it: report that we found it
+ aFilename.Set(ctype[tuple+1]);
+ return(KErrRFC2231Encoded);
+ }
+ tuple+=2;
+ }
+
+ // Not found in the content type, try content disposition
+ tuple=0;
+ const CDesC8Array& cdisp=aMimeInfo.ContentDispositionParams();
+ while(tuple<cdisp.Count())
+ {
+#ifdef PRINTING
+ TPtrC8 t1=cdisp[tuple],t2=cdisp[tuple+1];
+ LogText(_L8(" %S %S"),&t1,&t2);
+#endif
+ // Look for "filename xxx"
+ if (cdisp[tuple].CompareF(KMIME_FILENAME)==0)
+ {
+ // Got it: report that we found it
+ aFilename.Set(cdisp[tuple+1]);
+ return(KErrNone);
+ }
+ else if (cdisp[tuple].CompareF(KMIME_FILENAME_RFC2231)==0)
+ {
+ // Got it: report that we found it
+ aFilename.Set(cdisp[tuple+1]);
+ return(KErrRFC2231Encoded);
+ }
+
+ tuple+=2;
+ }
+
+ // Didn't find it
+ return(KErrNotFound);
+ }
+
+void CImImap4Session::FindFilenameDecodeL(const CImMimeHeader& aMimeInfo, TFileName& aFileName)
+ {
+ // Make an attachment name
+ aFileName.Zero();
+
+ TPtrC8 origFileName;
+
+ // Look for filename in Content-Type list
+ TInt err = FindFilename(aMimeInfo, origFileName);
+ if (KErrNotFound == err)
+ {
+ // Fall back to simple "attachment" (language specific)
+ aFileName=iDefaultAttachmentName->Des();
+ }
+ else if (KErrRFC2231Encoded == err)
+ {
+ // A file name has been found but it is encoded (RFC2231)
+ // Use the default file name but append the file extension so that its type can be recognised
+ aFileName=iDefaultAttachmentName->Des();
+ TInt dotPos = origFileName.Length() - 1;
+ TBool dotFound = EFalse;
+
+ // Find the extension
+ while ((dotPos != 0) && (!dotFound))
+ {
+ if (origFileName[dotPos] == '.')
+ {
+ dotFound = ETrue;
+ // Extension found: append it to the filename
+ TInt extensionLength = origFileName.Length() - dotPos;
+ if ((aFileName.Length() + extensionLength) <= aFileName.MaxLength())
+ {
+ HBufC* extension = HBufC::NewLC(extensionLength);
+ extension->Des().Copy(origFileName.Right(extensionLength));
+ aFileName.Append(*extension);
+ CleanupStack::PopAndDestroy(extension);
+ }
+ }
+
+ --dotPos;
+ }
+ }
+ else
+ {
+ // Run it through the QP decoder
+ HBufC *decoded=HBufC::NewLC(origFileName.Length());
+ TPtr decoded_ptr(decoded->Des());
+
+ // Decode filename from the header
+ iHeaderConverter->DecodeHeaderFieldL(origFileName, decoded_ptr);
+
+ DBG((LogText(_L8("FindFilenameDecode: '%S' to '%S' "),&origFileName,&decoded_ptr)));
+
+ // Need to do a check on the filename length here.
+ // If it is too long, set to the max possible, keeping extension.
+ TFileName path;
+
+ TInt fileNameLength = path.Length() + decoded_ptr.Length();
+
+ if( fileNameLength > KMaxFileName)
+ {
+#ifdef __WINS__
+ TFileName winsFileName;
+ TFileName mailStoreDrive;
+ TDriveUnit drive(MessageServer::CurrentDriveL(iFs));
+ mailStoreDrive.Append(drive.Name());
+ mailStoreDrive.Append(KPathDelimiter);
+ MapEmulatedFileName(winsFileName, mailStoreDrive);
+ TInt prefixLen = winsFileName.Length();
+#else
+ TInt prefixLen = 0;
+#endif
+ // Crop the Old File Name
+ TInt lengthToCrop = (fileNameLength - KMaxFileName) + prefixLen;
+ // Use LocateReverse rather than TParsePtr as decoded_ptr may be > 256 chars
+ TInt dot = decoded_ptr.LocateReverse( '.' );
+ TPtrC extension = decoded_ptr.Mid(dot != KErrNotFound ? dot : decoded_ptr.Length());
+ TInt newFileNameLength = decoded_ptr.Length() - extension.Length() - lengthToCrop;
+ TPtrC newFileName=decoded_ptr.Left(newFileNameLength);
+
+ // Create the New File Name (ie File Name & Extension)
+ aFileName.Zero();
+ aFileName.Append(newFileName);
+ aFileName.Append(extension);
+ }
+ else
+ {
+ aFileName.Copy(decoded_ptr);
+ }
+ CleanupStack::PopAndDestroy(); // decoded
+ }
+ }
+
+// Update iDate in iMailboxId to show the time now (last sync time)
+void CImImap4Session::SyncCompleteL()
+ {
+ DBG((LogText(_L8("CImImap4Session::SyncCompleteL()"))));
+ // Find entry
+ SetEntryL(iMailboxId);
+ TMsvEmailEntry message=iEntry->Entry();
+
+ // Find 'now'
+ TTime now;
+ now.UniversalTime();
+ message.iDate=now;
+
+ // Check to see if there has been a change in the number of messages in the remote folder.
+ TBool folderSizeChanged=(message.RemoteFolderEntries()!=iMailboxSize);
+
+ // Set 'unread' flag on folder if there are any unread messages within it
+ if (FIXBOOL(message.Unread())!=iSomeUnread || !message.Visible() || folderSizeChanged)
+ {
+ // Update flags
+ message.SetUnread(iSomeUnread);
+ message.SetVisible(ETrue);
+ message.SetRemoteFolderEntries(iMailboxSize);
+ ChangeEntryBulkL(message);
+ }
+
+ // we need to ensure the hierarchy of folders containing this one
+ // is now visible. Note previously this incorrectly only did this
+ // when we were not in DisconncetedUserMode
+ do
+ {
+ // Move up one
+ SetEntryL(message.Parent());
+ message=iEntry->Entry();
+
+ // Ensure visibility
+ if (!message.Visible())
+ {
+ message.SetVisible(ETrue);
+ ChangeEntryL(message);
+ }
+ }
+ while(message.iType!=KUidMsvServiceEntry);
+
+ // Before we got back to the idle state, we need to commit any
+ // outstanding entries to the index file to complete the bulk
+ // synchronization operation
+ iEntry->CompleteBulk();
+
+ if (iReissueIdle)
+ {
+ iState=EImapStateSelected;
+ DoStartIdleL();
+ }
+ }
+
+// Reset subscription flags for all children, and recurse into folders
+void CImImap4Session::ResetSubscriptionFlagsL(const TMsvId aFolder)
+ {
+ // Do this one
+ SetEntryL(aFolder);
+ TMsvEmailEntry entry=iEntry->Entry();
+
+ // A folder or service? If not, return
+ if (entry.iType!=KUidMsvServiceEntry &&
+ entry.iType!=KUidMsvFolderEntry)
+ return;
+
+ // Reset flag if needed
+ if (entry.Subscribed())
+ {
+ // Reset flag and save
+ entry.SetSubscribed(EFalse);
+ ChangeEntryL(entry);
+ }
+
+ // Any children?
+ CMsvEntrySelection *children=new (ELeave) CMsvEntrySelection;
+ CleanupStack::PushL(children);
+ GetChildrenL(*children);
+ if (children->Count())
+ {
+ // Do each in turn
+ for(TInt child=0;child<children->Count();child++)
+ ResetSubscriptionFlagsL((*children)[child]);
+ }
+ CleanupStack::PopAndDestroy();
+ }
+
+TBool CImImap4Session::IsCancelling() const
+ {
+ return iCancelAndIdle;
+ }
+
+void CImImap4Session::CancelAndIdleL(TBool aReissueIdle)
+ {
+ // Flag that a cancel and idle command has been requested.
+ iCancelAndIdle = ETrue;
+
+ switch( iState )
+ {
+ case EImapStateFetchWait:
+ {
+ // Stop requesting fetches and wait for the current fetches to be completed.
+ iState = EImapStateFetchCancelWait;
+ iReissueIdle = aReissueIdle;
+
+ // Start an idle timer - this is ensure that if the GPRS session is currently
+ // suspended we don't hang forever waiting for the remaining fetch data to be
+ // received.
+ iCancelTimer->After(KImapFetchCancelIdleTime);
+
+ // Delete any partially downloaded attachments
+ if ( iAttachmentFileState == EFileIsOpen )
+ {
+ iAttachmentFileState=EFileIsIncomplete;
+ if(iCaf->Processing())
+ {
+ iCaf->EndProcessingL();
+ }
+ else
+ {
+ iAttachmentFile->CloseFile();
+ }
+ CMsvStore* store = iEntry->EditStoreL();
+ CleanupStack::PushL(store);
+ // Could be multiple attachments in the folder.
+ TInt i;
+ TInt attachmentCount = store->AttachmentManagerL().AttachmentCount();
+ for(i=0;i<attachmentCount;i++)
+ {
+ // Remove [0] as array is shuffled. Once index [n] is removed n+1 becomes n
+ store->AttachmentManagerExtensionsL().RemoveAttachmentL(0);
+ }
+ if(attachmentCount)
+ store->CommitL();
+ CleanupStack::PopAndDestroy(store);
+ TMsvEmailEntry message=iEntry->Entry();
+
+ CreateAttachmentInfoL((TMsvEmailEntry&)iEntry->Entry());
+ ChangeEntryBulkL(message);
+ }
+
+ DBG((LogText(_L8("CImImap4Session::CancelAndIdleL() - fetch cancel; continue to process requested fetches - no more parts to be requested"))));
+ } break;
+
+ case EImapStateIdleWait:
+ {
+ // Not much to do - already issued IDLE command, just wait for the IDLE
+ // command to 'start'.
+ // NOTE - set the iReissueIdle flag to ensure that IssueIdleRead is
+ // called when we get the server response.
+ iReissueIdle = aReissueIdle;
+
+ // Start an idle timer - this is ensure that if the GPRS session is currently
+ // suspended we don't hang forever waiting for the remaining data to be
+ // received.
+ iCancelTimer->After(KImapFetchCancelIdleTime);
+
+ DBG((LogText(_L8("CImImap4Session::CancelAndIdleL() - waiting for IDLE - continue"))));
+ } break;
+
+ case EImapStateIdling:
+ {
+ // Nothing much to do here - already IDLE-ing! Unset the cancel-and-idle flag
+ iCancelAndIdle = EFalse;
+ DBG((LogText(_L8("CImImap4Session::CancelAndIdleL() - already IDLE-ing - do nothing"))));
+ } break;
+
+ case EImapStateStopIdleWait:
+ {
+ // Need to wait for the current IDLE command to complete, then re-issue
+ // another one!!
+ iReissueIdle = aReissueIdle;
+
+ // Start an idle timer - this is ensure that if the GPRS session is currently
+ // suspended we don't hang forever waiting for the remaining data to be
+ // received.
+ iCancelTimer->After(KImapFetchCancelIdleTime);
+
+ DBG((LogText(_L8("CImImap4Session::CancelAndIdleL() - IDLE stop requested - wait for response and then re-issue IDLE command"))));
+ } break;
+
+ case EImapStateSelectWait:
+ {
+ // Waiting for select to complete - issue IDLE once select completes.
+ // A check will be made to ensure that the selected mailbox is the inbox.
+
+ iReissueIdle = aReissueIdle;
+
+ // Start an idle timer - this is ensure that if the GPRS session is currently
+ // suspended we don't hang forever waiting for the remaining data to be
+ // received.
+ iCancelTimer->After(KImapFetchCancelIdleTime);
+
+ DBG((LogText(_L8("CImImap4Session::CancelAndIdleL() - cancel select - wait for response and then issue IDLE command"))));
+ } break;
+
+ case EImapStateSelected:
+ {
+ // A mailbox has been selected - does IDLE need to be re-issued?
+ iReissueIdle = aReissueIdle;
+
+ if( iReissueIdle )
+ {
+ // Yep, need to re-issue the IDLE (as was IDLE-ing before).
+ // For this need to be in the INBOX as a writable-select.
+
+ // First reset counts to safe values here to avoid reporting left
+ // over values from previous fetch. Correct values will be set up
+ // once headers have been fetched and parts counted (taken from SelectL).
+ iProgress.iPartsToDo=iProgress.iBytesToDo=1;
+ iProgress.iPartsDone=iProgress.iBytesDone=0;
+
+ // Do the select (if we really need to) or skip if possible.
+ if( iMailboxId==GetInbox() && iMailboxWritable )
+ {
+ DBG((LogText(_L8("Need to re-issue IDLE command"))));
+
+ // No need to do the select - so re-issue the IDLE
+ DoStartIdleL();
+ }
+ else
+ {
+ DBG((LogText(_L8("Need to issue IDLE - first select Inbox (writable)"))));
+
+ // Looks like we need to do the select...
+ DoSelectL(GetInbox(), ETrue);
+ }
+ }
+ else
+ {
+ DBG((LogText(_L8("Do not re-issue IDLE - cancel completed"))));
+
+ // Cancelling completed - stay in this mailbox and do not issue idle.
+ iCancelAndIdle = EFalse;
+ }
+ } break;
+
+ case EImapStateMoveEntryWait:
+ {
+ // We're cancelling a move entry: we need to stop it specifically
+ iMoveEntry->Cancel();
+ iState=iSavedState;
+
+ // Recurse (yuk!) into method with the new 'saved' state.
+ CancelAndIdleL(aReissueIdle);
+ return;
+ }
+ // No break statement required due to return statement
+
+ default:
+ // For all other states - just do a 'normal' cancel - this will probably
+ // disconnect the session.
+ iCancelAndIdle = EFalse;
+ Cancel();
+ return;
+ }
+
+ // Need to complete the parent/observer.
+ CMsgActive::DoCancel();
+ }
+
+// Called when parent wants to cancel current operation
+void CImImap4Session::DoCancel()
+ {
+ DBG((LogText(_L8("CImImap4Session::DoCancel() called whilst in state %d"),iState)));
+
+ if(IsIdling())
+ {
+ iIdleRead->Cancel();
+ }
+ else
+ {
+ if(iAttachmentFile && iAttachmentFileState==EFileIsOpen)
+ {
+ DBG((LogText(_L8("CImImap4Session::DoCancel() closing attachment file"))));
+ if(iCaf->Processing())
+ {
+ TRAP_IGNORE(iCaf->EndProcessingL());
+ }
+ else
+ {
+ iAttachmentFile->CloseFile();
+ }
+ iAttachmentFileState=EFileNotOpen;
+ }
+
+ // What were we about to do?
+ switch(iState)
+ {
+ case EImapStateConnectWait:
+ case EImapStateGreetingWait:
+ case EImapStateLoginSendUser:
+ case EImapStateLoginSendPassword:
+ case EImapStateLoginWait:
+ DoDisconnect();
+ break;
+
+ case EImapStateSelectWait:
+ // Selecting: fail back to noselect
+ iState=EImapStateNoSelect;
+ break;
+
+ case EImapStateMoveEntryWait:
+ // We're cancelling a move entry: we need to stop it specifically
+ iMoveEntry->Cancel();
+ iState=iSavedState;
+ break;
+
+
+ default:
+ // Something else: disconnect for safety
+ DoDisconnect();
+ break;
+ }
+
+ // Note tag which we've cancelled: anything outstanding, basically,
+ // which means anything up to (and including) the last command issued,
+ // which is iTag
+ iCancelledTag=iTag;
+
+ iImapIO->Cancel();
+ }
+
+ iIdleTimer->Cancel();
+
+ DBG((LogText(_L8("CImImap4Session::DoCancel() finished 1"))));
+
+ // ...ask parent to finish up
+ CMsgActive::DoCancel();
+
+ DBG((LogText(_L8("CImImap4Session::DoCancel() finished 2"))));
+ }
+
+// Disconnect and complete with an error code
+void CImImap4Session::Fail(const TInt aError)
+ {
+ DBG((LogText(_L8("CImImap4Session::Fail(%d)"),aError)));
+ DoDisconnect();
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::Fail(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ Complete(aError);
+ }
+
+void CImImap4Session::DoDisconnect()
+ {
+ DBG(LogText (_L8("CImImap4Session::DoDisconnect()")));
+ iImapIO->Disconnect();
+ iState = EImapStateDisconnected;
+ iSecurityState = EUnknown;
+ iCommandsOutstanding = 0;
+ iSendQueued = EFalse;
+ iReceiveQueued = EFalse;
+ iReissueIdle = EFalse;
+ iCancelAndIdle = EFalse;
+ iIdleTimer->Cancel();
+ iIdleTimerExpired = EFalse;
+ }
+
+void CImImap4Session::DummyComplete(TInt aError)
+ {
+ DBG(LogText(_L8("+ CImImap4Session::DummyComplete(err=%d)"), aError));
+ if(aError >= KErrNone)
+ { // Got a response, let's have a look at it
+ CImapAtom* p=iImapIO->RootAtom()->Child();
+ if (!p)
+ {
+ aError = KErrNotFound;
+ return;
+ }
+
+ if (p->Compare(KIMAP_UNTAGGED))
+ {
+ // Process it, it could be something useful
+ TRAP( aError, ProcessUntaggedL(p->ToNextL(),EFalse) );
+ DBG( LogText(_L8("Dummy read untagged msg processed with %d"), aError) );
+ }
+ else
+ {
+ // Got a valid response that wasn't untagged... uhoh!
+ DBG( LogText(_L8("Dummy read received a non-untagged response!")) );
+ aError = KErrCorrupt;
+ }
+ }
+
+ if(aError < KErrNone)
+ {
+ // If the dummy read returned an error then the line was probably dropped,
+ // or, if the read returned something that was rubbish then the server is
+ // not playing by the rules.
+ // Either way, let's disconnect.
+ LostConnection(aError);
+
+ // Can't report the error, no one to report to
+ }
+ DBG( LogText(_L8("- CImImap4Session::DummyComplete()")) );
+ }
+
+void CImImap4Session::LostConnection(TInt /*aError*/)
+ {
+ // the line must have been dropped so call DoDisconnect to ensure state is up
+ // to date
+ DoDisconnect();
+
+ // mark service as offline immediately
+ // the returned error code ignored
+ if (iEntry->SetEntry(iServiceId))
+ {
+ TMsvEntry entry=iEntry->Entry();
+ entry.SetConnected(EFalse);
+ iEntry->ChangeEntry(entry);
+ }
+ }
+
+
+void CImImap4Session::IdleReadError(TInt aError)
+ {
+ // Read completed with an error, probably lost the connection
+ DBG(LogText(_L8("IMAP Idle outstanding read completed with %d"), aError));
+
+ LostConnection(aError);
+
+ // Stop the idle timer so that it does not try to restart the idle when it expires
+ iIdleTimer->Cancel();
+ }
+
+
+void CImImap4Session::IssueIdleRead()
+ {
+ __ASSERT_DEBUG(IsIdling() && !IsActive(), gPanic(EBadUseOfImap4Op));
+ DBG((LogText(_L8("Idle read issued"))));
+ iIdleRead->Start(iStatus);
+ DBG((LogText(_L8("******************************************************************"))));
+ DBG((LogText(_L8("CImImap4Session::IssueIdleRead(): waiting for iIdleRead to wake me"))));
+ DBG((LogText(_L8("******************************************************************"))));
+ SetActive();
+ }
+
+
+void CImImap4Session::IssueDummy()
+ {
+#ifdef ASYNC_NOTIFICATIONS
+ // Issue a dummy read from the CImapIO class so we can check the connection
+ // status. ONLY IF WE'RE CONNECTED!
+ if (iState>=EImapStateNoSelect)
+ {
+ iDummyRead->Start();
+ }
+#endif
+ }
+
+void CImImap4Session::CancelDummy()
+ {
+#ifdef ASYNC_NOTIFICATIONS
+ // Cancel it!
+ iDummyRead->Cancel();
+#endif
+ }
+
+void CImImap4Session::ReissueIdleL()
+ {
+ DBG((LogText(_L8("CImImap4Session::Re-issueIdle(): State: %d"), iState)));
+ Cancel();
+
+ DBG((LogText(_L8("CImImap4Session::ReIssueIdle(): setting iReissueIdle to true"))));
+ iReissueIdle=ETrue;
+ DoStopIdleL();
+ }
+
+void CImImap4Session::ReissueDummy()
+ {
+ IssueDummy();
+ }
+
+
+void CImImap4Session::DoComplete(TInt& aStatus)
+ {
+ DBG((LogText(_L8("CImImap4Session::DoComplete(iState=%d, aStatus=%d)"),iState,aStatus)));
+
+ if (iState == EImapStateSelected)
+ {
+ iCompoundStopIdle = EFalse;
+ iStoppingIdleForSync = EFalse;
+ }
+
+ if( iState != EImapStateFetchCancelWait &&
+ iAttachmentFile && iAttachmentFileState==EFileIsOpen )
+ {
+ // Do not close the attachment file if we're cancelling the fetch - we
+ // will still be receiving data and if this completes the attachment
+ // then we want the attachment file available for that.
+
+ DBG((LogText(_L8("CImImap4Session::DoComplete closing attachment file"))));
+ if(iCaf->Processing())
+ {
+ TRAP_IGNORE(iCaf->EndProcessingL());
+ }
+ else
+ {
+ iAttachmentFile->CloseFile();
+ }
+ iAttachmentFileState=EFileNotOpen;
+ }
+
+ // All ok?
+ if (aStatus==KErrNone)
+ {
+ // Everything is fine. However, we need to queue a dummy read from the
+ // CImapIO layer to ensure that we get notified if the connection dies
+ // unexpectedly
+
+ // Update the progress error code first.
+ iProgress.iErrorCode=aStatus;
+
+ if (ImapIdleSupported()==EFalse)
+ {
+ IssueDummy();
+ }
+
+ if (IsIdling())
+ {
+ IssueIdleRead();
+ }
+
+ return;
+ }
+
+ if( iCancelAndIdle )
+ {
+ // Record the error code and exit the method - ensure that we don't
+ // disconnect.
+ iProgress.iErrorCode=aStatus;
+ return;
+ }
+
+ // Some error has ocurred. Deal with it.
+ switch(iState)
+ {
+ case EImapStateCreateWait:
+ case EImapStateRenameWait:
+ case EImapStateDeleteWait:
+ case EImapStateSubscribeWait:
+ // A 'KErrIMAPNO' error isn't fatal to the connection
+ if (aStatus==KErrCancel)
+ {
+ // Back to previous state: these commands won't have
+ // disturbed it.
+ iState=iSavedState;
+ return;
+ }
+ else if (aStatus==KErrIMAPNO)
+ {
+ // Report error
+ if (iState == EImapStateDeleteWait)
+ iProgress.iErrorCode=KErrImapCantDeleteFolder;
+ else
+ iProgress.iErrorCode=KErrNotSupported;
+ aStatus=iProgress.iErrorCode;
+
+ // Back to previous state
+ iState=iSavedState;
+ return;
+ }
+
+ // Otherwise, process as per normal
+ break;
+
+ case EImapStateSelectWait:
+ case EImapStateSynchroniseWait:
+ // KErrIMAPNO isn't fatal, we just go back to the selected state
+ if (aStatus==KErrIMAPNO)
+ {
+ iState=EImapStateSelected;
+ return;
+ }
+ break;
+
+ case EImapStateMoveEntryWait:
+ // We're done with the moveentry
+
+ // Park the move entry again
+ iMoveEntry->SetEntry(NULL);
+ break;
+
+ case EImapStateIdleWait:
+ case EImapStateStopIdleWait:
+ if (iIdleTimerExpired)
+ {
+ // error has occurred following re-issue of an IDLE command
+ // Notify the server MTM that the error has occurred as there
+ // is no outstanding asynchonous request on this session.
+ // (IDLE is issued autonomously by the IMAP Session).
+ iObserver.NonCompletedFailure();
+ }
+ break;
+
+ case EImapStateFetchCancelWait:
+ // record the error (i.e. cancel) for progress and do not disconnect.
+ iProgress.iErrorCode=aStatus;
+ // drop through to next case... (as that is returning and so not disconnecting).
+ case EImapStateNoSelect:
+ case EImapStateSelected:
+ case EImapStateIdling:
+ return;
+
+ default:
+ break;
+ }
+
+ // If we get here with an error, then we need to disconnect.
+ // Earlier on, if there was a time when disconnection wasn't
+ // required, we would have returned.
+ DoDisconnect();
+
+ // Save error code in progress
+ iProgress.iErrorCode=aStatus;
+ }
+
+// Copy a message: in fact, we move the entire message to the destination, but
+// then recreate the empty shell (no parts fetched) of the source
+void CImImap4Session::CopyMessage(TRequestStatus& aRequestStatus, const TMsvId aSourceFolder, const TMsvId aSource, const TMsvId aDestinationFolder, TMsvId* aNewSource, const TBool aRemoveOriginal)
+ {
+ TInt err=KErrNone;
+ if (!Connected())
+ {
+ Queue(aRequestStatus);
+ err=KErrDisconnected;
+ }
+ else
+ TRAP(err,CopyMessageL(aRequestStatus, aSourceFolder, aSource, aDestinationFolder, aNewSource, aRemoveOriginal));
+ if (err!=KErrNone)
+ {
+ // park moveentry if it fails to get going
+ if (iMoveEntry)
+ iMoveEntry->SetEntry(NULL);
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::CopyMessage(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+ Complete(err);
+ }
+ }
+
+void CImImap4Session::CopyMessageL(TRequestStatus& aRequestStatus, const TMsvId aSourceFolder, const TMsvId aSource, const TMsvId aDestinationFolder, TMsvId* aNewSource, const TBool aRemoveOriginal)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND CopyMessage(%x (in %x) to %x)"),aSource,aSourceFolder,aDestinationFolder)));
+
+ Queue(aRequestStatus);
+
+ // Get a moveentry if we don't already have one
+ if (!iMoveEntry)
+ {
+ // Get a MoveEntry: we need to ask for one as a child of this entry, so
+ // move it to the root and ask for a child in the local service, which should
+ // always be there.
+ SetEntryL(KMsvRootIndexEntryId);
+
+ // Get child
+ iMoveEntry=iEntry->NewEntryL(KMsvLocalServiceIndexEntryId);
+ }
+
+ // Do the move, using the iMoveEntry CMsvServerEntry object, after parking our original
+ // one
+ iEntry->SetEntry(NULL);
+ User::LeaveIfError(iMoveEntry->SetEntry(aSourceFolder));
+
+ DBG((LogText(_L8("About to call iEntry->MoveEntry(%x,%x) when in folder %x"),aSource,aDestinationFolder,aSourceFolder)));
+
+ // We're moving - note bits for bottom half handling
+ iMoveSource=aSource;
+ iMoveSourceFolder=aSourceFolder;
+ iNewSource=aNewSource;
+
+ // Cancel any dummy operation that might be outstanding
+ if (ImapIdleSupported()==EFalse)
+ {
+ CancelDummy();
+ }
+
+ // Trap around this so we can park moveentry if it fails
+ //DS - now selectively either Copy or Move.
+ aRemoveOriginal?
+ iMoveEntry->MoveEntryL(aSource,aDestinationFolder,iStatus):
+ iMoveEntry->CopyEntryL(aSource,aDestinationFolder,iStatus);
+
+ // Move into the new state and go active
+ iSavedState=iState;
+ iState=EImapStateMoveEntryWait;
+ if (!IsActive()) SetActive();
+ }
+
+#if 0
+// Debug only: print out a representation of the parse tree
+void CImImap4Session::showtree(CImapAtom *root,int indent)
+ {
+ TInt b=0;
+
+ // Run through children
+ do
+ {
+ TPtrC8 atom=root->Atom();
+ LogText(_L8("%03d Sibling %d: '%S'"),indent,b++,&atom);
+
+ if (root->Child())
+ showtree(root->Child(),indent+2);
+
+ root=root->Next();
+ }
+ while(root);
+ }
+#endif
+
+// Parse greeting message
+TInt CImImap4Session::ProcessGreetingL()
+ {
+ CImapAtom* p=iRootAtom->ToChildL();
+
+ // Should be a '*'
+ if (!p->Compare(KIMAP_UNTAGGED))
+ User::Leave(KErrGeneral);
+
+ // Greeting line can be:
+ // * BYE ... (server busy)
+ // * PREAUTH ... (no login needed)
+ // * OK ... (normal)
+ p=p->ToNextL();
+ if (p->Compare(KIMAP_BYE))
+ {
+ // Server is busy
+ return(KErrImapServerBusy);
+ }
+ else if (p->Compare(KIMAP_PREAUTH))
+ {
+ // Already authorised, straight into Noselect
+ iSavedState=EImapStateNoSelect;
+ }
+ else if (p->Compare(KIMAP_OK))
+ {
+ // Need to login
+ iSavedState=EImapStateLoginWait;
+
+ // Is this a CC:Mail server? (paranoid mode)
+ if (p->Next())
+ {
+ if (p->Next()->Compare(_L8("CC:Mail")))
+ {
+ // We are, note it.
+ iTalkingToCCMail=ETrue;
+
+ DBG((LogText(_L8("We're talking to a CC:Mail server, modified fetch strategy enabled."))));
+ }
+ else if (p->Next()->Compare(_L8("OpenMail")) )
+ {
+ iTalkingToOpenMail=ETrue;
+ DBG((LogText(_L8("We're talking to an OpenMail server, modified fetch strategy enabled."))));
+ }
+ }
+ }
+
+ // Looks ok
+ return(KErrNone);
+ }
+
+// Parse select reply messages
+TInt CImImap4Session::ProcessCommandReplyL()
+ {
+ CImapAtom *p=iRootAtom->ToChildL();
+
+ // Which command does this reply belong to?
+ TInt thisis=iTag-(iCommandsOutstanding-1);
+
+ // A cancelled command?
+ TBool cancelled(EFalse);
+ if (thisis<=iCancelledTag)
+ {
+ cancelled=ETrue;
+ }
+
+ // '+' indicates ideling
+ if (p->Compare(KIMAP_CONTINUATION))
+ {
+ return(ProcessContinuationResponse(p->ToNextL()));
+ }
+
+ // '*' indicates an untagged message
+ if (p->Compare(KIMAP_UNTAGGED))
+ {
+ // Process it
+ return(ProcessUntaggedL(p->ToNextL(),cancelled));
+ }
+
+ // If we got here, it's a tagged reply.
+ // Check it's the one we're expecting
+ TInt tag(0);
+ TInt error(p->Value(tag));
+ if (error!=KErrNone)
+ {
+ // Problem parsing
+ return error;
+ }
+
+ // Some command sequencing debugging
+ DBG((LogText(_L8("Expecting tag %d, got tag %d"),thisis,tag)));
+
+ // One less outstanding command
+ iCommandsOutstanding--;
+
+ // If the tagged reply is for a command that had been cancelled,
+ // and there are still commands outstanding, then don't complete:
+ // instead, just keep reading replies.
+ if (tag < iCancelledTag + 1)
+ {
+ return KErrNotReady;
+ }
+
+ // Move on to result
+ p=p->ToNextL();
+
+#ifdef PRINTING
+ // Print success of failure
+ TPtrC8 n=p->Atom();
+ LogText(_L8("Result for command tag %d is '%S'"),iTag,&n);
+#endif
+ // If it's OK, pass it to untagged processor
+ if (p->Compare(KIMAP_OK))
+ {
+ // It might have stuff like 'READ-WRITE' in it... but only if
+ // the next atom has a child (ie open bracket)
+ if (p->Next() && p->Next()->Child())
+ {
+ DBG((LogText(_L8("CImap4Session::ProcessCommandReply(): OK recieved with children"))));
+ // Ignore the return code: we've got our tagged reply!
+ ProcessUntaggedL(p,EFalse);
+ }
+ else
+ {
+ // received ok response
+ DBG((LogText(_L8("CImap4Session::ProcessCommandReply(): OK received - no children"))));
+ }
+ return(KErrNone);
+ }
+ else if (p->Compare(KIMAP_NO))
+ {
+ // The server didn't like this
+ return(KErrIMAPNO);
+ }
+
+ // It's not OK: there's been an error
+ return(KErrGeneral);
+ }
+
+// Parse Continuation Response
+TInt CImImap4Session::ProcessContinuationResponse(CImapAtom* /*aAtom*/)
+ {
+ if (iState==EImapStateIdleWait)
+ {
+ iState = EImapStateIdling;
+ if(!iIdleTimer->IsActive())
+ {
+ iIdleTimer->After(iIdleTimeout);
+ }
+ return KErrNone;
+ }
+ else
+ {
+ return KErrArgument;
+ }
+ }
+
+// Parse untagged messages
+TInt CImImap4Session::ProcessUntaggedL(CImapAtom *aAtom, const TBool aCancelled)
+ {
+ DBG((LogText(_L8("CImap4Session::ProcessUntaggedL(): running..."))));
+
+ CImapAtom *p=aAtom;
+
+ // Look at first atom
+ if (static_cast<TChar>(p->Atom()[0]).IsDigit())
+ {
+ // First atom is a number
+ TUint msgnr(0);
+
+ // Got it ok?
+ if (p->Value(msgnr)!=KErrNone)
+ User::Leave(KErrArgument);
+
+ // Next atom will be one of:
+ // EXISTS, RECENT, FETCH, etc
+ p=p->ToNextL();
+ if (p->Compare(KIMAP_EXISTS))
+ {
+ // Mailbox size changed?
+ DBG((LogText(_L8("Mailbox size now %d was %d"),msgnr,iMailboxSize)));
+
+ // Note it
+ if (iMailboxSize != static_cast<TInt>(msgnr) )
+ {
+ // Set it to EXISTS
+ iMailboxSize=msgnr;
+
+ // if the EXISTS didn't report a change in size then
+ // pretend it wasn't received
+ iMailboxReceivedExists=ETrue;
+ }
+
+ // Resize index
+ iFolderIndex.SetSizeL(iMailboxSize);
+ }
+ else if (p->Compare(KIMAP_EXPUNGE))
+ {
+ // Note it
+ iMailboxReceivedExpunge=ETrue;
+ }
+ else if (p->Compare(KIMAP_RECENT))
+ {
+ // Note it
+ iMailboxRecent=msgnr;
+ }
+ else if (!aCancelled && p->Compare(KIMAP_FETCH))
+ {
+ // Process fetch: any fetch data following?
+ if (p->Next() && p->Next()->Child())
+ {
+ // Got an open bracket situation, looks good
+ return(ProcessFetchL(msgnr,p->Next()->Child()));
+ }
+ else
+ User::Leave(KErrGeneral);
+ }
+ else
+ {
+#ifdef PRINTING
+ // Unknown
+ TPtrC8 a=p->Atom();
+ LogText(_L8("Unknown reply '* %d %S'"),msgnr,&a);
+#endif
+ }
+ }
+ else
+ {
+ // First atom not a number. Is it OK?
+ if (p->Compare(KIMAP_OK))
+ {
+ // OK *can* be followed by bracketed attrib or attrib/value pair
+ // however, this is not always the case: for example, EXAMINE'ing
+ // a new folder with Netscape IMAP4rev1 Service 3.56 gives this
+ // response:
+ // 24/01/99 13:44:58 >> 53 EXAMINE "Thingy/trevor"
+ // 24/01/99 13:44:59 << * OK Reset UID sequence counter.
+ // This is totally legal in the spec, but not awfully useful to us as
+ // plain text messages are server-specific and are really for carbon-
+ // based lifeforms to read.
+ if ((p=p->Next())==NULL)
+ {
+ // No message. Just '* OK'. What a pointless waste of bandwidth.
+ return(KErrNotReady);
+ }
+
+ // Is this the start of a bracketed construct?
+ if (p->Compare(_L8("(")) ||
+ p->Compare(_L8("[")))
+ {
+ CImapAtom* child=p->ToChildL();
+
+ if (child->Compare(KIMAP_UIDVALIDITY))
+ {
+ // Save it
+ child=child->ToNextL();
+ if (child->Value(iUidValidity)!=KErrNone)
+ User::Leave(KErrArgument);
+ }
+ else if (child->Compare(KIMAP_UIDNEXT))
+ {
+ // Save it
+ child=child->ToNextL();
+ if (child->Value(iUidNext)!=KErrNone)
+ User::Leave(KErrArgument);
+ }
+ else if (child->Compare(KIMAP_READWRITE))
+ {
+ // Note read-write open
+ iMailboxWritable=ETrue;
+ }
+ else if (child->Compare(KIMAP_READONLY))
+ {
+ // Note read-only open
+ iMailboxWritable=EFalse;
+ }
+ else if (child->Compare(KIMAP_ALERT))
+ {
+ // alerts to be handled here, but return with no error until then
+ LogText(_L8("Alert received- SessionState:iState %d"),iState);
+ if(iState==EImapStateIdling)
+ {
+ return KErrNone;
+ }
+ }
+ else
+ {
+#ifdef PRINTING
+ TPtrC8 unk=p->Atom();
+ LogText(_L8("* OK [%S ???]"),&unk);
+#endif
+ }
+ }
+ else
+ {
+#ifdef PRINTING
+ TPtrC8 unk=p->Atom();
+ LogText(_L8("* OK %S ???"),&unk);
+#endif
+
+ if(iState==EImapStateIdling)
+ {
+ return(KErrNotReady);
+ }
+ }
+ }
+ else if (!aCancelled && p->Compare(KIMAP_LIST))
+ {
+ // Ignore it unless we've got somewhere to save it
+ ProcessListL(p->ToNextL());
+ }
+ else if (!aCancelled && p->Compare(KIMAP_LSUB))
+ {
+ // Process subscription list reply
+ ProcessLsubL(p->ToNextL());
+ }
+ else if (!aCancelled && p->Compare(KIMAP_SEARCH))
+ {
+ // Process UID search reply
+ // Need to check that we actually received a list of UIDs in the response
+ // because under certain circumstances we can get a reply that lists no UIDs.
+ // This can happen if the search command specifies a set of UIDs but none
+ // of them can be found on the server because they have all been expunged.
+ // It can also happen if the search command includes a search string but
+ // no messages on the server match it.
+ if (p->Next())
+ {
+ ProcessSearchL(p->ToNextL());
+ }
+ }
+ else if (p->Compare(KIMAP_BYE))
+ {
+ // Are we already logging out?
+ if (iState!=EImapStateLogoutWait)
+ {
+ // Unexpected disconnection
+ // WRITE!
+ }
+ }
+ else if (p->Compare(KIMAP_NO))
+ {
+#ifdef PRINTING
+ // NO message from server. Display it.
+ LogText(_L8("Got NO:"));
+ while((p=p->Next())!=NULL)
+ {
+ TPtrC8 word(p->Atom());
+ LogText(_L8(" %S"),&word);
+ }
+#endif
+ if(iState==EImapStateIdling)
+ {
+ // ignore this and remain in IDLE.
+ return KErrNotReady;
+ }
+ }
+ else if (!aCancelled && p->Compare(KIMAP_FLAGS))
+ {
+ // FLAGS response during folder open
+ iMailboxReceivedFlags=ETrue;
+#ifdef PRINTING
+ LogText(_L8("Got FLAGS:"));
+ p=p->ToNextL();
+ p=p->ToChildL();
+ do
+ {
+ TPtrC8 word(p->Atom());
+ LogText(_L8(" %S"),&word);
+ p=p->Next();
+ }
+ while(p!=NULL);
+#endif
+ }
+ else if (p->Compare(KIMAP_CAPABILITY))
+ {
+ // clear here just for good measure
+ iSeenVersion=EFalse;
+ iCapabilityIdleSupport = EFalse;
+ iCapabilityStartTLS=EFalse;
+ iCapabilityLoginDisabled=EFalse;
+
+ // CAPABILITY reply
+ while((p=p->Next())!=NULL)
+ {
+ if (p->Compare(KIMAP_VERSION))
+ iSeenVersion=ETrue;
+ else if (p->Compare(KIMAP_IDLE))
+ iCapabilityIdleSupport = ETrue;
+ else if (p->Compare(KIMAP_STARTTLS))
+ iCapabilityStartTLS=ETrue;
+ else if (p->Compare(KIMAP_LOGINDISABLED))
+ iCapabilityLoginDisabled=ETrue;
+ }
+ }
+ else
+ {
+#ifdef PRINTING
+ // Unknown
+ TPtrC8 a=p->Atom();
+ LogText(_L8("Unknown reply '* %S'"),&a);
+#endif
+ }
+ }
+
+ return(KErrNotReady);
+ }
+
+// Fill in a CImHeader from an envelope atom
+void CImImap4Session::ProcessEnvelopeL(CImHeader* aHeader, TMsvEntry& aEntry, CImapAtom *aAtom)
+ {
+ CImapAtom *q=aAtom->ToChildL();
+ TPtrC8 tptr;
+ // ensure that nothing is placed on the cleanup stack between here
+ // and calls to ProcessAddress/ProcessAddressList
+ HBufC8 *address=HBufC8::NewLC(KImapAddressSizeInc);
+
+ DBG((LogText(_L8("Processing envelope data"))));
+
+ // Parse date information
+ tptr.Set(q->Atom());
+ TImRfc822DateField date;
+ date.ParseDateField(tptr,aEntry.iDate);
+ q=q->ToNextL();
+
+ // Subject in CImHeader (TMsvEntry is later on after post-processing)
+ if (!q->Compare(KIMAP_NIL))
+ aHeader->SetSubjectL(q->Atom());
+ q=q->ToNextL();
+
+ // From information: both in CImHeader and TMsvEntry
+ if (q->Child())
+ {
+ DBG((LogText(_L8("Processing 'From' information"))));
+
+ ProcessAddressL(&address,q->ToChildL());
+ aHeader->SetFromL(address->Des());
+ }
+ else
+ {
+ // No From information. Set blank
+ aHeader->SetFromL(_L(""));
+ }
+ q=q->ToNextL();
+
+ // Discard sender information
+ q=q->ToNextL();
+
+ // ReplyTo information
+ if (q->Child())
+ {
+ DBG((LogText(_L8("Processing 'ReplyTo' information"))));
+
+ // Replyto exists
+ ProcessAddressL(&address,q->ToChildL());
+ aHeader->SetReplyToL(address->Des());
+ }
+ else
+ {
+ // No replyto. Use From info
+ aHeader->SetReplyToL(aHeader->From());
+ }
+ q=q->ToNextL();
+
+ // To information
+ DBG((LogText(_L8("Processing 'To' information"))));
+
+ ProcessAddressListL(&address,aHeader->ToRecipients(),q->Child());
+ q=q->ToNextL();
+
+ // CC list
+ DBG((LogText(_L8("Processing 'CC' information"))));
+
+ ProcessAddressListL(&address,aHeader->CcRecipients(),q->Child());
+ q=q->ToNextL();
+
+ // BCC list
+ DBG((LogText(_L8("Processing 'BCC' information"))));
+
+ ProcessAddressListL(&address,aHeader->BccRecipients(),q->Child());
+ q=q->ToNextL();
+
+ // In-Reply-To
+ q=q->ToNextL();
+
+ // Message-Id
+ aHeader->SetImMsgIdL(q->AtomNoAngleBrackets());
+
+ // Decode any QP encoding in header fields
+ iHeaderConverter->DecodeAllHeaderFieldsL(*aHeader);
+
+ // Set from line in TMsvEntry
+ aEntry.iDetails.Set(aHeader->From());
+
+ // Set subject in TMsvEntry
+ aEntry.iDescription.Set(aHeader->Subject());
+
+ // Get rid of buffer
+ CleanupStack::PopAndDestroy();
+
+ DBG((LogText(_L8("Finished processing envelope information"))));
+ }
+
+void CImImap4Session::StripSpace(HBufC8* aBuf)
+ {
+ TInt len = aBuf->Length();
+ TInt in = 0;
+ TInt out = 0;
+ TPtr8 p = aBuf->Des();
+ while (in < len)
+ {
+ TUint8 c = p[in++];
+ if (c > ' ')
+ p[out++] = c;
+ }
+ // we could shrink the buffer here but we won't bother because it
+ // is going to get copied and freed anyway
+ }
+
+// Fill in a CImHeader from the extra header fields atoms, currently
+// Priority and Receipt info. aText is an extract direct from the
+// message header, ie lines of name: value\r\n terminated with an
+// empty line. Not known whether the lines can be folded so assume
+// they may.
+void CImImap4Session::ProcessHeaderExtraL(CImHeader* aHeader, CImMimeHeader* aMimeHeader, TMsvEmailEntry* aEntry, TPtrC8 aText)
+ {
+#ifdef _DEBUG
+ TPtrC8 dump = aText.Left(256);
+ DBG((LogText(_L8("Processing HeaderExtra data '%S'"), &dump)));
+#endif
+
+ // utils class
+ CImcvUtils* utils=CImcvUtils::NewLC();
+
+ TPtrC8 line = aText;
+ HBufC8* valueBuf = NULL;
+
+ TPtrC8 name;
+
+ TBool foundReplyToPrompt = EFalse;
+ // Check for content-type Application/xxx
+ // There may be a CAF agent ready to consume the content if it's DRM
+ // If aMimeHeader is set then this is the mime header prior to the actual mime section download
+ if(aMimeHeader && aMimeHeader->ContentType().MatchF(KImcvApplication) == 0)
+ {
+ // CAF registration requires concatenated content-type and subtype
+ // The type and subtype have been received and stored.
+ // Create buffer for concatenating. + 1 creates space for '/'
+ HBufC8* buf = HBufC8::NewLC(aMimeHeader->ContentSubType().Length() + aMimeHeader->ContentType().Length() + 1);
+ TPtr8 ptr(buf->Des());
+ ptr.Copy(aMimeHeader->ContentType());
+ ptr.Append(KImcvForwardSlash);
+ ptr.Append(aMimeHeader->ContentSubType());
+ // Registration does not necessarily succeed but we don't care at this point.
+ iCaf->RegisterL(ptr);
+ CleanupStack::PopAndDestroy(buf);
+ }
+
+ while (line.Length())
+ {
+ TBool processPrevious = valueBuf != NULL;
+ TPtrC8 current;
+
+ TInt len = line.Find(KImcvCRLF);
+ if (len > 0)
+ {
+ // split line into this one and the rest
+ current.Set(line.Left(len));
+ line.Set(line.Mid(len+2));
+
+ // handle folded headers
+ if (current[0] <= ' ' && valueBuf)
+ {
+ HBufC8* buf=valueBuf->ReAllocL( valueBuf->Length() + current.Length() );
+ if (buf!=valueBuf)
+ {
+ CleanupStack::Pop();
+ CleanupStack::PushL(valueBuf=buf);
+ }
+ valueBuf->Des().Append( current );
+
+ processPrevious = EFalse;
+ }
+ }
+ else
+ {
+ // set line to null
+ line.Set(line.Left(0));
+ }
+
+ // find matching headers, can only be set if valueBuf was
+ // non-null
+ if (processPrevious)
+ {
+ // Dont put the following line back in as will cause a panic if the subject
+ // field is too long. Defect EXT-53KD67.
+ //DBG((LogText(_L8("header: name %S value %S"), &name, valueBuf)));
+
+ if (aEntry)
+ {
+ CDesC8ArrayFlat* array = new(ELeave) CDesC8ArrayFlat(4);
+ CleanupStack::PushL(array);
+ CImcvUtils::PriorityFieldsL(*array);
+ for (TInt i(0); i<array->Count(); i++)
+ {
+ if(name.CompareF((*array)[i])==0)
+ {
+ aEntry->SetPriority(utils->EvaluatePriorityText(*valueBuf));
+ }
+ }
+ CleanupStack::PopAndDestroy(array);
+ }
+
+ if (aHeader)
+ {
+ CImcvUtils* imcvUtils = CImcvUtils::NewLC();
+ if (imcvUtils->EvaluateReturnReceiptFields(name))
+ {
+ aHeader->SetReceiptAddressL(*valueBuf);
+
+ // Only set Receipt flag if this email has not
+ // been 'seen' by somebody - to prevent multiple
+ // notifications
+ if (!aEntry->SeenIMAP4Flag())
+ aEntry->SetReceipt(ETrue);
+ }
+
+ if((name.CompareF(KImcvFromPrompt))==0)
+ {
+ // Set from line in TMsvEntry
+ aHeader->SetFromL(*valueBuf);
+ }
+ else if((name.CompareF(KImcvSubjectPrompt))==0)
+ {
+ // Set subject in TMsvEntry
+ aHeader->SetSubjectL(*valueBuf);
+ }
+ else if((name.CompareF(KImcvDatePrompt))==0)
+ {
+ if(!iParsedTime)
+ {
+ // Set date in TMsvEntry
+ TImRfc822DateField date;
+ date.ParseDateField(*valueBuf,aEntry->iDate);
+ }
+ }
+ else if((name.CompareF(KImcvReceivedPrompt))==0)
+ {
+ if(!iParsedTime)
+ {
+ // Set date in TMsvEntry
+ TImRfc822DateField date;
+
+ //remove the data before the comma, to just leave the date
+ TPtr8 ptr(valueBuf->Des());
+ TInt lPos=ptr.Locate(';');
+ ptr = ptr.Right(ptr.Length()-lPos-2);
+ date.ParseDateField(ptr,aEntry->iDate);
+ iParsedTime=ETrue;
+ }
+ }
+ else if((name.CompareF(KImcvReplyToPrompt))==0)
+ {
+ aHeader->SetReplyToL(*valueBuf);
+ foundReplyToPrompt = ETrue;
+ }
+ else if((name.CompareF(KImcvMessageIdPrompt))==0)
+ aHeader->SetImMsgIdL(*valueBuf);
+ else if((name.CompareF(KImcvToPrompt))==0)
+ ProcessAddressListL(aHeader->ToRecipients(), &valueBuf);
+ else if((name.CompareF(KImcvCcPrompt))==0)
+ ProcessAddressListL(aHeader->CcRecipients(), &valueBuf);
+ else if((name.CompareF(KImcvBccPrompt))==0)
+ ProcessAddressListL(aHeader->BccRecipients(), &valueBuf);
+
+ CleanupStack::PopAndDestroy(); // imcvUtils
+
+ // we are currently ignoring DispositionOptions as
+ // there is nowhere to store it
+ }
+
+ if (aMimeHeader)
+ {
+ // Check to see if this extra header data should be passed to the CAF agent
+ if(iCaf->Registered())
+ {
+ iCaf->AddToMetaDataL(name,valueBuf->Des());
+ }
+ if (name.CompareF(KImcvContentBase) == 0)
+ {
+ StripSpace(valueBuf);
+ aMimeHeader->SetContentBaseL(*valueBuf);
+ }
+ else if (name.CompareF(KImcvContentLocation) == 0)
+ {
+ StripSpace(valueBuf);
+
+ HBufC *decoded=HBufC::NewLC(valueBuf->Length());
+ TPtr decoded_ptr(decoded->Des());
+
+ iHeaderConverter->DecodeHeaderFieldL(*valueBuf,decoded_ptr);
+ aMimeHeader->SetContentLocationL(*decoded);
+ CleanupStack::PopAndDestroy(); // decoded
+ }
+ }
+
+ CleanupStack::PopAndDestroy(); // valueBuf
+ valueBuf = NULL;
+ }
+
+ if (current.Length() && current[0] > ' ')
+ {
+ // split this line into name and value
+ TInt colon = current.Locate(':');
+
+ name.Set(current.Left(colon+1)); // include the colon
+ TPtrC8 value = current.Mid(colon+1);
+
+ // skip any initial WS in the value
+ while (value.Length() != 0 && value[0] <= ' ')
+ value.Set(value.Mid(1));
+
+ valueBuf = value.AllocLC();
+ }
+ }
+
+ if (aHeader)
+ {
+ // If no reply to information, use the From value
+ if (!foundReplyToPrompt)
+ {
+ aHeader->SetReplyToL(aHeader->From());
+ }
+
+ // Decode any QP encoding in header fields
+ iHeaderConverter->DecodeAllHeaderFieldsL(*aHeader);
+
+ // Set from line in TMsvEntry
+ aEntry->iDetails.Set(aHeader->From());
+
+ // Set subject in TMsvEntry
+ aEntry->iDescription.Set(aHeader->Subject());
+ }
+
+ // just in case
+ if (valueBuf)
+ CleanupStack::PopAndDestroy(); // valueBuf
+
+ // pop off the items allocated
+ CleanupStack::PopAndDestroy(); // utils
+
+ DBG((LogText(_L8("Finished processing HeaderExtra"))));
+ }
+
+// adapted this function from the one in CImRecvConvert
+void CImImap4Session::GetDefaultFilename(TDes& aName, const TMsvEmailEntry& aMessage, const CImMimeHeader* mime)
+ {
+ aName = *iDefaultAttachmentName;
+
+ // Add on appropriate extension
+ if (aMessage.iType == KUidMsvEmailTextEntry)
+ {
+ aName.Append(KTextExtension);
+ }
+ else if (aMessage.MHTMLEmail())
+ {
+ aName.Append(KHtmlExtension);
+ }
+ else if (aMessage.VCard() || aMessage.VCalendar())
+ {
+ aName.Append(KVCardExtension);
+ }
+ else if (aMessage.ICalendar())
+ {
+ aName.Append(KICalExtension);
+ }
+ else if ( aMessage.iType == KUidMsvAttachmentEntry )
+ {
+ if ( (mime->ContentSubType()==KImcvBmp) ||
+ (mime->ContentSubType()==KImcvGif) ||
+ (mime->ContentSubType()==KImcvJpeg) ||
+ (mime->ContentSubType()==KImcvTiff) ||
+ (mime->ContentSubType()==KImcvWav) )
+ {
+ TBuf<KMaxExtensionLength> buf;
+ buf.Copy(mime->ContentSubType());
+ aName.Append(KImcvFullStop);
+ aName.Append(buf);
+ }
+ }
+ }
+
+TBool CImImap4Session::DoesAtomContainAttachment(CImapAtom *aAtom)
+// Check through all of this Atom's Siblings to see if they contain an attachment
+ {
+ TBool hasAttachment = EFalse;
+ CImapAtom* currentAtom = aAtom;
+
+ // Search through all of the Sibling Atoms
+ while (currentAtom != NULL)
+ {
+ // Check if there is a Child Atom with an Attachment
+ if (currentAtom->Child() != NULL)
+ {
+ if (currentAtom->Child()->Compare(KMIME_ATTACHMENT))
+ {
+ // This Sibling contains an Attachment.
+ hasAttachment = ETrue;
+ break;
+ }
+ }
+
+ // Move onto the next sibling
+ currentAtom = currentAtom->Next();
+ }
+
+ return hasAttachment;
+ }
+
+// Build a single entry
+void CImImap4Session::BuildTreeOneL(const TMsvId aParent, CImapAtom *aAtom, const TDesC8& aPath,
+ const TMsvId aThisMessage, TInt& aAttachments, TBool& aIsMHTML, TInt& aRelatedAttachments)
+ {
+ DBG((LogText(_L8("BuildTreeOneL(message=%x, parent=%x)"),aThisMessage,aParent)));
+
+ // First, is this actually an entry, or another level of nesting?
+ if (aAtom->Child())
+ {
+ // Another level of nesting? Call BuildTreeL()
+ BuildTreeL(aParent,aAtom,aPath,aThisMessage,aAttachments,aIsMHTML, aRelatedAttachments);
+ return;
+ }
+
+ // Skeleton for new entry
+ SetEntryL(aParent);
+
+ TFileName attachmentFilename; // DS somewhere to store an attachment filename
+ TMsvEmailEntry message;
+ message.iSize=0;
+ message.iMtm=KUidMsgTypeIMAP4;
+ message.iServiceId=iServiceId;
+ message.SetUID(iMessageUid);
+ message.SetValidUID(ETrue);
+ message.SetComplete(EFalse);
+
+ // Reply from server is in this form:
+ // TYPE SUBTYPE (PARAM1 VALUE1 ...) ID DESCRIPTION ENCODING OCTETS
+ //
+ // Text parts:
+ // TYPE SUBTYPE (PARAM1 VALUE1 ...) ID DESCRIPTION ENCODING OCTETS NLINES
+
+ // Save mime TYPE/SUBTYPE
+ CImMimeHeader *mime=CImMimeHeader::NewLC();
+ CImapAtom *type=aAtom;
+ CImapAtom *subtype=aAtom->Next();
+ mime->SetContentTypeL(type->Atom());
+ mime->SetContentSubTypeL(subtype->Atom());
+
+#ifdef PRINTING
+ TPtrC8 mt=type->Atom(),ms=subtype->Atom();
+ LogText(_L8(" MIME type %S/%S"),&mt,&ms);
+#endif
+
+ // We start by assuming the data will be stored as a binary
+ // attachment
+ message.iType=KUidMsvAttachmentEntry;
+ if (type->Compare(KMIME_TEXT))
+ {
+ // text/html?
+ if (subtype->Compare(KMIME_HTML))
+ {
+ // If this Atom doesn't contain an Attachment, then this is a MHTML Message.
+ if (!DoesAtomContainAttachment(subtype))
+ {
+ message.iType=KUidMsvEmailHtmlEntry;
+ aIsMHTML=ETrue;
+ }
+ }
+ // text/x-vcard?
+ else if (subtype->Compare(KMIME_XVCARD))
+ {
+ // Set vCard flag in message
+ message.SetVCard(ETrue);
+
+ // Defaults to binary
+ }
+ // text/x-vcalendar
+ else if (subtype->Compare(KMIME_VCALENDAR))
+ {
+ // Set vCalendar flag in message
+ message.SetVCalendar(ETrue);
+ iIsVCalendar = ETrue;
+
+ // Defaults to binary
+ }
+ // text/calendar
+ else if (subtype->Compare(KMIME_ICALENDAR))
+ {
+ // Set iCalendar flag in message
+ message.SetICalendar(ETrue);
+ iIsICalendar = ETrue;
+
+ // Defaults to binary
+ }
+ else
+ message.iType=KUidMsvEmailTextEntry;
+ }
+
+ // ...and mime path
+ mime->SetRelativePathL(aPath);
+
+ DBG((LogText(_L8(" MIME path %S"),&aPath)));
+
+ // Parameter list
+ CImapAtom *parameter=subtype->ToNextL();
+
+ TUint charset = KUidMsvCharsetNone;
+
+ // Store parameter stuff
+ if (!parameter->Compare(KIMAP_NIL))
+ {
+ DBG((LogText(_L8(" Parameter list:"))));
+
+ // Process list
+ CImapAtom *type_param=parameter->ToChildL();
+ while(type_param && type_param->Next())
+ {
+ CImapAtom *type_value;
+ type_value=type_param->ToNextL();
+
+ // All items are 2-tuples (parameter value (...)): get both, and store
+ TPtrC8 param=type_param->Atom();
+ TPtrC8 value=type_value->Atom();
+
+ DBG((LogText(_L8(" %S %S"),¶m,&value)));
+
+ mime->ContentTypeParams().AppendL(param);
+ mime->ContentTypeParams().AppendL(value);
+
+ // Have we come across a 'NAME' tuple? If so, force the MIME type of this
+ // entry to be an attachment.
+ if ((param.CompareF(KMIME_NAME)==0)
+ || (param.CompareF(KMIME_NAME_RFC2231) == 0))
+ {
+ DBG((LogText(_L8("It has an attachment filename, therefore this is an attachment"))));
+
+ FindFilenameDecodeL(*mime,attachmentFilename);
+ StripIllegalCharactersFromFileName(attachmentFilename);
+ message.iDetails.Set(attachmentFilename);
+
+ // If embedded message do not save as an attachment
+ if (message.iType!=KUidMsvMessageEntry)
+ message.iType=KUidMsvAttachmentEntry;
+ }
+ else if (param.CompareF(KImcvCharset)==0)
+ {
+ // Set the Mime charset from the parameter value
+ if (value.Length() != 0)
+ {
+ charset = iCharConv->GetMimeCharsetUidL(value);
+ }
+ }
+
+
+ // Next item
+ type_param=type_value->Next();
+ }
+ }
+
+ mime->SetMimeCharset(charset);
+
+ // ID: save it
+ CImapAtom *id=parameter->ToNextL();
+ if (!id->Compare(_L8("NIL")))
+ mime->SetContentIDL(id->AtomNoAngleBrackets());
+
+ // Description: save it
+ CImapAtom *description=id->ToNextL();
+ if (!description->Compare(_L8("NIL")))
+ mime->SetContentDescriptionL(description->Atom());
+
+ // Encoding
+ CImapAtom *encoding=description->ToNextL();
+ mime->SetContentTransferEncodingL(encoding->Atom());
+
+#ifdef PRINTING
+ TPtrC8 enc=encoding->Atom();
+ LogText(_L8(" Encoding %S"),&enc);
+#endif
+
+ // Octets (encoded form)
+ CImapAtom *octets=encoding->ToNextL();
+ TInt actualsize;
+ if (octets->Value(actualsize)!=KErrNone)
+ User::Leave(KErrGeneral);
+
+ // Twiddle this to show *decoded* size: this is basically the size of
+ // this part, multiplied by 6/8 if it's BASE64 encoded. For all other
+ // encodings, we leave the size as-is as there's no hard & fast rule
+ // which can be applied.
+ if (encoding->Compare(KMIME_BASE64))
+ message.iSize=(actualsize*6)/8;
+ else
+ message.iSize=actualsize;
+
+ // Add into total message size
+ iDecodedSizeOfAllParts+=message.iSize;
+
+ // Store *remote* size in a dodgy place
+ message.iBioType=actualsize;
+
+ //If any part of email (text/plain mime, text/html mime, attachment....)
+ // is empty then should not fetch it.
+ if(actualsize == 0)
+ {
+ message.SetComplete(ETrue);
+ }
+
+#ifdef PRINTING
+ LogText(_L8(" Octets %d"),message.iBioType);
+
+ TPtrC8 type_p=type->Atom(),subtype_p=subtype->Atom();
+ LogText(_L8("Building mime stuff: %S/%S"),&type_p,&subtype_p);
+#endif
+
+ // MD5 block will start after any optional parts
+ CImapAtom *md5;
+
+ if (type->Compare(KMIME_MESSAGE) && subtype->Compare(KMIME_RFC822))
+ {
+ // Skip RFC822 header, which should *all* be present
+ // Like this for clarity
+ CImapAtom *envelope=octets->ToNextL();
+ CImapAtom *structure=envelope->ToNextL();
+ CImapAtom *nooflines=structure->ToNextL();
+
+ // embedded message - marked as a message
+ message.iType=KUidMsvMessageEntry;
+
+ iDecodedSizeOfAllParts-=message.iSize;
+
+ // Next atom is MD5 - IF PRESENT
+ md5=nooflines->Next();
+ }
+ else
+ {
+ // Find MD5 block: if this part is TEXT/* we have number of lines next
+ if (type->Compare(KMIME_TEXT))
+ {
+ // Number of lines is next atom, followed by MD5 - IF PRESENT
+ CImapAtom *nooflines=octets->ToNextL();
+ md5=nooflines->Next();
+ }
+ else
+ md5=octets->Next();
+ }
+
+ // Do we have any extended fields? If so, deal with them
+ if (md5)
+ {
+ // Next (if present) is Content-Disposition, closely followed by language
+ CImapAtom *disposition=md5->Next();
+ CImapAtom *language=(disposition==NULL)?NULL:disposition->Next();
+ language=language; // Stop .aer warnings: we know it's not used (yet)
+
+ DBG((LogText(_L8("Processing content-disposition"))));
+
+ // Store disposition stuff
+ if (disposition && !disposition->Compare(KIMAP_NIL))
+ {
+ // Process list
+ CImapAtom *pos=disposition->Child();
+ while(pos)
+ {
+ // Single item (eg "INLINE") or 2-tuple (eg ("FILENAME" "blah.gif"))?
+ if (pos->Child())
+ {
+ // Tuple
+ CImapAtom* tuple = pos->ToChildL();
+ while(tuple)
+ {
+ mime->ContentDispositionParams().AppendL(tuple->Atom());
+ mime->ContentDispositionParams().AppendL(tuple->ToNextL()->Atom());
+
+ // Filename? If so, force this as an attachment
+ if ((tuple->Atom().CompareF(KMIME_FILENAME)==0)
+ || (tuple->Atom().CompareF(KMIME_FILENAME_RFC2231)==0))
+ {
+ DBG((LogText(_L8("It has an attachment filename, therefore this is an attachment"))));
+ FindFilenameDecodeL(*mime,attachmentFilename);
+ StripIllegalCharactersFromFileName(attachmentFilename);
+ message.iDetails.Set(attachmentFilename);
+
+ // If embedded message do not save as an attachment
+ if (message.iType!=KUidMsvMessageEntry)
+ message.iType=KUidMsvAttachmentEntry;
+ }
+
+ // Skip to next tuple
+ tuple = tuple->ToNextL()->Next();
+ }
+ }
+ else
+ {
+ // Single item
+ mime->ContentDispositionParams().AppendL(pos->Atom());
+ mime->ContentDispositionParams().AppendL(_L8(""));
+ }
+
+ // Skip to next entry
+ pos=pos->Next();
+ }
+ }
+ }
+
+ // Now we're working on the type
+ if (message.iType==KUidMsvMessageEntry)
+ {
+ // MESSAGE/RFC822
+ // This means that the next atom will be the envelope info, and the
+ // one following that will be the body structure of the embedded
+ // message.
+ //
+ // This is an entire message-within-a-message and so gets treated like
+ // an actual mail (has it's own multipartdata thing)
+
+ // Make CImHeader bits
+ CImHeader *messageheader=CImHeader::NewLC();
+ CImapAtom *envelope=octets->ToNextL();
+ ProcessEnvelopeL(messageheader,message,envelope);
+
+ // Create message
+ User::LeaveIfError(iEntry->CreateEntryBulk(message));
+ SetEntryL(message.Id());
+
+ // Store CImHeader bits
+ CMsvStore* entryStore=iEntry->EditStoreL();
+ CleanupStack::PushL(entryStore);
+ messageheader->StoreL(*entryStore);
+ mime->StoreL(*entryStore);
+ entryStore->CommitL();
+ CleanupStack::PopAndDestroy(3);
+
+#if SET_RELATED_ID
+ // DS - Set message's iRelatedId to messageId to allow later UI kludges
+ TMsvEntry changeEntry(iEntry->Entry());
+ changeEntry.iRelatedId=changeEntry.Id();
+ ChangeEntryBulkL(changeEntry);
+#endif
+ // Descend into attachments of this embedded message
+ CImapAtom *structure=envelope->ToNextL();
+ TInt attachments=0;
+ TBool isMHTML=EFalse;
+
+ BuildTreeL(message.Id(),structure->ToChildL(),aPath,message.Id(),attachments,isMHTML,aRelatedAttachments);
+ DBG((LogText(_L8("Build embedded message id %x attachments %d MHTML %d"),message.Id(),attachments,isMHTML)));
+
+ // Save attachment and MHTML flags
+ if (attachments>0 || isMHTML)
+ {
+ SetEntryL(message.Id());
+ TMsvEmailEntry thisMessage=iEntry->Entry();
+
+ if (attachments>0)
+ {
+ thisMessage.SetAttachment(ETrue);
+ }
+
+ if (isMHTML)
+ {
+ thisMessage.SetMHTMLEmail(ETrue);
+ }
+
+ ChangeEntryBulkL(thisMessage);
+ }
+
+ // we are now counting embedded messages as attachments
+ aAttachments++;
+ }
+ else
+ {
+ // Something else - create an attachment entry
+ SetEntryL(aParent);
+
+ // save parent folder type
+ TImEmailFolderType parentFolderType = ((TMsvEmailEntry)iEntry->Entry()).MessageFolderType();
+
+ // set attachment and HTML flags on item
+ if ( message.iType==KUidMsvAttachmentEntry)
+ message.SetAttachment(ETrue);
+
+ if ( message.iType==KUidMsvEmailHtmlEntry)
+ message.SetMHTMLEmail(ETrue);
+
+ // ensure there is a filename if it is a non-text item (which
+ // also controls iFetchIsText, the flag used in DecodeAndStore
+ // to say whether to stream to a file or RichText store.
+ if (message.iType!=KUidMsvEmailTextEntry && message.iDetails.Length() == 0)
+ {
+ // use iAttachmentName for temporary buffer
+ GetDefaultFilename(iAttachmentName, message, mime);
+ message.iDetails.Set(iAttachmentName);
+ }
+
+ User::LeaveIfError(iEntry->CreateEntryBulk(message));
+ SetEntryL(message.Id());
+
+ DBG((LogText(_L8("Created attachment id %x as child of %x - type %d"),message.Id(),aParent, parentFolderType)));
+
+#if SET_RELATED_ID
+ // DS - Set message's iRelatedId to messageId to allow later UI kludges
+ TMsvEntry changeEntry(iEntry->Entry());
+ changeEntry.iRelatedId=changeEntry.Id();
+ ChangeEntryBulkL(changeEntry);
+#endif
+
+ DBG((LogText(_L8("Streaming MIME info into id %x"),iEntry->Entry().Id())));
+
+ // Stream the MIME info out into the message
+ // This will either stream it to the actual message (if the above if
+ // evaluated to True, the entry is still set to the message), or to
+ // the newly created child
+ CMsvStore* entryStore=iEntry->EditStoreL();
+ CleanupStack::PushL(entryStore);
+ mime->StoreL(*entryStore);
+ entryStore->CommitL();
+ CleanupStack::PopAndDestroy(2, mime);
+
+ // This entry is NOT an attachment in the following cases -
+ // 1) This is an attachment whose parent is a MULTIPART/RELATED folder.
+ // In this case, this entry could be a image entity for an MHTML
+ // entry with the same parent.
+ // 2) This is an MHTML entry whose parent is a MULTIPART/ALTERNATIVE
+ // folder. In this case, this entry is the MHTML alternative to a
+ // text entry with the same parent.
+ // 3) This is an MHTML entry whose parent is MESSAGE folder. In this
+ // case, the message is a simple MHTML message with no text
+ // alternative or embedded image.
+ // 4) This is an MHTML entry whose parent is a MULTIPART/RELATED folder.
+ // In this case, this entry is the MHTML for the message.
+ // 5) This is an MHTML entry whose parent is a MULTIPART/MIXED folder.
+ // In this case, this entry is the MHTML for the message. It cannot
+ // be the attachment it self as then it would be of type attachment.
+ // Therefore, an entry is only an attachment if is of type attachment and
+ // its parent is not a MULTIPART/RELATED folder.
+ if( message.iType==KUidMsvAttachmentEntry && parentFolderType != EFolderTypeRelated )
+ {
+ ++aAttachments;
+ }
+ // if it is related we might want to include it if the message
+ // turns out not to be MHTML
+ else if ( message.iType==KUidMsvAttachmentEntry &&
+ parentFolderType == EFolderTypeRelated )
+ {
+ ++aRelatedAttachments;
+ }
+ }
+
+ DBG((LogText(_L8("BuildTreeOneL done: created id %x, attachments so far %d"), message.Id(), aAttachments)));
+ }
+
+// Build attachment tree below a message
+void CImImap4Session::BuildTreeL(TMsvId aParent, CImapAtom *aAtom, const TDesC8& aPath,
+ const TMsvId aThisMessage, TInt& aAttachments, TBool& aIsMHTML, TInt& aRelatedAttachments)
+ {
+ DBG((LogText(_L8("BuildTreeL(message=%x, parent=%x"),aThisMessage,aParent)));
+
+ // One attachment only?
+ if (aAtom->Child()==NULL)
+ {
+ // Deal with the single entry (doesn't use AllocL)
+ HBufC8* newpath=HBufC8::NewLC(aPath.Length()+4);
+ *newpath=aPath;
+ if (aPath.Length())
+ newpath->Des().Append(_L8("."));
+ newpath->Des().AppendNum(1);
+ BuildTreeOneL(aParent,aAtom,newpath->Des(),aThisMessage,aAttachments,aIsMHTML, aRelatedAttachments);
+ CleanupStack::PopAndDestroy();
+ }
+ else
+ {
+ // Nest down a level: create a folder
+ SetEntryL(aParent);
+ TMsvEmailEntry message;
+ message.iMtm=KUidMsgTypeIMAP4;
+ message.iServiceId=iServiceId;
+ message.iType=KUidMsvFolderEntry;
+ message.iSize=0;
+ message.SetComplete(EFalse);
+ User::LeaveIfError(iEntry->CreateEntryBulk(message));
+
+ DBG((LogText(_L8("Created attachment folder id %x as child of %x"),message.Id(),aParent)));
+
+ aParent=message.Id();
+
+ // CC:Mail server doesn't respond to BODYSTRUCTURE correctly:
+ // it gives the same response as FETCH BODY, ie it doesn't have
+ // all the extended MIME stuff.
+ // Skip to the last 4 atoms: this is the multipart type & stuff
+ CImapAtom *multipart=aAtom;
+ while(multipart && multipart->Child()!=NULL)
+ multipart=multipart->Next();
+
+ // Got anything?
+ if (multipart)
+ {
+ // Parse multipart type string, do this first so
+ // information is available when parsing children
+ TImEmailFolderType ft=EFolderTypeUnknown;
+ if (multipart->Compare(KImcvRelated))
+ ft=EFolderTypeRelated;
+ if (multipart->Compare(KImcvMixed))
+ ft=EFolderTypeMixed;
+ if (multipart->Compare(KImcvParallel))
+ ft=EFolderTypeParallel;
+ if (multipart->Compare(KImcvAlternative))
+ ft=EFolderTypeAlternative;
+ if (multipart->Compare(KImcvDigest))
+ ft=EFolderTypeDigest;
+
+ SetEntryL(aParent);
+
+ // ...and save it
+ TMsvEmailEntry folder=iEntry->Entry();
+ folder.SetMessageFolderType(ft);
+#if SET_RELATED_ID
+ // DS - Set message's iRelatedId to messageId to allow later UI kludges
+ folder.iRelatedId=folder.Id();
+#endif
+ ChangeEntryBulkL(folder);
+
+ // Process the multipart object
+ TInt subnr=1;
+ while(aAtom && aAtom!=multipart)
+ {
+ // Tag or child?
+ if (aAtom->Child())
+ {
+ // Process item (doesn't use AllocL)
+ HBufC8* newpath=HBufC8::NewLC(aPath.Length()+4);
+ *newpath=aPath;
+ if (aPath.Length())
+ newpath->Des().Append(_L8("."));
+ newpath->Des().AppendNum(subnr++);
+ BuildTreeOneL(aParent,aAtom->ToChildL(),newpath->Des(),
+ aThisMessage,aAttachments,aIsMHTML, aRelatedAttachments);
+ CleanupStack::PopAndDestroy();
+ }
+
+ // Next item
+ aAtom=aAtom->Next();
+ }
+ }
+ }
+ }
+
+
+// convert text from its charset and write to richtext store. aText
+// can span multiple and partial lines
+void CImImap4Session::WriteToBodyL(const TDesC8& aText)
+ {
+ TInt pos = iMessageBody->DocumentLength();
+
+ // Add bits of body text, converting along the way, till no characters left
+ // .. to convert.
+
+ // Convert text before writing to body.
+ TInt rem = 0;
+
+ // there will be a max of one output char per input byte
+ HBufC16* text16=HBufC16::NewLC(aText.Length());
+ TPtr16 ptr16=text16->Des();
+
+ if (!iPreparedToConvert)
+ {
+ ptr16.Copy(aText);
+ iMessageBody->InsertL(pos, ptr16);
+ }
+ else
+ {
+ TInt unconvertedChars, firstPos; // not used
+ rem = iCharConv->ConvertToOurCharsetL(aText, ptr16,
+ unconvertedChars, firstPos);
+ if (rem < 0) // error
+ {
+ // Copy unconverted characters.
+ ptr16.Copy(aText);
+ iMessageBody->InsertL(pos, ptr16);
+ }
+ else if (rem && rem < iLeftOver.MaxLength())
+ iLeftOver.Copy(aText.Right(rem));
+
+ // convert CRLF to ELineBreak
+ TInt start = 0;
+ TInt length = ptr16.Length();
+ TInt i;
+ for (i=1; i<length; i++)
+ {
+ if (ptr16[i-1] == KImcvCR && ptr16[i] == KImcvLF)
+ {
+ ptr16[i-1] = CEditableText::ELineBreak;
+
+ // write this line to body
+ TPtrC ptr = ptr16.Mid(start, i-start);
+ iMessageBody->InsertL(pos, ptr);
+ pos += ptr.Length();
+ start = i+1;
+ }
+ }
+
+ if (start != i)
+ {
+ TPtrC ptr = ptr16.Mid(start, i-start);
+ iMessageBody->InsertL(pos, ptr);
+ }
+ }
+
+ CleanupStack::PopAndDestroy(); // text16
+ }
+
+// convert text from its charset and write to file, return error code
+// from write
+TInt CImImap4Session::WriteToAttachmentL(const TDesC8& aText)
+ {
+ TInt error;
+
+ // Convert text before writing to attachment.
+ TInt rem = 0;
+
+ // there will be a max of one output char per input byte
+ HBufC16* text16=HBufC16::NewLC(aText.Length());
+ TPtr16 ptr16=text16->Des();
+
+ if (!iPreparedToConvert)
+ {
+ if(iCaf->Processing())
+ {
+ error = iCaf->WriteData(aText);
+ }
+ else
+ {
+ error = iAttachmentFile->WriteFile(aText);
+ }
+ }
+ else
+ {
+ TInt unconvertedChars, firstPos; // not used
+ rem = iCharConv->ConvertToOurCharsetL(aText, ptr16,
+ unconvertedChars, firstPos);
+ if (rem < 0) // error
+ {
+ ptr16.Copy(aText); // Copy unconverted characters.
+ }
+ else if (rem && rem < iLeftOver.MaxLength())
+ {
+ // any remainder is due to partial code sequence not lack of space
+ iLeftOver.Copy(aText.Right(rem));
+ }
+
+ TPtrC8 text8((TUint8*) text16->Des().Ptr(), text16->Des().Size());
+ if(iCaf->Processing())
+ {
+ error = iCaf->WriteData(text8);
+ }
+ else
+ {
+ error = iAttachmentFile->WriteFile(text8);
+ }
+ }
+
+ CleanupStack::PopAndDestroy(); // text16
+
+ return error;
+ }
+
+// Copied and adapted this function from the one in CImRecvConvert
+TBool CImImap4Session::CheckUUEStartL(const TDesC8& aSourceLine)
+ {
+ // Checks if the descriptor contains the UUE begin header
+ // Extracts the file name if it is
+
+ TInt sourceLength = aSourceLine.Length();
+ if(sourceLength < KImcvUueStart().Length()+3) // can't be "begin ###", it's not long enough; 3=length of ###
+ return EFalse;
+
+ if(!aSourceLine.Left(KImcvUueStart().Length()).CompareF(KImcvUueStart)) // start of line might be UUE boundary
+ {
+ // we also need to check that the next three chars are numbers - Unix file access code
+ const TUint8* sourceLinePtr = aSourceLine.Ptr();
+ TInt length=KImcvUueStart().Length();// this defines length as 6 ie. "b e g i n "
+ if( TChar(sourceLinePtr[length]).IsDigit() &&
+ TChar(sourceLinePtr[length+1]).IsDigit() &&
+ TChar(sourceLinePtr[length+2]).IsDigit() )
+ {
+ // Found 'begin ###' at the start of a line - assume this is a UUencode header
+ // The attachment name in this header is ignored. We use the value from the MIME header
+ return ETrue;
+ }
+ }
+
+ return EFalse;
+ }
+
+
+// Decode and store received data
+void CImImap4Session::DecodeAndStoreL(const TPtrC8& aBodyData, const TBool aEndOfStream)
+ {
+ DBG((LogText(_L8("DecodeAndStore(%d bytes, endofstream=%d, encoding=%d, iLeftOver=%d)"),aBodyData.Length(),aEndOfStream,iEncodingType,iLeftOver.Length())));
+
+ // Somewhere to store decoded data, at least as long as source (plus anything we have left
+ // in the partial line buffer which may now get consumed)
+ TInt outputbuffersize=aBodyData.Length()+4;
+ if (iPartialLine)
+ outputbuffersize+=iPartialLine->Des().Length();
+
+ HBufC8* decoded=HBufC8::NewLC(outputbuffersize);
+ TPtr8 decoded_ptr=decoded->Des();
+
+ // Bump progress: bytesdone is *encoded* length, so we just use the encoded length
+ iProgress.iBytesDone+=aBodyData.Length();
+ // Which decoder are we using?
+ switch(iEncodingType)
+ {
+ case EEncodingTypeNone:
+ case EEncodingType7Bit:
+ case EEncodingType8Bit:
+ case EEncodingTypeBinary:
+ case EEncodingTypeUnknown:
+ // Nothing to do, just copy data
+ decoded->Des().Append(aBodyData);
+ break;
+
+ case EEncodingTypeBASE64:
+ // Decode Base64 data: just filter it through decoder, it
+ // ignores line breaks anyway.
+ iB64Decoder.Decode(aBodyData,decoded_ptr);
+ break;
+
+ case EEncodingTypeUU:
+ {
+ TPtrC8 bodydata=aBodyData;
+
+ // Got a partial buffer?
+ if (!iPartialLine)
+ {
+ // Allocate buffer
+ iPartialLine=HBufC8::NewL(KUuDecodedLineLength);
+ iUUDecoding = EFalse;
+ }
+
+ // Decode UUEncoded data: line by line
+ TBool decodeEnded = EFalse;
+ TInt position=0;
+ while ( bodydata.Length() && !decodeEnded )
+ {
+ // Find() returns the start of "\r\n". The decoding algorithm
+ // requires that the encoded line contains the "\r\n".
+ TInt lineEnd = bodydata.Find( _L8("\r\n") );
+ if (lineEnd != KErrNotFound)
+ {
+ lineEnd = lineEnd + 2;
+ AppendExtendL( &iPartialLine, bodydata.Left( lineEnd ), EFalse);
+
+ bodydata.Set( bodydata.Mid( lineEnd ) );
+
+ // Check for a well-formated begin-tag
+ if ( CheckUUEStartL( iPartialLine->Des() ) )
+ {
+ iUUDecoding = ETrue;
+ }
+ else if ( iPartialLine->Compare( KImcvUueEnd ) != 0 && iUUDecoding )
+ {
+ // Every malformatted string is decoded as an empty string
+ // with length 0. Appending such a string is harmless.
+ TPtr8 destination((unsigned char*)decoded_ptr.Ptr()+position,0,outputbuffersize-position);
+ iUUDecoder.Decode(*iPartialLine,destination);
+ position+=destination.Length();
+ }
+ else if ( iUUDecoding )
+ {
+ decodeEnded = ETrue;
+ iUUDecoding = EFalse;
+ }
+
+ iPartialLine->Des().Zero();
+ }
+ else
+ {
+ AppendExtendL( &iPartialLine, bodydata, EFalse);
+
+ // advance to end of bodydata
+ bodydata.Set(bodydata.Ptr()+bodydata.Length(), 0);
+ }
+ }
+ decoded->Des().SetLength(position);
+ break;
+ }
+
+ case EEncodingTypeQP:
+ {
+ TPtrC8 bodydata=aBodyData;
+
+ // Got a partial buffer?
+ if (!iPartialLine)
+ {
+ // Allocate buffer
+ iPartialLine=HBufC8::NewL(256);
+ }
+
+ // Build buffer to decode: basically, QP decoder wants CRLF terminated
+ // lines, so we build them in the iPartialLine buffer. There may be
+ // stuff already there from previous data packet - so we just append.
+ TInt position=0;
+ while(bodydata.Length())
+ {
+ // Find a line break
+ TInt lineend=bodydata.Find(_L8("\r\n"));
+
+ // No break?
+ if (lineend==KErrNotFound && !aEndOfStream)
+ {
+ // Stick it all in the partialline buffer, we should get a CRLF
+ // soon...
+ AppendExtendL( &iPartialLine,bodydata, EFalse);
+ break;
+ }
+ else
+ {
+ if (lineend==KErrNotFound)
+ {
+ // Append whole thing left to buffer
+ AppendExtendL( &iPartialLine,bodydata, EFalse);
+
+ // advance to end of bodydata
+ bodydata.Set(bodydata.Ptr()+bodydata.Length(), 0);
+ }
+ else
+ {
+ // Append to buffer up to that point (including the \r\n)
+ AppendExtendL( &iPartialLine,bodydata.Left(lineend+2), EFalse);
+
+ // Remove from the buffer we're working on (including the \r\n)
+ bodydata.Set(bodydata.Ptr()+lineend+2,bodydata.Length()-lineend-2);
+ }
+
+ // Decode & skip on in buffer
+ TPtr8 destination((unsigned char*)decoded_ptr.Ptr()+position,0,outputbuffersize-position);
+ iQPDecoder.Decode(*iPartialLine,destination);
+ position+=destination.Length();
+ iPartialLine->Des().Zero();
+ }
+ }
+
+ // Update decoded
+ decoded->Des().SetLength(position);
+ break;
+ }
+ }
+
+ // put back any partially converted data
+ if (iLeftOver.Length())
+ {
+ decoded->Des().Insert(0, iLeftOver);
+ iLeftOver.SetLength(0);
+ }
+
+ // What format is it? TEXT/* we put into a richtext thingy, otherwise just
+ // stream it to store
+ if (iFetchIsText)
+ {
+ if(aEndOfStream && (iMessageBody || iBodyBuf) && iBodyPartRemainingSize)
+ {
+ CleanupStack::Pop();// decoded
+ TInt newSize = decoded->Size() + iFooterString->Size();
+ decoded = decoded->ReAlloc(newSize);
+ CleanupStack::PushL(decoded);
+ decoded->Des().Append(*iFooterString);
+ delete iFooterString;
+ iFooterString = NULL;
+ }
+ // Got somewhere to put it? Store it!
+ if (iStore8BitData)
+ {
+ if (decoded->Length() && iBodyBuf)
+ iBodyBuf->InsertL(iBodyBuf->Size(), *decoded);
+ }
+ else
+ {
+ if (decoded->Length() && iMessageBody)
+ WriteToBodyL(decoded->Des());
+ }
+
+
+ // Got the whole thing buffered?
+ if (aEndOfStream && (iMessageBody || iBodyBuf))
+ {
+ DBG((LogText(_L8("Doing StoreBodyTextL()"))));
+
+ // The whole message is built in iMessageBody or iBodyBuf. Store it.
+ SetEntryL(iMessageId);
+ CMsvStore *entryStore=iEntry->EditStoreL();
+ CleanupStack::PushL(entryStore);
+ if (iStore8BitData)
+ iBodyText->StoreL(*entryStore, *iBodyBuf);
+ else
+ entryStore->StoreBodyTextL(*iMessageBody);
+ entryStore->CommitL();
+ CleanupStack::PopAndDestroy();
+
+ // Get rid of body copy, etc
+ delete iBodyBuf;
+ iBodyBuf = NULL;
+ delete iMessageBody;
+ iMessageBody=NULL;
+ }
+ }
+ else
+ {
+ // Select the entry
+ SetEntryL(iMessageId);
+
+ // Save it direct to store
+ if (iAttachmentFileState==EFileNotOpen)
+ {
+ // Get and set Attachment File path
+ TFileName filepath;
+
+ // Retrieving the attachment name from an earlier saved one
+ // If it's a CAF interested file then this will get overidden
+ CMsvStore* store = iEntry->ReadStoreL();
+ CleanupStack::PushL(store);
+ MMsvAttachmentManager& attachmentMgr = store->AttachmentManagerL();
+ if(attachmentMgr.AttachmentCount())
+ {
+ // get the file path
+ CMsvAttachment* attachment = attachmentMgr.GetAttachmentInfoL(0);
+ CleanupStack::PushL(attachment);
+ filepath = attachment->FilePath();
+ CleanupStack::PopAndDestroy(attachment);
+ }
+
+ if (iAttachmentFullPath)
+ {
+ delete iAttachmentFullPath;
+ iAttachmentFullPath=NULL;
+ }
+ if(attachmentMgr.AttachmentCount())
+ {
+ TParse fileParser;
+ User::LeaveIfError(fileParser.Set(filepath, NULL, NULL));
+ iAttachmentFullPath=fileParser.DriveAndPath().AllocL();
+ }
+ // We've already extracted the attachment file name in
+ // BuildTree so just copy it out of details
+ iAttachmentName=iEntry->Entry().iDetails;
+ if(attachmentMgr.AttachmentCount())
+ {
+ DBG((LogText(_L8("name '%S', '%S'"),iAttachmentFullPath,&iAttachmentName)));
+ }
+ if (!iAttachmentFile)
+ iAttachmentFile=new (ELeave) TImAttachmentFile(iFs);
+ CleanupStack::PopAndDestroy(store); // store opened above
+ store = iEntry->EditStoreL();
+ CleanupStack::PushL(store);
+ // Could be multiple attachments in the folder.
+ TInt attachmentCount = store->AttachmentManagerL().AttachmentCount();
+ for(TInt i=0;i<attachmentCount;i++)
+ {
+ // Remove [0] as array is shuffled. Once index [n] is removed n+1 becomes n
+ store->AttachmentManagerExtensionsL().RemoveAttachmentL(0);
+ }
+ if(attachmentCount)
+ store->CommitL();
+
+ // Now create the attachment entry
+ CMsvAttachment* attachment = CMsvAttachment::NewL(CMsvAttachment::EMsvFile);
+ CleanupStack::PushL(attachment);
+ attachment->SetAttachmentNameL(iAttachmentName);
+
+ // Need to create the MIME-type information - first get the MIME headers
+ CImMimeHeader* mimeHeaders = CImMimeHeader::NewLC();
+ mimeHeaders->RestoreL(*store);
+
+ HBufC8* buf = HBufC8::NewLC(mimeHeaders->ContentSubType().Length() + mimeHeaders->ContentType().Length() + 1);
+ TPtr8 ptr(buf->Des());
+ ptr.Copy(mimeHeaders->ContentType());
+ ptr.Append(KImcvForwardSlash);
+ ptr.Append(mimeHeaders->ContentSubType());
+
+ attachment->SetMimeTypeL(ptr);
+
+ CleanupStack::PopAndDestroy(2, mimeHeaders);
+
+
+ RFile file;
+ if(iCaf->Registered())
+ {
+ iCaf->PrepareProcessingL(); // Init the CAF import file session
+ RFile startFile;
+ TFileName suggestedFileName;
+ if(iCaf->GetSuggestedAttachmentFileName(suggestedFileName) == KErrNone) // CAF agent may provide a filename
+ {
+ store->CreateShareProtectedAttachmentL(suggestedFileName,startFile,attachment);
+ }
+ else
+ {
+ store->CreateShareProtectedAttachmentL(iAttachmentName,startFile,attachment);
+ }
+ iCaf->StartProcessing(iDefaultAttachmentName->Des(),attachment->FilePath(),*iEntry,startFile); // Init the CAF session
+ startFile.Close();
+ }
+ else
+ {
+ // Normal behaviour
+ store->AttachmentManagerExtensionsL().CreateAttachmentL(iAttachmentName,file,attachment);
+ iAttachmentFile->SetFileHandle(file,TImAttachmentFile::EImFileWrite);
+ }
+
+ // CreateAttachmentL takes ownership of CMsvAttachment so if call was successful we can pop it here
+ CleanupStack::Pop(attachment);
+
+ iAttachmentFileState = EFileIsOpen;
+ store->CommitL();
+ CleanupStack::PopAndDestroy(store);
+
+ if (iAttachmentFileState!=EFileIsOpen)
+ {
+ DBG((LogText(_L8("Couldn't open file!"))));
+ }
+ }
+
+ if (iAttachmentFileState==EFileIsOpen && decoded->Length())
+ {
+ // write decoded data into a file if there is any data there to write
+ TInt error=WriteToAttachmentL(decoded->Des());
+
+ if (error!=KErrNone)
+ {
+ // the file write failed, (eg.there is no space left set new file state
+ // and skip any remaining encoded data in message
+ iAttachmentFileState=EFileIsIncomplete;
+
+ DBG((LogText(_L8("Failed to write %d bytes to attachment file (error=%d): deleting it"),decoded->Length(),error)));
+ if(iCaf->Processing())
+ {
+ iCaf->EndProcessingL();
+ }
+ else
+ {
+ iAttachmentFile->CloseFile();
+ }
+
+ CMsvStore* store = iEntry->EditStoreL();
+ CleanupStack::PushL(store);
+ // Could be multiple attachments in the folder.
+ TInt i;
+ TInt attachmentCount = store->AttachmentManagerL().AttachmentCount();
+ for(i=0;i<attachmentCount;i++)
+ {
+ // Remove [0] as array is shuffled. Once index [n] is removed n+1 becomes n
+ store->AttachmentManagerExtensionsL().RemoveAttachmentL(0);
+ }
+ if(attachmentCount)
+ store->CommitL();
+ CleanupStack::PopAndDestroy(store);
+ TMsvEmailEntry message=iEntry->Entry();
+ message.SetAttachment(EFalse);
+ ChangeEntryBulkL(message);
+
+ // Leave with the error
+ User::Leave(error);
+ }
+ else
+ {
+ DBG((LogText(_L8("Written %d bytes to attachment file"),decoded->Length())));
+ }
+ }
+
+ // Finished?
+ if ((aEndOfStream) && (iAttachmentFileState == EFileIsOpen))
+ {
+ DBG((LogText(_L8("Closing attachment file"))));
+ if(iCaf->Processing())
+ {
+ iCaf->EndProcessingL();
+ }
+ else
+ {
+ iAttachmentFile->CloseFile();
+ }
+ iAttachmentFileState=EFileNotOpen;
+ }
+ }
+
+ // Free memory
+ CleanupStack::PopAndDestroy();
+ }
+
+// Given that aId has become complete see if we can propagate the
+// Complete state and partial fetch state flag up
+void CImImap4Session::PropagateCompleteFlagL(TMsvId aId, TBool aDoBodyText,TBool aPartialFetched)
+ {
+ CMsvEntrySelection* selection=new (ELeave) CMsvEntrySelection;
+ CleanupStack::PushL(selection);
+
+ // get the siblings of this id
+ SetEntryL(aId);
+ TMsvId parent = iEntry->Entry().Parent();
+
+ // finish if we've managed to reach the top
+ if (parent == KMsvRootIndexEntryId)
+ return;
+
+ SetEntryL(parent);
+
+ // finish if we've reached a service
+ if (iEntry->Entry().iType == KUidMsvServiceEntry)
+ return;
+
+ GetChildrenL(*selection);
+
+ TBool complete=ETrue;
+ TBool bodyTextComplete=ETrue;
+ TBool partiallyFetched=EFalse;
+
+ TBool related=((TMsvEmailEntry) iEntry->Entry()).MessageFolderType()==EFolderTypeRelated ?
+ ETrue:EFalse;
+ for (TInt i=0; i < selection->Count(); i++)
+ {
+ SetEntryL((*selection)[i]);
+ if (!iEntry->Entry().Complete())
+ {
+ complete=EFalse;
+ if((iEntry->Entry().iType==KUidMsvFolderEntry) && aPartialFetched)
+ complete=ETrue;
+ // The current part is not complete so...
+ // if it is either a text part or a HTML part then the body
+ // text is marked as being incomplete.
+ //
+ // This code means that, if present, then both the text/plain
+ // and text/html alternatives need to be downloaded before
+ // the body text is marked as being complete.
+ if ((iEntry->Entry().iType == KUidMsvEmailTextEntry)
+ || (iEntry->Entry().iType == KUidMsvEmailHtmlEntry ) || related )
+ {
+ if(aPartialFetched)
+ {
+ complete = ETrue;
+ bodyTextComplete=ETrue;
+ }
+ else
+ bodyTextComplete=EFalse;
+ }
+
+ break;
+ }
+ }
+
+ CleanupStack::PopAndDestroy(); // selection
+
+ // if all the siblings were complete then make the parent
+ // complete and continue up.
+ if (complete || ((aDoBodyText || related) && bodyTextComplete))
+ {
+ SetEntryL(parent);
+ TMsvEmailEntry entry = iEntry->Entry();
+
+ // check whether parent is complete, this wil prevent us
+ // checking all the messages in a real folder as they will all
+ // be initialised to Complete
+ if (!entry.Complete())
+ {
+ if (complete || ((iEntry->Entry().iType==KUidMsvFolderEntry) && aPartialFetched))
+ entry.SetComplete(ETrue);
+ if(aPartialFetched)
+ {
+ if((iEntry->Entry().iType != KUidMsvAttachmentEntry) &&
+ (iEntry->Entry().iType != KUidMsvEmailExternalBodyEntry))
+ {
+ entry.SetPartialDownloaded(ETrue);
+ }
+ partiallyFetched = ETrue;
+ }
+ else
+ {
+ entry.SetPartialDownloaded(EFalse);
+ partiallyFetched = EFalse;
+ }
+ entry.SetBodyTextComplete(ETrue);
+ ChangeEntryL(entry);
+
+ PropagateCompleteFlagL(parent, related|aDoBodyText,partiallyFetched);
+ }
+ else if (entry.PartialDownloaded())
+ {
+ entry.SetPartialDownloaded(EFalse);
+ ChangeEntryL(entry);
+ PropagateCompleteFlagL(parent, related|aDoBodyText,partiallyFetched);
+ }
+ }
+ }
+
+void CImImap4Session::CreateAttachmentInfoL(TMsvEmailEntry& aMsvEmailEntry)
+ {
+ // create an empty attachment to store the attachment infomation, for the case
+ // where the attachment is not downloaded due to download limits.
+ CMsvStore* store = iEntry->EditStoreL();
+ CleanupStack::PushL(store);
+
+ MMsvAttachmentManager& attachmentMgr = store->AttachmentManagerL();
+
+ // Check to see if this entry already has an attachment - if so, then don't
+ // add it again!
+ if( attachmentMgr.AttachmentCount() == 0 )
+ {
+ MMsvAttachmentManagerSync& attachmentMgrSync = store->AttachmentManagerExtensionsL();
+
+ // Now create the attachment entry
+ CMsvAttachment* attachment = CMsvAttachment::NewL(CMsvAttachment::EMsvFile);
+ CleanupStack::PushL(attachment);
+
+ // Need to create the MIME-type information - first get the MIME headers
+ CImMimeHeader* mimeHeaders = CImMimeHeader::NewLC();
+ mimeHeaders->RestoreL(*store);
+
+ HBufC8* buf = HBufC8::NewLC(mimeHeaders->ContentSubType().Length() + mimeHeaders->ContentType().Length() + 1);
+ TPtr8 ptr(buf->Des());
+ ptr.Copy(mimeHeaders->ContentType());
+ ptr.Append(KImcvForwardSlash);
+ ptr.Append(mimeHeaders->ContentSubType());
+
+ attachment->SetMimeTypeL(ptr);
+
+ CleanupStack::PopAndDestroy(2, mimeHeaders);
+
+ attachment->SetComplete(EFalse);
+ attachment->SetSize(aMsvEmailEntry.iSize);
+ attachment->SetAttachmentNameL(aMsvEmailEntry.iDetails);
+ RFile file;
+ attachmentMgrSync.CreateAttachmentL(aMsvEmailEntry.iDetails,file,attachment);
+ CleanupStack::Pop(attachment); // ownership passed to attachment manager
+ file.Close();
+ store->CommitL();
+ }
+ CleanupStack::PopAndDestroy(store);
+ }
+
+
+// Parse fetch messages
+TInt CImImap4Session::ProcessFetchL(const TUint aMsgnr, CImapAtom *aAtom)
+ {
+ CImapAtom *p=aAtom;
+ CImapAtom *attribute;
+ CImapAtom *structure=NULL;
+ CImapAtom *flags=NULL;
+ CImapAtom *bodydata=NULL;
+ CImapAtom *header=NULL;
+ TInt error=KErrNotReady;
+ TInt rfc822size=0;
+ TBool foundUnwantedMimeHeader=EFalse;
+ TBool wholeMessage=EFalse;
+ TInt fetchSizeBytes = static_cast<TInt>(iServiceSettings->FetchSize());
+
+ // Fetch data consists of attribute/value pairs
+ while(p!=NULL)
+ {
+ // Get attribute & value
+ attribute=p;
+ // broken servers can give us just an attribute rather than an
+ // attribute pair, if so then just finish the scan here and
+ // process what we've got
+ p=p->Next();
+ if (p==NULL)
+ break;
+
+ // Work on attributes
+ if (attribute->Compare(KIMAP_UID))
+ {
+ iFoundUid = ETrue;
+
+ // Lex it ok?
+ if (p->Value(iMessageUid)!=KErrNone)
+ User::Leave(KErrArgument);
+
+ // Skip to next attribute
+ p=p->Next();
+ }
+
+ else if (attribute->Compare(KIMAP_BODY))
+ {
+ // some example responses
+
+ // expected without extra header fields
+ // * 1 FETCH (UID 1 BODY[2]<0> {1024}
+
+ // expected with extra (empty) header fields
+ // * 2 FETCH (UID 35 BODY[1.HEADER.FIELDS ("CONTENT-BASE" "CONTENT-LOCATION")] "" BODY[1]<0> {60}
+
+ // unwanted BODYSTRUCTURE info
+ // * 3 FETCH (UID 2 BODY ("text" "plain" ("CHARSET" "us-ascii") NIL NIL "7bit" 4337 67) BODY[1]<0> {1024}
+
+ // unwanted nested BODYSTRUCTURE info
+ // * 4 FETCH (UID 1 BODY (("text" "plain" ("CHARSET" "us-ascii") NIL NIL "7bit" 1346 53)
+ // ("text" "plain" ("NAME" "install.ins") NIL NIL "7bit" 5156 215) "MIXED")
+ // BODY[2]<0> {1024}
+
+ // unwanted BODYSTRUCTURE info and complete message
+ // * 5 FETCH (UID 2 BODY ("text" "plain" ("CHARSET" "us-ascii") NIL NIL "7bit" 4337 67) BODY[1] {1550}
+
+ // Body part is across & down one (it's in [])
+ CImapAtom* bodypart=p->ToChildL();
+
+ if (bodypart->CompareTail(KIMAP_HEADERFIELDS))
+ {
+ // Got BODY[HEADER.FIELDS ("header1" "header2" ...)] {LEN} data ...
+ // or BODY[<part>.HEADER.FIELDS ("header1" "header2" ...)] {LEN} data ...
+ header=p->ToNextL();
+#ifdef PRINTING
+ TPtrC8 bpt=bodypart->Next()->Child()->Atom();
+ LogText(_L8("Found header fields (%S...)"),&bpt);
+#endif
+ // Skip to next attribute
+ p=header->Next();
+ }
+ else if (bodypart->CompareTail(KIMAP_MIME))
+ {
+ // Got BODY[MIME] {LEN} data ...
+ // or BODY[<part>.MIME] {LEN} data ...
+ header=p->ToNextL();
+
+ DBG((LogText(_L8("Found MIME header fields"))));
+
+ // Skip to next attribute
+ p=header->Next();
+ }
+ else if (bodypart->Child() != NULL || bodypart->Next() != NULL)
+ {
+ // we've been unexpectedly returned a BODY () response
+ // which we don't want, so just skip it
+ DBG((LogText(_L8("Unexpected BODY response, ignoring"))));
+ p=p->Next();
+ }
+ else
+ {
+ // is body data, ie BODY[part]<offset> or BODY[part]
+
+ // Offset is next atom
+ CImapAtom* offset=p->ToNextL();
+
+ // there may not be an offset in which case offset is
+ // actually bodydata
+
+ // Get the offset
+ TUint offsetn=0;
+ // see if this is an offset
+ if( offset->Child() != NULL )
+ {
+ TLex8 lex(offset->Child()->Atom());
+ if( lex.Val(offsetn)!=KErrNone )
+ {
+ error=KErrGeneral;
+ break;
+ }
+
+ // Body data is next atom
+ bodydata=offset->Next();
+ }
+ else
+ {
+ // if not an offset then this is not a partial
+ // message
+ bodydata=offset;
+ wholeMessage=ETrue;
+ }
+
+ // Additional code added to address additional unrequested non-RFC data sent by the
+ // imap server caused problems in downloading the email and any attachments.
+ // Upto this point the BODY tag has correctly been interpreted.
+ //
+ // We may however have an unwanted MIME header data which we will need to process or get rid of.
+ // If we have an unwanted mime header then we currently have the bodydata atom storing:
+ //
+ // BODY[x.MIME]<offset>
+ // MIME_HEADER_INFO
+ // BODY_DATA
+ //
+ // or
+ //
+ // BODY.PEEK[x.MIME]<offset>
+ // BODY_DATA
+ //
+ // The problem here is that the BODY or BODY.PEEK tag will be read as the actual body data.
+ // We need to move the bodydata pointer to the beginning of the MIME_HEADER_INFO.
+ // When decoding the information we will send the data after the header info to be decoded.
+
+ CImapAtom* nextbodypart = NULL;
+ CImapAtom* mimetest =NULL;
+
+ // Check if the unwanted body tag is "BODY" or "BODY.PEEK"
+ if((bodydata->Compare(KIMAP_BODY)) || (bodydata->Compare(KIMAP_BODYPEEK))) //we have an extra body tag
+ {
+ //find out if it is MIME
+ nextbodypart=bodydata->Next();
+ if(nextbodypart)
+ {
+ mimetest = nextbodypart->Child();
+ if(mimetest)
+ {
+ if (mimetest->CompareTail(KIMAP_MIME))
+ {
+
+ // Unrequested tag is "BODY.PEEK"
+ if((bodydata->Compare(KIMAP_BODYPEEK)))
+ {
+ LogText(_L8("UNEXPECTED RESPONSE: TAG \"BODY.PEEK\"- Found unwanted additional \"BODY.PEEK\" tag in the FETCH response"));
+ LogText(_L8("PROCESSING UNEXPECTED RESPONSE: TAG \"BODY.PEEK\" - Additional MIME header data NOT expected to PREFIX the bodypart - unlike additional \"BODY\" tag"));
+ // Not expecting additional / unwanted mime header info at the beginning of the body of the message part
+ // Hence, no additional processing required on body data
+ foundUnwantedMimeHeader=EFalse;
+ }
+ else // Unrequested tag is "BODY"
+ {
+ LogText(_L8("UNEXPECTED RESPONSE: TAG \"BODY\" Found unwanted additional \"BODY\" tag in the FETCH response"));
+ LogText(_L8("PROCESSING UNEXPECTED RESPONSE: TAG \"BODY\"- Expecting UNWANTED MIME HEADER DATA prefixing the BODY DATA of the message part"));
+ // Expecting additional / unwanted mime header info at the beginning of the body data of the message part.
+ // Hence, the atom pointed to by bodydata pointer will be parsed / truncated appropriately to extract just
+ // the bodypart later.
+ foundUnwantedMimeHeader=ETrue;
+ }
+ //we may have an offset that we need to ignore
+ CImapAtom* possOffset = nextbodypart->Next();
+ TLex8 lex(possOffset->Atom());
+ if (lex.Get()=='<') //has an offset
+ {
+ // Body data is next atom
+ bodydata=possOffset->Next();
+ }
+ else
+ {
+ bodydata=possOffset;
+ }
+ }
+ }
+ }
+ }//end of code addressing
+
+
+#ifdef PRINTING
+ TPtrC8 bpt=bodypart->Atom();
+ LogText(_L8("Found body part [%S] iSizeWait %d"),&bpt,iSizeWait);
+#endif
+ if (iSizeWait && bodydata!=NULL)
+ {
+ // No longer waiting for the size
+ iSizeWait=EFalse;
+
+ // Size of this part
+ TUint sizen=bodydata->Atom().Length();
+
+ DBG((LogText(_L8(" offset=%d, length=%d"),offsetn,sizen)));
+ TInt fetchSize = fetchSizeBytes;
+
+ // In CC:Mail workaround mode?
+ if (iTalkingToCCMail || iTalkingToOpenMail)
+ {
+ // How much message is there left to fetch?
+ TInt sizeleft=iSizeOfThisPart-(offsetn+sizen);
+
+ if (sizeleft>0)
+ {
+ if( iState != EImapStateFetchCancelWait )
+ {
+ // Limit chunk size
+ if(iFetchPartialMail)
+ {
+ fetchSize = GetFetchSizeL(sizeleft,offsetn+sizen);
+ if(fetchSize > fetchSizeBytes )
+ {
+ fetchSize = fetchSizeBytes;
+ }
+ }
+ else
+ {
+ if (sizeleft>fetchSizeBytes)
+ {
+ fetchSize=fetchSizeBytes;
+ }
+ }
+ // Issue new fetch command
+ NewTag();
+ TPtrC8 bp(bodypart->Atom());
+ if (iServiceSettings->UpdatingSeenFlags())
+ {
+ iImapIO->SendL(iStatus,KImapFetchBodyPeek,
+ iTag,iMessageFetching,&bp,(offsetn+sizen),sizeleft);
+ }
+ else
+ {
+ iImapIO->SendL(iStatus,KImapFetchBody,
+ iTag,iMessageFetching,&bp,(offsetn+sizen),sizeleft);
+ }
+
+ NewTagSent();
+
+ // Get the rest of this line uninterrupted
+ error=KErrWrite;
+ }
+ }
+ else
+ {
+ // Got the whole message
+ TInt sizeleft=iSizeOfThisPart-(offsetn+sizen);
+ if(iFetchPartialMail && (sizeleft || !iHtmlEntryPart))
+ {
+ ProcessFooterMessageL(sizeleft);
+ }
+ }
+ }
+ else
+ {
+ // Anything more to get? We decide this on wether we got near to
+ // our requested packet size on the last fetch: if we were within
+ // 100 bytes of the requested size, we ask for another load just
+ // in case the server is serving us line by line. Otherwise, we
+ // assume that was the end of the data and flush it out.
+
+ // Check whether we have downloaded the message completely or not,
+ // before sending the FETCH command again.
+ iSizeLeftToFetch = iSizeOfThisPart-(offsetn+sizen);
+ if ((fetchSizeBytes-sizen)<100 && iSizeLeftToFetch>0)
+ {
+ if( iState != EImapStateFetchCancelWait )
+ {
+ TInt sizeleft=iSizeOfThisPart-(offsetn+sizen);
+ fetchSize=sizeleft;
+ if(iFetchPartialMail)
+ {
+ fetchSize = GetFetchSizeL(sizeleft,offsetn+sizen);
+ }
+ if(fetchSize > fetchSizeBytes)
+ {
+ fetchSize = fetchSizeBytes;
+ }
+ // Yes, issue a new fetch command
+ NewTag();
+ TPtrC8 bp(bodypart->Atom());
+ if (iServiceSettings->UpdatingSeenFlags())
+ {
+ iImapIO->SendL(iStatus,KImapFetchBodyPeek,
+ iTag,iMessageFetching,&bp,(offsetn+sizen),fetchSize);
+
+ }
+ else
+ {
+ iImapIO->SendL(iStatus,KImapFetchBody,
+ iTag,iMessageFetching,&bp,(offsetn+sizen),fetchSize);
+ }
+ NewTagSent();
+
+ // Get the rest of this line uninterrupted
+ error=KErrWrite;
+ }
+ }
+ else
+ {
+ // Got the whole message
+ TInt sizeleft = iSizeOfThisPart-(offsetn+sizen);
+
+ if(iFetchPartialMail && (sizeleft || !iHtmlEntryPart))
+ {
+ ProcessFooterMessageL(sizeleft);
+ }
+ }
+ }
+ }
+
+ // Finish processing here if we didn't get everything
+ if (!iGotWholeLine)
+ break;
+
+ // Skip to next attribute
+ p=bodydata->Next();
+ }
+ }
+ else if (attribute->Compare(KIMAP_BODYSTRUCTURE))
+ {
+ // Body structure: Save it until later when we have created
+ // the message - then we can create the attachment tree
+ // underneath it
+ structure=p->ToChildL();
+
+ // Skip to next attribute
+ p=p->Next();
+ }
+ else if (attribute->Compare(KIMAP_FLAGS))
+ {
+ // Process flag list later
+ flags=p;
+
+ // Skip to next attribute
+ p=p->Next();
+ }
+ else if (attribute->Compare(KIMAP_RFC822SIZE))
+ {
+ // Save total message size
+ if (p->Value(rfc822size)!=KErrNone)
+ User::Leave(KErrGeneral);
+
+ // Skip to next attribute
+ p=p->Next();
+ }
+
+ else
+ {
+#ifdef PRINTING
+ TPtrC8 att=attribute->Atom();
+ LogText(_L8("Unknown attribute '%S'"),&att);
+ //showtree(attribute,0);
+#endif
+ }
+ }
+
+ DBG((LogText(_L8("About to process, error=%d, iGotWholeLine=%d, iSyncState=%d, bodydata=%x, wholeMessage=%d"),
+ error,iGotWholeLine,iSyncState,(int)bodydata,wholeMessage)));
+
+ // No error?
+ if ((error==KErrNotReady || error==KErrWrite) && iGotWholeLine)
+ {
+ // check to see if uid is present in server message. If not present, we are getting a
+ // message flag update
+ if(!iFoundUid)
+ {
+#ifdef PRINTING
+ DBG((LogText(_L8("UID not present in Fetch, so process flags"))));
+#endif
+
+ // this is just a message flag update
+
+ TInt msgnr = aMsgnr;
+
+ // if the aMsgnr index into the local message array points to a valid message entry
+ if((msgnr < iFolderIndex.Size()) && (iFolderIndex[aMsgnr-1].iMsvId != 0))
+ {
+
+#ifdef PRINTING
+ for(TInt i=0; i<iFolderIndex.Size(); i++)
+ {
+ DBG((LogText(_L8("iFolderIndex[%d].iMsvId=%d"), i, iFolderIndex[i].iMsvId)));
+ }
+#endif
+ // Mirror flags and ensure that the message is visible
+ // as it might have been made invisible by
+ // unsubscribing.
+
+ // set the current entry to be the message pointed to by aMsgnr
+ SetEntryL(iFolderIndex[aMsgnr - 1].iMsvId);
+
+#ifdef PRINTING
+ DBG((LogText(_L8("SetEntry for aMsgnr: %d with msvid %d"), aMsgnr-1, iFolderIndex[aMsgnr-1].iMsvId)));
+#endif
+
+ TMsvEmailEntry message=iEntry->Entry();
+
+ // since there is no uid associated with this server response, we just need to update flags
+ if (ProcessFlagsL(flags,message)|| !message.Visible())
+ {
+ message.SetVisible(ETrue);
+ ChangeEntryL(message);
+ }
+
+#ifdef PRINTING
+ DBG((LogText(_L8("check for deleted imap 4 flags"))));
+#endif
+
+ if (message.DeletedIMAP4Flag())
+ {
+ iRemoteMessagesDeleteTagged++;
+ }
+ }
+
+ return(KErrNotReady);
+ }
+
+ // What synchronisation state are we in?
+ switch(iSyncState)
+ {
+ case ENotSyncing:
+ // Ignore it
+ break;
+
+ case EFetching:
+ {
+ if (header)
+ {
+ ProcessHeaderExtraL(NULL,iAttachmentMimeInfo,NULL,header->Atom());
+
+ // Store CImMimeHeader info
+ SetEntryL(iMessageId);
+ CMsvStore* entryStore=iEntry->EditStoreL();
+ CleanupStack::PushL(entryStore);
+ iAttachmentMimeInfo->StoreL(*entryStore);
+ entryStore->CommitL();
+ CleanupStack::PopAndDestroy(entryStore);
+ }
+
+ // Now, decode the body data and store it: completion can't be
+ // indicated by 'complete' as we may have been called with a partial line,
+ // so we just rely on the same 'early a full buffer' indicator as
+ // we do when issuing the pipelined fetches above
+
+ TBool endOfStream = EFalse;
+ if (iTalkingToCCMail || iTalkingToOpenMail)
+ {
+ // As we'll never get the 0 byte terminating read with CC:mail, we have to use
+ // our own definition of "end of file", which is simply a packet smaller than
+ // the maximum fetch size
+ if (bodydata)
+ {
+ endOfStream=bodydata->Atom().Length()!=fetchSizeBytes;
+ DecodeAndStoreL(bodydata->Atom(),endOfStream);
+ }
+ }
+ else
+ {
+ // Here, we'll treat anything 100 or more bytes shy of the maximum packet size as
+ // end of file. If we're closer than that, a new read will have been issued which will
+ // return 0 bytes, which *will* cause the EOF to be signalled.
+ if (bodydata)
+ {
+ endOfStream=wholeMessage || (!((fetchSizeBytes-bodydata->Atom().Length())<100) || (iSizeLeftToFetch == 0));
+
+ // depending on whether or not we found an unwanted mime header we need to get rid of it.
+ // This is done in response to the imap server issues where the server was sending additional information.
+ // At this point we know how big the extra header is and we can calculate the correct size of the bodydata by subtracting the
+ // size of the header.
+ if(foundUnwantedMimeHeader)
+ {
+ foundUnwantedMimeHeader=EFalse;
+ //calculate size of actual bodydata without the additional header
+ TInt length = bodydata->Atom().Length() - header->Atom().Length();
+ //call decode and store, but only pass in the data we are interested in i.e.: skip the additional header.
+ DecodeAndStoreL(bodydata->Atom().Right(length),endOfStream);
+ }
+ else //bodydata only points to the data
+ {
+ DecodeAndStoreL(bodydata->Atom(),endOfStream);
+ }
+ }
+ }
+
+ // Update flags on message: it should have been marked as read if it wasn't
+ // already
+ if (flags || endOfStream && ( iState != EImapStateFetchCancelWait ))
+ {
+ // SJM 19990922: Previously this only set the Unread
+ // flag in a rather inefficient way. Change to use new
+ // return value of ProcessFlags
+ SetEntryL(iMessageId);
+ TMsvEmailEntry message=iEntry->Entry();
+
+ TBool hasBodyText = message.iType == KUidMsvEmailTextEntry || message.iType == KUidMsvEmailHtmlEntry;
+ TBool partiallyDownloaded = EFalse;
+ if (endOfStream)
+ {
+ message.SetComplete(ETrue);
+ if(iFetchPartialMail && iBodyPartRemainingSize && message.iType == KUidMsvEmailTextEntry)
+ {
+ message.SetPartialDownloaded(ETrue);
+ partiallyDownloaded = ETrue;
+ }
+ else
+ {
+ message.SetPartialDownloaded(EFalse);
+ }
+
+ if (hasBodyText)
+ message.SetBodyTextComplete(ETrue);
+ }
+
+ // Process flags in this fetch response
+ TBool changed = EFalse;
+ if (flags)
+ changed = ProcessFlagsL(flags,message);
+
+ if (changed || endOfStream)
+ ChangeEntryBulkL(message);
+
+ if (endOfStream)
+ {
+ //iMessagePartsFetchOK++;
+ PropagateCompleteFlagL(iMessageId, hasBodyText, partiallyDownloaded);
+ }
+ }
+ break;
+ }
+
+ case ESyncListNew:
+ case ESyncOld:
+ // Got a message's details
+
+ // check folder position is not out of bounds and that we have not run out of the local index.
+ if (iFolderPosition >= iFolderIndex.Size())
+ {
+ // All done/ array was out of bounds
+ DBG((LogText(_L8("ERROR - Position %d was out of bounds for the FolderIndex arrays size (%d)"),iFolderPosition,iFolderIndex.Size())));
+ iSyncState = ENotSyncing;
+ break;
+ }
+
+ // Collecting UIDs of messages in the folder
+ DBG((LogText(_L8("At pos %d, expecting UID %u, got UID %u"),
+ iFolderPosition,iFolderIndex[iFolderPosition].iUid,iMessageUid)));
+
+ // Messages deleted from remote mailbox?
+ while(iFolderPosition<iFolderIndex.Size() &&
+ iMessageUid>iFolderIndex[iFolderPosition].iUid)
+ {
+ // Orphan this message
+ DBG((LogText(_L8("Orphaning UID %u"),iFolderIndex[iFolderPosition].iUid)));
+
+ if (iFolderIndex[iFolderPosition].iUid != KIllegalUID)
+ {
+ // Do it
+ OrphanMessageL(iFolderIndex[iFolderPosition].iMsvId);
+ }
+ else
+ {
+ DBG((LogText(_L8("Illegal UID, do not delete."))));
+ }
+
+ // Remove it from the index
+ iFolderIndex.Expunge(iFolderPosition+1);
+
+ // Increment stats
+ iOrphanedMessages++;
+ }
+
+ // Run out of local index?
+ if (iFolderPosition>=iFolderIndex.Size())
+ {
+ // All done
+ iSyncState=ENotSyncing;
+ }
+ // In sync again?
+ else if (iMessageUid==iFolderIndex[iFolderPosition].iUid)
+ {
+ // Fine, mirror flag information onto local message
+ DBG((LogText(_L8("Match UID %u, mirroring flags"),iMessageUid)));
+
+ // Mirror flags and ensure that the message is visible
+ // as it might have been made invisible by
+ // unsubscribing.
+ SetEntryL(iFolderIndex[iFolderPosition].iMsvId);
+ TMsvEmailEntry message=iEntry->Entry();
+ if (ProcessFlagsL(flags,message)|| !message.Visible())
+ {
+ message.SetVisible(ETrue);
+ ChangeEntryBulkL(message);
+ }
+
+ if (message.DeletedIMAP4Flag())
+ iRemoteMessagesDeleteTagged++;
+
+ // Next message
+ iFolderPosition++;
+
+ // Update counters.
+ iMsgsDone++;
+ iHeadersFetched++;
+ }
+ else if (iMessageUid<iHighestUid)
+ {
+ // If we are seeing uids below the oldest mirrored message, then it is likely
+ // that the sync limit has changed. The following code captures the range of
+ // "missing" messages.
+ if (iMessageUid<iMissingUidLow || iMissingUidLow==0)
+ iMissingUidLow=iMessageUid;
+ if (iMessageUid>iMissingUidHigh || iMissingUidHigh==0)
+ iMissingUidHigh=iMessageUid;
+ }
+ break;
+
+ case EGettingStructure:
+ {
+ error = KErrNone;
+ User::LeaveIfError(iEntry->SetEntry(iGetPart));
+
+ if (!(iEntry->Entry().Owner()))
+ {
+ TMsvEmailEntry entry = iEntry->Entry();
+
+ //reset flag for new message
+ iParsedTime=EFalse;
+ //initialise the time-stamp to the current UTC time
+ entry.iDate.UniversalTime();
+
+ // Make a CImHeader to populate with the data
+ CImHeader *messageheader=CImHeader::NewLC();
+ if (header)
+ ProcessHeaderExtraL(messageheader,NULL,&entry,header->Atom());
+
+ // Set correct 'remote size' in CImHeader
+ messageheader->SetRemoteSize(rfc822size);
+
+ // Create a message store.
+ CMsvStore* entryStore=iEntry->EditStoreL();
+ CleanupStack::PushL(entryStore);
+
+ // Store the RFC822 header information.
+ messageheader->StoreL(*entryStore);
+ entryStore->CommitL();
+ CleanupStack::PopAndDestroy(entryStore);
+
+ TInt attachments=0;
+ TInt relatedAttachments=0;
+ TBool isMHTML=EFalse;
+ iDecodedSizeOfAllParts = 0;
+
+ // Create the message entry structure under the root message
+ BuildTreeL(entry.Id(),structure,_L8(""),entry.Id(),attachments,isMHTML,relatedAttachments);
+ if(isMHTML==EFalse)
+ attachments+=relatedAttachments;
+
+ // Now that the structure has been created we can set the real message attributes.
+ // The MHTML, attachment flags and size were estimated (hopefully correctly) when the envelope was downloaded.
+ entry.iSize = iDecodedSizeOfAllParts;
+ entry.SetMHTMLEmail(isMHTML);
+ entry.SetAttachment(attachments);
+ entry.SetICalendar(iIsICalendar);
+ entry.SetVCalendar(iIsVCalendar);
+
+ /* If IDLE is enabled for the account , a new session is not created,
+ so iIsICalendar,iIsVCalendar are not initialised to EFalse
+ when we fetch a new message, so do it HERE */
+
+ iIsICalendar = EFalse;
+ iIsVCalendar = EFalse;
+
+ User::LeaveIfError(iEntry->SetEntry(entry.Id()));
+ User::LeaveIfError(iEntry->ChangeEntryBulk(entry));
+ CleanupStack::PopAndDestroy(messageheader);
+ }
+
+ if (ImapIdleSupported()==EFalse)
+ {
+ CancelDummy();
+ }
+
+ DoFetchL();
+ iJustSentFetch = ETrue;
+ }
+ break;
+
+ case ESyncNew:
+ {
+ // Got a message's details
+
+ // First, let's check we asked for it: for example, the UW server
+ // gives us messages not in the correct range!
+ if (iMessageUid<=iHighestUid)
+ {
+ DBG((LogText(_L8("Searching local messages for %d"),iMessageUid)));
+ while (iFolderPosition<iFolderIndex.Size())
+ {
+ if (iMessageUid==iFolderIndex[iFolderPosition].iUid)
+ {
+ DBG((LogText(_L8("Got duplicate UID %d - ignoring"),iMessageUid)));
+ return(error);
+ }
+ else if (iFolderIndex[iFolderPosition].iUid>iMessageUid || iFolderIndex[iFolderPosition].iMsvId==-1)
+ break;
+ iFolderPosition++;
+ }
+ }
+
+ // Update counters.
+ iMsgsDone++;
+ iHeadersFetched++;
+
+ // Creating messages in current folder: create this one
+ SetEntryL(iMailboxId);
+
+ // Check to see we have at least the minimum free disk space available
+ if (--iCheckDiskSpaceCounter <= 0)
+ {
+ // If we are running low on disk space then leave
+ ImCheckDiskSpace::LeaveIfLowDiskL(iFs, iCurrentDrive);
+ iCheckDiskSpaceCounter = KCheckDiskSpaceEveryNMessages;
+ }
+
+ TMsvEmailEntry message;
+ message.iType=KUidMsvMessageEntry;
+ message.iMtm=KUidMsgTypeIMAP4;
+ message.iServiceId=iServiceId;
+ message.SetUID(iMessageUid);
+ message.SetValidUID(ETrue);
+ message.SetComplete(EFalse);
+ message.SetUnread(ETrue);
+
+ //reset flag for new message
+ iParsedTime=EFalse;
+ //initialise the time-stamp to the current UTC time
+ message.iDate.UniversalTime();
+
+ // Process message flags
+ ProcessFlagsL(flags,message);
+
+ if (message.DeletedIMAP4Flag())
+ iRemoteMessagesDeleteTagged++;
+
+ // Size of root message entry gets twiddled later
+ message.iSize=0;
+
+ // Set new flag
+ message.SetNew(ETrue);
+
+ // initialise the send state since the constructor sets it
+ // to StateUnknown
+ message.SetSendingState(KMsvSendStateNotApplicable);
+
+ // Make a CImHeader to populate with the data
+ CImHeader *messageheader=CImHeader::NewLC();
+
+ if (header)
+ {
+ ProcessHeaderExtraL(messageheader,NULL,&message,header->Atom());
+ }
+
+ // Set correct 'remote size' in CImHeader
+ messageheader->SetRemoteSize(rfc822size);
+
+ // Save message size & attachment flag
+ SetMessageFlagsL(message, structure);
+
+ // Create message
+ User::LeaveIfError(iEntry->CreateEntryBulk(message));
+ SetEntryL(iMessageId=message.Id());
+
+
+#if SET_RELATED_ID
+ // DS - Set message's iRelatedId to messageId to allow later UI kludges
+ message.iRelatedId=iMessageId;
+#endif
+ ChangeEntryBulkL(message);
+ CleanupStack::PopAndDestroy(messageheader);
+ break;
+ }
+ default:
+ // ESyncListNew and ESyncSearch should not result in a FETCH reply.
+ __ASSERT_DEBUG(EFalse,gPanic(EUnknownState));
+ break;
+ }
+ }
+
+ return(error);
+ }
+
+// code originally from void CImRecvConvert::ParseRecipientListL(...)
+void CImImap4Session::ProcessAddressListL(CDesCArray& aWhere, HBufC8** aAddresses)
+ {
+ TInt length((*aAddresses)->Length());
+ HBufC8* pBuf=HBufC8::NewLC(length);
+ TPtrC8 source((*aAddresses)->Ptr(), length);
+ const TUint8* ptr(source.Ptr());
+ const TUint8* lastCharPtr(ptr + source.Length() - 1);
+ TUint8 lookFor(0);
+ TInt count(0);
+ TBool finishedEntry(EFalse);
+
+ // get past white space
+ while(*ptr&&((*ptr==KImcvSP)||(*ptr==KImcvSemiColon))) ptr++;
+
+ // Entries are separated by commas or semicolons.
+ // Separators do not count if they appear within
+ // "", <>, () or embedded series of these, eg "(one, two)"
+ // so we need to keep track of these, including nesting.
+ while(*ptr && ptr <= lastCharPtr)
+ {
+ if(pBuf->Length()==0)
+ {
+ finishedEntry = EFalse;
+ }
+
+ switch(*ptr)
+ {
+ case KImcvLeftBracket:
+ if(lookFor==KImcvRightBracket)
+ { // We've already had a "(", so now we need another one
+ count++;
+ }
+ else if(lookFor==0)
+ { //We weren't looking for anything else, now we need to
+ lookFor = KImcvRightBracket;
+ count = 1;
+ }
+ // else we were already looking for something else, ignore this
+ break;
+ case KImcvLeftChevron:
+ if(lookFor==KImcvRightChevron)
+ { //We've already had a "<", so now we need another one
+ count++;
+ }
+ else if(lookFor==0)
+ { //We weren't looking for anything else
+ lookFor = KImcvRightChevron;
+ count = 1;
+ }
+ // else we were already looking for something else, ignore this
+ break;
+ case KImcvDoubleQuote:
+ if(lookFor==KImcvDoubleQuote)
+ { // We already had a quote, so this matches it
+ lookFor = 0;
+ }
+ else if(lookFor==0)
+ { //We weren't looking for anything else
+ lookFor = KImcvDoubleQuote;
+ }
+ // else we were already looking for something else, ignore this
+ break;
+ case KImcvRightBracket:
+ case KImcvRightChevron:
+ if(*ptr == lookFor)
+ { //If we have found what we were looking for, decrease the count
+ count--;
+ if(count==0)
+ { // Got everything, now we're not looking for anything
+ lookFor = 0;
+ }
+ // else keep looking for the same thing again
+ }
+ // else we're looking for something else, ignore it
+ break;
+ case KImcvComma:
+ case KImcvSemiColon:
+ // If we're not looking for anything, we're finished
+ if (lookFor == 0)
+ finishedEntry = ETrue;
+ // else this comma or semicolon is part of a different token, ignore it
+ break;
+ }
+
+ if(!finishedEntry)
+ {
+ pBuf->Des().Append((TChar)*ptr);
+ // move to the next character
+ ptr++;
+ }
+ else
+ {
+ // that's it! store the address away
+#ifdef UNICODE
+ HBufC16* pBuf16 = HBufC16::NewLC(pBuf->Des().Length());
+ pBuf16->Des().Copy(pBuf->Des());
+ aWhere.AppendL( (HBufC16&) *pBuf16 );
+ CleanupStack::PopAndDestroy(pBuf16); // pBuf16
+#else
+ aWhere.AppendL( *pBuf );
+#endif
+ pBuf->Des().SetLength(0);
+ finishedEntry = EFalse; //Ready for next entry
+
+ // get past the separator
+ ptr++;
+
+ // get past white space (& any other separators)
+ while(*ptr && (*ptr==KImcvSP || *ptr==KImcvTab || *ptr==KImcvComma || *ptr==KImcvSemiColon)) ptr++;
+ }
+ }
+ // catch the last name in the list
+ if (pBuf)
+ {
+ TInt recipientLength(pBuf->Length());
+ if (recipientLength > 0)
+ {
+#ifdef UNICODE
+ HBufC16* pBuf16 = HBufC16::NewLC(recipientLength);
+ pBuf16->Des().Copy(*pBuf);
+ aWhere.AppendL(*pBuf16);
+ CleanupStack::PopAndDestroy(pBuf16); // pBuf16
+#else
+ aWhere.AppendL( *pBuf );
+#endif
+ }
+ }
+ CleanupStack::PopAndDestroy(pBuf); // pBuf
+ }
+
+void CImImap4Session::ProcessFooterMessageL(TInt aSizeLeft)
+ {
+ TUid type = iEntry->Entry().iType;
+ if (type == KUidMsvEmailTextEntry)
+ {
+ if(iHtmlEntrySize)
+ {
+ iBodyPartRemainingSize = aSizeLeft + iHtmlEntrySize;
+ }
+ else
+ {
+ iBodyPartRemainingSize = aSizeLeft;
+ }
+ // Message has both text and html and if sizeleft = 0 ,
+ // then there could be only html part left on server
+ // Message has only plain text, then footer message not required if sizeleft = 0
+ if(iBodyPartRemainingSize)
+ AttachFooterInfoL();
+ }
+ }
+
+void CImImap4Session::AttachFooterInfoL()
+ {
+ DBG((LogText(_L8("AttachFooterInfoL(): Footer Sting for this partially downloaded message"))));
+ RResourceFile resFile;
+ OpenResourceFileL(resFile,iFs); // NB leaves if file not found
+ const TInt KNoOfDigitsInMailSize = 10; // maximum length for no of digits for remaining body parts size
+ _LIT(KIntegerDirective,"%d");
+ // make sure the resource file will be closed if anything goes wrong
+ // CloseResourceFile is declared in IMCMMAIN.H and defined in IMCMMAIN.CPP
+ TCleanupItem close(CloseResourceFile,&resFile);
+ CleanupStack::PushL(close);
+
+ // Read the string for remaining mail size for footer
+ HBufC8* buf = NULL;
+ buf = resFile.AllocReadLC( PARTIAL_DOWNLOAD_FOOTER_MESSAGE );
+ TResourceReader reader;
+ reader.SetBuffer(buf);
+ // Check if %d is not found in the resource string (To avoid problems due to localisation)
+ if(buf->Find((TDesC8&)KIntegerDirective) == KErrNotFound)
+ {
+ iFooterString = (reader.ReadTPtrC()).AllocL();
+ }
+ else
+ {
+ HBufC* resourceBuf = (reader.ReadTPtrC()).AllocL();
+ iFooterString = HBufC::NewL(resourceBuf->Length()+KNoOfDigitsInMailSize);
+ iFooterString->Des().Format(*resourceBuf,(iBodyPartRemainingSize / KKiloByteSize));
+ delete resourceBuf;
+ }
+ CleanupStack::PopAndDestroy(2); // buf, resFile (Close resfile)
+ }
+
+// Set the following attributes of the given message entry for the IMAP information contained in atom tree:
+// iSize
+//
+void CImImap4Session::SetMessageFlagsL(TMsvEmailEntry& aMessageEntry, CImapAtom* aRootAtom)
+ {
+ CArrayFixFlat<CImapAtom*>* atomStack = new (ELeave) CArrayFixFlat<CImapAtom*>(10);
+ CleanupStack::PushL(atomStack);
+ atomStack->AppendL(aRootAtom);
+
+ TBool hasAttachments = EFalse;
+ TBool hasHtml = EFalse;
+ TBool possibleHtml = EFalse;
+ TBool afterRelated = EFalse;
+ TBool afterAlternative = EFalse;
+ TBool htmlAfterAltRel = EFalse;
+ TBool hasICalendar = EFalse;
+ TBool hasVCalendar = EFalse;
+ TInt size = 0;
+
+ CImapAtom* currentAtom;
+
+ TBool doneAllSiblings;
+ TBool base64;
+ TInt index;
+ TBool numeric;
+ TInt atomValue;
+ TBool foundSize;
+
+ // the root atom is of these types, the message body is an attachment
+ if(aRootAtom->Compare(KMIME_IMAGE) ||
+ aRootAtom->Compare(KMIME_AUDIO) ||
+ aRootAtom->Compare(KMIME_APPLICATION)||
+ aRootAtom->Compare(KMIME_VIDEO))
+ {
+ hasAttachments = ETrue;
+ }
+
+ while (atomStack->Count() != 0)
+ {
+ // Pop the top atom off of the stack
+ currentAtom = (*atomStack)[atomStack->Count() - 1];
+ atomStack->ResizeL(atomStack->Count() - 1);
+ base64 = EFalse;
+
+ // Run through all the sibling atoms unless this atom is a message/rfc822
+ if( currentAtom->Compare(KMIME_MESSAGE) && currentAtom->Next()->Compare(KMIME_RFC822) )
+ doneAllSiblings = ETrue;
+ else
+ doneAllSiblings = EFalse;
+ foundSize = EFalse;
+ possibleHtml = EFalse;
+ TInt siblingIndex = 0;
+ while (!doneAllSiblings)
+ {
+ // If a previous Sibling Atom has a HTML tag, then this could possibly
+ // be an HTML message. So we need to check all Sibling Atoms (ie the
+ // current atom) to see if they contain Attachments (ie check their
+ // child atoms for the Tag "ATTACHMENT"). If an attachment is found
+ // then ignore the HTML flag. If none of the siblings contain an
+ // attachment, then set the HTML flag, as this is a MHTML message.
+ if (possibleHtml)
+ {
+ // Check if the Child has an Attachment
+ if (currentAtom->Child() != NULL)
+ {
+ if (currentAtom->Child()->Compare(KMIME_ATTACHMENT))
+ {
+ // This Sibling contains an Attachment, so ignore the HTML flag.
+ possibleHtml = EFalse;
+ }
+ }
+
+ // Check if we have searched all Sibling Atoms
+ if (possibleHtml && currentAtom->Next() == NULL)
+ {
+ // None of the Siblings have attachments, so set the HTML flag.
+ hasHtml = ETrue;
+ possibleHtml = EFalse;
+ }
+
+ }
+
+ // If there is a child atom then add it to the stack, we will check it later
+ if (currentAtom->Child() != NULL)
+ atomStack->AppendL(currentAtom->Child());
+
+ // If we pass a related atom then we may have an html message
+ // remember for later
+ if (currentAtom->Compare(KMIME_RELATED))
+ afterRelated = ETrue;
+
+ // If we pass an alternative atom then we may have an html message
+ // remember for later
+ if (currentAtom->Compare(KMIME_ALTERNATIVE))
+ afterAlternative = ETrue;
+
+ // If we find an html under a related or alternative then we
+ // can ignore the mixed section and assume that we have an html mail
+ if (afterAlternative || afterRelated)
+ {
+ if (currentAtom->Compare(KMIME_HTML))
+ htmlAfterAltRel = ETrue;
+ }
+
+ // MIXED ? If so then this email probably contains attachments
+ // or if there is a message/delivery-status then that is an
+ // attachment.
+ if (currentAtom->Compare(KMIME_MIXED) || currentAtom->Compare(KMIME_DELIVERY_STATUS))
+ {
+ hasAttachments = ETrue;
+ }
+
+ // HTML ? If so this email could be an HTML message. To make sure
+ // we need to check this Atom's siblings
+ if (currentAtom->Compare(KMIME_HTML))
+ possibleHtml = ETrue;
+
+ // Does this sibling atom say that the data is base64 encoded ?
+ // If so then we need to remember it to calculate the size.
+ if (currentAtom->Compare(KMIME_BASE64))
+ base64 = ETrue;
+
+ if (currentAtom->Compare(KMIME_ICALENDAR))
+ hasICalendar = ETrue;
+
+ if (currentAtom->Compare(KMIME_VCALENDAR))
+ hasVCalendar = ETrue;
+
+ // If this is the first numeric value of the current siblings then it is a size
+ // and must be added to the message size total
+ // Note that this size must be multiplied by 3/4 if it is base64
+ if (!foundSize && siblingIndex == 6)
+ {
+ index = currentAtom->Atom().Length();
+
+ if (index != 0)
+ numeric = ETrue;
+ else
+ // If the atom is of 0 length then it can't possibly be numeric.
+ numeric = EFalse;
+
+ while ((index--) && (numeric))
+ {
+ if ((currentAtom->Atom()[index] < '0')
+ || (currentAtom->Atom()[index] > '9'))
+ numeric = EFalse;
+ }
+
+ if (numeric)
+ {
+ TLex8 lex(currentAtom->Atom());
+ User::LeaveIfError(lex.Val(atomValue));
+ if (base64)
+ atomValue = (atomValue * 3) / 4;
+ size += atomValue;
+ foundSize = ETrue;
+ }
+ }
+
+ siblingIndex++;
+ currentAtom = currentAtom->Next();
+ if (currentAtom == NULL)
+ doneAllSiblings = ETrue;
+ }
+ }
+
+ // Set the size
+ aMessageEntry.iSize = size;
+
+ // Set the Attachment, MHTML, ICalendar and VCalendar flags, if required.
+ if (hasAttachments)
+ {
+ aMessageEntry.SetAttachment(ETrue);
+ }
+
+ if( hasHtml || htmlAfterAltRel )
+ {
+ aMessageEntry.SetMHTMLEmail(ETrue);
+ }
+
+ if(hasICalendar)
+ {
+ aMessageEntry.SetICalendar(ETrue);
+ }
+
+ if(hasVCalendar)
+ {
+ aMessageEntry.SetVCalendar(ETrue);
+ }
+
+ CleanupStack::PopAndDestroy(atomStack);
+ }
+
+TInt32 CImImap4Session::GetFetchSizeL(TInt32 aSizeLeft, TInt32 aSizeDownLoaded)
+{
+ TInt fetchSizeBytes = static_cast<TInt>(iServiceSettings->FetchSize());
+ TInt32 minimumLimit = fetchSizeBytes;
+ TInt32 fetchSize = fetchSizeBytes;
+ TUid type = iEntry->Entry().iType;
+
+ if(iGetPartialMailInfo.iPartialMailOptions == ENoSizeLimits)
+ {
+ return KMaxTInt;
+ }
+
+ if (type == KUidMsvEmailTextEntry || type == KUidMsvEmailHtmlEntry)
+ {
+ if(type == KUidMsvEmailHtmlEntry)
+ {
+// iHtmlEntrySize = iSizeOfThisPart;
+ minimumLimit = Minimum(iGetPartialMailInfo.iBodyTextSizeLimit,iGetPartialMailInfo.iTotalSizeLimit-iBodyTextSize);
+ }
+ else
+ {
+ // store body text size so that we can check whether html part for
+ // this message can be downloaded
+ // text size + html size < iBodyTextSizeLimit then download html part
+ iBodyTextSize = iSizeOfThisPart;
+ minimumLimit = Minimum(iGetPartialMailInfo.iBodyTextSizeLimit,iGetPartialMailInfo.iTotalSizeLimit);
+ }
+ // check disk space
+ if(!iIsDiskSpaceChecked)
+ {
+ CheckForDiskSpaceL(minimumLimit);
+ iIsDiskSpaceChecked = ETrue;
+ }
+
+ fetchSize = FetchSize(minimumLimit,aSizeDownLoaded,aSizeLeft);
+ }
+ else if (type == KUidMsvAttachmentEntry || type == KUidMsvEmailExternalBodyEntry)
+ {
+ minimumLimit = Minimum(iGetPartialMailInfo.iAttachmentSizeLimit,
+ iGetPartialMailInfo.iTotalSizeLimit-(iBodyTextSize+iHtmlEntrySize));
+
+ if(!iIsDiskSpaceChecked)
+ {
+ CheckForDiskSpaceL(minimumLimit);
+ iIsDiskSpaceChecked = ETrue;
+ }
+
+ fetchSize = FetchSize(minimumLimit,aSizeDownLoaded,aSizeLeft);
+ }
+ return fetchSize;
+ }
+
+TInt32 CImImap4Session::FetchSize(TInt32 aMinimumLimit,TInt32 aSizeDownLoaded, TInt32 aSizeLeft)
+ {
+ TInt fetchSizeBytes = static_cast<TInt>(iServiceSettings->FetchSize());
+ TInt32 fetchSize = fetchSizeBytes;
+ if(aSizeLeft > (aMinimumLimit-aSizeDownLoaded))
+ {
+ if((aMinimumLimit-aSizeDownLoaded) < fetchSizeBytes)
+ {
+ fetchSize = aMinimumLimit-aSizeDownLoaded;
+ }
+ }
+ else
+ {
+ if(aSizeLeft < fetchSizeBytes)
+ {
+ fetchSize = aSizeLeft;
+ }
+ }
+ return fetchSize;
+ }
+
+// Process an address list structure into a CImHeader
+void CImImap4Session::ProcessAddressListL(HBufC8 **aBufferPtr, CDesCArray& aWhere, CImapAtom *aAtom)
+ {
+ while(aAtom)
+ {
+ // Process this address and add it to ToRecipients()
+ ProcessAddressL(aBufferPtr,aAtom);
+
+#ifdef UNICODE
+ HBufC *newaddress=HBufC::NewL((*aBufferPtr)->Length());
+ CleanupStack::PushL(newaddress);
+ newaddress->Des().Copy((*aBufferPtr)->Des());
+ aWhere.AppendL(newaddress->Des());
+ CleanupStack::PopAndDestroy();
+#else
+ aWhere.AppendL((*aBufferPtr)->Des());
+#endif
+
+ // Next address
+ aAtom=aAtom->Next();
+ }
+ }
+
+void CImImap4Session::AppendExtendL(HBufC8** aBufferPtr, const TDesC8& aText, TBool aOnStack)
+ {
+ HBufC8 *buffer = *aBufferPtr;
+ TInt32 space = buffer->Des().MaxLength() - (buffer->Des().Length() + aText.Length());
+ if (space < 0)
+ {
+ TInt32 inc = (-space) + KImapAddressSizeInc - ((-space) % KImapAddressSizeInc);
+ TInt32 newSize = buffer->Des().MaxLength() + inc;
+ HBufC8 *newBuf = buffer->ReAllocL(newSize);
+
+ if (aOnStack && newBuf!=buffer)
+ {
+ CleanupStack::Pop();
+ CleanupStack::PushL(newBuf);
+ }
+
+ *aBufferPtr = buffer = newBuf;
+ }
+ buffer->Des().Append(aText);
+ }
+
+// Process an address structure into a buffer
+void CImImap4Session::ProcessAddressL(HBufC8** aBufferPtr, CImapAtom *aAtom)
+ {
+ // Erase buffer
+ (*aBufferPtr)->Des().Zero();
+
+ // Descend into address
+ aAtom=aAtom->ToChildL();
+
+ // Save name
+ CImapAtom* name=aAtom;
+ aAtom=aAtom->ToNextL();
+
+ // Skip route
+ aAtom=aAtom->ToNextL();
+
+ // Save user
+ CImapAtom* user=aAtom;
+ aAtom=aAtom->ToNextL();
+
+ // Save host
+ CImapAtom* host=aAtom;
+
+ // Build address string: is there a name?
+ if (name->Compare(_L8("")) || name->Compare(_L8("NIL")))
+ {
+ // No, just save user@host
+ AppendExtendL(aBufferPtr, user->Atom());
+ AppendExtendL(aBufferPtr, _L8("@"));
+ AppendExtendL(aBufferPtr, host->Atom());
+ }
+ else
+ {
+ // Yes, in the form 'Name <user@host>'
+ AppendExtendL(aBufferPtr, _L8("\""));
+ AppendExtendL(aBufferPtr, name->Atom());
+ AppendExtendL(aBufferPtr, _L8("\""));
+ AppendExtendL(aBufferPtr, _L8(" <"));
+ AppendExtendL(aBufferPtr, user->Atom());
+ AppendExtendL(aBufferPtr, _L8("@"));
+ AppendExtendL(aBufferPtr, host->Atom());
+ AppendExtendL(aBufferPtr, _L8(">"));
+ }
+
+#ifdef PRINTING
+ TPtrC8 addr=(*aBufferPtr)->Des();
+ if (addr.Length() > 256)
+ addr.Set(addr.Ptr(), 256);
+ LogText(_L8(" address '%S'"),&addr);
+#endif
+ }
+
+// SJM 19990922. Previous version of this function used SetUnread
+// rather than SetIMAP4Unread or SetIMAP4Flags. This meant that the
+// IMAP4Unread flag itself was never set. This seems like a bug.
+
+TBool CImImap4Session::ProcessFlagsL(CImapAtom* aAtom, TMsvEmailEntry& aMessage)
+ {
+ TBool unread = EFalse;
+ TBool seen = EFalse;
+ TBool answered = EFalse;
+ TBool flagged = EFalse;
+ TBool deleted = EFalse;
+ TBool draft = EFalse;
+ TBool recent = EFalse;
+ TBool flagsUpdated = EFalse;
+
+ // Descend
+ aAtom=aAtom->Child();
+
+ // Process flags
+ while(aAtom!=NULL)
+ {
+ // Check for standard IMAP flags
+ if (aAtom->Compare(KIMAPFLAG_ANSWERED))
+ answered = ETrue;
+ else if (aAtom->Compare(KIMAPFLAG_DELETED))
+ deleted = ETrue;
+ else if (aAtom->Compare(KIMAPFLAG_DRAFT))
+ draft = ETrue;
+ else if (aAtom->Compare(KIMAPFLAG_FLAGGED))
+ flagged = ETrue;
+ else if (aAtom->Compare(KIMAPFLAG_RECENT))
+ recent = ETrue;
+ else if (aAtom->Compare(KIMAPFLAG_SEEN))
+ seen = ETrue;
+ else if (aAtom->Compare(KIMAPFLAG_UNREAD))
+ {
+ unread = ETrue;
+
+ // There is at least one unread message in this folder
+ iSomeUnread=ETrue;
+ }
+
+ // Next atom
+ aAtom=aAtom->Next();
+ }
+
+
+ TBool oUnread, oSeen, oAnswered, oFlagged, oDeleted, oDraft, oRecent;
+ aMessage.GetIMAP4Flags(oUnread, oSeen, oAnswered, oFlagged, oDeleted, oDraft, oRecent);
+
+ // Are we configured to update the \seen flag on the server?
+ if (iServiceSettings->UpdatingSeenFlags())
+ {
+ // Make a note to update the servers \Seen flag if CHANGED on the client
+ // and different to the servers version
+ if ( FIXBOOL(aMessage.Unread()) == seen && FIXBOOL(oSeen) == seen)
+ {
+ if (aMessage.Unread())
+ iClearSeenList->AppendL(aMessage.Id());
+ else
+ iSetSeenList->AppendL(aMessage.Id());
+ }
+ }
+
+ if ( FIXBOOL(oUnread) != unread || FIXBOOL(oSeen) != seen || FIXBOOL(oAnswered) != answered
+ || FIXBOOL(oFlagged) != flagged || FIXBOOL(oDeleted) != deleted
+ || FIXBOOL(oDraft) != draft || FIXBOOL(oRecent) != recent )
+ {
+ aMessage.SetIMAP4Flags(unread, seen, answered, flagged, deleted, draft, recent);
+ flagsUpdated = ETrue;
+ }
+
+ // Are we configured to update the \seen flag on the server?
+ if (iServiceSettings->UpdatingSeenFlags())
+ {
+ // Now copy the inverse of the \Seen flag down to the clients Unread flag
+ // except when LastSyncSeen is set (ie when the client version is more up to date)
+ // This means that the client Read status ALWAYS reflects the IMAP \Seen state
+ if ( FIXBOOL(aMessage.Unread()) == seen && FIXBOOL(oSeen) != seen)
+ {
+ aMessage.SetUnread(!seen);
+ flagsUpdated = ETrue;
+ }
+ }
+
+ return flagsUpdated;
+ }
+
+// Process output from list command
+void CImImap4Session::ProcessListL(CImapAtom *aAtom)
+ {
+ // Just getting hierarchy separator?
+ if (iState==EImapStateSeparatorWait)
+ {
+ // Save it away
+ aAtom=aAtom->ToNextL();
+
+ // Is it one character long? (all should be) - if not, ignore it
+ if (aAtom->Atom().Length()!=1)
+ return;
+
+ // Save it for local use
+ iHierarchySeparator=aAtom->Atom();
+
+ // Update service entry
+ CEmailAccounts* account = CEmailAccounts::NewLC();
+ TImapAccount id;
+ id.iImapAccountId = iEntry->Entry().MtmData2(); // iMtmData2 of the service entry contains TImapAccountId
+ id.iImapAccountName = iEntry->Entry().iDetails;
+ id.iImapService = iEntry->Entry().iServiceId;
+ id.iSmtpService = iEntry->Entry().iRelatedId;
+
+ account->LoadImapSettingsL(id, *iServiceSettings);
+
+ // Set new path separator
+ iServiceSettings->SetPathSeparator(iHierarchySeparator[0]);
+ id.iImapAccountName = KNullDesC; // So that account name is not updated
+ account->SaveImapSettingsL(id, *iServiceSettings);
+ CleanupStack::PopAndDestroy(account);
+ return;
+ }
+
+ // Anywhere to save it?
+ if (!iList) return;
+
+ // Getting hierarchy from server - process the lot
+ CImapAtom *fl;
+ CImImap4DirStruct *entry=new (ELeave) CImImap4DirStruct;
+ CleanupStack::PushL(entry);
+
+ // List reply is of the form: * LIST (flags) "pathsep" "path"
+
+ // At the start, it's a mailbox
+ entry->iIsMailbox=ETrue;
+ entry->iIsFolder=ETrue;
+
+ // Process flags
+ if ((fl=aAtom->Child())!=NULL)
+ {
+ while(fl)
+ {
+ // Check flags
+ if (fl->Compare(KIMAPFLAG_NOSELECT))
+ {
+ // \Noselect means it isn't a mailbox
+ entry->iIsMailbox=EFalse;
+ }
+ else if (fl->Compare(KIMAPFLAG_NOINFERIORS))
+ {
+ // \Noinferiors means it can't be a folder
+ entry->iIsFolder=EFalse;
+ }
+
+ // Next flag
+ fl=fl->Next();
+ }
+ }
+
+ // Path separator: only save this if it is a valid length
+ aAtom=aAtom->ToNextL();
+ if (aAtom->Atom().Length()==1)
+ iHierarchySeparator=aAtom->Atom();
+
+ // Path
+ aAtom=aAtom->ToNextL();
+ TPtrC8 path(aAtom->Atom());
+
+ // In case server has returned us the parent when we asked for children,
+ // ignore items which are too small to be path(separator)child names.
+ if (path.Length()>iCommandBuf.Length())
+ {
+ // CC:Mail has an annoying habit of not listening to us when we ask for
+ // children, and returning siblings instead: this takes some extra CPU
+ // time, so we only do it if we're talking to a CC:Mail server, but here
+ // we check that the returned path is actually what we asked for before
+ // proceding.
+ if (iTalkingToCCMail)
+ {
+ // Check path starts with iCommandBuf's contents
+#ifdef UNICODE
+ HBufC8 *narrow=HBufC8::NewL(iCommandBuf.Length());
+ narrow->Des().Copy(iCommandBuf);
+ if (path.Find(*narrow)!=0)
+ {
+ // Nope. Ignore this. Silly server.
+ delete narrow;
+ CleanupStack::PopAndDestroy();
+ return;
+ }
+ delete narrow;
+#else
+ if (path.Find(iCommandBuf)!=0)
+ {
+ // Nope. Ignore this. Silly server.
+ CleanupStack::PopAndDestroy();
+ return;
+ }
+#endif
+ }
+
+ // Add leaf, ignoring the path that all servers return before
+ // the leaf.
+
+ // Removed the UNICODE special case since we might need to
+ // modify the buffer now to do the UnModUTF7.
+#if 1
+ TPtrC8 leaf = path.Mid(iCommandBuf.Length());
+ HBufC* text = DoUnModUTF7LC( leaf );
+ entry->SetLeafnameL(text->Des());
+
+ CleanupStack::PopAndDestroy();
+#else
+ HBufC *newleaf=HBufC::NewL(path.Length()-iCommandBuf.Length());
+ CleanupStack::PushL(newleaf);
+ newleaf->Des().Copy(path.Mid(iCommandBuf.Length()));
+ entry->SetLeafnameL(newleaf->Des());
+ CleanupStack::PopAndDestroy();
+#endif
+
+ // If there's anything there (might just be the path), then add it to array
+ if (entry->Leafname().Length()>0)
+ {
+ // Add to array
+ iList->AppendL(entry);
+
+ // It's in the array now
+ CleanupStack::Pop();
+ }
+ else
+ {
+ // Get rid of it - it's not needed
+ CleanupStack::PopAndDestroy();
+ }
+ }
+ else
+ {
+ // Get rid of it - it's not needed
+ CleanupStack::PopAndDestroy();
+ }
+ }
+
+// Process output from list command
+void CImImap4Session::ProcessLsubL(CImapAtom *aAtom)
+ {
+ // Process the lot: it'll be a full path which we need to cut up
+ // Reply is of the form: * LSUB (flags) "pathsep" "path"
+
+ // Skip flags
+ aAtom=aAtom->ToNextL();
+
+ // We already know the path separator, so skip it
+ aAtom=aAtom->ToNextL();
+
+ // Strip folder path: to do this we just remove the start of the path from
+ // the returned string, so we need the length of it.
+ TInt pathlength=iFolderPath.Length();
+
+ // If the length is non-zero, this means that there is a folderpath, and
+ // we will need to strip both it, and the following path separator character.
+ // If there is no path, we don't want to strip anything, so we don't
+ // increment it at all.
+ if (pathlength)
+ pathlength++;
+
+ // Get a (possibly wide) copy of the path, skipping the prefix
+ TPtrC8 skippedPrefix(aAtom->Atom().Mid(pathlength));
+ HBufC* basePath = DoUnModUTF7LC( skippedPrefix );
+ TPtrC path(basePath->Des());
+
+ // Go down through path
+ TMsvId where=iServiceId;
+ while(where)
+ {
+ // Truncate this bit of path: look for a hierarchy separator
+ TInt separator=path.Locate(iHierarchySeparator[0]);
+ TPtrC element(path);
+
+ // Anything?
+ if (separator!=KErrNotFound)
+ {
+ // Truncate this element there
+ element.Set(element.Ptr(),separator);
+ }
+
+ // Try to find this element at the search level
+ SetEntryL(where);
+ GetChildrenL(*iSelection);
+
+ // Nothing? Give up.
+ if (!iSelection->Count())
+ break; // out of while
+
+ // Check all the children
+ TInt a;
+ for(a=0;a<iSelection->Count();a++)
+ {
+ // This one?
+ SetEntryL((*iSelection)[a]);
+
+ if (iEntry->Entry().iDetails.Compare(element)==0)
+ {
+ // Found it! Are we at the end, ie no more elements?
+ if (separator==KErrNotFound)
+ {
+ // Set subscribed flag
+ TMsvEmailEntry message=iEntry->Entry();
+ if (!message.Subscribed())
+ {
+ // It needs changing, do it
+ message.SetSubscribed(ETrue);
+ ChangeEntryL(message);
+ }
+
+ // All done: this will exit the loop
+ where=0;
+ break;
+ }
+ else
+ {
+ // Update path
+ where=(*iSelection)[a];
+ path.Set(path.Ptr()+separator+1,path.Length()-separator-1);
+
+ // Exit loop
+ break;
+ }
+ }
+ }
+
+ if (a==iSelection->Count())
+ {
+ // Didn't find it. Humm
+ DBG((LogText(_L8("Didn't find entry '%S': tree out of date?"),&element)));
+
+ break; // out of while
+ }
+ }
+
+ CleanupStack::PopAndDestroy();
+ }
+
+void CImImap4Session::SendLoginL()
+ {
+ // We need to login in as few steps as possible, ie avoid using
+ // literals if possible as this incurrs a RTT delay as we have to
+ // wait for server's OK before sending the literal.
+
+ // Set up to send a command
+ NewTag();
+
+ // No need to quote? Do it in one line if we can,
+ if (!iLiteralUsername && !iLiteralPassword)
+ {
+ // Send login line. Turn off logging before sending
+ iImapIO->PerformLogging(EFalse);
+ iImapIO->SendL(iStatus,_L8("%d LOGIN %S %S\r\n"),iTag,iUsername,iPassword);
+ iImapIO->PerformLogging(ETrue);
+ }
+ else
+ {
+ if (iLiteralUsername)
+ {
+ // Send literal username
+ iImapIO->SendL(iStatus,_L8("%d LOGIN {%d}\r\n"),iTag,iUsername->Length());
+ iState=EImapStateLoginSendUser;
+ }
+ else
+ {
+ // Send username and literal password. Turn off logging before sending
+ iImapIO->PerformLogging(EFalse);
+ iImapIO->SendL(iStatus,_L8("%d LOGIN %S {%d}\r\n"),iTag,iUsername,iPassword->Length());
+ iImapIO->PerformLogging(ETrue);
+
+ iState=EImapStateLoginSendPassword;
+ }
+ }
+
+ // Don't complete yet!
+ NewTagSent();
+ }
+
+void CImImap4Session::SendCapabilityL()
+ {
+ // Check server capabilities
+ iState=EImapStateCapabilityWait;
+ iSeenVersion=EFalse;
+
+ // Send the command
+ NewTag();
+ iImapIO->SendL(iStatus,_L8("%d CAPABILITY\r\n"),iTag);
+ NewTagSent();
+ }
+
+void CImImap4Session::StartIdle(TRequestStatus& aRequestStatus)
+ {
+ DBG((LogText(_L8("CImImap4Session::StartIdle()"))));
+ TInt err=KErrNone;
+ if (!Connected() || iState==EImapStateIdleWait)
+ {
+ Queue(aRequestStatus);
+ err=KErrDisconnected;
+ }
+ else
+ TRAP(err,StartIdleL(aRequestStatus));
+ if (err!=KErrNone)
+ {
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::StartIdle(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+
+ Complete(err);
+ }
+ }
+
+void CImImap4Session::StartIdleL(TRequestStatus& aRequestStatus)
+ {
+ DBG((LogText(_L8("CImImap4Session::StartIdleL()"))));
+ Queue(aRequestStatus);
+ DoStartIdleL();
+ }
+
+void CImImap4Session::DoStartIdleL()
+ {
+ DBG((LogText(_L8("CImImap4Session::DoStartIdleL()"))));
+ __ASSERT_DEBUG(ImapIdleSupported(), gPanic(EBadUseOfImap4Op));
+ if(!ImapIdleSupported())
+ {
+ User::LeaveIfError(KErrGeneral);//Bad use of Imap4Op
+ }
+ __ASSERT_DEBUG(iState==EImapStateSelected, gPanic(ESyncWhenNotSelected));
+ if(!(iState==EImapStateSelected))
+ {
+ User::LeaveIfError(KErrArgument);// Sync when not selected .
+ }
+ // Reset flags
+ iMailboxReceivedExists=EFalse;
+ iMailboxReceivedExpunge=EFalse;
+ iMailboxReceivedFlags=EFalse;
+
+ iState=EImapStateIdleWait;
+ SendMessageL(KIMAPC_IDLE);
+ }
+
+void CImImap4Session::StopIdle(TRequestStatus& aRequestStatus)
+ {
+ DBG((LogText(_L8("CImImap4Session::StopIdle()"))));
+ TInt err(KErrNone);
+ if (!Connected())
+ {
+ Queue(aRequestStatus);
+ err=KErrDisconnected;
+ }
+ else
+ {
+ // stop idle is only called by compound command
+ // so flag this
+ iCompoundStopIdle = ETrue;
+ TRAP(err,StopIdleL(aRequestStatus));
+ }
+ if (err!=KErrNone)
+ {
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::StopIdle(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ Complete(err);
+ }
+ }
+
+void CImImap4Session::SyncStopIdleL(TRequestStatus& aRequestStatus)
+ {
+ DBG(LogText(_L8("CImImap4Session::SyncStopIdleL (state=%d)"), iState));
+ iStoppingIdleForSync = ETrue;
+ StopIdleL(aRequestStatus);
+ }
+
+void CImImap4Session::StopIdleL(TRequestStatus& aRequestStatus)
+ {
+ DBG((LogText(_L8("CImImap4Session::StopIdleL()"))));
+ Cancel();
+ Queue(aRequestStatus);
+ DoStopIdleL();
+ }
+
+void CImImap4Session::DoStopIdleL()
+ {
+ DBG(LogText(_L8("CImImap4Session::DoStopIdleL(state=%d)"),iState)) ;
+ __ASSERT_DEBUG(ImapIdleSupported(), gPanic(EBadUseOfImap4Op));
+ if(!ImapIdleSupported())
+ {
+ User::LeaveIfError(KErrGeneral);//Bad Use of Imap4Op
+ }
+ __ASSERT_DEBUG(IsIdling(), gPanic(EInvalidStatus));
+ if(!IsIdling())
+ {
+ User::LeaveIfError(KErrGeneral); // Invalid Status
+ }
+
+ iIdleTimer->Cancel();
+ iState=EImapStateStopIdleWait;
+
+ SendUntaggedMessageWithTimeoutL(KIMAPC_DONE, KImapDoneInactivityTimeSeconds);
+ }
+
+// Process output from search command
+// V2 version could alter significantly and become #ifdef-unreadable
+void CImImap4Session::ProcessSearchL(CImapAtom *aAtom)
+ {
+ // Process the reply from a uid search command into an fixed array of uids.
+ // The data is recieved as a series of atoms containing numerical only data.
+ // If non-numerical data is recieved, then this function will leave.
+ // Use the key to do inserts in sequence. Takes care of duplicates
+ TKeyArrayFix key(0,ECmpTInt32);
+ TUint atomUid;
+ while (aAtom)
+ {
+ if (aAtom->Atom().Length()>0)
+ {
+ // Check for numerical value.
+ TChar atomChar(aAtom->Atom()[0]);
+ if (atomChar.IsDigit() && aAtom->Value(atomUid)==KErrNone)
+ {
+ // Append it to the end search UID list
+ // Put in sequence no duplicates
+ TRAPD(err,iSearchList->InsertIsqL(static_cast<TUint32>(atomUid),key));
+ if(err != KErrNone && err != KErrAlreadyExists)
+ {
+ User::Leave(err);
+ }
+ }
+ else
+ // Not a number or cant get its value.
+ User::Leave(KErrArgument);
+ }
+ else
+ {
+ // Null atom.
+ User::Leave(KErrArgument);
+ }
+ aAtom=aAtom->Next();
+ }
+
+ DBG((LogText(_L8("UID search found %d UIDs in remote folder"),iSearchList->Count())));
+ }
+
+// A tag has completed OK
+void CImImap4Session::CommandCompleteL(TInt aResult)
+ {
+ DBG((LogText(_L8("CImImap4Session::CommandComplete(state=%d, result=%d)"),iState,aResult)));
+ iCommandFailure=aResult;
+
+ // Data has been received from the remote server - can cancel the cancel-timer,
+ // cancel wasn't cos of hanging due to GPRS suspend.
+ iCancelTimer->Cancel();
+
+ switch(iState)
+ {
+ case EImapStateCapabilityWait:
+ // Did we see the correct version ID?
+ if (!iSeenVersion)
+ {
+ // No - not in any of the lines between issuing the capability
+ // command and the completion of the command
+ Fail(KErrImapServerVersion);
+ return;
+ }
+
+ // Move into whatever logon state is required
+ iState=iSavedState;
+
+ // Need to login?
+ if (iState==EImapStateLoginWait)
+ {
+ switch (iSecurityState)
+ {
+ case ESecure:
+ case EUnsecure:
+ // shouldn't ever get here...
+ SendLoginL();
+ break;
+
+ case ENegotiating:
+ SendLoginL();
+ iSecurityState=ESecure;
+ break;
+
+ case EUnknown:
+ if (iServiceSettings->SecureSockets())
+ {
+ if (iCapabilityStartTLS)
+ {
+ // send StartTLS
+ NewTag();
+ iImapIO->SetTLSResponseL();
+ iImapIO->SendL(iStatus,_L8("%d STARTTLS\r\n"),iTag);
+ NewTagSent();
+ iState=EImapStateStartTLSWait;
+ iSendQueued=EFalse; // no need to queue 'send' cause session will take care of the response.
+ }
+ else
+ Fail(KErrImapServerNoSecurity);
+ }
+ else
+ {
+ if (iCapabilityLoginDisabled)
+ Fail(KErrImapServerLoginDisabled);
+ else
+ {
+ SendLoginL();
+ iSecurityState=EUnsecure;
+ }
+ }
+ break;
+ }
+
+ return;
+ }
+ break;
+
+ case EImapStateStartTLSWait:
+ SendCapabilityL();
+ iSecurityState = ENegotiating;
+ return;
+
+ case EImapStateSelectWait:
+ switch(aResult)
+ {
+ case KErrNone:
+ // Select OK
+ DBG((LogText(_L8("CImImap4Session::CommandCompleteL(): setting iState to EImapStateSelected"))));
+ iState=EImapStateSelected;
+ if( iCancelAndIdle )
+ {
+ if( iReissueIdle )
+ {
+ // Need to re-issue the IDLE command - probably due to a cancel
+ // during a populate commmand and session was IDLE before the
+ // command.
+ // As the populate could have been for a message not in the Inbox,
+ // (and this is the completion of SELECT command for appropriate
+ // mailbox) need to ensure IDLE started in inbox.
+
+ // Do the select (if we really need to) or skip if possible.
+ if( iMailboxId==GetInbox() && iMailboxWritable )
+ {
+ DBG((LogText(_L8("Need to re-issue IDLE command"))));
+
+ // No need to do the select - so re-issue the IDLE
+ DoStartIdleL();
+ }
+ else
+ {
+ DBG((LogText(_L8("Need to issue IDLE - first select Inbox (writable)"))));
+
+ // Looks like we need to do the select...
+ DoSelectL(GetInbox(), ETrue);
+ }
+ }
+ else
+ {
+ DBG((LogText(_L8("Do not re-issue IDLE - cancel completed"))));
+
+ // Cancelling completed - stay in this mailbox and do not issue idle.
+ iCancelAndIdle = EFalse;
+ }
+ }
+ break;
+
+ case KErrIMAPNO:
+ DBG((LogText(_L8("CImap4Session::CommandComplete(): iState=EImapStateSelectWait"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::CommandComplete(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ // Select failed
+ Complete(KErrIMAPNO);
+ return;
+ }
+ break;
+
+ case EImapStateSeparatorWait:
+ case EImapStateCloseWait:
+ // Back to unselected
+ iState=EImapStateNoSelect;
+ break;
+
+ case EImapStateCommandWait:
+ case EImapStateListWait:
+ case EImapStateLsubWait:
+ case EImapStateAppendResultWait:
+ // Restore old state: could have been selected/unselected
+ iState=iSavedState;
+ break;
+
+ case EImapStateFetchCancelWait:
+ // No more parts to fetch - reset the fetch list
+ iFetchList->Reset();
+
+ // Dispose of any buffers we've got left
+ delete iPartialLine;
+ iPartialLine=NULL;
+ delete iAttachmentFile;
+ iAttachmentFile=NULL;
+ delete iAttachmentFullPath;
+ iAttachmentFullPath=NULL;
+ delete iAttachmentMimeInfo;
+ iAttachmentMimeInfo=NULL;
+
+ // Ensure attachment file state correct so that any re-fetch will be ok
+ iAttachmentFileState=EFileNotOpen;
+
+ // All done - ensure any changes are committed to disk
+ iEntry->CompleteBulk();
+
+ // Back in selected state - do we need to move back to inbox?
+ iState=EImapStateSelected;
+ iSyncState=ENotSyncing;
+ if( iCancelAndIdle )
+ {
+ if( iReissueIdle )
+ {
+ // Need to re-issue the IDLE command - probably due to a cancel
+ // during a populate commmand and session was IDLE before the
+ // command.
+ // As the populate could have been for a message not in the Inbox,
+ // (and this is the completion of SELECT command for appropriate
+ // mailbox) need to ensure IDLE started in inbox.
+
+ // Do the select (if we really need to) or skip if possible.
+ if( iMailboxId==GetInbox() && iMailboxWritable )
+ {
+ DBG((LogText(_L8("Need to re-issue IDLE command"))));
+
+ // No need to do the select - so re-issue the IDLE
+ DoStartIdleL();
+ }
+ else
+ {
+ DBG((LogText(_L8("Need to issue IDLE - first select Inbox (writable)"))));
+
+ // Looks like we need to do the select...
+ DoSelectL(GetInbox(), ETrue);
+ }
+ }
+ else
+ {
+ DBG((LogText(_L8("Do not re-issue IDLE - cancel completed"))));
+
+ // Cancelling completed - stay in this mailbox and do not issue idle.
+ iCancelAndIdle = EFalse;
+ }
+ }
+ break;
+
+ case EImapStateFetchWait:
+ // Fetch has completed: any more parts to go?
+ iProgress.iPartsDone++;
+
+ if (iFetchList->Count())
+ {
+ // Yes, fetch next one
+ FetchAnItemL((*iFetchList)[0]);
+ iFetchList->Delete(0,1);
+ return;
+ }
+
+ // Dispose of any buffers we've got left
+ delete iPartialLine;
+ iPartialLine=NULL;
+ delete iAttachmentFile;
+ iAttachmentFile=NULL;
+ delete iAttachmentFullPath;
+ iAttachmentFullPath=NULL;
+ delete iAttachmentMimeInfo;
+ iAttachmentMimeInfo=NULL;
+ // All done - ensure any changes are committed to disk
+ iEntry->CompleteBulk();
+ iState=EImapStateSelected;
+ iSyncState=ENotSyncing;
+ break;
+
+ case EImapStateLoginWait:
+ switch(aResult)
+ {
+ case KErrNone:
+ // Login OK: do we know the separator character?
+ if (!iHierarchySeparator.Length())
+ {
+ // No, ask server for it
+ NewTag();
+ iImapIO->SendL(iStatus,_L8("%d LIST \"\" \"\"\r\n"),iTag);
+ NewTagSent();
+ iState=EImapStateSeparatorWait;
+ return;
+ }
+
+ iState=EImapStateNoSelect;
+ break;
+
+ case KErrIMAPNO:
+ case KErrImapBadLogon:
+ // Bad username/password
+ Fail(KErrImapBadLogon);
+ return;
+ }
+ break;
+ case EImapStateSelected:
+ {
+ // reset flag after stop idle completes
+ iCompoundStopIdle = EFalse;
+ iStoppingIdleForSync = EFalse;
+ }
+ case EImapStateCreateWait:
+ case EImapStateRenameWait:
+ case EImapStateDeleteWait:
+ case EImapStateSubscribeWait:
+ // Modification operation has happened: did it go OK?
+ if (aResult==KErrNone)
+ {
+ TMsvEmailEntry message;
+ SetEntryL(iCommandIds[0]);
+
+ switch(iState)
+ {
+ case EImapStateCreateWait:
+ {
+ // We need to create the actual folder, as it now exists on the server
+ message.iType=KUidMsvFolderEntry;
+ message.iMtm=KUidMsgTypeIMAP4;
+ message.iServiceId=iServiceId;
+ message.SetMtmData1(0);
+ message.SetMtmData2(0);
+ message.SetMtmData3(0);
+ message.SetValidUID(EFalse);
+ message.SetMailbox(ETrue); // Default to creating a mailbox
+ message.SetComplete(ETrue);
+ message.iSize=0;
+ message.iDetails.Set(iCommandBuf);
+ iEntry->CreateEntry(message);
+ // Save the created id in the progress buffer
+ iProgress.iReturnedMsvId=message.Id();
+ break;
+ }
+
+ case EImapStateRenameWait:
+ {
+ // Modify the entry
+ message=iEntry->Entry();
+ message.iDetails.Set(iCommandBuf);
+ ChangeEntryL(message);
+ break;
+ }
+
+ case EImapStateDeleteWait:
+ // Delete the entry: we are set to the parent currently
+ iEntry->DeleteEntry(iCommandIds[1]);
+ break;
+
+ case EImapStateSubscribeWait:
+ // Set/reset the subscription flag
+ message=iEntry->Entry();
+ message.SetSubscribed(iCommandFlags[0]);
+ ChangeEntryL(message);
+ break;
+
+ default: // To stop AER warnings
+ break;
+ }
+
+ // Back to previous state
+ iState=iSavedState;
+ }
+ DBG((LogText(_L8("CImap4Session::CommandComplete(): iState=EImapStateSubscribeWait"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::CommandComplete(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ Complete(aResult);
+ return;
+
+ case EImapStateDeleteAllWait:
+ // Expunge the folder by closing
+ NewTag();
+ iImapIO->SendL(iStatus,_L8("%d CLOSE\r\n"),iTag);
+ NewTagSent();
+ iState=EImapStateDeleteFolderWait;
+ return;
+
+ case EImapStateDeleteFolderWait:
+ {
+ // Delete all children in folder locally as they have all been expunged
+ SetEntryL(iMailboxId);
+ GetChildrenL(*iSelection);
+ TInt a=0;
+ while(a<iSelection->Count())
+ iEntry->DeleteEntry((*iSelection)[a++]);
+ iSelection->Reset();
+
+ // Back to unselected as we've closed the folder
+ iState=EImapStateNoSelect;
+ break;
+ }
+
+ case EImapStateSynchroniseWait:
+ {
+ if (aResult==KErrNone && iSyncState==ESyncSearch)
+ {
+ iFolderPosition+=KImapUidSearchSize;
+ // Check to see if we have all the messages.
+ if (iFolderPosition>=iMailboxSize)
+ {
+ DBG((LogText(_L8("UID search complete"))));
+
+ // Set the mailbox size to the number of UIDs returned by the search
+ iMailboxSize = iSearchList->Count();
+
+ iSyncState=ESyncListOld;
+ // We have the full list of remote UIDs - fall through.
+ }
+ else
+ {
+ // Should be able to hit this code if KImapUidSearchSize is reduced to < the size
+ // of the remote mailbox (iMailboxSize)
+ // SearchString non 0 means a refined UID SEARCH is required
+ DBG((LogText(_L8("UID search - get next manageable block of UIDs"))));
+ NewTag();
+ if(iServiceSettings->SearchString().Length() != 0)
+ {
+ // Refined search required
+ // Still uses the indexes but appends user specified search criteria
+ _LIT8(KSearchString,"%d UID SEARCH %d:%d %S\r\n");
+ TPtrC8 ptr = iServiceSettings->SearchString();
+ iImapIO->SendL(iStatus,KSearchString,iTag,iFolderPosition+1,Min(iMailboxSize,iFolderPosition+KImapUidSearchSize),&ptr);
+ }
+ else
+ // Normal unrefined SEARCH. Will pull back all the UIDs between indexes
+ {
+ _LIT8(KSearchString,"%d UID SEARCH %d:%d\r\n");
+ iImapIO->SendL(iStatus,KSearchString,iTag,iFolderPosition+1,Min(iMailboxSize,iFolderPosition+KImapUidSearchSize));
+ }
+ NewTagSent();
+ return;
+ }
+ }
+
+ if (aResult==KErrNone && iSyncState==ESyncListOld)
+ {
+ // At this point the remote command to search out folder UIDs has completed. This
+ // list will contain all UIDs for messages in the remote folder. This may be too
+ // many, and if so, the list is truncated to only include the N most recent
+ // messages in accordance with the synchronisation limit.
+
+ TInt local=0;
+ TInt remote=0;
+ // if no UID Search string defined then the old logic is used
+ TInt syncThresh = 0;
+ if(iServiceSettings->SearchString().Length() != 0)
+ {
+ syncThresh = 0;
+ }
+ else // If no search string is set we will use the old behaviour
+ {
+ syncThresh=(iSyncLimit<iMailboxSize)?iMailboxSize-iSyncLimit:0;
+ }
+ TBool orphanThis=EFalse;
+ TBool folderPositionFound=EFalse;
+ iFolderPosition=0;
+
+ // At this stage (prior to truncation), we have a full list of all the message
+ // UIDs in the remote folder. We also have a list of all UIDs for messages in
+ // the local folder. With these lists, we can establish which local messages
+ // have been orphaned and which have not using the following rules.
+
+ // * If the remote message is no longer there:
+ // (1) Then the local message is orphaned (always).
+ // * If the remote message is still there and it is not one of the N most recent:
+ // (2) If the local message has body parts do nothing.
+ // (3) If the local message does not have body parts, then it is orphaned
+ // unless is is a message we have selected for download.
+ // * If the remote message is still there and it is one of the N most recent:
+ // (4) Do nothing.
+
+ // Search through the local folder list while checking the remote folder list.
+ while (local<iFolderIndex.Size() && iFolderIndex[local].iMsvId!=-1)
+ {
+ orphanThis=EFalse; // Default orphaning of this message to false.
+
+ // Find next uid in remote folder uid list that is >= local entry uid.
+ while (remote<iMailboxSize)
+ {
+ if (remote < iSearchList->Count() &&
+ (*iSearchList)[remote]>=iFolderIndex[local].iUid)
+ break;
+ remote++;
+ }
+
+ // Within scope of search list?
+ if (remote<iMailboxSize)
+ {
+ TBool inSyncRange=(remote>=syncThresh);
+ TBool uidMatch=((*iSearchList)[remote]==iFolderIndex[local].iUid);
+ TBool uidNewer=((*iSearchList)[remote]>iFolderIndex[local].iUid);
+
+ // Folder position must point to 1st old local message that matches a
+ // remote message that will be sync'ed.
+ if (uidMatch && inSyncRange && !folderPositionFound)
+ {
+ iFolderPosition=local;
+ folderPositionFound=ETrue;
+ }
+
+ if (uidNewer)
+ {
+ // Here the next remote uid is greater than the local uid indicating that
+ // a message has been removed on the remote folder and that this local
+ // message should be orphaned. See case (1) above.
+
+ DBG((LogText(_L8("Message no longer available on remote server, orphaning"))));
+ orphanThis=ETrue;
+ }
+ else if (uidMatch && !inSyncRange)
+ {
+ // Here the remote uid matches the local uid, but the message falls outside
+ // of the N most recent messages. See cases (2) & (3).
+
+ DBG((LogText(_L8("Local message old (%u)"),iFolderIndex[local].iUid)));
+
+ SetEntryL(iFolderIndex[local].iMsvId);
+ TMsvEmailEntry message(iEntry->Entry());
+ TBool inSyncSelection = EFalse;
+
+ // Is the message part of the synchronisation selection?
+ // If so, we will want to view the message after the sync so don't delete.
+ if (iSynchronisationSelection && iSynchronisationSelection->Count() > 1)
+ {
+ if (iSynchronisationSelection->Find(iFolderIndex[local].iMsvId) != KErrNotFound)
+ {
+ inSyncSelection = ETrue;
+ }
+ }
+
+ // Does message have any downloaded parts?
+ if (!message.Complete() &&
+ !message.BodyTextComplete() &&
+ !inSyncSelection)
+ {
+ // The local message does not have any body parts and
+ // is not selected for download, so it is orphaned.
+ // See case (3) above.
+
+ DBG((LogText(_L8("Local message (%u) is only header and not selected for download, deleting"),iFolderIndex[local].iUid)));
+ orphanThis=ETrue;
+ }
+ }
+ }
+ else
+ {
+ // Outside of scope of search list, so none of the remaining local
+ // messages are present in the remote folder and therefore are orphaned.
+ // See case (1) above.
+
+ DBG((LogText(_L8("Message no longer available on remote server, orphaning"))));
+ orphanThis=ETrue;
+ }
+
+ // Orphan this one?
+ if (orphanThis)
+ {
+ OrphanMessageL(iFolderIndex[local].iMsvId);
+ iFolderIndex.Expunge(local+1);
+ iOrphanedMessages++;
+ }
+ else
+ {
+ // If we have arrive here, then the local message is one of the N most
+ // recent and still exists remotely OR it exists remotely, is not one of
+ // the N most recent and the local message had body parts.
+ // See cases (2) & (4) above.
+
+ local++;
+ }
+ } // End of the big while()
+
+ // Trim the list down to the most recent UIDs consistant with the sync limit.
+ if (syncThresh && iSearchList->Count() > syncThresh)
+ iSearchList->Delete(0,syncThresh);
+
+ // So now "iFolderIndex" will only have messages that are in the remote folder.
+ // And therefore, the highest UID stored in "iFolderIndex" will be the most
+ // recent old message. Also, the lowest UID in "iSearchList" will be the oldest
+ // sync'able remote message.
+
+ // Are there any old messages left?
+ if (iFolderIndex.Size() && folderPositionFound)
+ {
+ DBG((LogText(_L8("Updating flags for %d old messages (UIDs %u to %u)"),
+ iFolderIndex.Size(),(*iSearchList)[0],iHighestUid)));
+
+ // Re-assign highest UID, the previous highest may no longer exist.
+ iHighestUid=iFolderIndex[iFolderIndex.Size()-1].iUid;
+ iSyncState=ESyncListNew;
+
+ // Fetch old messages.
+ NewTag();
+ // If a UID search string has been specified, the we should create the UID FETCH
+ // string from the UID integer list otherwise they'll all come down.
+ if(iServiceSettings->SearchString().Length() != 0)
+ {
+ CreateUidStringL(); // Construct the UID string from the UID list
+ TPtrC8 ptr(iUidString->Des());
+ _LIT8(KFetchString,"%d UID FETCH %S (UID FLAGS)\r\n");
+ iImapIO->SendL(iStatus,KFetchString,iTag,&ptr);
+ }
+ else
+ {
+ _LIT8(KFetchString,"%d UID FETCH %d:%d (UID FLAGS)\r\n");
+ iImapIO->SendL(iStatus,KFetchString,iTag,(*iSearchList)[0],iHighestUid);
+ }
+ NewTagSent();
+ return;
+ }
+ else
+ {
+ DBG((LogText(_L8("No old message headers to update"))));
+
+ iSyncState=ESyncListNew;
+ iHighestUid=0;
+ // All remote messages are new - fall through.
+ }
+ }
+
+ if (aResult==KErrNone && iSyncState==ESyncListNew)
+ {
+ // At this point, the remote command to fetch all old messages has completed.
+ // Now we can look at fetching all new messages. 'iHighestUid' will contain the
+ // highest UID of the old messages. The top entry in 'iSearchList' will contain
+ // the highest UID in the remote folder. This gives us the range of UID to fetch
+ // for new messages.
+
+ // First check are there any new messages to fetch? If 'iHighestUid' is the highest
+ // UID locally and remotely, then we finished sync'ing when we completed the old
+ // sync.
+ if (iSearchList->Count() == 0)
+ {
+ DBG((LogText(_L8("Search List is empty"))));
+ }
+ else if (iHighestUid<(*iSearchList)[iSearchList->Count()-1])
+ {
+ TUint32 uidLow=iHighestUid;
+ TUint32 uidHigh=(*iSearchList)[iSearchList->Count()-1];
+
+ // Only want new messages.
+ uidLow++;
+
+ // Are there only new messages (and no old)?
+ if (iHighestUid==0)
+ {
+ // Set this to ensure range is correct.
+ uidLow=(*iSearchList)[0];
+ }
+
+ // Perform the new sync.
+ SynchroniseNewL(uidLow,uidHigh);
+ return;
+ }
+ else
+ {
+ DBG((LogText(_L8("No new message headers to sync"))));
+
+ // Synchronisation complete - fall through.
+ }
+ iSyncState=ESyncNew;
+ }
+
+ // Synchronising: moving on to the next state?
+ if (aResult==KErrNone && iSyncState==ESyncOld)
+ {
+ // At this point, the remote command to list old messages has
+ // completed: however, if there was a 'gap' at the end (ie messages at
+ // the end of our copy of the mailbox had been deleted) we won't have
+ // noticed it until this point as we woulnd't have got back in sync which
+ // happens when we get a UID which isn't sequential.
+ // So, as we now know there are no more messages in the range we know about
+ // locally, anything else must have been deleted remotely.
+ while(iFolderPosition<iFolderIndex.Size())
+ {
+ // Orphan this message
+ DBG((LogText(_L8("Orphaning UID %u"),iFolderIndex[iFolderPosition].iUid)));
+
+ // Do it
+ OrphanMessageL(iFolderIndex[iFolderPosition].iMsvId);
+
+ // Remove it from the index
+ iFolderIndex.Expunge(iFolderPosition+1);
+
+ // Increment stats
+ iOrphanedMessages++;
+ }
+
+ // Anything to new sync? If we've processed all the messages up to
+ // iMailboxSize, then there's nothing left that might possibly be
+ // new
+ if (iMsgsDone<iMailboxSize)
+ {
+ // Do new sync
+ SynchroniseNewL();
+ return;
+ }
+
+ iSyncState=ESyncNew;
+ }
+
+ if (aResult==KErrNone && iSyncState==ESyncNew)
+ {
+ // If there were any "missing" messages found during the sync that we should have
+ // mirrored previously, get these now.
+ if (iMissingUidLow!=0 && iMissingUidHigh!=0)
+ {
+ DBG((LogText(_L8("Missing messages detected %d - %d"),iMissingUidLow,iMissingUidHigh)));
+
+ SynchroniseNewL(iMissingUidLow,iMissingUidHigh);
+ iMissingUidLow=iMissingUidHigh=0;
+ return;
+ }
+ }
+
+ // If we got a NO response in the course of the sync, we've not fully sync'ed the folder,
+ // so we shouldn't update the last sync date, etc. BUT as NO isn't a fatal response, we should
+ // not disconnect.
+ if (aResult!=KErrIMAPNO)
+ {
+ // Are we configured to update the \seen flag on the server?
+ if (iServiceSettings->UpdatingSeenFlags())
+ {
+ DBG((LogText(_L8("Sync completed: updating servers flags"))));
+
+ // Now drop through to EImapStateSetSeenWait case
+ iState = EImapStateSetSeenWait;
+ iSyncState=ENotSyncing;
+ }
+ else
+ {
+ // Not updating servers \seen flags - use old code to continue after sync
+ // Back to selected state
+ iState=EImapStateSelected;
+ iSyncState=ENotSyncing;
+
+ // We've synchronised the folder. Update iDate field to
+ // indicate last sync date of this folder.
+ // This also updates the 'folders done' count
+ SyncCompleteL();
+ DBG((LogText(_L8("CImap4Session::CommandComplete(): iState=EImapStateSynchroniseWait"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::CommandComplete(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ Complete(aResult);
+ return;
+ }
+ }
+ else
+ {
+ // NO isn't a fatal response.Ignore it,continue...
+ iState=EImapStateSelected;
+ iSyncState=ENotSyncing;
+ DBG((LogText(_L8("CImap4Session::CommandComplete(): iState=EImapStateSynchroniseWait, aResult == KErrIMAPNO"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::CommandComplete(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ Complete(aResult);
+ return;
+ }
+ }
+
+ case EImapStateSetSeenWait: // Only a valid state when iServiceSettings->UpdatingSeenFlags() is true
+ // We may not be able to set all the seen flags due to a large list,
+ // so call ProcessSeenFlagsL until False is returned
+ if (ProcessSeenFlagsL(ESetSeenFlag))
+ {
+ // More have been processed - wait for response in same state.
+ return;
+ }
+ // Now drop through to EImapStateClearSeenWait state
+ iState=EImapStateClearSeenWait;
+ iSyncState=ENotSyncing;
+
+ case EImapStateClearSeenWait: // Only a valid state when iServiceSettings->UpdatingSeenFlags() is true
+ // We may not be able to clear all the seen flags due to a large list,
+ // so call ProcessSeenFlagsL until False is returned
+ if (ProcessSeenFlagsL(EClearSeenFlag))
+ {
+ // More have been processed - wait for response in same state.
+ return;
+ }
+
+ // All seen flags processed - return to selected state
+ // Will only arrive here if iServiceSettings->UpdatingSeenFlags() is true
+ iState=EImapStateSelected;
+ iSyncState=ENotSyncing;
+
+ // We've synchronised the folder. Update iDate field to
+ // indicate last sync date of this folder.
+ // This also updates the 'folders done' count
+ SyncCompleteL();
+ DBG((LogText(_L8("CImap4Session::CommandComplete(): iState=EImapStateClearSeenWait"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::CommandComplete(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ Complete(aResult);
+ return;
+
+ case EImapStateLogoutWait:
+ DoDisconnect();
+ break;
+
+ case EImapStateDeleteMarkWait:
+ // Messages marked for deletion OK, send close command
+ SendMessageL(KIMAPC_CLOSE);
+ iState=EImapStateExpungeWait;
+ return;
+
+ case EImapStateExpungeWait:
+ {
+ DBG((LogText(_L8("Close completed OK: expunging messages locally"))));
+
+ // Expunge the messages locally, the close was successful
+ for(TInt a=0;a<iFolderIndex.Size();a++)
+ {
+ // If it's got a valid MsvId, delete it!
+ if (iFolderIndex[a].iMsvId)
+ {
+ DBG((LogText(_L8("Expunging message %x"),iFolderIndex[a].iMsvId)));
+
+ DeleteMessageL(iFolderIndex[a].iMsvId);
+ }
+ }
+
+ // Back to unselected state (close sucessful)
+ iState=EImapStateNoSelect;
+ break;
+ }
+
+ case EImapStateIdleWait:
+ {
+ DBG((LogText(_L8("CImImap4Session::CommandCompleteL(): iState = EImapStateIdleWait"))));
+ if (iCompoundStopIdle)
+ {
+ // the reply from server is missed if
+ // compound command
+ GetReply(EFalse);
+ }
+ break;
+ }
+
+ case EImapStateIdling:
+ {
+ DBG((LogText(_L8("CImImap4Session::CommandCompleteL(): iState = EImapStateIdling"))));
+ if (iReissueIdle)
+ {
+ // IDLE command has been re-issued after synchronise OR a re-issue
+ // after a cancel-and-idle (if idling before).
+ // NOTE - also unset cancel-and-idle flag here too.
+ iReissueIdle = EFalse;
+ iCancelAndIdle = EFalse;
+ IssueIdleRead();
+ }
+ break;
+ }
+
+ case EImapStateStopIdleWait:
+ {
+ DBG((LogText(_L8("Idling completed OK"))));
+
+ iState=EImapStateSelected;
+
+ if (FolderChanged())
+ {
+ DBG((LogText(_L8("CImImap4Session::CommandCompleteL(): FolderChanged"))));
+
+ // If the folder has changed then we are going to do a synchronise to
+ // ensure we have up to date message information. At the end of the
+ // synchronise we would normally reissue the idle command, however
+ // we don't want to do this if we are stopping idle due to a
+ // compound command or a sync command. The compound / sync active
+ // objects will reissue the idle themselves.
+ if(!iCompoundStopIdle && !iStoppingIdleForSync)
+ {
+ DBG((LogText(_L8("CImImap4Session::CommandComplete(): ReissueIdle = true"))));
+ iReissueIdle=ETrue;
+ }
+ DoSynchroniseL(EFalse);
+ return;
+ }
+ else if (iReissueIdle)
+ {
+ if( !iCancelAndIdle && !iIdleTimerExpired )
+ {
+ // Don't unset the iReissueIdle flag - need it to be true to ensure
+ // that IssueIdleRead is issued once 'Done' is received.
+ // FYI - IssueIdleRead is normally called in the DoComplete method
+ // when the StartIdle request completes. If we've cancel-and-idle-ed,
+ // then DoComplete won't be called when we do actually go idle.
+ DBG((LogText(_L8("CImImap4Session::CommandComplete(): ReissueIdle = false"))));
+ iReissueIdle = EFalse;
+ }
+ iIdleTimerExpired = EFalse;
+ DoStartIdleL();
+ return;
+ }
+ else if (iDisconnectAfterIdleStopped)
+ {
+ iDisconnectAfterIdleStopped=EFalse;
+ DoDisconnectL();
+ return;
+ }
+
+ break;
+ }
+
+ default:
+ gPanic(ECommandCompleteInUnknownState);
+ return;
+ }
+
+ // Completing an operation upwards: ensure iEntry is not pointing to anything
+ SetEntryL(NULL);
+
+ DBG((LogText(_L8("CImap4Session::CommandComplete(): End of method"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::CommandComplete(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+ Complete(KErrNone);
+ }
+
+
+/*
+Overrides CMsgActive::RunL() to allow check for errored completion of
+autonomously initiated IO requests, specifically the reissue of IDLE
+commands on idle timeout.
+*/
+void CImImap4Session::RunL()
+ {
+ TInt status=iStatus.Int();
+ if (status < KErrNone &&
+ iIdleTimerExpired &&
+ (iState==EImapStateIdleWait || iState==EImapStateStopIdleWait))
+ {
+ DoComplete(status);
+ }
+ else
+ {
+ // Behave exactly as CMsgActive::RunL
+ if (status>=KErrNone)
+ {
+ TRAPD(error,DoRunL()); // continue operations, may re-queue
+ __ASSERT_DEBUG(error==KErrNone || !IsActive(),User::Invariant()); // must not requeue in error situations
+ if (IsActive()) // requeud
+ return;
+ status=error;
+ }
+ Complete(status);
+ }
+ }
+
+
+
+// A child async process has completed
+void CImImap4Session::DoRunL()
+ {
+ DBG((LogText(_L8("CImImap4Session::DoRunL(status=%d, state=%d)"),iStatus.Int(),iState)));
+
+ // Did we have a send queued? If so, this is just the send completion: we
+ // now queue a receive and clear the send flag
+ if (iSendQueued)
+ {
+ DBG((LogText(_L8("CImImap4Session::DoRunL(): iSendQueued"))));
+
+ // No send queued anymore...
+ iSendQueued=EFalse;
+ // Any problems sending?
+ if(iStatus.Int()!=KErrNone)
+ {
+ // Yes, humm...
+ DBG((LogText(_L8("Error during send %d"),iStatus.Int())));
+
+ // We have to Fail() here, as the connection might be screwed:
+ // we just don't know. Fail() will disconnect us and reset our
+ // internal state.
+ Fail(KErrImapSendFail);
+ }
+ else
+ {
+ // Have we just issued a partial fetch, ie waiting for body
+ // size?
+ if (iSizeWait)
+ {
+ // Queue partial line fetch
+ GetReply(ETrue);
+ }
+ else
+ {
+ // Queue full line read
+ GetReply(EFalse);
+ }
+ }
+ return;
+ }
+
+ // Was a receive queued? If so, we need to get the new root pointer
+ if (iReceiveQueued)
+ {
+ DBG((LogText(_L8("CImImap4Session::DoRunL(): iReceiveQueued"))));
+ // Not anymore
+ iReceiveQueued=EFalse;
+
+ // Get root
+ iRootAtom=iImapIO->RootAtom();
+
+ DBG((LogText(_L8("CImImap4Session::DoRunL(): Got root"))));
+
+ // Did we get the whole line?
+ if (iStatus.Int()==KErrFoundEOL)
+ {
+ DBG((LogText(_L8("CImImap4Session::DoRunL(): got the whole line"))));
+ iGotWholeLine=ETrue;
+ iStatus=KErrNone;
+ }
+ else
+ {
+ DBG((LogText(_L8("CImImap4Session::DoRunL(): didn't got the whole line"))));
+ iGotWholeLine=EFalse;
+ }
+ }
+
+ // Problems with connection?
+ if (iStatus.Int()!=KErrNone)
+ {
+ DBG((LogText(_L8("CImImap4Session::DoRunL(): Problems with connection"))));
+ if (iState==EImapStateConnectWait)
+ Fail(KErrImapConnectFail);
+ else
+ Fail(KErrImapServerFail);
+
+ return;
+ }
+
+ DBG((LogText(_L8("CImImap4Session::DoRunL(): checking iState"))));
+
+ switch(iState)
+ {
+ // Waiting for connect response
+ case EImapStateConnectWait:
+ {
+ // Get the bearer idle timeout
+ TUint32 timeout;
+ User::LeaveIfError(iImapIO->GetLastSocketActivityTimeout(timeout));
+
+ // Sets timeout to iMtmData1. This is used by Imcm.
+ SetEntryL(iServiceId);
+ TMsvEntry entry = iEntry->Entry();
+ entry.SetMtmData1(timeout);
+ iEntry->ChangeEntry(entry);
+ DBG((LogText(_L8("IdleTimeout %d"),timeout)));
+
+ // Connected, queue receive for greeting line
+ iState=EImapStateGreetingWait;
+ GetReply(ETrue);
+ break;
+ }
+
+ // Waiting for greeting line
+ case EImapStateGreetingWait:
+ {
+ TInt result=KErrNone; // To stop .AER warnings...
+
+ // Process line
+ TRAPD(err,result=ProcessGreetingL());
+ if (err!=KErrNone)
+ Fail(KErrImapServerFail);
+ else if (result!=KErrNone)
+ {
+ // Greeting process returned an error
+ DBG((LogText(_L8("CImap4Session::CommandComplete(): iState=EImapStateGreetingWait"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::CommandComplete(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+ Complete(result);
+ }
+ else
+ {
+ SendCapabilityL();
+ }
+ break;
+ }
+
+ // Waiting for continuation response to send username
+ case EImapStateLoginSendUser:
+ {
+ // Looking for a '+'
+ CImapAtom *p=iRootAtom->ToChildL();
+
+ // '*' indicates an untagged message
+ if (p->Compare(KIMAP_UNTAGGED))
+ {
+ // Process it
+ TRAPD(err,ProcessUntaggedL(p->ToNextL(),EFalse));
+
+ // A problem here is a server fail, nothing else
+ if (err!=KErrNone)
+ Fail(KErrImapServerFail);
+ }
+ else if (p->Compare(KIMAP_CONTINUATION))
+ {
+ // Send username literal
+
+ // Literal password?
+ if (iLiteralPassword)
+ {
+ // Turn off logging before sending
+ iImapIO->PerformLogging(EFalse);
+ iImapIO->SendL(iStatus,_L8("%S {%d}\r\n"),iUsername,iPassword->Length());
+ iImapIO->PerformLogging(ETrue);
+
+ iState=EImapStateLoginSendPassword;
+ }
+ else
+ {
+ // Turn off logging before sending
+ iImapIO->PerformLogging(EFalse);
+ iImapIO->SendL(iStatus,_L8("%S %S\r\n"),iUsername,iPassword);
+ iImapIO->PerformLogging(ETrue);
+
+ iState=EImapStateLoginWait;
+ }
+
+ iSendQueued=ETrue;
+ DBG((LogText(_L8("*******************************************************"))));
+ DBG((LogText(_L8("CImap4Session::DoRunL(): waiting for iImapIO to wake me"))));
+ DBG((LogText(_L8("*******************************************************"))));
+
+ SetActive();
+ }
+ break;
+ }
+
+ // Waiting for continuation response to send password
+ case EImapStateLoginSendPassword:
+ {
+ // Looking for a '+'
+ CImapAtom *p=iRootAtom->ToChildL();
+
+ // '*' indicates an untagged message
+ if (p->Compare(KIMAP_UNTAGGED))
+ {
+ // Process it
+ TRAPD(err,ProcessUntaggedL(p->ToNextL(),EFalse));
+
+ // A problem here is a server fail, nothing else
+ if (err!=KErrNone)
+ Fail(KErrImapServerFail);
+ }
+ else if (p->Compare(KIMAP_CONTINUATION))
+ {
+ // Send password literal
+ iSendQueued=ETrue;
+ DBG((LogText(_L8("*******************************************************"))));
+ DBG((LogText(_L8("CImap4Session::DoRunL(): waiting for iImapIO to wake me"))));
+ DBG((LogText(_L8("*******************************************************"))));
+
+ SetActive();
+ // Turn off logging before sending
+ iImapIO->PerformLogging(EFalse);
+ iImapIO->SendL(iStatus,_L8("%S\r\n"),iPassword);
+ iImapIO->PerformLogging(ETrue);
+
+ iState=EImapStateLoginWait;
+ }
+ break;
+ }
+
+ case EImapStateSetSeenWait: // Wait for setting the send flags response
+ case EImapStateClearSeenWait: // Wait for clearing the send flags response
+ case EImapStateCapabilityWait: // Wait for reply to capability command
+ case EImapStateLoginWait: // Wait for reply to login command
+ case EImapStateCommandWait: // Generic OK/BAD command issued, wait for response
+ case EImapStateCreateWait: // Create command issued, wait for response
+ case EImapStateRenameWait: // Rename command issued, wait for response
+ case EImapStateDeleteWait: // Delete command issued, wait for response
+ case EImapStateDeleteAllWait: // DeleteAll command issued, wait for response
+ case EImapStateDeleteFolderWait:// DeleteAll has issued a folder delete, wait for response
+ case EImapStateSeparatorWait: // Wait for reply to null list command
+ case EImapStateSubscribeWait: // Wait for subscription command reply
+ case EImapStateSelectWait: // Wait for all select information
+ case EImapStateSynchroniseWait: // Wait for synchronise
+ case EImapStateListWait: // Wait for all list information
+ case EImapStateLsubWait: // Wait for all lsub information
+ case EImapStateAppendResultWait:// Wait for append result
+ case EImapStateDeleteMarkWait: // Marking messages for deletion before CLOSE
+ case EImapStateExpungeWait: // Messages expunged: remove them locally
+ case EImapStateExpungeAllWait: // Messages expunged from remote, remove them
+ case EImapStateCloseWait: // Wait for close to finish
+ case EImapStateLogoutWait: // Wait for logout
+ case EImapStateStartTLSWait: // Wait for starttls to return
+ case EImapStateIdleWait:
+ case EImapStateIdling:
+ case EImapStateStopIdleWait:
+ {
+ TInt result=KErrNone; // To stop .AER warnings...
+
+ // Process it
+ TRAPD(err,result=ProcessCommandReplyL());
+ if (err!=KErrNone)
+ {
+ // check for TLS errors
+ if (iState == EImapStateCapabilityWait && iSecurityState == ENegotiating)
+ err = KErrImapTLSNegotiateFailed;
+
+ // We've had a fail during reply processing: pass it on
+ Fail(err);
+ }
+ else
+ {
+ switch(result)
+ {
+ case KErrNotReady:
+ {
+ if (IsIdling() && FolderChanged())
+ {
+ DBG((LogText(_L8("CImImap4Session::DoRunL(): Calling DoStopIdleL"))));
+ DoStopIdleL();
+ }
+ else if (IsIdling())
+ {
+ IssueIdleRead();
+ return;
+ }
+ else
+ {
+ // Need the next line/part of line
+ GetReply(EFalse);
+ }
+ break;
+ }
+
+ default:
+ // Complete it if there's no error or if it's just a 'NO'
+ // reply (ie, KErrNotSupported)
+ if (result>=KErrNone || result==KErrNotSupported)
+ {
+ DBG((LogText(_L8("CImImap4Session::DoRunL(): No error - calling CommandComplete()"))));
+ CommandCompleteL(result);
+ }
+ else
+ Fail(result);
+ break;
+ }
+ }
+ break;
+ }
+
+ case EImapStateAppendSizeWait: // Work out the size of the append
+ {
+ // Sizing operation completed, get the size
+ iCommandSize=iMessageSizer->MessageSize();
+ delete iMessageSizer;
+ iMessageSizer=NULL;
+
+ DBG((LogText(_L8("Sized message to %d bytes"),iCommandSize)));
+
+ iProgress.iBytesToDo=iCommandSize;
+ iProgress.iBytesDone=0;
+
+ // Make a sender, to actually send the text
+ delete iMessageSender;
+ iMessageSender=NULL;
+ iMessageSender=CImSendMessage::NewL(/*iFs,*/*iEntry);
+
+ // Change in API, should be retrieving iSettings.SendCopyToSelf(),
+ // instead using default ESendNoCopy.
+ iMessageSender->InitialiseL(iCommandIds[0], ESendAsMimeEmail,
+ iMessageDate, iHost, iCharset, ESendNoCopy);
+
+ // Make path to the message's destination, and send command
+ HBufC8* path=MakePathL(iCommandIds[1],ETrue);
+
+ // Reset the entry to first param(message id), as it will be changed to
+ // second param(directory id) in the MakePathL function. This entry
+ // will be sent to destination as message(line by line) by CImSendMessage class.
+ SetEntryL(iCommandIds[0]);
+
+ CleanupStack::PushL(path);
+ DoQuoteL(path);
+ TPtrC8 pathptr=path->Des();
+ NewTag();
+ iImapIO->SendL(iStatus,_L8("%d APPEND \"%S\" {%d}\r\n"),iTag,&pathptr,iCommandSize);
+ NewTagSent();
+ CleanupStack::PopAndDestroy();
+
+ // Append the actual message
+ iState=EImapStateAppendPromptWait;
+ break;
+ }
+
+ case EImapStateAppendPromptWait: // Wait for prompt before sending message body
+ {
+ // Looking for a '+'
+ CImapAtom *p=iRootAtom->ToChildL();
+
+ // '*' indicates an untagged message
+ if (p->Compare(KIMAP_UNTAGGED))
+ {
+ // Process it
+ TRAPD(err,ProcessUntaggedL(p->ToNextL(),EFalse));
+ if (err!=KErrNone)
+ Fail(err);
+ }
+ else if (p->Compare(KIMAP_CONTINUATION))
+ {
+ // Start sending message body: move into sending body state
+ iState=EImapStateAppendWait;
+
+ // Make line buffer
+ delete iLineBuffer;
+ iLineBuffer=NULL;
+ iLineBuffer=HBufC8::NewL(KImMailMaxBufferSize);
+
+ // Send first line: Must be more than one line, so we don't bother
+ // with checking the return code.
+ TInt padcount=0;
+ TPtr8 line=iLineBuffer->Des();
+ iMessageSender->NextLineL(line,padcount);
+ iImapIO->SendL(iStatus,_L8("%S"),&line);
+
+ DBG((LogText(_L8("CImap4Session::DoRunL(): iState = EImapStateAppendWait"))));
+ DBG((LogText(_L8("*******************************************************"))));
+ DBG((LogText(_L8("CImap4Session::DoRunL(): waiting for iImapIO to wake me"))));
+ DBG((LogText(_L8("*******************************************************"))));
+
+ SetActive();
+ iSendQueued=EFalse; // We want to come back to here - we've not sent a command
+ iProgress.iBytesDone+=line.Length();
+
+ DBG((LogText(_L8("Sent: %d '%S'"),line.Length(),&line)));
+ }
+ break;
+ }
+
+ case EImapStateAppendWait:
+ {
+ // Send line of message
+ TInt padcount=0;
+ TPtr8 line=iLineBuffer->Des();
+ if (iMessageSender->NextLineL(line,padcount)==KImCvFinished)
+ {
+ // Send last line, plus CRLF to terminate command
+ iImapIO->SendL(iStatus,_L8("%S\r\n"),&line);
+ iProgress.iBytesDone+=line.Length();
+
+ DBG((LogText(_L8("Sent: %d '%S'"),line.Length(),&line)));
+ DBG((LogText(_L8("Total bytes sent %d plus the CRLF"),iProgress.iBytesDone)));
+
+ delete iLineBuffer;
+ iLineBuffer=NULL;
+ delete iMessageSender;
+ iMessageSender=NULL;
+ iSendQueued=ETrue;
+ iState=EImapStateAppendResultWait;
+ }
+ else
+ {
+ // Send a line
+ iImapIO->SendL(iStatus,_L8("%S"),&line);
+ iSendQueued=EFalse;
+ iProgress.iBytesDone+=line.Length();
+
+ DBG((LogText(_L8("Sent: %d '%S'"),line.Length(),&line)));
+ }
+
+ DBG((LogText(_L8("CImap4Session::DoRunL(): iState = EImapStateAppendWait"))));
+ DBG((LogText(_L8("*******************************************************"))));
+ DBG((LogText(_L8("CImap4Session::DoRunL(): waiting for iImapIO to wake me"))));
+ DBG((LogText(_L8("*******************************************************"))));
+
+ SetActive();
+ break;
+ }
+
+ case EImapStateFetchCancelWait:
+ case EImapStateFetchWait:// Wait for body length/data
+ {
+ TInt result=KErrNone; // To stop .AER warnings...
+
+ // Process it
+ TRAPD(err,result=ProcessCommandReplyL());
+
+ if (err!=KErrNone)
+ Fail(err);
+ else
+ {
+ //LogText(_L8("ProcessCommandReplyL() returned %d\n"),result);
+ switch(result)
+ {
+ case KErrNotReady:
+ // Still waiting for body size, another partial fetch
+ if (iSizeWait)
+ GetReply(ETrue);
+ else
+ GetReply(EFalse);
+ break;
+ case KErrImapInvalidServerResponse:
+ // Nothing to do , return back
+ break;
+
+ case KErrWrite:
+ // Process has issued a command of its own, nothing for us to
+ // do here
+ break;
+
+ default:
+ // Complete it
+ if (!iCommandsOutstanding)
+ {
+ DBG((LogText(_L8("CImap4Session::DoRunL(): No commands outstanding- calling CommandComplete()"))));
+ CommandCompleteL(result);
+ }
+ else
+ {
+ if (iJustSentFetch)
+ {
+ DBG((LogText(_L8("CImap4Session::DoRunL(): Just sent fetch"))));
+ iJustSentFetch = EFalse;
+ }
+ else
+ {
+ // Get next fetch result
+ DBG((LogText(_L8("CImap4Session::DoRunL(): NOT just sent fetch"))));
+ iSizeWait=ETrue;
+ GetReply(ETrue);
+ }
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+ case EImapStateMoveEntryWait:
+ {
+ // We're done with the moveentry
+
+ // Park the move entry again
+ iMoveEntry->SetEntry(NULL);
+
+ // Copy the structure: the MsvId is still the same, it's just not in the same
+ // place.
+
+ //DS - Selectively using a copy or a move depending on user intention, so no need for this
+ //"copy structure back to mirror" malarky.
+ // TMsvId newid=CopyLevelL(iMoveSource,iMoveSourceFolder);
+
+ // Note this ID in the iRelatedId, so that higher levels know where we are again...
+
+ //DS iMoveSource could be either the same message moved to a different place
+ //or still the original message - could employ some logic for differentiating if there
+ //is some need to twiddle flags etc, but I'll leave that for someone else to do...
+ SetEntryL(iMoveSource);
+ TMsvEntry entry=iEntry->Entry();
+
+ entry=iEntry->Entry();
+ entry.SetNew(EFalse);
+ ChangeEntryL(entry);
+
+ // Park iEntry
+ SetEntryL(NULL);
+
+ // Inform caller of new ID
+ //DS as far as I can tell, this isn't used!
+ //*iNewSource=newid;
+
+ // Back to previous state
+ iState=iSavedState;
+ break;
+ }
+
+ default:
+ gPanic(ERunLInUnknownState);
+ return;
+ }
+ DBG((LogText(_L8("CImImap4Session::DoRunL(): exiting..."))));
+ }
+
+
+// The IMAP Idle case for doing a "GetReply".
+// Called by CImImap4SessionIdleRead::Start() to get the whole thing going.
+void CImImap4Session::DoIdleRead(TRequestStatus& aIdleReadStatus)
+ {
+ // Unlike a normal read, give the reponse to the CImImap4SessionIdleRead when done
+ iImapIO->GetReply(aIdleReadStatus);
+ iReceiveQueued=ETrue;
+ }
+
+void CImImap4Session::CancelIdleRead()
+ {
+ // Undo what DoIdleRead() did
+ iImapIO->Cancel();
+ iReceiveQueued=EFalse;
+ }
+
+// The standard case for a normal transaction "GetReply"
+// Queue request from IO layer to get the next atom
+void CImImap4Session::GetReply(const TBool aPartialReturn)
+ {
+ // Cancel any dummy operation
+ if (ImapIdleSupported()==EFalse)
+ {
+ CancelDummy();
+ }
+
+ // Queue the receive
+ if (aPartialReturn)
+ {
+ // Get a partial line, as we need to see how much data is on its way
+ // for flood control/issuing next fetch command to keep it streaming.
+ iImapIO->GetReply(iStatus,80,ETrue);
+ }
+ else
+ {
+ // Get a *whole* line, we don't want a partial return
+ iImapIO->GetReply(iStatus);
+ }
+
+ // Note that we have ???
+ iReceiveQueued=ETrue;
+ DBG((LogText(_L8("*******************************************************"))));
+ DBG((LogText(_L8("CImap4Session::GetReply(): waiting for iImapIO to wake me"))));
+ DBG((LogText(_L8("*******************************************************"))));
+
+ SetActive();
+ }
+
+// Make a new tag, and queue send
+void CImImap4Session::NewTag()
+ {
+ if (ImapIdleSupported()==EFalse)
+ {
+ // Cancel any dummy operation that might be outstanding
+ CancelDummy();
+ }
+
+ // Make a new tag
+ iTag++;
+
+ // One more outstanding command
+ iCommandsOutstanding++;
+ }
+
+void CImImap4Session::NewTagSent()
+ {
+ // Go active and note that a send has been queued
+ SetActive();
+ iSendQueued=ETrue;
+ }
+
+// Queue sending of a command line
+void CImImap4Session::SendMessageL(const TDesC8& aMessage)
+ {
+ __ASSERT_DEBUG(iSendQueued==EFalse,gPanic(EAlreadySending));
+ if(!(iSendQueued==EFalse))
+ {
+ User::LeaveIfError(KErrInUse);//Already Sending
+ }
+ // Make a new tag
+ NewTag();
+ iImapIO->SendL(iStatus,_L8("%d %S\r\n"),iTag,&aMessage);
+ NewTagSent();
+ }
+
+void CImImap4Session::SendUntaggedMessageL(const TDesC8 &aMessage)
+ {
+ __ASSERT_DEBUG(iSendQueued==EFalse,gPanic(EAlreadySending));
+ if(!(iSendQueued==EFalse))
+ {
+ User::LeaveIfError(KErrInUse); // Already Sending
+ }
+ if (ImapIdleSupported()==EFalse)
+ {
+ // Cancel any dummy operation that might be outstanding
+ CancelDummy();
+ }
+
+ iImapIO->SendL(iStatus,KImapCommand,&aMessage);
+ DBG((LogText(_L8("*******************************************************"))));
+ DBG((LogText(_L8("CImap4Session::SendUntaggedMessageL(): waiting for iImapIO to wake me"))));
+ DBG((LogText(_L8("*******************************************************"))));
+
+ SetActive();
+ iSendQueued=ETrue;
+ }
+
+/**
+Allows a untagged message to be sent to the server with a short idle timeout applied.
+This is used when a fast response is expected
+*/
+void CImImap4Session::SendUntaggedMessageWithTimeoutL(const TDesC8 &aMessage, TInt aTimeout)
+ {
+ __ASSERT_ALWAYS(iSendQueued==EFalse,gPanic(EAlreadySending));
+ if(!(iSendQueued==EFalse))
+ {
+ User::LeaveIfError(KErrInUse); // Already Sending
+ }
+ if (ImapIdleSupported()==EFalse)
+ {
+ // Cancel any dummy operation that might be outstanding
+ CancelDummy();
+ }
+
+ iImapIO->SendWithTimeoutL(iStatus, aTimeout, KImapCommand, &aMessage);
+ DBG((LogText(_L8("*******************************************************"))));
+ DBG((LogText(_L8("CImap4Session::SendUntaggedMessageWithTimeoutL(): waiting for iImapIO to wake me"))));
+ DBG((LogText(_L8("*******************************************************"))));
+
+ SetActive();
+ iSendQueued=ETrue;
+ }
+
+// Construct a full mailbox path, given a TMsvId
+// This is expensive in memory movement terms, as it works UP the path,
+// inserting new data at the start. This is based on the principle that it's
+// more expensive to find an entry in the index with SetEntryL() than it is to
+// move some bytes about, otherwise we'd find the path upwards then create the
+// string downwards.
+HBufC8* CImImap4Session::MakePathL(const TMsvId aTarget, const TBool aIncludeLeaf)
+ {
+ __ASSERT_DEBUG(iState>=EImapStateNoSelect,gPanic(ENotLoggedOn));
+ if(!(iState>=EImapStateNoSelect))
+ {
+ User::LeaveIfError(KErrGeneral);
+ }
+ // Making a path: we start with nothing
+ HBufC8 *path=HBufC8::NewLC(256);
+ TBool skipfirst=ETrue;
+ TMsvId traverse=aTarget;
+
+ // Move to the entry
+ SetEntryL(traverse);
+
+ // Skipping the leaf?
+ if (!aIncludeLeaf && iEntry->Entry().iType!=KUidMsvServiceEntry)
+ {
+ // Up a level before we generate the path
+ SetEntryL(traverse=iEntry->Entry().Parent());
+ }
+
+ // check and see if we are dealing with the INBOX, in which case
+ // return immediately
+ if (iEntry->Entry().Parent()==iServiceId &&
+ iEntry->Entry().iDetails.CompareF(KIMAP_INBOX)==0)
+ {
+ path->Des().Insert(0,_L8("INBOX"));
+ CleanupStack::Pop();
+ return path;
+ }
+
+ iCharConv->PrepareToConvertToFromOurCharsetL(KCharacterSetIdentifierImapUtf7);
+
+ // While we can still go up within this service...
+ while(iEntry->Entry().iType!=KUidMsvServiceEntry)
+ {
+ // Add the name of this component to the path
+ if (!skipfirst)
+ path->Des().Insert(0,iHierarchySeparator);
+ else
+ skipfirst=EFalse;
+
+ // this should be a better sized allocation but the path is
+ // fixed to 256 anyway so this will do
+ HBufC8* utf7=HBufC8::NewL(256);
+ CleanupStack::PushL(utf7);
+
+ TInt numUC, indexUC;
+ TPtr8 des = utf7->Des();
+ iCharConv->ConvertFromOurCharsetL(iEntry->Entry().iDetails, des, numUC, indexUC);
+ path->Des().Insert(0,utf7->Des());
+
+ CleanupStack::PopAndDestroy();
+
+ // Go up a level
+ SetEntryL(traverse=iEntry->Entry().Parent());
+ }
+
+ // Add the path at the very start, if it exists
+ if (iFolderPath.Length())
+ {
+ // Anything there already? If not, don't bother with the separator
+ if (path->Des().Length()) path->Des().Insert(0,iHierarchySeparator);
+ path->Des().Insert(0,iFolderPath);
+ }
+
+ // Pop it off cleanup stack
+ CleanupStack::Pop();
+
+ // Return the path
+ return(path);
+ }
+
+// Queue a connection
+void CImImap4Session::ConnectL(TRequestStatus& aRequestStatus, const TMsvId aService)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND Connect(%x)"),aService)));
+ __ASSERT_DEBUG(iState==EImapStateDisconnected,gPanic(EOpenWhenNotClosed));
+ if(!(iState==EImapStateDisconnected))
+ {
+ User::LeaveIfError(KErrInUse); // Open when not closed.
+ }
+ // Any progress we give now should be related to this connect, so we need to
+ // clear any previous progress first.
+ ResetStats();
+
+ Queue(aRequestStatus);
+
+ // Get host details from server
+ iServiceId=aService;
+ SetEntryL(iServiceId);
+
+ // get the iap preferences
+ CEmailAccounts* account = CEmailAccounts::NewLC();
+ if (iPrefs == NULL)
+ {
+ iPrefs = CImIAPPreferences::NewLC();
+ CleanupStack::Pop(iPrefs);
+ }
+
+ TImapAccount id;
+ id.iImapAccountId = iEntry->Entry().MtmData2(); // iMtmData2 of the service entry contains TImapAccountId
+ id.iImapAccountName = iEntry->Entry().iDetails;
+ id.iImapService = iEntry->Entry().iServiceId;
+ id.iSmtpService = iEntry->Entry().iRelatedId;
+
+ account->LoadImapSettingsL(id, *iServiceSettings);
+ account->LoadImapIapSettingsL(id, *iPrefs);
+ CleanupStack::PopAndDestroy(account);
+
+ // Copy details
+ delete iUsername; //need to delete iUsername first, just in case already it has a value.
+ iUsername = NULL;
+ iUsername=iServiceSettings->LoginName().AllocL();
+ delete iPassword; //need to delete iPassword first, just in case already it has a value.
+ iPassword = NULL;
+ iPassword=iServiceSettings->Password().AllocL();
+ iFolderPath=iServiceSettings->FolderPath();
+ iHost=iServiceSettings->ServerAddress();
+ iPort=iServiceSettings->Port();
+ iUseIdleCommand = iServiceSettings->ImapIdle();
+
+ iIdleTimeout = iServiceSettings->ImapIdleTimeout();
+ DBG((LogText(_L8("ImapIdleTimeout %d"),iIdleTimeout)));
+ // convert from seconds to microseconds
+ iIdleTimeout *= 1000000;
+
+ // Path separator: we store it as a string locally
+ iHierarchySeparator.Zero();
+ if (iServiceSettings->PathSeparator())
+ {
+ // Append to string
+ iHierarchySeparator.Append(iServiceSettings->PathSeparator());
+ }
+
+ // Any characters that will need quoting in them?
+ iLiteralUsername=EFalse;
+ int a;
+ TPtr8 userName = iUsername->Des();
+ for(a=0;a<iUsername->Length();a++)
+ {
+ if (userName[a]<=32 || userName[a]>=127 || userName[a]=='\"' || userName[a]=='%' ||
+ userName[a]=='(' || userName[a]==')' || userName[a]=='*' || userName[a]=='\\' ||
+ userName[a]=='{' || userName[a]=='}' )
+ {
+ iLiteralUsername=ETrue;
+ break;
+ }
+ }
+
+ iLiteralPassword=EFalse;
+ TPtr8 passWord = iPassword->Des();
+ for(a=0;a<iPassword->Length();a++)
+ {
+ if (passWord[a]<=32 || passWord[a]>=127 || passWord[a]=='\"' || passWord[a]=='%' ||
+ passWord[a]=='(' || passWord[a]==')' || passWord[a]=='*' || passWord[a]=='\\' ||
+ passWord[a]=='{' || passWord[a]=='}' )
+ {
+ iLiteralPassword=ETrue;
+ break;
+ }
+ }
+
+ // Until we know we're seeing CC:Mail...
+ iTalkingToCCMail=EFalse;
+ iTalkingToOpenMail=EFalse;
+
+ // Start the connect
+ iState=EImapStateConnectWait;
+ iSendQueued=EFalse;
+ TBool sslWrappedSocket=iServiceSettings->SSLWrapper();
+
+ // if local primarysession is active then set the local textserversion of ImapIO object.
+ if(iPrimarySession)
+ {
+ // Setting of PrimaryTextServerSession, Going to be set on the secondary session.
+ iImapIO->SetPrimaryTextServerSession(iPrimarySession->GetImap4Session()->GetTextServerSession());
+ }
+ iImapIO->ConnectL(iStatus,iHost,iPort,*iPrefs, sslWrappedSocket);
+
+#ifdef PRINTING
+ // Log version number now logfile is open
+ LogText(_L8("IMPS release 022.8"));
+
+ // Log connection destination
+ LogText(_L8("Connection queued to %S, port %d"),&iHost,iPort);
+
+ // Note any literal usage
+ if (iLiteralUsername)
+ LogText(_L8("Username contains unusual characters: using literal for username"));
+ if (iLiteralPassword)
+ LogText(_L8("Password contains unusual characters: using literal for password"));
+#endif
+ SetActive();
+ }
+
+// Queue a disconnection
+void CImImap4Session::DisconnectL(TRequestStatus& aRequestStatus)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND Disconnect"))));
+ Cancel();
+
+ Queue(aRequestStatus);
+
+ // What are we doing at the moment?
+ if (iState<EImapStateNoSelect)
+ {
+ DoDisconnect();
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::DisconnectL(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+ Complete(KErrNone);
+ }
+ else if (IsIdling())
+ {
+ iDisconnectAfterIdleStopped = ETrue;
+ DoStopIdleL();
+ }
+ else
+ {
+ DoDisconnectL();
+ }
+ }
+
+void CImImap4Session::DoDisconnectL()
+ {
+ // Send logout command
+ iState=EImapStateLogoutWait;
+ SendMessageL(KIMAPC_LOGOUT);
+ }
+
+// Are we connected?
+TBool CImImap4Session::Connected()
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND Connected?"))));
+
+ if (iState>=EImapStateNoSelect)
+ return(ETrue);
+ return(EFalse);
+ }
+
+// Are we busy?
+TBool CImImap4Session::Busy()
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND Busy?"))));
+
+ if (iState==EImapStateDisconnected ||
+ iState==EImapStateNoSelect ||
+ iState==EImapStateSelected ||
+ iState==EImapStateIdling)
+ return(EFalse);
+ return(ETrue);
+ }
+
+// Setting of PrimarySession, Going to be set on the secondary session.
+void CImImap4Session::SetPrimarySession(CActiveWrapper* aPrimarySession)
+ {
+ iPrimarySession=aPrimarySession;
+ }
+
+// Return of current textserversession
+CImTextServerSession* CImImap4Session::GetTextServerSession()
+ {
+ return iImapIO->GetTextServerSession();
+ }
+
+// Return the service settings
+CImImap4Settings* CImImap4Session::ServiceSettings()
+ {
+ // Return them
+ return(iServiceSettings);
+ }
+
+// List folder structure
+void CImImap4Session::ListL(TRequestStatus& aRequestStatus, const TMsvId aFolder, CArrayPtr<CImImap4DirStruct>* aList)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND List(%x)"),aFolder)));
+ __ASSERT_DEBUG(iState==EImapStateNoSelect || iState==EImapStateSelected,gPanic(ESelectWhenNotReady));
+ if(!(iState==EImapStateNoSelect || iState==EImapStateSelected))
+ {
+ User::LeaveIfError(KErrNotReady);// Select when not ready
+ }
+ Queue(aRequestStatus);
+
+ // Form the path
+ HBufC8* path=NULL;
+ TRAPD(err,path=MakePathL(aFolder,ETrue));
+ if (err!=KErrNone)
+ {
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::ListL(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+ Complete(err);
+ return;
+ }
+ CleanupStack::PushL(path);
+
+ // Empty path? If not, append hierarchy separator
+ if (path->Length())
+ {
+ // Get more space
+ HBufC8 *rpath=path->ReAllocL(path->Length()+1);
+
+ // Moved?
+ if (path!=rpath)
+ {
+ // Get rid of old one and push new one
+ CleanupStack::Pop();
+ CleanupStack::PushL(path=rpath);
+ }
+
+ path->Des().Append(iHierarchySeparator);
+ }
+
+ // Save path, as we take this off replies to get the leaf: we need to
+ // do this *before* we quote it as replies will be unquoted by the
+ // time we process them.
+ iCommandBuf.Copy(*path);
+
+ // Quote it
+ DoQuoteL(path);
+
+ // Send the command
+ NewTag();
+ iImapIO->SendL(iStatus,_L8("%d LIST \"\" \"%S%%\"\r\n"),iTag,path);
+ NewTagSent();
+
+ // Save list pointer to add to, and reset it
+ iList=aList;
+ iList->ResetAndDestroy();
+
+ // Dispose of path
+ CleanupStack::PopAndDestroy();
+
+ // Save last state (selected/unselected) for restoring afterwards
+ iSavedState=iState;
+ iState=EImapStateListWait;
+ }
+
+// Update subscribed bits on local folder structure
+// Local structure must have been refreshed before this is used:
+// if folders listed in the LSUB reply don't exist, they're ignored
+// silently.
+void CImImap4Session::LsubL(TRequestStatus& aRequestStatus)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND Lsub"))));
+ __ASSERT_DEBUG(iState==EImapStateNoSelect || iState==EImapStateSelected,gPanic(ESelectWhenNotReady));
+ if(!(iState==EImapStateNoSelect || iState==EImapStateSelected))
+ {
+ User::LeaveIfError(KErrNotReady);
+ }
+
+ Queue(aRequestStatus);
+
+ // First, we need to go through the entire service, resetting all
+ // the 'remote subscribed' flags.
+ ResetSubscriptionFlagsL(iServiceId);
+
+ // Build a buffer to quote the folder path (it may be necessary)
+ HBufC8* path=HBufC8::NewL(iFolderPath.Length()+1);
+ CleanupStack::PushL(path);
+ path->Des().Append(iFolderPath);
+ if (iFolderPath.Length())
+ path->Des().Append(iHierarchySeparator);
+
+ // Quote it
+ DoQuoteL(path);
+
+ // Send the command to list all of the folders in the tree: we
+ // can't do it hierarchically, as the servers aren't clever enough
+ // to tell us at parent levels about folders that contain subscribed
+ // children. Pah.
+ NewTag();
+
+ // Make a Des & send it
+ TPtrC8 pathdes(path->Des());
+ iImapIO->SendL(iStatus,_L8("%d LSUB \"\" \"%S*\"\r\n"),
+ iTag,&pathdes);
+ NewTagSent();
+
+ // Clear up
+ CleanupStack::PopAndDestroy();
+
+ // Wait for reply
+ iSavedState=iState;
+ iState=EImapStateLsubWait;
+ }
+
+
+// Create a mailbox or folder
+void CImImap4Session::Create(TRequestStatus& aRequestStatus, const TMsvId aParent, const TDesC& aLeafName, const TBool aFolder)
+ {
+ TInt err=KErrNone;
+ if (!Connected())
+ {
+ Queue(aRequestStatus);
+ err=KErrDisconnected;
+ }
+ else
+ TRAP(err,CreateL(aRequestStatus, aParent, aLeafName, aFolder));
+ if (err!=KErrNone)
+ {
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::Create(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+ Complete(err);
+ }
+ }
+
+// Create a mailbox or folder
+void CImImap4Session::CreateL(TRequestStatus& aRequestStatus, const TMsvId aParent, const TDesC& aLeafName, const TBool aFolder)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND Create(%x,%S,%d)"),aParent,&aLeafName,aFolder)));
+ __ASSERT_DEBUG(iState==EImapStateNoSelect || iState==EImapStateSelected,gPanic(ECreateWhenNotReady));
+ if(!(iState==EImapStateNoSelect || iState==EImapStateSelected))
+ {
+ User::LeaveIfError(KErrNotReady);
+ }
+ Queue(aRequestStatus);
+
+ // Make the path
+ iSavedState=iState;
+ iState=EImapStateCreateWait;
+ HBufC8 *path=NULL; // To stop .AER warnings...
+ TRAPD(err,path=MakePathL(aParent,ETrue));
+ if (err!=KErrNone)
+ {
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::CreateL(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+ Complete(err);
+ return;
+ }
+ CleanupStack::PushL(path);
+
+ // Increase length of buffer for full name
+ TInt encodedLeafMaxSize = 2+aLeafName.Size()*4/3 + 1;
+ HBufC8* rpath=path->ReAllocL(path->Length()+2+encodedLeafMaxSize);
+
+ // Moved?
+ if (rpath!=path)
+ {
+ // Destroy old one and push new one
+ CleanupStack::Pop();
+ CleanupStack::PushL(path=rpath);
+ }
+
+ // Path not blank? Put a separator in there
+ if (path->Des().Length())
+ path->Des().Append(iHierarchySeparator);
+
+ iCharConv->PrepareToConvertToFromOurCharsetL(KCharacterSetIdentifierImapUtf7);
+
+ // Add leafname, via the utf7 encoder
+ HBufC8* utf7=HBufC8::NewL(encodedLeafMaxSize);
+ CleanupStack::PushL(utf7);
+
+ TInt numUC, indexUC;
+ TPtr8 des = utf7->Des();
+ iCharConv->ConvertFromOurCharsetL(aLeafName, des, numUC, indexUC);
+ path->Des().Append(des);
+
+ CleanupStack::PopAndDestroy(); // utf7
+
+ // Put a trailing hierarchy separator on there too if necessary
+ if (aFolder)
+ path->Des().Append(iHierarchySeparator);
+
+ // Quote it if necessary
+ DoQuoteL(path);
+ TPtrC8 pathdes(path->Des());
+
+ // Creating a folder
+ NewTag();
+ iImapIO->SendL(iStatus,_L8("%d CREATE \"%S\"\r\n"),iTag,&pathdes);
+ NewTagSent();
+
+ // Save what type of create we were doing
+ iCommandFlags[0]=aFolder;
+ iCommandIds[0]=aParent;
+ iCommandBuf=aLeafName;
+
+ // Free memory
+ CleanupStack::PopAndDestroy();
+ }
+
+// Rename a mailbox
+void CImImap4Session::Rename(TRequestStatus& aRequestStatus, const TMsvId aTarget, const TDesC& aNewName)
+ {
+ TInt err=KErrNone;
+ if (!Connected())
+ {
+ Queue(aRequestStatus);
+ err=KErrDisconnected;
+ }
+ else
+ TRAP(err,RenameL(aRequestStatus, aTarget, aNewName));
+ if (err!=KErrNone)
+ Complete(err);
+ }
+
+// Rename a mailbox
+void CImImap4Session::RenameL(TRequestStatus& aRequestStatus, const TMsvId aTarget, const TDesC& aNewName)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND Rename(%x,%S)"),aTarget,&aNewName)));
+ __ASSERT_DEBUG(iState==EImapStateNoSelect || iState==EImapStateSelected,gPanic(ERenameWhenNotReady));
+ if(!(iState==EImapStateNoSelect || iState==EImapStateSelected))
+ {
+ User::LeaveIfError(KErrNotReady);
+ }
+
+ Queue(aRequestStatus);
+
+ // Make the paths
+ HBufC8* path=NULL; // To stop .AER warnings...
+ TRAPD(err,path=MakePathL(aTarget,ETrue));
+ if (err!=KErrNone)
+ {
+ Complete(err);
+ return;
+ }
+ CleanupStack::PushL(path);
+
+ DoQuoteL(path);
+
+ HBufC8* newpath=NULL; // To stop .AER warnings...
+ TRAP(err,newpath=MakePathL(aTarget,EFalse));
+ if (err!=KErrNone)
+ {
+ CleanupStack::PopAndDestroy();
+ Complete(err);
+ return;
+ }
+ CleanupStack::PushL(newpath);
+
+ // Extend buffer for new name
+ TInt encodedLeafMaxSize = 2+aNewName.Size()*4/3 + 1;
+ HBufC8* rnewpath=newpath->ReAllocL(newpath->Length()+2+encodedLeafMaxSize);
+
+ // Moved?
+ if (rnewpath!=newpath)
+ {
+ // Destroy old one and push new one
+ CleanupStack::Pop();
+ CleanupStack::PushL(newpath=rnewpath);
+ }
+
+ if (newpath->Length())
+ newpath->Des().Append(iHierarchySeparator);
+
+ iCharConv->PrepareToConvertToFromOurCharsetL(KCharacterSetIdentifierImapUtf7);
+
+ // Add leafname, via the utf7 encoder
+ HBufC8* utf7=HBufC8::NewL(encodedLeafMaxSize);
+ CleanupStack::PushL(utf7);
+
+ TInt numUC, indexUC;
+ TPtr8 des = utf7->Des();
+ iCharConv->ConvertFromOurCharsetL(aNewName, des, numUC, indexUC);
+ newpath->Des().Append(des);
+
+ CleanupStack::PopAndDestroy(); // utf7
+
+ DoQuoteL(newpath);
+
+ // Save rename parameters
+ iCommandIds[0]=aTarget;
+ iCommandBuf=aNewName; // this is still the original, unencoded name
+
+ // Set states
+ iSavedState=iState;
+ iState=EImapStateRenameWait;
+
+ // Send the command
+ NewTag();
+ TPtrC8 pathdes(path->Des());
+ TPtrC8 newpathdes(newpath->Des());
+ iImapIO->SendL(iStatus,_L8("%d RENAME \"%S\" \"%S\"\r\n"),iTag,&pathdes,&newpathdes);
+ NewTagSent();
+
+ // Free memory
+ CleanupStack::PopAndDestroy(2);
+ }
+
+// Delete a message/mailbox
+void CImImap4Session::Delete(TRequestStatus& aRequestStatus, const CMsvEntrySelection& aTargetSel)
+ {
+ TInt err=KErrNone;
+ if (!Connected())
+ err=KErrDisconnected;
+ else
+ TRAP(err,DeleteL(aRequestStatus,aTargetSel));
+ if (err!=KErrNone)
+ {
+ Queue(aRequestStatus);
+ Complete(err);
+ }
+ }
+
+// Delete a message/mailbox
+void CImImap4Session::Delete(TRequestStatus& aRequestStatus, const TMsvId aTarget)
+ {
+ TInt err=KErrNone;
+ if (!Connected())
+ err=KErrDisconnected;
+ else
+ TRAP(err,DeleteL(aRequestStatus,aTarget));
+ if (err!=KErrNone)
+ {
+ Queue(aRequestStatus);
+ Complete(err);
+ }
+ }
+void CImImap4Session::DeleteEntryL( const TMsvId aTarget)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND Delete(%x)"),aTarget)));
+
+ // Move to the entry in question
+ SetEntryL(aTarget);
+
+ LOG_COMMANDS(( LogText(_L8("COMMAND Delete(message)"))));
+ __ASSERT_DEBUG(iState==EImapStateSelected,gPanic(EDeleteWhenNotReady));
+ if(!(iState==EImapStateSelected))
+ {
+ User::LeaveIfError(KErrNotReady);
+ }
+
+ // SJM, remove check for right mailbox as we may be trying to
+ // delete a moved entry which is in fact no longer in the
+ // right mailbox.
+#if 0
+ // Check we're in the right mailbox
+ if (iEntry->Entry().Parent()!=iMailboxId)
+ {
+ // Nope.
+ Queue(aRequestStatus);
+ Complete(KErrImapWrongFolder);
+ return;
+ }
+#endif
+
+ // Set deleted flag on this entry
+ TMsvEmailEntry entry=iEntry->Entry();
+ entry.SetDeletedIMAP4Flag(ETrue);
+ ChangeEntryL(entry);
+ }
+
+void CImImap4Session::DeleteL(TRequestStatus& aRequestStatus, const CMsvEntrySelection& aTargetSel)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND Delete (%x)"),aTargetSel[0])));
+
+ // Move to the entry in question
+ SetEntryL(aTargetSel[0]);
+
+ CMsvEntrySelection* sel=aTargetSel.CopyL();
+ delete iSelection;
+ iSelection=sel;
+
+ // Only deleting message seletion currently
+ if (iEntry->Entry().iType==KUidMsvMessageEntry)
+ {
+ // Set delete flag on all selected entries.
+ TInt count=iSelection->Count();
+ while (count--)
+ DeleteEntryL((*iSelection)[count]);
+
+ // Force a folder close with expunge
+ CloseL(aRequestStatus,ETrue);
+ }
+ else
+ {
+ LOG_COMMANDS(( LogText(_L8("COMMAND Delete - Can only delete selection of Messages"))));
+
+ // Deleting selection of entries whicxh are not messages
+ Queue(aRequestStatus);
+ Complete(KErrNotSupported);
+ }
+ }
+
+void CImImap4Session::DeleteL(TRequestStatus& aRequestStatus, const TMsvId aTarget)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND Delete(%x)"),aTarget)));
+
+ // Move to the entry in question
+ SetEntryL(aTarget);
+
+ // A message?
+ if (iEntry->Entry().iType==KUidMsvMessageEntry)
+ {
+ DeleteEntryL(aTarget);
+
+ // Temporary: force a folder close with expunge
+ CloseL(aRequestStatus,ETrue);
+ }
+ // A folder?
+ else if (iEntry->Entry().iType==KUidMsvFolderEntry)
+ {
+ LOG_COMMANDS(( LogText(_L8("COMMAND Delete(folder)"))));
+ __ASSERT_DEBUG(iState==EImapStateNoSelect,gPanic(EDeleteWhenNotReady));
+ if(!(iState==EImapStateNoSelect))
+ {
+ User::LeaveIfError(KErrNotReady);
+ }
+
+ Queue(aRequestStatus);
+
+ // Save IDs of parent and target for actually doing the local delete when
+ // the remote one completes successfully.
+ iCommandIds[0]=iEntry->Entry().Parent();
+ iCommandIds[1]=aTarget;
+
+ // Get path to delete
+ HBufC8* path=NULL; // To stop .AER warnings...
+ path=MakePathL(aTarget,ETrue);
+ CleanupStack::PushL(path);
+ DoQuoteL(path);
+
+ // Set state
+ iSavedState=iState;
+ iState=EImapStateDeleteWait;
+
+ // Send command
+ NewTag();
+ TPtrC8 pathdes(path->Des());
+ iImapIO->SendL(iStatus,_L8("%d DELETE \"%S\"\r\n"),iTag,&pathdes);
+ NewTagSent();
+
+ // Destroy buffer
+ CleanupStack::PopAndDestroy();
+ }
+ // Something else?
+ else
+ {
+ LOG_COMMANDS(( LogText(_L8("COMMAND Delete(unknown)"))));
+
+ // Delete of something that isn't a folder or a message. Erk!
+ Queue(aRequestStatus);
+ Complete(KErrNotSupported);
+ }
+ }
+
+// Delete everything in this folder
+void CImImap4Session::DeleteAllMessagesL(TRequestStatus& aRequestStatus)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND DeleteAllMessages(%x)"),iMailboxId)));
+
+ // We have to be selected
+ __ASSERT_DEBUG(iState==EImapStateSelected,gPanic(EDeleteWhenNotReady));
+ if(!(iState==EImapStateSelected))
+ {
+ User::LeaveIfError(KErrNotReady);
+ }
+
+ Queue(aRequestStatus);
+
+ // We're going to send a command of one style or another
+ NewTag();
+
+ // Are there any messages remotely to delete?
+ if (iMailboxSize==0)
+ {
+ // No: just do a close
+ iState=EImapStateCommandWait;
+ iSavedState=EImapStateNoSelect;
+ iImapIO->SendL(iStatus,_L8("%d CLOSE\r\n"),iTag);
+ }
+ else
+ {
+ // DeleteAllMessages is a special case: we want to delete everything,
+ // regardless of wether it's in the mirror or not. So, we set deleted
+ // flags on everything then expunge the folder
+ iState=EImapStateDeleteAllWait;
+
+ // Send command: we go into deleteall wait as the next
+ iImapIO->SendL(iStatus,_L8("%d STORE 1:* +FLAGS (\\Deleted)\r\n"),iTag);
+ }
+
+ // Sent it
+ NewTagSent();
+ }
+
+// Select a folder
+void CImImap4Session::Select(TRequestStatus& aRequestStatus, const TMsvId aFolder, const TBool aReadWrite)
+ {
+ TInt err=KErrNone;
+ if (!Connected())
+ {
+ Queue(aRequestStatus);
+ err=KErrDisconnected;
+ }
+ else
+ TRAP(err,SelectL(aRequestStatus, aFolder, aReadWrite));
+ if (err!=KErrNone)
+ {
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::Select(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+ Complete(err);
+ }
+ }
+
+void CImImap4Session::SelectL(TRequestStatus& aRequestStatus, const TMsvId aFolder, const TBool aReadWrite)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND Select(%x (rw=%d), in state %d. Current mailbox=%x)"),aFolder,aReadWrite?1:0,iState,iMailboxId)));
+
+ if (!(iState==EImapStateNoSelect || iState==EImapStateSelected))
+ {
+ User::LeaveIfError(KErrNotReady);
+ }
+ Queue(aRequestStatus);
+
+ // reset counts to safe values here to avoid reporting left over
+ // values from previous fetch. Correct values will be set up once
+ // headers have been fetched and parts counted.
+ iProgress.iPartsToDo=iProgress.iBytesToDo=1;
+ iProgress.iPartsDone=iProgress.iBytesDone=0;
+
+ // Do the select
+
+ // Is it already selected and the read/write state is compatible?
+ // Skip the command if possible!
+ if (iMailboxId==aFolder && iState==EImapStateSelected &&
+ ((aReadWrite && iMailboxWritable) || !aReadWrite))
+ {
+ if (ImapIdleSupported())
+ {
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::SelectL(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+ Complete(KErrNone);
+ return;
+ }
+ else
+ {
+ // Just NOOP it so that we know about any mailbox size changes
+ NewTag();
+ iImapIO->SendL(iStatus,_L8("%d NOOP\r\n"),iTag);
+ iState=EImapStateSelectWait;
+ NewTagSent();
+ return;
+ }
+ }
+
+ // Ok looks as if the SELECT actually needs to be done.
+ DoSelectL(aFolder, aReadWrite);
+ }
+
+void CImImap4Session::DoSelectL(const TMsvId aFolder, const TBool aReadWrite)
+ {
+ NewTag();
+
+ // We should always get an EXISTS after a SELECT but just in case
+ // we will force it true here. This ensures that a NewOnlySync
+ // will always do the sync when it involves selecting a new folder
+ iMailboxReceivedExists=ETrue;
+
+ // Store name of new mailbox
+ iMailboxId=aFolder;
+
+ // Get rid of old index and reset everything
+ iFolderIndex.Reset();
+ iMailboxSize=0;
+ iMsgsDone=0;
+ iMailboxRecent=0;
+ iUidValidity=0;
+ iUidNext=0;
+
+ // Is it special-case inbox?
+ SetEntryL(iMailboxId);
+#if 0
+ if (iEntry->Entry().Parent()==iServiceId && iEntry->Entry().iDetails.CompareF(KIMAP_INBOX)==0)
+ {
+ // Inbox: no path prepended
+ iImapIO->SendL(iStatus,aReadWrite?_L8("%d SELECT INBOX\r\n"):_L8("%d EXAMINE INBOX\r\n"),iTag);
+ iMailboxIsInbox=ETrue;
+ }
+ else
+ {
+#endif
+ // Create path and send select command
+ HBufC8* path=MakePathL(iMailboxId,ETrue);
+ CleanupStack::PushL(path);
+ DoQuoteL(path);
+ TPtrC8 pathptr=path->Des();
+ iImapIO->SendL(iStatus,aReadWrite?_L8("%d SELECT \"%S\"\r\n"):_L8("%d EXAMINE \"%S\"\r\n"),iTag,&pathptr);
+ CleanupStack::PopAndDestroy();
+#if 0
+ iMailboxIsInbox=EFalse;
+ }
+#endif
+ // Sent command
+ iState=EImapStateSelectWait;
+ NewTagSent();
+ }
+
+// Copy a message to a new folder
+void CImImap4Session::Copy(TRequestStatus& aRequestStatus, const TMsvId aSource, const TMsvId aDestination, TBool aUnSelectIfSameFolder)
+ {
+ TInt err=KErrNone;
+ if (!Connected())
+ {
+ Queue(aRequestStatus);
+ err=KErrDisconnected;
+ }
+ else
+ TRAP(err,CopyL(aRequestStatus, aSource, aDestination, aUnSelectIfSameFolder));
+ if (err!=KErrNone)
+ {
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::Copy(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+ Complete(err);
+ }
+ }
+
+void CImImap4Session::CopyL(TRequestStatus& aRequestStatus, const TMsvId aSource, const TMsvId aDestination, TBool aUnSelectIfSameFolder)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND Copy(%x,%x)"),aSource,aDestination)));
+ __ASSERT_DEBUG(iState==EImapStateSelected,gPanic(ECopyWhenNotSelected));
+ if(!(iState==EImapStateSelected))
+ {
+ User::LeaveIfError(KErrArgument);//Copy when not selected
+ }
+
+ Queue(aRequestStatus);
+
+ // Make destination folder path
+ HBufC8 *command=NULL; // To stop .AER warnings...
+ TRAPD(err,command=MakePathL(aDestination,ETrue));
+ if (err!=KErrNone)
+ {
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::CopyL(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+ Complete(err);
+ return;
+ }
+ CleanupStack::PushL(command);
+ DoQuoteL(command);
+
+ // Check that source is in the folder we have got selected: this also
+ // ensures it's actually a message, as the parent of attachments wouldn't
+ // be the folder we have selected...
+ SetEntryL(aSource);
+ __ASSERT_DEBUG(iEntry->Entry().Parent()==iMailboxId,gPanic(ECopyNotFromSelectedFolder));
+ if(!(iEntry->Entry().Parent()==iMailboxId))
+ {
+ User::LeaveIfError(KErrGeneral);
+ }
+ // Make command
+ TMsvEmailEntry entry=iEntry->Entry();
+ NewTag();
+ TPtrC8 commanddes(command->Des());
+ iImapIO->SendL(iStatus,_L8("%d UID COPY %u \"%S\"\r\n"),iTag,entry.UID(),&commanddes);
+ NewTagSent();
+
+ // If we're copying to the currently selected folder, pretend we've unselected it.
+ // This ensures we'll pick up on any changes in the next new sync
+ if (aUnSelectIfSameFolder && iMailboxId==aDestination && iState==EImapStateSelected)
+ {
+ // Return to 'no select' state after this
+ iSavedState=EImapStateNoSelect;
+ }
+ else
+ {
+ // Save existing selected/unselected state
+ iSavedState=iState;
+ }
+
+ // Set up state
+ iState=EImapStateCommandWait;
+
+ // Delete buffer
+ CleanupStack::PopAndDestroy();
+ }
+
+// Copy a message to a new folder
+void CImImap4Session::Append(TRequestStatus& aRequestStatus, const TMsvId aSource, const TMsvId aDestination)
+ {
+ TInt err=KErrNone;
+ if (!Connected())
+ {
+ Queue(aRequestStatus);
+ err=KErrDisconnected;
+ }
+ else
+ TRAP(err,AppendL(aRequestStatus, aSource, aDestination));
+ if (err!=KErrNone)
+ Complete(err);
+ }
+
+void CImImap4Session::AppendL(TRequestStatus& aRequestStatus, const TMsvId aSource, const TMsvId aDestination)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND Append(%x,%x)"),aSource,aDestination)));
+
+ Queue(aRequestStatus);
+
+ // Check that source is a complete message
+ SetEntryL(aSource);
+
+ // Check it's a message
+ if (iEntry->Entry().iType!=KUidMsvMessageEntry)
+ {
+ // Can't do it!
+ Complete(KErrGeneral);
+ return;
+ }
+
+ // Cancel any dummy operation that might be outstanding
+ if (ImapIdleSupported()==EFalse)
+ {
+ CancelDummy();
+ }
+
+ // Size message for sending using a CImCalculateMessageSize
+ delete iMessageSizer;
+ iMessageSizer=NULL;
+ iMessageSizer=CImCalculateMsgSize::NewL(iFs,*iEntry);
+
+ // Start sizing operation, using MIME: save source & destination for next step
+ // Use iHost (hostname of remote server) as the domain name seed for the MsgId
+ iCommandIds[0]=aSource;
+ iCommandIds[1]=aDestination;
+ iMessageDate=iEntry->Entry().iDate;
+ iMessageSizer->StartL(iStatus,iCommandIds[0], ESendAsMimeEmail,
+ iMessageDate, iHost, iCharset);
+
+ // If we're appending to the currently selected folder, pretend we've unselected it.
+ // This ensures we'll pick up on any changes in the next new sync
+ if (iMailboxId==aDestination && iState==EImapStateSelected)
+ {
+ // Return to 'no select' state after this
+ iSavedState=EImapStateNoSelect;
+ }
+ else
+ {
+ // Save existing selected/unselected state
+ iSavedState=iState;
+ }
+
+ iState=EImapStateAppendSizeWait;
+ if (!IsActive()) SetActive();
+ }
+
+// Close a selected folder
+void CImImap4Session::Close(TRequestStatus& aRequestStatus, const TBool aExpunge)
+ {
+ TInt err=KErrNone;
+ if (!Connected())
+ {
+ Queue(aRequestStatus);
+ err=KErrDisconnected;
+ }
+ else
+ TRAP(err,CloseL(aRequestStatus, aExpunge));
+ if (err!=KErrNone)
+ Complete(err);
+ }
+
+void CImImap4Session::CloseL(TRequestStatus& aRequestStatus, const TBool aExpunge)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND Close(%d)"),aExpunge)));
+
+ Queue(aRequestStatus);
+
+ // Folder currently open? Just complete if we're not selected
+ if (iState==EImapStateNoSelect)
+ {
+ Complete(KErrNone);
+ return;
+ }
+
+ // Expunging? If not, just send close command
+ if (!aExpunge)
+ {
+ // Send close
+ SendMessageL(KIMAPC_CLOSE);
+ iSavedState=EImapStateNoSelect;
+ iState=EImapStateCommandWait;
+ }
+ else
+ {
+ // The deletion strategy will build up runs of UIDs in order to make the
+ // most efficient use of bandwidth. However, as UIDs are not necessarily
+ // contiguous, so we use their sorted position in the mirror to decide on
+ // runs. UIDs will be allocated on an increasing basis,as the server cannot
+ // have magically invented new UIDs between 2 existing ones.
+ // UIds can miss from run by using email sync limits.
+ //
+ // We use TInt64's because AppendNum() cannot take a TUint32, which is
+ // what a UID is.
+ SetEntryL(iMailboxId);
+ GetChildrenL(*iSelection);
+ TRAPD(err,MakeSortedFolderIndexL());
+ if (err!=KErrNone)
+ {
+ Complete(err);
+ return;
+ }
+ TInt pos=0;
+ TInt run=0;
+ TInt64 last=0;
+ TInt deleted=0;
+
+ // Build command
+ HBufC8* command=HBufC8::NewLC(256);
+
+ // Start command
+ command->Des().Append(_L8("UID STORE "));
+
+ while(pos<iFolderIndex.Size())
+ {
+ // Look for messages with deleted flag set
+ SetEntryL(iFolderIndex[pos].iMsvId);
+ if (((TMsvEmailEntry)iEntry->Entry()).DeletedIMAP4Flag())
+ {
+ LOG_COMMANDS((LogText(_L8("Message #%d marked as deleted"),pos+1)));
+
+ deleted++;
+ // If uids are missing from run
+ if ((pos > 0 )&& (((TUint)iFolderIndex[pos].iUid - (TUint)iFolderIndex[pos-1].iUid) > 1))
+ {
+ // Breaking a run
+ if(run > 1)
+ {
+ // A run of at least 2 is a range. Append 'last' UID,
+ // after removing comma and append a comma
+ command->Des().Delete(command->Des().Length()-1,1);
+ command->Des().Append(_L8(":"));
+ command->Des().AppendNum(last);
+ command->Des().Append(_L8(","));
+ }
+ // run broken
+ run = 0;
+ }
+
+ // This one is deleted. Are we in a run?
+ if (!run)
+ {
+ // No, start of a run/single item. Add to command
+ // Enough room for this?
+ if ((command->Length()+32)>command->Size())
+ {
+ // Extend buffer
+ HBufC8* rcommand=command->ReAllocL(command->Size()+64);
+
+ // Moved?
+ if (rcommand!=command)
+ {
+ // Destroy old one and push new one
+ CleanupStack::Pop();
+ CleanupStack::PushL(command=rcommand);
+ }
+ }
+
+ // Single number, plus a comma to terminate
+ TInt64 uid=(TUint)((TMsvEmailEntry)iEntry->Entry()).UID();
+ command->Des().AppendNum(uid);
+ command->Des().Append(_L8(","));
+
+ // A run of 1 :-)
+ run++;
+ }
+ else
+ {
+ // We're in a run already. Extend it: it will be terminated
+ // when the run is broken or we exit.
+ last=(TUint)iFolderIndex[pos].iUid;
+ run++;
+ }
+ }
+ else
+ {
+ // Mark this MsvId as 0 (so it will be kept in local expunge)
+ iFolderIndex[pos].iMsvId=0;
+
+ // Breaking a run?
+ if (run>1)
+ {
+ // A run of at least 2 is a range. Append 'last' UID,
+ // after removing comma
+ command->Des().Delete(command->Des().Length()-1,1);
+ command->Des().Append(_L8(":"));
+ command->Des().AppendNum(last);
+ command->Des().Append(_L8(","));
+ }
+
+ // Run broken
+ run=0;
+ }
+
+ // Next message
+ pos++;
+ }
+
+ // Anything deleted?
+ if (deleted)
+ {
+ // Remove the last character in the command string
+ command->Des().Delete(command->Des().Length()-1,1);
+
+ // A run to complete?
+ if (run>1)
+ {
+ // A run of at least 2 is a range. Append 'last' UID.
+ command->Des().Append(_L8(":"));
+ command->Des().AppendNum(last);
+ }
+
+ // Append flags & send command
+ command->Des().Append(_L8(" +FLAGS (\\Deleted)"));
+ SendMessageL(command->Des());
+ iState=EImapStateDeleteMarkWait;
+ }
+ else
+ {
+ // Nothing to do: Just close the folder
+ iState=EImapStateCloseWait;
+ SendMessageL(KIMAPC_CLOSE);
+ }
+
+ // Get rid of command buffer
+ CleanupStack::PopAndDestroy();
+ }
+ }
+
+// Orphan a local message
+void CImImap4Session::OrphanMessageL(const TMsvId aMessage)
+ {
+ DBG((LogText(_L8("OrphanMessageL(%x)"),aMessage)));
+
+ // We no longer orphan the messages based on whether they have been downloaded.
+ // That was just delaying the inevitable and causing other problems
+ DeleteMessageL(aMessage);
+ DBG((LogText(_L8(" Deleting message"))));
+ }
+
+// Delete a local message
+void CImImap4Session::DeleteMessageL(const TMsvId aMessage)
+ {
+ DBG((LogText(_L8("CImImap4Session::DeleteMessageL(%x)"),aMessage)));
+
+ if(aMessage == KMsvNullIndexEntryId)
+ {
+ DBG((LogText(_L8("Attempted delete of null entry(%d)"),aMessage)));
+ return;
+ }
+ // Delete message and all subparts: first, move to parent
+ DBG((LogText(_L8(" SetEntry(%x)"),aMessage)));
+ SetEntryL(aMessage);
+
+ DBG((LogText(_L8(" SetEntry(%x)"),iEntry->Entry().Parent())));
+ SetEntryL(iEntry->Entry().Parent());
+
+ // Do it
+ DBG((LogText(_L8(" About to DeleteEntry(%x)"),aMessage)));
+ // Do not leave when entry is in use
+ TInt err (iEntry->DeleteEntry(aMessage));
+ if(err==KErrInUse)
+ {
+ DBG((LogText(_L8("CImImap4Session::DeleteMessageL() dont leave if err = KErrInUse"))));
+ }
+ else
+ {
+ User::LeaveIfError(err);
+ }
+
+ DBG((LogText(_L8(" Done!"))));
+ }
+
+// Get MESSAGE ONLY children of a folder. Ignore shadows as they are
+// not going to be synced against the server
+void CImImap4Session::GetMessageChildrenL(const TMsvId aFolder, CMsvEntrySelection* aChildren)
+ {
+ // Get *all* the children
+ SetEntryL(aFolder);
+ GetChildrenL(*aChildren);
+
+ if(iCachedEntryData)
+ {
+ delete iCachedEntryData;
+ iCachedEntryData = 0;
+ }
+ iCachedEntryData = new(ELeave) CArrayFixFlat<TMsvCacheData>(5);
+
+ // Go through them, checking to see if they're messages and removing ones that aren't
+ TInt pos=0;
+ while(pos<aChildren->Count())
+ {
+ TMsvEntry* entryPtr;
+ TMsvId id = (*aChildren)[pos];
+ User::LeaveIfError(iEntry->GetEntryFromId(id,entryPtr));
+
+ // Is it a message? And is it real (not shadow)
+ if (entryPtr->iType!=KUidMsvMessageEntry ||
+ entryPtr->iRelatedId != KMsvNullIndexEntryId )
+ {
+ // No, remove it
+ aChildren->Delete(pos,1);
+ }
+ else
+ {
+ //cache two parts of the TMsvEntry data to avoid having to refind it later
+ TMsvCacheData data;
+ data.iOrphan = ((TMsvEmailEntry)(*entryPtr)).Orphan();
+ data.iUid = ((TMsvEmailEntry)(*entryPtr)).UID();
+ iCachedEntryData->AppendL(data);
+ // Next entry
+ pos++;
+ }
+ }
+ }
+
+// Synchronise a folder
+void CImImap4Session::Synchronise(TRequestStatus& aRequestStatus, TBool aNewOnly)
+ {
+ DBG((LogText(_L8("CImImap4Session::Synchronise()"))));
+ TInt err=KErrNone;
+ if (!Connected())
+ {
+ Queue(aRequestStatus);
+ err=KErrDisconnected;
+ }
+ else
+ TRAP(err,SynchroniseL(aRequestStatus, aNewOnly));
+ if (err!=KErrNone)
+ {
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::Synchronise(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+
+ Complete(err);
+ }
+ }
+
+void CImImap4Session::SynchroniseL(TRequestStatus& aRequestStatus, TBool aNewOnly)
+ {
+ __ASSERT_DEBUG(iState==EImapStateSelected,gPanic(ESyncWhenNotSelected));
+ if(!(iState==EImapStateSelected))
+ {
+ User::LeaveIfError(KErrArgument);// Sync when not selected .
+ }
+
+ Queue(aRequestStatus);
+
+ DoSynchroniseL(aNewOnly);
+ }
+
+void CImImap4Session::DoSynchroniseL(TBool aNewOnly)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND Synchronise"))));
+ __ASSERT_DEBUG(iState==EImapStateSelected,gPanic(ESyncWhenNotSelected));
+ if(!(iState==EImapStateSelected))
+ {
+ User::LeaveIfError(KErrArgument);// Sync when not selected .
+ }
+ // clear flags that may have been be set by SELECT or NOOP to say
+ // that a sync is required
+ iMailboxReceivedExists=EFalse;
+ iMailboxReceivedExpunge=EFalse;
+ iMailboxReceivedFlags=EFalse;
+
+ // Some pre-bits that need doing - get the children & count them
+ SetEntryL(iMailboxId);
+
+ TMsvEmailEntry message=iEntry->Entry();
+ GetMessageChildrenL(iMailboxId,iSelection);
+ TInt noofchildren=iSelection->Count();
+
+ if (!aNewOnly && noofchildren)
+ {
+ // Delete any orphaned messages completely at this point
+ DBG((LogText(_L8("Looking for orphans in %d local messages"),noofchildren)));
+
+ TInt pos=0;
+ while(pos<noofchildren)
+ {
+ if((*iCachedEntryData)[pos].iOrphan)
+ {
+ DBG((LogText(_L8(" Deleting orphan %x"),(*iSelection)[pos])));
+
+ // Delete it
+ SetEntryL(iMailboxId);
+ iEntry->DeleteEntry((*iSelection)[pos]);
+ // Remove it from selection
+ iSelection->Delete(pos,1);
+ noofchildren--;
+ }
+ else
+ {
+ // Move on to next entry
+ pos++;
+ }
+ }
+ }
+
+ // First thing we have to do: check the UIDVALIDITY of the mirror and the
+ // remote folder match. If not, we have to orphan everything in the mirror
+ // and start again.
+ // We also do this if there are 0 messages in the remote mailbox (0 EXISTS)
+ // and there are messages locally
+ if (!message.ValidUID() || iUidValidity!=message.UID() || iMailboxSize==0)
+ {
+ // They don't match: do we have local children?
+#ifdef PRINTING
+ if (!iMailboxSize)
+ LogText(_L8("No remote messages"));
+ else
+ LogText(_L8("UIDVALIDITY changed: local %u, remote %u"),
+ message.UID(),iUidValidity);
+#endif
+
+ // If we were doing a new-only sync, change this to a full sync as the
+ // UIDVALIDITY shows major changes
+ aNewOnly=EFalse;
+
+ if (noofchildren)
+ {
+ // We've got local children: orphan them
+ DBG((LogText(_L8("Orphaning %d local messages"),noofchildren)));
+
+ for(TInt a=0;a<noofchildren;a++)
+ {
+ // ...we should be skipping locally generated messages
+ OrphanMessageL((*iSelection)[a]);
+ }
+
+ // Reget the number of children as this may have changed due to
+ // the orphaning process
+ GetMessageChildrenL(iMailboxId,iSelection);
+ noofchildren=iSelection->Count();
+ }
+
+ // Now, we match the remote's UIDVALIDITY: reset the pointer as it may
+ // well have been used by the orphaning process above.
+ SetEntryL(iMailboxId);
+ if (message.UID()!=iUidValidity || !message.ValidUID())
+ {
+ // Do the change if necessary
+ message.SetUID(iUidValidity);
+ message.SetValidUID(ETrue);
+ ChangeEntryBulkL(message);
+ }
+ }
+
+ // We've processed none of the remote messages yet
+ iMsgsDone=0;
+
+ // Any remote messages? If not, complete now as there's nothing else to do
+ if (iMailboxSize==0)
+ {
+ // This folder is now sync'ed
+ // No need to set seen flags as no messages in remote mailbox
+ SyncCompleteL();
+ DBG((LogText(_L8("CImap4Session::DoSynchroniseL(): iMailboxSize=0, folder now synched"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::DoSynchroniseL(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+ Complete(KErrNone);
+ return;
+ }
+
+ // Start the synchronise with sync'ing old messages: are there any
+ // messages in our mirror folder?
+ DBG((LogText(_L8("CImImap4Session::DoSynchroniseL(): Setting iState to EImapStateSynchroniseWait"))));
+ iState=EImapStateSynchroniseWait;
+ iSomeUnread=EFalse;
+ iHighestUid=0;
+
+ // Zero the "missing" message range limits.
+ iMissingUidLow=0;
+ iMissingUidHigh=0;
+
+ // Clear RX byte counter
+ iImapIO->RXbytes(ETrue);
+
+ // Any children?
+ iFolderIndex.Reset();
+ if (noofchildren>0)
+ {
+ // Children exist, we need to do an old-sync to check all the messages
+ // are still there.
+
+ // Build an index of UIDs/TMsvIds currently in the mirror folder, and
+ // sort this by UID: this is the order in which we expect the fetch to
+ // return UIDs - any missing have been deleted on the server. They may
+ // well not be in UID order in the index because locally-appended
+ // messages will not have been added to the index in UID order.
+ TRAPD(err,MakeSortedFolderIndexL(ETrue));
+ if (err!=KErrNone)
+ {
+ DBG((LogText(_L8("CImap4Session::DoSynchroniseL(): children exist, need to do old sync"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::DoSynchroniseL(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+ Complete(err);
+ return;
+ }
+
+ // Find the highest UID in the index
+ iHighestUid=iFolderIndex[noofchildren-1].iUid;
+ }
+
+ // Retrieve folder synchronisation limit.
+ if (iEntry->Entry().Parent()==iServiceId && iEntry->Entry().iDetails.CompareF(KIMAP_INBOX)==0)
+ {
+ DBG((LogText(_L8("Folder sync of inbox folder"))));
+
+ // Leave iSyncLimit at the maximum if a Search String is set
+ // If no Search String is set and this is the inbox, then use the inbox sync limit.
+ if(iServiceSettings->SearchString().Length() == 0)
+ {
+ iSyncLimit=iServiceSettings->InboxSynchronisationLimit();
+ }
+ }
+ else
+ {
+ // Otherwise use the folder sync limit.
+ // Leave iSyncLimit at the maximum if a Search String is set
+ DBG((LogText(_L8("Folder sync of non-inbox folder"))));
+
+ if(iServiceSettings->SearchString().Length() == 0)
+ {
+ iSyncLimit=iServiceSettings->MailboxSynchronisationLimit();
+ }
+ }
+
+ // Get the user defined UID SEARCH string if there is one
+ // Do a refined search if there's a string
+ if(iServiceSettings->SearchString().Length() != 0)
+ {
+ iSyncState=ESyncSearch;
+ iFolderPosition=0;
+ iSearchList->Reset();
+ NewTag();
+ // Refined search
+ _LIT8(KSearchString,"%d UID SEARCH 1:%d %S\r\n");
+ TPtrC8 ptr = iServiceSettings->SearchString();
+ iImapIO->SendL(iStatus,KSearchString,iTag,Min(iMailboxSize,KImapUidSearchSize),&ptr);
+ NewTagSent();
+ return;
+ }
+ else // if no search string we use the old behaviour
+ // Check the folder synchronisation limit.
+ if (iSyncLimit>KImImapSynchroniseNone)
+ {
+ DBG((LogText(_L8("Folder sync limited to %d messages"),iSyncLimit)));
+
+ // Limited folder synchronisation, perform a UID search.
+ iSyncState=ESyncSearch;
+ iFolderPosition=0;
+
+ // Reset the search list.
+ iSearchList->Reset();
+
+ // Perform a UID search on this folder.
+ NewTag();
+ iImapIO->SendL(iStatus,_L8("%d UID SEARCH 1:%d\r\n"),iTag,Min(iMailboxSize,KImapUidSearchSize));
+ NewTagSent();
+ return;
+ }
+ else if (iSyncLimit==KImImapSynchroniseNone)
+ {
+ DBG((LogText(_L8("No folder sync required"))));
+
+ // No synchronisation required.
+ // This folder is now sync'ed
+ SyncCompleteL();
+ // Back to selected state
+ iState=EImapStateSelected;
+ iSyncState=ENotSyncing;
+ DBG((LogText(_L8("CImap4Session::DoSynchroniseL(): iSyncLimit=KImImapSynchroniseNone, no sync required"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::DoSynchroniseL(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+ Complete(KErrNone);
+ return;
+ }
+ else if (iSyncLimit<=KImImapSynchroniseAll)
+ {
+ DBG((LogText(_L8("Full folder sync required"))));
+
+ // Full synchronisation required - fall through.
+ }
+
+ if (noofchildren>0)
+ {
+ if (!aNewOnly && iHighestUid>0)
+ {
+ // Do old sync
+ iSyncState=ESyncOld;
+ iFolderPosition=0;
+ NewTag();
+ // If a UID Search String is used it looks like this is FULL sync only
+ // so leave as is
+ iImapIO->SendL(iStatus,_L8("%d UID FETCH 1:%d (UID FLAGS)\r\n"),
+ iTag,iHighestUid);
+ NewTagSent();
+ return;
+ }
+ }
+
+ // Do new sync
+ SynchroniseNewL();
+ }
+
+void CImImap4Session::ColonSeparatorToSpace(TDes8& buf)
+ {
+ TInt colon;
+ while ((colon = buf.Locate(':')) != KErrNotFound)
+ buf.Replace(colon,1,_L8(" "));
+ buf.TrimRight();
+ }
+
+// Do second stage of sync with uid list, ie new synchronise.
+void CImImap4Session::SynchroniseNewL(const TUint32 aLowUid,const TUint32 aHighUid)
+ {
+ DBG((LogText(_L8("CImImap4Session::SynchroniseNewL()"))));
+ iSyncState=ESyncNew;
+ iCheckDiskSpaceCounter = 0;
+ iFolderPosition = 0;
+
+ // First, resize folder index to hold all messages in the folder,
+ // as opposed to the old sync list. This will preserve the old
+ // contents of the index, which is what we want as it's up-to-date
+ // and correct.
+ iFolderIndex.SetSizeL(iMailboxSize);
+
+ DBG((LogText(_L8("Synchronising new messages (UIDs %u to %u)"),aLowUid,aHighUid)));
+
+ // Create list of priority fields to request
+ TBuf8<256> priorityFields;
+ CDesC8ArrayFlat* array = new(ELeave) CDesC8ArrayFlat(4);
+ CleanupStack::PushL(array);
+ CImcvUtils::PriorityFieldsL(*array);
+ for (TInt i=0; i<array->Count(); i++)
+ {
+ priorityFields.Append((*array)[i]);
+ }
+ CleanupStack::PopAndDestroy(array);
+ ColonSeparatorToSpace(priorityFields);
+
+ // Send command
+ NewTag();
+
+ // If a UID search string has been specified, the we should create the UID FETCH
+ // string from the UID integer list.
+ if(iServiceSettings->SearchString().Length() != 0)
+ {
+ CreateUidStringL();
+ TPtrC8 ptr(iUidString->Des());
+ iImapIO->SendL(iStatus,KImapFetchSmallHeaderRangeRefined,iTag,&ptr, &priorityFields);
+ }
+ else
+ {
+ iImapIO->SendL(iStatus,KImapFetchSmallHeaderRange,iTag,aLowUid,aHighUid, &priorityFields);
+ }
+ NewTagSent();
+ }
+
+// Do second stage of sync, ie new synchronise.
+void CImImap4Session::SynchroniseNewL()
+ {
+ iSyncState=ESyncNew;
+ iCheckDiskSpaceCounter = 0;
+ iFolderPosition = 0;
+
+ // First, resize folder index to hold all messages in the folder,
+ // as opposed to the old sync list. This will preserve the old
+ // contents of the index, which is what we want as it's up-to-date
+ // and correct.
+ iFolderIndex.SetSizeL(iMailboxSize);
+
+ // fetch just the header of the new mails
+ FetchHeaderL(iHighestUid+1);
+ }
+
+// Build the fetch list
+void CImImap4Session::AddFetchItemL(TMsvId aPart, TImap4GetMailOptions aPartTypes, TBool& aHasTextParts)
+ {
+ DBG((LogText(_L8("AddFetchItemL(id %x, parts=%d)"),aPart,aPartTypes)));
+
+ // Is this part fetchable?
+ SetEntryL(aPart);
+
+ // if the part is complete, then this means everything below it is
+ // complete and therefore we don't need to fetch anything.
+ if ( iEntry->Entry().iType != KUidMsvFolderEntry && iEntry->Entry().Complete()
+ && !(((TMsvEmailEntry)iEntry->Entry()).PartialDownloaded()))
+ {
+ DBG((LogText(_L8("Skipping, already complete"))));
+
+ // If this is an attachment which has been marked complete because it has
+ // zero size, we still need to add it to the attachment manager.
+ if ((iEntry->Entry().iType == KUidMsvAttachmentEntry ||
+ iEntry->Entry().iType == KUidMsvEmailExternalBodyEntry) &&
+ (iEntry->Entry().iSize == 0))
+ {
+ DBG((LogText(_L8("Creating zero length attachment"))));
+ CreateAttachmentInfoL((TMsvEmailEntry&)iEntry->Entry());
+ }
+
+ return;
+ }
+
+ TBool addChildren = EFalse;
+ TBool addPart = EFalse;
+
+ TUid type = iEntry->Entry().iType;
+ if (type == KUidMsvFolderEntry || type == KUidMsvMessageEntry)
+ {
+ // don't fetch anything - just let it recurse
+ addChildren = ETrue;
+ }
+ else if (type == KUidMsvEmailTextEntry || type == KUidMsvEmailHtmlEntry)
+ {
+ aHasTextParts = ETrue;
+
+ if (aPartTypes == EGetImap4EmailBodyText ||
+ aPartTypes == EGetImap4EmailBodyTextAndAttachments ||
+ aPartTypes == EGetImap4EmailBodyAlternativeText)
+ {
+ addPart = ETrue;
+ }
+ }
+ else if (type == KUidMsvAttachmentEntry || type == KUidMsvEmailExternalBodyEntry)
+ {
+ if (aPartTypes == EGetImap4EmailBodyTextAndAttachments ||
+ aPartTypes == EGetImap4EmailAttachments)
+ {
+ addPart = ETrue;
+ }
+ else
+ {
+ SetEntryL(iEntry->Entry().Parent());
+ TImEmailFolderType folderType = static_cast<TMsvEmailEntry>((iEntry->Entry())).MessageFolderType();
+ SetEntryL(aPart);
+
+ if( folderType==EFolderTypeRelated )
+ {
+ // if asked for bodytext and it is an attachment then
+ // fetch it if attachment is in a folder of
+ // Multipart/Related as it is most likely part of an MHTML
+ // document
+ addPart = ETrue;
+ }
+ else if( ( folderType == EFolderTypeAlternative || folderType == EFolderTypeUnknown ) && aPartTypes == EGetImap4EmailBodyAlternativeText)
+ {
+ // if non-HTML text alternative parts are requested, the alternative
+ // folder is checked and get the mime content type for the part
+ CMsvStore* store = iEntry->ReadStoreL();
+ CleanupStack::PushL(store);
+ CImMimeHeader* mimeHeaders = CImMimeHeader::NewLC();
+ mimeHeaders->RestoreL(*store);
+
+ if( mimeHeaders->ContentType().CompareF(KMIME_TEXT)==0 )
+ {
+ // This is a alternative text part, and should be treated
+ // as a text part
+ addPart = ETrue;
+ }
+
+ CleanupStack::PopAndDestroy(2, store); // mimeHeaders, store
+ }
+
+ // Store needs to be closed before calling CreateAttachmentInfoL
+ if(!addPart)
+ {
+ CreateAttachmentInfoL((TMsvEmailEntry&)iEntry->Entry());
+ }
+ }
+ }
+ else
+ {
+ __ASSERT_DEBUG(0, gPanic(EUnknownMsvType));
+
+ // for anything else, if not debug mode then fetch anyway
+ addPart = ETrue;
+ }
+
+ if (addPart)
+ {
+ iFetchList->AppendL(aPart);
+
+ // Add this part's size to the size total
+ iProgress.iBytesToDo+=iEntry->Entry().iBioType;
+ }
+
+ if (addChildren)
+ {
+ // Check the children
+ CMsvEntrySelection *selection=new (ELeave) CMsvEntrySelection;
+ CleanupStack::PushL(selection);
+ GetChildrenL(*selection);
+
+ DBG((LogText(_L8("...ID %x has %d children"),iEntry->Entry().Id(),selection->Count())));
+
+ for(TInt a=0;a<selection->Count();a++)
+ {
+ // Process child
+ AddFetchItemL((*selection)[a],aPartTypes,aHasTextParts);
+ }
+ CleanupStack::PopAndDestroy();
+ }
+ }
+
+void CImImap4Session::AddFetchItemL(TMsvId aPart, TImImap4GetPartialMailInfo aGetPartialMailInfo, TBool& aHasTextParts)
+ {
+ DBG((LogText(_L8("AddFetchItemL(id %x, parts=%d)"),aPart,aGetPartialMailInfo.iPartialMailOptions)));
+
+ // Is this part fetchable?
+ SetEntryL(aPart);
+
+ //if the part is complete, then this means everything below it is
+ // complete and therefore we don't need to fetch anything.
+ if (iEntry->Entry().iType != KUidMsvFolderEntry && iEntry->Entry().Complete())
+ {
+ DBG((LogText(_L8("Skipping, already complete"))));
+ // If this is an attachment which has been marked complete because it has
+ // zero size, we still need to add it to the attachment manager.
+ if ((iEntry->Entry().iType == KUidMsvAttachmentEntry ||
+ iEntry->Entry().iType == KUidMsvEmailExternalBodyEntry) &&
+ (iEntry->Entry().iSize == 0))
+ {
+ DBG((LogText(_L8("Creating zero length attachment"))));
+ CreateAttachmentInfoL((TMsvEmailEntry&)iEntry->Entry());
+ }
+
+ return;
+ }
+
+ TBool addChildren = EFalse;
+ TBool addPart = EFalse;
+
+ TUid type = iEntry->Entry().iType;
+ if (type == KUidMsvFolderEntry || type == KUidMsvMessageEntry)
+ {
+ // don't fetch anything - just let it recurse
+ addChildren = ETrue;
+ }
+ else if (type == KUidMsvEmailTextEntry)
+ {
+ aHasTextParts = ETrue;
+
+ if(aGetPartialMailInfo.iPartialMailOptions == ENoSizeLimits)
+ addPart = ETrue;
+ else if (aGetPartialMailInfo.iPartialMailOptions == ECumulative)
+ {
+ if(iGetPartialMailInfo.iTotalSizeLimit > 0)
+ {
+ addPart = ETrue;
+ iBodyTextSize = iEntry->Entry().iBioType;
+ }
+ }
+ else if (aGetPartialMailInfo.iPartialMailOptions == EBodyTextOnly ||
+ aGetPartialMailInfo.iPartialMailOptions == EBodyTextAndAttachments ||
+ aGetPartialMailInfo.iPartialMailOptions == EBodyAlternativeText )
+ {
+ addPart = ETrue;
+ iBodyTextSize = iEntry->Entry().iBioType;
+ }
+ }
+ else if (type == KUidMsvEmailHtmlEntry)
+ {
+ aHasTextParts = ETrue;
+
+ iHtmlEntrySize = iEntry->Entry().iBioType;
+ if(aGetPartialMailInfo.iPartialMailOptions == ENoSizeLimits)
+ addPart = ETrue;
+ else if (aGetPartialMailInfo.iPartialMailOptions == ECumulative)
+ {
+ if((iGetPartialMailInfo.iTotalSizeLimit > 0 ) &&
+ ((iBodyTextSize + iEntry->Entry().iBioType) <= iGetPartialMailInfo.iTotalSizeLimit))
+ {
+ addPart = ETrue;
+ }
+ }
+ else if (aGetPartialMailInfo.iPartialMailOptions == EBodyTextOnly ||
+ aGetPartialMailInfo.iPartialMailOptions == EBodyTextAndAttachments ||
+ aGetPartialMailInfo.iPartialMailOptions == EBodyAlternativeText )
+
+ {
+ if(iBodyTextSize + iEntry->Entry().iBioType <=
+ Minimum(iGetPartialMailInfo.iBodyTextSizeLimit,iGetPartialMailInfo.iTotalSizeLimit))
+ {
+ addPart = ETrue;
+ }
+ }
+ // In case of html entry, store html entry id to check later,(when attaching partial footer
+ // message)if whole body text is downloaded and the html size is not to be downloaded
+ if(addPart)
+ iHtmlEntryPart = aPart;
+ }
+ else if (type == KUidMsvAttachmentEntry || type == KUidMsvEmailExternalBodyEntry)
+ {
+
+ if(aGetPartialMailInfo.iPartialMailOptions == ENoSizeLimits)
+ addPart = ETrue;
+ else if (aGetPartialMailInfo.iPartialMailOptions == ECumulative)
+ {
+ if(iGetPartialMailInfo.iTotalSizeLimit > 0 &&
+ ((iBodyTextSize + iSizeOfToBeFetchedAttachments + iEntry->Entry().iBioType) <= iGetPartialMailInfo.iTotalSizeLimit))
+ {
+ addPart = ETrue;
+ if((iBodyTextSize + iSizeOfToBeFetchedAttachments + iEntry->Entry().iBioType + iHtmlEntrySize)
+ >= iGetPartialMailInfo.iTotalSizeLimit)
+ {
+ RemoveHtmlPart(iHtmlEntryPart);
+ }
+ iSizeOfToBeFetchedAttachments+=iEntry->Entry().iBioType;
+ }
+ else
+ {
+ CreateAttachmentInfoL((TMsvEmailEntry&)iEntry->Entry());
+ // for Ecumulative option ,after the body part downloading, check if there is any
+ // attachment which can be downloaded , then check if the html part can be included.
+ if((iBodyTextSize + iSizeOfToBeFetchedAttachments + iHtmlEntrySize) >= iGetPartialMailInfo.iTotalSizeLimit)
+ {
+ RemoveHtmlPart(iHtmlEntryPart);
+ }
+ }
+ }
+ else if (aGetPartialMailInfo.iPartialMailOptions == EAttachmentsOnly ||
+ aGetPartialMailInfo.iPartialMailOptions == EBodyTextAndAttachments)
+
+ {
+ if(iEntry->Entry().iBioType <=
+ Minimum(iGetPartialMailInfo.iAttachmentSizeLimit,iGetPartialMailInfo.iTotalSizeLimit))
+ {
+ addPart = ETrue;
+ }
+ else
+ {
+ CreateAttachmentInfoL((TMsvEmailEntry&)iEntry->Entry());
+ }
+ }
+ else
+ {
+ SetEntryL(iEntry->Entry().Parent());
+ TImEmailFolderType folderType = static_cast<TMsvEmailEntry>((iEntry->Entry())).MessageFolderType();
+ SetEntryL(aPart);
+
+ if( folderType==EFolderTypeRelated )
+ {
+ // if asked for bodytext and it is an attachment then
+ // fetch it if attachment is in a folder of
+ // Multipart/Related as it is most likely part of an MHTML
+ // document
+ addPart = ETrue;
+ }
+ else if( folderType==EFolderTypeAlternative &&
+ aGetPartialMailInfo.iPartialMailOptions==EBodyAlternativeText &&
+ iEntry->Entry().iBioType <= Minimum(iGetPartialMailInfo.iAttachmentSizeLimit,
+ iGetPartialMailInfo.iTotalSizeLimit) )
+ {
+ // if non-HTML text alternative parts are requested, the alternative
+ // folder is checked and get the mime content type for the part
+ CMsvStore* store = iEntry->ReadStoreL();
+ CleanupStack::PushL(store);
+ CImMimeHeader* mimeHeaders = CImMimeHeader::NewLC();
+ mimeHeaders->RestoreL(*store);
+
+ if( mimeHeaders->ContentType().CompareF(KMIME_TEXT)==0 )
+ {
+ // This is a alternative text part, and should be treated
+ // as a text part
+ addPart = ETrue;
+ }
+
+ CleanupStack::PopAndDestroy(2, store); // mimeHeaders, store
+ }
+
+ if(!addPart)
+ {
+ CreateAttachmentInfoL((TMsvEmailEntry&)iEntry->Entry());
+ }
+ }
+ }
+ else
+ {
+ __ASSERT_DEBUG(0, gPanic(EUnknownMsvType));
+
+ // for anything else, if not debug mode then fetch anyway
+ addPart = ETrue;
+ }
+
+ if (addPart)
+ {
+ iFetchList->AppendL(aPart);
+ // Add this part's size to the size total
+ iProgress.iBytesToDo+=iEntry->Entry().iBioType;
+ }
+
+ if (addChildren)
+ {
+ // Check the children
+ CMsvEntrySelection *selection=new (ELeave) CMsvEntrySelection;
+ CleanupStack::PushL(selection);
+ GetChildrenL(*selection);
+
+ DBG((LogText(_L8("...ID %x has %d children"),iEntry->Entry().Id(),selection->Count())));
+
+ for(TInt a=0;a<selection->Count();a++)
+ {
+ // Process child
+ AddFetchItemL((*selection)[a],aGetPartialMailInfo,aHasTextParts);
+ }
+ CleanupStack::PopAndDestroy();
+ }
+ }
+
+void CImImap4Session::RemoveHtmlPart(TMsvId aPart)
+ {
+ // removes the html part from the download list only if it exists in the list
+ if(aPart)
+ {
+ TInt aIndex = 0;
+ TKeyArrayFix sortKey(0, ECmpTInt32);
+ iFetchList->Find(aPart,sortKey,aIndex);
+ iFetchList->Delete(aIndex,1);
+ iHtmlEntryPart=0;
+ }
+ }
+
+// Checks for the minimum size limit between message type size limit
+// (attachment size limit/body text sizelimit)
+TInt32 CImImap4Session::Minimum(TInt32 aThisPartTypeSizeLimit,TInt32 aTotalMailSizeLimit)
+ {
+ if(aTotalMailSizeLimit > 0)
+ {
+ if(aThisPartTypeSizeLimit > aTotalMailSizeLimit)
+ return aTotalMailSizeLimit;
+ else
+ return aThisPartTypeSizeLimit;
+ }
+ else
+ return aThisPartTypeSizeLimit;
+ }
+
+// Issue command to fetch an item in the fetch list
+void CImImap4Session::FetchAnItemL(const TMsvId aPart)
+ {
+ DBG((LogText(_L8("FetchAnItemL(%x)"),aPart)));
+
+ // set the iFoundUid member variable to false to show that a UID has not been
+ // found in the fetch response yet
+ iFoundUid = EFalse;
+
+ // Get part ID and read the MIME header so we can work out encoding style
+ iMessageId=aPart;
+ SetEntryL(iMessageId);
+ CMsvStore *store=iEntry->ReadStoreL();
+ CleanupStack::PushL(store);
+
+ iIsDiskSpaceChecked = EFalse;
+ // Get MIME header
+ if (!iAttachmentMimeInfo)
+ iAttachmentMimeInfo=CImMimeHeader::NewL();
+ iAttachmentMimeInfo->RestoreL(*store);
+
+
+ // We don't reset the stats here, as they're stats for a single
+ // fetch operation, which may include multiple parts of the same
+ // message. Stats initialisation is done in the FetchBody()
+ // function.
+
+ // Find the UID we need to fetch
+ TMsvEmailEntry entry = iEntry->Entry();
+ iMessageFetching=entry.UID();
+
+ // check there is enough disk space for this part (plus slop)
+ // iBioType contains remote size which will never be less than the
+ // local size so is a safe value to use
+ if(!iFetchPartialMail)
+ {
+ CheckForDiskSpaceL(entry.iBioType);
+ }
+
+ // Save encoding type
+ iEncodingType=iAttachmentMimeInfo->ContentTransferEncoding();
+ iB64Decoder.Initialise();
+
+ // Clear QP buffer
+ if (iPartialLine && iPartialLine->Length())
+ iPartialLine->Des().Zero();
+
+ // Is this a text fetch? (if so, save as a richtext in the database, as
+ // opposed to a separate file). Default to being an attachment.
+
+ // SJM: This code used to check for an attachment filename and
+ // force FetchIsTest False in that case. This is unnecessary as
+ // this check is already done in BuildTreeOne and reflected in the
+ // entry.iType.
+
+ iFetchIsText = EFalse;
+ if (entry.iType == KUidMsvEmailTextEntry)
+ {
+ iFetchIsText = ETrue;
+ // New message body
+ if (iStore8BitData)
+ {
+ delete iBodyBuf;
+ iBodyBuf = NULL;
+ iBodyBuf = CBufSeg::NewL(KBodyTextChunkSizeBytes);
+ delete iBodyText;
+ iBodyText = NULL;
+ iBodyText = CMsvBodyText::NewL();
+ }
+ else
+ {
+ delete iMessageBody;
+ iMessageBody = NULL;
+ iMessageBody=CRichText::NewL(iParaLayer, iCharLayer);
+ }
+ }
+
+ // Waiting for size
+ iSizeWait=ETrue;
+
+ // Size of item (mainly for CC:Mail bug workaround) */
+ iSizeOfThisPart=entry.iBioType;
+
+ // if going into richtext only then setup charset conversion
+ iPreparedToConvert=EFalse;
+ TInt fetchSizeBytes = static_cast<TInt>(iServiceSettings->FetchSize());
+ // If this was a partially fetched message then there is some already fetched
+ // message content in the message store
+ TInt32 fetchSize = fetchSizeBytes;
+
+ if (iFetchIsText)
+ {
+
+ TUint charsetId = iAttachmentMimeInfo->MimeCharset();
+
+ if (iStore8BitData)
+ {
+ iBodyText->SetDefaultCharacterSet(iCharConv->SystemDefaultCharset());
+ if (charsetId == KUidMsvCharsetNone)
+ iBodyText->SetCharacterSet(0);
+ else
+ iBodyText->SetCharacterSet(charsetId);
+ }
+ else
+ {
+ if (charsetId == KUidMsvCharsetNone)
+ charsetId = iCharConv->SystemDefaultCharset();
+ }
+
+ iAttachmentMimeInfo->SetMimeCharset(charsetId);
+ if (!iStore8BitData)
+ {
+ if (charsetId != KUidMsvCharsetNone)
+ iPreparedToConvert = iCharConv->PrepareToConvertToFromOurCharsetL(charsetId);
+ }
+ }
+
+ // ensure nothing left over
+ iLeftOver.SetLength(0);
+
+ DBG((LogText(_L8("Starting fetch for body part (MsvId=%x, iSizeOfThisPart=%d, iFetchIsText=%d, convert=%d)"),
+ iMessageId,iSizeOfThisPart,iFetchIsText, iPreparedToConvert)));
+
+ // Issue fetch command
+ NewTag();
+ TPtrC8 path=iAttachmentMimeInfo->RelativePath();
+
+ if(iFetchPartialMail)
+ fetchSize = GetFetchSizeL(iSizeOfThisPart,0); // As this is the first time 0 size downloaded already
+
+ if(fetchSize > fetchSizeBytes)
+ fetchSize = fetchSizeBytes;
+
+ //Some servers dont support MIME.And it contains single body type.
+ //Using KImapFetchBodyPeek/KImapFetchBody to fetch the body of mails.
+ if(iProgress.iPartsToDo == 1)
+ {
+ if (iServiceSettings->UpdatingSeenFlags())
+ {
+ iImapIO->SendL(iStatus,KImapFetchBodyPeek, iTag,iMessageFetching,&path,0,fetchSize);
+ }
+ else
+ {
+ iImapIO->SendL(iStatus,KImapFetchBody, iTag,iMessageFetching,&path,0,fetchSize);
+ }
+ }
+ else
+ {
+ if (iServiceSettings->UpdatingSeenFlags())
+ {
+ iImapIO->SendL(iStatus,KImapFetchMimeBodyPeek, iTag,iMessageFetching,&path,fetchSize,&path);
+ }
+ else
+ {
+ iImapIO->SendL(iStatus,KImapFetchMimeBody, iTag,iMessageFetching,&path,fetchSize,&path);
+ }
+ }
+ NewTagSent();
+ CleanupStack::PopAndDestroy();
+ }
+
+void CImImap4Session::CheckForDiskSpaceL(TInt aSizeToBeDownloaded)
+ {
+ TInt needSpace = 0;
+ TVolumeInfo volumeInfo;
+ User::LeaveIfError(iFs.Volume(volumeInfo, iCurrentDrive));
+
+ needSpace = KMinimumDiskSpaceForSync + aSizeToBeDownloaded;
+ if (volumeInfo.iFree < needSpace)
+ User::Leave(KErrDiskFull);
+ }
+
+// Fetch body for partial download
+void CImImap4Session::FetchBody(TRequestStatus& aRequestStatus, const TMsvId aPart,
+ TImImap4GetPartialMailInfo aGetPartialMailInfo)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND FetchBody(%x)"),aPart)));
+ TInt err=KErrNone;
+
+ CheckForPartialPopulate(aGetPartialMailInfo);
+ if (!Connected())
+ {
+ Queue(aRequestStatus);
+ err=KErrDisconnected;
+ }
+ else
+ {
+ TRAP(err,FetchBodyL(aRequestStatus, aPart));
+ }
+ if (err!=KErrNone)
+ {
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::FetchBody(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+
+ Complete(err);
+ }
+ }
+
+// Checks if the partial mail download parameters are set to default
+// and the full download mail option is set, then this is a request for full download.
+void CImImap4Session::CheckForPartialPopulate(TImImap4GetPartialMailInfo aGetPartialMailInfo)
+ {
+ if(aGetPartialMailInfo.iPartialMailOptions == ENoSizeLimits &&
+ aGetPartialMailInfo.iTotalSizeLimit == KMaxTInt &&
+ aGetPartialMailInfo.iBodyTextSizeLimit == KMaxTInt &&
+ aGetPartialMailInfo.iAttachmentSizeLimit == KMaxTInt &&
+ (aGetPartialMailInfo.iGetMailBodyParts == EGetImap4EmailHeaders ||
+ aGetPartialMailInfo.iGetMailBodyParts == EGetImap4EmailBodyText ||
+ aGetPartialMailInfo.iGetMailBodyParts == EGetImap4EmailBodyTextAndAttachments ||
+ aGetPartialMailInfo.iGetMailBodyParts == EGetImap4EmailAttachments ||
+ aGetPartialMailInfo.iGetMailBodyParts == EGetImap4EmailBodyAlternativeText))
+ {
+ DBG((LogText(_L8("Populate option = %d)"),aGetPartialMailInfo.iGetMailBodyParts)));
+ iFetchPartialMail = EFalse;
+ iGetOptions = aGetPartialMailInfo.iGetMailBodyParts;
+ }
+ else
+ {
+ DBG((LogText(_L8("Populate option = %d)"),aGetPartialMailInfo.iPartialMailOptions)));
+ iFetchPartialMail = ETrue;
+ iGetPartialMailInfo = aGetPartialMailInfo;
+ }
+ }
+
+void CImImap4Session::FetchBodyL(TRequestStatus& aRequestStatus, const TMsvId aPart)
+ {
+ __ASSERT_DEBUG(iState==EImapStateSelected,gPanic(EFetchWhenNotSelected));
+ if(!(iState==EImapStateSelected))
+ {
+ User::LeaveIfError(KErrArgument);// Fetch when not selected .
+ }
+ iGetPart = aPart;
+
+ Queue(aRequestStatus);
+
+ // if we only want headers then there is nothing to do as they
+ // have already been fetched on synchronisation. We complete here
+ // and then the Compound object will copy the structure across
+ if(!iFetchPartialMail)
+ {
+ if (iGetOptions == EGetImap4EmailHeaders)
+ {
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+ DBG((LogText(_L8("CImap4Session::FetchBodyL(): calling Complete()"))));
+ DBG((LogText(_L8("-----------------------------------------------------------"))));
+
+ Complete(KErrNone);
+ return;
+ }
+ }
+ // Get some basic info on what we're fetching
+ SetEntryL(aPart);
+
+ // First, check that we're in the right folder: this involves working up from the
+ // base part looking for a folder which matches the current iMailboxId. If we get
+ // to the service without finding the current folder, then we're not in the
+ // right place...
+ TBool messageFound = EFalse;
+ while((iEntry->Entry().Id()!=iMailboxId) && (!messageFound))
+ {
+ // Reached the service?
+ if (iEntry->Entry().iType==KUidMsvServiceEntry)
+ {
+ // Didn't find the folder - we must have the wrong one selected
+ Complete(KErrImapWrongFolder);
+
+ DBG((LogText(_L8("In the wrong folder!"))));
+
+ return;
+ }
+
+ // Reached the message ?
+ if (iEntry->Entry().iType==KUidMsvMessageEntry)
+ {
+ iMessageUid = ((TMsvEmailEntry&)iEntry->Entry()).UID();
+ iSyncState = EGettingStructure;
+ iState = EImapStateFetchWait;
+ if (!(iEntry->Entry().Owner()))
+ {
+ // If there are no child entries then we need to fetch the envelope and some headers (again.)
+ // We can generate the message structure and header stores from the envelope and headers after they have been fetched.
+ if (ImapIdleSupported()==EFalse)
+ {
+ CancelDummy();
+ }
+ // We don't know the size of the body part yet as there is no structure.
+ // The envelope will be fetched shortly so clear the progress information for now.
+ // Set the iBytesToDo to 1 rather than 0 to avoid any possible division by 0 errors
+ iProgress.iBytesToDo=1;
+ iProgress.iBytesDone=0;
+ FetchLargeHeaderL(iMessageUid, EFalse);
+ }
+ else
+ {
+ // If the structure is already present then do the fetch
+ if (ImapIdleSupported()==EFalse)
+ {
+ CancelDummy();
+ }
+ DoFetchL();
+ messageFound = ETrue;
+ }
+ }
+
+ // Up a level
+ SetEntryL(iEntry->Entry().Parent());
+ }
+ }
+
+// Set/reset local subscribed flag
+void CImImap4Session::LocalSubscribeL(const TMsvId aTarget, const TBool aSubscribe)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND LocalSubscribe(%d,%d)"),aTarget,aSubscribe)));
+
+ // Set/reset subscribed flag
+ SetEntryL(aTarget);
+ TMsvEmailEntry message=iEntry->Entry();
+
+ // Change only if necessary
+ if (message.Subscribed()!=aSubscribe)
+ {
+ message.SetSubscribed(aSubscribe);
+ ChangeEntryL(message);
+ }
+ }
+
+// Set/reset remote subscribed flag
+void CImImap4Session::RemoteSubscribeL(TRequestStatus& aRequestStatus, const TMsvId aTarget, const TBool aSubscribe)
+ {
+ LOG_COMMANDS((LogText(_L8("COMMAND RemoteSubscribe(%d,%d)"),aTarget,aSubscribe)));
+ __ASSERT_DEBUG(iState==EImapStateNoSelect || iState==EImapStateSelected,gPanic(ESubscribeWhenNotReady));
+ if(!(iState==EImapStateNoSelect || iState==EImapStateSelected))
+ {
+ User::LeaveIfError(KErrInUse);// Subscribe when not ready.
+ }
+ Queue(aRequestStatus);
+
+ // Save ID for updating subscription flag when the remote one completes
+ // successfully.
+ iCommandIds[0]=aTarget;
+ iCommandFlags[0]=aSubscribe;
+
+ // Get path to delete
+ HBufC8* path=NULL; // To stop .AER warnings...
+ TRAPD(err,path=MakePathL(aTarget,ETrue));
+ if (err!=KErrNone)
+ {
+ Complete(err);
+ return;
+ }
+ CleanupStack::PushL(path);
+ DoQuoteL(path);
+
+ // Set state
+ iSavedState=iState;
+ iState=EImapStateSubscribeWait;
+
+ // Send command
+ NewTag();
+ TPtrC8 pathdes(path->Des());
+ if(aSubscribe)
+ iImapIO->SendL(iStatus,_L8("%d %S\"%S\"\r\n"),iTag,&KIMAPC_SUBSCRIBE,&pathdes);
+ else
+ iImapIO->SendL(iStatus,_L8("%d %S\"%S\"\r\n"),iTag,&KIMAPC_UNSUBSCRIBE,&pathdes);
+ NewTagSent();
+
+ // Destroy buffer
+ CleanupStack::PopAndDestroy();
+ }
+
+// Reset sync/fetch stats
+void CImImap4Session::ResetStats()
+ {
+ // Reset all the stats
+ iHeadersFetched=0;
+ iOrphanedMessages=0;
+ iRemoteMessagesDeleteTagged=0;
+ iMsgsDone=0;
+
+ iProgress.iState = TImap4GenericProgress::EIdle;
+ iProgress.iImap4SubStateProgress = TImap4GenericProgress::EIdle;
+ iProgress.iMsgsToDo = iProgress.iMsgsDone = 0;
+ iProgress.iPartsToDo = iProgress.iPartsDone = 0;
+ iProgress.iBytesToDo = iProgress.iBytesDone = 0;
+ iProgress.iErrorCode = KErrNone;
+ iProgress.iReturnedMsvId = 0;
+ iProgress.iTotalSize = 0;
+ }
+
+// Return progress
+
+// Msgs ToDo/Done are only setup when doing a Synchronise command, ie
+// in state EImapStateSynchroniseWait.
+
+// Parts/Bytes ToDo/Done are set up and used in a FetchBody command,
+// ie in state EImapStateFetchWait.
+
+// BytesToDo/Done are used in Append command.
+
+void CImImap4Session::IncSyncStats(TImap4SyncProgress& aSync)
+ {
+ // do the synchronising stats
+ aSync.iHeadersFetched += iHeadersFetched;
+ aSync.iOrphanedMessages += iOrphanedMessages;
+ aSync.iRemoteMessagesDeleteTagged += iRemoteMessagesDeleteTagged;
+
+ if(iServiceSettings->SearchString().Length() != 0)
+ {
+ aSync.iMsgsToDo=iMailboxSize;
+ }
+ else
+ {
+ aSync.iMsgsToDo=(iSyncLimit<=0)?iMailboxSize:Min(iMailboxSize,iSyncLimit);
+ }
+ aSync.iMsgsDone = Min(iMsgsDone,aSync.iMsgsToDo);
+ }
+
+TImap4GenericProgress CImImap4Session::Progress()
+ {
+ // update the state with what we're doing
+ if (iState==EImapStateDisconnected)
+ {
+ iProgress.iState=TImap4GenericProgress::EDisconnected;
+ }
+ else if (iState<EImapStateNoSelect)
+ {
+ iProgress.iState = TImap4GenericProgress::EConnecting;
+ // If we're in the connecting state, get the stage and IAP values
+ // and store them in iMsgsDone and iMsgsToDo to be accessed via
+ // TImap4GenericProgress::ConnectionState() and TImap4GenericProgress::ConnectionIAP()
+ iProgress.iMsgsDone = iImapIO->GetConnectionStage();
+ TUint32 iap;
+ TInt err = iImapIO->GetIAPValue(iap);
+ if (err == KErrNone)
+ {
+ iProgress.iMsgsToDo = iap;
+ }
+ else
+ {
+ iProgress.iMsgsToDo = err;
+ }
+ }
+ else if (iState==EImapStateNoSelect || iState==EImapStateSelected || iState==EImapStateIdling)
+ {
+ iProgress.iState=TImap4GenericProgress::EIdle;
+ }
+ else
+ {
+ switch(iState)
+ {
+ case EImapStateFetchWait:
+ iProgress.iState=TImap4GenericProgress::EFetching;
+ break;
+
+ case EImapStateAppendSizeWait:
+ case EImapStateAppendPromptWait:
+ case EImapStateAppendWait:
+ case EImapStateAppendResultWait:
+ iProgress.iState=TImap4GenericProgress::EAppending;
+ break;
+
+ case EImapStateSynchroniseWait:
+ iProgress.iState=TImap4GenericProgress::ESyncing;
+ break;
+
+ case EImapStateLogoutWait:
+ iProgress.iState=TImap4GenericProgress::EDisconnecting;
+ break;
+
+ case EImapStateDeleteWait:
+ case EImapStateDeleteAllWait:
+ case EImapStateDeleteFolderWait:
+ case EImapStateDeleteMarkWait:
+ case EImapStateExpungeWait:
+ case EImapStateExpungeAllWait:
+ iProgress.iState = TImap4GenericProgress::EDeleting;
+ break;
+
+ case EImapStateSelectWait:
+ iProgress.iState = TImap4GenericProgress::ESelecting;
+ break;
+
+ default:
+ // Just 'busy' otherwise
+ iProgress.iState=TImap4GenericProgress::EBusy;
+ break;
+ }
+ }
+
+ return iProgress;
+ }
+
+// Set entry pointer
+void CImImap4Session::SetEntry(CMsvServerEntry *aEntry)
+ {
+ // Take note of this CMsvServerEntry
+ iEntry=aEntry;
+
+ // Park entry
+ iEntry->SetEntry(NULL);
+ }
+
+// Park entries
+void CImImap4Session::Park()
+ {
+ // Park normal entry
+ iEntry->SetEntry(NULL);
+
+ // Park moveentry if it exists
+ if (iMoveEntry) iMoveEntry->SetEntry(NULL);
+ }
+
+TInt CImImap4Session::CommandFailure() const
+ {
+ return iCommandFailure;
+ }
+
+void CImImap4Session::FetchHeaderL(TUint aUid)
+ {
+ iFolderIndex.SetSizeL(iMailboxSize);
+
+ // Create list of priority fields to request
+ TBuf8<256> priorityFields;
+ CDesC8ArrayFlat* array = new(ELeave) CDesC8ArrayFlat(4);
+ CleanupStack::PushL(array);
+ CImcvUtils::PriorityFieldsL(*array);
+ for (TInt i=0; i<array->Count(); i++)
+ {
+ priorityFields.Append((*array)[i]);
+ }
+ CleanupStack::PopAndDestroy(array);
+ ColonSeparatorToSpace(priorityFields);
+
+ NewTag();
+
+ iImapIO->SendL(iStatus, KImapFetchSmallHeaderToEnd,iTag,aUid, &priorityFields);
+ NewTagSent();
+ }
+
+void CImImap4Session::FetchLargeHeaderL(TUint aUid, TBool aRange)
+ {
+ // First, resize folder index to hold all messages in the folder,
+ // as opposed to the old sync list. This will preserve the old
+ // contents of the index, which is what we want as it's up-to-date
+ // and correct.
+ iFolderIndex.SetSizeL(iMailboxSize);
+
+ // build list of header fields we want note that
+ // ReceiptFieldStrings returns strings colon terminated which we
+ // convert to spaces for the fetch
+ TBuf8<256> buf;
+
+ CDesC8ArrayFlat* array = new(ELeave) CDesC8ArrayFlat(4);
+ CleanupStack::PushL(array);
+
+ CImcvUtils::ReceiptFieldsL(*array);
+ TInt i;
+ for (i=0; i<array->Count(); i++)
+ buf.Append((*array)[i]);
+
+ CImcvUtils::PriorityFieldsL(*array);
+ for (i=0; i<array->Count(); i++)
+ buf.Append((*array)[i]);
+
+ CleanupStack::PopAndDestroy(); // array
+
+ ColonSeparatorToSpace(buf);
+
+ // Send command
+ NewTag();
+ if (!aRange)
+ {
+ // We only want one envelope
+ iImapIO->SendL(iStatus, KImapFetchLargeHeader, iTag, aUid, &buf);
+ }
+ else
+ {
+ // If a UID search string has been specified, then we should create the UID FETCH
+ // string from the UID integer list.
+ if(iServiceSettings->SearchString().Length() !=0)
+ {
+ CreateUidStringL();
+ TPtrC8 ptr(iUidString->Des());
+ iImapIO->SendL(iStatus, KImapFetchLargeHeaderRangeRefined,iTag,&ptr,&buf);
+ }
+ else
+ {
+ iImapIO->SendL(iStatus, KImapFetchLargeHeaderRange,iTag,aUid, &buf);
+ }
+ }
+ NewTagSent();
+ }
+
+void CImImap4Session::DoFetchL()
+ {
+ DBG((LogText(_L8("CImap4Session::DoFetchL(): running..."))));
+
+ User::LeaveIfError(iEntry->SetEntry(iGetPart));
+ TUid type=iEntry->Entry().iType;
+
+ // if we are not asking for a Message type then override the get
+ // options to ensure that this is fetched
+ if(!iFetchPartialMail)
+ {
+ if (type != KUidMsvMessageEntry)
+ iGetOptions = EGetImap4EmailBodyTextAndAttachments;
+ }
+
+ User::LeaveIfError(iEntry->SetEntry(iServiceId));
+
+ // What have we been asked to fetch? Build a list of parts to fetch: if the
+ // part requested has any children, that is.
+ // Reset stats
+ iProgress.iBytesToDo=0;
+ iProgress.iBytesDone=0;
+ iFetchList->Reset();
+ iHtmlEntryPart = 0;
+ iBodyTextSize = 0;
+ iHtmlEntrySize = 0;
+ iBodyPartRemainingSize = 0;
+ iFooterString = NULL;
+ iSizeOfToBeFetchedAttachments=0;
+ TBool hasTextParts = EFalse;
+ if(iFetchPartialMail)
+ {
+ DBG((LogText(_L8("Using partial mail options"))));
+ AddFetchItemL(iGetPart,iGetPartialMailInfo,hasTextParts);
+ DBG((LogText(_L8("Found %d parts to fetch (options=%d)"),iFetchList->Count(),iGetPartialMailInfo.iPartialMailOptions)));
+ }
+ else
+ {
+ AddFetchItemL(iGetPart,iGetOptions,hasTextParts);
+ DBG((LogText(_L8("Found %d parts to fetch (options=%d)"),iFetchList->Count(),iGetOptions)));
+ }
+
+ if( !hasTextParts && type == KUidMsvMessageEntry )
+ {
+ // There are no text parts to this message - need to set body text
+ // complete flag to true otherwise UI may allow such a message to
+ // repeatedly be 'fetched' even though there is no text to fetch!
+ //
+ // So, set body text complete and message complete flags on the entry
+ // specified by iGetPart.
+ DBG((LogText(_L8("Message %d has no text parts - setting complete flag and body text complete flag to ETrue"),iGetPart)));
+
+ User::LeaveIfError(iEntry->SetEntry(iGetPart));
+ TMsvEmailEntry message = iEntry->Entry();
+
+ message.SetBodyTextComplete(ETrue);
+
+ ChangeEntryL(message);
+
+ // NOTE - not sure if necessary, but changing back to service entry to
+ // ensure consistent behaviour.
+ User::LeaveIfError(iEntry->SetEntry(iServiceId));
+ }
+
+ // Any parts at all?
+ if (iFetchList->Count() == 0 || iState == EImapStateFetchCancelWait)
+ {
+ // No, complete the fetch.
+ if( iState != EImapStateFetchCancelWait )
+ {
+ iState=EImapStateSelectWait;
+ iSyncState=ENotSyncing;
+ }
+
+ if( iCommandsOutstanding )
+ {
+ // Waiting for tagged response for fetch command that got the
+ // email structure
+ GetReply(EFalse);
+ }
+ else
+ {
+ // Message structure already known - therefore not waiting for the
+ // tagged response, complete immediately
+ CommandCompleteL(KErrNone);
+ }
+ return;
+ }
+
+ // Part count for stats
+ if (iFetchList->Count() > 0)
+ {
+ iProgress.iPartsToDo=iFetchList->Count();
+ iProgress.iPartsDone=0;
+
+ // Do the fetch
+ iState=EImapStateFetchWait;
+ iSyncState=EFetching;
+
+ DBG((LogText(_L8("Starting body fetch of %d parts (%d bytes)"),
+ iProgress.iPartsToDo,iProgress.iBytesToDo)));
+
+ // Make the command to send to the server
+ FetchAnItemL((*iFetchList)[0]);
+ iFetchList->Delete(0,1);
+ }
+
+ }
+
+TInt CImImap4Session::CalculateDownloadSizeL(const CMsvEntrySelection& aSelection)
+ {
+ TInt totalSize = 0;
+
+ // Do a quick tally on the size of messages which are to be copied / moved.
+ TInt count=aSelection.Count();
+ while (count--)
+ {
+ SetEntryL(aSelection.At(count));
+ // Only add the size up if the message is not complete.
+ if(!iEntry->Entry().Complete())
+ {
+ totalSize += iEntry->Entry().iSize;
+ }
+ }
+ return totalSize;
+ }
+
+void CImImap4Session::SetSynchronisationSelectionL(CMsvEntrySelection &aSelection)
+ {
+ // Used by the server mtm to prevent any messages selected for retrieval
+ // from being deleted during a synchronisation with a synchronisation limit set.
+ delete iSynchronisationSelection;
+ iSynchronisationSelection = 0;
+ iSynchronisationSelection = aSelection.CopyL();
+ }
+
+void CImImap4Session::SetInbox(TMsvId aInbox)
+ {
+ iInbox=aInbox;
+ }
+
+TMsvId CImImap4Session::GetInbox()
+ {
+ return iInbox;
+ }
+
+// Set or clear the \Seen flags on the server (aSettingsFlag = ETrue -> Sets the flag)
+// Returns False if no messages need to be processed
+TBool CImImap4Session::ProcessSeenFlagsL(TSeenFlagUpdateMode aUpdateMode)
+ {
+ CArrayFixFlat<TMsvId>* pendingList;
+ TBool settingFlag = (aUpdateMode == ESetSeenFlag);
+
+ // Point pendingList to the correct list
+ pendingList = (settingFlag ? iSetSeenList: iClearSeenList);
+
+ // Exit if nothing to process
+ if (!pendingList->Count())
+ {
+ return EFalse;
+ }
+
+ #ifdef PRINTING
+ _LIT8(KCommandProcessFlags, "COMMAND ProcessSeenFlags(%d)");
+ LOG_COMMANDS((LogText(KCommandProcessFlags, aUpdateMode)));
+ #endif
+
+ _LIT8(KStoreFlagsSetCommand, "%d UID STORE %S +FLAGS (\\Seen)\r\n");
+ _LIT8(KStoreFlagsClearCommand, "%d UID STORE %S -FLAGS (\\Seen)\r\n");
+ const TInt KMaxUIDsToProcess = 50;
+ const TInt KMaxCharsPerUID = 12;
+
+ // Ensure that the buffer passed to CImapIO does not exceed 1024
+ __ASSERT_DEBUG(KMaxUIDsToProcess * KMaxCharsPerUID < 1024 - (KStoreFlagsSetCommand().Length() + 10), gPanic(KMaxBufferLengthExceeded));
+
+ TInt pos = 0;
+ TInt stored = 0;
+ TInt run = 0;
+ TInt UID;
+ TInt lastUID = -1;
+ TInt listCount = pendingList->Count();
+ TBool haveSentCommand = EFalse;
+ TMsvEmailEntry message;
+ HBufC8* command=HBufC8::NewLC(KMaxUIDsToProcess * KMaxCharsPerUID);
+ TPtr8 commandDes = command->Des();
+
+ // Build up a list of UID's who's \Seen flag needs changing.
+ // To save bandwidth, group contiguous blocks of UID's together,
+ // e.g. 1:6,8:12,14 instead of 1,2,3,4,5,6,8,9,10,12,14
+ while(pos < listCount && stored < KMaxUIDsToProcess)
+ {
+ SetEntryL(pendingList->At(pos));
+ message = iEntry->Entry();
+ UID = static_cast<TInt>(message.UID());
+
+ // Should never have the state when the list contains flags that match the servers flag
+ __ASSERT_DEBUG(FIXBOOL(message.Unread()) != settingFlag, gPanic(KSettingSeenFlagToExistingState));
+
+ // Set the Seen flag for the message
+ message.SetSeenIMAP4Flag(settingFlag);
+ ChangeEntryL(message);
+ // If not first time through and the UID follows the previous one
+ if (pos !=0 && UID == lastUID+1)
+ {
+ // We are in a run
+ run++;
+ }
+ else
+ {
+ // If we were in a run
+ if (run)
+ {
+ // Append a ':' followed by the UID at the end of the run
+ commandDes.Append(TChar(':'));
+ commandDes.AppendNum(lastUID);
+ stored++;
+ // Reset the run count
+ run = 0;
+ }
+ // Append ',' only if not the first time through
+ if (pos!=0)
+ {
+ commandDes.Append(TChar(','));
+ }
+ // Append the current UID
+ commandDes.AppendNum(UID);
+ stored++;
+ }
+ // Next message
+ pos++;
+ lastUID = UID;
+ } // end while
+
+ // If we are at the end of the list but still in a run, add the last uid to the command buffer
+ if (run)
+ {
+ commandDes.Append(TChar(':'));
+ commandDes.AppendNum(lastUID);
+ }
+
+ // Anything stored?
+ if (stored)
+ {
+ // Get a new tag
+ NewTag();
+
+ if (settingFlag)
+ iImapIO->SendL(iStatus,KStoreFlagsSetCommand, iTag, command);
+ else
+ iImapIO->SendL(iStatus,KStoreFlagsClearCommand, iTag, command);
+
+ // Send the command
+ NewTagSent();
+ // Remove the processed flags from the list (pos = num_to_remove)
+ pendingList->Delete(0, pos);
+ haveSentCommand = ETrue;
+ }
+ else
+ {
+ // Should never have the state when the list contained flags but we never sent the 'STORE FLAGS' command to the server
+ __ASSERT_DEBUG(FIXBOOL(message.Unread()) != settingFlag, gPanic(KErrorBuildingStoreFlagsCommand));
+ }
+
+ // Get rid of command buffer
+ CleanupStack::PopAndDestroy(command);
+ return haveSentCommand;
+ }
+// Creates an IMAP4 defined optimised string of UIDs from the integer list of UIDs
+// retrieved by the UID SEARCH command.
+// Example: If the input array is 123 125 126 127 300 302 303 304 308
+// The output string will be "123,125:127,300,302,303:304,308
+// For the time being at least, keep the string as a class instance variable for possible re-use
+void CImImap4Session::CreateUidStringL()
+ {
+ iUidString->Des().Zero();
+ TPtr8 ptr(iUidString->Des());
+ TUint32* current = &iSearchList->At(0); // Loop iterator
+ TUint32* start(NULL); // Keep track of the start of a UID sequence
+ TInt count = iSearchList->Count(); // Quick access
+ while(current < &iSearchList->At(0) + count) // Loop till the end of the integer list
+ {
+ // Check for room to add 2 integers plus 2 separators.
+ // If not enough room then reallocate
+ if((ptr.MaxLength() - ptr.Length()) < (KMaxUint32Chars)+2)
+ {
+ iUidString = iUidString->ReAllocL(ptr.MaxLength()*2); // Make sure this time
+ ptr.Set(iUidString->Des());
+ }
+ start = current; // Mark the start of a possible sequence
+ if(current != &iSearchList->At(0)) // If it's the first time through the loop don't append separator
+ {
+ ptr.Append(',');
+ }
+ ptr.AppendNum(*start); // First integer always written
+ // Loop until end of sequence or end of list.
+ // will loop through 125 126 127
+ while(current < &iSearchList->At(count-1) && *current == (*(current+1))-1)
+ {
+ ++current;
+ }
+ // If there was a run then current won't equal start.
+ // That being the case, append the sequence separator followed by last in sequence
+ // 125:127
+ // If there's no sequence, the next int is written by ptr.AppendNum(*start)
+ if(current != start)
+ {
+ ptr.Append(':');
+ ptr.AppendNum(*current);
+ }
+ ++current;
+ }
+ }
+
+void CImImap4Session::IdleTimerExpiredL()
+ {
+ iIdleTimerExpired = ETrue;
+ ReissueIdleL();
+ }
+
+void CImImap4Session::CancelTimerExpired()
+ {
+ Cancel();
+ if (iState != EImapStateDisconnected)
+ {
+ DoDisconnect();
+ }
+ iObserver.NonCompletedFailure();
+ }
+
+
+CIdleTimeoutTimer::CIdleTimeoutTimer(CImImap4Session& aOperation)
+: CTimer(EPriorityHigh), iOperation(aOperation)
+ {}
+
+void CIdleTimeoutTimer::RunL()
+ {
+ iOperation.IdleTimerExpiredL();
+ }
+
+CIdleTimeoutTimer* CIdleTimeoutTimer::NewL(CImImap4Session& aOperation)
+ {
+ CIdleTimeoutTimer* self = new(ELeave) CIdleTimeoutTimer(aOperation);
+ CleanupStack::PushL(self);
+ self->ConstructL(); // CTimer
+ CActiveScheduler::Add(self);
+ CleanupStack::Pop();
+ return self;
+ }
+
+
+CImImap4SessionDummyRead* CImImap4SessionDummyRead::NewL(CImImap4Session& aSession, CImapIO& aImapIO, TInt aPriority)
+ {
+ CImImap4SessionDummyRead* self = new (ELeave) CImImap4SessionDummyRead(aSession, aImapIO, aPriority);
+ return self;
+ }
+
+CImImap4SessionDummyRead::CImImap4SessionDummyRead(CImImap4Session& aSession, CImapIO& aImapIO, TInt aPriority)
+: CActive(aPriority), iSession(aSession), iImapIO(aImapIO)
+ {
+ DBG(iSession.LogText(_L8("+ CImImap4SessionDummyRead()") ) );
+ CActiveScheduler::Add(this);
+ DBG(iSession.LogText(_L8("- CImImap4SessionDummyRead()") ) );
+ }
+
+CImImap4SessionDummyRead::~CImImap4SessionDummyRead()
+ {
+ DBG(iSession.LogText(_L8("+ ~CImImap4SessionDummyRead") ) );
+ Cancel();
+ DBG(iSession.LogText(_L8("- ~CImImap4SessionDummyRead") ) );
+ }
+
+void CImImap4SessionDummyRead::Start()
+ {
+ DBG( iSession.LogText( _L8("+ CImImap4SessionDummyRead::Start()") ) );
+ if(IsActive())
+ {
+ // already have an outstanding dummy read, ignore this
+ DBG( iSession.LogText( _L8("CImImap4SessionDummyRead::Start():Already active") ) );
+ return;
+ }
+
+ DBG( iSession.LogText( _L8("+ CImImap4SessionDummyRead::Start()") ) );
+ // As the spec is ambiguous about when we can get untagged responses,
+ // we can get them any time, so ask for a proper reply
+ iImapIO.GetReply(iStatus);
+
+ DBG((iSession.LogText(_L8("*******************************************************"))));
+ DBG((iSession.LogText(_L8("CImap4SessionDummyRead::Start(): waiting for iImapIO to wake me"))));
+ DBG((iSession.LogText(_L8("*******************************************************"))));
+
+ SetActive();
+ DBG( iSession.LogText( _L8("- CImImap4SessionDummyRead::Start()") ) );
+ }
+
+void CImImap4SessionDummyRead::DoCancel()
+ {
+ DBG( iSession.LogText( _L8("+ CImImap4SessionDummyRead::DoCancel()") ) );
+ iImapIO.Cancel();
+ DBG( iSession.LogText( _L8("- CImImap4SessionDummyRead::DoCancel()") ) );
+ }
+
+void CImImap4SessionDummyRead::RunL()
+ {
+ //Inform iOperation that the dummy read has completed.
+
+ //Under normal conditions the dummy read should never complete.
+ //However, if it does complete it is likely that iStatus != KErrNone.
+ DBG( iSession.LogText( _L8("+ CImImap4SessionDummyRead::RunL()") ) );
+ iSession.DummyComplete(iStatus.Int());
+ if (iStatus.Int() >= KErrNone)
+ {
+ DBG( iSession.LogText(_L8(" CImImap4SessionDummyRead::RunL() There might be more data coming, issue dummy reading again.")) );
+ iSession.ReissueDummy();
+ }
+ DBG( iSession.LogText( _L8("- CImImap4SessionDummyRead::RunL()") ) );
+ }
+
+
+CImImap4SessionIdleRead* CImImap4SessionIdleRead::NewL(CImImap4Session& aOperation, TInt aPriority)
+ {
+ CImImap4SessionIdleRead* self = new (ELeave) CImImap4SessionIdleRead(aOperation, aPriority);
+ return self;
+ }
+
+CImImap4SessionIdleRead::CImImap4SessionIdleRead(CImImap4Session& aOperation, TInt aPriority)
+: CActive(aPriority), iOperation(aOperation)
+ {
+ CActiveScheduler::Add(this);
+ }
+
+CImImap4SessionIdleRead::~CImImap4SessionIdleRead()
+ {
+ Cancel();
+ }
+
+void CImImap4SessionIdleRead::Start(TRequestStatus& aStatus)
+ {
+ // Store the status for completion later
+ iOperationStatus = &aStatus;
+ *iOperationStatus = KRequestPending;
+
+ // Issue the read with *our* status
+ iOperation.DoIdleRead(iStatus);
+ DBG((iOperation.LogText(_L8("*******************************************************"))));
+ DBG((iOperation.LogText(_L8("CImap4SessionIdleRead::Start(): waiting for iOperation to wake me"))));
+ DBG((iOperation.LogText(_L8("*******************************************************"))));
+
+ SetActive();
+ }
+
+void CImImap4SessionIdleRead::DoCancel()
+ {
+ DBG(iOperation.LogText(_L8("CImImap4SessionIdleRead::DoCancel()")));
+
+ // Cancel the idle read we started
+ iOperation.CancelIdleRead();
+ DBG((iOperation.LogText(_L8("-----------------------------------------------------------"))));
+ DBG((iOperation.LogText(_L8("CImap4SessionIdleRead::DoCancel(): calling request complete"))));
+ DBG((iOperation.LogText(_L8("-----------------------------------------------------------"))));
+
+ User::RequestComplete(iOperationStatus, KErrCancel);
+ }
+
+void CImImap4SessionIdleRead::RunL()
+ {
+ TInt err = iStatus.Int();
+ DBG(iOperation.LogText(_L8("CImImap4SessionIdleRead::RunL(iStatus=%d)"),err));
+
+ if(err < KErrNone)
+ { // Special case for error
+ // Need to do this because if read completed with an error,
+ // the RequestComplete would not handle this by itself as
+ // the RunL would not be called and neither would the
+ // DoComplete (because the session's iReport is NULL...)
+ iOperation.IdleReadError(err);
+ }
+ DBG((iOperation.LogText(_L8("-----------------------------------------------------------"))));
+ DBG((iOperation.LogText(_L8("CImap4SessionIdleRead::RunL(): calling request complete"))));
+ DBG((iOperation.LogText(_L8("-----------------------------------------------------------"))));
+
+ User::RequestComplete(iOperationStatus, err);
+ }