voipplugins/sipconnectionprovider/ipvoicemailengine/src/ipvmbxengine.cpp
author Pat Downey <patd@symbian.org>
Wed, 01 Sep 2010 12:29:57 +0100
branchRCL_3
changeset 22 d38647835c2e
permissions -rw-r--r--
Revert incorrect RCL_3 drop: Revision: 201033 Kit: 201035

/*
* Copyright (c) 2002-2010 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:  Creates SMS message if Event in MailBox has occurred.
*
*/



// Include hierarchy change can't be done. Smuthdr.h uses
// other files from messaging/sms (smsstd.h and smutset.h). It doesn't seem
// to find them without old way to declare include folder using SYSTEMINCLUDE.
//
#include <smuthdr.h>/*messaging/\sms/\*/
#include <msvuids.h>
#include <txtrich.h>
#include <spsettings.h>
#include <spproperty.h>
#include <utf.h>
#include <mcemanager.h>
#include <ipvoicemailengine.rsg>

#include "ipvmbxengine.h"
#include "ipvmbxeventmonitor.h"
#include "ipvmbxbase.h"
#include "ipvmbxlogger.h"
#include "ipvmbxconstants.h"
#include "ipvmbxpanic.h"


const TUid KAppUID = { 0x1020596F };
const TInt KMaxMessageDigits = 10;
const TInt KAccountTextLength = 21;
const TInt KOneChar = 1;
const TInt KTwoChars = 2;
const TInt KSmsLength = 160;
const TInt KTotalLength = 2;
const TInt KTotalOldLength = 3;
const TInt KMinIpVoiceMailBoxUriLength = 3;
const TInt KSpecialNameCharsCount = 10;
const TInt KMaxMsgDescriptions = 99;

_LIT( KEndLine, "\n" );
_LIT( KTotal, "%N" );
_LIT( KTotalNew, "%0N" );
_LIT( KIpVmbxAppEngineResourceFileDirectory, "\\resource\\" );
_LIT( KIpVmbxAppEngineResourceFileName, "ipvoicemailengine.rsc" );
_LIT( KSipString, "sip:");
_LIT( KOptionalSeparator, "\n\n" );
_LIT8( KTotalOld, "%1N" );
_LIT8( KOneMessage, "1" );
_LIT8( KNoMessages, "0" );
_LIT8( KMessagesWaiting8, "messages-waiting" );
_LIT8( KNewMessages8, "yes" );
_LIT8( KMessageAccount8, "message-account" );
_LIT8( KVoiceMessage8, "voice-message" );
_LIT8( KSlash8, "/" );
_LIT8( KColon8, ":" );
_LIT8( KCrlf8, "\r\n" );
_LIT8( KEndLine8, "\n" );
_LIT8( KHTab8, "\t");
_LIT8( KSpace8, " ");

const TText KNameDigitLowest = '\x30';
const TText KNameDigitHighest = '\x39';
const TText KNameCharUpLowest = '\x41';
const TText KNameCharUpHighest = '\x5a';
const TText KNameCharLowLowest = '\x61';
const TText KNameCharLowHighest = '\x7a';
// Legal characters in name header
// 2d, 2e, 21, 25, 2a, 5f, 2b, 60, 27, 7e
const TText8 KSpecialNameChars[KSpecialNameCharsCount] =
    {
    '-',
    '.',
    '!',
    '%',
    '*',
    '_',
    '+',
    '`',
    '\'',
    '~'
    };
const TText KSpace = '\x20';
const TText KValueLowestChar = KSpace;
const TText KValueHighestChar = '\xfd';
const TText KCr = '\x0d';
const TText KLf = '\x0a';
const TText KHTab = '\x09';


// ============================ MEMBER FUNCTIONS ==============================

// ----------------------------------------------------------------------------
// C++ default constructor can NOT contain any code, that
// might leave.
// ----------------------------------------------------------------------------
//
CIpVmbxEngine::CIpVmbxEngine( CIpVmbxInterface& aInterface ):
    iEventData(),
    iInterface( aInterface )
    {
    }


// ----------------------------------------------------------------------------
// Symbian 2nd phase constructor can leave.
// ----------------------------------------------------------------------------
//
void CIpVmbxEngine::ConstructL()
    {
    // One manager = one event observer (restricted)
    iIpVmbxEventMonitor = new ( ELeave ) TIpVmbxEventMonitor( *this );
    }


// ----------------------------------------------------------------------------
// Two-phased constructor.
// ----------------------------------------------------------------------------
//
EXPORT_C CIpVmbxEngine* CIpVmbxEngine::NewL( CIpVmbxInterface& aInterface )
    {
    IPVMEPRINT( "CIpVmbxEngine::NewL" );

    CIpVmbxEngine* self = new( ELeave ) CIpVmbxEngine( aInterface );
    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop( self );

    return self;
    }


// ----------------------------------------------------------------------------
// Destructor.
// ----------------------------------------------------------------------------
//
CIpVmbxEngine::~CIpVmbxEngine()
    {
    IPVMEPRINT( "CIpVmbxEngine::~CIpVmbxEngine - IN" );

    iVmbxBaseArray.ResetAndDestroy();
    iVmbxBaseArray.Close();

    delete iMceManager;
    delete iServiceSettings;
    delete iIpVmbxEventMonitor;

    IPVMEPRINT( "CIpVmbxEngine::~CIpVmbxEngine - OUT" );
    }


