diff -r 000000000000 -r 72b543305e3a email/pop3andsmtpmtm/smtpservermtm/src/IMSMFILE.CPP --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/email/pop3andsmtpmtm/smtpservermtm/src/IMSMFILE.CPP Thu Dec 17 08:44:11 2009 +0200 @@ -0,0 +1,533 @@ +// 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: +// File Mailer for Internet SMTP Transport Driver +// +// + +#include // CMsgActive +#include // TMsvEntry, CMsvEntrySelection +#include // CMsvServerEntry +#include // CMsvStore +#include // TImMessageField +#include // CImHeader +#include // CImSendMessage + +#include "SMTS.H" +#include "IMSM.H" // KSmtpFilePriority +#include "IMSMSEND.H" // CImSmtpFile +#include "SMTSUTIL.H" // forward declarations for utility fns + +const TInt KMsvSuspendCheckGranuality=15; + +//As per RFC-2821(Section: 4.5.3.2 Timeouts) Maximum time out for Data termination is 10 minutes. +const TInt KSmtpDataTerminationTimeout=600; + +CImSmtpFile* CImSmtpFile::NewL(CImTextServerSession& aSocket, + CMsvServerEntry& aServerEntry, + TTime& aTime, + TBuf8& aSmtpBuffer, + CSmtpSettings& aSettings,TBool aIsBccRcpt) + { + CImSmtpFile* self= new (ELeave) CImSmtpFile(aSocket,aSmtpBuffer,aServerEntry); + CleanupStack::PushL(self); + self->ConstructL(aTime,aSettings,aIsBccRcpt); + CleanupStack::Pop(); + return self; + } + + +CImSmtpFile::CImSmtpFile(CImTextServerSession& aSocket,TBuf8& aSmtpBuffer,CMsvServerEntry &aServerEntry) + : CMsgActive(KImSmtpFilePriority), + iSocket(aSocket), + iServerEntry(aServerEntry), + iSmtpBuffer(aSmtpBuffer) + + { + __DECLARE_NAME(_S("CImSmtpFile")); + } + +void CImSmtpFile::ConstructL(TTime& aTimeNow,CSmtpSettings& aSettings,TBool aIsBccRcpt) + + { + iSendCopyToSelf=aSettings.SendCopyToSelf(); + + iRecipientType = aIsBccRcpt? ERcptBcc : ERcptTo; + + // Create and Initialise the CImSendMessage object.. + iSendMessage = CImSendMessage::NewL(iServerEntry); + + // Create objects to store header and body form the message file + // Read contents of message file into containers + iHeader = CImHeader::NewLC(); // PushL(iHeader) + CleanupStack::Pop(); // iHeader + + iEntryId=iServerEntry.Entry().Id(); + + CMsvStore* store = iServerEntry.EditStoreL(); + CleanupStack::PushL(store); + + // The message store entry always uses UTC, so set it now because the call to + // GetHeaderFromStoreL updates the time on that entry. + aTimeNow.UniversalTime(); + GetHeaderFromStoreL(*store,iServerEntry, aTimeNow); // Read ImHeader object from message file + CleanupStack::PopAndDestroy();//store + + // Sets iBodyType (TImBodyConvAlgorithm). + SelectBodyEncodingTypeL(aSettings.BodyEncoding(), iHeader->BodyEncoding()); + + // Create the message to send. + // Note that when sending the message, we use the HomeTime as opposed to UniversalTime. + // This is because RFC822 mandates that the time in emails should be local time. + TTime homeTime; + homeTime.HomeTime(); + iSendMessage->InitialiseL(iEntryId,iBodyType, homeTime, aSettings.ServerAddress(), + aSettings.DefaultMsgCharSet().iUid, aSettings.SendCopyToSelf()); + + CActiveScheduler::Add(this); // Add SmtpFile to scheduler's queue + } + + +void CImSmtpFile::SetBytesToSend(TInt aNumBytes) + { + // Set the number of Bytes to send + iBytesToSend = aNumBytes; + } + +TInt CImSmtpFile::BytesToSend() const + { + return iBytesToSend; + } + +TInt CImSmtpFile::TotalMsgSizeL() +// Return the Size of this Message + { + TInt msgSize = 0; + if (iSendMessage) + { + // factor of 4/3 is here to attempt to take account of the increase in size that will happen + // when the message is encoded + msgSize = TInt((4.0/3)*(iSendMessage->HeaderSize() + iSendMessage->BodySizeL())); + } + + return msgSize; + } + + +void CImSmtpFile::GetHeaderFromStoreL(CMsvStore& aStore, CMsvServerEntry& aServerEntry, TTime& aTimeNow) + { + // Retrieve the CImHeader object + iHeader->RestoreL(aStore); + + TMsvEntry entry = aServerEntry.Entry(); + entry.iDate=aTimeNow; + entry.SetSendingState(KMsvSendStateSending); + entry.SetFailed(EFalse); + aServerEntry.ChangeEntry(entry); + } + + +void CImSmtpFile::StartL(TRequestStatus& aSessionStatus) + { + // Start sending the message file, by initialising the state machine + iCompleted=KErrNone; + iRecipientArray=NULL; + iState=EResettingSmtp; + + ChangeStateL(); // Start the state machine + Queue(aSessionStatus); + } + + +void CImSmtpFile::GetProgress(TImImailFileProgress& aFileProgress) + { + // Return info about how much of message has been sent so far + aFileProgress.iBytesToSend = iBytesToSend; + aFileProgress.iBytesSent = iBytesSent; + aFileProgress.iSessionState = (TSmtpSessionState)iState; + } + + +void CImSmtpFile::DoCancel() + { + // Cancel any pending socket call + iSocket.Cancel(); + CMsgActive::DoCancel(); // MUST be the last statement in DoCancel(); + } + + +TInt CImSmtpFile::State() + { + // return current state of state machine + return iState; + } + + +TBool CImSmtpFile::NextRecipientL() + { + // Return True if there is another recipient identified in imheader; + // ...if True, then "iRecipient" is set to point to next email address descriptor. + // + // This function scans through empty "To" and "Cc" fields + // if necessary to locate another recipient email address. + + //Precondition: 'Bcc' recipients are dealt with in CImSmtpSession + if(iRecipientType==ERcptBcc) + return EFalse; + + if (!iRecipientArray) + { + // Nextrecipient has been called for the 1st time, so get "To:" list from CImHeader + iRecipientArray = &iHeader->ToRecipients(); + if (iSendCopyToSelf==ESendCopyAsToRecipient) + { + if ( iHeader->ReceiptAddress().Length() ) + iRecipientArray->AppendL(iHeader->ReceiptAddress()); + else if ( iHeader->ReplyTo().Length() ) + iRecipientArray->AppendL(iHeader->ReplyTo()); + } + iRecipientIndex = 0; + iRecipientType = ERcptTo; + } + + while ((iRecipientIndex >= iRecipientArray->Count()) && (iRecipientType <= ERcptCc)) + { + // Find the next Email address (if it exists) + iRecipientArray = &iHeader->CcRecipients(); + if (iSendCopyToSelf==ESendCopyAsCcRecipient) + { + if ( iHeader->ReceiptAddress().Length() ) + iRecipientArray->AppendL(iHeader->ReceiptAddress()); + else if ( iHeader->ReplyTo().Length() ) + iRecipientArray->AppendL(iHeader->ReplyTo()); + } + iRecipientIndex=0; + iRecipientType++; + } + + if (iRecipientType <= ERcptCc) + { + iRecipient.Set((*iRecipientArray)[iRecipientIndex++]); + return ETrue; // Found an email address; iRecipient points to it now. + } + return EFalse; // iRecipientType>ERcptCc, so tell caller there are no more Email addresses left + } + + +void CImSmtpFile::DoRunL() + { + iCompleted=iStatus.Int(); // remember completion code - which is >= KErrNone + + if (iState!=ESendData && iCompleted==KErrNone) + { + TInt SmtpCode = ESmtpNoReturnCode; + TBool CommandAccepted = GetCurrentTextLine(); + if (CommandAccepted) + { + SmtpCode = SmtpResponseCode(iSmtpBuffer,iSmtpMultiLineResponse,iSmtpLastMultiLineResponse); // parse out SMTP code from text response + CommandAccepted = LastSmtpCommandAccepted(SmtpCode,SmtpFilePositiveResponse(iState)); // was response accepted by remote server? + } + + // "RSET", "DATA" and "." SMTP commands should never return a multi line response + __ASSERT_DEBUG(iState!=ESendData,gPanic(EImsmBodyTextNotHandledHere)); + + if ((!CommandAccepted) && (iCompleted==KErrNone)) + { + // Record an SMTP error only if no other error has been signalled. + iCompleted = SmtpFileError(SmtpCode); + } + } + + if ((iState==EEndData) || (iCompleted!=KErrNone)) + { + // I have completed last state, or an error has occurred... + // so don't requeue state machine + return; + } + + // Normal situation... choose the next state... + iState=SelectNextStateL(); + ChangeStateL(); + } + + +TInt CImSmtpFile::SmtpFilePositiveResponse(TInt aState) + { + // Utility function returns 1st digit of 3 digit response code which + // I expect the remote SMTP server to return if last command succeeded. + TInt ExpectedFirstDigit; + + if (aState==EBeginData) + { + ExpectedFirstDigit = 3; // SMTP server should repond to "DATA" command with code "324" + } + else + { + ExpectedFirstDigit = 2; // SMTP server should normally respond with code "250" + } + return ExpectedFirstDigit; + } + + +TInt CImSmtpFile::SelectNextStateL() + { + // Choose the next state for the state machine + TInt NextState; + + switch (iState) + { + case ERcptToSmtp: // is there another recipient address? + NextState = (NextRecipientL()) ? ERcptToSmtp : EBeginData; + break; + case ESendData: // is there any more data to send? + NextState = iMoreRfc822Data ? ESendData : EEndData; + break; + default: + NextState = iState+1; + break; + } + + return NextState; + } + + +void CImSmtpFile::ChangeStateL() + { + // + // State machine of the whole SMTP File mailer. + // + TImMessageField emailField; + + switch (iState) + { + case EResettingSmtp: + SendAndQueueRead(KSmtpResetCommand); + break; + + case EMailFromSmtp: + __ASSERT_DEBUG(iSmtpBuffer.Length(),gPanic(EImsmNoFromAddress)); + if (emailField.ValidInternetEmailAddress(iHeader->From())) + { + iSmtpBuffer = KSmtpMailFromCommand; + iSmtpBuffer.Append(emailField.GetValidInternetEmailAddressFromString(iHeader->From())); + iSmtpBuffer.Append(KSmtpCloseAngleBracket); + SendAndQueueRead(iSmtpBuffer); + } + else + { + // no Email address for return path, so refuse to send this message + RequestComplete(iStatus,0-KSmtpNoMailFromErr); + } + break; + + case ERcptToSmtp: + if(iRecipientType==ERcptBcc) + { + iRecipientArray = &iHeader->BccRecipients(); + iRecipient.Set((*iRecipientArray)[0]); // The should be only one Bcc recipient + } + else //'To' and 'Cc' recipients + { + if(!iRecipientArray) + NextRecipientL();// state was EMailFromSmtp, so initialise iRecipient + } + + + // BUG_FIX SW1-659.. check with new build.. + // Prepend the recipient address... into iSmtpBuffer and insert the command seq. + iSmtpBuffer.Copy(emailField.GetValidInternetEmailAddressFromString(iRecipient)); + iSmtpBuffer.Insert(0, KSmtpRcptToCommand); + iSmtpBuffer.Append(KSmtpCloseAngleBracket); + + SendAndQueueRead(iSmtpBuffer); + break; + + case EBeginData: + SendAndQueueRead(KSmtpDataCommand); + break; + + case ESendData: // send RFC822 message now + SendOneLineOfData(); + break; + + case EEndData: + // The bytes sent count is NOT accurate, so make sure this value + // doesn't exceed the number to send + iBytesSent = iBytesToSend; + // If smtp server did not respond within 10 mins after sending ".", the socket is timed out + iSocket.SendQueueReceiveWithTimeout(iStatus, KSmtpDataTerminationTimeout, KSmtpEndOfDataCommand); + break; + + default: // Unknown state + gPanic(EImsmBadFileState); + break; + } + SetActive(); + } + + +void CImSmtpFile::SendOneLineOfData() + { + TInt paddedBytes=0; + + if((iSuspendCheck++ % KMsvSuspendCheckGranuality) == 0) + { + // save the id, reset to top entry, check suspended state, fail if suspended + TMsvId savedId=iServerEntry.Entry().Id(); + // if we can't get to the parent entry, just keep sending + TInt error=iServerEntry.SetEntry(iEntryId); + __ASSERT_DEBUG(error==KErrNone,gPanic(EImsmUnableToSetServerEntryToMessage)); + if(error==KErrNone) + { + const TUint sendState=iServerEntry.Entry().SendingState(); + // we would be in a bad way if we can't get back to the entry. + // but there isn't anything we can do about it + error=iServerEntry.SetEntry(savedId); + __ASSERT_ALWAYS(error==KErrNone,gPanic(EImsmUnableToSetServerEntryBack)); + + if(sendState==KMsvSendStateSuspended) + { + RequestComplete(iStatus,KErrCancel); + return; + } + } + } + + //->>Bug- SMTS was expecting a KImCvFinished (=-1) when IMCM finishes with the msg header and boby + // instead IMCM returns a KImCvAdvance (=1) to indicate the end of msg. + // this is fixed temporarily by using KImCvAdvance instead of KImCvFinished. + TRAPD(error,iMoreRfc822Data=(KImCvFinished!= iSendMessage->NextLineL(iSmtpBuffer,paddedBytes))); // "iMoreRfc822Data" flag signals if this is last line of RFC822 body + if (!error) + { + __ASSERT_DEBUG(iSmtpBuffer.Length(),gPanic(EImsmBadSmtpBuffer)); + + // Checking for end of data = . + + if (iSmtpBuffer[0]=='.') + { + paddedBytes++; + iSmtpBuffer.Insert(0,KStuffDot); + } + + __ASSERT_DEBUG(iSmtpBuffer.Match(KSmtpMatchCrLf)==iSmtpBuffer.Length()-2,gPanic(EImsmNoCrLfTerminator)); + iSocket.Send(iStatus,iSmtpBuffer); // send line of text to TCP/IP socket + + // update progress information + iBytesSent+=iSmtpBuffer.Length(); // NB counting terminating "\r\n" at end of each line + + // The bytes sent count is NOT accurate, so make sure the progress + // does NOT exceed the amount to Send + if(iBytesSent > iBytesToSend) + iBytesSent = iBytesToSend; + } + else + { + RequestComplete(iStatus,error); + } + } + + +TInt CImSmtpFile::SmtpFileError(TInt aSmtpErrorCode) + { + // Try to identify specific error condition from state and error code + // if unable to do so, return SMTP code. + + switch (iState) + { + case EMailFromSmtp: + if (aSmtpErrorCode==ESmtpSyntaxError ||aSmtpErrorCode==ESmtpUserNotLocal) + return(0-KSmtpBadMailFromAddress); + + // If we receive one of the authentication error codes in response to our mail + // from command, then this indicates that the initial login was refused, but + // we decided to go on and try to do the send anyway. + // Return the KSmtpLoginRefused error code to indicate this. + // Note that the ESmtpTempAuthenticationFailure failure code should not be + // received in response to a mail from command, but some servers seem to use it. + if (aSmtpErrorCode == ESmtpTempAuthenticationFailure || + aSmtpErrorCode == ESmtpAuthenticationRequired) + { + return (0 - KSmtpLoginRefused); + } + break; + + case ERcptToSmtp: + if (aSmtpErrorCode==ESmtpSyntaxError || aSmtpErrorCode==ESmtpMailboxNoAccess + ||aSmtpErrorCode==ESmtpMailboxName) + return (0-KSmtpBadRcptToAddress); + break; + + case EResettingSmtp: + case EBeginData: + case ESendData: + case EEndData: + break; + + default: // Unknown state + gPanic(EImsmBadFileState); + break; + } + + TInt error=IdentifySmtpError(aSmtpErrorCode); + return (error ? error : 0-KSmtpUnknownErr); // return SMTP error code, if recognised + } + + +void CImSmtpFile::DoComplete(TInt& aStatus) + { + if (iCompleted) + { + // override completion code with my own (positive) error value + aStatus=iCompleted; + } + } + + +void CImSmtpFile::SelectBodyEncodingTypeL(TMsgOutboxBodyEncoding anSettingsValue, + TMsgOutboxBodyEncoding anHeaderValue) + { + if (anHeaderValue!=EMsgOutboxDefault) + iBodyType= anHeaderValue==EMsgOutboxNoAlgorithm ? ESendAsSimpleEmail:ESendAsMimeEmail; + else if (anSettingsValue!=EMsgOutboxDefault) + iBodyType= anSettingsValue==EMsgOutboxNoAlgorithm ? ESendAsSimpleEmail:ESendAsMimeEmail; + else + iBodyType=ESendAsMimeEmail; // Default setting. + } + +// Creating library \EPOC32\RELEASE\WINS\UDEB\IMUT.LIB and object \EPOC32\RELEASE\WINS\UDEB\IMUT.exp + + +void CImSmtpFile::SendAndQueueRead(const TDesC8& aDesc) + { + // Send SMTP command string to remote SMTP server - + // and then queues asynch read request for a response from the remote SMTP server. + iSocket.SendQueueReceive(iStatus,aDesc); + } + + +TBool CImSmtpFile::GetCurrentTextLine() + { + TInt result=iSocket.GetCurrentTextLine(iSmtpBuffer); + __ASSERT_DEBUG(result==ECRLFTerminated,gPanic(EImsmBadSmtpBuffer)); + return (result==ECRLFTerminated); + } + + +CImSmtpFile::~CImSmtpFile() + { + // Destructor. Must NOT destroy iSocket as it is owned by iSmtpSession, not by iSmtpFile. + Cancel(); + + delete iHeader; + delete iSendMessage; + }