/**********************************************************************/
/**                       Microsoft Windows NT                       **/
/**                Copyright(c) Microsoft Corp., 1994                **/
/**********************************************************************/

/*
    basereq.cxx

    This module contains the http base request class implementation


    FILE HISTORY:
        Johnl           24-Aug-1994   Created
        MuraliK         16-May-1995   Modified LogInformation structure
                                      after adding additional fields.
        MuraliK         13-Oct-1995   Created basereq file from old httreq.cxx
*/


#include "w3p.hxx"
#include <inetinfo.h>
#include "basereq.hxx"
#include <lonsi.hxx>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>

#include <iscaptrc.h>

#pragma warning( disable:4355 )   // 'this' used in base member initialization

//
//  Private constants.
//


//
//  Default response buffer size
//

#define DEF_RESP_BUFF_SIZE           4096

//
// minimum buffer size after custom headers
//

#define POST_CUSTOM_HEADERS_SIZE    256

//
//  We cache our read and write buffers but if they get beyond this size, we
//  free it and start from scratch on the next request
//

#define MAX_CLIENT_SIZE_ALLOWED      (4 * 4096)

//
//  Handle used to indicate request to deny access to current HTTP request
//

#define IIS_ACCESS_DENIED_HANDLE     ((HANDLE)1)

//
//  Public functions.
//

//
//  Private functions.
//

#define CONST_TO_STRING(x)  #x

HTTP_REQ_BASE::HTTP_REQ_BASE(
    CLIENT_CONN * pClientConn,
    PVOID         pvInitialBuff,
    DWORD         cbInitialBuff
    ) :
    _tcpauth             ( TCPAUTH_SERVER | TCPAUTH_UUENCODE ),
    _Filter              ( this ),
    _fValid              ( FALSE ),
    _bufServerResp       ( DEF_RESP_BUFF_SIZE ),
    _dwLogHttpResponse   ( HT_DONT_LOG ),
    _pURIInfo            ( NULL ),
    _pMetaData           ( NULL )
{
#if defined(CAL_ENABLED)
    m_pCalAuthCtxt = NULL;
    m_pCalSslCtxt = NULL;
#endif

    InitializeSession( pClientConn,
                       pvInitialBuff,
                       cbInitialBuff );
    DBG_ASSERT( pClientConn->CheckSignature() );

    if ( !_Filter.IsValid() ||
         !_bufServerResp.QueryPtr() )
    {
        return;
    }

    _acIpAccess = AC_NOT_CHECKED;
    _fNeedDnsCheck = FALSE;

    _fValid = TRUE;
}

VOID
HTTP_REQ_BASE::InitializeSession(
    CLIENT_CONN * pClientConn,
    PVOID         pvInitialBuff,
    DWORD         cbInitialBuff
    )
/*++

Routine Description:

    This is a pseudo constructor called by the buffer list code.

    This routine should initialize all of the items that should be reset
    between TCP sessions (but may remain valid over multiple requests on the
    same TCP session, i.e., "Connection: keep-alive")

Arguments:

    pClientConn - Client connection object we're speaking to

--*/
{
    _pClientConn        = pClientConn;
    _pW3Instance        = NULL;
    _pW3Stats           = g_pW3Stats;
    _fKeepConn          = FALSE;
    _fSecurePort        = pClientConn->IsSecurePort();

    ResetAuth( TRUE );
    _fSingleRequestAuth = FALSE;

    _cFilesSent         = 0;
    _cFilesReceived     = 0;
    _cbBytesSent        = 0;
    _cbBytesReceived    = 0;
    _cbTotalBytesSent   = 0;
    _cbTotalBytesReceived= 0;

    _fAsyncSendPosted   = FALSE;

    _Filter.Reset();
    _pAuthFilter = NULL;

    _strHostAddr.Reset();
    _dwRenegotiated    = 0;
    _dwSslNegoFlags = 0;

    IF_DEBUG(REQUEST) {
        DBGPRINTF(( DBG_CONTEXT,
                "[InitializeSession] time=%u HTTP=%08x\n",
                GetTickCount(), (LPVOID)this
             ));
    }
}

HTTP_REQ_BASE::~HTTP_REQ_BASE( VOID )
{
}


BOOL
HTTP_REQ_BASE::ResetAuth(
    BOOL    fSessionReset
    )
/*++

Routine Description:

    This method is called to reset authentication status.

Arguments:

    None

--*/
{
    TCP_REQUIRE( _tcpauth.Reset( fSessionReset ) );
    _fClearTextPass     = FALSE;
    _fAnonymous         = FALSE;
    _fMappedAcct        = FALSE;
    _fAuthenticating    = FALSE;
    _fLoggedOn          = FALSE;
    _fInvalidAccessToken = FALSE;
    _fAuthTypeDigest    = FALSE;
    _fAuthSystem        = FALSE;
    _fAuthCert          = FALSE;

    _cbLastAnonAcctDesc = 0;

    _strAuthType.Reset();
    _strUserName.Reset();
    _strPassword.Reset();
    _strUnmappedUserName.Reset();
    _strUnmappedPassword.Reset();

    _fSingleRequestAuth = FALSE;

#if defined(CAL_ENABLED)
    if ( m_pCalAuthCtxt )
    {
        CalDisconnect( m_pCalAuthCtxt );
        m_pCalAuthCtxt = NULL;
    }
#endif

    return TRUE;
}


BOOL
HTTP_REQ_BASE::Reset(
    BOOL fResetPipelineInfo
    )
/*++

Routine Description:

    This method is called after an individual request has been processed.
    If the session is being kept open, this object can be used again for
    the next request on this TCP session.  In this case, various items such
    as authentication information etc. remain valid.

    The method is also called once when the object is first allocated.

Arguments:

--*/
{
    _fStartTimeValid    = FALSE;

    _VersionMajor       = 0;
    _VersionMinor       = 0;
    _cbEntityBody       = 0;
    _cbTotalEntityBody  = 0;
    _cbContentLength    = 0;

    _cbClientRequest    = 0;

    if (fResetPipelineInfo)
    {
        _cbOldData      = 0;
    }
    else if (_cbOldData > 0)
    {
        _cbOldData     -= _cbBytesReceived;
    }

    _cbExtraData        = 0;
    _pchExtraData       = NULL;
    _liModifiedSince.QuadPart = 0;
    _liUnlessModifiedSince.QuadPart = 0;
    _liUnmodifiedSince.QuadPart = 0;
    _dwModifiedSinceLength = 0;

    _status         = NO_ERROR;
    _cbBytesWritten = 0;

    _HeaderList.Reset();

    _Filter.Reset();

    //
    //  Reset our statistics for this request
    //

    _cbTotalBytesSent     += _cbBytesSent;
    _cbTotalBytesReceived += _cbBytesReceived;
    _cbBytesSent          = 0;
    _cbBytesReceived      = 0;

    _fAsyncSendPosted     = FALSE;

    //
    //  Don't log this request unless we're explicity indicated a status
    //

    _dwLogHttpResponse    = HT_DONT_LOG;
    _dwLogWinError        = NO_ERROR;

    _strURL.Reset();
    _strURLPathInfo.Reset();
    _strURLParams.Reset();
    _strLogParams.Reset();
    _strPathInfo.Reset();
    _strRawURL.Reset();
    _strOriginalURL.Reset();
    _strMethod.Reset();
    _strAuthInfo.Reset();
    _strPhysicalPath.Reset();
    _strUnmappedPhysicalPath.Reset();

    _strDenialHdrs.Reset();
    _strRespHdrs.Reset();
    _strRange.Reset();

    _bProcessingCustomError = FALSE;
    _bForceNoCache = FALSE;
    _bSendContentLocation = FALSE;
    _bSendVary = FALSE;

    ClearNoCache();
    ClearSendCL();
    ClearSendVary();

    _fAuthenticationRequested = FALSE;
    _fProxyRequest            = FALSE;
    _fBasicRealm              = FALSE;
    _fIfModifier              = FALSE;
    _fHaveContentLength       = FALSE;
    _fLogRecordWritten        = FALSE;
    _dwExpireInDay            = 0x7fffffff;

    if ( _fSingleRequestAuth )
    {
        LPW3_SERVER_STATISTICS pStatsObj = QueryW3StatsObj();
        pStatsObj->DecrCurrentNonAnonymousUsers();

        ResetAuth( FALSE );
        _fSingleRequestAuth = FALSE;
    }

    //  Accept range variables

    _fProcessByteRange = FALSE;
    _fUnsatisfiableByteRange = FALSE;
    _fAcceptRange      = FALSE;
    _iRangeIdx         = 0;
    _cbMimeMultipart   = 0;
    _fMimeMultipart    = FALSE;

    _acIpAccess = AC_NOT_CHECKED;
    _fNeedDnsCheck = FALSE;

    _fChunked           = FALSE;
#if 0    // Not used anywhere /SAB
    _fIsWrite           = FALSE;
#endif
    _fDiscNoError       = FALSE;
    _fNoDisconnectOnError = FALSE;

    SetState( HTR_READING_CLIENT_REQUEST );


    return TRUE;
}

VOID
HTTP_REQ_BASE::SessionTerminated(
    VOID
    )
/*++

Routine Description:

    This method updates the statistics when the TCP session is closed just
    before this object gets destructed (or placed on the free list).

Arguments:

--*/
{
    //
    //  Notify filters
    //

    if ( _Filter.IsNotificationNeeded( SF_NOTIFY_END_OF_NET_SESSION,
                                       IsSecurePort() ))
    {
        _Filter.NotifyEndOfNetSession();
    }

    LPW3_SERVER_STATISTICS pStatsObj = QueryW3StatsObj();
    W3_STATISTICS_1 * pW3Stats = pStatsObj->QueryStatsObj();


    //
    //  Update the statistics
    //

    if ( _fLoggedOn )
    {
        if ( _fAnonymous ) {
            pStatsObj->DecrCurrentAnonymousUsers();
        }
        else {
            pStatsObj->DecrCurrentNonAnonymousUsers();
        }
    }

    pStatsObj->LockStatistics();

    pW3Stats->TotalBytesSent.QuadPart     += _cbTotalBytesSent + _cbBytesSent;
    pW3Stats->TotalBytesReceived.QuadPart += _cbTotalBytesReceived + _cbBytesReceived;
    pW3Stats->TotalFilesSent              += _cFilesSent;
    pW3Stats->TotalFilesReceived          += _cFilesReceived;

    pStatsObj->UnlockStatistics();


    TCP_REQUIRE( _tcpauth.Reset() );

#if defined(CAL_ENABLED)
    if ( m_pCalSslCtxt )
    {
        CalDisconnect( m_pCalSslCtxt );
        m_pCalSslCtxt = NULL;
    }

    if ( m_pCalAuthCtxt )
    {
        CalDisconnect( m_pCalAuthCtxt );
        m_pCalAuthCtxt = NULL;
    }
#endif

    //
    // Cleanup the filter
    //

    _Filter.Cleanup( );

    //
    //  Make sure our input buffer doesn't grow too large.  For example if
    //  somebody just sent 500k of entity data, we want to release the memory
    //  that was used to store that.
    //

    if ( _bufClientRequest.QuerySize() > MAX_CLIENT_SIZE_ALLOWED )
    {
        _bufClientRequest.FreeMemory();
    }
}

BOOL
HTTP_REQ_BASE::OnIfModifiedSince(
    CHAR * pszValue
    )
/*++

Routine Description:

    Extracts the modified date for later use

Arguments:

    pszValue - Pointer to zero terminated string

--*/
{

    if ( StringTimeToFileTime( pszValue,
                               &_liModifiedSince ))
    {
        CHAR        *pszLength;

        _fIfModifier = TRUE;

        pszLength = strchr(pszValue, ';');

        if (pszLength != NULL)
        {
            pszLength++;

            while (isspace((UCHAR)(*pszLength)))
            {
                pszLength++;
            }

            if (!_strnicmp(pszLength, "length", sizeof("length") - 1))
            {
                pszLength += sizeof("length") - 1;

                while (isspace((UCHAR)(*pszLength)))
                {
                    pszLength++;
                }

                if (*pszLength == '=')
                {
                    pszLength++;

                    while (isspace((UCHAR)(*pszLength)))
                    {
                        pszLength++;
                    }

                    _dwModifiedSinceLength = atoi(pszLength);
                }
            }
        }
        return TRUE;
    }

    //
    //  If we couldn't parse the time, then just ignore this field all
    //  together
    //

    DBGPRINTF(( DBG_CONTEXT,
               "[OnIfModifiedSince] Error %d parsing If-Modified-Since time, ignoring field\n",
                GetLastError() ));

    _liModifiedSince.QuadPart = 0;
    _dwModifiedSinceLength = 0;

    return TRUE;
}

BOOL
HTTP_REQ_BASE::OnIfUnmodifiedSince(
    CHAR * pszValue
    )
/*++

Routine Description:

    Extracts the unmodified date for later use

Arguments:

    pszValue - Pointer to zero terminated string

--*/
{
    if ( StringTimeToFileTime( pszValue,
                               &_liUnmodifiedSince ))
    {
        _fIfModifier = TRUE;
        return TRUE;
    }

    //
    //  If we couldn't parse the time, then just ignore this field all
    //  together
    //

    DBGPRINTF(( DBG_CONTEXT,
               "[OnIfUnmodifiedSince] Error %d parsing If-Modified-Since time, ignoring field\n",
                GetLastError() ));

    _liUnmodifiedSince.QuadPart = 0;

    return TRUE;
}

BOOL
HTTP_REQ_BASE::OnUnlessModifiedSince(
    CHAR * pszValue
    )