// ----------------------------------------------------------------------------
// Starts subscription to VoiceMailBox -server
// ----------------------------------------------------------------------------
//
void CIpVmbxEngine::SubscribeProfileL(
    TUint32 aServiceProviderId,
    CSIPProfile& aSipProfile )
    {
    IPVMEPRINT( "CIpVmbxEngine::SubscribeProfileL - IN" );

    if ( !iBasicServicesRunning )
        {
        iServiceSettings = CSPSettings::NewL();
        // one manager recommended for one component
        iMceManager = CMceManager::NewL( KAppUID, &iEventData );
        iMceManager->SetEventObserver( iIpVmbxEventMonitor );
        iBasicServicesRunning = ETrue;
        }

    HBufC16* voiceMailUri16 = HBufC16::NewLC( KMaxIpVoiceMailBoxUriLength );
    TPtr16 ptrvoiceMailUri16( voiceMailUri16->Des() );
    TInt reSubscribe = 0;

    // Fetch MWI address
    CSPProperty* mwiAddress = CSPProperty::NewLC();
    User::LeaveIfError( iServiceSettings->FindPropertyL(
        aServiceProviderId,
        ESubPropertyVMBXMWIAddress,
        *mwiAddress ) );
    User::LeaveIfError( mwiAddress->GetValue( ptrvoiceMailUri16 ) );
    if ( ptrvoiceMailUri16.Length() < KMinIpVoiceMailBoxUriLength )
        {
        IPVMEPRINT( "CIpVmbxEngine::SubscribeProfileL - MWI not found, Leaving..." );
        User::Leave( KErrNotFound );
        }
    IPVMEPRINT( "CIpVmbxEngine::SubscribeProfileL - MWI found" );
    if ( KErrNotFound == ptrvoiceMailUri16.Find( KSipString ) )
        {
        ptrvoiceMailUri16.Insert( 0, KSipString );
        }
    CleanupStack::PopAndDestroy( mwiAddress );

    // Fetch also subscribe interval
    CSPProperty* mwiInterval = CSPProperty::NewLC();
    User::LeaveIfError( iServiceSettings->FindPropertyL(
        aServiceProviderId,
        ESubPropertyVMBXMWISubscribeInterval,
        *mwiInterval) );
    User::LeaveIfError( mwiInterval->GetValue( reSubscribe ) );
    CleanupStack::PopAndDestroy( mwiInterval );

    HBufC8* voiceMailUri8 = CnvUtfConverter::ConvertFromUnicodeToUtf8L(
        ptrvoiceMailUri16 );
    CleanupStack::PopAndDestroy( voiceMailUri16 );
    CleanupStack::PushL( voiceMailUri8 );

    TInt index = iVmbxBaseArray.Count();
    TBool newProfile = ETrue;
    if ( index )
        {
        // Might be second subscription or pending operation.
        // Though IPVME and SCP supports currently only one VMBX connection
        for ( TInt i = 0; i < index; i++ )
            {
            CIpVmbxBase* const subscription( iVmbxBaseArray[i] );
            if ( *voiceMailUri8 == subscription->VmbxUrl() )
                {
                // Subscription already exists
                if ( CIpVmbxBase::EDisabled == subscription->State() )
                    {
                    // Subscription was disabled, enable
                    newProfile = EFalse;
                    subscription->Initialize(
                        aServiceProviderId,
                        aSipProfile );

                    subscription->SubscribeL( reSubscribe );
                    }
                else
                    {
                    User::Leave( KErrAlreadyExists );
                    }
                }
            }
        }
    if ( newProfile )
        {
        // Subscription to new VMBX account
        IPVMEPRINT( "CIpVmbxEngine::New AppBase" );
        CIpVmbxBase* app = CIpVmbxBase::NewL(
            *this,
            *voiceMailUri8,
            *iMceManager );
        CleanupStack::PushL( app );
        iVmbxBaseArray.AppendL( app );
        CleanupStack::Pop( app );

        app->Initialize( aServiceProviderId, aSipProfile );
        app->SubscribeL( reSubscribe );
        }

    CleanupStack::PopAndDestroy( voiceMailUri8 );

    IPVMEPRINT( "CIpVmbxEngine::SubscribeProfileL - OUT" );
    }


// ---------------------------------------------------------------------------
//
// ---------------------------------------------------------------------------
//
TInt CIpVmbxEngine::ProfileSubscribed(
    TUint32 aServiceProviderId,
    TBool& aProfileSubscribed )
    {
    CIpVmbxBase* subscription( SubscriptionByProvider( aServiceProviderId ) );
    TInt err = KErrNotFound;

    if ( subscription )
        {
        err = KErrNone;
        if ( CIpVmbxBase::ERegistered == subscription->State() )
            {
            aProfileSubscribed = ETrue;
            }
        else
            {
            aProfileSubscribed = EFalse;
            }
        }

    return err;
    }


// ---------------------------------------------------------------------------
// Resolve base class matching to recipient
// ---------------------------------------------------------------------------
//
CIpVmbxBase* CIpVmbxEngine::SubscriptionByRecipient(
    const TDesC8& aRecipient8 )
    {
    CIpVmbxBase* base( NULL );

    for ( TInt i = 0; i < iVmbxBaseArray.Count(); i++ )
        {
        if ( iVmbxBaseArray[i]->VmbxUrl() == aRecipient8 )
            {
            base = iVmbxBaseArray[i];
            }
        }

    return base;
    }


