/** @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