/*++

Routine Description:

    Extracts the modified date for later use

Arguments:

    pszValue - Pointer to zero terminated string

--*/
{
    BOOL fRet;

    if ( StringTimeToFileTime( pszValue,
                               &_liUnlessModifiedSince ))
    {
        return TRUE;
    }

    //
    //  If we couldn't parse the time, then just ignore this field all
    //  together
    //

    DBGPRINTF(( DBG_CONTEXT,
               "[OnIfUnmodifiedSince] Error %d parsing If-Unmodified-Since time, ignoring field\n",
                GetLastError() ));

    _liUnlessModifiedSince.QuadPart = 0;

    return TRUE;
}

BOOL
HTTP_REQ_BASE::OnIfMatch(
    CHAR * pszValue
    )
/*++

Routine Description:

    Handle the If-Match header.

Arguments:

    pszValue - Pointer to zero terminated string

--*/
{
    _fIfModifier = TRUE;
    return TRUE;
}

BOOL
HTTP_REQ_BASE::OnIfNoneMatch(
    CHAR * pszValue
    )
/*++

Routine Description:

    Handle the If-None-Match header.

Arguments:

    pszValue - Pointer to zero terminated string

--*/
{
    _fIfModifier = TRUE;
    return TRUE;
}

BOOL
HTTP_REQ_BASE::OnIfRange(
    CHAR * pszValue
    )
/*++

Routine Description:

    Handle the If-Range header.

Arguments:

    pszValue - Pointer to zero terminated string

--*/
{
    _fIfModifier = TRUE;
    return TRUE;
}

/*******************************************************************

    NAME:       HTTP_REQ_BASE::OnContentLength

    SYNOPSIS:   Pulls out the number of bytes we expect the client to give us

    ENTRY:      pszValue - Pointer to a zero terminated string

    RETURNS:    TRUE if successful, FALSE if the field wasn't found

    HISTORY:
        Johnl       21-Sep-1994 Created

********************************************************************/

BOOL HTTP_REQ_BASE::OnContentLength( CHAR * pszValue )
{
    CHAR        *pszEnd;

    if (!IsChunked())
    {

        // + and - aren't valid as part of a content length.
        if (*pszValue == '-' || *pszValue == '+')
        {
            SetLastError(ERROR_INVALID_PARAMETER);
            return FALSE;
        }

        errno = 0; // WinSE 16859

        _cbContentLength = strtoul( pszValue, &pszEnd, 10 );

        if (_cbContentLength == ULONG_MAX)
        {
            SetLastError(ERROR_INVALID_PARAMETER);
            return FALSE;
        }

        if (_cbContentLength == 0 || _cbContentLength == ULONG_MAX)
        {
            // Might possibly have underflow or overflow.

            if (errno == ERANGE || pszEnd == pszValue)
            {
                // Either had an overflow/underflow or no conversion.

                SetLastError(ERROR_INVALID_PARAMETER);
                return FALSE;
            }
        }

        // See what terminated the scan.

        if (*pszEnd != '\0' && !isspace((UCHAR)(*pszEnd)))
        {
            SetLastError(ERROR_INVALID_PARAMETER);
            return FALSE;
        }

        _fHaveContentLength = TRUE;
    }

    return TRUE;
}


BOOL
HTTP_REQ_BASE::OnHost (
    CHAR * pszValue
    )
/*++

Routine Description:

        Processes the HTTP "Host: domain name" field

Return Value:

    TRUE if successful, FALSE on error

--*/
{

    if ( !_strHostAddr.Copy( (TCHAR *) pszValue ) )
    {
        return FALSE;
    }

    //
    // remove erroneous port info if present
    // NYI: It will be very useful to get the length information
    //

    PSTR pP = (PSTR) memchr( _strHostAddr.QueryStr(), ':',
                             _strHostAddr.QueryCB() );
    if ( pP != NULL )
    {
        *pP = '\0';
        _strHostAddr.SetLen( DIFF(pP - _strHostAddr.QueryStr()) );
    }


    return TRUE;
}

BOOL
HTTP_REQ_BASE::OnRange (
    CHAR * pszValue
    )
/*++

Routine Description:

        Processes the HTTP "Range" field

Return Value:

    TRUE if successful, FALSE on error

--*/
{
    while ( *pszValue && isspace((UCHAR)(*pszValue)) )
        ++pszValue;

    if ( !_strnicmp( pszValue, "bytes", sizeof("bytes")-1 ) )
    {
        pszValue += sizeof("bytes")-1;
        while ( *pszValue && *pszValue++ != '=' )
            ;
        while ( *pszValue && isspace((UCHAR)(*pszValue)) )
            ++pszValue;
        if ( !_strRange.Copy( (TCHAR *) pszValue ) )
            return FALSE;
    }

    return TRUE;
}


CHAR *
HTTP_REQ_BASE::QueryHostAddr (
    VOID
    )
/*++

Routine Description:

        Returns the local domain name if specified in the request
        or else the local network address

Return Value:

    ASCII representation of the local address

--*/
{
    if ( QueryW3Instance() == NULL )
    {
        return _pClientConn->QueryLocalAddr();
    }

    if ( IsProxyRequest() )
    {
        return ( QueryW3Instance()->QueryDefaultHostName() != NULL ?
                 QueryW3Instance()->QueryDefaultHostName() : 
                 QueryClientConn()->QueryLocalAddr() );
    }
    else
    {
        return 
            (CHAR *) (_strHostAddr.IsEmpty()
                      ? ( (QueryW3Instance()->QueryDefaultHostName() != NULL)
                    ? QueryW3Instance()->QueryDefaultHostName() : _pClientConn->QueryLocalAddr() )
                    : _strHostAddr.QueryStr());
    }
}
 

#if 0

Authorization info now processed after processing is complete

BOOL
HTTP_REQ_BASE::OnAuthorization(
    CHAR * pszValue
    )
/*++

Routine Description:

    Processes the HTTP "Authorization: <type> <authdata>" field

Return Value:

    TRUE if successful, FALSE on error

--*/
{
    return TRUE;
}
#endif

BOOL
HTTP_REQ_BASE::ProcessAuthorization(
    CHAR * pszValue
    )
/*++

Routine Description:

    Processes the HTTP "Authorization: <type> <authdata>" field
    at logo time

Return Value:

    TRUE if successful, FALSE on error

--*/
{
    //
    //  If a filter has indicated this is a proxy request, use the
    //  authorization information
    //

    if ( !IsProxyRequest() )
    {
        return ParseAuthorization( pszValue );
    }

    return TRUE;
}


BOOL
HTTP_REQ_BASE::ParseAuthorization (
    CHAR * pszValue
    )
/*++

Routine Description:

    Processes the HTTP "Authorization: <type> <authdata>" field
        or "Proxy-Authorization: <type> <authdata>" field

Return Value:

    TRUE if successful, FALSE on error

--*/
{
    CHAR *          pchBlob;
    CHAR *          pchSpace = NULL;
    DWORD           dwAuthFlags = QueryAuthentication();

    //
    //  If we've already logged this user and they're specifying authentication
    //  headers, back out the old authentication information and
    //  re-authenticate.
    //

    if ( IsLoggedOn() )
    {
        if ( !QueryW3Instance()->ProcessNtcrIfLoggedOn() )
        {
            //
            //  Ignore the the authorization information if we're logged on with
            //  an NT provider.  Otherwise we authenticate every request with a
            //  full challenge response
            //

            if ( !_fClearTextPass && !_fAnonymous )
            {
                goto NotFound;
            }
        }

        if ( _fAnonymous )
        {
            QueryW3StatsObj()->DecrCurrentAnonymousUsers();
        }
        else
        {
            QueryW3StatsObj()->DecrCurrentNonAnonymousUsers();
        }

        ResetAuth( FALSE );
    }

    //
    //  If only anonymous is checked, ignore all authentication information
    //  (i.e., force all users to the anonymous user).
    //

    if ( !(dwAuthFlags & (~INET_INFO_AUTH_ANONYMOUS) ))
    {
        goto NotFound;
    }

    //
    //  Now break out the authorization type and see if it's an
    //  authorization type we understand
    //

    pchSpace = pchBlob = strchr( pszValue, ' ' );

    if ( pchBlob )
    {
        *pchBlob = '\0';
        pchBlob++;
    }
    else
    {
        pchBlob = "";
    }

    if ( !_strAuthType.Copy( pszValue ) )
    {
        return FALSE;
    }

    //
    //  This processes "user name:password"
    //

    if ( !_stricmp( _strAuthType.QueryStr(), "Basic" ) ||
         !_stricmp( _strAuthType.QueryStr(), "user" ))
    {
        //
        //  If Basic is not enabled, force the user to anonymous if
        //  anon is enabled or kick them out with Access denied
        //

        if ( !(dwAuthFlags & INET_INFO_AUTH_CLEARTEXT) )
        {
            if ( dwAuthFlags & INET_INFO_AUTH_ANONYMOUS )
            {
                _HeaderList.FastMapCancel( HM_AUT );
                _strAuthType.Reset();
                goto NotFound;
            }
            else
            {
                SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG );
                SetLastError( ERROR_ACCESS_DENIED );
                return FALSE;
            }
        }

        //
        //  If the type is Basic, then the string has been uuencoded
        //

        if ( !ExtractClearNameAndPswd( pchBlob,
                                       &_strUserName,
                                       &_strPassword,
                                       *_strAuthType.QueryStr() == 'B' || 
                                       *_strAuthType.QueryStr() == 'b' ))
        {
            //
            // If we can't extract the username/pwd from Authorization header for
            // Basic, we'll assume the client sent an invalid blob
            //
            SetDeniedFlags( SF_DENIED_LOGON );
            SetLastError( ERROR_ACCESS_DENIED );
            return FALSE;
        }

       IF_DEBUG( PARSING )
       {
           DBGPRINTF(( DBG_CONTEXT,
                      "[OnAuthorization] User name = %s\n",
                       _strUserName.QueryStr(),
                       _strPassword.QueryStr() ));
       }

       _fClearTextPass = TRUE;

    }
    else if ( !_stricmp( _strAuthType.QueryStr(), "Digest" ) ||
              !_stricmp( _strAuthType.QueryStr(), "NT-Digest" ) )
    {
#if 0
        if ( !(QueryAuthentication()
                & INET_INFO_AUTH_MD5_AUTH) )
        {
            goto non_allowed;
        }

        LPSTR aValueTable[ MD5_AUTH_LAST ];
        STR strNonce;

        if ( !ParseForName( pchBlob,
                            MD5_AUTH_NAMES,
                            MD5_AUTH_LAST,
                            aValueTable ) ||
             aValueTable[MD5_AUTH_USERNAME] == NULL ||
             aValueTable[MD5_AUTH_REALM] == NULL ||
             aValueTable[MD5_AUTH_URI] == NULL ||
             aValueTable[MD5_AUTH_NONCE] == NULL ||
             aValueTable[MD5_AUTH_RESPONSE] == NULL )
        {
            SetLastError( ERROR_INVALID_PARAMETER );
            return FALSE;
        }

        if ( GenerateNonce( &strNonce, IISSUBA_MD5 ) )
        {
            if ( _tcpauth.LogonDigestUser(
                        aValueTable[MD5_AUTH_USERNAME],
                        aValueTable[MD5_AUTH_REALM],
                        aValueTable[MD5_AUTH_URI],
                        _strMethod.QueryStr(),
                        aValueTable[MD5_AUTH_NONCE],
                        strNonce.QueryStr(),
                        aValueTable[MD5_AUTH_RESPONSE],
                        IISSUBA_MD5,
                        g_pTsvcInfo ) )
            {
                _fAuthenticating = FALSE;
                _fLoggedOn = TRUE;
            }
            else
            {
                DWORD err = GetLastError();
                if ( err == ERROR_ACCESS_DENIED ||
                     err == ERROR_LOGON_FAILURE )
                {
                    _fAuthenticating = FALSE;
                    SetDeniedFlags( SF_DENIED_LOGON );
                    SetKeepConn( FALSE );
                }

                return FALSE;
            }
        }
        else
        {
            return FALSE;
        }
#else
        _fAuthTypeDigest = TRUE;
        _strUserName.Reset();
        _strPassword.Copy ( pchBlob );
#endif
    }
    else
    {
        //
        //  See if it's one of the SSP packages
        //

        BUFFER buff;
        BOOL   fNeedMoreData;
        DWORD  cbOut;

        if ( !QueryMetaData()->CheckSSPPackage( _strAuthType.QueryStr() ) )
        {
            goto NotFound;
        }

        //
        //  If NTLM is not enabled, force the user to anonymous if
        //  anon is enabled or kick them out with Access denied
        //

        if ( !(dwAuthFlags & INET_INFO_AUTH_NT_AUTH) )
        {
            if ( dwAuthFlags & INET_INFO_AUTH_ANONYMOUS )
            {
                goto NotFound;
            }
            else
            {
                SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG );
                SetLastError( ERROR_ACCESS_DENIED );
                return FALSE;
            }
        }

        //
        //  Process the authentication blob the client sent
        //  us and build the blob to be returned in _strAuthInfo
        //

        if ( !_tcpauth.Converse( pchBlob,
                                 0,
                                 &buff,
                                 &cbOut,
                                 &fNeedMoreData,
                                 QueryMetaData()->QueryAuthentInfo(),
                                 _strAuthType.QueryStr(),
                                 NULL,
                                 NULL,
                                 QueryW3Instance() ) ||
             !_strAuthInfo.Copy( _strAuthType )           ||
             !_strAuthInfo.Append( " ", 1 )               ||
             !_strAuthInfo.Append( cbOut ? ((CHAR *) buff.QueryPtr()) :
                                           "" ))
        {
            DWORD err = GetLastError();

            //
            //  If the authentication package gives us a denied error, then
            //  we need to reset our authorization info to indicate the client
            //  needs to start from scratch.  We also force a disconnect.
            //

            if ( err == ERROR_ACCESS_DENIED ||
                 err == ERROR_LOGON_FAILURE )
            {
                _fAuthenticating = FALSE;
                SetDeniedFlags( SF_DENIED_LOGON );
                SetKeepConn( FALSE );
            }

            if ( err == ERROR_PASSWORD_EXPIRED ||
                 err == ERROR_PASSWORD_MUST_CHANGE )
            {
                _fAuthenticating = FALSE;
                SetDeniedFlags( SF_DENIED_LOGON );
                SetKeepConn( FALSE );
            }

            return FALSE;
        }

        _fAuthenticating = fNeedMoreData;
        if ( !fNeedMoreData && !cbOut )
        {
            _strAuthInfo.Reset();
        }

        //
        //  If the last server side conversation succeeded and there isn't
        //  any more data, then we've successfully logged the user on
        //

        if ( _fLoggedOn = !fNeedMoreData )
        {
            if ( !CheckValidSSPILogin() )
            {
                return FALSE;
            }
        }