// ----------------------------------------------------------------------------
// EventMonitor has received event and the event will be gained and checked
// ----------------------------------------------------------------------------
//
void CIpVmbxEngine::EventReceivedL( const TDesC8& aRecipient8 )
    {
    IPVMEPRINT( "CIpVmbxEngine::EventReceivedL - IN" );

    CIpVmbxBase* subscription( SubscriptionByRecipient( aRecipient8 ) );
    if ( !subscription )
        {
        User::Leave( KErrNotFound );
        }

    HBufC8* content8( iEventData.GetContent() );
    CleanupStack::PushL( content8 );

    HBufC8* totalMessages8 = HBufC8::NewLC( KMaxMessageDigits );
    HBufC8* newMessages8 = HBufC8::NewLC( KMaxMessageDigits );
    HBufC8* from8 = HBufC8::NewLC(
        KMaxIpVoiceMailBoxUriLength + KAccountTextLength );
    TPtr8 ptrTotalMessages8( totalMessages8->Des() );
    TPtr8 ptrNewMessages8( newMessages8->Des() );
    TPtr8 ptrFrom8( from8->Des() );
    TPtr8 ptrContent8( content8->Des() );

    TBool createSMS = EFalse;
    // no need to handle errors, returned parameters can still
    // be interpreted correctly
    //
    TRAP_IGNORE(
        ParseNotifyContentL(
            createSMS,
            ptrContent8,
            ptrTotalMessages8,
            ptrNewMessages8,
            ptrFrom8 ) );

    if ( ptrNewMessages8 == KNoMessages )
        {
        // protocol test fix, new message should not be created
        // if new message count is 0
        IPVMEPRINT( "CIpVmbxEngine::EventReceivedL - No new messages" );
        createSMS = EFalse;
        }
    if ( 0 == ptrFrom8.Length() )
        {
        ptrFrom8 = subscription->VmbxUrl();
        }

    TInt totalMsgs = 0;
    TInt newMsgs = 0;
    TLex8 msgsConvert;
    msgsConvert.Assign( *totalMessages8 );
    if ( KErrNone == msgsConvert.Val( totalMsgs ) )
        {
        msgsConvert.Assign( *newMessages8 );
        if ( KErrNone != msgsConvert.Val( newMsgs ) )
            {
            totalMsgs = 0;
            }
        }

    TInt curTotal = 0;
    TInt curNew = 0;
    subscription->AccountMessageCount( curTotal, curNew );

    TBool statusChanged = EFalse;
    if ( totalMsgs != curTotal || newMsgs != curNew )
        {
        statusChanged = ETrue;
        IPVMEPRINT( "CIpVmbxEngine::EventReceivedL - Status changed" );
        subscription->SetAccountMessageCount( totalMsgs, newMsgs );
        }

    if ( createSMS && statusChanged )
        {
        IPVMEPRINT( "CIpVmbxEngine::EventReceivedL - Create message body" );
        TBuf8< KSmsLength > messageBody8;
        CreateMessageBodyL(
            *content8,
            ptrTotalMessages8,
            ptrNewMessages8,
            ptrFrom8,
            messageBody8 );
        CreateSMSMessageL( ptrFrom8, messageBody8 );
        }

    CleanupStack::PopAndDestroy( from8 );
    CleanupStack::PopAndDestroy( newMessages8 );
    CleanupStack::PopAndDestroy( totalMessages8 );
    CleanupStack::PopAndDestroy( content8 );

    IPVMEPRINT( "CIpVmbxEngine::EventReceivedL - OUT" );
    }


// ---------------------------------------------------------------------------
//
// ---------------------------------------------------------------------------
//
void CIpVmbxEngine::UnsubscribeProfileL( TUint32 aServiceProviderId )
    {
    IPVMEPRINT2( "CIpVmbxEngine::UnsubscribeProfileL: %d - IN", 
	    aServiceProviderId );
    
    CIpVmbxBase* subscription( SubscriptionByProvider( aServiceProviderId ) );
    if ( !subscription )
        {
        User::Leave( KErrNotFound );
        }
    
    IPVMEPRINT2( "CIpVmbxEngine::UnsubscribeProfileL: state=%d",
	    subscription->State() );

    switch( subscription->State() )
        {
        case CIpVmbxBase::ERegistered:
            {
            subscription->TerminateEventL();
            break;
            }
        case CIpVmbxBase::EDisabled:
            {
            CleanVmbxBase();
            break;
            }
        case CIpVmbxBase::ESubscribing:
            {
            subscription->Cancel();
            CleanVmbxBase();
            break;
            }
        case CIpVmbxBase::ETerminating:
            {
            User::Leave( KErrCancel );
            break;
            }
        default:
            {
            IPVMEPRINT( "No implementation" );
            }
        }

    }


// ----------------------------------------------------------------------------
//
// ----------------------------------------------------------------------------
//
void CIpVmbxEngine::CleanVmbxBase()
    {
    for ( TInt i = 0; i < iVmbxBaseArray.Count(); i++ )
        {
        if ( CIpVmbxBase::ETerminating == iVmbxBaseArray[i]->State() ||
            CIpVmbxBase::EDisabled == iVmbxBaseArray[i]->State() )
            {
            delete iVmbxBaseArray[i];
            iVmbxBaseArray.Remove( i );
            }
        }
    }


