pkiutilities/ocsp/transport/transporthttp.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Tue, 26 Jan 2010 15:20:08 +0200
changeset 0 164170e6151a
permissions -rw-r--r--
Revision: 201004

// Copyright (c) 2001-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:
// Implement the transport class for using HTTP GET and POST.
// Links the OCSP module to the HTTP module.
// 
//

#include <http/cecomfilter.h>
#include <escapeutils.h>
#include "securitypolicy.h"
#include "ocsppolicy.h"
#include "ocsptransport.h"
#include "panic.h"
#include <tconvbase64.h>
#include "callbacktimer.h"

_LIT8(KOCSPContentTypeRequest, "application/ocsp-request");
_LIT8(KOCSPContentTypeResponse, "application/ocsp-response");
_LIT8(KSlash, "/");

// Min request size for the use of POST method (else use GET method)
const TInt KMinReqSizeForHTTPPOST = 255;

const TInt KMillSecsToMicroSecs = 1000;

// Default value of timeout means it's disabled
const TInt KTimeoutDisabledValue = KTransportDefaultRequestTimeout;

EXPORT_C COCSPTransportHttp* COCSPTransportHttp::NewL(const TDesC8& aUri, TUint32& aIap)
	{
	COCSPTransportHttp* self = new (ELeave) COCSPTransportHttp(aIap);
	CleanupStack::PushL(self);
	self->ConstructL(aUri);
	CleanupStack::Pop(self);
	return self;
	}

COCSPTransportHttp::COCSPTransportHttp(TUint32& aIap)
	: CActive(CActive::EPriorityStandard), iIap(aIap), 
	  iTimeout(KTimeoutDisabledValue)
	{
	CActiveScheduler::Add(this);
	}
	
void COCSPTransportHttp::ConstructL(const TDesC8& aUri)
	{
	// Create the timer object
	iTimer = CCallbackTimer::NewL(*this);

	TUriParser8	uri;
	uri.Parse(aUri);
	iHTTPSession.OpenL(uri, iIap, EFalse);
	
	// Install the HTTP filter (filter UID is specified in swipolicy.ini file)
	InstallHttpFilterL();
	}

COCSPTransportHttp::~COCSPTransportHttp()
	{
	Deque();
	iHTTPTransaction.Close();
	iHTTPSession.Close();

	delete iUri;
	delete iRequest;
	delete iResponseData;
	delete iTimer;
	}

// From MOCSPTransport
void COCSPTransportHttp::SendRequest(const TDesC8& aURI, 
										 const TDesC8& aRequest,
										 const TInt aTimeout,
										 TRequestStatus& aStatus)
	{
	iCallBack = &aStatus;
	aStatus = KRequestPending;
	iServerFound = ETrue;

	delete iUri;
	delete iRequest;

	iTimeout = aTimeout;
	iUri = aURI.Alloc();
	iRequest = aRequest.Alloc();

	// Check for out of memory
	if (iUri == NULL || iRequest == NULL)
		{
		Complete(KErrNoMemory);
		}

	// Take action depending on current state
	if (iState == ETransportConnectingState)
		{
		// Establish a connection
		// Note: Timeout and retries don't apply to this operation
		iState = ETransportConnectingState;
		iHTTPSession.StartConnection(iStatus);
		SetActive();
		}
	else
		{
		// We've connected already, send the request.
		iStatus = KRequestPending;
		TRAPD(err, DoSendRequestL(*iUri, *iRequest));
		if (err == KErrNone)
			{
			// Start the timer (if enabled)
			if (iTimeout > KTimeoutDisabledValue)
				{
				iTimer->After(iTimeout * KMillSecsToMicroSecs);
				}
			SetActive();
			}
		else
			{
			Complete(err);
			}
		}
	}

void COCSPTransportHttp::InstallHttpFilterL()
	{
	// Install filters (if any)
	Swi::RSecPolHandle secPol;
	secPol.OpenLC();
	TUint32 filterId = secPol().OcspHttpHeaderFilter();
	if (filterId != 0)
		{
		TUid headerFilterUid = {filterId};
		CEComFilter::InstallFilterL(iHTTPSession.HTTPSession(), headerFilterUid);
		}
	CleanupStack::PopAndDestroy(&secPol);
	}

