/*
* Copyright (c) 2005 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "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 HttpCacheUtil
*
*/

// INCLUDE FILES
#include "HttpCacheUtil.h"
#include <http/rhttpheaders.h>
#include <http/RHTTPTransaction.h>
#include <http.h>
#include <stdlib.h>
#include <string.h>
#include <stringpool.h>
#include <flogger.h>
#include "TInternetdate.h"
#include "HttpFilterCommonStringsExt.h"
#include "HttpCacheEntry.h"

// EXTERNAL DATA STRUCTURES

// EXTERNAL FUNCTION PROTOTYPES

// CONSTANTS
_LIT8( KHttpNewLine, "\r\n" );
// if you change it here, change in HeadersToBufferL too
_LIT8( KHttpFieldSeparator, ": " );
_LIT8( KHttpValueSep, "," );
const TInt KMaxHeaderStrLen = 1024;
_LIT8( KHttpContentType, "Content-Type" );

#ifdef __CACHELOG__
_LIT( KDateString,"%D%M%Y%/0%1%/1%2%/2%3%/3 %-B%:0%J%:1%T%:2%S%.%*C4%:3%+B");
_LIT( KHttpCacheGeneralFileName, "cachehandler.txt" );
_LIT( KHttpCacheHashFileName, "hash.txt" );
const TInt KCurrentLogLevel = 0;
#endif // __CACHELOG__

// MACROS

// LOCAL CONSTANTS AND MACROS

// MODULE DATA STRUCTURES

// LOCAL FUNCTION PROTOTYPES

// FORWARD DECLARATIONS

// ============================= LOCAL FUNCTIONS ===============================

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

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