// ----------------------------------------------------------------------------
//
// ----------------------------------------------------------------------------
//
void CIpVmbxEngine::HandleMessage(
    TUint32 aServiceProviderId,
    CIpVmbxEngine::TIpVmbxMessages aMessage )
    {
    IPVMEPRINT2( "CIpVmbxEngine::HandleMessage: %d - IN", aMessage );

    CIpVmbxBase* subscription( SubscriptionByProvider( aServiceProviderId ) );
    // Save current state because some functions modify states
    CIpVmbxBase::TIpVmbxBaseStates baseState = subscription->State();
    MIpVmbxObserver::TVmbxMessage message =
        MIpVmbxObserver::EIncorrectSettings;
    TBool send = ETrue;

    switch ( aMessage )
        {
        case CIpVmbxEngine::EEngineSubscribed:
            message = MIpVmbxObserver::ESubscribed;
            break;
        case CIpVmbxEngine::EEngineTerminated:
            {
            if ( CIpVmbxBase::EDisabled != baseState )
                {
                CleanVmbxBase();
                message = MIpVmbxObserver::EUnsubscribed;
                }
            else
                {
                send = EFalse;
                }
            break;
            }
        case CIpVmbxEngine::EEngineUndefined:
            {
            send = EFalse;
            break;
            }
        case CIpVmbxEngine::EEngineSubscribeRejected:
        case CIpVmbxEngine::EEngineIncorrectAccount:
            {
            subscription->Cancel();
            CleanVmbxBase();
            message = MIpVmbxObserver::EIncorrectSettings;
            break;
            }
        case CIpVmbxEngine::EEngineSmsError:
            {
            subscription->Cancel();
            message = MIpVmbxObserver::ESmsError;
            break;
            }
        case CIpVmbxEngine::EEngineSmsOom:
            message = MIpVmbxObserver::ENoMemory;
            break;
        case CIpVmbxEngine::EEngineNetworkLost:
            {
            subscription->Cancel();
            subscription->DeleteEvent();
            send = EFalse;
            break;
            }
        case CIpVmbxEngine::EEngineNetworkError:
            {
            subscription->Cancel();
            subscription->DeleteEvent();
            message = MIpVmbxObserver::ENetworkError;
            break;
            }
        case CIpVmbxEngine::EEngineFatalNetworkError:
            {
            subscription->Cancel();
            subscription->DeleteEvent();
            message = MIpVmbxObserver::EFatalNetworkError;
            break;
            }
        default:
            IPVMEPRINT( "Unhandled message!" );
        }

    if ( send )
        {
        iInterface.SendMessage( aServiceProviderId, message );
        }

    IPVMEPRINT( "CIpVmbxEngine::HandleMessage - OUT" );
    }


// ----------------------------------------------------------------------------
//
// ----------------------------------------------------------------------------
//
CIpVmbxBase* CIpVmbxEngine::SubscriptionByProvider(
    TUint32 aServiceProviderId )
    {
    CIpVmbxBase* base( NULL );
    for ( TInt i = 0; i < iVmbxBaseArray.Count(); i++ )
        {
        if ( iVmbxBaseArray[i]->ServiceProviderId() == aServiceProviderId )
            {
            base = iVmbxBaseArray[i];
            }
        }
    return base;
    }


// ----------------------------------------------------------------------------
// From class MMsvSessionObserver.
// Indicates an event has occurred from a Message Server session.
// ----------------------------------------------------------------------------
//
void CIpVmbxEngine::HandleSessionEventL(
    TMsvSessionEvent /*aEvent*/,
    TAny* /*aArg1*/,
    TAny* /*aArg2*/,
    TAny* /*aArg3*/ )
    {
    IPVMEPRINT( "CIpVmbxEngine::HandleSessionEventL - Dummy implementation" );
    }


// ----------------------------------------------------------------------------
// Parses critical and important optional parts of content. Already
// processed data is cut from content.
// ----------------------------------------------------------------------------
//
void CIpVmbxEngine::ParseNotifyContentL(
    TBool& aCreateSms,
    TDes8& aContent8,
    TDes8& aTotalMessages8,
    TDes8& aNewMessages8,
    TDes8& aFrom8 ) const
    {
#ifdef _DEBUG
    HBufC* print = HBufC::NewLC( aContent8.Length() );
    print->Des().Copy( aContent8 );
    IPVMEPRINT2( "CIpVmbxEngine::ParseNotifyContentL - aContent8:%S", &print->Des() )
    CleanupStack::PopAndDestroy( print );
#endif // _DEBUG
    aCreateSms = EFalse;

    // check required content
    TLex8 analyzer8;
    TPtrC8 posPtr8;
    TPtrC8 dataPtr8;
    TPtrC8 messagePtr8;

    TInt atPosWaiting = aContent8.FindF( KMessagesWaiting8 );
    // check important OPTIONAL fields
    TInt atPosAccount = aContent8.FindF( KMessageAccount8 );
    TInt atPosMessage = aContent8.FindF( KVoiceMessage8 );

    if ( 0 == atPosWaiting )
        {
        // data field found from correct place
        messagePtr8.Set( FetchMessagePartL( aContent8 ) );
        analyzer8.Assign(
            messagePtr8.Mid( messagePtr8.Find( KColon8 ) + KOneChar ) );
        dataPtr8.Set( analyzer8.NextToken() );
        if ( 0 == dataPtr8.CompareF( KNewMessages8 ) )
            {
            // message(s) waiting
            aCreateSms = ETrue;
            }
        // cut processed data
        posPtr8.Set( aContent8.Mid( messagePtr8.Length() ) );
        analyzer8.Assign( posPtr8 );
        }
    else
        {
        IPVMEPRINT( "CIpVmbxEngine::ParseNotifyContentL - leave with KErrCorrupt" );
        // malformed critical part of message
        User::Leave( KErrCorrupt );
        }

    if ( KErrNotFound != atPosAccount && KErrNotFound != atPosMessage )
        {
        // both optionals found
        if ( !( atPosAccount < atPosMessage ) )
            {
            IPVMEPRINT( "CIpVmbxEngine::ParseNotifyContentL - leave with KErrCorrupt 2" );
            // incorrect format
            User::Leave( KErrCorrupt );
            }
        }

    if ( KErrNotFound != atPosAccount && aCreateSms )
        {
        // get account
        IPVMEPRINT( "CIpVmbxEngine::ParseNotifyContentL - get account" );
        messagePtr8.Set( FetchMessagePartL( posPtr8 ) );
        analyzer8.Assign(
            messagePtr8.Mid( messagePtr8.Find( KColon8 ) + KOneChar ) );
        dataPtr8.Set( analyzer8.NextToken() );
        posPtr8.Set( posPtr8.Mid( messagePtr8.Length() ) );
        analyzer8.Assign( posPtr8 );


        aFrom8.Copy( dataPtr8.Left( aFrom8.MaxLength() ) );
        }

    if ( KErrNotFound != atPosMessage )
        {
        messagePtr8.Set( FetchMessagePartL( posPtr8 ) );
        analyzer8.Assign(
            messagePtr8.Mid( messagePtr8.Find( KColon8 ) + KOneChar ) );

        // can hold value required by specification
        TUint oldMessageCount = 0;
        TUint newMessageCount = 0;
        TLex8 value;

        analyzer8.SkipSpace();
        User::LeaveIfError( analyzer8.Val( newMessageCount ) );
        analyzer8.SkipSpace();
        User::LeaveIfError( KSlash8().Locate( analyzer8.Get() ) );

        analyzer8.SkipSpace();
        User::LeaveIfError( analyzer8.Val( oldMessageCount ) );

        aNewMessages8.Num( newMessageCount );
        aTotalMessages8.Num( ( TInt64 ) oldMessageCount + ( TInt64 ) newMessageCount );

        posPtr8.Set( posPtr8.Mid( messagePtr8.Length() ) );
        }

    // Clean string off processed data
    aContent8.Delete( 0, aContent8.Length() - posPtr8.Length() );

    IPVMEPRINT( "CIpVmbxEngine::ParseNotifyContentL - OUT" );
    }