#if 0
        else if ( !IsKeepConnSet() )
        {
            // no point in sending data : connection won't be kept alive
            // assume that auth method is session oriented
            SetDeniedFlags( SF_DENIED_LOGON );
            SetLastError( ERROR_ACCESS_DENIED );
            return FALSE;
        }
#endif
    }

NotFound:

    //
    //  Restore the string
    //

    if ( pchSpace )
    {
        *pchSpace = ' ';
    }

    return TRUE;
}

BOOL
HTTP_REQ_BASE::OnProxyAuthorization(
    CHAR * pszValue
    )
/*++

Routine Description:

    Processes the HTTP "Proxy-Authorization: <type> <authdata>" field

Return Value:

    TRUE if successful, FALSE on error

--*/
{
    //
    //  If a filter has indicated this is a proxy request, use the
    //  authorization information
    //

    if ( IsProxyRequest() )
    {
        return ParseAuthorization( pszValue );
    }

    return TRUE;
}

/*******************************************************************

    NAME:       HTTP_REQ_BASE::BuildStatusLine

    SYNOPSIS:   Formulates the HTTP status line of the server response to
                the client of the form:

                    <http version> <status code> <reason> <CrLf>

    ENTRY:      pbufResp - Receives status string
                dwHTTPError - Response code to load
                dwError2 - Optional reason error

    NOTES:      Optional acceptable authentication information will be
                added if the HTTP error is access denied

    HISTORY:
        Johnl       29-Aug-1994 Created

********************************************************************/

BOOL HTTP_REQ_BASE::BuildStatusLine( BUFFER *       pbufResp,
                                     DWORD          dwHTTPError,
                                     DWORD          dwError2,
                                     LPSTR          pszError2,
                                     STR            *pstrErrorStr)
{
    STACK_STR( strStatus, MAX_PATH );
    STACK_STR( strError2, MAX_PATH );
    LPSTR  pErr2 = NULL;
    LPSTR  pFormatStrBuff = NULL;
    CHAR * pszStatus;
    CHAR   ach[64];
    CHAR * pszTail;

    //
    //  Get the HTTP error string
    //

    switch ( dwHTTPError )
    {
    case HT_OK:
        pszStatus = "OK";
        break;

    case HT_RANGE:
        pszStatus = "Partial content";
        break;

    case HT_NOT_MODIFIED:
        pszStatus = "Not Modified";
        break;

    case HT_REDIRECT:
        pszStatus = "Object Moved";
        break;

    default:
        if ( !g_pInetSvc->LoadStr( strStatus, dwHTTPError + ID_HTTP_ERROR_BASE ))
        {
            DBGPRINTF((DBG_CONTEXT,
                      "BuildErrorResponse: failed to load HTTP status code %d (res=%d), error %d\n",
                       dwHTTPError,
                       dwHTTPError + ID_HTTP_ERROR_BASE,
                       GetLastError() ));

            pszStatus = "Error";
        }
        else
        {
            pszStatus = strStatus.QueryStr();
        }

        break;
    }

    //
    //  If the client wants a secondary error string, get it now
    //

    if ( dwError2 )
    {
        if ( g_pInetSvc->LoadStr( strError2, dwError2 ))
        {
            pErr2 = strError2.QueryStr();
        }
        else
        {
            DBGPRINTF((DBG_CONTEXT,
                      "BuildErrorResponse: failed to load 2nd status code %d (res=%d), error %d\n",
                       dwError2,
                       dwError2,
                       GetLastError() ));

            //
            //  Couldn't load the string, just provide the error number then
            //

                        wsprintf( ach, "%d (0x%08lx)", dwError2, dwError2 );

            if ( !strError2.Copy( ach ))
                return FALSE;

            pErr2 = strError2.QueryStr();
        }

        if ( dwError2 == ERROR_BAD_EXE_FORMAT )
        {
            // format the message to include file name
            DWORD dwL = 0;
            // handle exception that could be generated if this message
            // requires more than the # of param we supply and AV
            __try {
                if ( !FormatMessage( ( FORMAT_MESSAGE_ALLOCATE_BUFFER|
                                       FORMAT_MESSAGE_FROM_STRING|
                                       FORMAT_MESSAGE_ARGUMENT_ARRAY
                                       ),
                                     strError2.QueryStr(),
                                     0,
                                     0,
                                     (LPTSTR)&pFormatStrBuff,
                                     dwL,
                                     (va_list*)&pszError2 )
                     ) {
                    pErr2 = NULL;
                    pFormatStrBuff = NULL;
                }
            }
            __except ( EXCEPTION_EXECUTE_HANDLER )
            {
                pErr2 = NULL;
                pFormatStrBuff = NULL;
            }
        }
    }

    //
    //  Make sure there is room for the wsprintf
    //

    if ( !pbufResp->Resize( strlen(pszStatus) + 1      +
                            (pErr2 ? strlen(pErr2) : 0) +
                            LEN_PSZ_HTTP_VERSION_STR    +
                            20 * sizeof(TCHAR) ))   // status code + space
    {
        if ( pFormatStrBuff )
            LocalFree( pFormatStrBuff );
        return FALSE;
    }

    pszTail = (CHAR *) pbufResp->QueryPtr();

    //
    //  Build "HTTP/1.0 ### <status>\r\n" or "HTTP/1.0 ### <status> h(<Error>)\r\n"
    //

    if (!g_ReplyWith11)
    {
        APPEND_NUMERIC_HEADER( pszTail, "HTTP/1.0 ", dwHTTPError, " " );
    }
    else
    {
        APPEND_NUMERIC_HEADER( pszTail, "HTTP/1.1 ", dwHTTPError, " " );
    }

    APPEND_PSZ_HEADER( pszTail, "", pszStatus, "" );

    if ( pErr2 )
    {
        if (pstrErrorStr != NULL)
        {
            if (!pstrErrorStr->Append(strError2))
            {
                pstrErrorStr->SetLen(0);
            }
        }
    }

    APPEND_STRING( pszTail, "\r\n" );

    if ( pFormatStrBuff )
    {
        LocalFree( pFormatStrBuff );
    }

    return TRUE;
}

BOOL HTTP_REQ_BASE::BuildExtendedStatus(
    STR   *        pstrResp,
    DWORD          dwHTTPError,
    DWORD          dwError2,
    DWORD          dwExplanation,
    LPSTR          pszError2
    )
/*++

Routine Description:

    This static method build a HTTP response string with extended explanation
    information

Arguments:

    pStr - Receives built response
    dwHTTPError - HTTP error response
    dwError2 - Extended error information (win/socket error)
    dwExplanation - String ID of the explanation text

Return Value:

    TRUE if successful, FALSE on error

--*/
{

    // NYI: Who does the resize for the string before calling pstrResp ??
    // How long is the buffer though ??
    //  The code originally assumed that there will be enough space
    //   to append strings after buildStatusLine -- we use assert to check it.
    //

    //
    //  "HTTP/<ver> <status>"
    //

    // NYI:  Do a downlevel cast and send the buffer pointer around :(
    if ( !BuildStatusLine( pstrResp,
                           dwHTTPError,
                           dwError2, pszError2))
    {
        return FALSE;
    }

    // NYI: I need to setlen here because the buffer object was used earlier.
    DBG_REQUIRE( pstrResp->SetLen( strlen(pstrResp->QueryStr())));

    //
    //  "Server: <Server>/<version>
    //  Obtain this fro the global cache.
    //

    DBG_REQUIRE( pstrResp->Append( szServerVersion,
                                   (cbServerVersionString))
                 );

#if 0
    //
    //  If we need to add an explanation, also include a content length
    //

    if ( dwExplanation )
    {
        STR     str;

        if ( !g_pInetSvc->LoadStr( str, dwExplanation ))
        {
            return FALSE;
        }

        DBG_REQUIRE( pstrResp->Append( str));
    }

#endif

    return TRUE;
}


BOOL
HTTP_REQ_BASE::LogonUser(
    BOOL * pfFinished
    )
/*++

Routine Description:

    This method attempts to retrieve an impersonation token based
    on the current request

Arguments:

    pfFinished - Set to TRUE if no further processing is needed

Return Value:

    TRUE if successful, FALSE on error

--*/
{
    BOOL fAsGuest;
    BOOL fAsAnonymous;
    BOOL fAsync;
    TCHAR * pszUser;
    DWORD cbUser;
    TCHAR * pszPswd;
    DWORD cbPswd;
    LPSTR pDns = NULL;
    DWORD dwAuth;
    HANDLE hAccessTokenPrimary = NULL;
    HANDLE hAccessTokenImpersonation = NULL;
    LPW3_SERVER_STATISTICS pStatsObj = QueryW3StatsObj();
    W3_STATISTICS_1 * pW3Stats = pStatsObj->QueryStatsObj();
    STACK_STR( strRealm, MAX_PATH); // make a local copy of the realm headers.

    dwAuth = QueryAuthentication();

    if ( _fAuthTypeDigest )
    {
        if ( dwAuth & INET_INFO_AUTH_MD5_AUTH  )
        {
            if ( !_strUserName.Resize( SF_MAX_USERNAME ) ||
                 !_strUnmappedUserName.Resize( SF_MAX_USERNAME ) ||
                 !_strUnmappedUserName.Copy( _strUserName ) ||
                 !_strPassword.Resize( SF_MAX_PASSWORD ) ||
                 !_strUnmappedPassword.Copy( _strPassword ) ||
                 !_strAuthType.Resize(SF_MAX_AUTH_TYPE) )
            {
                return FALSE;
            }

            if ( _Filter.IsNotificationNeeded( SF_NOTIFY_AUTHENTICATIONEX,
                                               IsSecurePort() ) &&
                 !_Filter.NotifyAuthInfoFiltersEx( _strUnmappedUserName.QueryStr(),
                                                   SF_MAX_USERNAME,
                                                   _strUserName.QueryStr(),
                                                   SF_MAX_USERNAME,
                                                   _strPassword.QueryStr(),
                                                   "",
                                                   QueryMetaData()->QueryAuthentInfo()->
                                                   strDefaultLogonDomain.QueryStr(),
                                                   _strAuthType.QueryStr(),
                                                   SF_MAX_AUTH_TYPE,
                                                   &hAccessTokenPrimary,
                                                   &hAccessTokenImpersonation,
                                                   pfFinished ))
            {
                SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_FILTER );
                return FALSE;
            }

            //
            //  The filter may have modified the lengths - reset the lengths.
            //  Note _strPassword doesn't need to be reset here since
            //  NotifyAuthInfoFiltersEx() doesn't modify it.
            //

            _strUnmappedUserName.SetLen( strlen( _strUnmappedUserName.QueryStr()));
            _strUserName.SetLen( strlen( _strUserName.QueryStr() ));
            _strAuthType.SetLen( strlen( _strAuthType.QueryStr() ));

            if ( *pfFinished )
            {
                return TRUE;
            }

            if ( hAccessTokenPrimary != NULL || hAccessTokenImpersonation != NULL )
            {
                _fMappedAcct = TRUE;
                _fSingleRequestAuth = TRUE;
                goto logged_in;
            }
        }

        IF_DEBUG(ERROR) {
            DBGPRINTF((DBG_CONTEXT,"Access denied based on configuration\n"));
        }
        SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG );
        SetLastError( ERROR_ACCESS_DENIED );
        return FALSE;
    }

    if ( !_strUnmappedUserName.Copy( _strUserName ) ||
         !_strUnmappedPassword.Copy( _strPassword ))
    {
        return FALSE;
    }

    if ( _Filter.IsNotificationNeeded( SF_NOTIFY_AUTHENTICATION,
                                       IsSecurePort() ))
    {
        if ( !_strUserName.Resize( SF_MAX_USERNAME ) ||
             !_strPassword.Resize( SF_MAX_PASSWORD ) )
        {
            return FALSE;
        }

        if ( !_Filter.NotifyAuthInfoFilters( _strUserName.QueryStr(),
                                              SF_MAX_USERNAME,
                                              _strPassword.QueryStr(),
                                              SF_MAX_PASSWORD,
                                              pfFinished ))
        {
            IF_DEBUG(ERROR) {
                DBGPRINTF((DBG_CONTEXT,"Access denied based on filter\n"));
            }
            SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_FILTER );
            return FALSE;
        }

        _strUserName.SetLen( strlen( _strUserName.QueryStr() ));
        _strPassword.SetLen( strlen( _strPassword.QueryStr() ));

        if ( *pfFinished )
        {
            return TRUE;
        }

        if ( strcmp( _strUserName.QueryStr(),
                     _strUnmappedUserName.QueryStr() ) )
        {
            _fMappedAcct = TRUE;
        }
    }

    if ( (dwAuth & INET_INFO_AUTH_MAPBASIC) &&
         _Filter.IsNotificationNeeded( SF_NOTIFY_AUTHENTICATIONEX,
                                       IsSecurePort() ) )
    {
        if ( !_strUserName.Resize( SF_MAX_USERNAME ) ||
             !_strPassword.Resize( SF_MAX_PASSWORD ) ||
             !_strAuthType.Resize( SF_MAX_AUTH_TYPE ) )
        {
            return FALSE;
        }
        //
        // generate the realm information for this request
        //

        strRealm.Copy( QueryMetaData()->QueryRealm()
                       ? QueryMetaData()->QueryRealm()
                       : QueryHostAddr() );

        if ( !_Filter.NotifyAuthInfoFiltersEx( _strUnmappedUserName.QueryStr(),
                                              SF_MAX_USERNAME,
                                              _strUserName.QueryStr(),
                                              SF_MAX_USERNAME,
                                              _strPassword.QueryStr(),
                                              strRealm.QueryStr(),
                                              QueryMetaData()->QueryAuthentInfo()->              
                                              strDefaultLogonDomain.QueryStr(), 
                                              _strAuthType.QueryStr(),
                                              SF_MAX_AUTH_TYPE,
                                              &hAccessTokenPrimary,
                                              &hAccessTokenImpersonation,
                                              pfFinished ))
        {
            IF_DEBUG(ERROR) {
                DBGPRINTF((DBG_CONTEXT,"Access denied based on filter\n"));
            }
            SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_FILTER );
            return FALSE;
        }

        _strUnmappedUserName.SetLen( strlen( _strUnmappedUserName.QueryStr()));
        _strUserName.SetLen( strlen( _strUserName.QueryStr() ));
        _strAuthType.SetLen( strlen( _strAuthType.QueryStr() ));

        if ( *pfFinished )
        {
            return TRUE;
        }
    }