// -----------------------------------------------------------------------------
// HttpCacheUtil::SetEntryOnTransL
//
// -----------------------------------------------------------------------------
//
void HttpCacheUtil::SetEntryOnTransL(
    CHttpCacheEntry& aEntry,
    RHTTPTransaction& aTrans,
    RStringF& aCacheEntryStr )
    {
    RHTTPTransactionPropertySet propSet = aTrans.PropertySet();

    // set transaction property with the event handler callback functions' pointer
    THTTPHdrVal tokenVal = (TInt)(CHttpCacheEntry*)&aEntry;
    aTrans.PropertySet().RemoveProperty( aCacheEntryStr );
    aTrans.PropertySet().SetPropertyL( aCacheEntryStr, tokenVal );
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::EntryOnTransL
//
// -----------------------------------------------------------------------------
//
CHttpCacheEntry* HttpCacheUtil::EntryOnTransL(
    RHTTPTransaction& aTrans,
    RStringF& aCacheEntryStr )
    {
    //
    CHttpCacheEntry* entry = NULL;
    //
    THTTPHdrVal entryPtr;
    RHTTPTransactionPropertySet propSet = aTrans.PropertySet();
    // this is a transaction, already forwarded to download manager
    if( propSet.Property( aCacheEntryStr, entryPtr ) )
        {
        entry = REINTERPRET_CAST( CHttpCacheEntry*, entryPtr.Int() );
        }
    return entry;
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::HeadersToBufferLC
//
// -----------------------------------------------------------------------------
//
HBufC8* HttpCacheUtil::HeadersToBufferLC(
    RHTTPHeaders& aHttpHeaders,
    RStringPool& aStrP )
    {
    // Get an iterator for the collection of response headers
    HBufC8* headerString = HBufC8::NewL( KMaxHeaderStrLen );
    TPtr8 headerPtr( headerString->Des() );
    TInt newLength( 0 );
    THTTPHdrFieldIter it = aHttpHeaders.Fields();

    while( it.AtEnd() == EFalse )
        {
        TPtrC8 rawData;
        // Get name of next header field
        RStringTokenF fieldName = it();
        RStringF fieldNameStr = aStrP.StringF( fieldName );
        aHttpHeaders.GetRawField( fieldNameStr, rawData );

        newLength+=( fieldNameStr.DesC().Length() + KHttpFieldSeparator().Length() + rawData.Length() +
            KHttpNewLine().Length() );

        if( headerPtr.MaxLength() < newLength )
            {
            // realloc
            HBufC8* temp = HBufC8::New( newLength + KMaxHeaderStrLen );
            if( !temp )
                {
                delete headerString;
                User::LeaveNoMemory();
                }
            temp->Des().Copy( headerPtr );
            delete headerString;
            headerString = temp;
            headerPtr.Set( headerString->Des() );
            }
        // append
        headerPtr.Append( fieldNameStr.DesC() );
        headerPtr.Append( KHttpFieldSeparator );
        headerPtr.Append( rawData );
        headerPtr.Append( KHttpNewLine );
        ++it;
        }
    CleanupStack::PushL( headerString );
    return headerString;
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::BufferToHeadersL
//
// -----------------------------------------------------------------------------
//
void HttpCacheUtil::BufferToHeadersL(
    const TDesC8& aHeaderStr,
    RHTTPHeaders& aHttpHeaders,
    RStringPool& aStrP )
    {
    // take header str line by line
    TInt sepPos( 0 );
    TInt lineEnd( 0 );
    TPtrC8 headerStr( aHeaderStr );

    while( lineEnd != KErrNotFound )
        {
        lineEnd = headerStr.Find( KHttpNewLine );
        sepPos = headerStr.Find( KHttpFieldSeparator );

        if( lineEnd != KErrNotFound && sepPos != KErrNotFound &&
            lineEnd > sepPos )
            {
            // ETag: "9be043c1-175-793-41419a44"
            TPtrC8 line( headerStr.Left( lineEnd ) );
            // ETag
            TPtrC8 key( line.Left( sepPos ) );
            // "9be043c1-175-793-41419a44"
            TInt sepEnd( sepPos + KHttpFieldSeparator().Length() );
            TPtrC8 value( line.Mid( sepEnd ) );
            // add it to the http header
            RStringF nameStr = aStrP.OpenFStringL( key );
            CleanupClosePushL( nameStr );
            // remove if exists
            THTTPHdrVal headerValue;
            if( aHttpHeaders.GetField( nameStr, 0, headerValue ) == KErrNone )
                {
                //
                aHttpHeaders.RemoveField( nameStr );
                }
            TRAPD( err, aHttpHeaders.SetRawFieldL( nameStr, value, KHttpValueSep ) );
            // leave OOM only
            if( err == KErrNoMemory )
                {
                User::Leave( err );
                }
            CleanupStack::PopAndDestroy(); // namestr

            // move string to the end of the line
            headerStr.Set( headerStr.Mid( lineEnd + KHttpNewLine().Length() ) );
            }
        }
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::BodyToBufferL
//
// -----------------------------------------------------------------------------
//
HBufC8* HttpCacheUtil::BodyToBufferL(
    MHTTPDataSupplier& aBodySupplier )
    {
    // get it from the transaction
    TPtrC8 ptr;
    aBodySupplier.GetNextDataPart( ptr );
    //
    return ptr.AllocL();
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::MergeHeadersLC
//
// -----------------------------------------------------------------------------
//
HBufC8* HttpCacheUtil::MergeHeadersLC(
    const TDesC8& aCachedHeaderStr,
    RHTTPHeaders& aResponseHeaders,
    RStringPool& aStrP )
    {
    // FYI: it uses the response header to merge
    // take header str line by line
    TInt sepPos( 0 );
    TInt lineEnd( 0 );
    TPtrC8 headerStr( aCachedHeaderStr );

    while( lineEnd != KErrNotFound )
        {
        lineEnd = headerStr.Find( KHttpNewLine );
        sepPos = headerStr.Find( KHttpFieldSeparator );

        if( lineEnd != KErrNotFound && sepPos != KErrNotFound &&
            lineEnd > sepPos )
            {
            // ETag: "9be043c1-175-793-41419a44"
            TPtrC8 line( headerStr.Left( lineEnd ) );
            // ETag
            TPtrC8 key( line.Left( sepPos ) );
            // "9be043c1-175-793-41419a44"
            TInt sepEnd( sepPos + KHttpFieldSeparator().Length() );
            TPtrC8 value( line.Mid( sepEnd ) );
            // add it to the http header
            RStringF nameStr = aStrP.OpenFStringL( key );
            CleanupClosePushL( nameStr );
            // avoid corrupted content type from the stack in case of 304
            if(key == KHttpContentType)
                {
                aResponseHeaders.RemoveField(nameStr);
                }
            THTTPHdrVal tempVal;
            // add this field unless it already exists
            if( aResponseHeaders.GetField( nameStr, 0, tempVal ) == KErrNotFound )
                {
                TRAPD( err, aResponseHeaders.SetRawFieldL( nameStr, value, KHttpValueSep ) );
                // leave OOM only
                if( err == KErrNoMemory )
                    {
                    User::Leave( err );
                    }
                }
            CleanupStack::PopAndDestroy(); // namestr
            // move string to the end of the line
            headerStr.Set( headerStr.Mid( lineEnd + KHttpNewLine().Length() ) );
            }
        }
    // we've got the merged headers, let's covert it to buffer
    return HeadersToBufferLC( aResponseHeaders, aStrP );
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::AddHeaderLC
//
// -----------------------------------------------------------------------------
//
HBufC8* HttpCacheUtil::AddHeaderLC(
    const TDesC8& aName,
    const TDesC8& aValue,
    const TDesC8& aHeaders )
    {
    HBufC8* newHeaders = NULL;
    // first check if the header exists so
    // then all we need to do is to update the value
    TInt pos( aHeaders.FindF( aName ) );
    //
    newHeaders = HBufC8::NewLC( aHeaders.Length() + aName.Length() + KHttpFieldSeparator().Length() +
        aValue.Length() + KHttpNewLine().Length() );
    //
    if( pos != KErrNotFound )
        {
        // replace old header with this new one
        TPtr8 newHeadersPtr( newHeaders->Des() );
        // copy headers
        newHeadersPtr.Copy( aHeaders );
        // check what we need to replace
        TPtrC8 leftover( aHeaders.Mid( pos ) );
        // headers are terminated by \r\n
        TInt endPos( leftover.Find( KHttpNewLine ) );
        if( endPos != KErrNotFound )
            {
            // value pos
            TInt valuePos( pos + aName.Length() + KHttpFieldSeparator().Length() );
            newHeadersPtr.Replace( valuePos, pos + endPos - valuePos, aValue );
            }
        }
    else
        {
        // add new header
        // new header = old header\r\n aName: aValue\r\n
        TPtr8 newHeadersPtr( newHeaders->Des() );
        // old headers
        newHeadersPtr.Copy( aHeaders );
        // Cache Control
        newHeadersPtr.Append( aName );
        // :
        newHeadersPtr.Append( KHttpFieldSeparator );
        // max age=180
        newHeadersPtr.Append( aValue );
        // \r\n
        newHeadersPtr.Append( KHttpNewLine );
        }
    return newHeaders;
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::HeaderFieldFromBufferLC
//
// -----------------------------------------------------------------------------
//
HBufC8* HttpCacheUtil::HeaderFieldFromBufferLC(
    const TDesC8& aHeaders,
    const TDesC8& aKey )
    {
    HBufC8* value = NULL;
    //
    TInt pos( aHeaders.Find( aKey ) );
    if( pos != KErrNotFound )
        {
        // line: Cache-Control: max-age=180
        TPtrC8 linePtr = aHeaders.Mid( pos );
        // line end \r\n
        TInt lineEnd( linePtr.Find( KHttpNewLine ) );
        // separator :
        TInt sepPos( linePtr.Find( KHttpFieldSeparator ) );
        //
        if( sepPos != KErrNotFound )
            {
            // fix line end
            lineEnd = lineEnd == KErrNotFound ? linePtr.Length(): lineEnd;
            // max-age=180
            TInt valueLen( lineEnd - ( sepPos + KHttpFieldSeparator().Length() ) );
            value = HBufC8::NewLC( valueLen );
            value->Des().Copy( linePtr.Mid( sepPos + KHttpFieldSeparator().Length(), valueLen ) );
            }
        }
    return value;
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::MethodFromStr
//
// -----------------------------------------------------------------------------
//

TCacheLoadMethod HttpCacheUtil::MethodFromStr(
    RStringF aMethodStr,
    RStringPool aStrP )
    {
    if( aMethodStr == aStrP.StringF( HTTP::EGET, RHTTPSession::GetTable() ) )
        {
        return EMethodGet;
        }
    if( aMethodStr == aStrP.StringF(HTTP::ECONNECT, RHTTPSession::GetTable() ) )
        {
        return EMethodConnect;
        }
    if( aMethodStr == aStrP.StringF(HTTP::EDELETE, RHTTPSession::GetTable() ) )
        {
        return EMethodDelete;
        }
    if( aMethodStr == aStrP.StringF(HTTP::EHEAD, RHTTPSession::GetTable() ) )
        {
        return EMethodHead;
        }
    if( aMethodStr == aStrP.StringF(HTTP::EOPTIONS, RHTTPSession::GetTable() ) )
        {
        return EMethodOptions;
        }
    if( aMethodStr == aStrP.StringF(HTTP::EPUT, RHTTPSession::GetTable() ) )
        {
        return EMethodPut;
        }
    if( aMethodStr == aStrP.StringF(HTTP::EPOST, RHTTPSession::GetTable() ) )
        {
        return EMethodPost;
        }
    if( aMethodStr == aStrP.StringF(HTTP::ETRACE, RHTTPSession::GetTable() ) )
        {
        return EMethodTrace;
        }
    return EMethodGet;
    }


// -----------------------------------------------------------------------------
// HttpCacheUtil::ProtectedEntry
//
// Returns true if the entry is protected
//
// -----------------------------------------------------------------------------
//
TBool HttpCacheUtil::ProtectedEntry(
    RHTTPHeaders& aHttpHeaders,
    RStringPool& aStrP )
    {
    TBool protectedEntry( EFalse );
    //
    THTTPHdrVal contType;
    RStringF fieldName = aStrP.StringF( HTTP::EContentType, RHTTPSession::GetTable() );
    //
    if( aHttpHeaders.GetField( fieldName, 0, contType ) == KErrNone )
        {
        if( contType.Type() == THTTPHdrVal::KStrFVal )
            {
            RStringF fieldValStr = aStrP.StringF( contType.StrF() );
            // css and javascrip are protected entries
            protectedEntry = fieldValStr.DesC().Find( _L8("text/css") ) != KErrNotFound ||
                fieldValStr.DesC().Find( _L8("javascript") ) != KErrNotFound;
            }
        }
    return protectedEntry;
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::CacheTimeIsFresh
//
// Returns true if the entry is fresh.
//
// -----------------------------------------------------------------------------
//
TBool HttpCacheUtil::CacheTimeIsFresh(
    const RHTTPHeaders& aRequestHeaders,
    const RHTTPHeaders& aCachedHeaders,
    RStringPool aStrP )
    {
    TInt status( KErrNone );
    TBool isFresh( EFalse );
    TInt64 freshness;
    TInt64 age;
    TInt64 maxAge;
    TInt64 minFresh;
    TInt64 maxStale;
    TTime cachedRequestTime;
    TTime cachedResponseTime;

    TTime date;
    TInt err;
    THTTPHdrVal dateValue;
    RStringF fieldName;

    // Get the date from the headers
    fieldName = aStrP.StringF( HTTP::EDate, RHTTPSession::GetTable() );
    err = aCachedHeaders.GetField( fieldName, 0, dateValue );
    if( err == KErrNotFound || dateValue.Type() != THTTPHdrVal::KDateVal )
        {
        date = 0;
        }
    else
        {
        date = TTime( dateValue.DateTime() );
        }
    cachedRequestTime = date;
    cachedResponseTime = date;

    // Get Max Age header. If maxAge = 0, then must revalidate.
    status = GetCacheControls( aCachedHeaders, &maxAge, NULL, NULL, NULL, NULL, NULL, aStrP );
    if( status == KErrNone &&  maxAge == 0 )
        {
        return EFalse; //Must revalidate
        }

    // Get the freshness and age of the cachedHeaders
    freshness = Freshness( aCachedHeaders, cachedResponseTime, aStrP );
    age = Age( aCachedHeaders, cachedRequestTime, cachedResponseTime, aStrP );
#ifdef __CACHELOG__
        TBuf<50> dateString;
        TTime fr( freshness );

        TRAP(err, fr.FormatL( dateString, KDateString ) );
        if ( err == KErrNone ) {
            HttpCacheUtil::WriteLog( 0, _L( "freshness" ) );
            HttpCacheUtil::WriteLog( 0, dateString );
            }

        TTime ca( age );
        TRAP( err, ca.FormatL( dateString, KDateString ) );
        if ( err == KErrNone ) {
            HttpCacheUtil::WriteLog( 0, _L( "age" ) );
            HttpCacheUtil::WriteLog( 0, dateString );
            }
#endif // __CACHELOG__

    // Get useful cache-control directives from the requestHeaders
    status = GetCacheControls( aRequestHeaders, &maxAge, &minFresh, &maxStale, NULL, NULL, NULL, aStrP );
    if( status == KErrNone )
        {
        // Above values for maxAge, minFresh and maxStale are in seconds
        // Convert to micro seconds before using it.

        // If maxStale is present it means that it is willing to
        // accept a response maxStale seconds after it expires.  To
        // allow this freshness is extended by maxStale seconds.
        if( maxStale != -1 )
            {
            if( maxStale == MAKE_TINT64( KMaxTInt, KMaxTInt ) )
                {
                freshness = MAKE_TINT64( KMaxTInt, KMaxTInt );
                }
            else
                {
                freshness += maxStale * 1000 * 1000;
                }
            }
        // If minFresh is present reject it if it would expire
        // within minFresh seconds.
        if( minFresh != -1 )
            {
            if( ( age + minFresh * 1000 * 1000 ) > freshness )
                {
                return EFalse;
                }
            }
        // If the age > request's maxAge, reject it
        if( maxAge != -1 )
            {
            if( age > maxAge * 1000 * 1000 )
                {
                return EFalse;
                }
            }
        // If age is less than freshness its fresh
    // If age == 0 and freshness == 0 - revalidation is needed
        if( freshness && age <= freshness )
    {
    isFresh = ETrue;
    }
    }
    return isFresh;
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::AddValidationHeaders
//
// Adds appropriate headers to make the given "request" conditional.
//
// -----------------------------------------------------------------------------
//
void HttpCacheUtil::AddValidationHeaders(
    const RHTTPHeaders aCachedHeaders,
    RHTTPHeaders& aRequestHeaders,
    RStringPool& aStrP )
    {
    THTTPHdrVal lastMod;
    THTTPHdrVal eTag;
    RStringF fieldName;
    TInt err;

    // Get the entry's last-modified header
    fieldName = aStrP.StringF( HTTP::ELastModified, RHTTPSession::GetTable() );
    err = aCachedHeaders.GetField( fieldName, 0, lastMod );

    // If last-Modified is present add an If-Modified-Since header
    if( err != KErrNotFound )
        {
        fieldName = aStrP.StringF( HTTP::EIfModifiedSince, RHTTPSession::GetTable() );
        TRAP_IGNORE( aRequestHeaders.SetFieldL( fieldName, lastMod ) );
        }

    // Get the entry's ETag header
    fieldName = aStrP.StringF( HTTP::EETag, RHTTPSession::GetTable() );
    err = aCachedHeaders.GetField( fieldName, 0, eTag );

    // If etag is present add an etag header
    if( err != KErrNotFound )
        {
        fieldName = aStrP.StringF( HTTP::EIfNoneMatch, RHTTPSession::GetTable() );
        TRAP_IGNORE( aRequestHeaders.SetFieldL( fieldName, eTag ) );
        }
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::OperatorCacheContent
// This function checks if aUrl is part of the operator domain. for examp: if aUrl->http://www.opdomain.com/pub/index.html
// and operator domain is http://www.opdomain.com/pub then it returns ETrue.
// -----------------------------------------------------------------------------
//
TBool HttpCacheUtil::OperatorCacheContent(
    const TDesC8& aOpDomain,
    const TDesC8& aUrl )
    {
    TUriParser8 opDomain;
    TUriParser8 url;
    TBool found( EFalse );

    // check if both URIs are correct.
    if( opDomain.Parse( aOpDomain ) == KErrNone && url.Parse( aUrl ) == KErrNone )
        {
        // host value must be the same. for examp: www.operator.co is different from www.operator.com
        // note: check if compare is case sensitive!
        // note2: if http:// is missing from operator domain then TUriParser8 takes the host part as path.
        if( opDomain.Compare( url, EUriHost ) == 0 )
            {
            // further checking on the path value. domain path must be a substring of the given url.
            // for examp: op path: /news
            // url path: /news/local_news.html
            const TDesC8& opPath = opDomain.Extract( EUriPath );
            if( opPath.Length() )
                {
                const TDesC8& urlPath = url.Extract( EUriPath );
                // we don't take the content if the url path is not defined (while domain path is) or the
                // domain path is not a substring of url path. actually url path must start with domain path
                // op path: /news url path: /media/news/local_news.html is not valid.
                found = ( urlPath.Length() && urlPath.FindF( opPath ) == 0 );
                }
            else
                {
                // no opPath means that we take every content on this host.
                found = ETrue;
                }
            }
        }
    return found;
    }


// -----------------------------------------------------------------------------
// HttpCacheUtil::VSSCacheContent
//
// -----------------------------------------------------------------------------
//
TBool HttpCacheUtil::VSSCacheContent( const TDesC8& aUrl, const HBufC8* aWhiteList )
    {
    TBool found( EFalse );

    if ( aWhiteList )
        {
        TUint8* bufPtr = (TUint8* ) aWhiteList->Ptr();
        TUint8* startBufPtr = bufPtr;
        TUint len = aWhiteList->Length();
        TInt i, startUri, uriLen;
        TPtrC8 uri ( aUrl );
        for ( i=0; i < len; i++ )
            {
            startUri = i;

            for ( ;( ( *bufPtr != ';' ) && ( i<len ) ) ; i++, bufPtr++ ) ;

            uriLen = i - startUri;

            if ( i == len )
                {
                //For getting total length
                uriLen += 2;
                }

            TPtrC8 uriDomain( startBufPtr, uriLen);
            if ( OperatorCacheContent( uriDomain, uri ) )
                {
                found = ETrue;
                break;
                }

            startBufPtr = ++bufPtr;
            i++;
            } //end for()
        } //end if ( aWhiteList )

    return found;
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::PragmaNoCache
//
// -----------------------------------------------------------------------------
//
TBool HttpCacheUtil::PragmaNoCache(
    RHTTPTransaction& aTrans )
    {
    //
    RHTTPHeaders requestHeaders = aTrans.Request().GetHeaderCollection();
    RStringPool strP = aTrans.Session().StringPool();
    RStringF fieldName = strP.StringF( HTTP::ENoCache, RHTTPSession::GetTable() );

    // check no-cache
    THTTPHdrVal headerVal;
    TInt noCacheField( requestHeaders.GetField( fieldName, 0, headerVal ) );
    // check no-store
    fieldName = strP.StringF( HTTP::ENoStore, RHTTPSession::GetTable() );
    TInt noStoreField( requestHeaders.GetField( fieldName, 0, headerVal ) );
    //
    return( noCacheField == KErrNone || noStoreField == KErrNone );
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::GetHeaderFileName
//
// -----------------------------------------------------------------------------
//
void HttpCacheUtil::GetHeaderFileName(
    const TDesC& aBodyFileName,
    TDes& aHeaderFileName )
    {
    // body file name = foo
    // header file name = foo.h
    aHeaderFileName.Copy( aBodyFileName );
    // take the filename and append the new extension
    aHeaderFileName.Append( KHttpCacheHeaderExt() );
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::AdjustExpirationTime
//
// -----------------------------------------------------------------------------
//
void HttpCacheUtil::AdjustExpirationTimeL(
    RHTTPHeaders& aResponseHeaders,
    RStringPool& aStrP )
    {
    const TStringTable& stringTable = RHTTPSession::GetTable();
    THTTPHdrVal hdrVal;

    if( aResponseHeaders.GetField( aStrP.StringF( HTTP::EExpires, stringTable ), 0, hdrVal ) == KErrNone )
        {
        TTime expDate( hdrVal.DateTime() );

#ifdef __CACHELOG__
        HttpCacheUtil::WriteLog( 0, _L( "adjust expiration time from" ) );

        TBuf<50> dateString;
        TTime expTime( hdrVal.DateTime() );

        expTime.FormatL( dateString, KDateString );
        HttpCacheUtil::WriteLog( 0, dateString );
#endif // __CACHELOG__

        // double it
        TTimeIntervalMinutes minutes;
        TTimeIntervalHours hours;
        TTime now;
        now.UniversalTime();

        if( expDate.MinutesFrom( now, minutes ) == KErrNone )
            {

#ifdef __CACHELOG__
        now.FormatL( dateString, KDateString );

        HttpCacheUtil::WriteLog( 0, _L( "current time" ) );
        HttpCacheUtil::WriteLog( 0, dateString );
        //
        now.FormatL( dateString, KDateString );
        HttpCacheUtil::WriteLog( 0, _L( "expires in (minutes)" ), minutes.Int() );
#endif // __CACHELOG__

            expDate+=minutes;
            }
        // minutes owerflow? take hours instead
        else if( expDate.HoursFrom( now, hours ) == KErrNone )
            {
            expDate+=hours;
            }
        else
            {
            // last resort
            TTimeIntervalDays days( expDate.DaysFrom( now ) );
            expDate+=days;
            }
        // set new date on the response header
        aResponseHeaders.RemoveField( aStrP.StringF( HTTP::EExpires, stringTable ) );
        // add it
        hdrVal.SetDateTime( expDate.DateTime() );
#ifdef __CACHELOG__
        HttpCacheUtil::WriteLog( 0, _L( "to" ) );
        //
        TTime newExptime( hdrVal.DateTime() );
        newExptime.FormatL( dateString, KDateString );
        HttpCacheUtil::WriteLog( 0, dateString );

        TTimeIntervalMinutes min;

        newExptime.MinutesFrom( now, min );

        HttpCacheUtil::WriteLog( 0, _L( "now it expires in (minutes)" ), min.Int() );
#endif // __CACHELOG__
        aResponseHeaders.SetFieldL( aStrP.StringF( HTTP::EExpires, stringTable ), hdrVal );
        }
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::CacheNeedsUpdateL
//
// -----------------------------------------------------------------------------
//
TBool HttpCacheUtil::CacheNeedsUpdateL(
    RHTTPHeaders& aResponseHeaders,
    const TDesC8& aCachedHeadersStr,
    RStringPool& aStrP )
    {
    HBufC8* valueStr;
    TTime cachedDate( 0 );
    TTime responseDate( 0 );
    TTime cachedLastModified( 0 );
    TTime responseLastModified( 0 );
    RStringF cachedETag;
    RStringF responseETag;
    RStringF fieldName;
    TInternetDate value;
    THTTPHdrVal hdrValue;
    TBool eTag( EFalse );
    TBool eLastModified( EFalse );
    TBool eDate( EFalse );
    TInt status;
    TBool update( ETrue );

     // date
    fieldName = aStrP.StringF( HTTP::EDate, RHTTPSession::GetTable() );
    // new
    status = aResponseHeaders.GetField( fieldName, 0, hdrValue );
    if( status == KErrNone )
        {
        responseDate = TTime( hdrValue.DateTime() );
        // orig
        valueStr = HttpCacheUtil::HeaderFieldFromBufferLC( aCachedHeadersStr, fieldName.DesC() );
        if( valueStr )
            {
            eDate = ETrue;
            value.SetDateL( valueStr->Des() );
            cachedDate = TTime( value.DateTime() );
            CleanupStack::PopAndDestroy(); // valueStr
            }
        }

    // last modified
    fieldName = aStrP.StringF( HTTP::ELastModified, RHTTPSession::GetTable() );
    // new
    status = aResponseHeaders.GetField( fieldName, 0, hdrValue );
    if( status == KErrNone )
        {
        responseLastModified = TTime( hdrValue.DateTime() );
        // orig
        valueStr = HttpCacheUtil::HeaderFieldFromBufferLC( aCachedHeadersStr, fieldName.DesC() );
        if( valueStr )
            {
            eLastModified = ETrue;
            value.SetDateL( valueStr->Des() );
            cachedLastModified  = TTime( value.DateTime() );
            CleanupStack::PopAndDestroy(); // valueStr
            }
        }

    // Etag
    fieldName = aStrP.StringF( HTTP::EETag, RHTTPSession::GetTable() );
    // new
    status = aResponseHeaders.GetField( fieldName, 0, hdrValue );
    if( status == KErrNone )
        {
        responseETag = hdrValue.StrF();
        // orig
        valueStr = HttpCacheUtil::HeaderFieldFromBufferLC( aCachedHeadersStr, fieldName.DesC() );
        if( valueStr )
            {
            TRAP( status, cachedETag = aStrP.OpenFStringL( valueStr->Des() ) );
            CleanupStack::PopAndDestroy(); // valueStr
            eTag = ( status == KErrNone );
            //
            if( eTag )
                {
                CleanupClosePushL( cachedETag );
                }
            }
        }

    // If the entry's date is greater than the responses's date,
    // ignore the response.
    if( eDate && cachedDate > responseDate )
        {
        update  = EFalse;
        }
    // If the entry's ETag does not equal the responses's ETag,
    // replace the cached entry with the response.
    else if( eTag && cachedETag != responseETag )
        {
        update  = ETrue;
        }
    // If the entry's lastMod is greater than the responses's lastMod,
    // ignore the response.
    else if( eLastModified && cachedLastModified > responseLastModified )
        {
        update  = EFalse;
        }
    // If the entry's lastMod is equal to the responses's lastMod,
    // update the cached headers with the new response headers.
    else if( eLastModified && cachedLastModified == responseLastModified )
        {
        update  = ETrue;
        }
    // If the entry's lastMod is less than the responses's lastMod,
    // replace the cached entry with the response.
    else if( eLastModified && cachedLastModified < responseLastModified )
        {
        update  = ETrue;
        }
    else
        {
        // If we get here the headers need to be replaced
        update  = ETrue;
        }
    //
    if( eTag )
        {
        CleanupStack::PopAndDestroy(); // cachedETag
        }
    return update;
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::IsCacheable
//
// -----------------------------------------------------------------------------
//
TBool HttpCacheUtil::IsCacheable(
    RHTTPTransaction& aTrans,
    TUint aMaxSize,
    TBool& aProtectedEntry )
    {
    TBool isCacheable( ETrue );
    TBool hasExplicitCache( EFalse );
    TInt64 maxAge;
    TBool noCache;
    TBool noStore;
    TTime now;
    TTime expires;
    RStringF fieldName;
    THTTPHdrVal expiresVal;
    TInt err;
    RStringPool strP = aTrans.Session().StringPool();
    RHTTPHeaders respHeaders = aTrans.Response().GetHeaderCollection();

    // not protected by default
    aProtectedEntry = EFalse;
    // 1. do not cache sesnitive content (DRM)
    // 2. do not cache content bigger than the cache
    // 3. check if the content is protected
    // 4. check normal cache directives

    // init the field name
    THTTPHdrVal contType;
    fieldName = strP.StringF( HTTP::EContentType, RHTTPSession::GetTable() );

    // check if this is a noncacheable content type
    if( respHeaders.GetField( fieldName, 0, contType ) == KErrNone &&
        contType.StrF() == strP.StringF( HttpFilterCommonStringsExt::EApplicationVndOmaDrm,
        HttpFilterCommonStringsExt::GetTable() ) )
        {
#ifdef __CACHELOG__
        HttpCacheUtil::WriteLog( 0, _L( "HttpCacheUtil::IsCacheable - sensitive content. do not cache" ) );
#endif
        // drm == nocache
        isCacheable = EFalse;
        }
    else
        {
        // check if the content is bigger than the cache
        THTTPHdrVal contLen;
        fieldName = strP.StringF( HTTP::EContentLength, RHTTPSession::GetTable() );

        if( respHeaders.GetField( fieldName, 0, contLen ) != KErrNotFound &&
            ( contLen.Type() == THTTPHdrVal::KTIntVal && contLen.Int() > aMaxSize ) )
            {
#ifdef __CACHELOG__
            HttpCacheUtil::WriteLog( 0, _L( "oversized content. do not cache" ) );
#endif
            // oversized content
            return EFalse;
            }
        // check if this is a proteced entry
        aProtectedEntry = ProtectedEntry( respHeaders, strP );
        // check various cache controls
        if( GetCacheControls( respHeaders, &maxAge, NULL, NULL, NULL,
            &noCache, &noStore, strP ) == KErrNone )
            {
            // There are several header "conditions" that make a resource
            // non-cachable.  Reject if they are present.
            //
            // If no-cache or no-store directives exist -> don't cache.
            if( noCache || noStore )
                {
#ifdef __CACHELOG__
                HttpCacheUtil::WriteLog( 0, _L( "HttpCacheUtil::IsCacheable - no cache/no store header. do not cache" ) );
#endif
                // no protection on this entry
                aProtectedEntry = EFalse;
                return EFalse;
                }
            // Get the current time
            now.UniversalTime();

            // Get the expires from the respHeaders
            fieldName = strP.StringF( HTTP::EExpires, RHTTPSession::GetTable() );
            err = respHeaders.GetField( fieldName, 0, expiresVal );
            if( err == KErrNone )
                {
                expires = TTime( expiresVal.DateTime() );
                }
            else
                {
                expires = now;
                }

            // if past-expire date do not cache. According to RFC2616, section 13.2.4/14.9.3,
            // if maxage is present, then ignore expires
            if (!maxAge && now > expires)
                {
                return EFalse;
                }

            if( err == KErrNone || maxAge > 0 )
                {
                hasExplicitCache = ETrue;
                }
            // Reject if the http status code doesn't equal 200, 304, 301, 410.
            // Note: We accept status codes, 307, 302 if some cache
            //       control directives exist.
            switch( aTrans.Response().StatusCode() )
                {
                case HTTPStatus::EOk:
                case HTTPStatus::ENotModified:
                case HTTPStatus::EMovedPermanently:
                case HTTPStatus::EGone:
                    {
                    break;
                    }
                case HTTPStatus::EFound:
                case HTTPStatus::ETemporaryRedirect:
                    {
                    if( !hasExplicitCache )
                        {
                        isCacheable = EFalse;
                        }
                    break;
                    }
                default:
                    {
                    isCacheable = EFalse;
                    break;
                    }
                }

            // Check method specific conditions.
            switch( HttpCacheUtil::MethodFromStr( aTrans.Request().Method(), strP ) )
                {
                case EMethodGet:
                    {
                    // Reject if the url contains a "query" part unless there
                    // are caching directives.
                    TBool isQuery;
                    isQuery = aTrans.Request().URI().IsPresent( EUriQuery );

                    if( isQuery && !hasExplicitCache )
                        {
                        isCacheable = EFalse;
                        }
                    break;
                    }
                case EMethodPost:
                    {
                    // Reject unless there are caching directives
                    if( !hasExplicitCache )
                        {
                        isCacheable = EFalse;
                        }
                    break;
                    }
                case EMethodHead:
                    {
                    // Don't cache Head responses...we don't need to, and the
                    // cache implementation can't currently handle it anyway.
                    // So just fall through to the default case.
                    // Reject all other methods
                    }
                default:
                    {
                    isCacheable = EFalse;
                    break;
                    }
                }
            }
        }
    return isCacheable;
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::WriteLog
//
// -----------------------------------------------------------------------------
//
void HttpCacheUtil::WriteLog(
    TInt aLogLevel,
    TPtrC aBuf,
    TInt aAny )
    {
#ifdef __CACHELOG__
    TBool log( aLogLevel <= KCurrentLogLevel );
    TPtrC fileName( KHttpCacheGeneralFileName );

    if ( aLogLevel == 1 )
        {
        // write logging to hash.txt
        fileName.Set( KHttpCacheHashFileName );
        log = ETrue;
        }

    if ( log )
        {
        if ( aAny != 0xffff )
            {
            RFileLogger::WriteFormat(_L("Browser"), fileName, EFileLoggingModeAppend,
                    _L("%S %d"), &aBuf, aAny );
            }
        else
            {
            RFileLogger::WriteFormat(_L("Browser"), fileName, EFileLoggingModeAppend,
                    _L("%S"), &aBuf );
            }
        }
#else // __CACHELOG__
    (void)aLogLevel;
    (void)aBuf;
    (void)aAny;
#endif // __CACHELOG__
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::WriteFormatLog
//
// -----------------------------------------------------------------------------
//
void HttpCacheUtil::WriteFormatLog(
    TInt aLogLevel,
    TRefByValue<const TDesC> aBuf, ... )
    {
#ifdef __CACHELOG__
    TBool log( aLogLevel <= KCurrentLogLevel );
    TPtrC fileName( KHttpCacheGeneralFileName );

    if ( aLogLevel == 1 )
        {
        // write logging to hash.txt
        fileName.Set( KHttpCacheHashFileName );
        log = ETrue;
        }

    if ( log )
        {
        VA_LIST args;
        VA_START(args, aBuf);
        TBuf16<1024> string;
        TDes16IgnoreOverflow overflow;
        string.AppendFormatList(aBuf, args, &overflow);
        RFileLogger::WriteFormat(_L("Browser"), fileName, EFileLoggingModeAppend, string);
        VA_END(args);
        }
#else // __CACHELOG__
    (void)aLogLevel;
    (void)aBuf;
#endif // __CACHELOG__
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::WriteLogFilenameAndUrl
//
// -----------------------------------------------------------------------------
//
void HttpCacheUtil::WriteLogFilenameAndUrl(
    TInt aLogLevel,
    TPtrC aMethodName,
    const TPtrC aFilename,
    const TDesC8& aUrl,
    TInt aAny,
    TLogItemType aItemType )
    {
#ifdef __CACHELOG__
    // Create a buffer for method name, filename, and url string
    _LIT(KColonSpace, " : ");
    _LIT(KSpace, " ");
    TInt itemTypeStringLen( 30 );
    TInt tmpLen( aMethodName.Length() + aFilename.Length() +
                 3*KColonSpace().Length() + aUrl.Length() + itemTypeStringLen );
    HBufC* tmp = HBufC::New( tmpLen );

    if ( tmp ) {
        TPtr tmpPtr( tmp->Des() );
        tmpPtr.Copy( aMethodName );
        tmpPtr.Append( KColonSpace );

        TChar backSlash('\\');
        TInt filenamePos( aFilename.LocateReverse( TChar('\\') ) );
        if ( filenamePos > 0 ) {
            tmpPtr.Append( aFilename.Right( aFilename.Length() - filenamePos - 1 ) );
            tmpPtr.Append( KColonSpace );
        }

        // Convert url to TPtr
        HBufC* tmpUrl = HBufC::New( aUrl.Length() );
        if ( tmpUrl ) {
            TPtr tmpUrlPtr( tmpUrl->Des() );
            tmpUrlPtr.Copy( aUrl );
            TInt urlPos( tmpUrlPtr.LocateReverse( TChar('/') ) );
            if ( urlPos > 0 && (aUrl.Length()-2 > urlPos) ) {
                tmpPtr.Append( tmpUrlPtr.Right( aUrl.Length() - urlPos - 1 ) );
                tmpPtr.Append( KColonSpace );
            }
            else {
                tmpPtr.Append( tmpUrlPtr );
                tmpPtr.Append( KColonSpace );
            }
        }

        // Append the bucketIndex, lookup table pos, etc...
        switch ( aItemType )
        {
            case ELogItemTypeNone:
                break;

            case ELogBucketIndex:
                tmpPtr.Append( _L("bucketIndex =") );
                break;

            case ELogEntrySize:
                tmpPtr.Append( _L("entrySize =") );
                break;

            case ELogLookupTablePos:
                tmpPtr.Append( _L("lookupTable pos =") );
                break;

            case ELogFileErrorCode:
                tmpPtr.Append( _L("file errorCode =") );
                break;

            default:
                break;
        }

        HttpCacheUtil::WriteLog( aLogLevel, tmpPtr, aAny );

        delete tmp;
        delete tmpUrl;
        }
#else // __CACHELOG__
    (void)aLogLevel;
    (void)aMethodName;
    (void)aFilename;
    (void)aUrl;
    (void)aAny;
    (void)aItemType;
#endif
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::WriteUrlToLog
//
// Get the freshness of the "entry".
//
// -----------------------------------------------------------------------------
//
void HttpCacheUtil::WriteUrlToLog(
    TInt aLogLevel,
    const TDesC8& aUrl,
    TInt aAny )
    {
#ifdef __CACHELOG__
    HBufC* tmp = HBufC::New( aUrl.Length() );
    if( tmp )
        {
        TPtr tmpPtr( tmp->Des() );
        tmpPtr.Copy( aUrl );
        HttpCacheUtil::WriteLog( aLogLevel, tmpPtr, aAny );
        delete tmp;
        }
#else // __CACHELOG__
    (void)aLogLevel;
    (void)aUrl;
    (void)aAny;
#endif // __CACHELOG__
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::WriteUrlToLog
//
// Get the freshness of the "entry".
//
// -----------------------------------------------------------------------------
//
void HttpCacheUtil::WriteUrlToLog(
    TInt aLogLevel,
    const TDesC& aTxt,
    const TDesC8& aUrl,
    TInt aAny )
    {
#ifdef __CACHELOG__
    TBool log( aLogLevel <= KCurrentLogLevel );
    TPtrC fileName( KHttpCacheGeneralFileName );

    if( aLogLevel == 1 )
        {
        // hash
        fileName.Set( KHttpCacheHashFileName );
        log = ETrue;
        }
    if( log )
        {    //
        HBufC* tmp = HBufC::New( aUrl.Length() );
        if( tmp )
            {
            TPtr tmpPtr( tmp->Des() );
            tmpPtr.Copy( aUrl );
            if( aAny != 0xffff )
                {
                RFileLogger::WriteFormat( _L("Browser"), fileName, EFileLoggingModeAppend,
                        _L("%S %S %d"), &aTxt, &tmpPtr, aAny );
                }
            else
                {
                RFileLogger::WriteFormat(_L("Browser"), fileName, EFileLoggingModeAppend,
                        _L("%S %S"), &aTxt, &tmpPtr );
                }
            delete tmp;
            }
        }
#else // __CACHELOG__
    (void)aLogLevel;
    (void)aTxt;
    (void)aUrl;
    (void)aAny;
#endif // __CACHELOG__
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::GenerateNameLC
// Given a URL, generates fully qualified Symbian path for storing HTTP response.
// The format is <cache base dir> + <subdirectory> + <file name>.
// Caller must free the returned HBufC* when done.
// -----------------------------------------------------------------------------
HBufC* HttpCacheUtil::GenerateNameLC( const TDesC8& aUrl, const TDesC& aBaseDir )
    {
    TUint32 crc (0);

    // Use the entire URL for CRC calculation: maximizes source
    // entropy/avoids collisions. Returns 8 char hex filename, use
    // KMaxFilenameLength to internalize/externalize.
    Mem::Crc32(crc, aUrl.Ptr(), aUrl.Size());

    // extract least significant 4 bits (nibble) for subdirectory
    TUint32 nibble( crc & (KCacheSubdirCount-1) );

    // filename has full path, drive:\directory\sub-directory\8-char filename
    // e.g. c:\system\cache\E\078AFEFE, where E is calculated by nibble()
    // and 078AFEFE is calculated by crc32()
    HBufC* fileName = HBufC::NewLC( KMaxPath );
    // Note in KFormat, the %08x : a 32-bit value can represented as 0xFFFFFFFF
    _LIT(KFormat,"%S%x%c%08x");
    fileName->Des().Format(KFormat, &aBaseDir, nibble, KPathDelimiter, crc);

    return fileName;
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::Freshness
//
// Get the freshness of the "entry".
//
// -----------------------------------------------------------------------------
//
TInt64 HttpCacheUtil::Freshness(
    const RHTTPHeaders& aHeaders,
    TTime aResponseTime,
    RStringPool aStrP )
    {
    TInt status( KErrNone );
    TInt64 maxAge;
    TTime expires;
    TTime lastModified( 0 );
    TTime date;
    RStringF fieldName;
    THTTPHdrVal hdrValue;
    TInt err;
    TInt64 freshness( 0 );

    // Get the date from the headers
    fieldName = aStrP.StringF( HTTP::EDate, RHTTPSession::GetTable() );
    err = aHeaders.GetField( fieldName, 0, hdrValue );
    if ( err == KErrNotFound || hdrValue.Type() != THTTPHdrVal::KDateVal )
        {
        date = aResponseTime;
        }
    else
        {
        date = TTime( hdrValue.DateTime() );
        }

    // Get useful cache-control directives
    status = GetCacheControls( aHeaders, &maxAge, NULL, NULL, NULL, NULL, NULL, aStrP );
    if ( status == KErrNone )
        {
        // max-age is in delta-seconds. Convert it to micro seconds.
        // All our calculations are in micro-seconds
        // If maxAge is present, use it
        if ( maxAge != -1 )
            {
            freshness = maxAge * 1000 * 1000;

            return freshness;
            }

        // Get the expires from the headers
        fieldName = aStrP.StringF( HTTP::EExpires, RHTTPSession::GetTable() );
        err = aHeaders.GetField( fieldName, 0, hdrValue );
        if ( err == KErrNotFound || hdrValue.Type() != THTTPHdrVal::KDateVal )
            {
            expires = 0;
            }
        else
            {
            expires = TTime( hdrValue.DateTime() );
            }

        // Otherwise, if the expires is present use it
        if ( err != KErrNotFound )
            {
            freshness = expires.Int64() - date.Int64();
            return freshness;
            }

        // Get the last modified from the headers
        fieldName = aStrP.StringF( HTTP::ELastModified, RHTTPSession::GetTable() );
        err = aHeaders.GetField( fieldName, 0, hdrValue );
        if( err == KErrNotFound || hdrValue.Type() != THTTPHdrVal::KDateVal )
            {
            hdrValue = 0;
            }
        else
            {
            lastModified = TTime( hdrValue.DateTime() );
            }

        // Otherwise, if last-modified is present use it
        if ( err != KErrNotFound )
            {
            if( aResponseTime > lastModified )
                {
                freshness = aResponseTime.Int64() - lastModified.Int64();
                freshness += ((TInt64)( freshness * 0.10 ) );
                }
            return freshness;
            }
        // If it get here then we have no way to determine if it is
        // fresh.  So we set freshness to zero, which will most likely
        // cause the resource to be validated.
        freshness = 0;
        }
    return freshness;
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::Age
//
// Get the age of the "entry".
//
// -----------------------------------------------------------------------------
//
TInt64 HttpCacheUtil::Age(
    const RHTTPHeaders& aHeaders,
    TTime aRequestTime,
    TTime aResponseTime,
    RStringPool aStrP )
    {
    TTime now;
    TDateTime date;
    TTime time;

    TInt64 correctedRecvAge;
    TInt64 apparentAge;
    TInt64 responseDelay;
    TInt64 correctedInitialAge;
    TInt64 residentTime;
    TInt64 currentAge;
    TInt64 age;


    RStringF fieldName;
    THTTPHdrVal dateValue;
    TInt err;

    // Get the current time. All internet dates are GMT
    now.UniversalTime();
#ifdef __CACHELOG__
    TBuf<50> dateString;
    //
    TRAP( err, now.FormatL( dateString, KDateString ) );
    if ( err == KErrNone ) {
        HttpCacheUtil::WriteLog( 0, _L( "current time" ) );
        HttpCacheUtil::WriteLog( 0, dateString );
        }
#endif // __CACHELOG__

    // The aRequestTime is same as that of headers time.

    time = aRequestTime;

    // Do a sanity check
    if( aRequestTime > now )
        {
        aRequestTime = now;
        }
    if( aResponseTime > now )
        {
        aResponseTime = now;
        }
    if( aRequestTime > aResponseTime )
        {
        aRequestTime = aResponseTime;
        }
    if( time > aResponseTime )
        {
        time = aResponseTime;
        }

    // Get the age from the headers.  If age is missing it equals
    // 0, which is a safe value to use below.
    fieldName = aStrP.StringF( HTTP::EAge, RHTTPSession::GetTable() );
    err = aHeaders.GetField( fieldName, 0, dateValue );
    if( err == KErrNotFound )
        {
        age = 0;
        }
    else
        {
        // Age should be in delta seconds
        if( dateValue.Type() == THTTPHdrVal::KTIntVal )
            {
            age = dateValue;
            }
        else
            {
            age = 0;
            }
        }

    // Get the "current" age
    apparentAge = Max( TInt64( 0 ), aResponseTime.Int64() - time.Int64() );
    // The TTime::Int64() gives the time in micro seconds. And the age field is in
    // delta seconds.
    correctedRecvAge = Max( apparentAge, age * 1000 * 1000 );
    responseDelay = aResponseTime.Int64() - aRequestTime.Int64();
    correctedInitialAge = correctedRecvAge + responseDelay;
    residentTime = now.Int64() - aResponseTime.Int64();

    currentAge = correctedInitialAge + residentTime;

    //the current age in micro seconds.
    return currentAge;
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::GetCacheControls
//
// Description: Returns various cache controls.
//
// -----------------------------------------------------------------------------
//
TInt HttpCacheUtil::GetCacheControls(
    const RHTTPHeaders& aHeaders,
    TInt64* aMaxAge,
    TInt64* aMinFresh,
    TInt64* aMaxStale,
    TBool* aMustRevalidate,
    TBool* aNoCache,
    TBool* aNoStore,
    RStringPool aStrP )
    {
    TInt status( KErrNone );
    TInt i;
    TInt cacheCount( 0 );
    RStringF directive;
    TInt64 value;
    char* extraValues = NULL;
    RStringF fieldName;


    if( aMaxAge )
        {
        *aMaxAge = -1;
        }
    if( aMinFresh )
        {
        *aMinFresh = -1;
        }
    if( aMaxStale )
        {
        *aMaxStale = -1;
        }
    if( aMustRevalidate )
        {
        *aMustRevalidate = EFalse;
        }
    if( aNoCache )
        {
        *aNoCache = EFalse;
        }
    if( aNoStore )
        {
        *aNoStore = EFalse;
        }

    // init the field name
    fieldName = aStrP.StringF( HTTP::ECacheControl, RHTTPSession::GetTable() );
    TRAP( status, cacheCount = aHeaders.FieldPartsL( fieldName ) );
    if( status == KErrNone )
        {
        for( i = 0; i < cacheCount; i++ )
            {
            status = GetCacheControlDirective( aHeaders, i, directive, &value, &extraValues, aStrP );
            if( status == KErrNone )
                {
                if( directive == aStrP.StringF( HTTP::EMaxAge, RHTTPSession::GetTable() ) )
                    {
                    if( aMaxAge )
                        {
                        *aMaxAge = value;
                        }
                    }
                else if( directive == aStrP.StringF( HTTP::EMinFresh, RHTTPSession::GetTable() ) )
                    {
                    if( aMinFresh )
                        {
                        *aMinFresh = value;
                        }
                    }
                else if( directive == aStrP.StringF( HTTP::EMaxStale, RHTTPSession::GetTable() ) )
                    {
                    if( aMaxStale )
                        {
                        *aMaxStale = value;
                        }
                    }
                else if( directive == aStrP.StringF( HTTP::EMustRevalidate, RHTTPSession::GetTable() ) )
                    {
                    if( aMustRevalidate )
                        {
                        *aMustRevalidate = ETrue;
                        }
                    }
                else if( directive == aStrP.StringF( HTTP::ENoCache, RHTTPSession::GetTable() ) )
                    {
                    if( aNoCache )
                        {
                        *aNoCache = ETrue;
                        }
                    }
                else if( directive == aStrP.StringF( HTTP::ENoStore, RHTTPSession::GetTable() ) )
                    {
                    if( aNoStore )
                        {
                        *aNoStore = ETrue;
                        }
                    }
                directive.Close();
                // It ignores extraValues, just free it
                User::Free( extraValues );
                extraValues = NULL;
                }
            else
                {
                break;
                }
            }
        }
    return status;
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::GetCacheControlDirective
//
// -----------------------------------------------------------------------------
//
TInt HttpCacheUtil::GetCacheControlDirective(
    const RHTTPHeaders& aHeaders,
    TInt aIndex,
    RStringF& aDirective,
    TInt64* aDirectiveValue,
    char** aExtraValue,
    RStringPool aStrP )
    {
    RStringF fieldName;
    THTTPHdrVal hdrVal;
    TInt err;

    // Get the cache-control from the headers
    fieldName = aStrP.StringF( HTTP::ECacheControl, RHTTPSession::GetTable() );
    aHeaders.GetField( fieldName, aIndex, hdrVal );

    if( hdrVal.Type() == THTTPHdrVal::KStrVal || hdrVal.Type() == THTTPHdrVal::KStrFVal )
        {
        RStringF cacheDir = hdrVal.StrF();

        TInt endPos;
        _LIT8(KFind, "=");

        endPos = cacheDir.DesC().Find( KFind );
        if( endPos != -1 )
            {
            TRAP( err, aDirective = aStrP.OpenFStringL( cacheDir.DesC().Left( endPos ) ) );
            if( err != KErrNone )
                {
                return err;
                }
            TPtrC8 value = cacheDir.DesC().Right( cacheDir.DesC().Length() - endPos - 1 );
            err = ExtractCacheControlDirectiveValue( aStrP, aDirective, value, aDirectiveValue, aExtraValue );
            if( err != KErrNone )
                {
                aDirective.Close();
                return err;
                }
            }
        else
            {
            aDirective = cacheDir.Copy();
            // Directives which MUST have values
            if( ( aDirective == aStrP.StringF( HTTP::EMaxAge, RHTTPSession::GetTable() ) ) ||
                ( aDirective == aStrP.StringF( HTTP::EMinFresh, RHTTPSession::GetTable() ) ) ||
                ( aDirective == aStrP.StringF( HTTP::ESMaxAge, RHTTPSession::GetTable() ) ) )
                {
                aDirective.Close();
                return KErrGeneral;
                }
            }
        }
    return KErrNone;
    }

// -----------------------------------------------------------------------------
// HttpCacheUtil::ExtractCacheControlDirectiveValue
//
// -----------------------------------------------------------------------------
//
TInt HttpCacheUtil::ExtractCacheControlDirectiveValue(
    RStringPool aStrP,
    RStringF& aDirective,
    TDesC8& aValue,
    TInt64* aDirectiveValue,
    char** aExtraValue )
    {
    TInt status( KErrNone );
    TInt temp;
    char* errorIfNull;

    *aDirectiveValue = -1;
    *aExtraValue = NULL;
    char* valuestr = (char*)User::Alloc( aValue.Length() + 1 );
    if( !valuestr )
        {
        return KErrNoMemory;
        }
    memcpy( valuestr, aValue.Ptr(), aValue.Length() );
    valuestr[ aValue.Length() ] = '\0';


    if( ( aDirective == aStrP.StringF( HTTP::EMaxAge, RHTTPSession::GetTable() ) ) ||
        ( aDirective == aStrP.StringF( HTTP::EMinFresh, RHTTPSession::GetTable() ) ) ||
        ( aDirective == aStrP.StringF( HTTP::ESMaxAge, RHTTPSession::GetTable() ) ) )
        {
        // Cases with directiveValues
        temp = strtoul( valuestr, &errorIfNull, 10 );
        if( !errorIfNull )
            {
            status = KErrGeneral;
            }
        else
            {
            *aDirectiveValue = temp;
            }
        User::Free( valuestr );
        return status;
        }

    if( ( aDirective == aStrP.StringF( HTTP::EMaxStale, RHTTPSession::GetTable() ) ) )
        {
        // Cases with optional directiveValues
        temp = strtoul( valuestr, &errorIfNull, 10 );
        if( errorIfNull )
            {
            *aDirectiveValue = temp;
            }
        User::Free( valuestr );
        return status;
        }

    if( ( aDirective == aStrP.StringF( HTTP::ENoCache, RHTTPSession::GetTable() ) ) ||
        ( aDirective == aStrP.StringF( HTTP::EPrivate, RHTTPSession::GetTable() ) ) )
        {
        *aExtraValue = valuestr;
        return status;
        }
    User::Free( valuestr );
    return status;
    }


// -----------------------------------------------------------------------------
// FilePathHash
// Hash function for Symbian file paths: discards case first
// -----------------------------------------------------------------------------
static TUint32 FilepathHash(const TDesC& aDes)
{
    //since this function is intensively used by the HashMap,
    //keeping (slow) heap allocation out of it.
    TBuf<KMaxPath> normalized ( aDes );

    normalized.LowerCase();
    return DefaultHash::Des16( normalized );
}

// -----------------------------------------------------------------------------
// FilepathIdent
// Comparator for Symbian file paths: Use case-insensitive compare
// -----------------------------------------------------------------------------
static TBool FilepathIdent(const TDesC& aL, const TDesC& aR)
{
    return ( aL.CompareF(aR) == 0 );
}


void HttpCacheUtil::GenerateCacheContentHashMapL(CHttpCacheFileHash*& aHashMap, RFs& aRfs, const TDesC& aCacheFolder, const TInt aInitialValue)
    {
    // need to be able to initialise hash here, but use it sensibly in calling function hence rather strange object construction
    aHashMap = CHttpCacheFileHash::NewLC(aInitialValue);
    
    CDirScan* scanner = CDirScan::NewLC( aRfs );

    //Configure CDirScan to tell you all contents of a particular directory hierarchy
    scanner->SetScanDataL( aCacheFolder, KEntryAttNormal, ESortNone );
    CDir* matchingFiles( 0 );

    //Step 1. Find out all files on disk: by walking the directory hierarchy, one directory at a time
    for (;;)
        {
        //1a. Get list of files in current directory, NULL if no directory left in tree
        scanner->NextL( matchingFiles );
        if ( !matchingFiles )
            break;

        TPtrC dir( scanner->FullPath() );

        //1b. Add any files found to the HashTable
        const TInt nMatches = matchingFiles->Count();
        for ( TInt i = 0; i < nMatches; i++ )
            {
            TEntry entry ( (*matchingFiles)[i] ) ;
            aHashMap->InsertAndStoreL( entry, dir );
            }

        delete matchingFiles;
        } // End of step 1: adding all known files on disk to Map

    CleanupStack::PopAndDestroy( 1, scanner );
    CleanupStack::Pop( aHashMap );
    
#ifdef __CACHELOG__
    {
    HttpCacheUtil::WriteFormatLog(0, _L("-----------START PRINTING MAP OF SIZE %d---------"), aHashMap->HashMap().Count());
    THttpCacheFileHashIter iter(aHashMap->HashMap());
    const TDesC* key;
    while ((key = iter.NextKey()) != 0)
        {
        const TFileInfo* val = iter.CurrentValue();
        HttpCacheUtil::WriteFormatLog(0, _L("MAP WALK: %S, with size = %d, value = %d "), key, val->iFileSize, val->iUserInt);
        }
    HttpCacheUtil::WriteFormatLog(0, _L("-----------DONE PRINTING MAP-------------"));
    }
#endif
    }

void HttpCacheUtil::EnsureTrailingSlash( TDes& aPath )
    {
    // fix folder by appending trailing \\ to the end -symbian thing
    // unless it is already there
    if (aPath.LocateReverse('\\') != aPath.Length() - 1)
        {
        aPath.Append(_L("\\"));
        }
    
    }


CHttpCacheFileHash::CHttpCacheFileHash(const TInt aInitialValue, const THashFunction32<TDesC>& aHash, const TIdentityRelation<TDesC>& aIdentity) : iHashMap(aHash, aIdentity), iInitialValue(aInitialValue)
    {
    }

CHttpCacheFileHash* CHttpCacheFileHash::NewLC(const TInt iInitialValue)
    {
    CHttpCacheFileHash* obj = new (ELeave) CHttpCacheFileHash(iInitialValue, &FilepathHash, &FilepathIdent);
    CleanupStack::PushL(obj);
    obj->ConstructL();
    return obj;
    }

CHttpCacheFileHash::~CHttpCacheFileHash()
    {
    iStringStorage.ResetAndDestroy();
    delete iFileInfoStorage;
    iHashMap.Close();
    }

void CHttpCacheFileHash::ConstructL()
    {
    iFileInfoStorage = new (ELeave) CArrayFixSeg<TFileInfo>(64);    // 64 objects at a time
    }

void CHttpCacheFileHash::InsertAndStoreL(const TEntry& aEntry, const TDesC& aDir)
    {
    TFileInfo& info = iFileInfoStorage->ExtendL();
    info.iFileSize = aEntry.iSize;
    info.iUserInt = iInitialValue;
    HBufC* fullPath = HBufC::NewL( aDir.Length() + aEntry.iName.Length() );
    iStringStorage.AppendL( fullPath ); //keep object safe for later destruction
    TPtr path(fullPath->Des());
    path.Copy( aDir );
    path.Append( aEntry.iName );
    iHashMap.Insert( fullPath , &info );
    }

//  End of File
