// Copyright (c) 2003-2009 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:
//

#include <http/rhttptransaction.h>
#include <http/rhttpheaders.h>
#include <http/rhttpresponse.h>
#include <httperr.h>
#include <httpstringconstants.h>
#include <imcvcodc.h>      //for base64 en/decoding
#include <bautils.h>
#include <e32math.h>
#include <hash.h>
#include <e32const.h>
#include <tconvbase64.h>
#include "testhttpfilter.h"
_LIT8( KTESTHTTPFilterName, "TestHttp");
_LIT8( KAuthenticationInfoStr, "Authentication-Info" );
_LIT8( KUserAgentProductToken,"3gpp-gba");
_LIT8( KColon, ":" );
const TInt KB64KeySize = 64;
_LIT(KTestHttpFilter, "TEST HTTP FILTER ");

void PanicTestHttpFilters(TInt aErr = 0)
{
    User::Panic(KTestHttpFilter, aErr);
}

// ============================ MEMBER FUNCTIONS ===============================

// -----------------------------------------------------------------------------
// CTestFilter::CTestFilter
// Constructor
// -----------------------------------------------------------------------------
//
CTestFilter::CTestFilter()
		: iBootstrapCount(0),iHaveCredentials(EFalse)
{

}

// -----------------------------------------------------------------------------
// CTestFilter::InstallFilterL
// Initialize the filter and register it to sesssion's filter collection
// -----------------------------------------------------------------------------
//
CTestFilterInterface* CTestFilter::InstallFilterL(TAny* aParams)
{
	__ASSERT_DEBUG(aParams != NULL, PanicTestHttpFilters());
	RHTTPSession* session = REINTERPRET_CAST(RHTTPSession*, aParams);
	CTestFilter* filter = new (ELeave) CTestFilter();
	CleanupStack::PushL(filter);
	filter->ConstructL(*session);
	CleanupStack::Pop(filter);
	return filter;
}
	
// -----------------------------------------------------------------------------
// CTestFilter::ConstructL
// Memory and resource allocation, leaves
// -----------------------------------------------------------------------------
//
void CTestFilter::ConstructL(RHTTPSession aSession)
{
	iStringPool = aSession.StringPool();
	// register the filter
	RStringF filterName = iStringPool.OpenFStringL( KTESTHTTPFilterName );
	CleanupClosePushL( filterName );
	iStringPool = aSession.StringPool();

	iUsernameStr = iStringPool.StringF( HTTP::EUsername, RHTTPSession::GetTable() );
    iPasswordStr = iStringPool.StringF( HTTP::EPassword, RHTTPSession::GetTable() );
	iUserAgent = iStringPool.OpenFStringL( KUserAgentProductToken );
    iAuthInfo = iStringPool.OpenFStringL( KAuthenticationInfoStr );
    
    //Regsiter for THTTPEvent::ESubmit http event
    aSession.FilterCollection().AddFilterL( *this,
                                            THTTPEvent::ESubmit,
                                            RStringF(),
                                            KAnyStatusCode,
                                            EStatusCodeHandler,
                                            filterName);
   
    //Regsiter for THTTPEvent::EGotResponseHeaders http event with "Authentication-Info" header 
    //and HTTPStatus::EOk http status code,just to know if the credentials established are valid.
    aSession.FilterCollection().AddFilterL( *this,
                                            THTTPEvent::EGotResponseHeaders,
                                            iAuthInfo,
                                            HTTPStatus::EOk,
                                            EStatusCodeHandler,
                                            filterName );

    //Regsiter for THTTPEvent::EGotResponseHeaders http event with "WWW-Authenticate" header 
    //and HTTPStatus::EUnauthorized http status code
    //The priority for this is set to MHTTPFilter::EStatusCodeHandler - 1,
    //so the filter intercepts the transaction before the default 
    //http digest authentication filter(which has priority MHTTPFilter::EStatusCodeHandler).

    aSession.FilterCollection().AddFilterL( *this, 
											THTTPEvent::EGotResponseHeaders,	// Any transaction event
											iStringPool.StringF(HTTP::EWWWAuthenticate,RHTTPSession::GetTable()),
											HTTPStatus::EUnauthorized,
											MHTTPFilter::EStatusCodeHandler - 1,	  // Priority of filter
											filterName );						      // Name of filter

	CleanupStack::PopAndDestroy( &filterName );
  
    iTimeOut =  CTimerOut::NewL(*this);
    
	}


//------------------------------------------------------------------------
// CTestFilter::~CTestFilter
// Destructor
//------------------------------------------------------------------------
//
CTestFilter::~CTestFilter()
{
	CleanupAll();			
	delete iTimeOut;
}

//------------------------------------------------------------------------
// CTestFilter::MHFLoad
//------------------------------------------------------------------------
//
void CTestFilter::MHFLoad(RHTTPSession, THTTPFilterHandle)
{
	++iLoadCount;
}

