// 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 "chttpclienthandler.h"
#include <http.h>
#include <http/framework/crxdata.h>
#include <http/framework/csecuritypolicy.h>
#include <inetprottextutils.h>
#include <uriutilscommon.h>
#include <uriutils.h>
#include <authority8.h>
#include <in_sock.h>
#include <securesocket.h>
#include <x509certext.h>
#include "chttptransportlayer.h"
#include "chttpclientfilter.h"
#include "chttpconnectfilter.h"
#include "chttpclientheadercodec.h"
#include "chttpclienttransaction.h"
#include "chttpconnectioninfo.h"
#include "chttpconnectionmanager.h"
#include "chttprequestcomposer.h"
#include "chttpresponseparser.h"
#include "thttpclientpanic.h"
const TInt KHttpDefaultPort = 80;
const TInt KHttpDefaultSecurePort = 443;
const TInt KHttpDefaultProxyPort = 8080;
const TInt KMaxConnectionManagers = 4;
const TInt KDefaultBatchingBufSize = 1400;
const TInt KDefaultMaxNumberTransactionsToPipeline = KMaxTInt;
const TInt KDefaultBufferSize = 32*1024;
_LIT8(KSecureHttpScheme, "https");
_LIT8(KHttpClientCodecName, "HTTP/client");
_LIT8(KUAProfile, "x-wap-profile");
const TUint KIPv6HostOpenBrace = '[';
const TUint KIPv6HostCloseBrace = ']';
class InetProtTextUtils;
CHttpClientHandler* CHttpClientHandler::NewL(TAny* aSession)
{
RHTTPSession* httpSession = REINTERPRET_CAST(RHTTPSession*, aSession);
CHttpClientHandler* self = new (ELeave) CHttpClientHandler(*httpSession);
CleanupStack::PushL(self);
self->ConstructL();
CleanupStack::Pop(self);
return self;
}
CHttpClientHandler::~CHttpClientHandler()
{
iProxyAddress.Close();
iConnectionManagers.ResetAndDestroy();
delete iTransportLayer;
delete iPipelineFallback;
__FLOG_CLOSE;
}
CHttpClientHandler::CHttpClientHandler(RHTTPSession aSession)
: CProtocolHandler(aSession),
iStringTable(RHTTPSession::GetTable())
{
__FLOG_OPEN("http", "httpclienthandler.txt");
__FLOG(_T8("HTTP Client Protocol Handler Log"));
}
void CHttpClientHandler::ConstructL()
{
CProtocolHandler::ConstructL(iSession);
CHttpClientFilter* clientFilter = CHttpClientFilter::NewL(iSession);
CleanupStack::PushL ( clientFilter );
CHttpConnectFilter::NewL(iSession);
CleanupStack::Pop (); // clientFilter
RHTTPConnectionInfo connInfo = iSession.ConnectionInfo();
RStringPool stringPool = iSession.StringPool();
THTTPHdrVal value;
TBool hasValue = connInfo.Property(stringPool.StringF(HTTP::ESessionClosing, iStringTable), value);
if( hasValue && value.Type() == THTTPHdrVal::KTIntVal )
{
iSessionClosingPtr = reinterpret_cast<TBool*>(value.Int());
}
else
{
__FLOG_0(_T8("!! Session Closing Flag NOT set"));
User::Leave(KErrNotFound);
}
iPipelineFallback = CHttpPipelineFallback::NewL();
}
CHttpConnectionInfo* CHttpClientHandler::PrepareTransactionL(RHTTPTransaction aTrans, TBool& aNeedTunnel, TBool& aCanPipeline)
{
// Check that filters are not added headers that are not needed for CONNECT method.
// Remove the unwanted headers. But do only if the property has been set for the
// strict CONNECT method.
EnsureStrictConnectMethodHeaders (aTrans);
// To prepare the transaction need to create the Request-URI, set the Host
// header and establish the connection info.
RStringPool stringPool = iSession.StringPool();
RHTTPRequest request = aTrans.Request();
SetupProxyInformation(aTrans);
// Store the method index from string table
TInt methodIndex = request.Method().Index(iStringTable);
// Check for https scheme.
const TUriC8& uri = request.URI();
// secure doesn't count if it's a connect request
TBool secure = methodIndex != HTTP::ECONNECT &&
(uri.Extract(EUriScheme).CompareF(KSecureHttpScheme) == 0);
// Create the Request-URI for this transaction...
TInt port = (secure) ? KHttpDefaultSecurePort : KHttpDefaultPort;
TPtrC8 host;
CreateRequestUriL(methodIndex, aTrans, host, port);
// If the host name is empty leave as the URI is not complete - a CONNECT
// method request has an empty host as it will go to the proxy.
if( host.Length() == 0 )
{
__FLOG_0(_T8("!! Invalid uri"));
__FLOG_2(_T8("-> Trans %d, Con %d : missing host!"), aTrans.Id(),GetConnectionID(aTrans));
User::Leave(KErrHttpInvalidUri);
}
// Set the Host header...
TBool isHttp10 = SetHostHeaderL(aTrans, host, port);
// Create the connection info. Is a proxy being used?
if( iUseProxy )
{
// Need a tunnel if request is going via a proxy and request is using
// the https scheme
if( secure )
{
__FLOG_4(_T8("Trans %d, Con %d : tunnel required to host %S, port %d"), aTrans.Id(),GetConnectionID(aTrans), &host, port);
// This request needs a tunnel to be established to the specified
// host/port - add the ETunnel property to the transaction.
aNeedTunnel = ETrue;
AddTunnelInfoL(aTrans, host, port);
}
// Need to obtain the proxy info.
const TDesC8& proxy = iProxyAddress.DesC();
TAuthorityParser8 auth;
auth.Parse(proxy);
host.Set(auth.Extract(EAuthorityHost));
// Read the proxy port if present
port = KHttpDefaultProxyPort; // Initialise with default proxy port
if( auth.IsPresent(EAuthorityPort) )
{
TPtrC8 portDesc = auth.Extract(EAuthorityPort);
TInt error = InetProtTextUtils::ConvertDescriptorToInt(portDesc, port);
if( error != KUriUtilsErrEmptyData )
{
// An empty port component is allowed - just ignore it.
User::LeaveIfError(error);
}
}
}
// Is a non-persistent connection required? The connection is non-persistent
// if either it is a HTTP/1.0 request or it is a HTTP/1.1 request and it has
// a Connection: close header.
RHTTPHeaders headers = request.GetHeaderCollection();
CHttpConnectionInfo::THttpPersistent persistentState = isHttp10 ?
CHttpConnectionInfo::ENonPersistent :
CHttpConnectionInfo::EPersistent;
THTTPHdrVal connVal;
RStringF connStr = stringPool.StringF(HTTP::EConnection, iStringTable);
if( headers.GetField(connStr,0,connVal) == KErrNone )
{
__ASSERT_DEBUG( !isHttp10, THttpClientPanic::Panic(THttpClientPanic::EInvalidHeaderForHTTP10) ); // no connection headers are allowed in HTTP/1.0
if( (connVal.Type() == THTTPHdrVal::KStrFVal) &&
(connVal.StrF() == stringPool.StringF(HTTP::EClose, iStringTable)) )
{
// Client has specified a Connection: close header - non-persistent
// connection.
persistentState = CHttpConnectionInfo::ENonPersistent;
}
}
// Create the connection info object
CHttpConnectionInfo* connectionInfo = CHttpConnectionInfo::NewL(stringPool, host, static_cast<TUint16>(port));
connectionInfo->SetSecureState(secure);
connectionInfo->SetPersistentState(persistentState);
// Check to see if this transaction can be pipelined.
switch(methodIndex)
{
case HTTP::EGET:
{
// Can only pipeline non-HTTP/1.0 requests...
if( !isHttp10 )
{
// These methods can be pipelined but only if pipelining has not been
// specifically disabled for the transaction.
aCanPipeline = (CheckPipelineSupport(aTrans) && !iPipelineFallback->NeedPipelineFallback(host));
__FLOG_4(_T8("Pipelining --- enabled: %d Support: %d Fallback: %d Host %S"), aCanPipeline, CheckPipelineSupport(aTrans), !iPipelineFallback->NeedPipelineFallback(host), &host);
break;
}
// Allow the HTTP/1.0 requests to drop through to default case.
}
default:
// All other methods are not to be pipelined.
aCanPipeline = EFalse;
break;
};
return connectionInfo;
}
void CHttpClientHandler::CreateRequestUriL(TInt aMethodIndex, RHTTPTransaction aTrans, TPtrC8& aHost, TInt& aPort)
{
RStringPool stringPool = iSession.StringPool();
RHTTPRequest request = aTrans.Request();
const TUriC8& uri = request.URI();
TBool secure = EFalse;
RStringF scheme = stringPool.OpenFStringL(uri.Extract(EUriScheme));
CleanupClosePushL(scheme);
if( aMethodIndex == HTTP::ECONNECT )
{
__ASSERT_DEBUG( iUseProxy, User::Invariant() );
aHost.Set(uri.Extract(EUriHost));
}
else if( uri.IsPresent(EUriHost) )
{
// The request URI is absolute - check the scheme is http or https
secure = uri.IsPresent(EUriScheme) && (scheme.Index(iStringTable) == HTTP::EHTTPS);
if( !uri.IsPresent(EUriScheme) || !secure &&
scheme.Index(iStringTable) != HTTP::EHTTP)
{
__FLOG_0(_T8("!! Invalid uri."));
__FLOG_3(_T8("-> Trans %d, Con %d: %S scheme is not supported!"), aTrans.Id(),GetConnectionID(aTrans), &scheme.DesC());
User::Leave(KErrHttpInvalidUri);
}
// Get the host and port info from it.
aHost.Set(uri.Extract(EUriHost));
// Check to see if a port has been specified
if( uri.IsPresent(EUriPort) )
{
TPtrC8 port = uri.Extract(EUriPort);
TInt error = InetProtTextUtils::ConvertDescriptorToInt(port, aPort);
if( error != KUriUtilsErrEmptyData )
{
// An empty port component is allowed - just ignore it.
User::LeaveIfError(error);
}
}
}
else
{
// The request URI is relative - update the request URI to be absolute.
// The client MUST have supplied a Host header.
RHTTPHeaders headers = request.GetHeaderCollection();
RStringF hostStr = stringPool.StringF(HTTP::EHost, iStringTable);
THTTPHdrVal hostVal;
if( headers.GetField(hostStr, 0, hostVal) == KErrNotFound )
{
// No Host header - do not know which host to connect to.
User::Leave(KErrHttpGeneralHeaderMissingHost);
}
__ASSERT_DEBUG( hostVal.Type() == THTTPHdrVal::KStrFVal, User::Invariant() );
// Set the host output argument
aHost.Set(hostVal.StrF().DesC());
// Create the absolute uri
CUri8* absoluteUri = CUri8::NewLC(uri);
RStringF httpStr = stringPool.StringF(HTTP::EHTTP, iStringTable);
absoluteUri->SetComponentL(httpStr.DesC(), EUriScheme);
absoluteUri->SetComponentL(aHost, EUriHost);
// Check for port info in the Host header
RStringF portStr = stringPool.StringF(HTTP::EPort, iStringTable);
THTTPHdrVal portVal;
TBool hasPort = (headers.GetParam(hostStr, portStr, portVal) == KErrNone);
if( hasPort && portVal.Type() == THTTPHdrVal::KTIntVal )
{
// Set the port output argument
aPort = portVal.Int();
// Convert the int value to its descriptor format
HBufC8* portBuf = NULL;
InetProtTextUtils::ConvertIntToDescriptorL(aPort, portBuf);
CleanupStack::PushL(portBuf);
absoluteUri->SetComponentL(*portBuf, EUriPort);
CleanupStack::PopAndDestroy(portBuf);
}
// Now set the absolute URI back into the transaction
request.SetURIL(absoluteUri->Uri());
CleanupStack::PopAndDestroy(absoluteUri);
}
// Create the Request-URI based on the request URI.
// NOTE - might have changed from earlier
const TUriC8 reqURI = request.URI();
// CONNECT uses authority while everything else uses absoluteURI or absolutePath
// use absolutePath for direct connections and tunnels, and absoluteURI for proxy
// note: OPTIONS uses "*" as well, but that should still work
CAuthority8* authToUse=NULL;
CUri8* uriToUse = NULL;
if( aMethodIndex == HTTP::ECONNECT)
{
authToUse = CAuthority8::NewLC();
authToUse->SetComponentL(reqURI.Extract(EUriUserinfo),EAuthorityUserinfo);
authToUse->SetComponentL(reqURI.Extract(EUriHost),EAuthorityHost);
authToUse->SetComponentL(reqURI.Extract(EUriPort),EAuthorityPort);
}
else
{
uriToUse = CUri8::NewLC(reqURI);
if( !iUseProxy || secure ) // if (useproxy and not secure) or (not useproxy) = not useproxy or secure
{// Not going via a proxy - need to remove the scheme and authority parts
uriToUse->RemoveComponentL(EUriScheme);
uriToUse->RemoveComponentL(EUriHost); // this also removes the userinfo + port
}
}
// Set the Request-URI for the request
RString uriStr = stringPool.OpenStringL(
authToUse ? authToUse->Authority().AuthorityDes() : uriToUse->Uri().UriDes());
CleanupClosePushL(uriStr);
THTTPHdrVal uriVal(uriStr);
aTrans.PropertySet().SetPropertyL(stringPool.StringF(HTTP::EUri, iStringTable), uriVal);
CleanupStack::PopAndDestroy(3, &scheme); // uriToUse or authToUse, uriStr
}
TBool CHttpClientHandler::SetHostHeaderL(RHTTPTransaction aTrans, const TDesC8& aHost, TInt aPort)
{
__START_PERFORMANCE_LOGGER();
// Set the Host header only if the request is not an HTTP/1.0 request.
RStringPool stringPool = iSession.StringPool();
RHTTPConnectionInfo connInfo = iSession.ConnectionInfo();
THTTPHdrVal httpVersion;
TBool isHttp10 = EFalse;
if( connInfo.Property(stringPool.StringF(HTTP::EHTTPVersion,iStringTable), httpVersion) )
{
__ASSERT_DEBUG( httpVersion.Type() == THTTPHdrVal::KStrFVal, THttpClientPanic::Panic(THttpClientPanic::EInvalidHeaderValueType) );
isHttp10 = (httpVersion.StrF() == stringPool.StringF(HTTP::EHttp10, iStringTable));
}
// Regardless of proxy settings a Host header based on the URI authority must be added unless
// this is an HTTP/1.0 request in which case a Host header should not exist.
RHTTPRequest request = aTrans.Request();
RHTTPHeaders headers = request.GetHeaderCollection();
RStringF hostStr = stringPool.StringF(HTTP::EHost, iStringTable);
if( !isHttp10 )
{
// If the Host header does not already exist, add it!
THTTPHdrVal hostValue;
if( headers.GetField(hostStr, 0, hostValue) == KErrNotFound )
{
// Set the Host header...
RStringF hostValStr;
// Check if its a literal IPV6 address
UriUtils::TUriHostType aHostsType = UriUtils::HostType( aHost );
if ( ( aHostsType != UriUtils::ETextHost ) && ( aHostsType != UriUtils::EIPv4Host ) ) // is an IPv6 or other future protocol address
{
HBufC8* ipv6LiteralHost = HBufC8::NewLC( aHost.Length() + 2 ); // add 2 for the braces
TPtr8 ipv6LiteralHostPtr = ipv6LiteralHost->Des();
ipv6LiteralHostPtr.Append( KIPv6HostOpenBrace );
ipv6LiteralHostPtr.Append( aHost );
ipv6LiteralHostPtr.Append( KIPv6HostCloseBrace );
hostValStr = stringPool.OpenFStringL( ipv6LiteralHostPtr );
CleanupStack::PopAndDestroy( ipv6LiteralHost );
}
else
{
hostValStr = stringPool.OpenFStringL( aHost );
}
CleanupClosePushL(hostValStr);
THTTPHdrVal hostVal(hostValStr);
headers.SetFieldL(hostStr, hostVal);
CleanupStack::PopAndDestroy(&hostValStr);
// Also set the port number if Host header is not empty and a port
// number is not the default.
if( aPort != KHttpDefaultPort && aPort != KHttpDefaultSecurePort )
{
THTTPHdrVal portVal(aPort);
RStringF portStr = stringPool.StringF(HTTP::EPort,iStringTable);
headers.SetParamL(hostStr, portStr, portVal, 0);
}
}
// else the Host header already exists, so do nothing
}
else // This is an HTTP/1.0 request
headers.RemoveField(hostStr);
__END_PERFORMANCE_LOGGER(_L(",CHttpClientHandler::SetHostHeaderL()"));
return isHttp10;
}
void CHttpClientHandler::AddTunnelInfoL(RHTTPTransaction aTrans, const TDesC8& aHost, TInt aPort)
{
// Convert the port number into a descriptor...
HBufC8* port = NULL;
InetProtTextUtils::ConvertIntToDescriptorL(aPort, port);
CleanupStack::PushL(port);
// Create the Request-URI for the CONNECT request - it is in the 'authority'
// form, as described in RFC2616, section 5.1.2.
CAuthority8* authority = CAuthority8::NewLC();
authority->SetComponentL(aHost, EAuthorityHost);
authority->SetComponentL(*port, EAuthorityPort);
// Set this as the ETunnel property in the transaction.
RStringPool stringPool = iSession.StringPool();
RStringF tunnelStr = stringPool.OpenFStringL(authority->Authority().AuthorityDes());
CleanupClosePushL(tunnelStr);
THTTPHdrVal tunnelVal(tunnelStr);
aTrans.PropertySet().SetPropertyL(stringPool.StringF(HTTP::ETunnel, iStringTable), tunnelVal);
CleanupStack::PopAndDestroy(3, port); // delete authority and close tunnelStr
}
TBool CHttpClientHandler::SelectConnectionManagerL(const CHttpConnectionInfo& aConnectionInfo, RHTTPTransaction aTrans, TBool aCanPipeline, CHttpConnectionManager *&aManager )
{
// Selecting a connection manager depends on whether the transaction can be
// pipelined. If so, then the connection manager of choice would be the one
// that is connected to the correct location and busy with other transactions.
// If there is no connection manager that fits this criteria, then the
// selection process follows that for a transaction that cannot be pipelined.
// The order of preference for selecting a connection manager -
// 1) A manager that is connected to the correct location and is available.
// This is the backup-choice for a transaction that can be pipelined.
// 2) A manager that is not connected to anywhere.
// 3) Create a new manager if the limit has not been reached.
// 4) Use a manager that is connected to a different host (but not being
// used) and will therefore need disconnecting and reconnecting.
CHttpConnectionManager* backupChoice = NULL;
CHttpConnectionManager* secondChoice = NULL;
CHttpConnectionManager* fourthChoice = NULL;
CHttpConnectionManager* managerConnecting = NULL;
TBool newConnection = ETrue;
TInt numConnectionsToSingleServer = 0;
TBool connectMethod = aTrans.Request().Method().Index(iStringTable) == HTTP::ECONNECT;
const TInt numConnMan = iConnectionManagers.Count();
for( TInt ii=0; (ii < numConnMan && aManager == NULL ); ++ii )
{
CHttpConnectionManager::TConnectionStatus status = iConnectionManagers[ii]->Status();
switch( status )
{
case CHttpConnectionManager::ENotConnected:
{
// This is a pretty good option because it just needs a connection
// to be established
secondChoice = iConnectionManagers[ii];
} break;
case CHttpConnectionManager::EConnectedAndAvailable:
{
// This is the ideal situation if the location matches, otherwise it
// becomes the fourth choice
const CHttpConnectionInfo& connectionInfo = iConnectionManagers[ii]->ConnectionInfo();
if( connectionInfo.HostAndPortMatches(aConnectionInfo) )
{
if(!connectMethod)
{
++numConnectionsToSingleServer;
if( aCanPipeline )
{
// This is the backup-choice
backupChoice = iConnectionManagers[ii];
}
else
{
// This is the one!
aManager = iConnectionManagers[ii];
newConnection = EFalse;
}
}
}
else
{
// Non-matching connection info - fourth choice.
fourthChoice = iConnectionManagers[ii];
}
} break;
case CHttpConnectionManager::EConnectedAndBusy:
{
// This is the ideal choice if the pipelining can done and the
// location and secure status match.
const CHttpConnectionInfo& connectionInfo = iConnectionManagers[ii]->ConnectionInfo();
TBool hostAndPortMatches = connectionInfo.HostAndPortMatches(aConnectionInfo);
if(hostAndPortMatches)
{
++numConnectionsToSingleServer;
}
if( !connectMethod && aCanPipeline &&
hostAndPortMatches &&
connectionInfo.IsSecure() == aConnectionInfo.IsSecure() )
{
// This is the one!
aManager = iConnectionManagers[ii];
newConnection = EFalse;
}
} break;
case CHttpConnectionManager::EConnectedNotAvailable:
{
// Do nothing - continue the search...
} break;
case CHttpConnectionManager::EConnectingNotAvailable:
{
const CHttpConnectionInfo& connectionInfo = iConnectionManagers[ii]->ConnectionInfo();
TBool hostAndPortMatches = connectionInfo.HostAndPortMatches(aConnectionInfo);
if(hostAndPortMatches)
{
++numConnectionsToSingleServer;
}
if (aCanPipeline && hostAndPortMatches && connectionInfo.IsSecure() == aConnectionInfo.IsSecure())
{
// We are connecting. So do not initiate another connection.
// We will be able to send the request via the same connection.
managerConnecting = iConnectionManagers[ii];
}
}
break;
default:
// There are no other transport handler states - should not reach here
User::Invariant();
break;
}
}
if( aManager == NULL)
{
if( aCanPipeline && backupChoice != NULL )
{
// Use the connection manager that is connected to the correct
// location but not busy.
aManager = backupChoice;
newConnection = EFalse;
}
else if( secondChoice != NULL )
{
// Use the second choice connection manager - idle one.
aManager = secondChoice;
}
else if( numConnMan < MaxNumConnectionManagers() )
{
// Have not reached the max number of connection managers and so
// can create a new connection manager - check for a transport layer
if( iTransportLayer == NULL )
{
// Create the transport layer
_LIT8(KTcpProtocol, "TCP");
THttpTransportConstructionParams params = THttpTransportConstructionParams(*this);
RHTTPConnectionInfo connInfo = iSession.ConnectionInfo();
RStringPool stringPool = iSession.StringPool();
THTTPHdrVal valPriority;
RStringF strConnMan = stringPool.StringF(HTTP::ETranspHndlrPriority , iStringTable);
params.iPriority = EFalse;
if( connInfo.Property(strConnMan, valPriority) )
{
if(valPriority== stringPool.StringF(HTTP::EEnableTranspHndlrPriority , iStringTable))
params.iPriority = ETrue;
}
iTransportLayer = CHttpTransportLayer::NewL(KTcpProtocol, params);
}
// Check we are doing an optimal pipelining. Read the property value only
// if we haven't created atleast one connection manager yet
if(numConnMan == 0)
{
RHTTPConnectionInfo connInfo = iSession.ConnectionInfo();
RStringPool stringPool = iSession.StringPool();
__ASSERT_DEBUG(!managerConnecting, User::Invariant());
THTTPHdrVal optimalPipelineValue;
RStringF strOptimalPipeline = stringPool.StringF(HTTP::EHttpOptimalPipelining, iStringTable);
if(connInfo.Property(strOptimalPipeline, optimalPipelineValue))
{
iEnableOptimalPipeline = (optimalPipelineValue == stringPool.StringF(HTTP::EHttpEnableOptimalPipelining, iStringTable));
}
}
if(managerConnecting)
{
newConnection = EFalse;
}
else
{
// Create the new connection manager
const TInt maxNumberTransactionsToPipeline = MaxNumTransactionsToPipeline();
aManager = CHttpConnectionManager::NewL(iTransportLayer->SocketFactory(), *this, *iPipelineFallback, maxNumberTransactionsToPipeline, iEnableOptimalPipeline);
CleanupStack::PushL(aManager);
__RecordConnectionManagerCreationL();
#if defined (_DEBUG) && defined (_LOGGING)
aManager->__logger__ = this->__logger__;
#endif
// Append to the store
User::LeaveIfError(iConnectionManagers.Append(aManager));
CleanupStack::Pop(aManager);
}
}
else if( fourthChoice != NULL )
{
// As a last resort reuse one that is connected to the wrong host but
// not being used.
aManager = fourthChoice;
}
}
return newConnection;
}
CHttpConnectionManager* CHttpClientHandler::SelectTunnelConnectionL(const CHttpConnectionInfo& aConnectionInfo, RHTTPTransaction aTrans, TBool aCanPipeline)
{
// Look for connection manager that is a tunnel connection via appropriate
// proxy to appropriate host. If the transaction can be pipelined, then the
// first choice is a connection manager that is connected and busy.
RStringPool stringPool = iSession.StringPool();
THTTPHdrVal hostVal;
#ifdef _DEBUG
TBool found =
#endif
aTrans.PropertySet().Property(stringPool.StringF(HTTP::ETunnel, iStringTable), hostVal);
__ASSERT_DEBUG( found, User::Invariant() );
__ASSERT_DEBUG( hostVal.Type() == THTTPHdrVal::KStrFVal, User::Invariant() );
RStringF host = hostVal.StrF();
TBool notifyCreateTunnel = ETrue;
CHttpConnectionManager* manager = NULL;
CHttpConnectionManager* backupChoice = NULL;
const TInt numConnMan = iConnectionManagers.Count();
for( TInt ii=0; (ii < numConnMan && manager == NULL ); ++ii )
{
CHttpConnectionManager::TConnectionStatus status = iConnectionManagers[ii]->Status();
switch( status )
{
case CHttpConnectionManager::ENotConnected:
case CHttpConnectionManager::EConnectedNotAvailable:
case CHttpConnectionManager::EConnectingNotAvailable:
{
// Do nothing - continue search.
} break;
case CHttpConnectionManager::EConnectedAndAvailable:
{
const CHttpConnectionInfo& connectionInfo = iConnectionManagers[ii]->ConnectionInfo();
if( connectionInfo.HostAndPortMatches(aConnectionInfo) &&
iConnectionManagers[ii]->TunnelMatches(host) )
{
// Location (ie the proxy) and tunnel host match - this is the
// backup choice if pipelining allowed, otherwise it is the one.
if( aCanPipeline )
{
// This is the backup choice...
backupChoice = iConnectionManagers[ii];
}
else
{
// This is the one! There is no need to create a tunnel.
manager = iConnectionManagers[ii];
notifyCreateTunnel = EFalse;
}
}
} break;
case CHttpConnectionManager::EConnectedAndBusy:
{
const CHttpConnectionInfo& connectionInfo = iConnectionManagers[ii]->ConnectionInfo();
if( aCanPipeline &&
connectionInfo.HostAndPortMatches(aConnectionInfo) &&
iConnectionManagers[ii]->TunnelMatches(host) )
{
// Location (ie the proxy) and tunnel host match and pipelining
// is allowed - this is the one! There is no need to create a
// tunnel.
manager = iConnectionManagers[ii];
notifyCreateTunnel = EFalse;
}
} break;
default:
// There are no other transport handler states - should not reach here
User::Invariant();
break;
}
}
if( manager == NULL && backupChoice != NULL )
{
// Use the back-up choice - now no need to create a tunnel.
manager = backupChoice;
notifyCreateTunnel = EFalse;
}
#if defined (_DEBUG) && defined (_LOGGING)
if( manager != NULL )
{
__FLOG_1(_T8("!! Tunnel to %S available"), &host.DesC());
__FLOG_6(
_T8("-> Trans %d, Con %d : can service via host %S, remote port %d (secure : %d, nonpersistent : %d)"),
aTrans.Id(),
GetConnectionID(aTrans),
&manager->ConnectionInfo().Host(),
manager->ConnectionInfo().Port(),
manager->ConnectionInfo().IsSecure(),
manager->ConnectionInfo().IsNonPersistent()
);
}
#endif
if( notifyCreateTunnel )
{
__FLOG_1(_T8("!! No tunnel to %S"), &host.DesC());
__FLOG_2(_T8("-> Trans %d, Con %d : cannot service until tunnel established"), aTrans.Id(),GetConnectionID(aTrans));
// Notify client (or filter) that a tunnel needs to be established
// before this transaction can be serviced.
aTrans.SendEventL(
THTTPEvent::ENeedTunnel,
THTTPEvent::EIncoming,
THTTPFilterHandle(THTTPFilterHandle::EProtocolHandler)
);
}
return manager;
}
TInt CHttpClientHandler::MaxNumConnectionManagers() const
{
// Has this value been previously cached?
if( iMaxNumConnectionManagers == 0 )
{
// No. Use this default should the property not be set
iMaxNumConnectionManagers = KMaxConnectionManagers;
// Check session properties
RHTTPConnectionInfo connInfo = iSession.ConnectionInfo();
RStringPool stringPool = iSession.StringPool();
THTTPHdrVal maxConnMan;
RStringF strConnMan = stringPool.StringF(HTTP::EMaxNumTransportHandlers, iStringTable);
if( connInfo.Property(strConnMan, maxConnMan) )
{
if( maxConnMan.Type() == THTTPHdrVal::KTIntVal )
iMaxNumConnectionManagers = maxConnMan.Int();
}
}
return iMaxNumConnectionManagers;
}
void CHttpClientHandler::SetupProxyInformation(RHTTPTransaction aTrans)
{
// Assume a direct connection unless the properties specifically indicate
// that a proxy should be used.
iUseProxy = EFalse;
iProxyAddress.Close();
RStringPool stringPool(iSession.StringPool());
THTTPHdrVal useProxy;
THTTPHdrVal address;
RStringF proxyUsage = stringPool.StringF(HTTP::EProxyUsage, iStringTable);
RStringF proxyAddress = stringPool.StringF(HTTP::EProxyAddress, iStringTable);
RHTTPPropertySet transactionProperties = aTrans.PropertySet();
RHTTPPropertySet sessionProperties = iSession.ConnectionInfo();
// First check the transaction properties for proxy info. If the transaction
// has its own proxy info set then this should be used, including the fact
// that a proxy should not be used. Otherwise check the session properties
// for proxy info.
if( transactionProperties.Property(proxyUsage, useProxy) )
{
__ASSERT_DEBUG( useProxy.Type() == THTTPHdrVal::KStrFVal, THttpClientPanic::Panic(THttpClientPanic::EInvalidProxySetting) );
// The transaction has proxy info set...
iUseProxy = (useProxy.StrF().Index(iStringTable) == HTTP::EUseProxy);
if( iUseProxy )
{
if( transactionProperties.Property(proxyAddress, address) )
{
__ASSERT_DEBUG( address.Type() == THTTPHdrVal::KStrFVal, THttpClientPanic::Panic(THttpClientPanic::EInvalidProxySetting) );
iProxyAddress = address.StrF().Copy();
}
else
{
// It is invalid to specify using a proxy and not set a proxy
// address!
THttpClientPanic::Panic(THttpClientPanic::EInvalidProxySetting);
}
}
}
else if( sessionProperties.Property(proxyUsage, useProxy) )
{
__ASSERT_DEBUG( useProxy.Type() == THTTPHdrVal::KStrFVal, THttpClientPanic::Panic(THttpClientPanic::EInvalidProxySetting) );
// The session has proxy info set...
iUseProxy = (useProxy.StrF().Index(iStringTable) == HTTP::EUseProxy);
if( iUseProxy )
{
if( sessionProperties.Property(proxyAddress, address) )
{
__ASSERT_DEBUG( address.Type() == THTTPHdrVal::KStrFVal, THttpClientPanic::Panic(THttpClientPanic::EInvalidProxySetting) );
iProxyAddress = address.StrF().Copy();
}
else
{
// It is invalid to specify using a proxy and not set a proxy
// address!
THttpClientPanic::Panic(THttpClientPanic::EInvalidProxySetting);
}
}
}
}
TBool CHttpClientHandler::CheckPipelineSupport(RHTTPTransaction aTrans)
{
// Check to see if the pipelining support has been disabled/enabled for this
// transaction.
RStringPool stringPool = iSession.StringPool();
RStringF pipeline = stringPool.StringF(HTTP::EHttpPipelining, iStringTable);
THTTPHdrVal value;
TBool canPipeline = ETrue;
if( aTrans.PropertySet().Property(pipeline, value) )
{
__ASSERT_DEBUG( value.Type() == THTTPHdrVal::KStrFVal, User::Invariant() );
canPipeline = (value.StrF().Index(iStringTable) != HTTP::EDisablePipelining);
}
return canPipeline;
}
/*
* Methods from CProtocolHandler
*/
TInt CHttpClientHandler::SessionServerCert(TCertInfo& /*aServerCert*/)
{
return KErrNotSupported;
}
TInt CHttpClientHandler::TransactionServerCert(TCertInfo& aServerCert, RHTTPTransaction aTransaction)
{
TInt error = KErrNotFound;
const CX509Certificate* cert = static_cast<const CX509Certificate*>(TransactionServerCert(aTransaction));
if(cert)
{
TRAPD( failed, GetCertInfoL(*cert, aServerCert));
// pass back the leaving system error.
error = (failed < 0 ) ? failed : KErrNone;
}
return error;
}
void CHttpClientHandler::CreateCodecL()
{
iCodec = CHeaderCodecPlugin::NewL( KHttpClientCodecName, iSession.StringPool());
}
CProtTransaction* CHttpClientHandler::CreateProtTransactionL(RHTTPTransaction aTransaction)
{
// Create the appropriate CProtTransaction object
CHttpClientTransaction* transaction = CHttpClientTransaction::NewL(aTransaction);
return transaction;
}
TBool CHttpClientHandler::ServiceL(CProtTransaction& aTrans)
{
__START_PERFORMANCE_LOGGER();
// Prepare the transaction...
TBool needTunnel = EFalse;
TBool canPipeline = EFalse;
CHttpConnectionInfo* info = PrepareTransactionL(aTrans.Transaction(), needTunnel, canPipeline);
CleanupStack::PushL(info);
CHttpConnectionManager* manager = NULL;
TBool isNewConnection = ETrue;
if( needTunnel )
{
// Find a connection that is tunnelling via appropriate proxy to the
// appropriate host.
manager = SelectTunnelConnectionL(*info, aTrans.Transaction(), canPipeline);
}
else
{
// Look for a normal connection.
isNewConnection = SelectConnectionManagerL(*info, aTrans.Transaction(), canPipeline, manager);
}
if( manager != NULL )
{
// Pass the connection manager to the transaction - need to do this
// before creating tx- and rx- data objects.
CHttpClientTransaction* trans = static_cast<CHttpClientTransaction*>(&aTrans);
trans->SetConnectionManager(*manager);
if(isNewConnection)
{
//Increment retry count only if it is a new connection.
trans->IncRetryCount();
}
// Create the tx- and rx- objects in the transaction
aTrans.CreateTxDataL();
aTrans.CreateRxDataL(*this);
#if defined (_DEBUG) && defined (_LOGGING)
CHttpRequestComposer* composer = static_cast<CHttpRequestComposer*>(&aTrans.TxData());
CHttpResponseParser* parser = static_cast<CHttpResponseParser*>(&aTrans.RxData());
composer->__logger__ = this->__logger__;
parser->__logger__ = this->__logger__;
#endif
// If the transaction cannot be pipelined, then set the connection
// manager to not allow pipelining. Once this transaction has completed
// the manager will revert to allowing pipelining.
if( !canPipeline )
manager->DisablePipelining();
// Remove connection info from cleanup stack before submiting to the
// connection manager - ownership is passed to the connection manager.
CleanupStack::Pop(info);
MHttpRequest& request = static_cast<CHttpRequestComposer&>(aTrans.TxData());
MHttpResponse& response = static_cast<CHttpResponseParser&>(aTrans.RxData());
manager->SubmitL(*info, request, response);
__END_PERFORMANCE_LOGGER(_L(",CHttpClientHandler::ServiceL()"));
return ETrue;
}
CleanupStack::PopAndDestroy(info);
__END_PERFORMANCE_LOGGER(_L(",CHttpClientHandler::ServiceL()"));
return EFalse;
}
void CHttpClientHandler::ClosedTransactionHook(CProtTransaction* aTrans)
{
__FLOG_0(_T8("!! Closing transaction - client request"));
__FLOG_1(_T8("-> Trans %d : closed"), aTrans->Transaction().Id());
delete aTrans;
}
void CHttpClientHandler::CancelTransactionHook(CProtTransaction& aTransaction)
{
// Is this transaction still alive - check to see if it still has a
// connection manager.
CHttpClientTransaction& trans = static_cast<CHttpClientTransaction&>(aTransaction);
CHttpConnectionManager* manager = trans.ConnectionManager();
__FLOG_0(_T8("!! Cancelling transaction - client request"));
if( manager != NULL )
{
__FLOG_2(_T8("-> Trans %d, Con %d : still alive - cancelling its connection manager"), aTransaction.Transaction().Id(),GetConnectionID(manager));
// Transaction is still alive - ask its connection manager to cancel it.
MHttpRequest& request = static_cast<CHttpRequestComposer&>(aTransaction.TxData());
MHttpResponse& response = static_cast<CHttpResponseParser&>(aTransaction.RxData());
manager->CancelSubmission(request, response);
// Connection is now cancelled - remove the connection manager from the
// transaction.
trans.RemoveConnectionManager();
}
#if defined (_DEBUG) && defined (_LOGGING)
else
__FLOG_1(_T8("-> Trans %d : already finished - nothing to do"), aTransaction.Transaction().Id());
#endif
}
void CHttpClientHandler::NotifyNewRequestBodyPart(CProtTransaction& aTransaction)
{
// Notify the transaction of more data.
static_cast<CHttpRequestComposer&>(aTransaction.TxData()).NotifyMoreRequestBodyData();
}
void CHttpClientHandler::GetInterfaceL(TUid aInterfaceId, MProtHandlerInterface*& aInterfacePtr)
{
switch(aInterfaceId.iUid)
{
case KProtHandlerTransactionServerCertUid:
{
aInterfacePtr = this;
break;
}
default:
{
CProtocolHandler::GetInterfaceL(aInterfaceId, aInterfacePtr);
break;
}
}
}
/*
* Methods from MConnectionPrefsProvider
*/
TBool CHttpClientHandler::SupplyCommsConnection( RConnection*& aConnectionPtr )
{
aConnectionPtr = NULL;
RHTTPConnectionInfo connInfo = iSession.ConnectionInfo();
RStringPool stringPool = iSession.StringPool();
THTTPHdrVal value;
TBool hasValue = connInfo.Property( stringPool.StringF(HTTP::EHttpSocketConnection, iStringTable), value );
if( hasValue && value.Type() == THTTPHdrVal::KTIntVal )
{
aConnectionPtr = reinterpret_cast<RConnection*>(value.Int());
return ETrue;
}
return EFalse;
}
TBool CHttpClientHandler::SupplySocketServerHandle ( TInt& aSocketServerHandle )
{
aSocketServerHandle = 0;
RHTTPConnectionInfo connInfo = iSession.ConnectionInfo();
RStringPool stringPool = iSession.StringPool();
THTTPHdrVal value;
TBool hasValue = connInfo.Property(stringPool.StringF(HTTP::EHttpSocketServ, iStringTable), value);
if( hasValue && value.Type() == THTTPHdrVal::KTIntVal )
{
aSocketServerHandle = value.Int();
return ETrue;
}
return EFalse;
}
void CHttpClientHandler::SetCommsConnectionL( RConnection* aConnectionPtr )
{
RHTTPConnectionInfo connInfo = iSession.ConnectionInfo();
RStringPool stringPool = iSession.StringPool();
TInt connectionPtrVal = reinterpret_cast<TInt>(aConnectionPtr);
connInfo.SetPropertyL ( stringPool.StringF(HTTP::EHttpSocketConnection, iStringTable ), THTTPHdrVal (connectionPtrVal) );
}
void CHttpClientHandler::SetSocketServerHandleL ( TInt aSocketServerHandle )
{
RHTTPConnectionInfo connInfo = iSession.ConnectionInfo();
RStringPool stringPool = iSession.StringPool();
connInfo.SetPropertyL ( stringPool.StringF(HTTP::EHttpSocketServ, iStringTable ), THTTPHdrVal (aSocketServerHandle) );
}
void CHttpClientHandler::GetSecurityPrefs(TBool& aDialogPrompt, MSecurityPolicy*& aSecurityPolicy)
{
// Set the security policy
aSecurityPolicy = iSecurityPolicy;
// Set the dialog info - check the session properties
THTTPHdrVal value;
RStringF secureDialog = iSession.StringPool().StringF(HTTP::ESecureDialog, iStringTable);
TBool hasValue = iSession.ConnectionInfo().Property(secureDialog, value);
if( hasValue && value.Type() == THTTPHdrVal::KStrFVal &&
value.StrF().Index(iStringTable) == HTTP::EDialogNoPrompt )
{
// Client has requested to not be prompted
aDialogPrompt = EFalse;
}
else
{
// The default value - the client will be prompted
aDialogPrompt = ETrue;
}
}
TBool CHttpClientHandler::ImmediateSocketShutdown()
{
TBool immediateSocketShutdown = EFalse;
if( *iSessionClosingPtr ) // iSessionClosingPtr cannot be NULL as its is set in ConstructL
{
// Session is closing down, check the session properties to check if the client has requested
// an immediate socket shutdown
THTTPHdrVal value;
RStringF socketShutdownMode = iSession.StringPool().StringF(HTTP::ESocketShutdownMode, iStringTable);
TBool hasValue = iSession.ConnectionInfo().Property(socketShutdownMode, value);
if( hasValue && value.Type() == THTTPHdrVal::KStrFVal &&
value.StrF().Index(iStringTable) == HTTP::ESocketShutdownImmediate )
{
__FLOG_0(_T8("!! Immediate socket shutdown requested by client"));
immediateSocketShutdown = ETrue;
}
}
return immediateSocketShutdown;
}
TInt CHttpClientHandler::SessionId()
{
THTTPHdrVal value;
TInt result = KErrNotFound;
const TBool hasValue = iSession.ConnectionInfo().Property(iSession.StringPool().StringF(HTTP::ESessionId, iStringTable), value);
if( hasValue && value.Type()==THTTPHdrVal::KTIntVal) // silently ignore inappropriate types
{
result = value.Int();
if(result<0)
{
result = KErrNotFound;
}
}
return result;
}
/*
* Methods from MRxDataObserver
*/
TInt CHttpClientHandler::SetStatus(CRxData& aRxData, TInt aStatus)
{
// Have received a status message from an Rx data object - check the status.
CHttpClientTransaction& protTrans = static_cast<CHttpClientTransaction&>(aRxData.ProtTrans());
RHTTPTransaction trans = protTrans.Transaction();
TInt err = KErrNone;
switch( aStatus )
{
case THTTPEvent::EResponseComplete:
{
__FLOG_2(_T8("Trans %d, Con %d : transaction completed"), trans.Id(), GetConnectionID(trans));
// The response is complete - the client has been passed all the data
// and released it. Check to see if this was a CONNECT request.
RStringPool stringPool = iSession.StringPool();
TInt status = aStatus;
if( trans.Request().Method().Index(iStringTable) == HTTP::ECONNECT )
{
if( HTTPStatus::IsSuccessful(trans.Response().StatusCode()) )
{
// A 2xx status code - tunnel has been successfully established.
// Mark the connection manager as tunnelled connection, providing
// the host to which the tunnel leads.
THTTPHdrVal hostVal;
#ifdef _DEBUG
TBool found =
#endif
trans.PropertySet().Property(stringPool.StringF(HTTP::ETunnel, iStringTable), hostVal);
__ASSERT_DEBUG( found, User::Invariant() );
__ASSERT_DEBUG( hostVal.Type() == THTTPHdrVal::KStrFVal, User::Invariant() );
protTrans.ConnectionManager()->TunnelConnection(hostVal.StrF());
}
else
{
status = THTTPEvent::EFailed;
#if defined (_DEBUG) && defined (_LOGGING)
THTTPHdrVal hostVal;
TBool found = trans.PropertySet().Property(stringPool.StringF(HTTP::ETunnel, iStringTable), hostVal);
__ASSERT_DEBUG( found, User::Invariant() );
__ASSERT_DEBUG( hostVal.Type() == THTTPHdrVal::KStrFVal, User::Invariant() );
const CHttpConnectionInfo& connectionInfo = protTrans.ConnectionManager()->ConnectionInfo();
__FLOG_2(
_T8("!! Tunnel failed : %d %S"),
trans.Response().StatusCode(),
&trans.Response().StatusText().DesC()
);
__FLOG_5(
_T8("-> Tunnel to %S on connection to host %S, remote port %d (secure : %d, nonpersistent : %d)"),
&hostVal.StrF().DesC(),
&connectionInfo.Host(),
connectionInfo.Port(),
connectionInfo.IsSecure(),
connectionInfo.IsNonPersistent()
);
#endif
}
}
// Ensure that the connection manager is still not dealing with this
// request.
MHttpRequest& request = static_cast<CHttpRequestComposer&>(protTrans.TxData());
protTrans.ConnectionManager()->CheckRequestComplete(request);
// The transaction has no further use for the connection manager - it
// can now be removed.
protTrans.RemoveConnectionManager();
// The transaction is now complete - inform the base class.
err = TransactionCompleted(trans, status);
} break;
default:
// Unknown status - do nothing, unless an error
if( aStatus < 0 )
{
__FLOG_1(_T8("!! Error : %d"), aStatus);
if( aStatus == KErrHttpPipeliningError )
{
__FLOG_2(_T8("-> Trans %d, Con %d : transaction was being pipelined"), trans.Id(), GetConnectionID(trans));
__FLOG_2(_T8("-> Trans %d, Con %d : re-try without pipelining"), trans.Id(), GetConnectionID(trans));
// Specify that this transaction should not be pipelined when
// it is re-submitted.
RStringPool stringPool = iSession.StringPool();
RStringF pipeline = stringPool.StringF(HTTP::EHttpPipelining, iStringTable);
RStringF disable = stringPool.StringF(HTTP::EDisablePipelining, iStringTable);
err = trans.PropertySet().SetProperty(pipeline, disable);
if(err != KErrNone)
{
break;
}
}
#if defined (_DEBUG) && defined (_LOGGING)
else if( aStatus == KErrHttpNonPipeliningError )
{
__FLOG_2(_T8("-> Trans %d, Con %d : transaction was being pipelined"), trans.Id(), GetConnectionID(trans));
__FLOG_2(_T8("-> Trans %d, Con %d : re-try with pipelining again"), trans.Id(), GetConnectionID(trans));
}
else
{
__FLOG_2(_T8("-> Trans %d, Con %d : transaction failed"), trans.Id(), GetConnectionID(trans));
}
#endif
// An error code has occured. As no further data will be exchanged
// with the origin server. The connection manager can now be removed.
protTrans.RemoveConnectionManager();
if( aStatus == KErrEof )
{
__FLOG_4(_T8("-> Trans %d, Con %d : reporting %d (KErrDisconnected) instead of %d"), trans.Id(), GetConnectionID(trans), KErrDisconnected, aStatus);
// Convert these errors to KErrDisconnected
aStatus = KErrDisconnected;
}
// Propagate the error back to the client and mark this transaction
// as completed.
err = TransactionCompleted(trans, aStatus);
}
break;
}
return err;
}
void CHttpClientHandler::SetStatusL(CRxData& aRxData, TInt aStatus)
{
User::LeaveIfError(SetStatus(aRxData, aStatus));
}
/*
* Methods from MHttpBatchingPropertiesCallback
*/
TInt CHttpClientHandler::GetMaxBatchingBufferSize()
{
TInt batchingBuffer = 0;
RHTTPConnectionInfo connInfo = iSession.ConnectionInfo();
RStringPool stringPool = iSession.StringPool();
THTTPHdrVal doBatching;
RStringF batchingSetting = stringPool.StringF(HTTP::EHttpBatching, iStringTable);
if (connInfo.Property(batchingSetting, doBatching))
{
TBool batchingSupported = EFalse; // default behaviour is batching disabled
// First of all check if batching has been enabled
batchingSupported = (doBatching.StrF().Index(iStringTable) == HTTP::EEnableBatching);
if (batchingSupported)
{
THTTPHdrVal bufferSize;
RStringF buffer = stringPool.StringF(HTTP::EBatchingBufferSize, iStringTable);
// If batching has been enabled, check for a client-specified buffer size to use
if (connInfo.Property(buffer, bufferSize))
{
__ASSERT_DEBUG(bufferSize.Type() == THTTPHdrVal::KTIntVal, THttpClientPanic::Panic(THttpClientPanic::EInvalidBatchingSetting));
batchingBuffer = bufferSize.Int();
}
// No client-specified buffer size therefore use the default value
else
batchingBuffer = KDefaultBatchingBufSize;
}
}
return batchingBuffer;
}
/*
* Methods from MProtHandlerInterface
*/
const CCertificate* CHttpClientHandler::SessionServerCert()
{
return NULL;
}
const CCertificate* CHttpClientHandler::TransactionServerCert(RHTTPTransaction aTransaction)
{
const CProtTransaction* trans = FindProtocolTransaction(aTransaction);
if(trans != NULL)
{
CHttpConnectionManager* manager = static_cast<const CHttpClientTransaction*>(trans)->ConnectionManager();
if( manager )
{
return manager->ServerCert();
}
}
return NULL;
}
void CHttpClientHandler::MHFRunL(RHTTPTransaction aTransaction, const THTTPEvent& aEvent)
{
// Handle the event
switch (aEvent.iStatus)
{
case THTTPEvent::EGetCipherSuite:
{
GetCipherSuiteL(aTransaction);
}
break;
case THTTPEvent::ECancelWaitFor100Continue:
{
const CProtTransaction* pT = FindProtocolTransaction(aTransaction);
if(pT != NULL)
{
static_cast<CHttpRequestComposer&>(pT->TxData()).CancelWaitFor100Continue();
}
}
break;
default:
CProtocolHandler::MHFRunL(aTransaction,aEvent);
}
}
void CHttpClientHandler::GetCipherSuiteL(RHTTPTransaction aTransaction)
{
RStringPool stringPool = iSession.StringPool();
RHTTPTransactionPropertySet properties(aTransaction.PropertySet());
const CProtTransaction* transaction = FindProtocolTransaction(aTransaction);
if (transaction != NULL)
{
CHttpConnectionManager* connectionManager = static_cast<const CHttpClientTransaction*>(transaction)->ConnectionManager();
if(connectionManager)
{
TBuf8<8> cipherSuite;
TInt error = connectionManager->CipherSuite(cipherSuite);
if (error == KErrNone)
{
RString cipherSuiteString = stringPool.OpenStringL(cipherSuite);
THTTPHdrVal hdrValue(cipherSuiteString);
CleanupClosePushL(cipherSuiteString);
properties.SetPropertyL(stringPool.StringF(HTTP::ECipherSuiteValue, iStringTable), hdrValue);
CleanupStack::PopAndDestroy(&cipherSuiteString);
return;
}
}
}
// No cipher suite could be obtained so set the ECipherSuiteValue property to an empty string.
properties.SetPropertyL(stringPool.StringF(HTTP::ECipherSuiteValue, iStringTable), THTTPHdrVal(RString()));
}
TInt CHttpClientHandler::MaxNumTransactionsToPipeline() const
{
/* This is called when the first transaction is submitted. This is so filters can set the
session setting HTTP::EMaxNumTransactionsToPipeline
However it is only required to be set when the first transaction is submitted. Therefore
check to see if the value is already cached.
*/
if (iMaxNumTransactionsToPipeline == 0)
{
iMaxNumTransactionsToPipeline = KDefaultMaxNumberTransactionsToPipeline;
// Check session properties
RHTTPConnectionInfo connInfo = iSession.ConnectionInfo();
RStringPool stringPool = iSession.StringPool();
THTTPHdrVal maxToPipeline;
RStringF maxToPipelineString = stringPool.StringF(HTTP::EMaxNumTransactionsToPipeline, iStringTable);
if(connInfo.Property(maxToPipelineString, maxToPipeline))
{
if(maxToPipeline.Type() == THTTPHdrVal::KTIntVal)
{
iMaxNumTransactionsToPipeline = maxToPipeline.Int();
}
}
}
return iMaxNumTransactionsToPipeline;
}
void CHttpClientHandler::__RecordConnectionManagerCreationL()
// In debug mode update a session property to record when each connection manager is created
// This is used to validate the runtime behavior of pipelining use cases
{
#if defined (_DEBUG)
_LIT8(KNumberConnectionManagers, "__NumConnectionManagers");
RStringPool stringPool = iSession.StringPool();
RStringF numberConnectionsString = stringPool.OpenFStringL(KNumberConnectionManagers);
CleanupClosePushL(numberConnectionsString);
RHTTPConnectionInfo connInfo = iSession.ConnectionInfo();
TInt numberConnections =0;
THTTPHdrVal numberConnectionsVal;
if (connInfo.Property(numberConnectionsString, numberConnectionsVal))
{
numberConnections = numberConnectionsVal.Int();
connInfo.RemoveProperty(numberConnectionsString);
}
numberConnections++;
numberConnectionsVal.SetInt(numberConnections);
connInfo.SetPropertyL(numberConnectionsString, numberConnectionsVal);
CleanupStack::PopAndDestroy(&numberConnectionsString);
#endif
}
#if defined (_DEBUG)
TInt CHttpClientHandler::GetConnectionID( const CHttpConnectionManager* aConnectionManager )
//in debug mode it returns the connection ID, if not in the list returns 0
{
TInt i = iConnectionManagers.Count();
for( ;i > 0; --i )
{
if( iConnectionManagers[i-1] == aConnectionManager )
{
break;
}
}
return i;
}
TInt CHttpClientHandler::GetConnectionID( const RHTTPTransaction &aTrans )
{
CHttpConnectionManager* manager = NULL;
const CProtTransaction* trans = FindProtocolTransaction(aTrans);
if(trans != NULL)
{
manager = static_cast< const CHttpClientTransaction* >( trans )->ConnectionManager();
}
return GetConnectionID( manager );
}
#endif
void CHttpClientHandler::GetCertInfoL(const CX509Certificate& aSource, TCertInfo& aDest)
{
TInt len;
//Fetch Fingerprint
len = Min(aSource.Fingerprint().Length(),aDest.iFingerprint.MaxLength());
aDest.iFingerprint.Copy(aSource.Fingerprint().Ptr(),len);
//Fetch SerialNumber
len = Min(aSource.SerialNumber().Length(),aDest.iSerialNo.MaxLength());
aDest.iSerialNo.Copy(aSource.SerialNumber().Ptr(),len);
//Fetch PublicKey
const CSubjectPublicKeyInfo& publicKeyInfo = aSource.PublicKey();
len = Min(publicKeyInfo.KeyData().Length(),aDest.iPublicKey.MaxLength());
aDest.iPublicKey.Copy(publicKeyInfo.KeyData().Ptr(),len);
//Fetch PublicKeyAlg
aDest.iPkAlg= publicKeyInfo.AlgorithmId();
//Fetch VersionNo
aDest.iVersionNo = aSource.Version();
//Fetch StartValDate
const CValidityPeriod& validityPeriod = aSource.ValidityPeriod();
aDest.iStartValDate = validityPeriod.Start();
//Fetch EndValDate
aDest.iEndValDate = validityPeriod.Finish();
//Fetch SubjectDNInfo
const CX500DistinguishedName& subjectName = aSource.SubjectName();
GetDNInfo(subjectName,aDest.iSubjectDNInfo);
//Fetch IssuerDNInfo;
const CX500DistinguishedName& issuerName = aSource.IssuerName();
GetDNInfo(issuerName,aDest.iIssuerDNInfo);
// Fetch Alt Name
aDest.iDNSName.Copy(KNullDesC);
//fetch digest alg
aDest.iDigAlg = aSource.SigningAlgorithm().DigestAlgorithm().Algorithm();
const CX509CertExtension* ext = aSource.Extension(KSubjectAltName);
if( ext )
{
CX509AltNameExt* subjectAltName = CX509AltNameExt::NewLC(ext->Data());
const CArrayPtrFlat<CX509GeneralName>& gNs = subjectAltName->AltName();
const TInt count = gNs.Count();
for( TInt i = 0; i < count; ++i )
{
const CX509GeneralName* gN = gNs.At(i);
if( gN->Tag() == EX509DNSName )
{
CX509DNSName* dNS = CX509DNSName::NewLC(gN->Data());
const TPtrC dNSValue = dNS->Name();
aDest.iDNSName.Copy(dNSValue);
CleanupStack::PopAndDestroy(dNS);
break;
}
}
CleanupStack::PopAndDestroy(subjectAltName);
}
}
void CHttpClientHandler::GetDNInfo(const CX500DistinguishedName& aSource, TDNInfo& aDest)
{
const TInt count = aSource.Count();
for( TInt i=0; i<count; ++i )
{
const CX520AttributeTypeAndValue& attribute = aSource.Element(i);
HBufC* valuePtr = NULL;
TDes8* destination = NULL;
TInt maxLength = 0 ;
TBool found = ETrue;
const TDesC& type = attribute.Type();
if( type.Compare(KX520CountryName) == 0 )
{
maxLength = aDest.iCountry.MaxLength();
destination = &aDest.iCountry;
}
else if( type.Compare(KX520OrganizationalUnitName) == 0 )
{
maxLength = aDest.iOrganizationUnit.MaxLength();
destination = &aDest.iOrganizationUnit;
}
else if( type.Compare(KX520OrganizationName) == 0 )
{
maxLength = aDest.iOrganization.MaxLength();
destination = &aDest.iOrganization;
}
else if( type.Compare(KX520CommonName) == 0 )
{
maxLength = aDest.iCommonName.MaxLength();
destination = &aDest.iCommonName;
}
else if( type.Compare(KX520LocalityName) == 0 )
{
maxLength = aDest.iLocality.MaxLength();
destination = &aDest.iLocality;
}
else
{
found = EFalse;
}
if( found )
{
TRAPD(ret, (valuePtr = attribute.ValueL()));
if( ret != KErrNone )
{
// Non fatal error - just zero the descriptor
destination->Zero();
}
else
{
TInt len = Min(valuePtr->Length(), maxLength);
TPtrC value = TPtrC(valuePtr->Des().Ptr(), len);
destination->Copy(value);
delete valuePtr;
}
}
}
}
TInt CHttpClientHandler::GetRecvBufferSize()
{
iRecvBufferSize = KDefaultBufferSize;
// Check session properties
RHTTPConnectionInfo connInfo = iSession.ConnectionInfo();
RStringPool stringPool = iSession.StringPool();
THTTPHdrVal value;
RStringF recvBufferSize = stringPool.StringF(HTTP::ERecvBufferSize, iStringTable);
if(connInfo.Property(recvBufferSize, value))
{
if(value.Type() == THTTPHdrVal::KTIntVal && value.Int() > 0)
{
iRecvBufferSize = value.Int();
}
}
return iRecvBufferSize;
}
void CHttpClientHandler::EnsureStrictConnectMethodHeaders(RHTTPTransaction aTransaction)
{
// Check only for CONNECT method request
// Strict & mandatory headers as needed by CONNECT request are
// User-Agent, x-wap-profile, EProxyAuthorization
RHTTPRequest request = aTransaction.Request();
RHTTPSession session = aTransaction.Session();
RStringPool sp = session.StringPool();
if(aTransaction.Request().Method().Index(iStringTable) == HTTP::ECONNECT)
{
RStringF strictConnectHeaders = sp.StringF(HTTP::EStrictConnectHeaders, iStringTable);
RHTTPPropertySet sessionProperties = session.ConnectionInfo();
THTTPHdrVal hdrVal;
if(sessionProperties.Property(strictConnectHeaders, hdrVal) &&
(hdrVal.Type() == THTTPHdrVal::KStrFVal) &&
(hdrVal.StrF().Index(iStringTable) == HTTP::EEnableStrictConnectHeaders))
{
RStringF profileHeader = sp.OpenFStringL(KUAProfile);
CleanupClosePushL(profileHeader);
RHTTPHeaders hdr = request.GetHeaderCollection();
THTTPHdrFieldIter it = hdr.Fields();
while(!it.AtEnd())
{
RStringTokenF hdrStr = it();
RStringF hdrNameStr = sp.StringF(hdrStr);
TInt hdrIndex = hdrNameStr.Index(iStringTable);
// Check if it is a UA/User-Agent/Proxy-Auhorization Profile header
if((hdrNameStr == profileHeader) || (hdrIndex == HTTP::EUserAgent) || (hdrIndex == HTTP::EProxyAuthorization))
{
++it; // Keep going
}
else
{
// Anything else remove the header and reset the iterator
hdr.RemoveField(hdrNameStr);
it.First(); // Not so efficient.
}
}
CleanupStack::PopAndDestroy(&profileHeader);
}
}
}