pkiutilities/ocsp/transport/transporthttp.cpp
changeset 0 164170e6151a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkiutilities/ocsp/transport/transporthttp.cpp	Tue Jan 26 15:20:08 2010 +0200
@@ -0,0 +1,558 @@
+// 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;
+		}
+	}
+