//------------------------------------------------------------------------
// CTestFilter::MHFUnload
//------------------------------------------------------------------------
//
void CTestFilter::MHFUnload(RHTTPSession , THTTPFilterHandle)
{
	 __ASSERT_DEBUG( iLoadCount >= 0, PanicTestHttpFilters() );
    
	if (--iLoadCount)
	{
		return;
	}
	delete this;
}

//------------------------------------------------------------------------
// CTestFilter::MHFRunL
// See MHTTPFilterBase::MHFRunL 
//------------------------------------------------------------------------
//
void CTestFilter::MHFRunL(RHTTPTransaction aTransaction, const THTTPEvent& aEvent)
{
	if (aEvent.iUID != KHTTPUid) 
		return;

	switch(aEvent.iStatus)
	{
		case THTTPEvent::ESubmit:
		{
			DoSubmitL(aTransaction);
		}
		break;

		case THTTPEvent::EGotResponseHeaders:
		{
			// Get HTTP status code from header (e.g. 200)
			RHTTPResponse resp = aTransaction.Response();
			CheckHeadersL( aTransaction );
		}
		break;
		default: 
		{
		}
		break;
	}
	
}


//------------------------------------------------------------------------
// CTestFilter::MHFRunError
// See MHTTPFilterBase::MHFRunError
//------------------------------------------------------------------------
//
TInt CTestFilter::MHFRunError(TInt /*aError*/, RHTTPTransaction aTransaction, const THTTPEvent& )
{
 	TInt error = 0;
	// map aError to global error message
	// pass the errorcode forward
	THTTPEvent httpEvent(error);
	TRAP_IGNORE(aTransaction.SendEventL(httpEvent, THTTPEvent::EIncoming, THTTPFilterHandle::ECurrentFilter ));
	return KErrNone;
}

//------------------------------------------------------------------------
// CTestFilter::MHFSessionRunL
// See MHTTPFilterBase::MHFSessionRunL
//------------------------------------------------------------------------
//
void CTestFilter::MHFSessionRunL(const THTTPSessionEvent& )
{
	// do nothing
}

//------------------------------------------------------------------------
// CTestFilter::MHFSessionRunL
// See MHTTPFilterBase::MHFSessionRunL
//------------------------------------------------------------------------
//
TInt CTestFilter::MHFSessionRunError(TInt aError, const THTTPSessionEvent& )
{
	// session problem
	return aError;
}
//------------------------------------------------------------------------
// CTestFilter::CheckHeadersL
// Check HTTP headers and extract MIME type
//------------------------------------------------------------------------
// 
void CTestFilter::CheckHeadersL(  RHTTPTransaction& aTrans )
{
	// read the header data and check the MIME type here	
	// check the status and body
	RHTTPResponse response = aTrans.Response();
	TInt status = response.StatusCode();
	THTTPHdrVal fieldVal;
	// check if status == 401 and realm is 3GPP then we need to bootstrap, if we are already bootstrappign this is the second call
	if(  status == HTTPStatus::EUnauthorized )
	{				
	
	   TInt headerPart=0;    	
	   THTTPHdrVal headerVal;
       TInt cred = KErrNotFound;
       RStringF wwwAuthHeader = iStringPool.StringF(HTTP::EWWWAuthenticate,RHTTPSession::GetTable());
	   RHTTPHeaders headers(aTrans.Response().GetHeaderCollection());

	    RString realm;
	    THTTPHdrVal hdrVal;
	    if (!headers.GetParam(wwwAuthHeader, iStringPool.StringF(HTTP::ERealm,RHTTPSession::GetTable()), 
				  hdrVal, headerPart))
	    {
			realm = hdrVal;
			const TDesC8& val = realm.DesC();
	   			if (headerPart == KErrNotFound)
					return;
	            THTTPHdrVal authTypeParam;


	 				RHTTPTransactionPropertySet propSet = aTrans.PropertySet();
	 				// if we are already bootstrapping results will be retrieved when request is resubmitted
	                iBootstrapPending = ETrue;
	                
             
	                TTimeIntervalMicroSeconds32 aInterval(20000000);
	                
	                iTimeOut->Start(aInterval); //Start the http post request timer, aInterval - timeout in micro-seconds
	                
	                if(!iBootstrapWait.IsStarted())
	                    {
                        iBootstrapWait.Start();
	                    }
	                  
	                if( iHaveCredentials )
	                    {
                        RHTTPTransactionPropertySet propSet = aTrans.PropertySet();
                       
                        //user name/pass word for accessing http://replab.nrln.net/digest/ 
                        //username: dummy, password: dummy
                        TBuf8<KB64KeySize> keyBase64(_L8("Aladdin")); 
                        TBuf8<KB64KeySize> keyBase64Username(_L8("open sesame"));;
                        RString username = iStringPool.OpenStringL( keyBase64 );
                        CleanupClosePushL<RString>( username );
                        RString password = iStringPool.OpenStringL( keyBase64Username );
                       CleanupClosePushL<RString>( password );
                        propSet.SetPropertyL( iUsernameStr, username );
                        propSet.SetPropertyL( iPasswordStr, password );
                        CleanupStack::PopAndDestroy(&password);
                        CleanupStack::PopAndDestroy(&username);
                        //Cancel the transaction
                        aTrans.Cancel();
                        // Re-submit the http request with much needed credentials
                        aTrans.SubmitL(); 
                        }
	                else
	                    {
                        //Since bootstrapping failed,Do not have to resubmit the http request ?
                        return;
                        }
			
		}
	}
}

