httpfilters/uaproffilter/src/uaproffilter.cpp
author Pat Downey <patd@symbian.org>
Wed, 01 Sep 2010 12:21:21 +0100
branchRCL_3
changeset 20 a0da872af3fa
parent 19 c0c2f28ace9c
permissions -rw-r--r--
Revert incorrect RCL_3 drop: Revision: 201029 Kit: 201035

/*
* Copyright (c) 2003 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:  Implementation of class CHttpUAProfFilter.
*
*
*/


// INCLUDE FILES

#include "uaproffilter.h"

#include <e32std.h>
#include <etelmm.h>
#include <mmtsy_names.h>

#include <http/rhttptransaction.h>
#include <http/rhttpconnectioninfo.h>
#include <http/rhttpheaders.h>
#include <httperr.h>

#include <browseruisdkcrkeys.h>
#include <webutilsinternalcrkeys.h>//for the profile strings

#include <cuseragent.h>
#include <featmgr.h>
#include <bldvariant.hrh>


// Token for IMEI/SN code in UA-header.
_LIT8( KImeiSnToken, "SN" );
_LIT8( KSlash, "/" );

// Space character.
LOCAL_D const TUint8 KSpaceChar = ' ';

// MACROS

/// OOM test helper.
//#define __TEST_OOM_IN_INSTALL
/// OOM test helper.
//#define __TEST_OOM_IN_MHFRUNL

// CONSTANTS

/// Initial buffer size for reading from Shared Data.
LOCAL_D const TInt KSdReadBufSize = 128;
/// profile
_LIT8( KProfile, "profile" );
/// x-wap-profile
_LIT8( KXWapProfile, "x-wap-profile" );
#if defined( _DEBUG )
/// Panic name for UAProf Filter.
_LIT( KUAProfFilter, "UAProfFilter" );
#endif
/// Length for imei code.
LOCAL_D const TInt KImeiLength = 15;


