email/pop3andsmtpmtm/smtpservermtm/src/IMSMFILE.CPP
changeset 0 72b543305e3a
--- /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;
+	}