// ----------------------------------------------------------------------------
// Creates SMS message and sends it
// ----------------------------------------------------------------------------
//
void CIpVmbxEngine::CreateSMSMessageL(
    const TDesC8& aFrom8,
    const TDesC8& aMessageBody8 )
    {
    IPVMEPRINT( "CIpVmbxEngine::CreateSMSMessageL - IN" );

    // Create the SMS header object...
    CParaFormatLayer* paraFormatLayer = CParaFormatLayer::NewL();
    CleanupStack::PushL( paraFormatLayer );
    CCharFormatLayer* charFormatLayer = CCharFormatLayer::NewL();
    CleanupStack::PushL( charFormatLayer );

    CRichText* bodyText = CRichText::NewL( paraFormatLayer, charFormatLayer );
    CleanupStack::PushL( bodyText );
    TInt position = 0;
    TBuf< KSmsLength > messageBody;
    messageBody.Copy( aMessageBody8.Left( messageBody.MaxLength() ) );
    bodyText->InsertL( position, messageBody );

    CSmsHeader* header = CSmsHeader::NewL( CSmsPDU::ESmsDeliver, *bodyText );
    CleanupStack::PushL( header );
    TBuf< KMaxIpVoiceMailBoxUriLength > from;
    from.Copy( aFrom8 );
    header->SetFromAddressL( from );

    TMsvEntry entry;
    entry.SetVisible(ETrue );
    entry.SetUnread( ETrue );
    entry.SetNew( ETrue );
    entry.iServiceId = KMsvRootIndexEntryId;
    entry.iType = KUidMsvMessageEntry;
    entry.iMtm = KUidMsgTypeSMS;
    entry.iDate.UniversalTime();
    entry.iSize = 0;
    entry.iDescription.Set( KNullDesC );
    entry.iDetails.Set( KNullDesC );

    header->Deliver().SetServiceCenterTimeStamp( entry.iDate );

    CSmsSettings* smsSettings = CSmsSettings::NewL();
    CleanupStack::PushL( smsSettings );
    smsSettings->SetDelivery( ESmsDeliveryImmediately );
    smsSettings->SetValidityPeriod( ESmsVPWeek );
    smsSettings->SetValidityPeriodFormat( TSmsFirstOctet::ESmsVPFInteger );
    smsSettings->SetReplyQuoted( EFalse );
    smsSettings->SetRejectDuplicate( ETrue );
    smsSettings->SetDelivery( ESmsDeliveryImmediately );
    smsSettings->SetDeliveryReport( ETrue );
    smsSettings->SetReplyPath( EFalse );
    smsSettings->SetMessageConversion( ESmsConvPIDNone );
    smsSettings->SetCanConcatenate( ETrue );
    smsSettings->SetUseServiceCenterTimeStampForDate( ETrue );

    header->SetSmsSettingsL(*smsSettings );

    CSmsNumber* rcpt = CSmsNumber::NewL();
    CleanupStack::PushL( rcpt );
    rcpt->SetAddressL( from.Left( KSmcmSmsNumberMaxNumberLength ) );
    header->Recipients().AppendL( rcpt );
    CleanupStack::Pop( rcpt );

    // Update entry description and details...
    CArrayPtrFlat< CSmsNumber >& recipient = header->Recipients();
    entry.iDetails.Set( recipient[0]->Address() );
    entry.iDescription.Set( bodyText->Read(
        0, smsSettings->DescriptionLength() ));
    entry.SetInPreparation( EFalse );

    // Create the entry - set context to the global outbox.
    TMsvSelectionOrdering ordering = TMsvSelectionOrdering(
        KMsvNoGrouping,
        EMsvSortByDescription,
        ETrue );
        CMsvSession* session = CMsvSession::OpenSyncL( *this );
        CleanupStack::PushL( session );
    CMsvEntry* centry = CMsvEntry::NewL(
        *session, KMsvRootIndexEntryId, ordering );
    CleanupStack::PushL( centry );
    centry->SetEntryL( KMsvGlobalInBoxIndexEntryId );
    centry->CreateL( entry );

    // Create new store and save header information
    centry->SetEntryL( entry.Id() );
    CMsvStore* store = centry->EditStoreL();
    CleanupStack::PushL(store);
    header->StoreL( *store );
    store->StoreBodyTextL( *bodyText );
    store->CommitL();

    CleanupStack::PopAndDestroy( store );
    CleanupStack::PopAndDestroy( centry );
    CleanupStack::PopAndDestroy( session );
    CleanupStack::PopAndDestroy( smsSettings );
    CleanupStack::PopAndDestroy( header );
    CleanupStack::PopAndDestroy( bodyText );
    CleanupStack::PopAndDestroy( charFormatLayer );
    CleanupStack::PopAndDestroy( paraFormatLayer );

    IPVMEPRINT( "CIpVmbxEngine::CreateSMSMessageL - OUT" );
    }