#if defined( __WINS__ )
/// Dummy IMEI value for WINS emulator.
_LIT( KEmulatorImei, "123456789012345" );
#endif

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

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::InstallFilterL()
// -----------------------------------------------------------------------------
//
CHttpUAProfFilterInterface* CHttpUAProfFilter::InstallFilterL( TAny* aSession )
    {
#ifdef __TEST_OOM_IN_INSTALL
    __UHEAP_SETFAIL( RHeap::ETrueRandom, 40 );
#endif /* def __TEST_OOM_IN_INSTALL */

    __ASSERT_DEBUG( aSession, User::Panic( KUAProfFilter, KErrArgument ) );
    RHTTPSession* session = REINTERPRET_CAST( RHTTPSession*, aSession );
    CHttpUAProfFilter* filter = new (ELeave) CHttpUAProfFilter( *session );
    CleanupStack::PushL( filter );
    filter->ConstructL();
    CleanupStack::Pop( filter );
    return filter;

#ifdef __TEST_OOM_IN_INSTALL
    __UHEAP_SETFAIL( RHeap::ENone, 1 );
#endif /* def __TEST_OOM_IN_INSTALL */
    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::~CHttpUAProfFilter()
// -----------------------------------------------------------------------------
//
CHttpUAProfFilter::~CHttpUAProfFilter()
    {
    // If we've been destroyed from the cleanup stack during creation
    // of the object, it might still be loaded. So check. (Normally the
    // delete is initiated by the 'delete this' in MHFUnload)
    if( iLoadCount > 0)
        {
        // As we're already in a destructor, MHFUnload must not delete us
        // again. So -1 value is set.
        iLoadCount = -1;
        iSession.FilterCollection().RemoveFilter( StringF( HTTP::EUserAgent ) );
        }
    // Make sure we don't close an unopened RStringF
    if( iUaProf3GOpen )
        {
        iUaProf3G.Close();
        }
    if( iUaProfOpen )
        {
        iUaProf.Close();
        }
    if( iUaNameOpen )
        {
        iUaName.Close();
        }
    if ( iUaNameWithImeiOpen )
        {
        iUaNameWithImei.Close();
        }
    if( iUaNameMmsOpen )
        {
        iUaNameMms.Close();
        }
    if ( iUaNameMmsWithImeiOpen )
        {
        iUaNameMmsWithImei.Close();
        }
    iMobilePhone.Close();
    iTelServer.Close();

    if ( iNotifyHandler )
    	{
    	iNotifyHandler->StopListening();	
    	}
    delete iRepositoryBrowser;
    delete iRepositoryCommonEngine;
    delete iNotifyHandler;
    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::MHFRunL()
// -----------------------------------------------------------------------------
//
void CHttpUAProfFilter::MHFRunL
( RHTTPTransaction aTransaction, const THTTPEvent& aEvent )
    {
#ifdef __TEST_OOM_IN_MHFRUNL
    __UHEAP_SETFAIL( RHeap::ETrueRandom, 30 );
#endif /* def __TEST_OOM_IN_MHFRUNL */

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

        default:
            {
            break;
            }
        }

#ifdef __TEST_OOM_IN_MHFRUNL
    __UHEAP_SETFAIL( RHeap::ENone, 1 );
#endif /* def __TEST_OOM_IN_MHFRUNL */
    }


// -----------------------------------------------------------------------------
// CHttpUAProfFilter::MHFRunError()
// -----------------------------------------------------------------------------
//
TInt CHttpUAProfFilter::MHFRunError
( TInt /*aError*/, RHTTPTransaction aTransaction, const THTTPEvent& /*aEvent*/ )
    {
    aTransaction.Fail();
    return KErrNone;
    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::MHFSessionRunL()
// -----------------------------------------------------------------------------
//
void CHttpUAProfFilter::MHFSessionRunL( const THTTPSessionEvent& /*aEvent*/ )
    {
    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::MHFSessionRunError()
// -----------------------------------------------------------------------------
//
TInt CHttpUAProfFilter::MHFSessionRunError
( TInt aError, const THTTPSessionEvent& /*aEvent*/ )
    {
    return aError;
    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::MHFUnload()
// -----------------------------------------------------------------------------
//
void CHttpUAProfFilter::MHFUnload( RHTTPSession /*aSession*/, 
                                   THTTPFilterHandle /*aHandle*/ )
    {
    // We must be only registered on one session, as we register in our
    // ConstructL and no-one else has a pointer to us. Therefore, we
    // know we can be deleted at this point.
    if( --iLoadCount == 0 )
        {
        delete this;
        }
    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::MHFLoad()
// -----------------------------------------------------------------------------
//
void CHttpUAProfFilter::MHFLoad
( RHTTPSession /*aSession*/, THTTPFilterHandle /*aHandle*/ )
    {
    ++iLoadCount;
    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::HandleNotifyInt()
// -----------------------------------------------------------------------------
//
void CHttpUAProfFilter::HandleNotifyInt(TUint32 aId, TInt aValue)
    {
    if ( aId == KBrowserIMEINotification )
        {
        TInt imeiSendingOn = aValue;
        iImeiSendingOn = (imeiSendingOn != 0);
        }
    #ifdef _DEBUG
        else
            {
            // This notification was not requested.
            User::Panic( KUAProfFilter, KErrGeneral );
            }
    #endif
    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::CHttpUAProfFilter()
// -----------------------------------------------------------------------------
//
CHttpUAProfFilter::CHttpUAProfFilter( RHTTPSession aSession )
: iUaProfOpen( EFalse ),
  iUaProf3GOpen( EFalse ),
  iUaNameOpen( EFalse ),
  iUaNameWithImeiOpen( EFalse ),
  iUaNameMmsOpen( EFalse ),
  iUaNameMmsWithImeiOpen( EFalse ),
  iSession( aSession ),
  iStringPool( aSession.StringPool() ),
  iStringTable( RHTTPSession::GetTable() ),
  iImeiFeatureEnabled( EFalse ),
  iImeiSendingOn( EFalse )
    { 
    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::ConstructL()
// -----------------------------------------------------------------------------
//
void CHttpUAProfFilter::ConstructL()
    {

    //iRepositoryCommonEngine object will be created in OpenProfileString()
    //iRepositoryBrowser object will be SetupImeiNotifyL()

    // This filter is opened only on one session, so it is safe to pre-open
    // the strings we are going to use.

    // UAProf string.
    OpenProfileStringsL();  // Uses iSdCli.

    // If 2G/3G distinction is supported, prepare querying the network type.
    // If no such distinction is made, we don't need RMobilePhone at all.
    if ( iUaProf3GOpen )
        {
        User::LeaveIfError( iTelServer.Connect() );
        User::LeaveIfError( iTelServer.LoadPhoneModule( KMmTsyModuleName ) );
        TInt numPhones;
        User::LeaveIfError( iTelServer.EnumeratePhones( numPhones ) );
        if( numPhones <= 0 )
            {
            // Huh???
            User::Leave( KErrCorrupt );
            }
        RTelServer::TPhoneInfo phoneInfo;
        User::LeaveIfError( iTelServer.GetPhoneInfo( 0, phoneInfo ) );
        User::LeaveIfError( iMobilePhone.Open( iTelServer, phoneInfo.iName ) );
        User::LeaveIfError( iMobilePhone.Initialise() );
        }

    // Setup IMEI-notify stuff, ignore any errors.
    // Expected error: Browser's INI file may not be present.
    TRAP_IGNORE( SetupImeiNotifyL() );

    // User Agent name string.
    OpenUserAgentNameStringsL();

    // Register filter.
    iSession.FilterCollection().AddFilterL
        (
        *this,
        THTTPEvent( THTTPEvent::ESubmit ),  // submit event
        MHTTPFilter::ETidyUp,               // priority
        StringF( HTTP::EUserAgent )         // name
        );
    }


// -----------------------------------------------------------------------------
// CHttpUAProfFilter::SetupImeiNotifyL()
// -----------------------------------------------------------------------------
//
void CHttpUAProfFilter::SetupImeiNotifyL()
    {
    // First check if this feature is enabled.
    FeatureManager::InitializeLibL();   // Warning: no leaving...
    iImeiFeatureEnabled = 
        FeatureManager::FeatureSupported( KFeatureIdBrowserIMEINotification );
    FeatureManager::UnInitializeLib();  // ... until this point!

    // If feature is enabled, query value and register for notification.
    if ( iImeiFeatureEnabled )
        {
        TInt imeiSendingOn( 0 );

        CRepository* iRepositoryBrowser = CRepository::NewL( KCRUidBrowser );
        iRepositoryBrowser->Get(  KBrowserIMEINotification, imeiSendingOn );
        iImeiSendingOn = (imeiSendingOn != 0);
        iNotifyHandler = CCenRepNotifyHandler::NewL(*this, 
                                                 *iRepositoryBrowser, 
                                                 CCenRepNotifyHandler::EIntKey, 
                                                 KBrowserIMEINotification);
        iNotifyHandler->StartListeningL();
        }

    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::SubmitL()
// -----------------------------------------------------------------------------
//
void CHttpUAProfFilter::SubmitL( RHTTPTransaction aTransaction )
    {
    RHTTPHeaders headers( aTransaction.Request().GetHeaderCollection() );
    
    // User-Agent: protocol independent header.

    // First check if client is MMS.
    TBool uaMms( EFalse );
    RStringF uaClient = iStringPool.OpenFStringL( KUserAgentClientId );
    CleanupClosePushL<RStringF>( uaClient );
    THTTPHdrVal val;
    if ( aTransaction.PropertySet().Property( uaClient, val ) &&
         val.Type() == THTTPHdrVal::KTIntVal &&
         val.Int() == KUserAgentCliMMS )
        {
        uaMms = ETrue;
        }
    CleanupStack::PopAndDestroy();  // Close uaClient.

    // If IMEI-notify is on (implies that feature is supported), use that.
    RStringF hdrVal;
    if ( uaMms )
        {
        hdrVal = iImeiSendingOn ? iUaNameMmsWithImei : iUaNameMms;
        }
    else
        {
        hdrVal = iImeiSendingOn ? iUaNameWithImei : iUaName;
        }
    AddOrReplaceHeaderL( headers, StringF( HTTP::EUserAgent ), hdrVal );

    // UAProf: protocol dependent.
    RStringF xProfile;
    THTTPHdrVal protocol;
    TBool protocolExists = iSession.ConnectionInfo().Property
        ( StringF( HTTP::EProtocol ), protocol );
    if( protocolExists &&
        protocol.Type() == THTTPHdrVal::KStrFVal &&
        protocol.StrF() == StringF( HTTP::EWSP ) )
        {
        // It's WSP - set "Profile".
        xProfile = iStringPool.OpenFStringL( KProfile );
        }
    else
        {
        // It's HTTP - set "x-wap-profile".
        xProfile = iStringPool.OpenFStringL( KXWapProfile );
        }
    CleanupClosePushL<RStringF>( xProfile );
    // Use 3G profile if:
    // - we have such (i.e. feature is supported); and
    // - network is 3G.
    hdrVal = (iUaProf3GOpen && Is3gNetworkL()) ? iUaProf3G : iUaProf;
    AddOrReplaceHeaderL( headers, xProfile, hdrVal );
    CleanupStack::PopAndDestroy();  // close xProfile
    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::AddOrReplaceHeaderL()
// -----------------------------------------------------------------------------
//
void CHttpUAProfFilter::AddOrReplaceHeaderL
( RHTTPHeaders& aHeaders, const RStringF& aName, const RStringF& aValue )
    {
    // Checking whether the header field is already set or not would be waste:
    // that is executed inside RemoveField anyway.
    (void)aHeaders.RemoveField( aName );
    aHeaders.SetFieldL( aName, THTTPHdrVal( aValue ) );
    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::OpenUserAgentNameStringsL()
// -----------------------------------------------------------------------------
//
void CHttpUAProfFilter::OpenUserAgentNameStringsL()
    {
    CUserAgent* usrAgnt = CUserAgent::NewL();
    CleanupStack::PushL( usrAgnt );//C.S: 1

    HBufC8* bufUA = usrAgnt->UserAgentL();
    CleanupStack::PushL( bufUA );//C.S 2
    if ( !bufUA || !bufUA->Length() )
        {
        // Reject empty value.
        //User::Leave( KErrCorrupt );
        User::Panic(_L("Improper UA string"), KErrCorrupt);
        }
    iUaName = iStringPool.OpenFStringL( *bufUA );
    iUaNameOpen = ETrue;
    
    //Read the MMS useragent string now
    HBufC8* bufUAMMS = usrAgnt->MMSUserAgentL();
    CleanupStack::PushL( bufUAMMS );//C.S 2
    if ( !bufUAMMS || !bufUAMMS->Length() )
        {
        // Reject empty value.
        //User::Leave( KErrCorrupt );
        User::Panic(_L("Improper UA string"), KErrCorrupt);
        }
    iUaNameMms = iStringPool.OpenFStringL( *bufUAMMS );
    iUaNameMmsOpen = ETrue;
    
    if ( iImeiFeatureEnabled )
        {
        // User Agent name string with IMEI code.
        TBuf<KImeiLength> imei16;
        GetImeiL( imei16 );
        HBufC8* imei8 = ConvertL( imei16 );
        CleanupStack::PushL( imei8 );//C.S 2

        // Add IMEI string to the UA-header.
        TPtr8 ptrUA = bufUA->Des();
        AddImeiToUserAgentL(ptrUA, *imei8);
        iUaNameWithImei =  iStringPool.OpenFStringL( *bufUA );
        iUaNameWithImeiOpen = ETrue;

        // Add IMEI string to the UA-MMS-header.
        TPtr8 ptrUAMMS = bufUAMMS->Des();
        AddImeiToUserAgentL(ptrUAMMS, *imei8);
        iUaNameMmsWithImei =  iStringPool.OpenFStringL( *bufUAMMS );
        iUaNameMmsWithImeiOpen = ETrue;
        
        CleanupStack::PopAndDestroy( imei8 );//C.S 2
        }
        
        
	CleanupStack::PopAndDestroy( bufUAMMS );
	CleanupStack::PopAndDestroy( bufUA );
    CleanupStack::PopAndDestroy( usrAgnt );//C.S 1 => OK
    }

void CHttpUAProfFilter::AddImeiToUserAgentL(TPtr8 &aUAStringPtr, const TDesC8& aImei)
    {

    TInt firstSpaceIndex = aUAStringPtr.Locate( TChar( KSpaceChar ) );

    if ( firstSpaceIndex != KErrNotFound )
        {
        // Insert IMEI before first space, separated by '/'
        aUAStringPtr.Insert( firstSpaceIndex++, KSlash);
        aUAStringPtr.Insert( firstSpaceIndex, KImeiSnToken);
        firstSpaceIndex += KImeiSnToken.iTypeLength;
        aUAStringPtr.Insert( firstSpaceIndex, aImei );
        }
    else  
        {
        // If there is no spaces, add IMEI to end of ENameMMS, separated by '/'
        aUAStringPtr.Append( KSlash );
        aUAStringPtr.Append( KImeiSnToken );
        aUAStringPtr.Append( aImei );
        }

    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::OpenProfileStringsL()
// -----------------------------------------------------------------------------
//
void CHttpUAProfFilter::OpenProfileStringsL()
    {
    // UAProf string.
	TRAPD(err1,iRepositoryCommonEngine = CRepository::NewL( KCRUidWebUtils ));
	
	if(KErrNone != err1)
		User::Panic( _L("CHttpUAProfFilter"), err1 );

    HBufC* buf16;

    User::LeaveIfError( ReadSdString( KWebUtilsUaProf , buf16 ) );

    CleanupStack::PushL( buf16 );
    if ( !buf16 || !buf16->Length() )
        {
        // Reject empty value.
        User::Leave( KErrCorrupt );
        }
    HBufC8* buf8 = ConvertL( *buf16 );
    CleanupStack::PushL( buf8 );    
    iUaProf = iStringPool.OpenFStringL( *buf8 );
    iUaProfOpen = ETrue;
    CleanupStack::PopAndDestroy( 2, buf16 );    // buf8, buf16

    // UAProf3G string. If missing/empty, not opened - this indicates that
    // the 2G/3G distinction feature is not supported.
    TInt err = ReadSdString( KWebUtilsUaProf3G, buf16 );

    CleanupStack::PushL( buf16 );
    if ( err && err != KErrNotFound )
        {
        // Error other than KErrNotFound, leave. KErrNotFound is OK, indicates
        // that this key is missing -> feature is not supported.
        User::Leave( err );
        }
    if ( buf16 && buf16->Length() )
        {
        buf8 = ConvertL( *buf16 );
        CleanupStack::PushL( buf8 );
        iUaProf3G = iStringPool.OpenFStringL( *buf8 );
        iUaProf3GOpen = ETrue;  // Also indicates that feature is supported.
        CleanupStack::PopAndDestroy( buf8 );
        }

    CleanupStack::PopAndDestroy( buf16 );
    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::ConvertL()
// -----------------------------------------------------------------------------
//
HBufC8* CHttpUAProfFilter::ConvertL( const TDesC16& aSrc )
    {
    HBufC8* buf = HBufC8::NewL( aSrc.Length() );
    buf->Des().Copy( aSrc );
    return buf;
    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::ReadSdString()
// -----------------------------------------------------------------------------
//
TInt CHttpUAProfFilter::ReadSdString( const TUint32 aKey, HBufC*& aBuf )
    {
    // We don't know the size of the string we are going to read (and Shared
    // data can't tell it). A trial-and-error method is used: we start with
    // a fixed size buffer and retry with increasing buffer size until success.
    TInt err( KErrTooBig );
    TInt round = 0;
    HBufC* buf = NULL;

    while( err == KErrTooBig )
        {
        // Increase buffer size until returned data can fit.
        delete buf;
        ++round;
        buf = HBufC::New( KSdReadBufSize * round );
        if( buf )
            {
            TPtr bufP( buf->Des() );
                err = iRepositoryCommonEngine->Get( aKey, bufP );
            }
        else
            {
            err = KErrNoMemory;
            }
        }

    if( err )
        {
        delete buf;
        buf = NULL; // Make lint happy.
        }

    aBuf = buf;
    return err;
    }

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::GetImeiL()
// -----------------------------------------------------------------------------
//
void CHttpUAProfFilter::GetImeiL( TDes& aBuf ) const
    {
    aBuf.Zero();  

    // Read IMEI on HW, use dummy under WINS
#if !defined( __WINS__ )
	TUint32 identityCaps;
	User::LeaveIfError(iMobilePhone.GetIdentityCaps(identityCaps));
	TBuf<RMobilePhone::KPhoneSerialNumberSize> serNumber;

if (identityCaps & RMobilePhone::KCapsGetSerialNumber)
    {
    TRequestStatus status;
    RMobilePhone::TMobilePhoneIdentityV1 mobilePhoneIdentity;
    iMobilePhone.GetPhoneId(status, mobilePhoneIdentity);
    User::WaitForRequest(status);
    User::LeaveIfError(status.Int());
    serNumber = mobilePhoneIdentity.iSerialNumber;
	aBuf.Copy(serNumber);
    }
#else
    aBuf.Copy( KEmulatorImei);
#endif
}

// -----------------------------------------------------------------------------
// CHttpUAProfFilter::Is3gNetworkL()
// -----------------------------------------------------------------------------
//
TBool CHttpUAProfFilter::Is3gNetworkL() const
    {
    // This method must not be called if 2G/3G distinction is not supported;
    // the RMobilePhone-related classes are not open in that case.
    __ASSERT_DEBUG( iUaProf3GOpen, \
        User::Panic( KUAProfFilter, KErrNotReady ) );
    TBool is3g( EFalse );
#if !defined( __WINS__ )
    RMobilePhone::TMobilePhoneNetworkMode networkMode;
    User::LeaveIfError( iMobilePhone.GetCurrentMode( networkMode ) );
    if( networkMode == RMobilePhone::ENetworkModeCdma2000 ||
        networkMode == RMobilePhone::ENetworkModeWcdma )
        {
        is3g = ETrue;
        }
#endif
    return is3g;
    }