--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/upnp/upnpstack/upnphttptransfer/src/httpuploadworker.cpp Tue Feb 02 01:12:20 2010 +0200
@@ -0,0 +1,763 @@
+/** @file
+* Copyright (c) 2007 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: Handles upload of one file
+*
+*/
+
+
+// User include files
+#include "httpuploadworker.h"
+
+// Constants
+_LIT8( KModuleName, "HttpTransfer" );
+_LIT8( KAllowedData, "*/*" );
+_LIT8( KContentTypeName, "Content-Type" );
+_LIT8( KDefaultContentType, "text/plain" );
+_LIT8( KExpect, "Expect" );
+_LIT8( K100Continue, "100-continue" );
+
+const TUint KDefaultBufferSize = 64*KKilo;
+const TUint KMaxBufferSize = 128*KKilo;
+
+// Time to wait for response after sending 100 continue post request.
+// Value in microseconds.
+const TInt K100ContinueResponseWaitTime = 3000000; // 3 seconds
+
+// ======== MEMBER FUNCTIONS ========
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::CHttpUploadWorker()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+CHttpUploadWorker::CHttpUploadWorker( MHttpTransferObserver& aObserver,
+ TUint /*aIAPId*/,
+ TUint aBufferSize,
+ MHttpWorkerObserver& aCallback )
+ {
+ iObserver = &aObserver;
+ iCallback = &aCallback;
+
+ iProcessState = EHttpTransactionIdle;
+
+ // Check that size of the buffer is between boundaries
+ if ( aBufferSize <= 0 )
+ {
+ iBufferSize = KDefaultBufferSize;
+ }
+ else if ( aBufferSize > KMaxBufferSize )
+ {
+ iBufferSize = KMaxBufferSize;
+ }
+ else
+ {
+ iBufferSize = aBufferSize;
+ }
+
+ iSendDataCount = 0;
+ iBodyFileOffset = 0;
+ iHttpStatus = 0;
+ iMoreToCome = EFalse;
+ iAlreadyDone = EFalse;
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::ConstructL()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+void CHttpUploadWorker::ConstructL()
+ {
+ CHttpTransferWorker::ConstructL();
+ ConnectL();
+ iSessionTimer = CHttpNotifyTimer::NewL( this );
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::NewL()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+CHttpUploadWorker* CHttpUploadWorker::NewL(
+ MHttpTransferObserver& aObserver,
+ TUint aIAPId,
+ TUint aBufferSize,
+ MHttpWorkerObserver& aCallback )
+ {
+ CHttpUploadWorker* self = CHttpUploadWorker::NewLC( aObserver,
+ aIAPId,
+ aBufferSize,
+ aCallback );
+ CleanupStack::Pop( self );
+ return self;
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::NewLC()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+CHttpUploadWorker* CHttpUploadWorker::NewLC(
+ MHttpTransferObserver& aObserver,
+ TUint aIAPId,
+ TUint aBufferSize,
+ MHttpWorkerObserver& aCallback )
+ {
+ CHttpUploadWorker* self = new( ELeave ) CHttpUploadWorker( aObserver,
+ aIAPId,
+ aBufferSize,
+ aCallback );
+ CleanupStack::PushL( self );
+ self->ConstructL();
+ return self;
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::~CHttpUploadWorker()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+CHttpUploadWorker::~CHttpUploadWorker()
+ {
+ delete iRequestBodyBuffer;
+ delete iSessionTimer;
+
+ // Destroy the 100 continue timer if it exists.
+ Destroy100ContinueTimer();
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::CancelTransfer()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+void CHttpUploadWorker::CancelTransfer()
+ {
+ iSessionTimer->Cancel();
+
+ // Destroy the 100 continue response wait timer if it exists.
+ Destroy100ContinueTimer();
+
+ if ( iProcessState == EHttpWaitingForStart )
+ {
+ delete iProcessedFile;
+ iProcessedFile = NULL;
+ iProcessState = EHttpTransactionIdle;
+ iCallback->WorkerCompleted();
+ }
+ // if process is finished do nothing
+ else if ( iProcessState != EHttpTransactionIdle )
+ {
+ iHttpTransaction.Cancel();
+
+ delete iRequestBodyBuffer;
+ iRequestBodyBuffer = NULL;
+
+ iProcessState = EHttpTransactionIdle;
+ iHttpTransaction.Close();
+
+ // call reset so that this worker can be used for the next download
+ Reset();
+
+ delete iProcessedFile;
+ iProcessedFile = NULL;
+
+ iOverallDataSize = 0;
+ iFile.Close();
+
+ // inform the worker observer
+ iCallback->WorkerCompleted();
+ }
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::MapPostTransactionError()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+TInt CHttpUploadWorker::MapPostTransactionError( TInt aError ) const
+ {
+ TInt error = KErrNone;
+ switch (aError)
+ {
+ case HTTPStatus::EForbidden:
+ {
+ error = KErrAccessDenied;
+ break;
+ }
+
+ case HTTPStatus::EMethodNotAllowed:
+ {
+ error = KErrAccessDenied;
+ break;
+ }
+
+ case HTTPStatus::EConflict:
+ {
+ // resource already exists .
+ error = KErrAlreadyExists;
+ break;
+ }
+
+ case HTTPStatus::EUnsupportedMediaType:
+ {
+ error = KErrNotSupported;
+ break;
+ }
+
+ case HTTPStatus::EInternalServerError:
+ {
+ error = KErrNotSupported;
+ break;
+ }
+
+ case HTTPStatus::EMovedPermanently:
+ {
+ // fall through
+ }
+
+ case HTTPStatus::ETemporaryRedirect:
+ {
+ // It is not allowed to write a file with the same name
+ // as an existing directory.
+ error = KErrGeneral;
+ break;
+ }
+
+ default:
+ {
+ if ( HTTPStatus::IsServerError( aError ) )
+ {
+ error = KErrNotSupported;
+ }
+ else if ( HTTPStatus::IsClientError( aError ) )
+ {
+ error = KErrAccessDenied;
+ }
+ else if ( HTTPStatus::IsRedirection( aError ) )
+ {
+ error = KErrNotFound;
+ }
+ else
+ {
+ if ( aError > 0 )
+ {
+ // An arbitrary choice for error codes that
+ // should not occur
+ error = KErrAccessDenied;
+ }
+ }
+ break;
+ }
+ }
+
+ return error;
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::CompleteAndNotify()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+void CHttpUploadWorker::CompleteAndNotify( TInt aError )
+ {
+ iSessionTimer->Cancel();
+
+ iProcessState = EHttpTransactionIdle;
+ iHttpTransaction.Cancel();
+ iHttpTransaction.Close();
+
+ delete iRequestBodyBuffer;
+ iRequestBodyBuffer = NULL;
+
+ // call reset so that this worker can be used for the next download
+ Reset();
+ if ( iProcessedFile )
+ {
+ iObserver->TransferCompleted( iProcessedFile->Key(), aError );
+ }
+
+ delete iProcessedFile;
+ iProcessedFile = NULL;
+
+ iOverallDataSize = 0;
+ iFile.Close();
+
+ // inform the worker observer
+ iCallback->WorkerCompleted();
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::StartProcessL()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+void CHttpUploadWorker::StartProcessL()
+ {
+ CHttpTransferWorker::StartProcessL();
+
+ SendL();
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::SendL()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+void CHttpUploadWorker::SendL()
+ {
+ // iAlreadyDone need to be reset
+ iSendDataCount = 0;
+ iBodyFileOffset = 0;
+ iHttpStatus = 0;
+ iMoreToCome = EFalse;
+ iAlreadyDone = EFalse;
+ // Open the file in the file system
+ User::LeaveIfError( iFile.Open( iFsSession,
+ *(iProcessedFile->Path()),
+ EFileShareReadersOnly | EFileRead ) );
+
+ User::LeaveIfError( iFile.Size( iOverallDataSize ) );
+
+ // get read buffer size from the Central Repository
+ iRequestBodyBuffer = HBufC8::NewL( iBufferSize );
+
+ InvokeHttpMethodL();
+
+ // Change state of the state machine
+ iProcessState = EHttpPostSent;
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::InvokeHttpMethodL()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+void CHttpUploadWorker::InvokeHttpMethodL()
+ {
+ // This actually creates the transaction, sets the headers and body
+ // and then starts the transaction.
+ TBool contentTypeExists = EFalse;
+
+ // open HTTP transaction
+ TUriParser8 uri;
+ User::LeaveIfError( uri.Parse( *( iProcessedFile->Uri() ) ) );
+
+ // POST HTTP method
+ RStringF method = iStringPool.StringF( HTTP::EPOST,
+ RHTTPSession::GetTable() );
+ CleanupClosePushL( method );
+
+ iHttpTransaction = iSession.OpenTransactionL( uri, *this, method );
+
+ RHTTPHeaders hdr = iHttpTransaction.Request().GetHeaderCollection();
+
+ SetHeaderL( hdr, HTTP::EUserAgent, KModuleName );
+ SetHeaderL( hdr, HTTP::EAccept, KAllowedData );
+
+ //Set the property of upnphttptransfer to ENotifyOnDisconnect
+ //Set the property of HTTP Transaction to EEnableDisconnectNotification
+ //The MHFRunL can get the really http error.
+ iHttpTransaction.PropertySet().SetPropertyL(
+ iSession.StringPool().StringF(
+ HTTP::ENotifyOnDisconnect,RHTTPSession::GetTable() ),
+ iSession.StringPool().StringF(
+ HTTP::EEnableDisconnectNotification,
+ RHTTPSession::GetTable() ) );
+ // Add arbitrary Http headers
+ RPointerArray<CHttpHeader> headerArray = iProcessedFile->Headers();
+ TBool expect100ContinueFound = EFalse;
+ for ( TInt i = 0; i < headerArray.Count(); i++ )
+ {
+ const TDesC8& headerName = headerArray[i]->FieldName();
+ const TDesC8& headerValue = headerArray[i]->FieldValue();
+
+ SetHeaderL( hdr,
+ headerName,
+ headerValue );
+ if ( headerName == KContentTypeName() )
+ {
+ contentTypeExists = ETrue;
+ }
+ if ( headerName == KExpect() )
+ {
+ if ( headerValue == K100Continue() )
+ {
+ // Wait for 100 continue before sending the body in case of
+ // "Expect: 100-Continue" header.
+ RHTTPTransactionPropertySet propSet =
+ iHttpTransaction.PropertySet();
+ RStringF notifyContinue =
+ iStringPool.StringF(
+ HTTP::ENotify100Continue,
+ RHTTPSession::GetTable() );
+ RStringF enableNotification =
+ iStringPool.StringF(
+ HTTP::EEnableNotification,
+ RHTTPSession::GetTable() );
+ THTTPHdrVal val( enableNotification );
+ propSet.SetPropertyL( notifyContinue, val );
+
+ // we found "Expect: 100-continue"-header
+ expect100ContinueFound = ETrue;
+
+ // Need to start a timer when sending the headers that waits
+ // for some time and if no response during that time, sends
+ // this event to continue with the body.
+ i100ContinueTimer = CPeriodic::NewL( CActive::EPriorityStandard );
+ i100ContinueTimer->Start(
+ K100ContinueResponseWaitTime, // time to cancel waiting
+ K100ContinueResponseWaitTime, // this is not actually used
+ TCallBack( Cancel100ContinueWaitL, this ) );
+ }
+ }
+ }
+
+ if ( contentTypeExists == EFalse )
+ {
+ SetHeaderL( hdr, HTTP::EContentType, KDefaultContentType );
+ }
+
+ if ( !expect100ContinueFound )
+ {
+ // "Expect: 100-continue" header not found. Do not wait for
+ // 100-continue. This might not be needed but it is added here to
+ // avoid possible regression by not changing the previous behaviour
+ // too much.
+ RHTTPTransactionPropertySet propSet = iHttpTransaction.PropertySet();
+ RStringF notifyContinue =
+ iStringPool.StringF(
+ HTTP::ENotify100Continue,
+ RHTTPSession::GetTable() );
+ RStringF disableNotification =
+ iStringPool.StringF(
+ HTTP::EDisableNotification,
+ RHTTPSession::GetTable() );
+ THTTPHdrVal val( disableNotification );
+ propSet.SetPropertyL( notifyContinue, val );
+ }
+
+ // Set Body data
+ MHTTPDataSupplier* dataSupplier = this;
+ iHttpTransaction.Request().SetBody( *dataSupplier );
+
+ // submit the request
+ iHttpTransaction.SubmitL();
+
+ //set timer
+ iSessionTimer->Cancel();
+ iSessionTimer->AfterSeconds( KSessionTimeout );
+ CleanupStack::PopAndDestroy( &method );
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::MHFRunL()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+void CHttpUploadWorker::MHFRunL( RHTTPTransaction aTransaction,
+ const THTTPEvent& aEvent )
+ {
+ iSessionTimer->Cancel();
+
+ // HTTP status code
+ RHTTPResponse response = aTransaction.Response();
+ iHttpStatus = response.StatusCode();
+
+ // If HTTP status is not what we expected, better to quit
+ // status 0 will be handled in switch case default
+
+ if ( !( iHttpStatus == HTTPStatus::EOk ||
+ iHttpStatus == HTTPStatus::ECreated ||
+ iHttpStatus == HTTPStatus::ENoContent ||
+ iHttpStatus == HTTPStatus::EContinue ||
+ iHttpStatus == 0 ) )
+ {
+ CompleteAndNotify(
+ MapPostTransactionError( iHttpStatus ) );
+ }
+ else
+ {
+
+ switch ( aEvent.iStatus )
+ {
+ // process the headers
+ case THTTPEvent::EGotResponseHeaders:
+ {
+ // POST response headers
+ // according to the RFC, location header is here if
+ // the entity was created
+ if( !iMoreToCome )
+ {
+ // Everything has been sent, no need to wait ESucceeded
+
+ if ( ( iHttpStatus == HTTPStatus::EOk ) ||
+ ( iHttpStatus == HTTPStatus::ECreated ) ||
+ ( iHttpStatus == HTTPStatus::ENoContent ) )
+ {
+ CompleteAndNotify( KErrNone );
+ }
+ else
+ {
+ CompleteAndNotify(
+ MapPostTransactionError( iHttpStatus ) );
+ }
+ }
+ break;
+ }
+ case THTTPEvent::EGotResponseBodyData:
+ {
+ // discard the body data
+ MHTTPDataSupplier* responseBody =
+ aTransaction.Response().Body();
+ ASSERT( responseBody );
+ // Done with that bit of body data
+ responseBody->ReleaseData();
+ break;
+ }
+ case THTTPEvent::EResponseComplete:
+ {
+ // The transaction's response is complete
+ break;
+ }
+ case THTTPEvent::ESucceeded:
+ {
+ // check the status code and decide whether we
+ // actually succeeded or not
+
+ // inform observers
+ // according to the RFC we expect 200 OK,
+ // 204 NO CONTENT or
+ // 201 CREATED
+ if ( ( iHttpStatus == HTTPStatus::EOk ) ||
+ ( iHttpStatus == HTTPStatus::ECreated ) ||
+ ( iHttpStatus == HTTPStatus::ENoContent ) )
+ {
+ CompleteAndNotify( KErrNone );
+ }
+ else
+ {
+ CompleteAndNotify(
+ MapPostTransactionError( iHttpStatus ) );
+ }
+ break;
+ }
+ case THTTPEvent::EFailed:
+ {
+ // This is not supposed to do if transaction is finished
+ // happens when cancel is done
+ if ( iProcessState != EHttpTransactionIdle )
+ {
+ CompleteAndNotify(
+ MapPostTransactionError( iHttpStatus ) );
+ }
+
+ break;
+ }
+ case THTTPEvent::EReceived100Continue:
+ {
+ // The server responded with a 100-Continue status code.
+ // HTTP FW continues with sending the body.
+ Destroy100ContinueTimer();
+ break;
+ }
+ default:
+ {
+ if ( aEvent.iStatus < 0 )
+ {
+ CompleteAndNotify( aEvent.iStatus );
+ }
+ else
+ {
+ CompleteAndNotify( KErrGeneral );
+ }
+ break;
+ }
+ }
+ }
+ }
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::MHFRunError()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+TInt CHttpUploadWorker::MHFRunError( TInt /*aError*/,
+ RHTTPTransaction /*aTransaction*/,
+ const THTTPEvent& /* aEvent*/ )
+ {
+ // Just notify the client about the failure and return KErrNone to
+ // the stack indicating that we handled this error.
+ if ( iProcessState != EHttpTransactionIdle )
+ {
+ CompleteAndNotify( MapPostTransactionError( iHttpStatus ) );
+ }
+
+ return KErrNone;
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::GetNextDataPart()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+TBool CHttpUploadWorker::GetNextDataPart( TPtrC8& aDataPart )
+ {
+ if ( !iAlreadyDone )// Note: this is zero only when new part is requested
+ {
+ if ( iSendDataCount == 0 )
+ {
+ // first run
+ TInt pos = iBodyFileOffset;
+ iFile.Seek( ESeekStart, pos );
+ }
+
+ // We read data that will be given to the stack next time,
+ // or we will find out that there is no more data...
+ TInt readLength;
+ if ( ( iOverallDataSize - iSendDataCount ) >= iBufferSize )
+ {
+ readLength = iBufferSize;
+ }
+ else
+ {
+ readLength = iOverallDataSize - iSendDataCount;
+ }
+
+ TPtr8 requestBodyBufferPtr = iRequestBodyBuffer->Des();
+
+ TInt err = iFile.Read( requestBodyBufferPtr, readLength );
+ iSendDataCount = iSendDataCount + iRequestBodyBuffer->Length();
+ if ( err == KErrNone )
+ {
+ if ( ( iSendDataCount < iOverallDataSize ) &&
+ ( iRequestBodyBuffer->Length() > 0 ) )
+ {
+ iMoreToCome = ETrue;
+ }
+ }
+ iAlreadyDone = ETrue;
+ } // on first call, allocate the current part
+
+ aDataPart.Set( *iRequestBodyBuffer ); // .. otherwise, just re-use it
+
+ //set timer
+ iSessionTimer->Cancel();
+ iSessionTimer->AfterSeconds( KSessionTimeout );
+
+ return !iMoreToCome;
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::ReleaseData()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+void CHttpUploadWorker::ReleaseData()
+ {
+ // HTTP client is ready with the current data
+ // notify observer if notifications are required
+ // and tell stack whether we have more data coming
+ // when done, close file etc.
+
+ // notify iObserver if iTrackProgress flag is on
+ if ( iProcessedFile->TrackingOn() )
+ {
+ iObserver->TransferProgress( iProcessedFile->Key(),
+ iSendDataCount,
+ iOverallDataSize );
+ }
+
+ if ( iMoreToCome )
+ {
+ TRAP_IGNORE( iHttpTransaction.NotifyNewRequestBodyPartL() );
+ iMoreToCome = EFalse;
+ }
+
+ iAlreadyDone = EFalse;
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::OverallDataSize()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+TInt CHttpUploadWorker::OverallDataSize()
+ {
+ return iOverallDataSize;
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::Reset()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+TInt CHttpUploadWorker::Reset()
+ {
+ // Reset send data count and buffer. This will cause GetNextDataPart()
+ // to start from the beginning.
+ iSendDataCount = 0;
+
+ // Destroy the 100 continue response wait timer if it exists.
+ Destroy100ContinueTimer();
+
+ return KErrNone;
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::Destroy100ContinueTimer()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+void CHttpUploadWorker::Destroy100ContinueTimer()
+ {
+ if ( i100ContinueTimer )
+ {
+ i100ContinueTimer->Cancel();
+ delete i100ContinueTimer;
+ i100ContinueTimer = NULL;
+ }
+ }
+
+// --------------------------------------------------------------------------
+// CHttpUploadWorker::Cancel100ContinueWaitL()
+// (See comments in header file)
+// --------------------------------------------------------------------------
+//
+TInt CHttpUploadWorker::Cancel100ContinueWaitL( TAny* aParam )
+ {
+ CHttpUploadWorker* self =
+ (static_cast<CHttpUploadWorker*>( aParam ));
+
+ // Destroy the timer. We don't need subsequent events.
+ self->Destroy100ContinueTimer();
+
+ // Cancel waiting for 100 continue. As the server did not respond,
+ // continue with sending the body.
+ self->iHttpTransaction.SendEventL(
+ THTTPEvent::ECancelWaitFor100Continue,
+ THTTPEvent::EOutgoing,
+ THTTPFilterHandle( THTTPFilterHandle::EClient) );
+
+ return KErrNone;
+ }
+
+// -----------------------------------------------------------------------------
+// CHttpUploadWorker::TimerEventL
+// Disconnect connection
+// -----------------------------------------------------------------------------
+//
+void CHttpUploadWorker::TimerEventL( CHttpNotifyTimer* /*aTimer*/ )
+ {
+ CompleteAndNotify( KErrTimedOut );
+ }
+
+// End of File