logged_in:
    pszUser = *_strUserName.QueryStr() ?
                           _strUserName.QueryStr():
                           NULL;
    cbUser = _strUserName.QueryCB();

    pszPswd = *_strPassword.QueryStr() ?
                           _strPassword.QueryStr():
                           NULL;
    cbPswd = _strPassword.QueryCB();

    pStatsObj->IncrLogonAttempts();

logged_in2:

    if ( (hAccessTokenPrimary != NULL) || (hAccessTokenImpersonation != NULL) )
    {
        _fMappedAcct = TRUE;
        if ( !_tcpauth.SetAccessToken( hAccessTokenPrimary,
                                       hAccessTokenImpersonation ))
        {
            DBGPRINTF(( DBG_CONTEXT,
                       "[ClearTextLogon] ::LogonUser failed, error %d\n",
                        GetLastError()));

            return FALSE;
        }
        fAsGuest = FALSE;
        fAsAnonymous = FALSE;
        _fClearTextPass = FALSE;
    }
    else
    {
        if ( !(dwAuth & INET_INFO_AUTH_ANONYMOUS ) && (pszUser == NULL) )
        {
            IF_DEBUG(ERROR) {
                DBGPRINTF((DBG_CONTEXT,
                    "Access denied based on configuration[%x]\n",
                    dwAuth));
            }

            SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG );
            SetLastError( ERROR_ACCESS_DENIED );
            return FALSE;
        }

        if ( QueryMetaData()->QueryAuthentInfo()->dwLogonMethod == LOGON32_LOGON_NETWORK )
        {
            switch ( QueryW3Instance()->QueryNetLogonWks() )
            {
                case MD_NETLOGON_WKS_DNS:
                    pDns = QueryClientConn()->QueryResolvedDnsName();
                    break;

                case MD_NETLOGON_WKS_IP:
                    pDns = QueryClientConn()->QueryRemoteAddr();
                    break;
            }
        }
        
        if ( ( cbUser > UNLEN ) || ( cbPswd > PWLEN ) )
        {
            SetDeniedFlags( SF_DENIED_LOGON );
            SetLastError( ERROR_INSUFFICIENT_BUFFER );
            return FALSE;
        }
        
        if ( !_tcpauth.ClearTextLogon( pszUser,
                                       pszPswd,
                                       &fAsGuest,
                                       &fAsAnonymous,
                                       QueryW3Instance(),
                                       QueryMetaData()->QueryAuthentInfo(),
                                       pDns ))
        {
            DBGPRINTF(( DBG_CONTEXT,
                       "[ClearTextLogon] ::LogonUser failed, error %d\n",
                        GetLastError()));

            DWORD dwErr = GetLastError();

            if ( (fAsAnonymous || fAsGuest) &&
                 (dwErr == ERROR_LOGIN_TIME_RESTRICTION ||
                  dwErr == ERROR_INVALID_LOGON_HOURS ||
                  dwErr == ERROR_ACCOUNT_DISABLED ||
                  dwErr == ERROR_ACCOUNT_LOCKED_OUT ||
                  dwErr == ERROR_ACCOUNT_EXPIRED ) )
            {
                dwErr = ERROR_ACCESS_DENIED;
            }

            //
            // If we pass an invalid username/domain to LogonUser, LastError is set
            // to ERROR_INVALID_PARAMETER, so we'll just massage that into returning 
            // "Access Denied"
            //
            if ( (dwErr == ERROR_ACCESS_DENIED) ||
                 (dwErr == ERROR_LOGON_FAILURE) ||
                 (dwErr == ERROR_INVALID_PARAMETER) )
            {
                SetDeniedFlags( SF_DENIED_LOGON );
                dwErr = ERROR_ACCESS_DENIED;
            }

            
            //
            // Query fully qualified name ( including domain )
            // so that we can prompt user for new password
            // with a name suitable for NetUserChangePassword
            //

            _tcpauth.QueryFullyQualifiedUserName( pszUser,
                    &_strUnmappedUserName,
                    QueryW3Instance(),
                    QueryMetaData()->QueryAuthentInfo());

            //
            // set last error here because QueryFullyQualifiedUserName
            // modified last error.
            //

            SetLastError( dwErr );

            return FALSE;
        }
    }

    //
    //  Are anonymous or clear text (basic) logons allowed?  We assume
    //  it's an NT logon if it's neither one of these
    //

    if ( fAsAnonymous &&
        !(dwAuth & INET_INFO_AUTH_ANONYMOUS ))
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[ClearTextLogon] Denying anonymous logon (not enabled), user %s\n",
                   _strUserName.QueryStr() ));

        SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG );
        SetLastError( ERROR_ACCESS_DENIED );
        return FALSE;
    }
    else if ( _fClearTextPass &&
              !(dwAuth & INET_INFO_AUTH_CLEARTEXT ))
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[ClearTextLogon] Denying clear text logon (not enabled), user %s\n",
                   _strUserName.QueryStr() ));

        SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG );
        SetLastError( ERROR_ACCESS_DENIED );
        return FALSE;
    }

    if ( fAsGuest )
    {
        if ( !(dwAuth & INET_INFO_AUTH_ANONYMOUS ) )
        {
            DBGPRINTF(( DBG_CONTEXT,
                       "[ClearTextLogon] Denying guest logon (not enabled), user %s\n",
                       _strUserName.QueryStr() ));

            SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG );
            SetLastError( ERROR_ACCESS_DENIED );
            return FALSE;
        }
        if ( !fAsAnonymous )
        {
            _tcpauth.Reset();
            _strUserName.Reset();
            _strPassword.Reset();
            pszUser = NULL;
            pszPswd = NULL;
            cbUser = 0;
            cbPswd = 0;
            goto logged_in2;
        }
    }

    _fLoggedOn   = TRUE;
    _fAnonymous  = fAsAnonymous;

    if ( fAsAnonymous )
    {
        pStatsObj->IncrAnonymousUsers();

        _cbLastAnonAcctDesc = QueryMetaData()->QueryAuthentInfo()->cbAnonAcctDesc;

        if (!_bufLastAnonAcctDesc.Resize(_cbLastAnonAcctDesc) )
        {
            // Couldn't resize the buffer properly, set the size to 0 so
            // we'll force a relogon each time to be safe.

            _cbLastAnonAcctDesc = 0;
        }

        memcpy(_bufLastAnonAcctDesc.QueryPtr(),
            QueryMetaData()->QueryAuthentInfo()->bAnonAcctDesc.QueryPtr(),
            _cbLastAnonAcctDesc);
    }
    else
    {
        pStatsObj->IncrNonAnonymousUsers();
        _cbLastAnonAcctDesc = 0;
    }

    return TRUE;
}



# define MAX_ERROR_MESSAGE_LEN   ( 500)
#define EXTRA_LOGGING_BUFFER_SIZE   2048

BOOL
HTTP_REQ_BASE::WriteLogRecord(
    VOID
    )
