diff -r f742655b05bf -r d38647835c2e voipplugins/sipconnectionprovider/ipvoicemailengine/src/ipvmbxengine.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/voipplugins/sipconnectionprovider/ipvoicemailengine/src/ipvmbxengine.cpp Wed Sep 01 12:29:57 2010 +0100 @@ -0,0 +1,1258 @@ +/* +* 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 /*messaging/\sms/\*/ +#include +#include +#include +#include +#include +#include +#include + +#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 ); + } + } +