void COCSPTransportHttp::DoSendRequestL(const TDesC8& aURI, 
											const TDesC8& aRequest)
	{
	delete iResponseData;
	iResponseData = NULL;
	iOCSPRequest.Set(aRequest);

	// Close previous transaction, if any
	iHTTPTransaction.Close();

	// If the size of the DER encoded request is less than 255 bytes use the GET method (if enabled)
	// else use the POST method
	RBuf8 url8;
	CleanupClosePushL(url8);
	TInt reqSize = aRequest.Length();

	COcspPolicy* ocspPolicy = COcspPolicy::NewL();
	TBool useHTTPGETMethod = ocspPolicy->IsHttpGETMethodEnabled();

	delete ocspPolicy;

	if (useHTTPGETMethod && reqSize < KMinReqSizeForHTTPPOST)
		{
		// Set the OCSP request as part of the url after Base64 and URL encoding
		
		TBase64 base64;
  		
		TInt destLen = ((iOCSPRequest.Length() - 1 ) / 3 + 1) * 4;
 		HBufC8* encodedBuf = HBufC8::NewMaxLC(destLen); // to get the decoded string
   		TPtr8 encodedPtr = encodedBuf->Des();
 		
 		base64.Encode(iOCSPRequest, encodedPtr);
		 				
 		HBufC8* escEncodedReq = EscapeUtils::EscapeEncodeL(encodedPtr, EscapeUtils::EEscapeUrlEncoded);

		CleanupStack::PushL(escEncodedReq);
		url8.CreateL(aURI, aURI.Length() + KSlash().Length() + escEncodedReq->Length());
		// Append a slash only if it's already not present
		if ((aURI.Length() > 0) && (aURI.Right(1) != KSlash))
			{
			url8.Append(KSlash);
			}
		url8.Append(*escEncodedReq);
		CleanupStack::PopAndDestroy(2, encodedBuf);

		if (iURI.Parse(url8))
			{
			User::Leave(OCSP::KErrInvalidURI);
			}

		// Create HTTP transaction, method = GET (default)
		iHTTPTransaction = iHTTPSession().OpenTransactionL(iURI, *this);

		// Add the appropriate Http headers
		AddHttpHeadersL();
		}
	else
		{
		// Use the passed in URL as is
		if (iURI.Parse(aURI))
			{
			User::Leave(OCSP::KErrInvalidURI);
			}

		// Create HTTP transaction, method = POST
		RStringPool stringPool = iHTTPSession().StringPool();
		iHTTPTransaction = iHTTPSession().OpenTransactionL(iURI, *this, stringPool.StringF(HTTP::EPOST,RHTTPSession::GetTable()));

		// Add the appropriate Http headers
		AddHttpHeadersL();

		// Setup the data supplier
		iHTTPTransaction.Request().SetBody(*this);
		}

	// Finally submit the transaction
	iHTTPTransaction.SubmitL();
	CleanupStack::PopAndDestroy(&url8);
	}

void COCSPTransportHttp::AddHttpHeadersL()
	{
	// Get request + it's headers
	RStringPool stringPool = iHTTPSession().StringPool();
	RHTTPRequest request = iHTTPTransaction.Request();
	RHTTPHeaders headers = request.GetHeaderCollection();

	// Host header: done by HTTP module
	// Content-Length header: done by HTTP module

	// Connection:close header:
	headers.SetFieldL(stringPool.StringF(HTTP::EConnection, RHTTPSession::GetTable()), stringPool.StringF(HTTP::EClose, RHTTPSession::GetTable()));

	// Content-Type header:
	RStringF ocspRequest = stringPool.OpenFStringL(KOCSPContentTypeRequest);
	CleanupClosePushL(ocspRequest);
	THTTPHdrVal contentTypeVal(ocspRequest);
	headers.SetFieldL(stringPool.StringF(HTTP::EContentType,RHTTPSession::GetTable()), contentTypeVal);
	CleanupStack::PopAndDestroy(&ocspRequest);
	}

// From MOCSPTransport
void COCSPTransportHttp::CancelRequest()
	{
	Cancel();
	}


// From MOCSPTransport
TPtrC8 COCSPTransportHttp::GetResponse() const
	{
	__ASSERT_ALWAYS(iResponseData, Panic(KErrNotReady));

	return iResponseData->Des();
	}


// From MHTTPDataSupplier
TBool COCSPTransportHttp::GetNextDataPart(TPtrC8& aDataPart)
	{
	aDataPart.Set(iOCSPRequest);
	return ETrue;  // This is the last chunk
	}