/*++

Routine Description:

    Writes a transaction log for this request

Return Value:

    TRUE if successful, FALSE on error

--*/
{

    //
    //  HACK ALERT
    //
    //  This function may crash if this request has already written
    //  a log record - bail out now if so.
    //
    //  This HACK must precede the call to EndOfRequest(), which
    //  is the likely cause of the crash.
    //
    //  NOTE we set flag true immediately (even though we still may
    //  not log) to absolutely guarantee that we won't hit the crash,
    //  and to keep code for this hack as localized as possible.
    //
    //  CONSIDER for AFTER we ship IIS 4.0:
    //      clean up cause of crash, and then remove this hack
    //

    if ( _fLogRecordWritten )
    {
        return TRUE;
    }

    _fLogRecordWritten = TRUE;


    INETLOG_INFORMATION  ilRequest;
    HTTP_FILTER_LOG      Log;
    DWORD                dwLog;
    BOOL                 fDontLog = FALSE;
    LPSTR                pszClientHostName = QueryClientConn()->QueryRemoteAddr();

    //
    //  Metadata pointer can be NULL at this point
    //

    if ( QueryMetaData() )
    {
        if ( QueryMetaData()->QueryDoReverseDns() &&
             QueryClientConn()->IsDnsResolved() )
        {
            pszClientHostName = QueryClientConn()->QueryResolvedDnsName();
        }

        fDontLog = QueryMetaData()->DontLog();
    }

    //
    //  Notify filters and close any handles for this request
    //

    EndOfRequest();

    //
    //  Log this request if we actually did anything
    //

    if ( _dwLogHttpResponse == HT_DONT_LOG || IsProxyRequest() )
    {
        if ( !IsProxyRequest() )
        {
            IF_DEBUG(REQUEST) {
                DBGPRINTF(( DBG_CONTEXT,
                            "[WriteLogRecord] not writing log record, status is HT_DONT_LOG\n" ));
            }
        }

        return TRUE;
    }

    //
    //  If no logging is required, get out now
    //

    if ( ((HTTP_REQUEST*)this)->QueryVerb() == HTV_TRACECK ||
         (QueryW3Instance() == NULL) ||
         (!QueryW3Instance()->IsLoggingEnabled()) ||
         (!QueryW3Instance()->IsLogErrors() && (QueryLogWinError() || (QueryLogHttpResponse() >= 400))) ||
         (!QueryW3Instance()->IsLogSuccess() && (QueryLogWinError() == NO_ERROR) && 
            (QueryLogHttpResponse() < 400)) ||
         fDontLog )
    {
        return TRUE;
    }

    if ( _Filter.IsNotificationNeeded( SF_NOTIFY_LOG,
                                       IsSecurePort() ))
    {
        //
        //  If we have filters, use the possible filter replacement items
        //

        Log.pszClientHostName     = pszClientHostName;
        Log.pszClientUserName     = _strUserName.QueryStr();
        Log.pszServerName         = QueryClientConn()->QueryLocalAddr();

        Log.pszOperation          = _strMethod.QueryStr();
        Log.pszTarget             = _strURL.QueryStr();

        Log.pszParameters         = _strLogParams.QueryCCH() ?
                                    _strLogParams.QueryStr() :
                                    _strURLParams.QueryStr();

        Log.dwHttpStatus          = QueryLogHttpResponse();
        Log.dwWin32Status         = QueryLogWinError();
        Log.dwBytesSent           = _cbBytesSent;
        Log.dwBytesRecvd          = _cbClientRequest + _cbTotalEntityBody;
        Log.msTimeForProcessing   = GetCurrentTime() - _msStartRequest;

        _Filter.NotifyLogFilters( &Log );

        ilRequest.pszClientHostName  =   (char *)   Log.pszClientHostName;
        ilRequest.pszClientUserName  =   (char *)   Log.pszClientUserName;
        ilRequest.pszServerAddress   =   (char *)   Log.pszServerName;

        ilRequest.pszOperation       =   (char *)   Log.pszOperation;
        ilRequest.pszTarget          =   (char *)   Log.pszTarget;
        ilRequest.pszParameters      =   (char *)   Log.pszParameters;
        ilRequest.dwProtocolStatus   =              Log.dwHttpStatus;
        ilRequest.dwWin32Status      =              Log.dwWin32Status;

        ilRequest.msTimeForProcessing   = Log.msTimeForProcessing;
        ilRequest.dwBytesSent       = Log.dwBytesSent;
        ilRequest.dwBytesRecvd      = Log.dwBytesRecvd;

        ilRequest.cbOperation           = strlen( Log.pszOperation );
        ilRequest.cbTarget              = strlen( Log.pszTarget );
        ilRequest.cbClientHostName      = strlen(ilRequest.pszClientHostName);
    }
    else
    {
        ilRequest.pszClientHostName     = pszClientHostName;
        ilRequest.pszClientUserName     = _strUserName.QueryStr();
        ilRequest.pszServerAddress      = QueryClientConn()->QueryLocalAddr();

        ilRequest.pszOperation          = _strMethod.QueryStr();
        ilRequest.pszTarget             = _strURL.QueryStr();

        ilRequest.pszParameters         = _strLogParams.QueryCCH() ?
                                          _strLogParams.QueryStr() :
                                          _strURLParams.QueryStr();

        ilRequest.dwProtocolStatus      = QueryLogHttpResponse();
        ilRequest.dwWin32Status         = QueryLogWinError();

        ilRequest.msTimeForProcessing   = GetCurrentTime() - _msStartRequest;
        ilRequest.dwBytesSent           = _cbBytesSent;
        ilRequest.dwBytesRecvd          = _cbClientRequest + _cbTotalEntityBody;

        //
        // Get length of some strings
        //

        ilRequest.cbOperation           = _strMethod.QueryCCH();
        ilRequest.cbTarget              = _strURL.QueryCCH();
        ilRequest.cbClientHostName      = strlen(ilRequest.pszClientHostName);
    }

    //
    // write capacity planning trace info.
    //
    
    if (GetIISCapTraceFlag())
    {
        PIIS_CAP_TRACE_INFO pHttpCapTraceInfo;

        pHttpCapTraceInfo = AtqGetCapTraceInfo(QueryClientConn()->QueryAtqContext());

        pHttpCapTraceInfo->IISCapTraceHeader.TraceHeader.Size = sizeof (IIS_CAP_TRACE_INFO);
        pHttpCapTraceInfo->IISCapTraceHeader.TraceHeader.Class.Type = EVENT_TRACE_TYPE_INFO;

        pHttpCapTraceInfo->MofFields[0].Length  = ilRequest.cbOperation+1;
        pHttpCapTraceInfo->MofFields[0].DataPtr = (ULONGLONG) ilRequest.pszOperation;

        pHttpCapTraceInfo->MofFields[1].Length  = ilRequest.cbTarget+1;
        pHttpCapTraceInfo->MofFields[1].DataPtr = (ULONGLONG) ilRequest.pszTarget;
        
        pHttpCapTraceInfo->MofFields[2].Length  = sizeof(DWORD);
        pHttpCapTraceInfo->MofFields[2].DataPtr = (ULONGLONG) &ilRequest.dwBytesSent;
        
        if ( ERROR_INVALID_HANDLE == TraceEvent ( GetIISCapTraceLoggerHandle(),
                                          (PEVENT_TRACE_HEADER) pHttpCapTraceInfo))            
        {
            SetIISCapTraceFlag(0);            
        }
    }

    BYTE  pchTemp[EXTRA_LOGGING_BUFFER_SIZE];
    BUFFER buf( pchTemp, EXTRA_LOGGING_BUFFER_SIZE); // init w/- stack buffer
    ilRequest.pszHTTPHeader= NULL;
    ilRequest.dwPort = QueryClientConn()->QueryPort( );

    ilRequest.pszVersion=(LPSTR)_HeaderList.FastMapQueryValue(HM_VER);

    //
    // Only log extra fields if the request was from a version > 0.9 [which didn't 
    // have headers] and extra logging fields have been requested
    //
    if ( !IsPointNine() &&
         QueryW3Instance()->m_Logging.IsRequiredExtraLoggingFields())
    {
        //
        // reconstruct the buffer
        //

        PCHAR pszFieldName =
            QueryW3Instance()->m_Logging.QueryExtraLoggingFields();

        DWORD cbValueSize=0;
        DWORD cbTotalSize=0;
        DWORD cbActualSize=0;
        DWORD cbCurrentBufferSize = EXTRA_LOGGING_BUFFER_SIZE;
        PCHAR pszValue;
        PCHAR pszBuff = (TCHAR *) buf.QueryPtr();

        while ( *pszFieldName != '\0' )
        {
            //
            // add the string into the buffer
            //

            pszValue = QueryHeaderList()->FindValue(
                                            pszFieldName,
                                            &cbValueSize
                                            );

            if ( pszValue == NULL ) {
                cbValueSize = 0;
                pszValue = "";
            }

            cbTotalSize = cbActualSize + cbValueSize + 2;
            if (cbTotalSize > cbCurrentBufferSize)
            {
                buf.Resize(cbTotalSize);
                cbCurrentBufferSize = cbTotalSize;
                pszBuff=(TCHAR*)buf.QueryPtr();
                pszBuff += cbActualSize;
            }

            CopyMemory( pszBuff, pszValue, strlen(pszValue)+1);
            pszFieldName += strlen(pszFieldName) + 1;
            pszBuff += cbValueSize+1;
            cbActualSize += cbValueSize+1;
            *pszBuff = '\0';
        }

        ilRequest.pszHTTPHeader = (PCHAR)buf.QueryPtr();
        ilRequest.cbHTTPHeaderSize = cbActualSize;
    }

    dwLog = QueryW3Instance()->m_Logging.LogInformation(&ilRequest);

    if ( dwLog != NO_ERROR )
    {
        DBGPRINTF((DBG_CONTEXT,
                   "[WriteLogRecord] - Failed, error %d\n",
                   GetLastError() ));

        //
        //  We should make sure LogInformation will never fail
        //
    }

    return TRUE;
}

BOOL
HTTP_REQ_BASE::AppendLogParameter(
    CHAR * pszParam
    )
/*++

Routine Description:

    Appends data for logging.

Arguments:

    pszParam - The data to be appended

Return Value:

    TRUE if successful, FALSE on error

Notes:

    The way that AppendLogData works is that it just appends
    data to the query string, so that when the query string
    is logged, the new data gets a free ride.

    The problem with this is that anyone who looks at the
    query string after AppendLogParameter will see the additional
    log data.

    To prevent this, we'll create a copy of the query string
    data upon the first call of this function and then append
    the new data to the copy.  When the log is written, the copy
    will be written if it has data, else the original query
    string will be written.

--*/
{
    //
    // If the _strLogParams buffer is empty, copy
    // the query string first.
    //

    if ( _strLogParams.QueryCCH() == 0 )
    {
        BOOL fRet = _strLogParams.Copy( _strURLParams.QueryStr() );

        if ( !fRet )
        {
            return fRet;
        }
    }

    //
    // Append the new data.
    //

    return _strLogParams.Append( pszParam );
}

BOOL
HTTP_REQ_BASE::BuildHttpHeader( OUT BOOL * pfFinished,
                                IN  CHAR * pchStatus OPTIONAL,
                                IN  CHAR * pchAdditionalHeaders OPTIONAL,
                                IN  DWORD  dwOptions)
