diff -r 000000000000 -r 72b543305e3a mmsengine/mmshttptransport/src/mmstransaction.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mmsengine/mmshttptransport/src/mmstransaction.cpp Thu Dec 17 08:44:11 2009 +0200 @@ -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 +#include +#include + +#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( 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