void CTestFilter::DoSubmitL( RHTTPTransaction aTransaction )
{
	RHTTPHeaders hdr = aTransaction.Request().GetHeaderCollection();
	
	RStringF fieldname = aTransaction.Session().StringPool().StringF(HTTP::EUserAgent,RHTTPSession::GetTable());
	
	TBool found = EFalse;
	TPtrC8 rawFieldData;
	
	hdr.GetRawField(fieldname, rawFieldData);
	
	found = rawFieldData.Find(iUserAgent.DesC()) != KErrNotFound;
	
	if(!found)
	    hdr.SetFieldL(fieldname, iUserAgent);
	
    TInt cred = KErrNotFound;
    
    if( iHaveCredentials )
        {
        TBuf8<KB64KeySize> keyBase64(_L8("Aladdin")); 
        TBuf8<KB64KeySize> keyBase64Username(_L8("open sesame"));;
        RString username = iStringPool.OpenStringL( keyBase64 );
        CleanupClosePushL<RString>( username );
        RString password = iStringPool.OpenStringL( keyBase64Username );
        CleanupClosePushL<RString>( password );
		EncodeBasicAuthL(username, password, aTransaction);
        CleanupStack::PopAndDestroy(&password);
        CleanupStack::PopAndDestroy(&username);
        	
        }
}

//-----------------------------------------------------------------------------
// CTestFilter::CleanupAll
// Cleanup all the transactions, in case a session error happens or a session
// is closed.
//-----------------------------------------------------------------------------
//
void CTestFilter::CleanupAll()
{	
	
	iUserAgent.Close();
	iAuthInfo.Close();
}

TBool CTestFilter::HandleTimerEventL(TTimerEventType /*aEventType*/)
{
    iBootstrapPending = EFalse;
    
    iHaveCredentials = ETrue;
     
    if (iBootstrapWait.IsStarted())
    	iBootstrapWait.AsyncStop();
    
    return ETrue;
}


void CTestFilter::EncodeBasicAuthL(RString& aUsername, 
										  RString& aPW, 
										  RHTTPTransaction& aTransaction)
	{
	// Standard, plain-text HTTP - Base 64 the name and password
	TBase64 codec;
	HBufC8* nameAndPW = HBufC8::NewMaxLC(aUsername.DesC().Length() + aPW.DesC().Length() + 1);
	_LIT8(KBasicAuthFormatString, "%S:%S");
	nameAndPW->Des().Format(KBasicAuthFormatString, &aUsername.DesC(), &aPW.DesC());
 
	// Conservatively allocate a buffer twice as big as the unencoded
	// buffer for the encoded string.
	HBufC8* encoded = HBufC8::NewMaxLC(nameAndPW->Des().Length() * 2);
	TPtr8 encodedPtr(encoded->Des());
	codec.Encode(*nameAndPW, encodedPtr);
	
	RString encodedString = iStringPool.OpenStringL(*encoded);
	CleanupStack::PopAndDestroy(2, nameAndPW); 

	CleanupClosePushL(encodedString);
	
	RHTTPHeaders requestHeaders(aTransaction.Request().GetHeaderCollection());
	
	requestHeaders.RemoveField(iStringPool.StringF(HTTP::EAuthorization,RHTTPSession::GetTable())); 
	requestHeaders.SetFieldL(iStringPool.StringF(HTTP::EAuthorization,RHTTPSession::GetTable()), 
									THTTPHdrVal(iStringPool.StringF(HTTP::EBasic,RHTTPSession::GetTable())));
	
	requestHeaders.SetFieldL(iStringPool.StringF(HTTP::EAuthorization,RHTTPSession::GetTable()), 
									THTTPHdrVal(encodedString));

	aTransaction.PropertySet().SetPropertyL(iStringPool.StringF(HTTP::EAuthorization,RHTTPSession::GetTable()), 
																THTTPHdrVal(encodedString));
	
	CleanupStack::PopAndDestroy(&encodedString);
	
	}
