upnp/upnpstack/upnphttptransfer/src/httpuploadworker.cpp
changeset 0 f5a58ecadc66
--- /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