// From MHTTPDataSupplier
void COCSPTransportHttp::ReleaseData()
	{
	// Nothing to do - from the OCSP side, ownership of the outgoing data is sorted already
	}


// From MHTTPDataSupplier
TInt COCSPTransportHttp::OverallDataSize()
	{
	return iOCSPRequest.Length();
	}


// From MHTTPDataSupplier
TInt COCSPTransportHttp::Reset()
	{
	return KErrNotSupported;
	}

// From MHTTPTransactionCallback
void COCSPTransportHttp::MHFRunL(RHTTPTransaction aTransaction, const THTTPEvent& aEvent)
	{
	__ASSERT_ALWAYS(aTransaction == iHTTPTransaction, Panic(KErrArgument));

	// Either ESucceeded or EFailed *will* happen
	switch (aEvent.iStatus)
		{
		case THTTPEvent::EGotResponseHeaders:
			ProcessHeadersL();
			break;
		case THTTPEvent::EGotResponseBodyData:
			ProcessDataL();
			break;
		case THTTPEvent::EResponseComplete:
			CheckDataCompleteL();
			break;
		case THTTPEvent::EFailed:
			{
			// Stop the timer
			iTimer->Cancel();
			TRequestStatus* reqstatus = &iStatus;
			if (iServerFound)
				{
				User::RequestComplete(reqstatus, OCSP::KErrTransportFailure);
				}
			else
				{
				User::RequestComplete(reqstatus, OCSP::KErrServerNotFound);
				}
			}
			break;
		case THTTPEvent::ESucceeded:
			{
			// Stop the timer
			iTimer->Cancel();
			CheckDataCompleteL();
			SetIAPL();
			TRequestStatus* status = &iStatus;
			User::RequestComplete(status, KErrNone);
			}
			break;
		case KErrTimedOut:
			// HTTP timed out - this really means it couldn't find the server mentioned in the url
			// Remember this error so we pass it up correctly when we receive "THTTPEvent::EFailed" event
			iServerFound = EFalse;
			break;
		default:
			// Some other event (or error).
			// Do nothing - HTTP module will cause EFailed or ESucceeded to occur
			break;
		}
	}
	
void COCSPTransportHttp::SetIAPL()
	{
	const RStringPool strPool = iHTTPSession().StringPool();
	RHTTPConnectionInfo connectionInfo = iHTTPSession().ConnectionInfo();
	THTTPHdrVal hdrVal;
	if (connectionInfo.Property(strPool.StringF(HTTP::EHttpSocketConnection, RHTTPSession::GetTable()),
			hdrVal)) 
		{
		RConnection* connection =  reinterpret_cast<RConnection*>(hdrVal.Int());
		_LIT(KIapId, "IAP\\Id");
		User::LeaveIfError(connection->GetIntSetting(KIapId, iIap));
		}	
	}


// From MHTTPTransactionCallback
TInt COCSPTransportHttp::MHFRunError(TInt aError, RHTTPTransaction aTransaction, const THTTPEvent& /*aEvent*/)
	{
	__ASSERT_ALWAYS(aTransaction == iHTTPTransaction, Panic(KErrArgument));

	// Process error (otherwise, we get a panic)
	AbortTransaction(aError);
	return KErrNone;
	}

// Methods from MCallbackTimer
void COCSPTransportHttp::TimerRun(TInt aError)
	{
	if (aError == KErrNone)
		{
		// Handle the timeout
		iHTTPTransaction.Cancel();
		TRequestStatus* status = &iStatus;
		User::RequestComplete(status, OCSP::KErrTransportTimeout);
		}
	else
		{
		// Propagate the error upward
		TRequestStatus* status = &iStatus;
		User::RequestComplete(status, aError);
		}
	}

