diff -r 238255e8b033 -r 84d9eb65b26f email/pop3andsmtpmtm/imapservermtm/src/IMAPSESS.CPP --- /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 +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include // resource definition for IMCV +#include // TResourceReader +#include + +#include "impsmtm.h" + +#include "imapsess.h" +#include "impspan.h" +#include "fldindex.h" +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __WINS__ +#include // for maxfilename lengths +#include +#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 " 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(8); + + // List of messages to fetch in single fetch operation + iFetchList=new (ELeave) CArrayFixFlat(8); + + // List of messages who's Seen status has changed + iSetSeenList=new (ELeave) CArrayFixFlat(8); + iClearSeenList=new (ELeave) CArrayFixFlat(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(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 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;aGetEntryFromId(id,entryPtr)); + iFolderIndex[a].iUid=((TMsvEmailEntry)(*entryPtr)).UID(); + iFolderIndex[a].iMsvId=id; + } + } + else + { + for(a=0;aGetEntryFromId(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;aPrepareToConvertToFromOurCharsetL(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;aDes().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(tuplebuf(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(tupleDes(); + } + 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;childCount();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;iAttachmentManagerExtensionsL().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(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(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); iCount(); 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 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; iInsertL(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;iAttachmentManagerExtensionsL().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;iAttachmentManagerExtensionsL().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(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[.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[.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] 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] + // MIME_HEADER_INFO + // BODY_DATA + // + // or + // + // BODY.PEEK[x.MIME] + // 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; iEntry(); + + // 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(iFolderPositioniFolderIndex[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 (iMessageUidiMissingUidHigh || 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 (iFolderPositioniMessageUid || 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* atomStack = new (ELeave) CArrayFixFlat(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(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(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 ' + 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;aCount();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(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(aCount()) + 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= local entry uid. + while (remoteCount() && + (*iSearchList)[remote]>=iFolderIndex[local].iUid) + break; + remote++; + } + + // Within scope of search list? + if (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(iFolderPositionUpdatingSeenFlags()) + { + 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=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;aLength();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;aLength();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) + 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* 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(posEntry()).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(5); + + // Go through them, checking to see if they're messages and removing ones that aren't + TInt pos=0; + while(posCount()) + { + 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(posDeleteEntry((*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;aCount(); + } + + // 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; iCount(); 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((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;aCount();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((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;aCount();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(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 (iStateGetConnectionStage(); + 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; iCount(); 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; iCount(); i++) + buf.Append((*array)[i]); + + CImcvUtils::PriorityFieldsL(*array); + for (i=0; iCount(); 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* 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(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); + }