/*
* 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 )
{
// Successfull 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