/*++

Routine Description:

    Builds a full HTTP header reply with an optional status and
    other headers/data

Arguments:

    pchStatus - optional HTTP status string like "401 Access Denied"
    pchAdditionalHeaders - optional additional HTTP or MIME headers and
        data.  Must supply own '\r\n' terminator if this parameter is
        supplied
    pfFinished - Set to TRUE if no further processing is required

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    STR     str;
    BOOL    fFinished = FALSE;

    if ( pchStatus )
    {
        DWORD cbLen = ::strlen( pchStatus);

        // NYI:  Can we compress the calls to string class here
        //  Also make sure enough space is allocated in string object

        if ( !str.Resize( LEN_PSZ_HTTP_VERSION_STR + cbLen + 4) ||
             !str.Copy( (!g_ReplyWith11 ? PSZ_HTTP_VERSION_STR :
                                          PSZ_HTTP_VERSION_STR11),
                        LEN_PSZ_HTTP_VERSION_STR )              ||
             !str.Append( pchStatus, cbLen )
             )
        {
            return FALSE;
        }

        // I am safe to assume space is there, because of resize
        DBG_ASSERT( str.QueryCB() < (str.QuerySize() - 2));
        str.AppendCRLF();
    }

    if ( !BuildBaseResponseHeader( QueryRespBuf(),
                                   pfFinished,
                                   (pchStatus ? &str : NULL ),
                                   dwOptions ))
    {
        return FALSE;
    }

    if ( pchAdditionalHeaders )
    {
        DWORD       dwAddlHdrLength;
        DWORD       dwCurrentLength;


        dwAddlHdrLength = strlen(pchAdditionalHeaders) + 1;
        dwCurrentLength = QueryRespBufCB();

        if (!QueryRespBuf()->Resize(dwCurrentLength + dwAddlHdrLength))
        {
            return FALSE;
        }

        memcpy(QueryRespBufPtr() + dwCurrentLength,
                pchAdditionalHeaders,
                dwAddlHdrLength);

    }

    return TRUE;

} // HTTP_REQ_BASE::BuildHttpHeader()

BOOL
HTTP_REQ_BASE::SendHeader(
    IN  CHAR * pchStatus OPTIONAL,
    IN  CHAR * pchAdditionalHeaders OPTIONAL,
    IN  DWORD  IOFlags,
    OUT BOOL * pfFinished,
    IN  DWORD  dwOptions,
    IN  BOOL   fWriteHeader
    )
/*++

Routine Description:

    Does a synchronous send of an HTTP header with optional status
    and additional headers.

Arguments:

    pchStatus - HTTP Status code (or NULL for "200 Ok")
    pchAdditionalHeaders - Headers to add to the standard response set
    IOFlags - IO_* flags to send the headers with
    pfFinished - Set to TRUE if no further processing is needed for this
        request
    fWriteHeader - Defaults to TRUE; pass FALSE to suppress writing headers
        (designed for callers that want to send header and body together)

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    DWORD   BytesSent;
    DWORD   cbAddHeaders;
    BOOL    fAnyChanges = FALSE;

    *pfFinished = FALSE;

    if ( pchAdditionalHeaders )
    {
        cbAddHeaders = strlen( pchAdditionalHeaders );

        if ( cbAddHeaders &&
             cbAddHeaders > QueryRespBuf()->QuerySize() / 2)
        {
            if ( !QueryRespBuf()->Resize( QueryRespBuf()->QuerySize() +
                                          cbAddHeaders ))
            {
                return FALSE;
            }
        }
    }

    if ( !BuildHttpHeader( pfFinished,
                           pchStatus,
                           pchAdditionalHeaders,
                           dwOptions))
    {
        return FALSE;
    }

    if ( *pfFinished )
    {
        return TRUE;
    }

    DBG_ASSERT( QueryRespBufCB() <=
                QueryRespBuf()->QuerySize() );

    if ( _Filter.IsNotificationNeeded( SF_NOTIFY_SEND_RESPONSE,
                                       IsSecurePort() ))
    {
        if ( !_Filter.NotifySendHeaders( QueryRespBufPtr(),
                                         pfFinished,
                                         &fAnyChanges,
                                         QueryRespBuf() ))
        {
            return FALSE;
        }

        if ( *pfFinished )
        {
            return TRUE;
        }
    }

    if ( fWriteHeader )
    {
        if ( !WriteFile( QueryRespBufPtr(),
                     QueryRespBufCB(),
                     &BytesSent,
                     IOFlags ))
        {
            return FALSE;
        }
    }

    return TRUE;

} // HTTP_REQ_BASE::SendHeader()



BOOL
HTTP_REQ_BASE::SendHeader(
    IN  CHAR * pchHeaders,
    IN  DWORD  cbHeaders,
    IN  DWORD  IOFlags,
    OUT BOOL * pfFinished
    )
/*++

Routine Description:

    Does a send of the HTTP header response where the status is already
    embedded in the header set

    If the pfFinished comes back TRUE and IO_FLAG_ASYNC was specified, then
    an IO completion will be made

Arguments:

    pchHeaders - Pointer to header set
    cbHeaders - Length of headers to send (or -1 if headers are '\0' terminated)
    IOFlags - IO_* flags to send the headers with
    pfFinished - Set to TRUE if no further processing is needed for this
        request

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    DWORD   BytesSent;
    DWORD   cbAddHeaders;
    BOOL    fAnyChanges = FALSE;

    *pfFinished = FALSE;

    if ( _Filter.IsNotificationNeeded( SF_NOTIFY_SEND_RESPONSE,
                                       IsSecurePort() ))
    {
        if ( !_Filter.NotifySendHeaders( pchHeaders,
                                         pfFinished,
                                         &fAnyChanges,
                                         QueryRespBuf() ))
        {
            return FALSE;
        }

        if ( *pfFinished )
        {
            return TRUE;
        }

        if ( fAnyChanges )
        {
            pchHeaders = QueryRespBufPtr();
            cbHeaders = QueryRespBufCB();
        }
    }

    if ( cbHeaders == -1 )
    {
        cbHeaders = strlen( pchHeaders );
    }

    if ( !WriteFile( pchHeaders,
                     cbHeaders,
                     &BytesSent,
                     IOFlags ))
    {
        return FALSE;
    }

    return TRUE;

} // HTTP_REQ_BASE::SendHeader()


#define NO_CACHE_HEADER_SIZE (sizeof("Cache-Control: no-cache,no-transform\r\n") - 1 +\
                                sizeof("Expires: Mon, 00 Jan 0000 00:00:00 GMT\r\n") - 1)


BOOL
HTTP_REQ_BASE::BuildBaseResponseHeader(
    BUFFER *     pbufResponse,
    BOOL *       pfFinished,
    STR *        pstrStatus,
    DWORD        dwOptions
    )
/*++

Routine Description:

    Builds a set of common server response headers

Arguments:

    pbufResponse - Receives response headers
    pfFinished - Set to TRUE if no further processing is needed
    pstrStatus - Optional HTTP response status (defaults to 200)
    dwOptions - bit field of options.
        HTTPH_SEND_GLOBAL_EXPIRE Indicates whether an
          "Expires: xxx" based on the global expires value
          is include with the headers
        HTTPH_NO_DATE indicates whether to generate a
          "Date:" header.

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    FILETIME    ftSysTime;
    CHAR        achTime[64];
    BOOL        fSysTimeValid = FALSE;
    CHAR *      pszResp;
    CHAR *      pszTail;
    DWORD       cb;
    DWORD       cbExpire;
    DWORD       cbCustom;
    DWORD       cbLeft;
    DWORD       dwSizeUsed;


    pszResp = (CHAR *) pbufResponse->QueryPtr();

    //
    //  Add the status line - "HTTP/1.0 nnn sss...\r\n"
    //

    if ( !pstrStatus )
    {

        pszTail = pszResp;
        if ( !_fProcessByteRange )
        {
            if (!g_ReplyWith11)
            {
                APPEND_STRING( pszTail, "HTTP/1.0 200 OK\r\n" );
            }
            else
            {
                APPEND_STRING( pszTail, "HTTP/1.1 200 OK\r\n" );
            }

        } else
        {
            if (!g_ReplyWith11)
            {
                APPEND_STRING( pszTail, "HTTP/1.0 206 Partial content\r\n" );
            }
            else
            {
                APPEND_STRING( pszTail, "HTTP/1.1 206 Partial content\r\n" );
            }
        }
    }
    else
    {
        // ++WinSE 27217
        DWORD dwRequired = pstrStatus->QuerySize()
            + MIN_BUFFER_SIZE_FOR_HEADERS;

        if(pbufResponse->QuerySize() < dwRequired)
        {
            if(!pbufResponse->Resize(dwRequired))
                return FALSE;
            pszResp = (CHAR *) pbufResponse->QueryPtr();
        }
        // --WinSE 27217

        strcpy( pszResp, pstrStatus->QueryStr() );
        pszTail = pszResp + strlen(pszResp);
    }

    //
    //  "Server: Microsoft/xxx
    //

    APPEND_VER_STR( pszTail );

    DBG_ASSERT( pbufResponse->QuerySize() >= MIN_BUFFER_SIZE_FOR_HEADERS );

    //
    //  Fill in the rest of the headers
    //

    //
    //  "Date: <GMT Time>" - Time the response was sent.
    //

    if ( !(dwOptions & HTTPH_NO_DATE ) )
    {
         // build Date: uses Date/Time cache
        pszTail += g_pDateTimeCache->GetFormattedCurrentDateTime( pszTail );
    }

    //
    //  Add an expires header and any custom headers if the feature
    //  is enabled and the caller wants it. Since we could be adding
    //  lots of headers here check for space.
    //
    //  Note for filters that send response headers, _pMetaData may
    //  be NULL at this point
    //

    if (!(dwOptions & HTTPH_NO_CUSTOM))
    {
        cbCustom = _pMetaData ? _pMetaData->QueryHeaders()->QueryCB() : 0;
    }
    else
    {
        cbCustom = 0;
    }

    if ( dwOptions & HTTPH_SEND_GLOBAL_EXPIRE )
    {
        if (!_bForceNoCache)
        {
            cbExpire = _pMetaData->QueryExpireMaxLength() +
                        _pMetaData->QueryCacheControlHeaderLength();
        }
        else
        {
            cbExpire = NO_CACHE_HEADER_SIZE;
        }
    }
    else
    {
        cbExpire = 0;
    }

    if ( (cb = cbCustom + cbExpire) != 0 )
    {
        cb += POST_CUSTOM_HEADERS_SIZE;

        // Find out how many bytes are left in the response buffer.

        cbLeft = pbufResponse->QuerySize() - DIFF(pszTail - pszResp);

        if (cb > cbLeft) {

            // Not enough left, try to resize the buffer.

            if ( !pbufResponse->Resize( pbufResponse->QuerySize() - cbLeft + cb + 1))
            {
                // Couldn't resize, fail.

                return FALSE;
            }

            pszTail = (CHAR *) pbufResponse->QueryPtr() + (pszTail - pszResp);

            // Update pszResp, in case it's used later.
            pszResp = (CHAR *) pbufResponse->QueryPtr();
        }

        if ( cbCustom )
        {
            memcpy( pszTail, _pMetaData->QueryHeaders()->QueryStr(), cbCustom + 1);
            pszTail += cbCustom;
        }

        if ( cbExpire )
        {
            DWORD       dwExpireHeaderLength;
            FILETIME    ftNow;
            DWORD       dwStaticMaxAge;
            DWORD       dwExpireMode;
            DWORD       dwDelta;

            if ( !fSysTimeValid )
            {
                ::IISGetCurrentTimeAsFileTime(&ftSysTime);
                fSysTimeValid = TRUE;
            }

            dwExpireHeaderLength = _pMetaData->QueryExpireHeaderLength();
            if (!_bForceNoCache)
            {
                dwExpireMode = _pMetaData->QueryExpireMode();

                if (_pMetaData->QueryCacheControlHeaderLength() != 0)
                {
                    memcpy( pszTail, _pMetaData->QueryCacheControlHeader(),
                        _pMetaData->QueryCacheControlHeaderLength() + 1 );

                    pszTail += _pMetaData->QueryCacheControlHeaderLength();
                }
            }
            else
            {
                memcpy(pszTail, "Cache-Control: no-cache,no-transform\r\n",
                    sizeof("Cache-Control: no-cache,no-transform\r\n"));
                pszTail += sizeof("Cache-Control: no-cache,no-transform\r\n") - 1;
                dwExpireMode = EXPIRE_MODE_DYNAMIC;
            }

            switch ( dwExpireMode )
            {
                case EXPIRE_MODE_STATIC:

                    if (!_pMetaData->QueryConfigNoCache() &&
                        !_pMetaData->QueryHaveMaxAge())
                    {
                        //
                        // Don't have a pre-configured max-age or no-cache
                        // header. Compute the proper one now.
                        LONGLONG        llNow;

                        llNow = *(LONGLONG *)&ftSysTime;

                        if (llNow < _pMetaData->QueryExpireTime().QuadPart)
                        {
                            llNow = _pMetaData->QueryExpireTime().QuadPart - llNow;
                            
                            llNow /= FILETIME_1_SECOND;      // Convert to seconds.
                            
                            if ( llNow > (LONGLONG)0xffffffff)
                            {
                                dwStaticMaxAge = 0xfffffff;
                            }
                            else
                            {
                                dwStaticMaxAge = (DWORD)llNow;
                            }
                        }
                        else
                        {
                            dwStaticMaxAge = 0;
                        }
                        
                        if (dwStaticMaxAge != 0)
                        {
                            APPEND_NUMERIC_HEADER(pszTail, "max-age=",
                                                  dwStaticMaxAge, "\r\n");
                        }
                        else
                        {
                            APPEND_STRING(pszTail, "no-cache\r\n");
                        }


                    }

                    memcpy( pszTail, _pMetaData->QueryExpireHeader(),
                        dwExpireHeaderLength + 1 );

                    pszTail += dwExpireHeaderLength;
                    break;

                case EXPIRE_MODE_DYNAMIC:
                    if (!_bForceNoCache)
                    {
                        dwDelta = _pMetaData->QueryExpireDelta();
                    }
                    else
                    {
                        dwDelta = 0;
                    }

                    if ( !::FileTimeToGMTEx( ftSysTime,
                                               achTime,
                                               sizeof(achTime),
                                               dwDelta ))
                    {
                        return FALSE;
                    }
                    APPEND_STRING( pszTail, "Expires: " );
                    cb = strlen( achTime );
                    memcpy( pszTail, achTime, cb );
                    pszTail += cb;
                    APPEND_STRING( pszTail, "\r\n" );

                    break;

                default:
                    break;
            }
        }
    }

    //
    //  "Connection: keep-alive" - Indicate if the server accepted the
    //      session modifier by reflecting it back to the client
    //

    if ( IsKeepConnSet() )
    {
        if (!IsOneOne() && !(dwOptions & HTTPH_NO_CONNECTION))
        {
            APPEND_STRING( pszTail, "Connection: keep-alive\r\n" );
        }

    } else
    {
        if (IsOneOne() && !(dwOptions & HTTPH_NO_CONNECTION))
        {
            APPEND_STRING( pszTail, "Connection: close\r\n" );
        }
    }

    if (dwOptions & HTTPH_SEND_GLOBAL_EXPIRE)
    {

        //
        // Check to see if we need to send a Content-Location: or
        // Vary: header.

        if (_bSendContentLocation)
        {
            CHAR        *pszHostName;
            DWORD       cbHostNameLength;

            pszHostName = QueryHostAddr();
            cbHostNameLength = strlen(pszHostName);

            dwSizeUsed = DIFF(pszTail - pszResp);

            if ( !pbufResponse->Resize(dwSizeUsed + POST_CUSTOM_HEADERS_SIZE +
                  sizeof("Content-Location: https:// \r\n") + sizeof(":65536") +
                  cbHostNameLength + _strRawURL.QueryCB()))
            {
                // Couldn't resize, fail.

                return FALSE;
            }
            pszResp = (CHAR *) pbufResponse->QueryPtr();

            pszTail = pszResp + dwSizeUsed;

            // WinSE 5600
            SHORT sPort = QueryClientConn()->QueryPort();
            CHAR szPort[sizeof(":65536")];
            DWORD szPortLen=0;

            if (QueryClientConn()->IsSecurePort())
            {
                if (sPort != 443 )
                {
                    szPortLen=wsprintf( szPort, ":%d", (USHORT) sPort );
                }
            }
            else
            {
                if ( sPort != QueryW3Instance()->QueryDefaultPort() )
                {
                    szPortLen=wsprintf( szPort, ":%d", (USHORT) sPort );
                }
            }

            if ( cbHostNameLength != 0 )
            {
                if (QueryClientConn()->IsSecurePort())
                {
                    APPEND_STRING(pszTail, "Content-Location: https://");
                }
                else
                {
                    APPEND_STRING(pszTail, "Content-Location: http://");
                }
                
                memcpy(pszTail, pszHostName, cbHostNameLength + 1);

                if ( szPortLen )
                {
                    memcpy(pszTail+cbHostNameLength,szPort,szPortLen+1);
                }
                
                pszTail += cbHostNameLength + szPortLen;
            }
            else
            {
                APPEND_STRING(pszTail, "Content-Location: ");
            }

            APPEND_STR_HEADER(pszTail, "", _strRawURL, "\r\n");
        }

        if (_bSendVary)
        {
            dwSizeUsed = DIFF(pszTail - pszResp);

            if ( !pbufResponse->Resize(dwSizeUsed + POST_CUSTOM_HEADERS_SIZE +
                                        sizeof("Vary: *\r\n")) )
            {
                // Couldn't resize, fail.

                return FALSE;
            }

            pszResp = (CHAR *) pbufResponse->QueryPtr();

            pszTail = pszResp + dwSizeUsed;

            APPEND_STRING(pszTail, "Vary: *\r\n");

        }
    }

    // Authentication headers -- indicate the server requests authentication

    if ( IsAuthenticationRequested() )
    {
        STR             strAuthHdrs;

        if ( !AppendAuthenticationHdrs( &strAuthHdrs,
                                        pfFinished ))
        {
            return FALSE;
        }

        if ( *pfFinished )
        {
            return TRUE;
        }
        
        if ( !QueryDenialHeaders()->IsEmpty() )
        {
            if ( !strAuthHdrs.Append( QueryDenialHeaders()->QueryStr() ) )
            {
                return FALSE;
            }
        }
        
        dwSizeUsed = DIFF(pszTail - pszResp);

        if ( !pbufResponse->Resize(dwSizeUsed + strAuthHdrs.QueryCB() + 1 ) )
        {
            // Couldn't resize, fail.

            return FALSE;
        }

        pszResp = (CHAR *) pbufResponse->QueryPtr();
        pszTail = pszResp + dwSizeUsed;

        memcpy( pszTail,
                strAuthHdrs.QueryStr(),
                strAuthHdrs.QueryCB() + 1 );
        
        pszTail += strAuthHdrs.QueryCB();
        
    }
    else if ( !_strAuthInfo.IsEmpty() )
    {
        dwSizeUsed = DIFF(pszTail - pszResp);

        if ( !pbufResponse->Resize(dwSizeUsed +
                                    sizeof("Proxy-Authenticate: \r\n") +
                                    _strAuthInfo.QueryCB()) )
        {
            // Couldn't resize, fail.

            return FALSE;
        }

        pszResp = (CHAR *) pbufResponse->QueryPtr();
        pszTail = pszResp + dwSizeUsed;

        pszTail += wsprintf( pszTail,
                             "%s: %s\r\n",
                             (IsProxyRequest() ? "Proxy-Authenticate" :
                                                 "WWW-Authenticate"),
                             _strAuthInfo.QueryStr() );
    }

    //
    //  Append headers specified by filters
    //

    if ( !QueryAdditionalRespHeaders()->IsEmpty() )
    {
        cb = QueryAdditionalRespHeaders()->QueryCB() + 1;
        cbLeft = pbufResponse->QuerySize() - DIFF(pszTail - pszResp);

        if ( cb > cbLeft )
        {
            if ( !pbufResponse->Resize( pbufResponse->QuerySize() + cb ))
            {
                return FALSE;
            }

            pszTail = (CHAR *) pbufResponse->QueryPtr() + (pszTail - pszResp);
        }

        memcpy( pszTail, QueryAdditionalRespHeaders()->QueryStr(), cb );
    }

    return TRUE;
}

BOOL WINAPI
DeleteFunc(
    CtxtHandle* pH,
    PVOID pF
)
/*++

Routine Description:

    Notification of SSPI security context destruction

Arguments:

    pH - SSPI security context
    pF - HTTP_REQ_BASE to notify

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    return ((HTTP_REQ_BASE*)pF)->NotifyRequestSecurityContextClose( pH );
}


BOOL
HTTP_REQ_BASE::SetCertificateInfo(
    IN PHTTP_FILTER_CERTIFICATE_INFO pData,
    IN CtxtHandle *pCtxt,
    IN HANDLE hPrimaryToken,
    IN HTTP_FILTER_DLL* pFilter
    )
/*++

Routine Description:

    Set SSL/PCT SSPI security context & access token

Arguments:

    pData - ptr to certificate info
    pCtxt - SSPI security context
    hPrimaryToken - access token bound to SSPI security context
    pFilter - calling filter

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    if ( pCtxt != NULL )
    {
        if ( hPrimaryToken == IIS_ACCESS_DENIED_HANDLE )
        {
            _fInvalidAccessToken = TRUE;

            return TRUE;
        }

        _pAuthFilter = NULL;

        W3_SERVER_INSTANCE *pInstance = QueryW3InstanceAggressively();

        if ( hPrimaryToken != NULL )
        {
            ResetAuth( TRUE );

            _pAuthFilter = pFilter;



            if ( !_tcpauth.SetSecurityContextToken( pCtxt,
                                                    hPrimaryToken,
                                                    DeleteFunc,
                                                    (PVOID)this,
                                                    ( pInstance ? 
                                                      pInstance->GetAndReferenceSSLInfoObj() :
                                                      NULL ) ) )
            {
                _pAuthFilter = NULL;
                return FALSE;
            }

            _strAuthType.Copy( "SSL/PCT", (sizeof( "SSL/PCT") - 1) );

            _fLoggedOn        = TRUE;
            _fAuthCert        = TRUE;
            _dwSslNegoFlags   |= SSLNEGO_MAP;

            _strPassword.Reset();

            QueryW3StatsObj()->IncrNonAnonymousUsers();

            //
            //  Get the user name
            //

            if ( !_tcpauth.QueryUserName( &_strUserName ) ||
                (_strUserName.SetLen( strlen(_strUserName.QueryStr()) ),
                 !_strUnmappedUserName.Copy( _strUserName )) )
            {
                DBGPRINTF(( DBG_CONTEXT,
                            "[SetCertificateInfo] Getting username failed, error %d\n",
                            GetLastError() ));

                return FALSE;
            }
        }
        else
        {
            if ( !_tcpauth.SetSecurityContextToken( pCtxt,
                                                    hPrimaryToken,
                                                    DeleteFunc,
                                                    (PVOID)this,
                                                    ( pInstance ? 
                                                      pInstance->GetAndReferenceSSLInfoObj() :
                                                      NULL ) ) )
            {
                return FALSE;
            }
        }

        return TRUE;
    }

    SetLastError( ERROR_INVALID_PARAMETER );

    DBG_ASSERT( FALSE );

    return FALSE;
}


BOOL
HTTP_REQ_BASE::CheckValidSSPILogin(
    VOID
    )
{
    // Check if guest account

    if ( _tcpauth.IsGuest( FALSE ) )
    {
        if ( !(QueryAuthentication() & INET_INFO_AUTH_ANONYMOUS) )
        {
            SetLastError( ERROR_LOGON_FAILURE );
            SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG );

            _fAuthenticating = FALSE;
            _fLoggedOn = FALSE;
            SetKeepConn( FALSE );

            return FALSE;
        }

        //
        // cancel current authorization & authentication
        //

        _HeaderList.FastMapCancel( HM_AUT );

        _fLoggedOn        = FALSE;
        _fInvalidAccessToken = FALSE;
        _fClearTextPass   = FALSE;
        _fAnonymous       = FALSE;
        _fAuthenticating  = FALSE;
        _fAuthTypeDigest  = FALSE;
        _fAuthSystem      = FALSE;
        _fAuthCert        = FALSE;
        _tcpauth.Reset();

        _strAuthType.Reset();
        _strUserName.Reset();
        _strPassword.Reset();
        _strUnmappedUserName.Reset();

        // return as if no authorization had been seen

        return TRUE;
    }

    QueryW3StatsObj()->IncrNonAnonymousUsers();

    //
    //  Get the user name
    //

    if ( !_tcpauth.QueryUserName( &_strUserName ) ||
         !_strUnmappedUserName.Copy( _strUserName ) )
    {
        DBGPRINTF(( DBG_CONTEXT,
                    "[OnAuthorization] Getting username failed, error %d\n",
                    GetLastError() ));

        return FALSE;
    }

    return TRUE;
}



BOOL
HTTP_REQ_BASE::CheckForBasicAuthenticationHeader(
    LPSTR pszHeaders
    )
/*++

Routine Description:

    Parse header for realm info in Basic authentication scheme

Arguments:

    pszHeaders - headers to parse

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    UINT cHeaders = strlen( pszHeaders );
    UINT cLine;
    UINT cToNext;
    LPSTR pEOL;
    int ch;

    while ( cHeaders )
    {
        if ( (pEOL = (LPSTR)memchr( pszHeaders, '\n', cHeaders)) == NULL )
        {
            cLine = cHeaders;
            cToNext = cHeaders;
        }
        else
        {
            cLine = DIFF(pEOL - pszHeaders);
            cToNext = cLine + 1;
            if ( pEOL != pszHeaders && pEOL[-1] == '\r' )
            {
                --cLine;
            }
        }

        if ( !_strnicmp( pszHeaders,
                    "WWW-Authenticate",
                    sizeof("WWW-Authenticate")-1 ) )
        {
            LPSTR pS = pszHeaders + sizeof("WWW-Authenticate:") - 1;
            UINT cS = cLine - (sizeof("WWW-Authenticate:") - 1);
            while ( cS && isspace( (UCHAR)(*pS) ) )
            {
                ++pS;
                --cS;
            }
            if ( !_strnicmp( pS, "basic", sizeof("basic")-1 ) )
            {
                while ( cS && isalpha( (UCHAR)(*pS) ) )
                {
                    ++pS;
                    --cS;
                }
                while ( cS && !isalpha( (UCHAR)(*pS) ) )
                {
                    ++pS;
                    --cS;
                }

                if ( !_strnicmp( pS, "realm", sizeof("realm")-1 ) )
                {
                    // check if realm value specified

                    while ( cS && *pS != '=' )
                    {
                        ++pS;
                        --cS;
                    }

#if 0
                    if ( cS )
                    {
                        ++pS;
                        --cS;

                        // locate start of realm value

                        while ( cS )
                        {
                            --cS;
                            if ( *pS++ == '"' )
                            {
                                break;
                            }
                        }
                        LPSTR pR = pS;

                        // locate end of realm value

                        while ( cS && *pS != '"' )
                        {
                            ++pS;
                            --cS;
                        }

                        ch = *pS;
                        *pS = '\0';
                        // pR contains zero-delimited realm
                        *pS = ch;
                    }
#endif
                    _fBasicRealm = TRUE;
                }
                return TRUE;
            }
        }

        cHeaders -= cToNext;
        pszHeaders += cToNext;
    }

    return FALSE;
}


BOOL
HTTP_REQ_BASE::LogonAsSystem(
    VOID
    )
/*++

Routine Description:

    Associate the system account with the current request
    AUTH_TYPE and LOGON_USER are set to "SYSTEM" if success

Arguments:

    None

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    HANDLE                  hTok;

    if ( _fLoggedOn )
    {
        if ( _fAnonymous ) {

            QueryW3StatsObj()->DecrCurrentAnonymousUsers();
        } else {
            QueryW3StatsObj()->DecrCurrentNonAnonymousUsers();
        }
    }

    ResetAuth( FALSE );

    if ( !IISDuplicateTokenEx( g_hSysAccToken,
            TOKEN_ALL_ACCESS,
            NULL,
            SecurityImpersonation,
            TokenPrimary,
            &hTok ))
    {
        return FALSE;
    }

    if ( _tcpauth.SetAccessToken( hTok, NULL ) )
    {
        _fLoggedOn               = TRUE;
        _fMappedAcct             = TRUE;
        _fAuthSystem             = TRUE;

        _HeaderList.FastMapCancel( HM_AUT );
        _HeaderList.FastMapCancel( HM_CON );
        SetKeepConn( FALSE );

        _strAuthType.Copy( PSZ_KWD_SYSTEM, LEN_PSZ_KWD_SYSTEM);
        _strUserName.Copy( PSZ_KWD_SYSTEM, LEN_PSZ_KWD_SYSTEM);
        _strPassword.Reset();

        QueryW3StatsObj()->IncrNonAnonymousUsers();

        //_strUnmappedUserName will contains real user name

        _fSingleRequestAuth = TRUE;

        return TRUE;
    }
    else
    {
        CloseHandle( hTok );
    }

    return FALSE;
}


GENERIC_MAPPING VrootFileGenericMapping =
{
    FILE_GENERIC_READ,
    FILE_GENERIC_WRITE,
    FILE_GENERIC_EXECUTE,
    FILE_ALL_ACCESS
};


VOID
HTTP_REQ_BASE::ReleaseCacheInfo(
    VOID
    )
/*++
    Description:

        Release ptr to URI & metadata cache

    Arguments:
        None

    Returns:
        Nothing

--*/
{
    if ( g_fGetBackTraces )
    {
        ULONG            ulHash;
        
        RtlWalkFrameChain( m_ppvFrames,
                           MAX_BACKTRACE_FRAMES,
                           0 );
    } 

    if ( _pURIInfo != NULL)
    {

        if (_pURIInfo->bIsCached)
        {
            TsCheckInCachedBlob( _pURIInfo );
        } else
        {
            TsFree(QueryW3Instance()->GetTsvcCache(), _pURIInfo );
        }

    }
    else
    {
        if (_pMetaData != NULL)
        {
            TsFreeMetaData(_pMetaData->QueryCacheInfo() );
        }
    }

    _pMetaData = NULL;
    _pURIInfo = NULL;
}


