mmsengine/mmshttptransport/src/mmstransaction.cpp
changeset 31 ebfee66fde93
child 47 5b14749788d7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mmsengine/mmshttptransport/src/mmstransaction.cpp	Fri Jun 04 10:25:39 2010 +0100
@@ -0,0 +1,1264 @@
+/*
+* Copyright (c) 2002-2006 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:  
+*     Class implementing transaction within WAP session
+*
+*/
+
+
+
+// INCLUDE FILES
+#include    <uriutils.h>
+#include    <httpstringconstants.h>
+#include    <uaproffilter_interface.h>
+
+#include    "mmstransaction.h"
+#include    "mmsservercommon.h"
+#include    "mmsconst.h"
+#include    "mmssession.h"
+#include    "mmserrors.h"
+#include    "MmsServerDebugLogging.h"
+#include    "mmscodecdatasupplier.h"
+#include    "mmscodecdatasink.h"
+
+
+// EXTERNAL FUNCTION PROTOTYPES  
+extern void gPanic(TMmsPanic aPanic);
+
+// CONSTANTS
+const TInt KMmsMaxRetryCount = 4;
+// HTTP error ranges
+const TInt KMmsHTTP100 = 100; // 1xx informative
+const TInt KMmsHTTP200 = 200; //
+const TInt KMmsHTTP300 = 300; //
+const TInt KMmsHTTP400 = 400; //
+const TInt KMmsHTTP500 = 500; //
+const TInt KMmsHTTP404 = 404; // Not Found
+const TInt KMmsHTTP413 = 413; // Request entity too large
+
+const TInt KMmsReserve = 10000; // extra for buffer
+    
+// MACROS
+// LOCAL CONSTANTS AND MACROS
+// MODULE DATA STRUCTURES
+// LOCAL FUNCTION PROTOTYPES
+// ==================== LOCAL FUNCTIONS ====================
+// ================= MEMBER FUNCTIONS =======================
+
+// ---------------------------------------------------------
+// CMmsTransaction
+// ---------------------------------------------------------
+//
+CMmsTransaction::CMmsTransaction()
+    : CMsgActive ( KMmsActiveObjectPriority ),
+    iState ( EMmsIdle ),
+    iUri ( NULL ),
+    iHTTPSession( NULL ),
+    iTimer( NULL ),
+    iTransferControl( NULL ),
+    iTransactionOpened( EFalse )
+    {
+    }
+
+// ---------------------------------------------------------
+// ConstructL
+// ---------------------------------------------------------
+//
+void CMmsTransaction::ConstructL()
+    {
+    iTimer = CMmsOperationTimer::NewL();
+    CActiveScheduler::Add( this );    
+    }
+
+// ---------------------------------------------------------
+// NewL
+// ---------------------------------------------------------
+//
+CMmsTransaction* CMmsTransaction::NewL()
+    {
+    CMmsTransaction* self = new (ELeave) CMmsTransaction();
+    CleanupStack::PushL( self );
+    self->ConstructL();
+    CleanupStack::Pop( self );
+    return self;
+    }
+
+    
+// ---------------------------------------------------------
+// ~CMmsTransaction
+// ---------------------------------------------------------
+//
+CMmsTransaction::~CMmsTransaction()
+    {
+    Cancel();
+    if ( iTransactionOpened )
+        {
+        RHTTPHeaders hdr = iTransaction.Request().GetHeaderCollection();
+        hdr.RemoveAllFields(); // clean up
+        iTransaction.Close();
+        }
+    delete iUri;
+    delete iTimer; 
+    }
+
+// ---------------------------------------------------------
+// ExecuteL
+// ---------------------------------------------------------
+//
+void CMmsTransaction::ExecuteL( 
+    RHTTPSession& aSession,    
+    CMmsBearerStatus& aTransferControl,
+    const TDesC& aUri,
+    const TInt aMethod,
+    CBufFlat& aMessageBuffer,
+    const TInt aTransactionTimer,
+    TInt32 aMaxReceiveSize,
+    TInt32 aExpectedReceiveSize,
+    MMmsCodecDataSupplier& aDataSupplier,
+    MMmsCodecDataSink& aDataSink,
+    TRequestStatus& aStatus )
+    {
+    LOG( _L("CMmsTransaction::ExecuteL") );
+    __ASSERT_DEBUG( iState == EMmsIdle, gPanic( EMmsAlreadyBusy ) );
+    
+    iError = KErrNone;
+  
+    iHTTPSession = &aSession;
+    iDataSupplier = &aDataSupplier;
+    iDataSink = &aDataSink;
+
+    iMaxReceiveSize = aMaxReceiveSize;
+    if( aMethod == HTTP::EGET )
+        {
+        //
+        // Setting adequate receivebuffer size for receive transactions
+        //
+        TInt32 reserve = KMmsReserve; // fixed reserve for unexpected surplus data
+        if ( aExpectedReceiveSize == KMmsChunkedBufferSize )
+            {
+            // we don't need extra when receiving in chunked mode
+            reserve = 0;
+            }
+        if( ( aExpectedReceiveSize + reserve ) > aMaxReceiveSize )
+            {
+            iExpectedReceiveSize = aMaxReceiveSize;
+            }
+        else
+            {
+            iExpectedReceiveSize = aExpectedReceiveSize + reserve;
+            }
+        }
+    iTransactionOpened = EFalse;
+
+    delete iUri;
+    iUri = NULL;
+    HBufC* newUri = aUri.AllocL();
+    iUri = newUri;    
+    
+    iBuffer = &aMessageBuffer;
+    iMethod = aMethod;
+    
+    iTransactionTimeout = aTransactionTimer;
+    iTransferControl = &aTransferControl;
+
+    // clear the flags, because we are reusing the class,
+    // we don't create a new instance every time
+    iRetryCount = 0;
+    iRequestOngoing = EFalse;
+    iSuspendOccurred = EFalse;
+    iGotBodyData = EFalse;
+    iDataChunkNumber = 0; // no data obtained yet
+    iCumulativeSize = 0; // received size or sent size, cumulative
+    iChunkSize = 0;
+    iEvent = THTTPEvent::ESubmit;
+
+    Queue( aStatus );   // aStatus = iStatus = KRequestPending
+
+    //
+    // Start transaction timer
+    //
+    if ( iTimer->IsActive() )
+        {
+        iTimer->Cancel();
+        }
+    iTimer->Start( (MMmsTransportObserver*) this, iTransactionTimeout );
+
+    //
+    // If bearer is already blocked when starting, execution enters a wait
+    //
+    if( iTransferControl->IsSuspended() )
+        {
+        LOG( _L(" - GPRS is Suspended -> waiting for resume before sending") );
+
+        // Transfer cancellation
+        iTransferControl->SubscribeNotification( (MMmsTransportObserver*) this );
+        // Start needed operations in suspend
+        GprsSuspended();
+
+        // someone must set our status to KRequestPending
+        iStatus = KRequestPending;
+
+#ifndef _NO_MMSS_LOGGING_
+        if ( IsActive() )
+            {
+            LOG( _L(" - already active") );
+            }
+#endif  //_NO_MMSS_LOGGING_
+        SetActive();
+        }
+    else // Bearer not blocked, continuing directly
+        {
+        iStatus = KRequestPending;
+        SetActive();    
+        TRequestStatus* status = &iStatus;
+        User::RequestComplete( status, KErrNone );
+        }
+    }
+        
+
+// ---------------------------------------------------------
+// RunL
+// ---------------------------------------------------------
+//
+void CMmsTransaction::RunL()
+    {
+    // DoRunL() takes the AO through the states, queuing another
+    // asynch step as required.
+    // if DoRunL() detects the end of the cycle it returns
+    // without queuing another cycle.
+
+    // If Run() would exit without queuing another cycle it reports
+    // completion to the client.
+    // In CMsgActive this is true if the asynch step or DoRunL fails,
+    // or the state cycle is complete
+    // However, we must keep our cycle going in spite of error(s).
+   
+    // The CMmsOperationTimer reports the exceed situation with error
+    // This must be handled in CMmsTransaction's DoRunL before returning to
+    // client
+
+    // Also the bearer suspend/resume status is informed via error
+    // Also handled in CMmsTransaction. 
+
+    // If we have completed ourselves, the error is already in iError.
+    // However, if an asynchronous operation completed us, the error
+    // comes back to us in iStatus.
+    // As we always complete ourselves by returning iError as the
+    // status, we get back either our original iError, or a new
+    // error value set by an asynchronous operation.
+    
+    TInt status = iStatus.Int();
+    
+    if ( iError == KErrNone )
+        {
+        iError = status;
+        }
+
+    // Only cancel can exit.
+    if ( status != KErrCancel || iError != KErrNone )
+        {
+        // iError contains the possible error from previous step.
+     
+        TRAPD( error, DoRunL() );    // continue operations, may re-queue
+        // must not requeue in error situations
+        // If we want to continue the loop in spite of error (yes, sir),
+        // we must not leave, but return the error in iError.
+        // Symbian code may leave, so we must trap the leaves here,
+        // as we want to keep the loop going unless something
+        // is really fatally wrong (we are completely out of memory,
+        // or the system has crashed)
+        __ASSERT_DEBUG( error == KErrNone || !IsActive(), User::Invariant() );
+        // active means AO has been requeued, just return
+        if ( IsActive() )             
+            {
+            return;
+            }
+
+        status = error;
+        if ( error == KErrNone )
+            {
+            // If DoRunL did not leave, possible error is in iError
+            status = iError;
+            }
+        }
+    LOG2( _L("CMmsTransaction::RunL: Complete with %d"), status );
+    Complete( status );
+    }
+
+// ---------------------------------------------------------
+// DoRunL
+// ---------------------------------------------------------
+//
+void CMmsTransaction::DoRunL()
+    {
+    //
+    // This routine takes the state machine through the states
+    // until an error is encountered or the cycle completes.
+    //
+    LOG3( _L("CMmsTransaction::DoRunL status %d, iError %d"), iStatus.Int(), iError );
+    // If we got last chunk already, do not cancel, let HTTP complete
+    if ( iError != KErrNone && iError != KMmsErrorBearerSuspended )
+        {
+        if ( iRequestOngoing )
+            {
+            iTransaction.Cancel();
+            iRequestOngoing = EFalse;
+            }
+        // Error code copied to status code
+        iStatus = iError;
+        }
+
+    // We continue to next state to finish properly
+    SelectNextState();
+    // If appropriate, ChangeState makes us active again
+    ChangeStateL();
+    }
+
+// ---------------------------------------------------------
+// SelectNextState
+// ---------------------------------------------------------
+//
+void CMmsTransaction::SelectNextState()
+    {
+    //
+    // The purpose of the switch statements is to cycle through
+    // states. These states handle certain task(s) and after
+    // completed CMmsTransaction is finished.
+    //
+    switch ( iState )
+        {
+        case EMmsIdle:
+            // start the loop
+            iState = EMmsSendingTransactionRequest;
+            break;
+        case EMmsSendingTransactionRequest:
+            iState = EMmsFinished;            
+            break;
+        case EMmsFinished:
+            // No more states
+            iError = KErrUnknown;           
+            break;
+        default:
+            // Illegal state, we should never get here
+            iError = KErrUnknown;
+            break;
+        }  
+    }
+    
+// ---------------------------------------------------------
+// ChangeStateL
+// ---------------------------------------------------------
+//
+void CMmsTransaction::ChangeStateL()
+    {
+    switch ( iState )
+        {
+        case EMmsSendingTransactionRequest:
+            MakeTransactionL();
+            break;
+        
+        case EMmsFinished:
+            FinishL();
+            break;
+ 
+        default:
+            // All other states are illegal
+            LOG( _L("CMmsTransaction::ChangeStateL(): ERROR: Illegal state") );
+            iError = KErrUnknown;
+            break;
+        }    
+    }
+
+// ---------------------------------------------------------
+// MakeTransactionL
+// ---------------------------------------------------------
+//
+void CMmsTransaction::MakeTransactionL()
+    {
+    LOG( _L("CMmsTransaction::MakeTransactionL") );
+
+    // Useragent has been added to session headers:
+    // It should always be present
+    HBufC8* uri = HBufC8::NewL( iUri->Length() );
+    CleanupStack::PushL( uri );
+    uri->Des().Copy( iUri->Des() );
+    TUriParser8 uri2;
+    uri2.Parse( uri->Des() );
+
+    RStringPool stringPool = iHTTPSession->StringPool();
+
+    iTransaction = iHTTPSession->OpenTransactionL( uri2,
+        *this,
+        stringPool.StringF( iMethod, RHTTPSession::GetTable() ) );
+
+    iTransactionOpened = ETrue;
+    CleanupStack::PopAndDestroy( uri );
+
+    // Set headers. If GET, no content type needed as no content present
+    // common accept headers are set as session headers
+    // these are done only on the first time. In retry cases, they already exist.
+    RHTTPHeaders hdr = iTransaction.Request().GetHeaderCollection();
+
+    // Adding accept headers has been moved to transaction,
+    // as automatic filters seemed to override session headers
+    // (session headers are not added if transaction headers already exist)
+
+    // NOTE: Currently Accept header contains the following line on purpose:
+    // "Accept: */*, application/vnd.wap.mms-message, application/vnd.wap.sic"
+    // in order to be compliant with some server vendors.
+
+    // Accept: "*/*"
+    SetHeaderL( hdr, HTTP::EAccept, KMmsAny );
+    // Accept: "application/vnd.wap.mms-message"
+    SetHeaderL( hdr, HTTP::EAccept, KMmsMessageContentType );
+    // Accept: "application/vnd.wap.sic"
+    SetHeaderL( hdr, HTTP::EAccept, KMmsWapSicContentType );
+
+    // Accept-Language: en
+    SetHeaderL( hdr, HTTP::EAcceptLanguage, KHTTPEnglish );
+
+    // Accept-Charset: utf-8
+    SetHeaderL( hdr, HTTP::EAcceptCharset, KHTTPUtf8 );
+    // end of headers moved from session to transaction
+
+    if( iMethod == HTTP::EPOST )
+        {
+        // Add contenttype header 
+        SetHeaderL( hdr, HTTP::EContentType, KMmsMessageContentType );
+
+        // Give payload supplier for stack in case of http post
+        iTransaction.Request().SetBody( *this );
+        }
+
+    // Set the 'Host:' header
+    SetHostHeaderL( hdr );
+
+    // Set UA headers
+    RStringF uaClient = iHTTPSession->StringPool().OpenFStringL( KUserAgentClientId );
+    CleanupClosePushL<RStringF>( uaClient );
+    iTransaction.PropertySet().SetPropertyL( uaClient, THTTPHdrVal( KUserAgentCliMMS ) );
+    CleanupStack::PopAndDestroy( &uaClient );
+    
+    // no response headers yet
+    iGotResponseHeaders = EFalse; 
+
+    // Perform the actual sending
+    iTransaction.SubmitL();
+    
+    // Start observer to monitor notifications
+    iTransferControl->SubscribeNotification( (MMmsTransportObserver*) this );
+    
+    iRequestOngoing = ETrue;
+
+    iStatus = KRequestPending;
+    SetActive();
+    }
+
+// ---------------------------------------------------------
+// FinishL
+// ---------------------------------------------------------
+//
+void CMmsTransaction::FinishL()
+    {
+    LOG( _L("CMmsTransaction::FinishL") );
+
+    //
+    // Cancel timer and observer
+    //
+    iTransferControl->Cancel();
+
+    //
+    // Completion came from own timers
+    //
+    if( iStatus == KMmsErrorTimeout || iStatus == KMmsErrorSuspendTimeout )
+        {
+        LOG( _L(" - MmsEngine's timer expired") );
+        if( iRequestOngoing )
+            {
+            iTransaction.Cancel();
+            }
+
+        if( iGotBodyData && iLastChunk )
+            {
+            LOG( _L(" - Data has been received, returning OK") );
+            iError = KErrNone;
+            }
+        else // no body data, emptying 
+            {
+            iBuffer->Reset();
+            }
+        return;
+        }
+
+    //
+    // Pause the timer (later it will be put back on, or cancelled)
+    //
+    iTimer->Pause();
+
+    //
+    // Completion came from HTTP stack
+    // go through switch based on event status
+    //
+    switch ( iEvent.iStatus )
+        {
+        // Successful case
+        case THTTPEvent::ESucceeded:
+            {
+            iRetryCount = 0;
+            iSuspendOccurred = EFalse;
+            iError = KErrNone; // successful.
+            LOG( _L(" - HTTP status OK") );
+            // If there was response data, we've already got it
+            break;
+            }
+
+        // Failure cases
+        case THTTPEvent::EFailed:
+            {
+            LOG2( _L(" - HTTP failed with eventcode %d"), iEvent.iStatus );
+
+            //
+            // Check for suspend cases:
+            // Errors due to GPRS suspend are handled either with immediate retry
+            // or immediatedly after suspend has resumed. Anyway, attempt is not given up.
+            //
+            if( ( iTransferControl->IsSuspended() || iSuspendOccurred ) &&
+                !iGotResponseHeaders && iRetryCount < KMmsMaxRetryCount )
+                {
+                // If we haven't got any response headers yet, no need to reset the
+                // data sink as it has not been given any data yet
+                iRetryCount++;
+                iTransaction.Close();
+                iTransactionOpened = EFalse; // closed already
+                iRequestOngoing = EFalse;
+                iState = EMmsIdle;
+                iError = KErrNone;
+                iSuspendOccurred = EFalse;
+                
+                if( !iTransferControl->IsSuspended() ) // i.e. iSuspendOccurred
+                    {
+                    LOG( _L(" - Suspend has occurred, but has resumed -> trying again immediatedly!") );
+                    TRequestStatus* status = &iStatus; 
+                    User::RequestComplete( status, KErrNone );
+                    iTimer->Continue();
+                    SetActive();
+                    return;
+                    }
+                else // GPRS is now suspended
+                    {
+                    LOG( _L(" - GPRS is now Suspended -> waiting for resume before retry.") );
+                    // Subscribe notifications from transferControl watcher
+                    // Also starting timer
+                    iTransferControl->SubscribeNotification( (MMmsTransportObserver*) this );
+                    iTimer->Continue();
+
+                    // Not completing here.. it will be done by the observer or the timer
+                    iStatus = KRequestPending;
+                    SetActive();
+                    return;
+                    }
+                }
+
+            //
+            // For some reason connection to network has been cut off after the first transaction.
+            // The following code tries immediate resend for the transaction if this has been the case.
+            // 
+            if( iStatus == KErrDisconnected     // IF connection has been cut off
+                && iRetryCount < 1 )            // AND no retries have been tried yet
+                {
+                LOG( _L(" - Connection has been disconnected, re-submitting.") );
+
+                iRetryCount++;
+                iState = EMmsSendingTransactionRequest;
+
+                iCumulativeSize = 0;
+                // reset data sink in case we got partial data.
+                // If the data sink is not in chunked mode, this does nothing
+                iDataSink->ResetDataSink();
+                // Just re-submitting the transaction
+                iTransaction.SubmitL();
+
+                iRequestOngoing = ETrue;
+                iError = KErrNone;
+                
+                // Start timer running
+                iTimer->Continue();
+                // start bearer observer
+                iTransferControl->SubscribeNotification( (MMmsTransportObserver*) this );
+
+                iStatus = KRequestPending;
+                SetActive();
+                return;
+                }
+                
+            //
+            // Timer can now be cancelled
+            //
+            iTimer->Cancel();
+
+            RHTTPResponse resp = iTransaction.Response();
+            LOG2( _L(" - HTTP status code %d "), resp.StatusCode() );
+
+            TInt error = KErrNone;
+            // crude mapping only based on first number
+            const TInt KMms100 = 100;
+            switch ( ( resp.StatusCode() / KMms100 ) * KMms100 )
+                {
+                case KMmsHTTP100: // 1xx Informative
+                    // We map "informative" to KErrNone.
+                    // Further analysis is needed to tell if there was a real error or not
+                    // Decoding of response from MMSC will determine the final error.
+                    error = KErrNone;
+                    break;
+                case KMmsHTTP200: // 2xx OK
+                    error = KErrNone;
+                    break;
+                case KMmsHTTP300: // 3xx Multiple choices
+                    error = KMmsErrorHTTPConfiguration;
+                    break;
+                case KMmsHTTP400: // 4xx Client error
+	                if( resp.StatusCode() == KMmsHTTP404 )
+                        {
+            	        error = KMmsErrorHTTPNotFound;
+                        }
+                    else if ( resp.StatusCode() == KMmsHTTP413 )
+                        {
+                        error = KMmsErrorEMRUExceeded;
+                        }
+                    else
+                        {
+                        error = KMmsErrorHTTPClientError;
+                        }
+                    break;
+                case KMmsHTTP500: // 5xx Server error
+                    error = KMmsErrorHTTPServerDown;
+                    break;
+                default:
+                    error = KErrUnknown;
+                    break;
+                }
+            
+            // If we get clear HTTP error code, we use it.
+            // Usually iError is KErrUnknown at this point if we have got an error event.
+            if ( error != KErrNone )
+                {
+                // If we have some previous error, KErrUnknown does not override
+                if ( error != KErrUnknown || iError == KErrNone )
+                    {
+                    iError = error;
+                    }
+                }
+
+            TBool mmsMessage = EFalse; // check if response data is of correct type
+            if( iGotResponseHeaders )
+                {
+                RHTTPHeaders hdr = resp.GetHeaderCollection();
+                RStringPool stringPool = iHTTPSession->StringPool();
+                RStringF contentType = stringPool.StringF(
+                    HTTP::EContentType, RHTTPSession::GetTable() );
+                THTTPHdrVal fieldVal;
+                if( hdr.GetField( contentType, 0, fieldVal ) == KErrNone )
+                    {
+                    // content type must match with KMmsMessageContentType
+                    RStringF valStr = stringPool.OpenFStringL( KMmsMessageContentType );
+                    CleanupClosePushL( valStr );
+                    if ( valStr == fieldVal.StrF() )
+                        {
+                        mmsMessage = ETrue;
+                        }
+                    CleanupStack::PopAndDestroy( &valStr );
+                    }
+                }
+
+            // If we got body data, we clear error in order to make
+            // mms server mtm to decode what we got.
+            // - But only if the content type really was "application/vnd.wap.mms-message"
+            // It is possible that the message is incomplete, but in some cases
+            // HTTP stack is not sure if it got all the data or not, so we must check
+            // if what we got can be decoded without problems.
+            // Because chunked decoding would cause problems otherwise,
+            // We can declare "no error" only if we got the last chunk
+            if( iGotBodyData && mmsMessage && iLastChunk )
+                {
+                iError = KErrNone;
+                }
+            break;
+            }
+
+        default:
+            LOG( _L("CMmsTransaction::FinishL(): Unknown response") );
+            // Other event = error
+            // - but don't override the error if we already got something.
+            if ( iError == KErrNone )
+                {
+                iError = KMmsErrorUnknownRespFromGw;
+                }
+            break;
+         }
+    
+    iSuspendOccurred = EFalse;
+    iRequestOngoing = EFalse;
+    iStatus = iError;
+    iState = EMmsIdle;
+    }
+
+// ---------------------------------------------------------
+// DoCancel
+// ---------------------------------------------------------
+//
+void CMmsTransaction::DoCancel()
+    {
+    LOG( _L("CMmsTransaction::DoCancel") );
+    if ( iRequestOngoing )
+        {
+        iTransaction.Cancel();
+        iRequestOngoing = EFalse;
+        }
+    iTimer->Cancel();
+    iTransferControl->Cancel();
+    iState = EMmsIdle;
+    
+    // Complete ourselves as no-one else is going to do it
+    TRequestStatus* status = &iStatus;
+    User::RequestComplete( status, KErrCancel );
+
+    // This completes the caller (session)
+    CMsgActive::DoCancel();
+    }
+
+// ---------------------------------------------------------
+// DoComplete
+// ---------------------------------------------------------
+//
+void CMmsTransaction::DoComplete( TInt& )
+    {
+    // just to be sure that nothing is left active, cancel
+    // all operations that may still be pending.
+    LOG( _L("CMmsTransaction::DoComplete") );
+
+    iTimer->Cancel();
+    iTransferControl->Cancel();
+
+    // transaction is automatically cancelled during close
+    if ( iTransactionOpened )
+        {
+        RHTTPHeaders hdr = iTransaction.Request().GetHeaderCollection();
+        hdr.RemoveAllFields(); // clean up
+        iTransaction.Close();
+        iTransactionOpened = EFalse; // closed already
+        }
+    if ( iDataSink )
+        {
+        // release data sink does nothing if there is nothing to release
+        // it is safe to call it.
+        iDataSink->RelaseDataSink();
+        iDataSink = NULL;
+        }
+        
+    // in case we have run out of memory or code leaves for some other
+    // fatal reason, we make sure we are idle for the next loop.
+    iState = EMmsIdle;
+    }
+    
+// ---------------------------------------------------------
+// TimerExpired
+// ---------------------------------------------------------
+//
+void CMmsTransaction::TimerExpired()
+    {
+    LOG( _L("CMmsTransaction::TimerExpired") );
+    // No transfer cancellation any more
+    iTransferControl->Cancel();
+
+    if( !IsActive() )
+        {
+        iStatus = KRequestPending;
+        SetActive();
+        }
+
+    // Setting the state so that we end up to FinishL() method
+    iState = EMmsSendingTransactionRequest;
+
+    // We complete with MMS Engine's own Timeout value in order 
+    // to avoid confusion with timeouts coming from lower layers
+    TRequestStatus* status = &iStatus; 
+    User::RequestComplete( status, KMmsErrorTimeout );
+        
+    iRetryCount = 0;
+    }
+
+// ---------------------------------------------------------
+// GprsSuspended
+// ---------------------------------------------------------
+//
+void CMmsTransaction::GprsSuspended()
+    {
+    LOG( _L("CMmsTransaction::GprsSuspended") );
+    }
+    
+// ---------------------------------------------------------
+// GprsResumed
+// ---------------------------------------------------------
+//
+void CMmsTransaction::GprsResumed()
+    {
+    //
+    // GPRS context has resumed
+    //
+    LOG( _L("CMmsTransaction::GprsResumed") );
+    if( iRequestOngoing )
+        {
+        // Set flag indicating that gprs was suspended at some stage
+        iSuspendOccurred = ETrue;
+        iTransferControl->SubscribeNotification( (MMmsTransportObserver*) this );
+        }
+    else
+        {
+        // No request ongoing yet, start state machine by completing ourselves
+        iTransferControl->Cancel();
+        TRequestStatus* status = &iStatus; 
+        User::RequestComplete( status, KErrNone );
+        }
+    }
+
+// ---------------------------------------------------------
+// TransferCancelled
+// ---------------------------------------------------------
+//
+void CMmsTransaction::TransferCancelled()
+    {
+    LOG( _L("CMmsTransaction::TransferCancelled") );
+    iTimer->Cancel();
+    
+    // It is possible that this method is called when there is no
+    // transaction going i.e. object is not active.
+    // It means that in that case we have complete ourselves 
+    if( !IsActive() )
+        {
+        iStatus = KRequestPending;
+        SetActive();
+        TRequestStatus* status = &iStatus; 
+        User::RequestComplete( status, KMmsErrorTransferCancelled );
+        }
+    else
+        {
+        // cancel getting events from the stack
+        if ( iRequestOngoing )
+            {
+            iTransaction.Cancel();
+            iRequestOngoing = EFalse;
+            }
+        iError = KMmsErrorTransferCancelled;
+        TRequestStatus* status = &iStatus; 
+        User::RequestComplete( status, KMmsErrorTransferCancelled );
+        }    
+    }
+
+// ---------------------------------------------------------
+// SetHeaderL
+// ---------------------------------------------------------
+//
+void CMmsTransaction::SetHeaderL( RHTTPHeaders aHeaders, TInt aHdrField, const TDesC8& aHdrValue )
+    {
+    RStringF valStr = iHTTPSession->StringPool().OpenFStringL( aHdrValue );
+    THTTPHdrVal val( valStr );
+    CleanupClosePushL( valStr );
+    aHeaders.SetFieldL(
+        iHTTPSession->StringPool().StringF( aHdrField, RHTTPSession::GetTable() ), val );
+    CleanupStack::PopAndDestroy( &valStr );
+    }
+
+// ---------------------------------------------------------
+// SetHostHeaderL
+// ---------------------------------------------------------
+//
+void CMmsTransaction::SetHostHeaderL( RHTTPHeaders aHeaders )
+    {
+    LOG( _L("CMmsTransaction::SetHostHeaderL()") );
+    //
+    // Get needed URI parts
+    //
+    TUriC8 uri3 = iTransaction.Request().URI();
+    TPtrC8 hostPtr = uri3.Extract(EUriHost); 
+    TPtrC8 portPtr = uri3.Extract(EUriPort); 
+
+    // If empty, nothing can be done
+    if( hostPtr.Length() == 0 )
+        {
+        return;
+        }
+
+    //
+    // If host-type == IPv6 -> include '[' and ']' characters to URI
+    //
+    UriUtils::TUriHostType hosttype = UriUtils::HostType( hostPtr );
+    if( hosttype == UriUtils::EIPv6Host )
+        {
+        LOG( _L(" - host type == IPv6") );
+        const TInt KMmsRoomForBrackets = 2;
+        hostPtr.Set( hostPtr.Ptr()-1, hostPtr.Length() + KMmsRoomForBrackets );
+        }
+
+    //
+    // If port not equal to 80 -> include it
+    //
+    _LIT8( KMmsPort, "80" );
+    if ( portPtr.Length() > 0 && portPtr.Compare( KMmsPort ) )
+        {
+        hostPtr.Set( hostPtr.Ptr(), hostPtr.Length() + 1 + portPtr.Length() );
+        }
+
+    //
+    // Remove possible old host-header and set new one
+    //
+    aHeaders.RemoveField( iHTTPSession->StringPool()
+        .StringF( HTTP::EHost, RHTTPSession::GetTable() ) );
+    SetHeaderL( aHeaders, HTTP::EHost, hostPtr );
+    }
+
+// ---------------------------------------------------------
+// MHFRunL
+// ---------------------------------------------------------
+//
+void CMmsTransaction::MHFRunL( RHTTPTransaction aTransaction, const THTTPEvent& aEvent )
+    {
+    TRequestStatus* status = &iStatus;
+    iEvent = aEvent;
+
+    // Switch through the events
+    switch (aEvent.iStatus)
+        {
+        case THTTPEvent::EGotResponseHeaders:
+            LOG( _L("CMmsTransaction::MHFRunL: Got response headers") );
+            {
+            // Clear buffer but do not compress
+            // We get the data in small chunks, and reallocating all the time
+            // may make us to run out of memory too soon.
+            iBuffer->Delete( 0, iBuffer->Size() );
+            iBuffer->ResizeL( 0 );
+            iGotResponseHeaders = ETrue; // Got response headers, can check content type
+            iDataChunkNumber = 0; // reset in case of retry
+            } 
+            break;
+
+        case THTTPEvent::EGotResponseBodyData:
+            {
+            LOG( _L("CMmsTransaction::MHFRunL: Got response body data") );
+            // Get the data
+            TPtrC8 bodyData;
+            MHTTPDataSupplier* dataSupplier = aTransaction.Response().Body();
+            iLastChunk = EFalse;
+            iLastChunk = dataSupplier->GetNextDataPart( bodyData );
+            
+            // We must remove CR and LF characters from the beginning of
+            // the first data chunk representing message body because some
+            // network proxies seem to add an extra CR-LF between headers and body.
+            // (A well formed MMS PDU always begins with 0x8C, so that we 
+            // never lose anything from a well formed PDU, and if the PDU
+            // is not well formed, it is a mess anyway.)
+            if ( iDataChunkNumber == 0 )
+                {
+                iPosition = 0;
+                iCumulativeSize = 0; // first data chunk, start counting from here
+                TInt overallDataSize = dataSupplier->OverallDataSize();
+#ifndef _NO_MMSS_LOGGING_
+                if ( iMethod == HTTP::EGET && iMaxReceiveSize != 0)
+                    {
+                    LOG2( _L("- can accept only %d bytes"), iMaxReceiveSize );
+                    }
+                if ( overallDataSize != KErrNotFound )
+                    {
+                    LOG2( _L("- going to get at least %d bytes"), overallDataSize );
+                    }
+                else
+                    {
+                    LOG( _L("- chunked transfer encoding used") );
+                    }
+#endif  //_NO_MMSS_LOGGING_
+                TInt removedChars = 0;
+                while ( bodyData.Find( KCr8 ) == 0 || bodyData.Find( KLf8 ) == 0 )
+                    {
+#ifndef _NO_MMSS_LOGGING_
+                    if ( bodyData.Find( KCr8 ) == 0 )
+                        {
+                        LOG( _L("- removed CR") );
+                        }
+                    else
+                        {
+                        LOG( _L("- removed LF") );
+                        }
+#endif  //_NO_MMSS_LOGGING_
+                    // remove first character
+                    bodyData.Set( bodyData.Mid( 1 ) );
+                    removedChars++;
+                    }
+
+                //
+                // Check if we are about to receive too much data
+                //
+                if ( iMethod == HTTP::EGET &&
+                    iMaxReceiveSize != 0 &&
+                    overallDataSize != KErrNotFound &&
+                    overallDataSize - removedChars > iMaxReceiveSize )
+                    {
+                    // The overall data size is more than what we want
+                    iError = KMmsErrorEMRUExceeded;
+                    }
+
+                //
+                // Size buffer to max message size
+                //
+                if ( iMethod == HTTP::EGET && iError != KMmsErrorEMRUExceeded )
+                    {
+                    LOG2( _L("- reserving %d bytes for receiving."), iExpectedReceiveSize );
+                    iBuffer->ResizeL( iExpectedReceiveSize );
+                    iBuffer->ResizeL( 0 );
+                    }
+                } // (iDataChunkNumber == 0) block
+
+            //
+            // Add the chunk number counter every time body data is received
+            //
+            iDataChunkNumber++;
+
+            // When sending MMS, message size is not checked
+            // When receiving MMS, size is checked against iMaxReceiveSize
+            // If iMaxReceiveSize == 0 -> no check
+            // If size > iMaxReceiveSize: "KMmsErrorEMRUExceeded"
+            iCumulativeSize += bodyData.Length();
+            if( iMethod == HTTP::EGET && 
+                iMaxReceiveSize > 0 && 
+                iCumulativeSize > iMaxReceiveSize )
+                {
+                iError = KMmsErrorEMRUExceeded;
+                }
+
+            //
+            // Check if we are getting too much data (compared to iMaxReceiveSize)
+            //
+            if ( iError == KMmsErrorEMRUExceeded )
+                {
+                // Release data and cancel transaction
+                // The following two statements have to be executed in this order
+                dataSupplier->ReleaseData();
+                aTransaction.Cancel();
+
+                // we say we did not get anything even if we might have fetched
+                // part of the data
+                iGotBodyData = EFalse;
+                iDataSink->RelaseDataSink();
+
+                // transaction was cancelled -> not getting any more events:
+                User::RequestComplete( status, iError ); 
+                }
+            else // (iError not equal to KMmsErrorEMRUExceeded)
+                {
+                iBuffer->ResizeL( bodyData.Length() + iPosition );
+                TInt currentData = bodyData.Length();
+                iBuffer->Write( iPosition, bodyData, currentData );
+                // release the databuffer
+                dataSupplier->ReleaseData();
+#ifndef _NO_MMSS_LOGGING_
+                LOG3( _L("- chunk size %d (cumulative %d)"), currentData, iCumulativeSize );
+                if ( iLastChunk )
+                    {
+                    LOG( _L("- last data chunk received!") );
+                    }
+#endif  //_NO_MMSS_LOGGING_
+
+                iPosition = 0;
+/////                
+                iError = iDataSink->NextDataPart( *iBuffer, iPosition, iLastChunk );
+////                
+                // if something goes wrong when saving the data, the transaction should be
+                // cancelled
+                // But if we got last chunk already, let the HTTP complete normally
+                if ( iError != KErrNone && !iLastChunk )
+                    {
+                    // We can only get an error here if we are in chunked mode
+                    // cancel transaction
+                    aTransaction.Cancel();
+                    iDataSink->RelaseDataSink();
+
+                    // transaction was cancelled -> not getting any more events:
+                    User::RequestComplete( status, iError );
+                    return; 
+                    }
+                
+                TInt amount = iBuffer->Size() - iPosition;
+                if ( iPosition != 0 )
+                    {
+                    // some data handled
+                    iBuffer->Delete( 0, iPosition );
+                    }
+                iBuffer->ResizeL( amount );
+                iPosition = amount; // continue writing from here
+
+                if ( iLastChunk )
+                    {
+                    iDataSink->RelaseDataSink();
+                    }
+
+                // If we get at least some body data, we continue to decode it even if 
+                // it might not be complete.
+                // The HTTP session may disconnect too soon, and HTTP stack may report error
+                // even if we already got all our body data.
+                iGotBodyData = ETrue;
+                }
+            } 
+            break;
+        case THTTPEvent::EResponseComplete:
+            {
+            LOG( _L("CMmsTransaction::MHFRunL: response complete") );
+            // never mind, ESucceeded or EFailed will follow
+            break;
+            }
+
+        case THTTPEvent::ESucceeded:
+            {
+            LOG( _L("CMmsTransaction::MHFRunL: Succeeded") );
+            LOG2( _L("- Got %d data chunks"), iDataChunkNumber );
+            User::RequestComplete( status, KErrNone );
+            break;
+            }
+
+        case THTTPEvent::EFailed:
+            {
+            LOG( _L("CMmsTransaction::MHFRunL: Failed") );
+            LOG2( _L("- Got %d data chunks"), iDataChunkNumber );
+            if ( iError == KErrNone )
+                {
+                iError = KErrUnknown;
+                }
+            User::RequestComplete( status, iError ); 
+            break;
+            }
+
+        default:
+            {
+            LOG2( _L("CMmsTransaction::MHFRunL: unknown event %d"), aEvent.iStatus );
+            // save the error, but don't override a possible earlier error
+            if ( aEvent.iStatus < 0 && iError == KErrNone )
+                {
+                iError = aEvent.iStatus;
+                }
+            break;
+            }
+        }
+    }
+
+// ---------------------------------------------------------
+// MHFRunError
+// ---------------------------------------------------------
+//
+TInt CMmsTransaction::MHFRunError(
+    TInt aError,
+    RHTTPTransaction aTransaction, 
+    const THTTPEvent& aEvent )
+    {
+    LOG2( _L("CMmsTransaction::MHFRunError: %d"), aError );
+    iEvent = aEvent;
+    iError = aError;
+    aTransaction.Close();
+    iTransactionOpened = EFalse;
+    iDataSink->RelaseDataSink();
+    TRequestStatus* status = &iStatus;
+    User::RequestComplete( status, aEvent.iStatus ); 
+    return KErrNone;
+    }
+
+// ---------------------------------------------------------
+// GetNextDataPart
+// ---------------------------------------------------------
+//
+TBool CMmsTransaction::GetNextDataPart( TPtrC8& aDataPart )
+    {
+    iLastChunk = EFalse;
+    iError = iDataSupplier->GetNextDataPart( aDataPart, iLastChunk );
+    iChunkSize = aDataPart.Size();
+    LOG2( _L("CMmsTransaction::GetNextDataPart, Chunk size %d"), iChunkSize );
+#ifndef _NO_MMSS_LOGGING_
+    if ( iLastChunk )
+        {
+        LOG( _L("CMmsTransaction:: last chunk") );
+        }
+#endif  //_NO_MMSS_LOGGING_
+    
+    
+    if ( iError != KErrNone )
+        {
+        // could not get data - cancel the transaction
+        iTransaction.Cancel();
+        }
+
+    return iLastChunk;
+    }
+
+// ---------------------------------------------------------
+// ReleaseData
+// ---------------------------------------------------------
+//
+void CMmsTransaction::ReleaseData()
+    {
+    iCumulativeSize += iChunkSize;
+    iChunkSize = 0;
+    LOG2( _L("CMmsTransaction::ReleaseData, cumulative %d"), iCumulativeSize );
+    
+    TInt error = iDataSupplier->ReleaseData();
+    if ( iError != KErrNone )
+        {
+        iError = error;
+        }
+    
+    if ( iError != KErrNone )
+        {
+        // could not get more data - cancel the transaction
+        iTransaction.Cancel();
+        }
+    else if ( !iLastChunk )
+        {
+        // If we have sent last chunk already, there is no more body data
+        // If the function leaves, there is nothing we can do about it.
+        // ReleaseData() must not leave.
+        TRAP_IGNORE( iTransaction.NotifyNewRequestBodyPartL() );
+        }
+    // else nont needed - no operation    
+    }
+
+// ---------------------------------------------------------
+// OverallDataSize
+// ---------------------------------------------------------
+//
+TInt CMmsTransaction::OverallDataSize()
+    {
+    TInt dataSize = iDataSupplier->OverallDataSize();
+    LOG2( _L("CMmsTransaction::OverallDataSize %d"), dataSize );
+    
+    return dataSize;
+    }
+
+// ---------------------------------------------------------
+// Reset
+// ---------------------------------------------------------
+//
+TInt CMmsTransaction::Reset()
+    {
+    LOG( _L("CMmsTransaction::Reset") );
+    // We always point to the start of data
+//    return KErrNone;
+    return iDataSupplier->ResetSupplier();
+
+    }
+
+//  End of File