// The headers have been received - check them out
void COCSPTransportHttp::ProcessHeadersL()
	{
	RHTTPResponse response = iHTTPTransaction.Response();
	RHTTPHeaders headers = response.GetHeaderCollection();
	RStringPool stringPool = iHTTPSession().StringPool();

	// Check content type
	RStringF ocspResponse = stringPool.OpenFStringL(KOCSPContentTypeResponse);
	CleanupClosePushL(ocspResponse);
	RStringF contentTypeString = stringPool.StringF(HTTP::EContentType,RHTTPSession::GetTable());
	THTTPHdrVal contentTypeVal;
	TInt error = headers.GetField(contentTypeString, 0, contentTypeVal);
	if (error != KErrNone 
		|| contentTypeVal.Type() != THTTPHdrVal::KStrFVal
		|| contentTypeVal.StrF() != ocspResponse)
		{
		User::Leave(OCSP::KErrTransportFailure);
		}
	CleanupStack::PopAndDestroy(); // close ocspResponse

	// Check content length - make descriptor ready to recieve data
	RStringF contentLengthString = stringPool.StringF(HTTP::EContentLength,RHTTPSession::GetTable());
	THTTPHdrVal contentLengthVal;
	error = headers.GetField(contentLengthString, 0, contentLengthVal);
	if (error == KErrNone
		&& contentLengthVal.Type() == THTTPHdrVal::KTIntVal)
		{
		// Make descriptor ready to receive response data
		__ASSERT_ALWAYS(iResponseData == NULL, Panic(KErrAlreadyExists));
		iResponseLength = contentLengthVal.Int();
		iResponseData = HBufC8::NewL(iResponseLength);
		}
	else
		{
		// No Contents-Length field in headers, or wrong data type
		User::Leave(OCSP::KErrTransportFailure);
		}
	}

void COCSPTransportHttp::ProcessDataL()
	{
	if (iResponseData)
		{
		// Some data has come in - copy it into our descriptor
		MHTTPDataSupplier* body = iHTTPTransaction.Response().Body();
		if (!body)
			{
			User::Leave(OCSP::KErrTransportFailure);
			}

		TPtrC8 dataChunk;
		TBool finished = body->GetNextDataPart(dataChunk);
		if (iResponseLength - iResponseData->Length() >= dataChunk.Length())
			{
			iResponseData->Des().Append(dataChunk);			
			body->ReleaseData();
			}
		else
			{
			// Data is longer than Contents-Length header said it would be - error
			body->ReleaseData();
			User::Leave(OCSP::KErrTransportFailure);
			}

		if (finished)
			{
			CheckDataCompleteL();
			}
		}		
	}

void COCSPTransportHttp::CheckDataCompleteL() const
	{
	if (iResponseLength != iResponseData->Length())
		{
		User::Leave(OCSP::KErrTransportFailure);
		}
	}

void COCSPTransportHttp::Complete(TInt aError)
	{
	if (iCallBack)
		{
		// If something went wrong, remove the dodgy response data
		if (aError != KErrNone)
			{
			delete iResponseData;
			iResponseData = NULL;
			}

		// Sets iCallBack back to NULL, so we can only do this once
		User::RequestComplete(iCallBack, aError);
		}
	}

void COCSPTransportHttp::RunL()
	{
	User::LeaveIfError(iStatus.Int());
	switch(iState)
		{
	case ETransportConnectingState:
		iState = ETransportSendRequestState;
		iStatus = KRequestPending;
		DoSendRequestL(*iUri, *iRequest);
		// Start the timer (if enabled)
		if (iTimeout > KTimeoutDisabledValue)
			{
			iTimer->After(iTimeout * KMillSecsToMicroSecs);
			}
		SetActive();
		break;

	case ETransportSendRequestState:
		// All OK
		Complete(KErrNone);
		break;
	default:
		User::Leave(KErrNotSupported);
		break;
		};
	}

TInt COCSPTransportHttp::RunError(TInt aError)
	{
	// If we failed in the transport connection state, return a
	// transport error. Otherwise, just propogate the error recieved.
	if (iState == ETransportConnectingState)
		{
		Complete(OCSP::KErrTransportFailure);
		}
	else
		{
		Complete(aError);
		}
	return KErrNone;
	}

void COCSPTransportHttp::DoCancel()
	{
	AbortTransaction(KErrCancel);
	Complete(KErrCancel);
	}

void COCSPTransportHttp::AbortTransaction(TInt aError)
	{
	switch(iState)
		{
		case ETransportConnectingState:
		iHTTPSession.CancelStart();
		break;

		case ETransportSendRequestState:
		iTimer->Cancel();
		iHTTPTransaction.Cancel();

		//DEF101099 Fix - In a very rare situation after starting invocation of this DoCancel(), the asynchronous request
		//is getting completed normally. In that case(iStatus!=KRequestPending) no need call this request complete codes,
		//otherwise which leads to a "Stray Signal" and ending up in a Panic - E32USER-CBase 46.
		if(iStatus == KRequestPending)
			{
			// Thanks to HTTP not exporting a callback for "DoCancel()" we complete the request here
			TRequestStatus* status = &iStatus;
			User::RequestComplete(status, aError);
			}	
		break;
		}
	}