diff -r 6a20128ce557 -r ebfee66fde93 email/imap4mtm/imapprotocolcontroller/src/cimapopfetchbody.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/email/imap4mtm/imapprotocolcontroller/src/cimapopfetchbody.cpp Fri Jun 04 10:25:39 2010 +0100 @@ -0,0 +1,2673 @@ +// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies). +// All rights reserved. +// This component and the accompanying materials are made available +// under the terms of "Eclipse Public License v1.0" +// which accompanies this distribution, and is available +// at the URL "http://www.eclipse.org/legal/epl-v10.html". +// +// Initial Contributors: +// Nokia Corporation - initial contribution. +// +// Contributors: +// +// Description: +// cimapopfetchbody.cpp +// + +#include +#include + +#include "cimapopfetchbody.h" +#include "cimapbodystructure.h" +#include "cimapenvelope.h" +#include "cimapfetchresponse.h" +#include "cfetchbodyinfo.h" +#include "cimapfetchbodyresponse.h" +#include "cimaprfc822headerfields.h" +#include "cimapmimeheaderfields.h" +#include "cimapsession.h" +#include "cimapsettings.h" +#include "cimapmailstore.h" +#include "cimaputils.h" +#include "cimaplogger.h" +#include +#include +#include +#include +#include "cimapsyncmanager.h" +#include "cimapfolder.h" + +#include +#include +#ifdef SYMBIAN_ENABLE_SPLIT_HEADERS +#include "timrfc822datefield.h" +#include "cimconvertheader.h" +#endif + +_LIT8(KIMAP_NIL, "NIL"); + +// MIME message types +_LIT8(KMIME_TEXT, "TEXT"); +_LIT8(KMIME_HTML, "HTML"); +_LIT8(KMIME_XVCARD, "X-VCARD"); +_LIT8(KMIME_VCALENDAR, "X-VCALENDAR"); +_LIT8(KMIME_ICALENDAR, "CALENDAR"); +_LIT8(KMIME_RTF, "RTF"); +_LIT8(KMIME_NAME, "NAME"); +_LIT8(KMIME_NAME_RFC2231, "NAME*"); +_LIT8(KMIME_ATTACHMENT, "ATTACHMENT"); +_LIT8(KMIME_FILENAME, "FILENAME"); +_LIT8(KMIME_FILENAME_RFC2231, "FILENAME*"); + +// Encoding types +_LIT8(KMIME_BASE64, "BASE64"); + +_LIT8(KIMAP_REL_PATH_SEPARATOR, "."); + +// Header fields to fetch when fetching the body structure. +_LIT8(KImapFetchLargeHeaderFields, "From Subject Reply-to To Cc Bcc Message-ID Return-Receipt-To X-Return-Receipt-To Disposition-Notification-To Disposition-Notification-Options"); + +// No longer fetch the following fields +// Received - date set on sync +// Date - date set on sync +// Subject - fetched during sync - copy from TMsvEmailEntry into CImHeader +// From - fetched during sync - copy from TMsvEmailEntry into CImHeader + +// Positive completion errors for FindFilenameL() +const TInt KErrRFC2231Encoded = 2; + + +// Efficient and SAFE way of comparing TBools which might have different integers representing TRUE +inline TBool BoolsAreEqual( TBool aA, TBool aB ) + { + return ((aA && aB) || (!aA && !aB)); + } + +inline TBool BoolsAreNotEqual( TBool aA, TBool aB ) + { + return ((!aA || !aB) && (aA || aB)); + } + +CImapOpFetchBody::CImapOpFetchBody( CImapSession*& aSession, + CImapSyncManager& aSyncManager, + CMsvServerEntry& aServerEntry, + CImapSettings& aImapSettings, + CImapMailStore& aMailStore) +: CImapOperation(aSession, aServerEntry, EPriorityStandard), + iSyncManager(aSyncManager), + iImapSettings(aImapSettings), iMailStore(aMailStore) + + { + + } + +CImapOpFetchBody::~CImapOpFetchBody() + { + Cancel(); + // Fetch Response objects + delete iFetchResponse; + delete iFetchBodyResponse; + iFetchList.ResetAndDestroy(); + + // CAF support + delete iCaf; + + // Characterset conversion + delete iHeaderConverter; + delete iCharConv; + delete iCharacterConverter; + } + +/** +Static construction for CImapOpFetchBody class. + +@param aSession connected IMAP Session object to use. +@param aServerEntry access to the message entry array. +@param aImapSettings access to the settings for the IMAP Service. +@return the newly created CImapOpFetchBody object. The caller is responsible for +deletion. +*/ +CImapOpFetchBody* CImapOpFetchBody::NewL(CImapSession*& aSession,CImapSyncManager& aSyncManager, CMsvServerEntry& aServerEntry, CImapSettings& aImapSettings, CImapMailStore& aMailStore) + { + CImapOpFetchBody* self = new (ELeave) CImapOpFetchBody(aSession, aSyncManager, aServerEntry, aImapSettings, aMailStore); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; + } + +void CImapOpFetchBody::ConstructL() + { + // Caf utils + iCaf = new (ELeave) CImCaf(CImapUtils::GetRef().Fs()); + + // Create converter objects + iCharacterConverter=CCnvCharacterSetConverter::NewL(); + iCharConv=CImConvertCharconv::NewL(*iCharacterConverter, CImapUtils::GetRef().Fs()); + 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); + + CActiveScheduler::Add(this); + } + +/** +Fetches the specified bodypart and all parts that exist below it that match +the specified fetch criteria. Thus if called with the root of a message as +the specified part, then all message parts that satisfy the criteria will +be fetched, up to the entire message contents. + +@param aStatus request status of the Proctocol Controller +@param aPart the ID in the local message store of the message part to fetch. +@param aGetPartialMailInfo: partial fetch settings. +*/ +void CImapOpFetchBody::FetchBodyL(TRequestStatus& aRequestStatus, const TMsvId aPart, const TImImap4GetPartialMailInfo& aGetPartialMailInfo) + { + __LOG_TEXT(iSession->LogId(), "CImapOpFetchBody::FetchBodyL(): fetching message"); + + iRequestedPart=aPart; + iGetPartialMailInfo=aGetPartialMailInfo; + Queue(aRequestStatus); + // sets the next state and sets active + DoFetchL(); + } + +/** +Initialises the fetch of the requested body parts. +If there is nothing to fetch (ie the client has requested a fetch of headers +only, which are fetched during synchronise) the user request is completed. +Otherwise, if the body structure representation for the message already exists +in the message store, the tree structure is parsed to identify parts to fetch. +Otherwise, a request is issued for the required message headers and bodystructure. +*/ +void CImapOpFetchBody::DoFetchL() + { + // Reset stats + iBytesToDo=0; + iBytesDone=0; + iFetchList.ResetAndDestroy(); + iHtmlEntryPart = 0; + iBodyTextSize = 0; + iHtmlEntrySize = 0; + iBodyPartRemainingSize = 0; + iSizeOfToBeFetchedAttachments=0; + iHasTextParts = 0; + + CheckForPartialPopulate(); + + // if we only want headers then there is nothing to do as they + // have already been fetched on synchronisation. We complete here. + if(!iFetchPartialMail && iGetPartialMailInfo.iGetMailBodyParts == EGetImap4EmailHeaders) + { + __LOG_TEXT(iSession->LogId(), "CImapOpFetchBody:DoFetchL(): Nothing to fetch, completing"); + Complete(KErrNone); + return; + } + + // Get info on what is being fetching + SetEntryL(iRequestedPart); + + // Find the root entry for the message this part belongs to. + // No need to check the selected folder is correct - the + // compound operation checks this. + TBool messageFound = EFalse; + while(!messageFound) + { + // Reached the message ? + if (iServerEntry.Entry().iType==KUidMsvMessageEntry) + { + messageFound = ETrue; + TMsvEmailEntry entry = iServerEntry.Entry(); + iMessageUid = entry.UID(); + iMessageMsvId = entry.Id(); + + // indicate that a fetch operation has been performed on this message. + // This is used to prevent subsequent fetches during 2-phase sync. + // ((TMsvEmailEntry&)iServerEntry.Entry()). + entry.SetValidUID(ETrue); + User::LeaveIfError(iServerEntry.ChangeEntry(entry)); + + if (!(iServerEntry.Entry().Owner())) + { + // If there are no child entries then we need to fetch + // the envelope and body structure + FetchLargeHeaderL(); + } + else + { + // If the structure is already present then build up an array of + // parts to be fetched from the data in the message server entry + // array and associated mime parts. Do this in the RunL + iCurrentStep=iNextStep=EBuildFetchList; + // complete self + SetActive(); + TRequestStatus* status = &iStatus; + User::RequestComplete(status, KErrNone); + } + } + // Up a level + SetEntryL(iServerEntry.Entry().Parent()); + } // end while(!messageFound) + } + +/** +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. +Allows for efficiency when identifying message parts to fetch. +*/ +void CImapOpFetchBody::CheckForPartialPopulate() + { + if(iGetPartialMailInfo.iPartialMailOptions == ENoSizeLimits && + iGetPartialMailInfo.iTotalSizeLimit == KMaxTInt && + iGetPartialMailInfo.iBodyTextSizeLimit == KMaxTInt && + iGetPartialMailInfo.iAttachmentSizeLimit == KMaxTInt && + (iGetPartialMailInfo.iGetMailBodyParts == EGetImap4EmailHeaders || + iGetPartialMailInfo.iGetMailBodyParts == EGetImap4EmailBodyText || + iGetPartialMailInfo.iGetMailBodyParts == EGetImap4EmailBodyTextAndAttachments || + iGetPartialMailInfo.iGetMailBodyParts == EGetImap4EmailAttachments || + iGetPartialMailInfo.iGetMailBodyParts == EGetImap4EmailBodyAlternativeText)) + { + __LOG_TEXT(iSession->LogId(), "CImapOpFetchBody: Doing non-partial fetch"); + iFetchPartialMail = EFalse; + } + else + { + __LOG_TEXT(iSession->LogId(), "CImapOpFetchBody: Doing partial fetch"); + iFetchPartialMail = ETrue; + } + } + +/** +Issues a request to IMAP Session to fetch the IMAP Header details and bodystructure. +*/ +void CImapOpFetchBody::FetchLargeHeaderL() + { + // Issue the fetch request to the imap session + delete iFetchResponse; + iFetchResponse = NULL; + iFetchResponse = CImapFetchResponse::NewL(); + iCurrentStep = EGetBodyStructure; + iNextStep = EProcessBodyStructure; + iSession->FetchBodyStructureAndHeadersL(iStatus, iMessageUid, KImapFetchLargeHeaderFields, *iFetchResponse); + SetActive(); + } + + + +/** +Called when asynchronous service requests complete. +Handles errors returned by aSynchronous services. +Implements the state handling for message fetching. +*/ +void CImapOpFetchBody::DoRunL() + { + // Handle any server errors + if (iStatus.Int()!=KErrNone) + { + if (TInt err=ProcessServerError() != KErrNone) + { + Complete(err); + return; + } + } + + iCurrentStep = iNextStep; + + switch (iCurrentStep) + { + case EProcessBodyStructure: + { + if (ProcessBodyStructureL()) + { + iNextStep = EFetchFirstPart; + // complete self + SetActive(); + TRequestStatus* status = &iStatus; + User::RequestComplete(status, KErrNone); + } + else + { + // No body structure. Message has probably been deleted from server but local + // copy has not yet been removed. + Complete(KErrNone); + return; + } + break; + } + case EBuildFetchList: + { + DoBuildFetchListL(); + iCurrentStep = EFetchFirstPart; + // falls through to fetch the first item in the fetch array. + } + case EFetchFirstPart: + { + // initialise progress counters: + iPartsToDo=iFetchList.Count(); + iPartsDone=0; + + if (FetchPartL()) + { + //DBG((LogText(_L8("Starting body fetch of %d parts (%d bytes)"), + iNextStep = EFetchNext; + } + else + { + // No parts identified for fetch - complete the user request. + Complete(KErrNone); + return; + } + break; + } + case EFetchNext: + { + FetchPartCompleteL(); + + // Issue fetch for the next part in the array + if (FetchPartL()) + { + iNextStep = EFetchNext; + } + else + { + // No more parts to fetch - complete the user request. + iCurrentStep=EFinished; + Complete(KErrNone); + return; + } + break; + } + default: + { + User::Leave(KErrNotSupported); + } + } + + if (!IsActive()) + { + SetActive(); + } + } + +/** +Special form of Cancel(), which enables message currently being fetched to be resumed. +*/ +void CImapOpFetchBody::CancelEnableResume() + { + Cancel(); + TRAP_IGNORE(ClearFetchAttemptedL()); + } + +/** +Clears the fetch-attempted flag (re-use of ValidUID flag) to indicate that the +cancelled fetch may be resumed according to download rules. +*/ +void CImapOpFetchBody::ClearFetchAttemptedL() + { + SetEntryL(iMessageMsvId); + TMsvEmailEntry entry = iServerEntry.Entry(); + entry.SetValidUID(EFalse); + User::LeaveIfError(iServerEntry.ChangeEntry(entry)); + } + +/** +Called by Cancel() to cancel asynchronous service requests +*/ +void CImapOpFetchBody::DoCancel() + { + switch (iCurrentStep) + { + // States involving a session request + case EGetBodyStructure: + case EFetchFirstPart: + case EFetchNext: + { + iSession->Cancel(); + break; + } + // Self completing or synchronous states + case EProcessBodyStructure: + case EBuildFetchList: + default: + { + break; + } + } // end of switch (iCurrentStep) + CMsgActive::DoCancel(); + } + +/** +Called when a user request is completed. +*/ +#ifdef __IMAP_LOGGING +void CImapOpFetchBody::DoComplete(TInt& aErr) +#else +void CImapOpFetchBody::DoComplete(TInt& /*aErr*/) +#endif //__IMAP_LOGGING + { + __LOG_FORMAT((iSession->LogId(), "CImapOpFetchBody::DoComplete(aErr = %d) CurrentStep = %d", aErr, iCurrentStep)); + } + +/** +Builds a list of parts to fetch from the server entry array. +This method is used when the mailstore representation of the bodystructure +for the message has previously been constructed. +*/ +void CImapOpFetchBody::DoBuildFetchListL() + { + SetEntryL(iRequestedPart); + TMsvEmailEntry temp = (TMsvEmailEntry)iServerEntry.Entry(); + TUid type=temp.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) + iGetPartialMailInfo.iGetMailBodyParts = EGetImap4EmailBodyTextAndAttachments; + } + + // Build a list of parts to fetch: + BuildFetchListL(iRequestedPart); + + UpdateBodyTextRemainingSizeForHtml(); + } + + +/** +Add the message part identified by aMessage and aMimeHeader to the array of +parts to fetch. + +Creates a CFetchBodyInfo object for each part to be fetched. This is the data +that the session requires to fetch each message part. + +Note that this function should only be called when the part has been correctly +identified as a part for fetching. + +@param aMessage the TMsvEmailEntry for the part. +@param aMimeHeader the mime header for the part. +@param aIsText bool indicating whether this is a text part. +@param aSizeToFetch amount of data to fetch - this may indicate a partial fetch + if less than the total size of the part. +*/ +void CImapOpFetchBody::AddFetchItemL(CImMimeHeader& aMimeHeader, TMsvEmailEntry& aMessage, TBool aIsText, TInt32 aSizeToFetch) + { + CFetchBodyInfo* fetchInfo=CFetchBodyInfo::NewLC(aMessage.Id()); + fetchInfo->SetSizeToFetch(aSizeToFetch); + fetchInfo->SetBodyPartRemainingSize(aMessage.iBioType - aSizeToFetch); + fetchInfo->SetRelativePathL(aMimeHeader.RelativePath()); + fetchInfo->SetIsText(aIsText); + fetchInfo->SetContentTransferEncoding(aMimeHeader.ContentTransferEncoding()); + + if (aIsText) + { + fetchInfo->SetCharsetId(aMimeHeader.MimeCharset()); + } + else if (aMimeHeader.ContentType().MatchF(KImcvApplication) == 0) + { + // Set the CAF pointer if the attachment is an application. + // This is not registered at this stage, this is done just + // prior to issuing the fetch request. This allows a single + // CImCaf instance to be re-used. + fetchInfo->SetCaf(iCaf); + } + + iFetchList.AppendL(fetchInfo); + CleanupStack::Pop(fetchInfo); + + // Add this part's size to the size total + iBytesToDo+=aMessage.iBioType; + } + +/** +Determines if an item is to be fetched, depending on the parttypes specified. +Items are also rejected if they have been marked complete due to having 0 +length. An attachment info object is created for attachment items rejected +this way. + +@param aMessage the TMsvEmailEntry for the part. +@param aMimeHeader the mime header for the part. +@param aPartTypes Get-Mail options specifying the parts to be fetched. +*/ +void CImapOpFetchBody::AddFilterFetchItemL(CImMimeHeader& aMimeHeader, TMsvEmailEntry& aEntry, TImap4GetMailOptions& aPartTypes) + { + __LOG_FORMAT((iSession->LogId(), "CImapOpFetchBody::AddFilterFetchItemL(entry id = %x) using get mail options", aEntry.Id())); + TUid type = aEntry.iType; + + // Defensive - nothing to fetch for folder and message entry types... + if (type == KUidMsvFolderEntry || type == KUidMsvMessageEntry) + { + return; + } + + // Part may be marked complete because it has 0 size, + // therefore we don't need to fetch anything. + if (aEntry.Complete() && !aEntry.PartialDownloaded()) + { + __LOG_TEXT(iSession->LogId(), " 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 ((type == KUidMsvAttachmentEntry || type == KUidMsvEmailExternalBodyEntry) && + (aEntry.iSize == 0)) + { + CreateAttachmentInfoL(iServerEntry.Entry()); + } + return; + } + + // All other entry types may require fetching + TBool addPart = EFalse; + TBool isText = EFalse; + + TBool addingTextParts = (aPartTypes == EGetImap4EmailBodyText || + aPartTypes == EGetImap4EmailBodyTextAndAttachments || + aPartTypes == EGetImap4EmailBodyAlternativeText); + + if (type == KUidMsvEmailTextEntry || type == KUidMsvEmailHtmlEntry || type == KUidMsvEmailRtfEntry) + { + if (type == KUidMsvEmailTextEntry) + { + isText = ETrue; + } + + iHasTextParts = ETrue; + + if (addingTextParts) + { + addPart = ETrue; + } + } + else if (type == KUidMsvAttachmentEntry || type == KUidMsvEmailExternalBodyEntry) + { + if (aPartTypes == EGetImap4EmailBodyTextAndAttachments || + aPartTypes == EGetImap4EmailAttachments) + { + addPart = ETrue; + } + else + { + SetEntryL(aEntry.Parent()); + TImEmailFolderType folderType = static_cast((iServerEntry.Entry())).MessageFolderType(); + SetEntryL(aEntry.Id()); + + if(folderType==EFolderTypeRelated && addingTextParts) + { + // if asked for bodytext and it is an attachment then fetch it if + // attachment is in a folder of type 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 + if(aMimeHeader.ContentType().CompareF(KMIME_TEXT)==0) + { + // This is a alternative text part, and should be treated + // as a text part + addPart = ETrue; + } + } + + // Create attachment info in the messaging store + if(!addPart) + { + CreateAttachmentInfoL(iServerEntry.Entry()); + } + } + } + else + { + __ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::EFetchBodyUnknownMsvType)); + addPart = ETrue; + } + + if (addPart) + { + AddFetchItemL(aMimeHeader, aEntry, isText, aEntry.iBioType); + } + } + +/** +Determines if an item is to be fetched, depending on the partial mail +options specified. +Items are also rejected if they have been marked complete due to having 0 +length. An attachment info object is created for attachment items rejected +this way. + +@param aMessage the TMsvEmailEntry for the part. +@param aMimeHeader the mime header for the part. +@param aGetPartialMailInfo partial mail options describes parts to fetch. +*/ +void CImapOpFetchBody::AddFilterFetchItemL(CImMimeHeader& aMimeHeader, TMsvEmailEntry& aEntry, TImImap4GetPartialMailInfo& aGetPartialMailInfo) + { + __LOG_FORMAT((iSession->LogId(), "CImapOpFetchBody::AddFilterFetchItemL(entry id = %x) using partial mail options", aEntry.Id())); + TUid type = aEntry.iType; + + // Defensive - nothing to fetch for folder and message entry types... + if (type == KUidMsvFolderEntry || type == KUidMsvMessageEntry) + { + return; + } + + // Part may be marked complete because it has 0 size, + // therefore we don't need to fetch anything. + if (aEntry.Complete() && !aEntry.PartialDownloaded()) + { + __LOG_TEXT(iSession->LogId(), " 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 ((aEntry.iType == KUidMsvAttachmentEntry || aEntry.iType == KUidMsvEmailExternalBodyEntry) + && (aEntry.iSize == 0)) + { + CreateAttachmentInfoL(iServerEntry.Entry()); + } + return; + } + + // All other entry types may require fetching + TBool addPart = EFalse; + TBool isText = EFalse; + TInt sizeToFetch = aEntry.iBioType; + + if (type == KUidMsvEmailTextEntry) + { + iHasTextParts = ETrue; + isText = ETrue; + if(aGetPartialMailInfo.iPartialMailOptions == ENoSizeLimits) + { + addPart = ETrue; + } + else if (aGetPartialMailInfo.iPartialMailOptions == ECumulative) + { + if(iGetPartialMailInfo.iTotalSizeLimit > 0) + { + addPart = ETrue; + sizeToFetch = Minimum(aEntry.iBioType, iGetPartialMailInfo.iTotalSizeLimit); + iBodyTextSize = aEntry.iBioType; + } + } + else if (aGetPartialMailInfo.iPartialMailOptions == EBodyTextOnly || + aGetPartialMailInfo.iPartialMailOptions == EBodyTextAndAttachments || + aGetPartialMailInfo.iPartialMailOptions == EBodyAlternativeText ) + { + addPart = ETrue; + sizeToFetch = Minimum(aEntry.iBioType, iGetPartialMailInfo.iBodyTextSizeLimit); + iBodyTextSize = aEntry.iBioType; + } + } + else if (type == KUidMsvEmailHtmlEntry) + { + iHasTextParts = ETrue; + iHtmlEntrySize = aEntry.iBioType; + if(aGetPartialMailInfo.iPartialMailOptions == ENoSizeLimits) + { + addPart = ETrue; + } + else if (aGetPartialMailInfo.iPartialMailOptions == ECumulative) + { + if((iGetPartialMailInfo.iTotalSizeLimit > 0 ) && + ((iBodyTextSize + aEntry.iBioType) <= iGetPartialMailInfo.iTotalSizeLimit)) + { + addPart = ETrue; + } + } + else if (aGetPartialMailInfo.iPartialMailOptions == EBodyTextOnly || + aGetPartialMailInfo.iPartialMailOptions == EBodyTextAndAttachments || + aGetPartialMailInfo.iPartialMailOptions == EBodyAlternativeText ) + + { + if(iBodyTextSize + aEntry.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 = aEntry.Id(); + } + } + else if (type == KUidMsvAttachmentEntry || type == KUidMsvEmailExternalBodyEntry) + { + if(aGetPartialMailInfo.iPartialMailOptions == ENoSizeLimits) + { + addPart = ETrue; + } + else if (aGetPartialMailInfo.iPartialMailOptions == ECumulative) + { + if(iGetPartialMailInfo.iTotalSizeLimit > 0 && ((iBodyTextSize + iSizeOfToBeFetchedAttachments + aEntry.iBioType) <= iGetPartialMailInfo.iTotalSizeLimit)) + { + addPart = ETrue; + if((iBodyTextSize + iSizeOfToBeFetchedAttachments + aEntry.iBioType + iHtmlEntrySize) >= iGetPartialMailInfo.iTotalSizeLimit) + { + RemoveHtmlPart(iHtmlEntryPart); + } + iSizeOfToBeFetchedAttachments+=aEntry.iBioType; + } + else + { + CreateAttachmentInfoL(aEntry); + // 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(aEntry.iBioType <= Minimum(iGetPartialMailInfo.iAttachmentSizeLimit,iGetPartialMailInfo.iTotalSizeLimit)) + { + addPart = ETrue; + } + else + { + CreateAttachmentInfoL(aEntry); + } + } + else + { + SetEntryL(iServerEntry.Entry().Parent()); + TImEmailFolderType folderType = static_cast((iServerEntry.Entry())).MessageFolderType(); + SetEntryL(aEntry.Id()); + + 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 && + aEntry.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 + if( aMimeHeader.ContentType().CompareF(KMIME_TEXT)==0 ) + { + // This is a alternative text part, and should be treated + // as a text part + addPart = ETrue; + } + } + + if(!addPart) + { + CreateAttachmentInfoL(aEntry); + } + } + } + else + { + __ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::EFetchBodyUnknownMsvType)); + + // for anything else, if not debug mode then fetch anyway + addPart = ETrue; + } + + if (addPart) + { + AddFetchItemL(aMimeHeader, aEntry, isText, sizeToFetch); + } + } + +/** +Builds an array of message parts to fetch. +This is a function is recursive, calling itself for entry parts that have +children, thus building up an array of parts to fetch for the entire message. + +This is a simple form of this function, used when performing a non-partial +fetch of the message. + +@param aPart The part to process +@param aPartTypes Specifies which components to fetch +*/ +void CImapOpFetchBody::BuildFetchListL(TMsvId aPart) + { + __LOG_FORMAT((iSession->LogId(), "CImapOpFetchBody::BuildFetchListL(%x)", aPart)); + + // Is this part fetchable? + SetEntryL(aPart); + + TUid type = iServerEntry.Entry().iType; + + // Folder and Message Entry entry types... + if (type == KUidMsvFolderEntry || type == KUidMsvMessageEntry) + { + // Nothing to fetch at this entry - check child entries + CMsvEntrySelection *selection=new (ELeave) CMsvEntrySelection; + CleanupStack::PushL(selection); + User::LeaveIfError(iServerEntry.GetChildren(*selection)); + + __LOG_FORMAT((iSession->LogId(), "...ID %x has %d children", iServerEntry.Entry().Id(),selection->Count())); + + TInt count = selection->Count(); + for(TInt a=0;aRestoreL(*store); + // finished with the read store + CleanupStack::PopAndDestroy(store); + + // get the entry for the part + TMsvEmailEntry entry = (TMsvEmailEntry)iServerEntry.Entry(); + + // Apply filters and add to fetch list + if (iFetchPartialMail) + { + AddFilterFetchItemL(*mimeHeader, entry, iGetPartialMailInfo); + } + else + { + AddFilterFetchItemL(*mimeHeader, entry, iGetPartialMailInfo.iGetMailBodyParts); + } + + // tidyup + CleanupStack::PopAndDestroy(mimeHeader); + } + + + +/** +Removes the html part from the download list if it exists in the list +Used when partial fetching. + +@param aPart ID of the HTML part to be removed from the fetch list +*/ +void CImapOpFetchBody::RemoveHtmlPart(TMsvId aPart) + { + if(aPart) + { + TInt count = iFetchList.Count(); + for (TInt i=0 ; iPartId()==aPart) + { + iFetchList.Remove(i); + delete info; + break; + } + } + } + } + +/** +Checks for the minimum size limit between message type size limit +(attachment size limit/body text sizelimit) + +@param aThisPartTypeSizeLimit the size limit for the part-type in question +@param aTotalMailSizeLimit the total mail size limit +*/ +TInt32 CImapOpFetchBody::Minimum(TInt32 aThisPartTypeSizeLimit,TInt32 aTotalMailSizeLimit) + { + if(aTotalMailSizeLimit > 0) + { + if(aThisPartTypeSizeLimit > aTotalMailSizeLimit) + return aTotalMailSizeLimit; + else + return aThisPartTypeSizeLimit; + } + else + return aThisPartTypeSizeLimit; + } + +/** +Entry point for Protocol Controller parsing of the received body structure +and message header information. + +This method builds the a TMsvEntry tree representing the bodystructure. +This method and the methods it calls use CreateEntryBulk() and ChangeEntryBulk() +in place of CreateEntry() and ChangeEntry(). +This means that the TMsvEntry tree is not left half-built should a leave occur. +The tree is commited using CompleteBulk as the final step of the method. + +@return ETrue if complete body structure exists, EFalse if not +*/ +TBool CImapOpFetchBody::ProcessBodyStructureL() + { + // We expect to get the header fields and bodystructure in the response. If we + // don't get them, then that probably indicates that the message we are fetching + // is no longer on the server. If so, we should exit straight away. + if ((iFetchResponse->HeaderFields() == NULL) || + (iFetchResponse->BodyStructure() == NULL)) + { + delete iFetchResponse; + iFetchResponse = NULL; + return EFalse; + } + + SetEntryL(iRequestedPart); + + // defensive - check that the structure has not already been constructed + if (!(iServerEntry.Entry().Owner())) + { + TMsvEmailEntry entry = iServerEntry.Entry(); + + // Build a CImHeader object from the returned header fields data. + CImHeader* imHeader = CImHeader::NewLC(); + PopulateImHeaderL(*(iFetchResponse->HeaderFields()), entry, *imHeader); + User::LeaveIfError(iServerEntry.ChangeEntry(entry)); + StoreHeadersL(imHeader, NULL); + CleanupStack::PopAndDestroy(imHeader); + + TInt attachments=0; + TInt relatedAttachments=0; + TBool isMHTML=EFalse; + iDecodedSizeOfAllParts = 0; + + // Create the message entry structure under the root message + CImapBodyStructure* bodystructure = iFetchResponse->BodyStructure(); + BuildTreeL(entry.Id(),bodystructure,KNullDesC8,entry.Id(),attachments,isMHTML,relatedAttachments); + + UpdateBodyTextRemainingSizeForHtml(); + + if(isMHTML==EFalse) + { + attachments+=relatedAttachments; + } + + // recover server entry context + SetEntryL(iRequestedPart); + entry = iServerEntry.Entry(); + + // 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); + //Resetting the values + iIsICalendar=EFalse; + iIsVCalendar=EFalse; + + if( !iHasTextParts && entry.iType == 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 message complete flags on the entry + __LOG_FORMAT((iSession->LogId(), "Message %d has no text parts - setting body text complete flag to ETrue", iRequestedPart)); + entry.SetBodyTextComplete(ETrue); + } + + SetEntryL(entry.Id()); + User::LeaveIfError(iServerEntry.ChangeEntryBulk(entry)); + } + + // Commit the tree created during this method(); + iServerEntry.CompleteBulk(); + + // No longer need the fetch response + delete iFetchResponse; + iFetchResponse = NULL; + + return ETrue; + } + + +/** +Populates a CImHeader object with data returned in the header and bodystructure +fetch. + +@param aHeaderFields Header field data structure returned by the header fetch operation +@param aEntry Message server entry for the message being fetched +@param aImHeader Target CImHeader object to be populated +*/ +void CImapOpFetchBody::PopulateImHeaderL(CImapRfc822HeaderFields& aHeaderFields, TMsvEmailEntry& aEntry, CImHeader& aImHeader) + { + // Subject, From + // Don't use the fields in aEntry as they may be in Unicode, which causes problems for + // the call to iHeaderConverter->DecodeAllHeaderFieldsL() below - which expects 8 bit data + aImHeader.SetSubjectL(aHeaderFields.FieldValue(CImapRfc822HeaderFields::EImapSubject)); + aImHeader.SetFromL(aHeaderFields.FieldValue(CImapRfc822HeaderFields::EImapFrom)); + + // Return-Receipt-To, X-Return-Receipt-To + const TDesC8& returnReceipt = aHeaderFields.ReturnReceiptField(); + if (returnReceipt != KNullDesC8()) + { + aImHeader.SetReceiptAddressL(returnReceipt); + if (!aEntry.SeenIMAP4Flag()) + { + aEntry.SetReceipt(ETrue); + } + } + + // Reply-to + if (aHeaderFields.FieldExists(CImapRfc822HeaderFields::EImapReplyTo)) + { + aImHeader.SetReplyToL(aHeaderFields.FieldValue(CImapRfc822HeaderFields::EImapReplyTo)); + } + else + { + aImHeader.SetReplyToL(aHeaderFields.FieldValue(CImapRfc822HeaderFields::EImapFrom)); + } + + // Message-ID + if(aHeaderFields.FieldExists(CImapRfc822HeaderFields::EImapMessageId)) + { + aImHeader.SetImMsgIdL(aHeaderFields.FieldValue(CImapRfc822HeaderFields::EImapMessageId)); + } + + // To + if(aHeaderFields.FieldExists(CImapRfc822HeaderFields::EImapTo)) + { + ProcessAddressListL(aImHeader.ToRecipients(), aHeaderFields.FieldValue(CImapRfc822HeaderFields::EImapTo)); + } + + // Cc + if(aHeaderFields.FieldExists(CImapRfc822HeaderFields::EImapCc)) + { + ProcessAddressListL(aImHeader.CcRecipients(), aHeaderFields.FieldValue(CImapRfc822HeaderFields::EImapCc)); + } + + // Bcc + if(aHeaderFields.FieldExists(CImapRfc822HeaderFields::EImapBcc)) + { + ProcessAddressListL(aImHeader.BccRecipients(), aHeaderFields.FieldValue(CImapRfc822HeaderFields::EImapBcc)); + } + + // Decode any QP encoding in header fields + iHeaderConverter->DecodeAllHeaderFieldsL(aImHeader); + } + + +/** +Builds attachment tree for an email message. + +Messaging stores email messages in the message store as a tree structure of +TMsvEntrys representing the mime structure of the message as described by the +BODYSTRUCTURE returned by the imap server. Multipart bodystructure parts are +represented by an entry of type KUidMsvFolderEntry and TImEmailFolderType as +appropriate to the multipart type string. Content parts (text, attachments etc) +are built by method BuildContentEntryL() below. For each mime part an associated +CImMimeHeader object is created and streamed to the messaging store. + +An array of parts to fetch according to message get-options is built up as the +bodystructure is parsed. The information gathered is that which the IMAP Session +requires to successfully fetch a specific message part. + +@param aParent The parent-part to be processed +@param aBodyStructure Parsed bodystructure data for the current part +@param aPath The IMAP relative path for message parts. +@param aThisMessage TMsvId of the root entry for the message. +@param aAttachments Counter for attachments. +@param aIsMHTML Flag indicating that the message has been identified as MHTML +@param aRelatedAttachments Counter for related attachments. +*/ +void CImapOpFetchBody::BuildTreeL(TMsvId aParent, + CImapBodyStructure* aBodyStructure, + const TDesC8& aPath, + const TMsvId aThisMessage, + TInt& aAttachments, + TBool& aIsMHTML, + TInt& aRelatedAttachments) + { + __LOG_FORMAT((iSession->LogId(), "CImapOpFetchBody::BuildTreeL(message=%x, parent=%x", aThisMessage, aParent)); + + // Is this a content entry? (ie, not a multipart entry) + if (aBodyStructure->BodyStructureType()!=CImapBodyStructure::ETypeMultipart) + { + // Build the content entry for the message part + HBufC8* newpath=HBufC8::NewLC(aPath.Length()+4); + *newpath=aPath; + if (aPath.Length()) + newpath->Des().Append(KIMAP_REL_PATH_SEPARATOR); + newpath->Des().AppendNum(1); + BuildContentEntryL(aParent, aBodyStructure, newpath->Des(), aThisMessage, aAttachments, aIsMHTML, aRelatedAttachments); + CleanupStack::PopAndDestroy(newpath); + } + // otherwise is a multipart entry + else + { + // Nest down a level: create a folder entry + SetEntryL(aParent); + TMsvEmailEntry message; + message.iMtm=KUidMsgTypeIMAP4; + message.iServiceId=iImapSettings.ServiceId(); + message.iType=KUidMsvFolderEntry; + message.iSize=0; + message.SetComplete(EFalse); + User::LeaveIfError(iServerEntry.CreateEntryBulk(message)); // bulk op completed at the end of ProcessBodyStructureL() + + __LOG_FORMAT((iSession->LogId(), " Created attachment folder id %x as child of %x", message.Id(), aParent)); + + aParent=message.Id(); + + TPtrC8 multipart = aBodyStructure->SubType(); + // Got anything? + if (multipart.Size()>0) + { + // Parse multipart type string, do this first so + // information is available when parsing children + TImEmailFolderType ft=EFolderTypeUnknown; + if (multipart.CompareF(KImcvRelated)==0) + { + __LOG_TEXT(iSession->LogId(), " Folder type MULTIPART/RELATED"); + ft=EFolderTypeRelated; + } + else if (multipart.CompareF(KImcvMixed)==0) + { + __LOG_TEXT(iSession->LogId(), " Folder type MULTIPART/MIXED"); + ft=EFolderTypeMixed; + } + else if (multipart.CompareF(KImcvParallel)==0) + { + __LOG_TEXT(iSession->LogId(), " Folder type MULTIPART/PARALLEL"); + ft=EFolderTypeParallel; + } + else if (multipart.CompareF(KImcvAlternative)==0) + { + __LOG_TEXT(iSession->LogId(), " Folder type MULTIPART/ALTERNATIVE"); + ft=EFolderTypeAlternative; + } + else if (multipart.CompareF(KImcvDigest)==0) + { + __LOG_TEXT(iSession->LogId(), " Folder type MULTIPART/DIGEST"); + ft=EFolderTypeDigest; + } + + SetEntryL(aParent); + + // ...and save it + TMsvEmailEntry folder=iServerEntry.Entry(); + folder.SetMessageFolderType(ft); +#if SET_RELATED_ID + // Set message's iRelatedId to messageId + folder.iRelatedId=folder.Id(); +#endif + User::LeaveIfError(iServerEntry.ChangeEntryBulk(folder)); + + // Process each of the multi-parts... + TInt subnr=1; + TInt numParts=aBodyStructure->EmbeddedBodyStructureList().Count(); + for (TInt i=0;iDes().Append(KIMAP_REL_PATH_SEPARATOR); + newpath->Des().AppendNum(subnr++); + BuildContentEntryL(aParent, + aBodyStructure->EmbeddedBodyStructureList()[i], + newpath->Des(), + aThisMessage, + aAttachments, + aIsMHTML, + aRelatedAttachments); + CleanupStack::PopAndDestroy(newpath); + } + } + } + } + + + +/** +Builds the TMsvEmailEntry and associated CImMimeHeader for an individual message content part. + +@param aParent TMsvId of the parent of the current part. When called + initially, this is the root entry representing the message +@param aBodyStructure Parsed bodystructure data for the current part +@param aPath The IMAP relative path for message parts. +@param aThisMessage TMsvId of the root entry for the message. +@param aAttachments Counter for attachments. +@param aIsMHTML Flag indicating that the message has been identified as MHTML +@param aRelatedAttachments Counter for related attachments. +*/ +void CImapOpFetchBody::BuildContentEntryL(const TMsvId aParent, CImapBodyStructure* aBodyStructure, const TDesC8& aPath, const TMsvId aThisMessage, TInt& aAttachments, TBool& aIsMHTML, TInt& aRelatedAttachments) + { + + // First, is this actually an entry, or another level of nesting? + if (aBodyStructure->BodyStructureType()==CImapBodyStructure::ETypeMultipart) + { + // Another level of nesting? Call BuildTreeL() + BuildTreeL(aParent, aBodyStructure, aPath, aThisMessage, aAttachments, aIsMHTML, aRelatedAttachments); + return; + } + + // Skeleton for new entry + SetEntryL(aParent); + + TFileName attachmentFilename; // somewhere to store an attachment filename + TMsvEmailEntry message; + message.iSize=0; + message.iMtm=KUidMsgTypeIMAP4; + message.iServiceId=iImapSettings.ServiceId(); + message.SetUID(iMessageUid); + message.SetValidUID(EFalse); // ValidUID Flag used to indicate if the message has ever been fetched + message.SetComplete(EFalse); + + // Save mime TYPE/SUBTYPE + CImMimeHeader* mime=CImMimeHeader::NewLC(); + TPtrC8 type = aBodyStructure->Type(); + TPtrC8 subtype = aBodyStructure->SubType(); + mime->SetContentTypeL(type); + mime->SetContentSubTypeL(subtype); + + __LOG_FORMAT((iSession->LogId(), " MIME: %S/%S", &type, &subtype)); + + // initialise the data type to be an attachment + message.iType=KUidMsvAttachmentEntry; + + // Store the relative path for the content part + mime->SetRelativePathL(aPath); + + if (aBodyStructure->BodyStructureType()==CImapBodyStructure::ETypeText) + { + ProcessTextSubtypeL(aBodyStructure, *mime, message, aIsMHTML); + } + + // Process the Parameter list + ProcessParameterListL(aBodyStructure->ParameterList(), *mime, message, attachmentFilename); + + // ID: save it + TPtrC8 id = aBodyStructure->BodyId(); + if (id.Length() > 0) + { + __LOG_FORMAT((iSession->LogId(), " Content ID: %S", &id)); + mime->SetContentIDL(StripAngledBrackets(id)); + } + + // Description: save it + TPtrC8 description = aBodyStructure->BodyDescription(); + if (description.Length() > 0) + { + __LOG_FORMAT((iSession->LogId(), " Content Description: %S", &description)); + mime->SetContentDescriptionL(description); + } + + // Encoding + TPtrC8 encoding = aBodyStructure->BodyEncoding(); + mime->SetContentTransferEncodingL(encoding); + __LOG_FORMAT((iSession->LogId(), " Content Transfer Encoding: %S", &encoding)); + + // Octets (encoded form) + TInt actualsize; + TLex8 lex(aBodyStructure->BodySizeOctets()); + lex.Val(actualsize); + + // Some servers gives a negative size value when the body text is empty. Need to reset this to + // zero to prevent corruption error later on. + if(actualsize < 0) + { + actualsize = 0; + } + + // Modify actualsize 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.CompareF(KMIME_BASE64)==0) + { + message.iSize=(actualsize*6)/8; + } + else + { + message.iSize=actualsize; + } + + // Add into total message size + iDecodedSizeOfAllParts+=message.iSize; + + // Use iBioType message entry data member to store size on remote server + 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); + } + + // mark attached emails as such + if (aBodyStructure->BodyStructureType()==CImapBodyStructure::ETypeMessageRfc822) + { + // embedded message - marked as a message + // NB needs to be set before ProcessExtendedFieldsL() can be called + message.iType=KUidMsvMessageEntry; + iDecodedSizeOfAllParts-=message.iSize; + } + + // Process any extended fields + ProcessExtendedFieldsL(aBodyStructure, *mime, message, attachmentFilename); + + // Now we're working on the type + if (message.iType==KUidMsvMessageEntry) + { + BuildEmbeddedMessageL(aBodyStructure, *mime, message, aPath, aAttachments); + } + else + { + BuildNonMessageEntryL(aParent, *mime, message, aAttachments, aRelatedAttachments); + } + CleanupStack::PopAndDestroy(mime); + __LOG_FORMAT((iSession->LogId(), " BuildContentEntryL done: created id %x, attachments so far %d", message.Id(), aAttachments)); + } + +/** +Sets flags in the TMsvEmailEntry according to the subtype for TEXT/xxx mime parts. + +@param aBodyStructure bodystructure component being processed +@param aMime (partially) parsed mime information +@param aMessage the email entry under construction +@param aIsMHTML Flag indicating that the message has been identified as MHTML +*/ +void CImapOpFetchBody::ProcessTextSubtypeL(CImapBodyStructure* aBodyStructure, CImMimeHeader& aMime, TMsvEmailEntry& aMessage, TBool& aIsMHTML) + { + // text/rtf? + if (aMime.ContentSubType().CompareF(KMIME_RTF)==0) + { + if (!(aBodyStructure->ExtDispositionName().CompareF(KMIME_ATTACHMENT)==0)) + { + aMessage.iType=KUidMsvEmailRtfEntry; + } + } + // text/html? + else if (aMime.ContentSubType().CompareF(KMIME_HTML)==0) + { + // If this is not an attachment (ie disposition field + // is not "attachment") then this is a MHTML Message. + if (!(aBodyStructure->ExtDispositionName().CompareF(KMIME_ATTACHMENT)==0)) + { + aMessage.iType=KUidMsvEmailHtmlEntry; + aIsMHTML=ETrue; + } + } + // text/x-vcard? + else if (aMime.ContentSubType().CompareF(KMIME_XVCARD)==0) + { + // Set vCard flag in message + aMessage.SetVCard(ETrue); + } + // text/x-vcalendar + else if (aMime.ContentSubType().CompareF(KMIME_VCALENDAR)==0) + { + // Set vCalendar flag in message + aMessage.SetVCalendar(ETrue); + iIsVCalendar = ETrue; + } + // text/calendar + else if (aMime.ContentSubType().CompareF(KMIME_ICALENDAR)==0) + { + // Set iCalendar flag in message + aMessage.SetICalendar(ETrue); + iIsICalendar = ETrue; + } + else + aMessage.iType=KUidMsvEmailTextEntry; + } + + + +/** +Populates the parameter lists of the CImMimeHeader object + +@param aParamList parameter list, retrieved from the bodystructure +@param aMime mime header object to be populated +@param aMessage the email entry under construction +@param aAttachmentFilename updated with a file name, if one is found +*/ +void CImapOpFetchBody::ProcessParameterListL(const CImapBodyStructure::RAttributeValuePairList& aParamList, CImMimeHeader& aMime, TMsvEmailEntry& aMessage, TFileName& aAttachmentFilename) + { + TUint charsetId = KUidMsvCharsetNone; + + TInt paramCount = aParamList.Count(); + if (paramCount > 0) + { + __LOG_TEXT(iSession->LogId(), " Parameter list:"); + + for(TInt i=0;iLogId(), " %S %S", ¶m, &value)); + + aMime.ContentTypeParams().AppendL(param); + aMime.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)) + { + __LOG_TEXT(iSession->LogId(), " Attachment filename found, therefore this is an attachment"); + + FindFilenameDecodeL(aMime,aAttachmentFilename); + StripIllegalCharactersFromFileName(aAttachmentFilename); + aMessage.iDetails.Set(aAttachmentFilename); + + // If embedded message do not save as an attachment + if (aMessage.iType!=KUidMsvMessageEntry) + { + aMessage.iType=KUidMsvAttachmentEntry; + } + } + else if (param.CompareF(KImcvCharset) == 0) + { + // We have found a charset parameter tuple. Convert the value to a + // charset ID for storage in the MIME headers. + if (value.Length() > 0) + { + charsetId = iCharConv->GetMimeCharsetUidL(value); + } + } + } + } + + aMime.SetMimeCharset(charsetId); + } + +/** +Populates extended header fields in the CImMimeHeader object + +@param aBodyStructure bodystructure object returned by the imap session +@param aMime mime header object to be populated +@param aMessage the email entry under construction +@param aAttachmentFilename updated with a file name, if one is found +*/ +void CImapOpFetchBody::ProcessExtendedFieldsL(CImapBodyStructure* aBodyStructure, CImMimeHeader& aMime, TMsvEmailEntry& aMessage, TFileName& aAttachmentFilename) + { + // Add the diposition name as the first pair of parameters. + TPtrC8 dispositionName = aBodyStructure->ExtDispositionName(); + if (dispositionName.Size() != 0) + { + __LOG_FORMAT((iSession->LogId(), " adding disp name: %S", &dispositionName)); + aMime.ContentDispositionParams().AppendL(dispositionName); + aMime.ContentDispositionParams().AppendL(KNullDesC8); + } + + // Now add the actual diposition parameters as name-value pairs + const CImapBodyStructure::RAttributeValuePairList& dispParams = aBodyStructure->ExtDispositionParameterList(); + TInt dispParamsCount = dispParams.Count(); // Note that this will be 0 if there is no disposition available + + for (TInt i=0; i < dispParamsCount; ++i) + { + __LOG_FORMAT((iSession->LogId(), " disp: %S %S", &dispParams[i].iAttribute, &dispParams[i].iValue)); + + aMime.ContentDispositionParams().AppendL(dispParams[i].iAttribute); + aMime.ContentDispositionParams().AppendL(dispParams[i].iValue); + + // Filename? If so, force this as an attachment + if ((dispParams[i].iAttribute.CompareF(KMIME_FILENAME)==0) + || (dispParams[i].iAttribute.CompareF(KMIME_FILENAME_RFC2231)==0)) + { + __LOG_TEXT(iSession->LogId(), " Attachment filename found in extended fields."); + FindFilenameDecodeL(aMime,aAttachmentFilename); + StripIllegalCharactersFromFileName(aAttachmentFilename); + aMessage.iDetails.Set(aAttachmentFilename); + + // If embedded message do not save as an attachment + if (aMessage.iType!=KUidMsvMessageEntry) + { + aMessage.iType=KUidMsvAttachmentEntry; + } + } + + } // end for + } + + + + +/** +Performs final parsing and construction of an embedded MESSAGE/RFC822 message + +As an MESSAGE/RFC822 part, the structure will contain envelope info. This is +parsed via ProcessEnvelopeL to construct a CImHeader object for the embedded +message. This is streamed to file along with the mime header information for +the message part. + +The CImapBodyStructure referred to via aBodyStructure->iMultiParts is the body +structure of the embedded message. This is an entire message-within-a-message +and so gets treated as a whole new mail. + +@param aBodyStructure Parsed bodystructure data for the current MESSAGE/RFC822 part +@param aMime mime header object to be populated +@param aMessage the email entry under construction +@param aPath The IMAP relative path for message parts. +@param aAttachments Counter for attachments. +*/ +void CImapOpFetchBody::BuildEmbeddedMessageL(CImapBodyStructure* aBodyStructure, CImMimeHeader& aMime, TMsvEmailEntry& aMessage, const TDesC8& aPath, TInt& aAttachments) + { + // Prepare a CImHeader object for the message, + // update flags as appropriate on the message entry. + CImHeader *messageheader=CImHeader::NewLC(); + ProcessEnvelopeL(messageheader, aMessage, aBodyStructure->GetRfc822EnvelopeStructureL()); + + // Create message + User::LeaveIfError(iServerEntry.CreateEntryBulk(aMessage)); // bulk op completed at the end of ProcessBodyStructureL() + SetEntryL(aMessage.Id()); + + // Store CImHeader and CImMimeHeader for the MESSAGE/RFC822 + StoreHeadersL(messageheader, &aMime); + CleanupStack::PopAndDestroy(messageheader); + +#if SET_RELATED_ID + // Set message's iRelatedId to messageId + TMsvEntry entryToChange(iServerEntry.Entry()); + entryToChange.iRelatedId=entryToChange.Id(); + ChangeEntryBulkL(entryToChange); // bulk op completed at the end of ProcessBodyStructureL() +#endif + // Process the bodystructure for this embedded message... + TInt attachments=0; + TInt relatedAttachments; + TBool isMHTML=EFalse; + + CImapBodyStructure* bodystructure = aBodyStructure->EmbeddedBodyStructureList()[0]; + BuildTreeL(aMessage.Id(), bodystructure, aPath, aMessage.Id(), attachments, isMHTML, relatedAttachments); + __LOG_FORMAT((iSession->LogId(), " Built embedded message id %x attachments %d MHTML %d", aMessage.Id(), attachments, isMHTML)); + + // Save attachment and MHTML flags + if (attachments>0 || isMHTML) + { + SetEntryL(aMessage.Id()); + TMsvEmailEntry thisMessage = iServerEntry.Entry(); + + if (attachments>0) + { + thisMessage.SetAttachment(ETrue); + } + + if (isMHTML) + { + thisMessage.SetMHTMLEmail(ETrue); + } + + User::LeaveIfError(iServerEntry.ChangeEntryBulk(thisMessage)); + } + + // embedded messages are counted as attachments + ++aAttachments; + } + + + + +/** +Performs final processing and construction of non-MESSAGE/RFC822 parts. +Stores the mime header information for the message part. + +This method is called for all message parts that are not multi-part and +not message/rfc822. Thus, each part for which this method is called is +eligible for fetch. This method calls AddFilterFetchItem to build up +the array of parts to fetch. + +@param aParent TMsvId of the parent of the current part. +@param aMime mime header object to be populated +@param aMessage the email entry under construction +@param aAttachments Counter for attachments. +@param aRelatedAttachments Counter for related attachments. +*/ +void CImapOpFetchBody::BuildNonMessageEntryL(const TMsvId& aParent, CImMimeHeader& aMime, TMsvEmailEntry& aMessage, TInt& aAttachments, TInt& aRelatedAttachments) + { + // Some other type of entry... + SetEntryL(aParent); + + // save parent folder type + TImEmailFolderType parentFolderType = ((TMsvEmailEntry)iServerEntry.Entry()).MessageFolderType(); + + // set attachment and HTML flags on item + if (aMessage.iType==KUidMsvAttachmentEntry) + { + aMessage.SetAttachment(ETrue); + } + + if (aMessage.iType==KUidMsvEmailHtmlEntry) + { + aMessage.SetMHTMLEmail(ETrue); + } + + // ensure there is a filename if it is a non-text item + if (aMessage.iType!=KUidMsvEmailTextEntry && aMessage.iDetails.Length() == 0) + { + // use iAttachmentName for temporary buffer + GetDefaultFilenameL(iAttachmentName, aMessage, &aMime); + aMessage.iDetails.Set(iAttachmentName); + } + + // Create the Entry + User::LeaveIfError(iServerEntry.CreateEntryBulk(aMessage)); // bulk op completed at the end of ProcessBodyStructureL() + SetEntryL(aMessage.Id()); + + __LOG_FORMAT((iSession->LogId(), " Created attachment id %x as child of %x - type %d", aMessage.Id(), aParent, parentFolderType)); + +#if SET_RELATED_ID + // Set message's iRelatedId to messageId + TMsvEntry entryToChange(iServerEntry.Entry()); + entryToChange.iRelatedId=entryToChange.Id(); + ChangeEntryBulkL(entryToChange); // bulk op completed at the end of ProcessBodyStructureL() +#endif + + // Stream the MIME info out into the newly created attachment entry. + StoreHeadersL(NULL, &aMime); + + // 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(aMessage.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 (aMessage.iType==KUidMsvAttachmentEntry && parentFolderType==EFolderTypeRelated) + { + ++aRelatedAttachments; + } + + // Apply filters and add to fetch list + if (iFetchPartialMail) + { + AddFilterFetchItemL(aMime, aMessage, iGetPartialMailInfo); + } + else + { + AddFilterFetchItemL(aMime, aMessage, iGetPartialMailInfo.iGetMailBodyParts); + } + } + + + +/** +Issues a request to fetch the next item in the array of parts to fetch. +@return EFalse if no parts remaining in the array. +*/ +TBool CImapOpFetchBody::FetchPartL() + { + // Anything to do? + if (iFetchList.Count() <= 0) + { + return EFalse; + } + else + { + // Create a new body response store to hold any response information + // from the fetch command. + delete iFetchBodyResponse; + iFetchBodyResponse = CImapFetchBodyResponse::NewL(); + + // If part to fetch was of type attachment, this must be registered + // with the CAF framework. This is indicated in the FetchBodyInfo by + // a non-NULL CAF pointer. + if ((iFetchList[0])->Caf()!=NULL) + { + RegisterWithCafL(*(iFetchList[0])); + } + + // UpdatingSeenFlags == ETrue means that we want to set the seen flag explicitly, + // using CImapSession::StoreL(), so peek should be ETrue too. + // UpdatingSeenFlags == EFalse meanse that we want the server to set the seen flag + // implicitly when the message is downloaded, + // - i.e. peek should be EFalse too. + TBool peek = iImapSettings.UpdatingSeenFlags(); + + iSession->FetchBodyL(iStatus, iMessageUid, peek, *(iFetchList[0]), *iFetchBodyResponse); + } + return ETrue; + } + + +/** +Registers the part with the CAF framework. + +This method is called for all parts of type Application that are +to be fetched. CAF registration requires concatenated content-type +and subtype. The type and subtype have previously been received +and stored in the CImMimeHeader for the part. + +If the part is not registered as a result of the call to +CImCaf::RegisterL(), the FetchBodyInfo object is updated to show +this. + +@param fetchInfo information about the part to be fetched. +*/ +void CImapOpFetchBody::RegisterWithCafL(CFetchBodyInfo& fetchInfo) + { + // load the mime header for the entry. + // Information will be required for parts to be fetched. + SetEntryL(fetchInfo.PartId()); + CImMimeHeader* mimeHeader = CImMimeHeader::NewLC(); + CMsvStore* store = iServerEntry.ReadStoreL(); + CleanupStack::PushL(store); + mimeHeader->RestoreL(*store); + // finished with the read store + CleanupStack::PopAndDestroy(store); + + // Create buffer for concatenating. + 1 creates space for '/' + HBufC8* buf = HBufC8::NewLC(mimeHeader->ContentSubType().Length() + mimeHeader->ContentType().Length() + 1); + TPtr8 ptr(buf->Des()); + ptr.Copy(mimeHeader->ContentType()); + ptr.Append(KImcvForwardSlash); + ptr.Append(mimeHeader->ContentSubType()); + + // Attempt to register with CAF - this may not succeed. + iCaf->RegisterL(ptr); + CleanupStack::PopAndDestroy(buf); + CleanupStack::PopAndDestroy(mimeHeader); + + // Clear the iCaf pointer in fetchInfo if CAF is not interested. + if(!iCaf->Registered()) + { + fetchInfo.ResetCaf(); + } + } + + +/** +Populates a CImHeader object given a CImapEnvelope abject. +Updates the passed TMsvEntry with appropriate information (date, from and subject) + +@param aHeader Header information object to populate +@param aServerEntry Server entry id associated with the envelope object +@param aImapEnvelope Parsed IMAP Envelope object. +*/ +void CImapOpFetchBody::ProcessEnvelopeL(CImHeader* aHeader, TMsvEntry& aEntry, CImapEnvelope& aImapEnvelope) + { + __LOG_FORMAT((iSession->LogId(), " Processing envelope data, id=%x", aEntry.Id())); + + TPtrC8 tptr; + // Parse date information + tptr.Set(aImapEnvelope.EnvDate()); + TImRfc822DateField date; + date.ParseDateField(tptr, aEntry.iDate); + + // Subject in CImHeader (TMsvEntry is later on after post-processing) + if (aImapEnvelope.EnvSubject().CompareF(KIMAP_NIL)!=0) + { + aHeader->SetSubjectL(aImapEnvelope.EnvSubject()); + } + + // From information: both in CImHeader and TMsvEntry + if (aImapEnvelope.EnvFrom().Count() != 0) + { + __LOG_TEXT(iSession->LogId(), " Processing 'From' information"); + HBufC16* fromAddr = aImapEnvelope.EnvFrom()[0].CreateAddressStringL(); + CleanupStack::PushL(fromAddr); + aHeader->SetFromL(*fromAddr); + CleanupStack::PopAndDestroy(fromAddr); + } + else + { + // No From information. Set blank + aHeader->SetFromL(KNullDesC); + } + + // Sender information is ignored. + + // ReplyTo information + if (aImapEnvelope.EnvReplyTo().Count()!=0) + { + __LOG_TEXT(iSession->LogId(), " Processing 'ReplyTo' information"); + HBufC16* replyToAddr = aImapEnvelope.EnvFrom()[0].CreateAddressStringL(); + CleanupStack::PushL(replyToAddr); + aHeader->SetReplyToL(*replyToAddr); + CleanupStack::PopAndDestroy(replyToAddr); + } + else + { + // No replyto. Use From info + aHeader->SetReplyToL(aHeader->From()); + } + + // To information + ProcessAddressListL(aHeader->ToRecipients(), aImapEnvelope.EnvTo()); + + // CC list + ProcessAddressListL(aHeader->CcRecipients(),aImapEnvelope.EnvCc()); + + // BCC list + ProcessAddressListL(aHeader->BccRecipients(),aImapEnvelope.EnvBcc()); + + // Message-Id + aHeader->SetImMsgIdL(StripAngledBrackets(aImapEnvelope.EnvMessageId())); + + // 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()); + + __LOG_TEXT(iSession->LogId(), " Finished processing envelope information"); + } + + +/** +Retrieves the default filename and appends the appropriate extension + +@param aWhere Destination descriptor array +@param aAddressArray Source address array +*/ +void CImapOpFetchBody::GetDefaultFilenameL(TDes& aName, const TMsvEmailEntry& aMessage, const CImMimeHeader* mime) + { + aName = iImapSettings.DefaultAttachmentName(); + + // Add on appropriate extension + if (aMessage.iType == KUidMsvEmailTextEntry) + { + aName.Append(KTextExtension); + } + else if(aMessage.iType == KUidMsvEmailRtfEntry) + { + aName.Append(KRtfExtension); + } + 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) ) + { + // Copy the 8-bit ContentSubType into the 16-bit buf + RBuf buf; + buf.CleanupClosePushL(); + buf.CreateL(mime->ContentSubType().Length()); + + buf.Copy(mime->ContentSubType()); // 16-bit <== 8-bit + + aName.Append(KImcvFullStop); + aName.Append(buf); + + CleanupStack::PopAndDestroy(&buf); + } + } + } + +/** +Copies addresses from an array of addresses into a target descriptor array. +The source array is of the form returned by the CImapEnvelope class + +@param aWhere Destination descriptor array +@param aAddressArray Source address array. +*/ +void CImapOpFetchBody::ProcessAddressListL(CDesCArray& aWhere, const CImapEnvelope::RArrayTAddress& aAddressArray) + { + for(TInt i=0;i < aAddressArray.Count();++i) + { + HBufC16* address = aAddressArray[i].CreateAddressStringL(); + CleanupStack::PushL(address); + aWhere.AppendL(address->Des()); + CleanupStack::PopAndDestroy(address); + } + } + +/** +Copies addresses from a Source descriptor containing one or more addresses +into a target descriptor array of individual addresses + +@param aWhere Destination descriptor array +@param aAddresses Source descriptor containing one or more addresses. +*/ +void CImapOpFetchBody::ProcessAddressListL(CDesCArray& aWhere, const TDesC8& 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 + HBufC16* pBuf16 = HBufC16::NewLC(pBuf->Des().Length()); + pBuf16->Des().Copy(pBuf->Des()); + aWhere.AppendL( (HBufC16&) *pBuf16 ); + CleanupStack::PopAndDestroy(pBuf16); // pBuf16 + 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) + { + HBufC16* pBuf16 = HBufC16::NewLC(recipientLength); + pBuf16->Des().Copy(*pBuf); + aWhere.AppendL(*pBuf16); + CleanupStack::PopAndDestroy(pBuf16); // pBuf16 + } + } + CleanupStack::PopAndDestroy(pBuf); // pBuf + } + + +/** +Searches mime information for a filename for an attachment. +If not found, the default attachment name is used. +If found but encoded according to RFC2231, the default attachment name is used + with the appropriate file extension appended. +Otherwise the found filename is QP decoded and cropped to be within max filename size. + +@param aMimeInfo The mime header associated with the attachment +@param aFileName Returns the filename, if found. +@return KErrNotFound if no filename found + KErrRFC2231Encoded if RFC2231 encoded filename found + KErrNone otherwise +*/ +TInt CImapOpFetchBody::FindFilenameL(const CImMimeHeader& aMimeInfo, TPtrC8& aFilename) + { + // Look in content-type list + const CDesC8Array& ctype=aMimeInfo.ContentTypeParams(); + + __LOG_FORMAT((iSession->LogId(), "CImapOpFetchBody::FindFilename: Checking %d entries in content-type list", ctype.Count())); + + TInt tuple=0; + TInt count = ctype.Count(); + while (tuple+1 < count) // we step in pairs, and use tuple and tuple+1, so tuple+1 needs to be less than count in order + { + +#ifdef PRINTING + TPtrC8 t1 = ctype[tuple]; + TPtrC8 t2 = (tuple+1 < count) ? ctype[tuple+1] : KNullDesC8(); + __LOG_FORMAT((iSession->LogId()," [%S] [%S]", &t1, &t2)); +#endif + + // Look for "name xxx" + if (ctype[tuple].CompareF(KMIME_NAME)==0) + { + // Got it: report that we found it + aFilename.Set(ctype[tuple+1]); + + // Check whether aFilename contains a meaningful file name + RBuf8 buf; + buf.CleanupClosePushL(); + buf.CreateL(aFilename); + buf.Trim(); + if(buf.Length()==0) + { + CleanupStack::PopAndDestroy(&buf); + return KErrNotFound; + } + + CleanupStack::PopAndDestroy(&buf); + 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 + const CDesC8Array& cdisp=aMimeInfo.ContentDispositionParams(); + __LOG_FORMAT((iSession->LogId(), "CImapOpFetchBody::FindFilename: Checking %d entries in disposition list", cdisp.Count())); + + tuple=0; + count = cdisp.Count(); + while(tuple+1 < count) // we step in pairs, and use tuple and tuple+1, so tuple+1 needs to be less than count in order + { + +#ifdef PRINTING + TPtrC8 t1 = cdisp[tuple]; + TPtrC8 t2 = (tuple+1 < count) ? cdisp[tuple+1] : KNullDesC8(); + __LOG_FORMAT((iSession->LogId(),"disp [%S] [%S]", &t1, &t2)); +#endif + + // Look for "filename xxx" + if (cdisp[tuple].CompareF(KMIME_FILENAME)==0) + { + // Got it: report that we found it + aFilename.Set(cdisp[tuple+1]); + return KErrNone; + } + else if (cdisp[tuple].CompareF(KMIME_FILENAME_RFC2231)==0) + { + // Got it: report that we found it + aFilename.Set(cdisp[tuple+1]); + return KErrRFC2231Encoded; + } + + tuple+=2; + } + + // Didn't find it + return KErrNotFound; + } + + +/** +Builds attachment filename, using data from the mime header information, if available. +If not found, the default attachment name is used. +If found but encoded according to RFC2231, the default attachment name is used + with the appropriate file extension appended. +Otherwise the found filename is QP decoded and cropped to be within max filename size. + +@param aMimeInfo The mime header associated with the attachment +@return aFileName The decoded filename for the attachment. +*/ +void CImapOpFetchBody::FindFilenameDecodeL(const CImMimeHeader& aMimeInfo, TFileName& aFileName) + { + // Make an attachment name + aFileName.Zero(); + + TPtrC8 origFileName; + + // Look for filename in Content-Type list + TInt err = FindFilenameL(aMimeInfo, origFileName); + if (KErrNotFound == err) + { + // Fall back to simple "attachment" (language specific) + aFileName=iImapSettings.DefaultAttachmentName(); + } + 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=iImapSettings.DefaultAttachmentName(); + 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; + } // end while ((dotPos != 0) && (!dotFound)) + } + 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); + + __LOG_FORMAT((iSession->LogId(), "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) + { + TInt prefixLen = 0; + // 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); + } + } + + +/** +Replaces illegal characters in a filename with a default +@param aName The filename to check. +*/ +void CImapOpFetchBody::StripIllegalCharactersFromFileName(TDes16& aName) + { + TInt length=aName.Length(); + for(TInt index=0; index < length; ++index) + { + TUint charr=(TUint)aName[index]; + if( charr == '*' || charr == '\\' || charr == '<' || charr == '>' || + charr == ':' || charr == '"' || charr == '/' || charr == '|' || + charr == '?' || charr < ' ') + { + aName[index] = KImcvDefaultChar; + } + } + } + + +/** +Synchronously stores IM Headers and Mime Headers +@param aImHeader an IM header to store. May be NULL. +@param aMimeHeader a Mime part header to store. May be NULL. +*/ +void CImapOpFetchBody::StoreHeadersL(CImHeader* aImHeader, CImMimeHeader* aMimeHeader) + { + CMsvStore* entryStore=iServerEntry.EditStoreL(); + CleanupStack::PushL(entryStore); + if (aImHeader != NULL) + { + __LOG_FORMAT((iSession->LogId(), " Streaming ImHeader info into id %x", iServerEntry.Entry().Id())); + aImHeader->StoreL(*entryStore); + } + if (aMimeHeader != NULL) + { + __LOG_FORMAT((iSession->LogId(), " Streaming MIME info into id %x", iServerEntry.Entry().Id())); + aMimeHeader->StoreL(*entryStore); + } + entryStore->CommitL(); + CleanupStack::PopAndDestroy(entryStore); + } + +/** +Check if the partial download options mean that we will download a text plain +part, but the text HTML alternative will not be downloaded. In this case, we +need to make sure the footer message is displayed on the text plain part to +show that the text HTML part is not being downloaded. +*/ +void CImapOpFetchBody::UpdateBodyTextRemainingSizeForHtml() + { + if ((iHtmlEntryPart == 0) && (iHtmlEntrySize > 0) && (iFetchList.Count() > 0)) + { + for (TInt part(0); part < iFetchList.Count(); ++part) + { + if (iFetchList[part]->IsText()) + { + iFetchList[part]->SetBodyPartRemainingSize(iFetchList[part]->BodyPartRemainingSize() + iHtmlEntrySize); + return; + } + } + } + } + +/** +Static method that strips the given string of any enclosing angled brackets < > +@param aString the descriptor that will have its brackets removed +@return a descriptor that points into aString, excluding any enclosing angled brackets +*/ +TPtrC8 CImapOpFetchBody::StripAngledBrackets(const TDesC8& aString) +// static method + { + TInt strLen = aString.Length(); + + if (strLen>2 && aString[0]==KImcvLeftChevron && aString[strLen-1]==KImcvRightChevron) + { + return aString.Mid(1, strLen-2); + } + + // If the string was not enclosed with angled brackets then just return the original string. + return aString; + } + +/** +Creates an empty attachment to store the attachment infomation, for the case +where the attachment is not downloaded due to download limits. +@param aMsvEmailEntry The email entry the attachment is associate with. +*/ +void CImapOpFetchBody::CreateAttachmentInfoL(const TMsvEntry& aMsvEmailEntry) + { + __LOG_TEXT(iSession->LogId(), "Creating zero length attachment"); + iMailStore.CreateAttachmentInfoL(aMsvEmailEntry.Id()); + } + + +/** +Called when a requsted part has been sucessfully fetched. +Updates flags on the TMsvEntry indicating that it is complete or +partially fetched as appropriate. + +Note - deletes the 0th entry from the array of parts to fetch. +*/ +void CImapOpFetchBody::FetchPartCompleteL() + { + CFetchBodyInfo* fetchInfo = iFetchList[0]; + CleanupStack::PushL(fetchInfo); + iFetchList.Remove(0); + SetEntryL(fetchInfo->PartId()); + TMsvEmailEntry message = iServerEntry.Entry(); + message.SetComplete(ETrue); + + // Store mime information returned from N.MIME + + CImapMimeHeaderFields* headerFields = iFetchBodyResponse->HeaderFields(); + + if (headerFields != NULL) + { + CImMimeHeader* mimeHeader = CImMimeHeader::NewLC(); + CMsvStore* store = iServerEntry.ReadStoreL(); + CleanupStack::PushL(store); + mimeHeader->RestoreL(*store); + CleanupStack::PopAndDestroy(store); + + if (headerFields->FieldExists(CImapMimeHeaderFields::EImapContentBase)) + { + mimeHeader->SetContentBaseL(headerFields->FieldValue(CImapMimeHeaderFields::EImapContentBase)); + } + + if (headerFields->FieldExists(CImapMimeHeaderFields::EImapContentLocation)) + { + const TDesC8& fieldValue = headerFields->FieldValue(CImapMimeHeaderFields::EImapContentLocation); + + HBufC* decodedBuffer = HBufC::NewLC(fieldValue.Length()); + TPtr decodedPtr(decodedBuffer->Des()); + + iHeaderConverter->DecodeHeaderFieldL(fieldValue, decodedPtr); + mimeHeader->SetContentLocationL(decodedPtr); + CleanupStack::PopAndDestroy(decodedBuffer); + } + + StoreHeadersL(NULL, mimeHeader); + CleanupStack::PopAndDestroy(mimeHeader); + } + + TBool hasBodyText = message.iType == KUidMsvEmailTextEntry || message.iType == KUidMsvEmailHtmlEntry; + TBool partiallyDownloaded = EFalse; + + if(iFetchPartialMail && + fetchInfo->BodyPartRemainingSize() != 0 && + message.iType == KUidMsvEmailTextEntry) + { + message.SetPartialDownloaded(ETrue); + partiallyDownloaded = ETrue; + } + else + { + message.SetPartialDownloaded(EFalse); + } + + if (hasBodyText) + { + message.SetBodyTextComplete(ETrue); + } + + User::LeaveIfError(iServerEntry.ChangeEntry(message)); + + + // Checking the flags + SetEntryL(iMessageMsvId); + TMsvEmailEntry messageentry = iServerEntry.Entry(); + CImapFolder* folder = iSyncManager.GetFolderL(messageentry.Parent()); + + if(folder!=NULL) + { + TMessageFlagInfo& flaginfo = iFetchBodyResponse->MessageFlagInfo(); + + // Flags from the fetch message + TBool seen = flaginfo.QueryFlag(TMessageFlagInfo::ESeen); + TBool answered = flaginfo.QueryFlag(TMessageFlagInfo::EAnswered); + TBool flagged = flaginfo.QueryFlag(TMessageFlagInfo::EFlagged); + TBool deleted = flaginfo.QueryFlag(TMessageFlagInfo::EDeleted); + TBool draft = flaginfo.QueryFlag(TMessageFlagInfo::EDraft); + TBool recent = flaginfo.QueryFlag(TMessageFlagInfo::ERecent); + + // Flags in the local message + TBool oUnread, oSeen, oAnswered, oFlagged, oDeleted, oDraft, oRecent; + messageentry.GetIMAP4Flags(oUnread, oSeen, oAnswered, oFlagged, oDeleted, oDraft, oRecent); + + // Are we configured to update the \seen flag on the server? + if (iImapSettings.UpdatingSeenFlags()) + { + // Make a note to update the servers \Seen flag if CHANGED on the client + // and different to the servers version + if (BoolsAreEqual(messageentry.Unread(), seen) && BoolsAreEqual(oSeen, seen)) + { + // The read flag has changed, but not on the server. So this must be a local change. + if (messageentry.Unread()) + { + folder->AppendClearSeenL(messageentry.UID()); + } + else + { + folder->AppendSetSeenL(messageentry.UID()); + } + } + } + + if (BoolsAreNotEqual(oSeen, seen) || + BoolsAreNotEqual(oAnswered, answered) || + BoolsAreNotEqual(oFlagged, flagged) || + BoolsAreNotEqual(oDeleted, deleted) || + BoolsAreNotEqual(oDraft, draft) || + BoolsAreNotEqual(oRecent, recent) ) + { + messageentry.SetIMAP4Flags(oUnread, seen, (answered || oAnswered), flagged, deleted, draft, recent); + } + + // update context + SetEntryL(iMessageMsvId); + User::LeaveIfError(iServerEntry.ChangeEntry(messageentry)); + } + + + // Finished with the fetch body response now + delete iFetchBodyResponse; + iFetchBodyResponse = NULL; + + PropagateCompleteFlagL(fetchInfo->PartId(), hasBodyText, partiallyDownloaded); + + // increment progress counters + ++iPartsDone; + iBytesDone+=fetchInfo->BytesFetched(); + + // fetch of part complete - delete the info + CleanupStack::PopAndDestroy(fetchInfo); + } + + + +/** +Propagates flags indicating message completeness up the tree structure for the +fetched message part. Popagates the Complete and Partial-Fetch status. + +@param aId ID of the message part that has just been fetched. +@param aDoBodyText indicates that the part is text or contains text parts +@param aPartialFetched indicates that the part is partially fetched. +*/ +void CImapOpFetchBody::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 = iServerEntry.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 (iServerEntry.Entry().iType == KUidMsvServiceEntry) + { + return; + } + + User::LeaveIfError(iServerEntry.GetChildren(*selection)); + + TBool complete=ETrue; + TBool bodyTextComplete=ETrue; + TBool partiallyFetched=EFalse; + + TBool related=((TMsvEmailEntry) iServerEntry.Entry()).MessageFolderType()==EFolderTypeRelated ? + ETrue:EFalse; + for (TInt i=0; i < selection->Count(); i++) + { + SetEntryL((*selection)[i]); + if (!iServerEntry.Entry().Complete()) + { + complete=EFalse; + if((iServerEntry.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 ((iServerEntry.Entry().iType == KUidMsvEmailTextEntry) + || (iServerEntry.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 = iServerEntry.Entry(); + + // check whether parent is complete, this will prevent us + // checking all the messages in a real folder as they will all + // be initialised to Complete + if (!entry.Complete()) + { + if (complete || ((iServerEntry.Entry().iType==KUidMsvFolderEntry) && aPartialFetched)) + { + entry.SetComplete(ETrue); + } + + if(aPartialFetched) + { + if((iServerEntry.Entry().iType != KUidMsvAttachmentEntry) && + (iServerEntry.Entry().iType != KUidMsvEmailExternalBodyEntry)) + { + entry.SetPartialDownloaded(ETrue); + } + partiallyFetched = ETrue; + } + else + { + entry.SetPartialDownloaded(EFalse); + partiallyFetched = EFalse; + } + + entry.SetBodyTextComplete(ETrue); + User::LeaveIfError(iServerEntry.ChangeEntry(entry)); + + PropagateCompleteFlagL(parent, related|aDoBodyText,partiallyFetched); + } + else if (entry.PartialDownloaded()) + { + entry.SetPartialDownloaded(EFalse); + User::LeaveIfError(iServerEntry.ChangeEntry(entry)); + PropagateCompleteFlagL(parent, related|aDoBodyText,partiallyFetched); + } + } + } + + +/** +Populates the passed progress object with progress information on the +current message fetch operation. +@param aGenericProgressn - progress information object to be populated. +*/ +void CImapOpFetchBody::Progress(TImap4GenericProgress& aGenericProgress) + { + aGenericProgress.iPartsToDo = iPartsToDo; + aGenericProgress.iPartsDone = iPartsDone; + aGenericProgress.iBytesToDo = iBytesToDo; + + // iBytesDone is the byte count of completed parts + // - need increase this by the parts done on the current part, + // if a fetch is outstanding. + TInt32 tempBytesDone = iBytesDone; + if (( iCurrentStep == EFetchFirstPart || iCurrentStep == EFetchNext) + && iFetchList.Count()>0) + { + tempBytesDone += (iFetchList[0])->BytesFetched(); + } + aGenericProgress.iBytesDone = tempBytesDone; + } + + +/** +Handles server error responses according to current step + +@return TInt error code for completion (if error fatal) +*/ +TInt CImapOpFetchBody::ProcessServerError() + { + switch(iCurrentStep) + { + case EGetBodyStructure: + case EProcessBodyStructure: + case EBuildFetchList: + case EFetchFirstPart: + case EFetchNext: + default: + return (iStatus.Int()); + } + // return KErrNone; + } +