// ----------------------------------------------------------------------------
// Parse optional headers. Parser must take account various optional
// characters in headers. String lengths are monitored every round to prevent
// panics when setting data.
// ----------------------------------------------------------------------------
//
void CIpVmbxEngine::ParseOptionalHeadersL(
    const TDesC8& aContent8,
    TDes8& aMessageBody8 ) const
    {
    IPVMEPRINT( "CIpVmbxEngine::ParseOptionalHeadersL - IN" );

    if ( aContent8.Left( KCrlf8().Length() ).Compare( KCrlf8  ) ||
        aContent8.Length() == KTwoChars )
        {
        // Checking if optional message does not exist or its too short.
        // Will also fix parsing of non standard basic Pingtel message.
        //
        User::Leave( KErrNotFound );
        }

    TFileName dll;
    Dll::FileName( dll );
    TFileName fileName( TParsePtrC( dll ).Drive() );
    fileName += KIpVmbxAppEngineResourceFileDirectory;
    fileName += KIpVmbxAppEngineResourceFileName;
    CStringResourceReader* resourceReader =
        CStringResourceReader::NewL( fileName );
    CleanupStack::PushL( resourceReader );

    if ( aMessageBody8.MaxLength() == aMessageBody8.Length() )
        {
        User::Leave( KErrOverflow );
        }
    else
        {
        // Add another separator before optional messages
        aMessageBody8.Append( KEndLine8 );
        }

    TPtrC8 messagePtr8;
    TPtrC8 tagPtr8;
    TPtrC8 dataPtr8;
    TPtrC8 posPtr8( aContent8 );
    TPtrC8 colon8;
    TInt messages = 0;
    HBufC* locTemp( HBufC::NewLC( KSmsLength ) );
    TPtr locPtr( locTemp->Des() );
    TPtrC resourcePtr( resourceReader->ReadResourceString( R_VOIP_VM_HEADER_COUNT ) );
    HBufC* variant( NULL );
    TLex8 analyzer8;
    HBufC8* partTemp( HBufC8::NewLC( KSmsLength ) );
    TPtr8 partPtr( partTemp->Des() );
    TBool messageEmpty = EFalse; // prevents creation of empty optional message part
    TBool appendMsgChange = EFalse;
    // Start sorting through message
    do
        {
        if ( ( KErrNotFound !=
            posPtr8.Left( KCrlf8().Length() ).FindF( KCrlf8 ) ||
            0 == messages ) && !messageEmpty )
            {
            // beginning of optional messages or another optional message
            if ( messages <= KMaxMsgDescriptions )
                {
                // add message count for first/next optional message
                // count is limited to two numbers = 99 (prevents also overflow when
                // KTotal is replaced)
                TBuf< KTwoChars > appendNum;
                appendNum.AppendNum( ++messages );
                locPtr.Zero();
                locPtr.Append( KOptionalSeparator );
                locPtr.Append( resourcePtr );
                locPtr.Replace( locPtr.Find( KTotal ), KTotal().Length(), appendNum );
                locPtr.Append( KEndLine );
                posPtr8.Set( posPtr8.Mid( KCrlf8().Length() ) );
                messageEmpty = ETrue;
                appendMsgChange = ETrue;
                }
            else
                {
                User::Leave( KErrOverflow );
                }
            }
        messagePtr8.Set( FetchMessagePartL( posPtr8 ) );
        analyzer8.Assign(
            messagePtr8.Left( messagePtr8.Find( KColon8 ) + KOneChar ) );
        tagPtr8.Set( analyzer8.NextToken() );
        if ( KErrNotFound == tagPtr8.Find( KColon8 ) )
            {
            // colon required
            colon8.Set( analyzer8.NextToken() );
            }
        analyzer8.Assign(
            messagePtr8.Mid( messagePtr8.Find( KColon8 ) + KOneChar ) );
        analyzer8.SkipSpace(); // skipping ws's since one newline is added later
        if ( analyzer8.Eos() )
            {
            // data contained only ws's we must decrease counter by length of CRLF
            analyzer8.UnGet();
            analyzer8.UnGet();
            }
        dataPtr8.Set(
            messagePtr8.Mid(
                messagePtr8.Find( KColon8 ) + KOneChar + analyzer8.Offset(),
                analyzer8.Remainder().Length() - KCrlf8().Length() ) );
        posPtr8.Set( posPtr8.Mid( messagePtr8.Length() ) );
        analyzer8.Assign( posPtr8 );

        variant = TranslateTagL( tagPtr8, *resourceReader );

        if ( variant &&
            variant->Length() + dataPtr8.Length() + KOneChar <
            partPtr.MaxLength() - partPtr.Length() )
            {
            // Resource translated to users phone variant language.
            // Optional message headers other than these aren't supported
            // because language cannot be verified and might differ from phone
            // variant language.
            partPtr.Append( *variant );
            partPtr.Append(  dataPtr8 );
            partPtr.Append( KEndLine8 );
            messageEmpty = EFalse;
            }
        delete variant;
        variant = NULL;

        if ( !appendMsgChange && !messageEmpty && aMessageBody8.MaxLength() - aMessageBody8.Length() >= partPtr.Length() )
            {
            aMessageBody8.Append( partPtr );
            partPtr.Zero();
            }
        else
            if ( !messageEmpty && aMessageBody8.MaxLength() - aMessageBody8.Length() >= partPtr.Length() + locPtr.Length() )
                {
                // Content OK, append to actual actual message
                aMessageBody8.Append( locPtr );
                aMessageBody8.Append( partPtr );
                partPtr.Zero();
                messageEmpty = EFalse;
                appendMsgChange = EFalse;
                }
            else
                {
                // partial ptr was too long for message, reset and try next part from content
                partPtr.Zero();
                messageEmpty = ETrue;
                }

        }while( !analyzer8.Eos() );

    CleanupStack::PopAndDestroy( partTemp );
    CleanupStack::PopAndDestroy( locTemp );
    CleanupStack::PopAndDestroy( resourceReader );

    IPVMEPRINT( "CIpVmbxEngine::ParseOptionalHeadersL - OUT" );
    }


