diff -r 000000000000 -r b16258d2340f applayerpluginsandutils/httpprotocolplugins/httpclient/chttpclienthandler.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/applayerpluginsandutils/httpprotocolplugins/httpclient/chttpclienthandler.cpp Tue Feb 02 01:09:52 2010 +0200 @@ -0,0 +1,1697 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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(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(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(&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(&aTrans.TxData()); + CHttpResponseParser* parser = static_cast(&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(aTrans.TxData()); + MHttpResponse& response = static_cast(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(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(aTransaction.TxData()); + MHttpResponse& response = static_cast(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(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(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(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(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(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(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(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(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& 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; iZero(); + } + 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); + } + } + } +