--- /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 <mentact.h> // CMsgActive
+#include <msvstd.h> // TMsvEntry, CMsvEntrySelection
+#include <msventry.h> // CMsvServerEntry
+#include <msvstore.h> // CMsvStore
+#include <miutpars.h> // TImMessageField
+#include <miuthdr.h> // CImHeader
+#include <imcvsend.h> // 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<KImMailMaxBufferSize>& 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<KImMailMaxBufferSize>& 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 = <CRLF>.
+
+ 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;
+ }