email/pop3andsmtpmtm/smtpservermtm/src/IMSMFILE.CPP
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Thu, 17 Dec 2009 08:44:11 +0200
changeset 0 72b543305e3a
permissions -rw-r--r--
Revision: 200949 Kit: 200951

// 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;
	}