// ----------------------------------------------------------------------------
//
// ----------------------------------------------------------------------------
//
void CIpVmbxEngine::ReadResourcesL(
    const TDesC8& aTotalMessages8,
    const TDesC8& aNewMessages8,
    TDes8& aMessagesBody8 )
    {
    IPVMEPRINT( "CIpVmbxEngine::ReadResourcesL - IN" );
    __ASSERT_DEBUG( KSmsLength == aMessagesBody8.MaxLength(), Panic( KErrArgument ) );

    TFileName dll;
    Dll::FileName( dll );
    TFileName fileName( TParsePtrC( dll ).Drive() );
    fileName += KIpVmbxAppEngineResourceFileDirectory;
    fileName += KIpVmbxAppEngineResourceFileName;
    CStringResourceReader* resourceReader =
        CStringResourceReader::NewL( fileName );
    CleanupStack::PushL( resourceReader );
    TPtrC messageText;

    if( !aTotalMessages8.Length() )
        {
        messageText.Set(
            resourceReader->ReadResourceString( R_VOIP_VM_MSG_SUM_UNKNOWN ) );
        aMessagesBody8.Copy( messageText );
        }
    else if( !aNewMessages8.Compare( KOneMessage ) &&
        !aTotalMessages8.Compare( KOneMessage ) )
        {
        messageText.Set(
            resourceReader->ReadResourceString( R_VOIP_NEW_VOICE_MESSAGE ) );
        aMessagesBody8.Copy( messageText );
        }
    else if( !aNewMessages8.Compare( KOneMessage ) &&
        aTotalMessages8.Compare( KOneMessage ) )
        {
        TBuf< KSmsLength > tempText;
        messageText.Set(
            resourceReader->ReadResourceString(
                R_VOIP_NEW_AND_OLD_VOICE_MSG ) );
        tempText.Copy( messageText );
        TInt atPosFirst = tempText.Find( KTotal );
        TInt messageLength = tempText.Length();
        TPtrC messagePart1 = tempText.Mid( 0 , atPosFirst );
        aMessagesBody8.Copy( messagePart1 );
        aMessagesBody8.Append( aTotalMessages8.Left( KMaxMessageDigits ) );
        TPtrC messagePart2 = tempText.Mid(
            atPosFirst + KTotalLength,
            messageLength - atPosFirst - KTotalLength );
        aMessagesBody8.Append( messagePart2 );
        }
    else if( aNewMessages8.Compare( KOneMessage ) &&
        aTotalMessages8.Compare( KOneMessage ) )
        {
        messageText.Set(
            resourceReader->ReadResourceString( R_VOIP_NEW_VOICE_MESSAGES ) );

        // Search new messages location and replace with real value
        TBuf8< KSmsLength > messagePart1;
        TInt pos = messageText.Find( KTotalNew );
        messagePart1.Copy( messageText.Left( pos ) );
        messagePart1.Append( aNewMessages8.Left( KMaxMessageDigits ) );
        messagePart1.Append( messageText.Mid( pos + KTotalOldLength ) );

        // Search total messages location and replace with real value
        TBuf8< KSmsLength > messagePart2;
        pos = messagePart1.Find( KTotalOld );
        messagePart2.Copy( messagePart1.Left( pos ) );
        messagePart2.Append( aTotalMessages8.Left( KMaxMessageDigits ) );
        messagePart2.Append( messagePart1.Mid( pos + KTotalOldLength ) );

        aMessagesBody8.Append( messagePart2 );
        }

    aMessagesBody8.Append( KEndLine );
    aMessagesBody8.Append( KEndLine );
    aMessagesBody8.Append(
        resourceReader->ReadResourceString( R_VOIP_VM_MSG_ACCOUNT ) );

    CleanupStack::PopAndDestroy( resourceReader );

    IPVMEPRINT( "CIpVmbxEngine::ReadResourcesL - OUT" );
    }

// ----------------------------------------------------------------------------
//
// ----------------------------------------------------------------------------
//
void CIpVmbxEngine::CreateMessageBodyL(
    const TDesC8& aContent8,
    const TDesC8& aTotal8,
    const TDesC8& aNew8,
    TDes8& aFrom8,
    TDes8& aMessageBody8 )
    {
    aFrom8.TrimRight();
    if ( 0 == aFrom8.Length() )
        {
        User::Leave( KErrArgument );
        }

    ReadResourcesL(
        aTotal8,
        aNew8,
        aMessageBody8 );

    if( aFrom8.Length() &&
        aMessageBody8.Length() + aFrom8.Length() < KSmsLength )
        {
        aMessageBody8.Append( aFrom8 );
        }

    TRAP_IGNORE( ParseOptionalHeadersL( aContent8, aMessageBody8 ) );
    }