BOOL
HTTP_REQ_BASE::VrootAccessCheck(
    PW3_METADATA    pMetaData,
    DWORD           dwDesiredAccess
    )
/*++
    Description:

        Check that the current access token have access to the
        ACL of the current virtual root

    Arguments:
        pMetaData - points to metadata object
        dwDesiredAccess - access right, e.g. FILE_GENERIC_READ

    Returns:
        TRUE if access granted, FALSE if access denied

--*/
{
    DWORD         dwGrantedAccess;
    BYTE          PrivSet[400];
    DWORD         cbPrivilegeSet = sizeof(PrivSet);
    BOOL          fAccessGranted;

    if ( pMetaData && pMetaData->QueryAcl() &&
         (!::AccessCheck( pMetaData->QueryAcl(),
                          QueryImpersonationHandle(),
                          dwDesiredAccess,
                          &VrootFileGenericMapping,
                          (PRIVILEGE_SET *) &PrivSet,
                          &cbPrivilegeSet,
                          &dwGrantedAccess,
                          &fAccessGranted ) ||
          !fAccessGranted) )
    {
        SetLastError( ERROR_ACCESS_DENIED );
        return FALSE;
    }

    return TRUE;
}

BOOL
ReadEntireFile(
    CHAR        *pszFileName,
    TSVC_CACHE  &Cache,
    HANDLE      User,
    BUFFER      *pBuf,
    DWORD       *pdwBytesRead
    )