// ----------------------------------------------------------------------------
// Creates translated tag from resources
// ----------------------------------------------------------------------------
//
HBufC* CIpVmbxEngine::TranslateTagL(
    const TDesC8& aTagPtr8,
    CStringResourceReader& aResourceReader ) const
    {
    HBufC* locTemp( NULL );
    TLex8 analyzer8( aTagPtr8 );
    TPtrC8 tag( analyzer8.NextToken() );
    if ( KErrNotFound != tag.Find( KColon8 ) )
        {
        // remove possible colon char to make tag matching easier
        tag.Set( tag.Left( tag.Length() - KOneChar ) );
        }
    TIpVmbxParseType::TParseTypes i = TIpVmbxParseType::EDetailTo;
    TIpVmbxParseType parseType;
    for ( ;i <= TIpVmbxParseType::EDetailId; i++ )
        {
        parseType.Set( i );
        if ( 0 == tag.MatchF( parseType.Tag() ) )
            {
            i = TIpVmbxParseType::EDetailId;
            locTemp =
                aResourceReader.ReadResourceString(
                    parseType.ResourceId() ).AllocL();
            }
        };

    return locTemp;
    }

// ----------------------------------------------------------------------------
// Gets and validates part of message.
// ----------------------------------------------------------------------------
//
TPtrC8 CIpVmbxEngine::FetchMessagePartL( const TDesC8& aContent8 ) const
    {
    IPVMEPRINT( "CIpVmbxEngine::FetchMessagePartL - IN" );

    TInt crlf = User::LeaveIfError( aContent8.FindF( KCrlf8 ) ) + KCrlf8().Length();
    // check for optional CRLF
    while( crlf < aContent8.Length() &&
        ( KErrNotFound != KHTab8().Locate( aContent8[crlf] ) ||
        KErrNotFound != KSpace8().Locate( aContent8[crlf] ) ) )
        {
        // This was optional CRLF, try next one
        crlf += User::LeaveIfError( aContent8.Mid( crlf ).FindF( KCrlf8 ) ) + KTwoChars;
        }
    // now we should have correct partial data
    TPtrC8 part8( aContent8.Left( crlf ) );
    TLex8 analyzer8( part8 );

    TPtrC8 tagPtr8(
        part8.Left( User::LeaveIfError( part8.Find( KColon8 ) ) ) );
    if ( !tagPtr8.Length() )
        {
        // At least one character required
        User::Leave( KErrCorrupt );
        }
    TestNamePartL( tagPtr8 );

    TPtrC8 dataPtr8( part8.Mid( tagPtr8.Length() ) );
    TestValuePartL( dataPtr8 );

    IPVMEPRINT( "CIpVmbxEngine::FetchMessagePartL - OUT" );
    return aContent8.Left( part8.Length() );
    }

// ----------------------------------------------------------------------------
// Validates name part of message.
// ----------------------------------------------------------------------------
//
void CIpVmbxEngine::TestNamePartL( const TDesC8& aNameHeader8 ) const
    {
    // Check legal chars %x41-5A / %x61-7A %x30-39 ( 2d, 2e, 21, 25, 2a, 5f, 2b, 60, 27, 7e)
    // from name header
    const TFixedArray< TText8, KSpecialNameCharsCount > legalCharsArray(
        &KSpecialNameChars[0], KSpecialNameCharsCount );
    TChar curChar;
    TLex8 analyzer8( aNameHeader8 );
    curChar = analyzer8.Get();
    TBool valid = ETrue;
    while ( !curChar.Eos() && valid )
        {
        valid = EFalse;
        if ( KNameDigitLowest <= curChar && KNameDigitHighest >= curChar ||
            KNameCharUpLowest <= curChar && KNameCharUpHighest >= curChar ||
            KNameCharLowLowest <= curChar && KNameCharLowHighest >= curChar )
            {
            valid = ETrue;
            }
        for ( TInt i = 0; legalCharsArray.Count() > i && !valid; i++ )
            {
            if ( curChar == legalCharsArray.At( i ) )
                {
                valid = ETrue;
                }
            }
        if ( valid )
            {
            curChar = analyzer8.Get();
            }
        }
    if ( !valid )
        {
        valid = ETrue;
        while ( !curChar.Eos() && valid )
            {
            valid = EFalse;
            if ( KSpace == curChar || KHTab == curChar )
                {
                valid = ETrue;
                }
            curChar = analyzer8.Get();
            }
        }
    if ( !valid )
        {
        User::Leave( KErrCorrupt );
        }
    }

// ----------------------------------------------------------------------------
// Validates value part of message.
// ----------------------------------------------------------------------------
//
void CIpVmbxEngine::TestValuePartL( const TDesC8& aValueHeader8 ) const
    {
    TBool valid = ETrue;
    TChar curChar;
    TLex8 analyzer8( aValueHeader8 );
    curChar = analyzer8.Get();

    while ( !curChar.Eos() && valid  )
        {
        valid = EFalse;
        if ( KValueLowestChar <= curChar && KValueHighestChar >= curChar )
            {
            valid = ETrue;
            }
        else
            if ( KCr == curChar || KLf == curChar || KHTab == curChar )
                {
                valid = ETrue;
                }
        curChar = analyzer8.Get();
        }
    if ( !valid )
        {
        User::Leave( KErrCorrupt );
        }
    }