/*++
    Description:

        Read an entire file into the specified buffer.

    Arguments:
        pszFileName - File name of file to be read.
        Cache       - Tsunami cache info for file read.
        User        - User token for opening the file.
        pBuf        - Pointer to buffer to be read into.

    Returns:
        TRUE if we read it, FALSE otherwise.

--*/
{
    HANDLE                  hFile;
    SECURITY_ATTRIBUTES     sa;
    DWORD                   dwFileSize, dwFileSizeHigh;
    DWORD                   dwCurrentBufSize;
    BOOL                    bFileReadSuccess;
    DWORD                   dwBytesRead;
    const                   CHAR    *pszFile[2];
    DWORD                   dwError;
    CHAR                    szError[17];

    dwError = NO_ERROR;

    if( !g_fIsWindows95 && !::ImpersonateLoggedOnUser( User ) ) {
        dwError = GetLastError();
        hFile = INVALID_HANDLE_VALUE;
    } else {
        sa.nLength = sizeof(sa);
        sa.lpSecurityDescriptor = NULL;
        sa.bInheritHandle = FALSE;

        hFile = CreateFile(
                    pszFileName,
                    GENERIC_READ,
                    FILE_SHARE_READ | FILE_SHARE_WRITE,
                    &sa,
                    OPEN_EXISTING,
                    FILE_FLAG_SEQUENTIAL_SCAN,
                    NULL
                    );

        if( hFile == INVALID_HANDLE_VALUE ) {
            dwError = GetLastError();
        }

        if ( !g_fIsWindows95 ) {
            ::RevertToSelf();
        }
    }

    if( hFile == INVALID_HANDLE_VALUE ) {
        DWORD           dwEventType;
        WORD            dwParamCount;

        ASSERT( dwError != NO_ERROR );

        pszFile[0] = pszFileName;
        dwParamCount = 1;

        // Couldn't read the error file for some reason, so just bail out
        // now.

        if (dwError == ERROR_ACCESS_DENIED)
        {

            // Couldn't read the file due to lack of access. Log an event,
            // and map the error to invalid configuration to prevent
            // us from initiating an HTTP authentication sequence.


            dwEventType = W3_EVENT_CANNOT_READ_FILE_SECURITY;

            dwError = ERROR_INVALID_PARAMETER;


        }
        else
        {
            if ( dwError == ERROR_FILE_NOT_FOUND ||
                 dwError == ERROR_PATH_NOT_FOUND
               )
            {
                dwEventType = W3_EVENT_CANNOT_READ_FILE_EXIST;
            }
            else
            {

                dwEventType = W3_EVENT_CANNOT_READ_FILE;
                _itoa( dwError, szError, 10 );
                pszFile[1] = szError;
                dwParamCount = 2;

            }
        }

        g_pInetSvc->LogEvent(  dwEventType,
                               dwParamCount,
                               pszFile,
                               0 );

        SetLastError(dwError);

        return FALSE;
    }

    // Query the size. If it's too large, fail.

    dwFileSize = ::GetFileSize( hFile, &dwFileSizeHigh );

    if (dwFileSizeHigh != 0 ||
        dwFileSize > MAX_CUSTOM_ERROR_FILE_SIZE)
    {
        CloseHandle( hFile );

        pszFile[0] = pszFileName;
        pszFile[1] = CONST_TO_STRING(MAX_CUSTOM_ERROR_FILE_SIZE);
        g_pInetSvc->LogEvent(  W3_EVENT_CANNOT_READ_FILE_SIZE,
                               2,
                               pszFile,
                               0 );

        SetLastError( ERROR_INVALID_DATA );
        return FALSE;
    }

    dwCurrentBufSize = strlen( (CHAR *)pBuf->QueryPtr() );

    if ( !pBuf->Resize(dwCurrentBufSize + dwFileSize + 1) )
    {
        // Couldn't resize the buffer, so fail.

        CloseHandle( hFile );

        pszFile[0] = pszFileName;
        g_pInetSvc->LogEvent(  W3_EVENT_CANNOT_READ_FILE_MEMORY,
                               1,
                               pszFile,
                               0 );

        SetLastError( ERROR_INSUFFICIENT_BUFFER );
        return FALSE;
    }

    // Now read the file.

    bFileReadSuccess = ::ReadFile( hFile,
                                   (CHAR *)pBuf->QueryPtr() + dwCurrentBufSize,
                                   dwFileSize,
                                   &dwBytesRead,
                                   NULL
                                   );

    dwError = GetLastError();

    // We're done with the file, so close the handle now.

    CloseHandle( hFile );

    if (!bFileReadSuccess || (dwBytesRead != dwFileSize))
    {
        // There was some sort of error in the file read, so bail out.

        _itoa( dwError, szError, 10 );

        pszFile[0] = pszFileName;
        pszFile[1] = szError;

        g_pInetSvc->LogEvent(  W3_EVENT_CANNOT_READ_FILE,
                               2,
                               pszFile,
                               0 );
        return FALSE;
    }

    CHAR *Temp = (CHAR *)pBuf->QueryPtr();
    *(Temp + dwCurrentBufSize + dwBytesRead) = '\0';

    *pdwBytesRead = dwBytesRead;

    return TRUE;
}

BOOL
HTTP_REQ_BASE::CheckCustomError(
    BUFFER  *pBuf,
    DWORD   dwErr,
    DWORD   dwSubError,
    BOOL    *pfFinished,
    DWORD   *pdwMsgSize,
    BOOL    bCheckURL
    )
/*++
    Description:

        Check for a custom error on message or URL for a specific error. If
        we find an custom error message, fill in the buffer with the message.
        If we find a URL, we'll reprocess the URL to handle the error.

    Arguments:
        pBuf        - Pointer to buffer to fill in with error message.
        dwErr       - Error code to be checked.
        pfFinished  - Pointer to boolean. Set to TRUE if no further processing
                        is required, FALSE otherwise. If it's set to FALSE then
                        the caller still needs to send the buffer. Valid iff
                        this function returns TRUE.
        bCheckURL   - TRUE if we are to check for a URL.

    Returns:
        TRUE if there was a custom error for this error, FALSE otherwise.

--*/
{
    PCUSTOM_ERROR_ENTRY     pErrEntry;
    DWORD                   dwLogHttpResponse;
    DWORD                   dwLogWinError;


    // First make sure we're not already processing a custom error.

    if (_bProcessingCustomError)
    {
        return FALSE;
    }

    // Make sure we've got MetaData.
    //
    // CODEWORK - Fix this so we try and find the metadata for the the root VR
    // in this case. We'll need to fix FindAndReferenceInstance etc. We'll
    // still have to fail if we don't have an instance.

    if (QueryMetaData() == NULL)
    {
        return FALSE;
    }

    // Now lookup the error code to see if we have a custom error.

    pErrEntry = QueryMetaData()->LookupCustomError(dwErr, dwSubError);

    if (pErrEntry == NULL)
    {
        // No custom error, return FALSE.

        return FALSE;
    }

    if (pErrEntry->IsFileError())
    {
        STR         strMimeType;
        DWORD       dwCurrentSize;
        DWORD       dwSizeNeeded;
        CHAR        *pszHeader;

        if (!SelectMimeMappingForFileExt(g_pInetSvc,
                                        (const CHAR *)pErrEntry->QueryErrorFileName(),
                                        &strMimeType
                                        ))
        {
            DBGPRINTF(( DBG_CONTEXT,
                    "CheckCustomError: Unable to get MIME type for %s\n",
                    pErrEntry->QueryErrorFileName()));

            return FALSE;
        }

        dwCurrentSize = strlen((CHAR *)pBuf->QueryPtr());
        dwSizeNeeded = dwCurrentSize + 1 + sizeof("Content-Type: ") - 1 +
            strMimeType.QueryCB() + sizeof("\r\n\r\n") - 1;

        if ((pBuf->QuerySize() < dwSizeNeeded) && !pBuf->Resize(dwSizeNeeded))
        {
            DBGPRINTF(( DBG_CONTEXT,
                    "CheckCustomError: Buffer too small and unable to resize\n"));

            return FALSE;

        }

        pszHeader = (CHAR *)pBuf->QueryPtr() + dwCurrentSize;

        APPEND_PSZ_HEADER(pszHeader, "Content-Type: ", strMimeType.QueryStr(), "\r\n\r\n");

        if (!ReadEntireFile(pErrEntry->QueryErrorFileName(),
                            QueryW3Instance()->GetTsvcCache(),
                            g_hSysAccToken,
                            pBuf,
                            pdwMsgSize))
        {
            // Have to undo the copy in we did.

            pszHeader = (CHAR *)pBuf->QueryPtr() + dwCurrentSize;
            *pszHeader = '\0';

            return FALSE;
        }

        // Otherwise it worked.

        *pfFinished = FALSE;
        return TRUE;


    }
    else
    {
        // This must be a custom URL. Create a new URL from the custom error
        // that includes the error code as a parameter, and reprocess the URL.

        HTTP_REQUEST    *pReq;
        CHAR            *Temp = (CHAR *)pBuf->QueryPtr();
        STR             strNewURL;
        CHAR            szError[20];
        enum CLIENT_CONN_STATE OldConnState;
        CHAR            *pszHostName;
        DWORD           cbHostNameLength;

        if (!bCheckURL)
        {
            return FALSE;
        }

        if (!strNewURL.Copy( (const CHAR *)pErrEntry->QueryErrorURL() ) )
        {
            return FALSE;
        }

        //
        // Remote URLs are not allowed as custom errors.
        //

        if ( !_strnicmp(strNewURL.QueryStr(),"http://",sizeof("http://")-1) ||
             !_strnicmp(strNewURL.QueryStr(),"https://",sizeof("https://") - 1) )
        {
            DBGPRINTF(( DBG_CONTEXT,
                    "CheckCustomError: URL is not Local. Returnig Failure\n"));
                    
            return FALSE;
        }
        
        if (!strNewURL.Append("?", sizeof("?") - 1))
        {
            return FALSE;
        }

        _itoa(dwErr, szError, 10);

        if (!strNewURL.Append( (const CHAR *)szError) )
        {
            return FALSE;
        }

        if (!strNewURL.Append(";", sizeof(";") - 1))
        {
            return FALSE;
        }

        pszHostName = QueryHostAddr();
        cbHostNameLength = strlen(pszHostName);

        if (cbHostNameLength != 0)
        {
            if (!strNewURL.Append("http://", sizeof("http://") -1) ||
                !strNewURL.Append(pszHostName, cbHostNameLength))
            {
                return FALSE;
            }
        }

        if (!strNewURL.Append(_strRawURL))
        {
            return FALSE;
        }

        *Temp = '\0';

        _bProcessingCustomError = TRUE;

        SetNoCache();
        SetSendVary();

        pReq = (HTTP_REQUEST *)this;

        pReq->CloseGetFile();

        //
        // Need to set the connection state to processing, because we might be
        // disconnecting right now. Save the old state in case the reprocess
        // fails.

        OldConnState = QueryClientConn()->QueryState();

        QueryClientConn()->SetState(CCS_PROCESSING_CLIENT_REQ);

        dwLogHttpResponse = QueryLogHttpResponse();
        dwLogWinError = QueryLogWinError();

        if ( !pReq->ReprocessURL(   strNewURL.QueryStr(),
                                    HTV_GET ))
        {
            //
            // if we failed & state is DOVERB restore to DONE to prevent
            // response from being generated twice (once here and once after doverb
            // processing in DoWork() )
            //

            if ( QueryState() == HTR_DOVERB )
            {
                SetState( HTR_DONE, dwLogHttpResponse, dwLogWinError );
            }

            QueryClientConn()->SetState(OldConnState);
            return FALSE;
        }

        *pfFinished = TRUE;
        return TRUE;
    }

}


BOOL
HTTP_REQ_BASE::IsClientProxy(
    VOID
    )
/*++
    Description:

        Check if request was issued by a proxy, as determined by following rules :
        - "Via:" header is present (HTTP/1.1)
        - "Forwarded:" header is present (some HTTP/1.0 implementations)
        - "User-Agent:" contains "via ..." (CERN proxy)

    Arguments:
        None

    Returns:
        TRUE if client request was issued by proxy

--*/
{
    LPSTR   pUA;
    UINT    cUA;
    LPSTR   pEnd;


    if ( _HeaderList.FastMapQueryValue( HM_VIA ) != NULL ||
         _HeaderList.FastMapQueryValue( HM_FWD ) != NULL )
    {
        return TRUE;
    }

    if ( !IsAtLeastOneOne() &&
         (pUA = (LPSTR)_HeaderList.FastMapQueryValue( HM_UAT )) != NULL )
    {
        cUA = strlen( pUA );
        pEnd = pUA + cUA - (sizeof("ia ")-1);

        //
        // scan for "[Vv]ia[ :]" in User-Agent: header
        //

        while ( pUA < pEnd )
        {
            if ( *pUA == 'V' || *pUA == 'v' )
            {
                if ( pUA[1] == 'i' &&
                     pUA[2] == 'a' &&
                     (pUA[3] == ' ' || pUA[3] == ':') )
                {
                    return TRUE;
                }
            }
            ++pUA;
        }
    }

    return FALSE;
}

W3_SERVER_INSTANCE* HTTP_REQ_BASE::QueryW3InstanceAggressively( VOID ) const
/*++
    Description:

        This tries "aggressively" to find the instance associated with this request.
        If the instance is already set, it returns that; otherwise, it tries to find
        an instance matching the IP/Port # for the connection associated with this
        request. 

        This is useful for requests that won't use Host headers eg SSL requests, where
        it may be necessary to determine the instance for a request before it's been
        set by the URL-parsing code.

        Note that the function -DOES NOT- update the _pW3Instance member.

    Arguments:
        None

    Returns:
       Pointer to associated instance if found, NULL if no instance was found.

--*/
{
    W3_SERVER_INSTANCE *pInstance = NULL;

    if ( _pW3Instance )
    {
        pInstance = _pW3Instance;
    }
    else
    {
        BOOL fExceeded ;
        
        //
        // Try specific (IP, port)  first, then try to wildcard the IP address. 
        // Port # can't be wildcarded.
        //
        
        IIS_ENDPOINT *pEndPoint = g_pInetSvc->FindAndReferenceEndpoint( 
                                                         _pClientConn->QueryPort(),
                                                         _pClientConn->QueryLocalIPAddress(),
                                                         FALSE,
                                                         FALSE );
        
        if ( !pEndPoint )
        {
            pEndPoint = g_pInetSvc->FindAndReferenceEndpoint( _pClientConn->QueryPort(),
                                                              0,
                                                              FALSE,
                                                              FALSE );
        }

        if ( pEndPoint )
        {
            pInstance = (W3_SERVER_INSTANCE *) pEndPoint->FindAndReferenceInstance(       
                                                             NULL,
                                                             _pClientConn->QueryLocalIPAddress(),
                                                             &fExceeded );
            
            //
            // FindAndReferenceInstance increments # of connections, and we're not
            // using up a connection
            //
            if ( pInstance )
            {
                pInstance->DecrementCurrentConnections(); 
                
                pInstance->Dereference();
                

            }
            pEndPoint->Dereference();
        }
    }

    return ( pInstance );
}