/*++




Copyright (c) 1994  Microsoft Corporation

Module Name:

    httpfilt.cxx

Abstract:

    This module contains the Microsoft HTTP server filter module

Author:

    John Ludeman (johnl)   31-Jan-1995

Revision History:

--*/

#include "w3p.hxx"

//
// Maximum allowable cached buffer
//

#define MAX_CACHED_FILTER_BUFFER    (8 * 1024)

//
//  Maximum number of filters we allow (per-instance - includes global filters)
//

#define MAX_FILTERS                 50

//
//  Private globals.
//

BOOL
WINAPI
ServerFilterCallback(
    struct _HTTP_FILTER_CONTEXT * pfc,
    enum SF_REQ_TYPE              se,
    void *                        pData,
    ULONG_PTR                     ul,
    ULONG_PTR                     ul2
    );

BOOL
WINAPI
GetServerVariable(
    struct _HTTP_FILTER_CONTEXT * pfc,
    LPSTR                         lpszVariableName,
    LPVOID                        lpvBuffer,
    LPDWORD                       lpdwSize
    );

BOOL
WINAPI
WriteFilterClient(
    struct _HTTP_FILTER_CONTEXT * pfc,
    LPVOID                        Buffer,
    LPDWORD                       lpdwBytes,
    DWORD                         dwReserved
    );

VOID *
WINAPI
AllocFilterMem(
    struct _HTTP_FILTER_CONTEXT * pfc,
    DWORD                         cbSize,
    DWORD                         dwReserved
    );

BOOL
WINAPI
ServerSupportFunction(
    struct _HTTP_FILTER_CONTEXT * pfc,
    enum SF_REQ_TYPE              sfReq,
    PVOID                         pData,
    ULONG_PTR                     ul1,
    ULONG_PTR                     ul2
    );

BOOL
WINAPI
AddFilterResponseHeaders(
    HTTP_FILTER_CONTEXT * pfc,
    LPSTR                 lpszHeaders,
    DWORD                 dwReserved
    );

VOID
FilterAtqCompletion(
    PVOID        Context,
    DWORD        BytesWritten,
    DWORD        CompletionStatus,
    OVERLAPPED * lpo
    );

VOID
ContinueRawRead(
    PVOID        Context,
    DWORD        BytesWritten,
    DWORD        CompletionStatus,
    OVERLAPPED * lpo
    );

BOOL
WINAPI
CompressionFilterCheck(
    HTTP_FILTER_CONTEXT *pfc,
    LPVOID              lpszEncodingString,
    ULONG_PTR           lpszVerbString,
    ULONG_PTR           sizesForBuffers
    );


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

BOOL
HTTP_FILTER::NotifyRequestSecurityContextClose(
    HTTP_FILTER_DLL * pFilterDLL,
    CtxtHandle *      pCtxt
    )
{
    HTTP_FILTER_REQUEST_CLOSE_SECURITY_CONTEXT hfcc;
    HTTP_FILTER_CONTEXT *   phfc;
    HTTP_FILTER_DLL *       pFilt;
    FILTER_LIST *           pFilterList;
    SF_STATUS_TYPE          sfStatus = SF_STATUS_REQ_HANDLED_NOTIFICATION;
    DWORD                   i;

    hfcc.pCtxt = (PVOID)pCtxt;

    phfc                     = QueryContext();
    phfc->fIsSecurePort      = QueryReq()->IsSecurePort();

    pFilterList = QueryFilterList();

    //
    //  If this filter doesn't support this type of notification
    //  then ignore it
    //

    if ( !pFilterDLL->IsNotificationNeeded(
             SF_NOTIFY_REQUEST_SECURITY_CONTEXT_CLOSE,
             phfc->fIsSecurePort ))
    {
        //
        //  This filter doesn't support this type of notification.
        //

        return FALSE;
    }

    //
    //  This request is targetted at a particular DLL so find the filter
    //  context in the filter list
    //

    for ( i = 0; i < pFilterList->QueryFilterCount(); i++ )
    {
        if ( pFilterDLL == pFilterList->QueryDll( i ) )
        {
            phfc->pFilterContext = QueryClientContext( i );

            sfStatus = (SF_STATUS_TYPE)
                       pFilterDLL->QueryEntryPoint()( phfc,
                                                      SF_NOTIFY_REQUEST_SECURITY_CONTEXT_CLOSE,
                                                      &hfcc );

            SetClientContext( i, phfc->pFilterContext );
        }

        break;
    }

    return sfStatus == SF_STATUS_REQ_HANDLED_NOTIFICATION;
}


BOOL
HTTP_FILTER::NotifyRawReadDataFilters(
    VOID *          pvInData,
    DWORD           cbInData,
    DWORD           cbInBuffer,
    VOID * *        ppvOutData,
    DWORD *         pcbOutData,
    BOOL *          pfRequestFinished,
    BOOL *          pfReadAgain
    )
/*++

Routine Description:

    This method handles notification of all filters that handle the
    raw data notifications.

    Note this is the only routine that needs to save phfc->pFilterContext
    because this is the only notification that can get occur while we're
    in another notification.

Arguments:

    pvInData - Raw data
    cbInData - count of bytes of raw data
    cbInBuffer - Size of input buffer
    ppvOutData - Receives pointer to buffer of translated data
    pcbOutData - Number of bytes of translated data
    pfRequestFinished - Set to TRUE if the filter completed request processing
    pfReadAgain - Set to TRUE if the caller should issue another read and
        call this routine again

Return Value:

    TRUE if successful, FALSE on error

--*/
{
    HTTP_FILTER_RAW_DATA    hfrd;
    HTTP_FILTER_CONTEXT *   phfc;
    HTTP_FILTER_DLL *       pFilterDLL;
    FILTER_LIST *           pFilterList;
    SF_STATUS_TYPE          sfStatus;
    VOID *                  pvClientContext;
    DWORD                   CurrentDll;
    DWORD                   i;
    PVOID                   pvtmp;

    //
    //  Don't notify on zero length writes
    //

    if ( !cbInData )
    {
        *ppvOutData        = pvInData;
        *pcbOutData        = cbInData;
        return TRUE;
    }

    //
    //  Fill out the raw read structure
    //

    hfrd.pvInData        = pvInData;
    hfrd.cbInData        = cbInData;
    hfrd.cbInBuffer      = cbInBuffer;

    //
    //  Increment the nested notification level
    //

    _cRawNotificationLevel++;

    //
    //  Initialize items specific to this request
    //

    phfc                     = QueryContext();
    phfc->fIsSecurePort      = QueryReq()->IsSecurePort();

    //
    //  Save the current client context in the filter structure and the
    //  current dll because we may be in the middle of another filter
    //  notification
    //

    pvClientContext  = phfc->pFilterContext;
    CurrentDll =       QueryCurrentDll();
    pFilterList =      QueryFilterList();

    //
    //  If a filter needs to do a WriteClient in the middle of a raw data
    //  notification, we only notify the filters down (or up) the chain
    //

    i = (CurrentDll == INVALID_DLL ? 0 : CurrentDll);

    //
    //  For recv operations, walk the list in order so encryption filters
    //  are at the front of the list
    //


    for ( ; i < pFilterList->QueryFilterCount(); i++ )
    {
        pFilterDLL = pFilterList->QueryDll( i );

        //
        // Notification flags are cached in the HTTP_FILTER object, but they're
        // only copied from the actual HTTP_FILTER_DLL object if a filter dll
        // disables a particular notification [sort of a copy-on-write scheme].
        // If a filter dll disables/changes a notification, we need to check the flags
        // in the HTTP_FILTER object, not those in the HTTP_FILTER_DLL object
        //
        // NOTE : This code is also executed in the functions
        // HTTP_FILTER::NotifyRawSendDataFilters and HTTP_FILTER::NotifyFilters, so
        // any changes/corrections need to be made there as well. The reason it wasn't
        // broken out into a function is that this code is on the main-line code path
        // for each request and making a function call for each filter loaded would
        // be an efficiency hit.
        //

        if ( !QueryNotificationChanged() )
        {
            if (! pFilterDLL->IsNotificationNeeded(SF_NOTIFY_READ_RAW_DATA,
                                                   phfc->fIsSecurePort ))
            {
                //
                //  This filter doesn't support this type of notification.
                //

                continue;
            }
        }
        else
        {
            if ( !IsDisableNotificationNeeded( i,
                                               SF_NOTIFY_READ_RAW_DATA,
                                               phfc->fIsSecurePort ))
            {
                //
                //  This filter doesn't support this type of notification.
                //

                continue;
            }
        }


        SetCurrentDll( i );

        pvtmp = phfc->pFilterContext = QueryClientContext( i );

        sfStatus = (SF_STATUS_TYPE)
                   pFilterDLL->QueryEntryPoint()( phfc,
                                                  SF_NOTIFY_READ_RAW_DATA,
                                                  &hfrd );
        if ( pvtmp != phfc->pFilterContext )
            SetClientContext( i, phfc->pFilterContext );

        switch ( sfStatus )
        {
        default:
            DBGPRINTF((DBG_CONTEXT,
                       "[NotifyRawReadDataFilters] Unknown status code from filter %d\n",
                       sfStatus ));
            //
            //  Fall through
            //

        case SF_STATUS_REQ_NEXT_NOTIFICATION:
            continue;

        case SF_STATUS_REQ_ERROR:
            SetCurrentDll( CurrentDll );
            _cRawNotificationLevel--;
            phfc->pFilterContext = pvClientContext;
            return FALSE;

        case SF_STATUS_REQ_FINISHED:
        case SF_STATUS_REQ_FINISHED_KEEP_CONN:  // Not supported for raw data
            QueryReq()->Disconnect();
            *pfRequestFinished = TRUE;
            goto Exit;

        case SF_STATUS_REQ_HANDLED_NOTIFICATION:

            //
            //  Don't notify any other filters
            //

            goto Exit;

        case SF_STATUS_REQ_READ_NEXT:

            *pfReadAgain = TRUE;
            goto Exit;
        }
    }

Exit:
    *ppvOutData        = hfrd.pvInData;
    *pcbOutData        = hfrd.cbInData;

    phfc->pFilterContext    = pvClientContext;
    _cRawNotificationLevel--;
    SetCurrentDll( CurrentDll );

    return TRUE;
}

BOOL
HTTP_FILTER::NotifyRawSendDataFilters(
    VOID *          pvInData,
    DWORD           cbInData,
    DWORD           cbInBuffer,
    VOID * *        ppvOutData,
    DWORD *         pcbOutData,
    BOOL *          pfRequestFinished
    )
/*++

Routine Description:

    This method handles notification of all filters that handle the
    raw data notifications.

    Note this is the only routine that needs to save phfc->pFilterContext
    because this is the only notification that can get occur while we're
    in another notification.

Arguments:

    pvInData - Raw data
    cbInData - count of bytes of raw data
    cbInBuffer - Size of input buffer
    ppvOutData - Receives pointer to buffer of translated data
    pcbOutData - Number of bytes of translated data
    pfRequestFinished - Set to TRUE if the filter completed request processing

Return Value:

    TRUE if successful, FALSE on error

--*/
{
    HTTP_FILTER_RAW_DATA    hfrd;
    HTTP_FILTER_CONTEXT *   phfc;
    HTTP_FILTER_DLL *       pFilterDLL;
    FILTER_LIST *           pFilterList;
    DWORD                   err;
    SF_STATUS_TYPE          sfStatus;
    VOID *                  pvClientContext;
    DWORD                   CurrentDll;
    DWORD                   i;
    PVOID                   pvtmp;

    //
    //  Don't notify on zero length writes
    //

    if ( !cbInData )
    {
        *ppvOutData = pvInData;
        *pcbOutData = cbInData;
        return TRUE;
    }

    pFilterList = QueryFilterList();

    //
    //  Fill out the raw send structure
    //

    hfrd.pvInData        = pvInData;
    hfrd.cbInData        = cbInData;
    hfrd.cbInBuffer      = cbInBuffer;

    //
    //  Increment the nested notification level
    //

    _cRawNotificationLevel++;

    //
    //  Initialize items specific to this request
    //

    phfc                     = QueryContext();
    phfc->fIsSecurePort      = QueryReq()->IsSecurePort();

    //
    //  Save the current client context in the filter structure and the
    //  current dll in the because we may be in the middle of another filter
    //  notification
    //

    pvClientContext  = phfc->pFilterContext;
    CurrentDll =       QueryCurrentDll();
    pFilterList =      QueryFilterList();

    //
    //  If a filter needs to do a WriteClient in the middle of a raw data
    //  notification, we only notify the filters up the chain
    //

    i = (CurrentDll == INVALID_DLL ? pFilterList->QueryFilterCount() - 1
                                            : CurrentDll) ;

    //
    //  For send operations, walk the list in reverse so encryption filters
    //  are at the end of the list
    //

    do {

        pFilterDLL = pFilterList->QueryDll( i );


        //
        // Notification flags are cached in the HTTP_FILTER object, but they're
        // only copied from the actual HTTP_FILTER_DLL object if a filter dll
        // disables a particular notification [sort of a copy-on-write scheme].
        // If a filter dll disables/changes a notification, we need to check the flags
        // in the HTTP_FILTER object, not those in the HTTP_FILTER_DLL object
        //
        // NOTE : This code is also executed in the functions
        // HTTP_FILTER::NotifyRawReadDataFilters and HTTP_FILTER::NotifyFilters, so
        // any changes/corrections need to be made there as well. The reason it wasn't
        // broken out into a function is that this code is on the main-line code path
        // for each request and making a function call for each filter loaded would
        // be an efficiency hit.
        //


        if ( !QueryNotificationChanged() )
        {
            if (! pFilterDLL->IsNotificationNeeded(SF_NOTIFY_SEND_RAW_DATA,
                                                   phfc->fIsSecurePort ))
            {
                //
                //  This filter doesn't support this type of notification.
                //

                continue;
            }
        }
        else
        {
            if ( !IsDisableNotificationNeeded( i,
                                               SF_NOTIFY_SEND_RAW_DATA,
                                               phfc->fIsSecurePort ))
            {
                //
                //  This filter doesn't support this type of notification.
                //

                continue;
            }
        }

        SetCurrentDll( i );

        pvtmp = phfc->pFilterContext = QueryClientContext( i );

        sfStatus = (SF_STATUS_TYPE)
                   pFilterDLL->QueryEntryPoint()( phfc,
                                                  SF_NOTIFY_SEND_RAW_DATA,
                                                  &hfrd );

        if ( pvtmp != phfc->pFilterContext )
            SetClientContext( i, phfc->pFilterContext );

        switch ( sfStatus )
        {
        default:
            DBGPRINTF((DBG_CONTEXT,
                       "[NotifyRawSendDataFilters] Unknown status code from filter %d\n",
                       sfStatus ));
            //
            //  Fall through
            //

        case SF_STATUS_REQ_NEXT_NOTIFICATION:
            continue;

        case SF_STATUS_REQ_ERROR:
            SetCurrentDll( CurrentDll );
            _cRawNotificationLevel--;
            phfc->pFilterContext = pvClientContext;
            return FALSE;

        case SF_STATUS_REQ_FINISHED:
        case SF_STATUS_REQ_FINISHED_KEEP_CONN:  // Not supported for raw data
            QueryReq()->Disconnect();
            *pfRequestFinished = TRUE;
            goto Exit;

        case SF_STATUS_REQ_HANDLED_NOTIFICATION:

            //
            //  Don't notify any other filters
            //

            goto Exit;
        }
    } while ( i-- > 0 );


Exit:
    *ppvOutData        = hfrd.pvInData;
    *pcbOutData        = hfrd.cbInData;

    phfc->pFilterContext    = pvClientContext;
    _cRawNotificationLevel--;
    SetCurrentDll( CurrentDll );

    return TRUE;
}


BOOL
HTTP_FILTER::NotifyRequestRenegotiate(
    HTTP_FILTER * pFilter,
    LPBOOL pfAccepted,
    BOOL fMapCert
    )
/*++

Routine Description:


Arguments:

    pFilter - Pointer to filter object
    pAccepted - updated with TRUE if request accepted

Return Value:

    TRUE if successful, FALSE on error

--*/
{
    HTTP_FILTER_CONTEXT *       phfc;
    HTTP_FILTER_DLL *           pFilterDLL;
    SF_STATUS_TYPE              sfStatus;
    HTTP_FILTER_REQUEST_CERT    hfrc;
    DWORD                       i;
    FILTER_LIST *               pFilterList;
    PVOID                       pvtmp;

    //
    //  Increment the nested notification level
    //

    pFilter->_cRawNotificationLevel++;

    //
    //  Initialize items specific to this request
    //

    phfc                     = pFilter->QueryContext();
    phfc->fIsSecurePort      = pFilter->QueryReq()->IsSecurePort();
    phfc->ulReserved         = 0;


    pFilterList =      QueryFilterList();

    hfrc.fMapCert = fMapCert;
    hfrc.dwReserved = 0;

    for ( i = 0; i < pFilterList->QueryFilterCount(); i++ )
    {
        pFilterDLL = pFilterList->QueryDll( i );

        //
        //  Skip this DLL if it doesn't want this notification
        //

        if ( !pFilterDLL->IsNotificationNeeded( SF_NOTIFY_RENEGOTIATE_CERT,
                                                phfc->fIsSecurePort ))
        {
            continue;
        }

        pFilter->SetCurrentDll( i );

        pvtmp = phfc->pFilterContext = pFilter->QueryClientContext( i );

        sfStatus = (SF_STATUS_TYPE)
                   pFilterDLL->QueryEntryPoint()( phfc,
                                                  SF_NOTIFY_RENEGOTIATE_CERT,
                                                  &hfrc );

        if ( pvtmp != phfc->pFilterContext )
            pFilter->SetClientContext( i, phfc->pFilterContext );

        switch ( sfStatus )
        {
        default:
            DBGPRINTF((DBG_CONTEXT,
                       "[NotifyRenegoCert] Unknown status code from filter %d\n",
                       sfStatus ));
            //
            //  Fall through
            //

        case SF_STATUS_REQ_NEXT_NOTIFICATION:
            continue;

        case SF_STATUS_REQ_ERROR:
            pFilter->SetCurrentDll( INVALID_DLL );
            pFilter->_cRawNotificationLevel--;
            return FALSE;

        case SF_STATUS_REQ_FINISHED:            // not supported
        case SF_STATUS_REQ_FINISHED_KEEP_CONN:  // Not supported at this point

            pFilter->QueryReq()->SetKeepConn( FALSE );
            goto Exit;

        case SF_STATUS_REQ_HANDLED_NOTIFICATION:

            //
            //  Don't notify any other filters
            //

            *pfAccepted = hfrc.fAccepted;

            goto Exit;
        }
    }

Exit:
    pFilter->SetCurrentDll( INVALID_DLL );
    pFilter->_cRawNotificationLevel--;

    return TRUE;
}


BOOL
HTTP_FILTER::NotifyAccessDenied(
    const CHAR *  pszURL,
    const CHAR *  pszPhysicalPath,
    BOOL *        pfFinished
    )
/*++

Routine Description:

    This method handles notification of all filters that handle the
    access denied notification

Arguments:

    pszURL - URL that was target of request
    pszPath - Physical path the URL mapped to
    pfFinished - Set to TRUE if no further processing is required

Return Value:

    TRUE if successful, FALSE on error

--*/
{
    HTTP_FILTER_ACCESS_DENIED   hfad;
    HTTP_FILTER_CONTEXT *       phfc;
    BOOL                        fRet;

    //
    //  If these flags are not set, then somebody hasn't indicated the reason
    //  for denying the user access
    //

    DBG_ASSERT( QueryDeniedFlags() != 0 );

    //
    //  Ignore the notification of a send "401 ..." if this notification
    //  generated it
    //

    if ( _fInAccessDeniedNotification )
    {
        return TRUE;
    }

    _fInAccessDeniedNotification = TRUE;

    //
    //  Fill out the url map structure
    //

    hfad.pszURL          = pszURL;
    hfad.pszPhysicalPath = pszPhysicalPath;
    hfad.dwReason        = QueryDeniedFlags();

    //
    //  Initialize items specific to this request
    //

    phfc                     = QueryContext();
    phfc->fIsSecurePort      = QueryReq()->IsSecurePort();

    fRet = NotifyFilters(
               SF_NOTIFY_ACCESS_DENIED,
               phfc,
               &hfad,
               pfFinished,
               FALSE );

    _fInAccessDeniedNotification = FALSE;

    return fRet;
}

BOOL
HTTP_FILTER::NotifySendHeaders( const CHAR * pszHeaderList,
                                BOOL *       pfFinished,
                                BOOL *       pfAnyChanges,
                                BUFFER *     pChangeBuff )
{
    HTTP_FILTER_SEND_RESPONSE   hfph;
    BOOL                        fRet;
    DWORD                       cbJump;

    hfph.GetHeader    = GetSendHeader;
    hfph.SetHeader    = SetSendHeader;
    hfph.AddHeader    = AddSendHeader;

    _pchSendHeaders   = pszHeaderList;

    // When parsing out the status code, beware of bad response buffers:
    //  - empty buffers (in case of HTTP 0.x requests)
    //  - buffers not starting with HTTP/X.X
    //

    // first line is the status

    cbJump = sizeof( "HTTP/X.X" );
    if ( strlen( pszHeaderList ) > ( cbJump - 1 ) )
    {
        pszHeaderList += cbJump;
    }

    while ( *pszHeaderList != '\0' &&
            *pszHeaderList != '\r' &&
            *pszHeaderList != '\n' &&
            !isdigit( (UCHAR)(*pszHeaderList) ))
    {
        pszHeaderList++;
    }

    // If for some reason, there was no version in string,
    // atoi() returns 0 on the empty string, which I guess is OK.

    hfph.HttpStatus = atoi( pszHeaderList );

    fRet = NotifyFilters( SF_NOTIFY_SEND_RESPONSE,
                          QueryContext(),
                          &hfph,
                          pfFinished,
                          FALSE );

    if ( pfAnyChanges )
    {
        *pfAnyChanges = _fSendHeadersChanged;
    }

    if ( fRet && _fSendHeadersChanged )
    {
        fRet = BuildNewSendHeaders( pChangeBuff );
    }

    //
    // Make sure we reparse the headers
    //
    SetSendHeadersParsed(FALSE);
    _SendHeaders.Reset();

    return fRet;
}

BOOL
FILTER_LIST::NotifyFilters(
    HTTP_FILTER *         pFilter,
    DWORD                 NotificationType,
    HTTP_FILTER_CONTEXT * phfc,
    PVOID                 NotificationData,
    BOOL *                pfFinished,
    BOOL                  fNotifyAll
    )
/*++

Routine Description:

    Notifies all registered filters on this filter list for the specified
    notification type

Arguments:

    pFilter - Pointer to filter making this request
    NotificationType - SF_NOTIFY_ flag indicating the notification type
    pfc - Pointer to filter context
    NotificationData - Pointer to notification specific structure
    fNotifyAll - If TRUE, always notify all filters regardless of return code
        from filter

Return Value:

    TRUE on success, FALSE on failure (call GetLastError)

--*/
{
    HTTP_FILTER_DLL *           pFilterDLL;
    DWORD                       err;
    SF_STATUS_TYPE              sfStatus;
    DWORD                       i;
    PVOID                       pvtmp;
    PVOID                       pvCurrentClientContext;

    //
    // In certain cases, we can send a notification to a filter while we're still
    // processing another filter's notification. In that case, we need to make sure
    // we restore the current filter's context when we're done with the notifications
    //
    pvCurrentClientContext = phfc->pFilterContext;

    phfc->fIsSecurePort  = pFilter->QueryReq()->IsSecurePort();

    for ( i = 0; i < m_cFilters; i++ )
    {
        pFilterDLL = QueryDll( i );

        //
        // Notification flags are cached in the HTTP_FILTER object, but they're
        // only copied from the actual HTTP_FILTER_DLL object if a filter dll
        // disables a particular notification [sort of a copy-on-write scheme].
        // If a filter dll disables/changes a notification, we need to check the flags
        // in the HTTP_FILTER object, not those in the HTTP_FILTER_DLL object
        //
        // NOTE : This code is also executed in the functions
        // HTTP_FILTER::NotifyRawSendDataFilters and HTTP_FILTER::NotifyRawReadDataFilters, so
        // any changes/corrections need to be made there as well. The reason it wasn't
        // broken out into a function is that this code is on the main-line code path
        // for each request and making a function call for each filter loaded would
        // be an efficiency hit.
        //

        if ( !pFilter->QueryNotificationChanged() )
        {
            if ( !pFilterDLL->IsNotificationNeeded( NotificationType,
                                                    phfc->fIsSecurePort ))
            {
                //
                //  This filter doesn't support this type of notification.
                //

                continue;
            }
        }
        else
        {
            if ( !pFilter->IsDisableNotificationNeeded( i,
                                                        NotificationType,
                                                        phfc->fIsSecurePort ) )
            {
                continue;
            }
        }

        pFilter->SetCurrentDll( i );

        pvtmp = phfc->pFilterContext = pFilter->QueryClientContext( i );

        sfStatus = (SF_STATUS_TYPE)
                   pFilterDLL->QueryEntryPoint()( phfc,
                                                  NotificationType,
                                                  NotificationData );

        if ( pvtmp != phfc->pFilterContext )
            pFilter->SetClientContext( i, phfc->pFilterContext );

        switch ( sfStatus )
        {
        default:
            DBGPRINTF((DBG_CONTEXT,
                       "[NotifyFilters] Unknown status code from filter %d\n",
                       sfStatus ));
            //
            //  Fall through
            //

        case SF_STATUS_REQ_NEXT_NOTIFICATION:
            continue;

        case SF_STATUS_REQ_ERROR:
            phfc->pFilterContext = pvCurrentClientContext;
            pFilter->SetCurrentDll( INVALID_DLL );
            return FALSE;

        case SF_STATUS_REQ_FINISHED:
        case SF_STATUS_REQ_FINISHED_KEEP_CONN:  // Not supported at this point

            pFilter->QueryReq()->SetKeepConn( FALSE );
            *pfFinished = TRUE;
            goto Exit;

        case SF_STATUS_REQ_HANDLED_NOTIFICATION:

            //
            //  Don't notify any other filters
            //

            goto Exit;
        }
    }

Exit:
    //
    // Reset the filter context we came in with
    //
    phfc->pFilterContext = pvCurrentClientContext;

    pFilter->SetCurrentDll( INVALID_DLL );

    return TRUE;
}

VOID FILTER_LIST::SetNotificationFlags(
    DWORD i,
    HTTP_FILTER_DLL * pFilterDll
    )
/*++

Routine Description:

    Adjusts the filter flags in the filter list. Note this is only safe on
    global filters, not per website filters.

Arguments:

    i - index of filter dll that is being adjusted
    pFilterDll - Pointer to filter dll that contains the updated list

Return Value:

--*/
{
    DWORD dwSec = 0;
    DWORD dwNonSec = 0;

    //
    //  Reset the flags in the array then rebuild the set of flags for
    //  the filter list.  Build the set in a temporary so the filter list
    //  gets updated in a single action so other filters won't be impacted
    //

    ((DWORD*) m_buffSecureArray.QueryPtr())[i] = pFilterDll->QuerySecureFlags();
    ((DWORD*) m_buffNonSecureArray.QueryPtr())[i] = pFilterDll->QueryNonsecureFlags();

    for (DWORD j = 0; j < m_cFilters; j++)
    {
        dwSec    |= QueryDll(j)->QuerySecureFlags();
        dwNonSec |= QueryDll(j)->QueryNonsecureFlags();
    }

    m_SecureNotifications = dwSec;
    m_NonSecureNotifications = dwNonSec;
}

HTTP_FILTER_DLL* FILTER_LIST::HasFilterDll(
    HTTP_FILTER_DLL *pFilterDll
    )
/*++

Routine Description:

    Checks whether this filter list contains a specific filter dll


Arguments:

    pFilterDll - filter dll to look for

Return Value:

    Pointer to matching HTTP_FILTER_DLL object if found, NULL otherwise.

    [This is probably redundant, since HTTP_FILTER_DLL objects are unique ...]

--*/
{
    HTTP_FILTER_DLL **apFilterDll = (HTTP_FILTER_DLL **)(m_buffFilterArray.QueryPtr());

    for ( DWORD i = 0; i < m_cFilters; i++ )
    {
        if ( apFilterDll[i] == pFilterDll )
        {
            return pFilterDll;
        }
    }

    return NULL;
}


HTTP_FILTER::HTTP_FILTER(
    HTTP_REQ_BASE       * pRequest
    )
/*++

Routine Description:

    Copies the filter context

Arguments:

    pRequest - Pointer to HTTP request this filter should be applied to

Return Value:

--*/
    : _fIsValid         ( FALSE ),
      _pRequest         ( pRequest ),
      _apvContexts      ( NULL ),
      _pFilterList      ( NULL ),
      _pGlobalFilterList( NULL ),
      _fSendHeadersChanged( FALSE ),
      _fSendHeadersParsed( FALSE ),
      _dwSecureNotifications( 0 ),
      _dwNonSecureNotifications( 0 ),
      _fNotificationsDisabled( FALSE )
{
    InitializeListHead( &_PoolHead );

    _hfc.cbSize             = sizeof( _hfc );
    _hfc.Revision           = HTTP_FILTER_REVISION;
    _hfc.ServerContext      = (void *) this;
    _hfc.ulReserved         = 0;

    _hfc.ServerSupportFunction = ServerFilterCallback;
    _hfc.GetServerVariable     = GetServerVariable;
    _hfc.AddResponseHeaders    = AddFilterResponseHeaders;
    _hfc.WriteClient           = WriteFilterClient;
    _hfc.AllocMem              = AllocFilterMem;

    _Overlapped.hEvent = NULL;
    _CurrentFilter     = INVALID_DLL;

    //
    //  Allocate the array of contexts for this request
    //

    _apvContexts = new PVOID[MAX_FILTERS];

    if ( !_apvContexts )
    {
        SetLastError( ERROR_NOT_ENOUGH_MEMORY );
        return;
    }

    //
    //  Initialize the array of filter contexts
    //

    memset( _apvContexts, 0, MAX_FILTERS * sizeof(PVOID) );


    //Reset();  We Assume Reset will be called when initializing the session

    SetGlobalFilterList( GLOBAL_FILTER_LIST() );

    _fIsValid = TRUE;
}

VOID
HTTP_FILTER::Reset(
    VOID
    )
/*++

Routine Description:

    Resets the state of this filter.  Called between net sessions.

--*/
{
    FILTER_POOL_ITEM * pfpi;

    _cbRecvRaw      = 0;
    _cbRecvTrans    = 0;
    _hFile          = NULL;
    _pFilterGetFile = NULL;
    _cbFileReadSize = 4096;
    _cRawNotificationLevel = 0;
    _dwDeniedFlags  = 0;
    _fInAccessDeniedNotification = FALSE;
    _pchSendHeaders = NULL;
    _fNotificationsDisabled = FALSE;

    if ( _fSendHeadersParsed )
    {
        _SendHeaders.Reset();
    }

    _fSendHeadersChanged = FALSE;
    _fSendHeadersParsed  = FALSE;
}

VOID
HTTP_FILTER::Cleanup(
    VOID
    )
/*++

Routine Description:

    Cleans up this filter.  Called after a session terminates.

--*/
{
    FILTER_POOL_ITEM * pfpi;

    //
    //  Reset the array of filter contexts
    //

    memset( _apvContexts, 0, MAX_FILTERS * sizeof(PVOID) );

    //
    // Clean up the allocated buffers
    //

    if ( _bufRecvRaw.QuerySize( ) > MAX_CACHED_FILTER_BUFFER ) {
        _bufRecvRaw.FreeMemory();
    }

    _bufRecvTrans.Resize( 0 );

    //
    // Free pool items
    //

    while ( !IsListEmpty( &_PoolHead )) {

         pfpi = CONTAINING_RECORD( _PoolHead.Flink,
                                   FILTER_POOL_ITEM,
                                   _ListEntry );

        RemoveEntryList( &pfpi->_ListEntry );

        delete pfpi;
    }

    if ( _pFilterList )
    {
        FILTER_LIST::Dereference( _pFilterList );
        _pFilterList = NULL;
    }

    _fNotificationsDisabled = FALSE;

} // Cleanup

HTTP_FILTER::~HTTP_FILTER(
    VOID
    )
/*++

Routine Description:

    Destructor for HTTP filter class

Arguments:

Return Value:

--*/
{
    if ( _Overlapped.hEvent )
        CloseHandle( _Overlapped.hEvent );

    if ( _pFilterList )
    {
        DBGPRINTF(( DBG_CONTEXT,
                    "[~HTTP_FILTER] Dereferencing _pFilterList at %lx\n",
                    _pFilterList ));

        FILTER_LIST::Dereference( _pFilterList );
        _pFilterList = NULL;
    }

#if 0   // Global filter lists are not dynamic so they're not counted
    if ( _pGlobalFilterList )
    {
        DBGPRINTF(( DBG_CONTEXT,
                    "[~HTTP_FILTER] Dereferencing _pGlobalFilterList at %lx\n",
                    _pGlobalFilterList ));

        FILTER_LIST::Dereference( _pGlobalFilterList );
        _pGlobalFilterList = NULL;
    }
#endif

    delete [] _apvContexts;
}

BOOL
HTTP_FILTER::ReadData(
    LPVOID lpBuffer,
    DWORD  nBytesToRead,
    DWORD  *pcbBytesRead,
    DWORD  dwFlags
    )
/*++

Routine Description:

    This method reads data from the network and calls the
    filter dlls.  It also handles data tracking on data overflow
    or underflow.  The number of bytes read are translated
    bytes, which may be more or less then actual bytes transferred
    on the network.

    The user's buffer is used for the initial receive.  If a filter comes
    back indicating it needs more data (got the first 8k of a 32k SSL message
    for example) then we switch to using the _bufRecvRaw buffers.

Arguments:

    lpBuffer - Destination buffer
    nBytesToRead - Number of bytes to read
    *pcbBytesRead - Number of bytes read
    dwFlags - IO_FLAG values

Return Value:

    TRUE on success, FALSE on failure (call GetLastError)

--*/
{
    DWORD    cbBytesRead = 0;
    DWORD    cbToCopy;

    if ( pcbBytesRead )
    {
        *pcbBytesRead = 0;
    }

    _pbReadBuff     = _pbClientBuff = (BYTE *) lpBuffer;
    _cbReadBuff     = _cbClientBuff = nBytesToRead;
    _cbReadBuffUsed = 0;

    //
    //  If there is old translated data that we need to give
    //  to the client, copy that now
    //

    if ( _cbRecvTrans )
    {
        cbToCopy = min( _cbRecvTrans, nBytesToRead );

        memcpy( lpBuffer,
                _bufRecvTrans.QueryPtr(),
                cbToCopy );

        if ( pcbBytesRead )
        {
            *pcbBytesRead = cbToCopy;
        }

        if ( _cbRecvTrans > cbToCopy )
        {
            _cbRecvTrans -= cbToCopy;

            //
            //  This should be very rare this happens, so flag it and if
            //  it does happen often then add an offset counter
            //

            DBGPRINTF(( DBG_CONTEXT,
                        "[HTTP_FILTER::ReadData] PERF WARNING! In place buffer copy (%d bytes)!\n",
                        _cbRecvTrans ));

            memmove( (BYTE *) _bufRecvTrans.QueryPtr(),
                     (BYTE *) _bufRecvTrans.QueryPtr() + cbToCopy,
                     _cbRecvTrans );
        }
        else
        {
            _cbRecvTrans = 0;
        }

        nBytesToRead    -= cbToCopy;
        _cbReadBuffUsed += cbToCopy;

        //
        //  If there were any bytes from the previous request, just complete
        //  the request now.  This prevents potentially blocking on a recv()
        //  with no data.
        //

        if ( dwFlags & IO_FLAG_ASYNC )
        {
            DBGPRINTF(( DBG_CONTEXT,
                        "[ReadData] Posting dummy completion\n" ));

            if ( !_pRequest->PostCompletionStatus( cbToCopy ))
            {
                return FALSE;
            }
        }

        return TRUE;
    }

    if ( dwFlags & IO_FLAG_SYNC )
    {
        BOOL fRet;

        //
        //  Get the raw data, put it after any old raw data.  We temporarily
        //  bump up the thread pool count so we don't eat all of the threads.
        //

        AtqSetInfo( AtqIncMaxPoolThreads, 0 );

        fRet = TcpSockRecv(_pRequest->QueryClientConn()->QuerySocket(),
                        (char *) _pbReadBuff + _cbReadBuffUsed,
                         nBytesToRead,
                         &cbBytesRead,
                         60 // 60s timeout
                         );

        AtqSetInfo( AtqDecMaxPoolThreads, 0 );

        if ( !fRet || (cbBytesRead == 0) )
        {
            return FALSE;
        }

        if ( !ContinueRawRead( cbBytesRead,
                               NO_ERROR,
                               NULL,
                               pcbBytesRead ))
        {
            return FALSE;
        }
    }
    else
    {
        DBG_ASSERT( dwFlags & IO_FLAG_ASYNC );

        //
        //  Hook the Atq IO completion routine so reads complete through
        //  ContinueRawRead
        //

        _OldAtqCompletion = (ATQ_COMPLETION) AtqContextSetInfo( QueryAtqContext(),
                                                                ATQ_INFO_COMPLETION,
                                                                (ULONG_PTR) ::ContinueRawRead );

        _OldAtqContext    = (PVOID) AtqContextSetInfo( QueryAtqContext(),
                                                       ATQ_INFO_COMPLETION_CONTEXT,
                                                       (ULONG_PTR) this );

        if ( !_pRequest->ReadFile( _pbReadBuff + _cbReadBuffUsed,
                                   nBytesToRead,
                                   NULL,
                                   IO_FLAG_ASYNC | IO_FLAG_NO_FILTER ))
        {
            AtqContextSetInfo( QueryAtqContext(),
                               ATQ_INFO_COMPLETION,
                               (ULONG_PTR) _OldAtqCompletion );

            AtqContextSetInfo( QueryAtqContext(),
                               ATQ_INFO_COMPLETION_CONTEXT,
                               (ULONG_PTR) _OldAtqContext );

            return FALSE;
        }
    }

    return TRUE;

} // HTTP_FILTER::ReadData


VOID
ContinueRawRead(
    PVOID        Context,
    DWORD        BytesWritten,
    DWORD        CompletionStatus,
    OVERLAPPED * lpo
    )
{
    HTTP_FILTER * pFilter;
    pFilter = (HTTP_FILTER *) Context;

    ((HTTP_FILTER *)Context)->ContinueRawRead( BytesWritten,
                                               CompletionStatus,
                                               lpo );
}

BOOL
HTTP_FILTER::ContinueRawRead(
    DWORD        cbBytesRead,
    DWORD        CompletionStatus,
    OVERLAPPED * lpo,
    DWORD *      pcbRead
    )
/*++

Routine Description:

    This method is the completion routine for an async (or sync) read.
    Note : It's a sync read if the overlapped structure [lpo] is null and
    a completion status [CompletionStatus] of NO_ERROR

Arguments:

    cbBytesRead - Number of bytes read
    CompletionStatus - status code of read operation
    lpo - Pointer to overlapped structure

Return Value:


    TRUE on success, FALSE on failure (call GetLastError)

--*/
{
    PVOID    pvOutData;
    DWORD    cbOutData;
    BOOL     fReadAgain;
    BOOL     fRequestFinished = FALSE;
    DWORD    cbToCopy;
    BOOL     fSyncRead = (lpo == NULL && CompletionStatus == NO_ERROR);

    //
    //  Don't update _cbReadBuffUsed until we get the data translated
    //

    if ( lpo )
    {
        _pRequest->Dereference();

        //
        //  If the socket has been closed get out.  It's the responsibility of the
        //  caller to detect this
        //

        if ( cbBytesRead == 0 )
        {
            goto CompleteRequest;
        }
    }

    if ( CompletionStatus )
    {
        goto CompleteRequest;
    }

ReadAgain:

    //
    //  Keeps track of the number of bytes the filter has *not* seen
    //

    _cbRecvRaw += cbBytesRead;

    //
    //  Call the filters to translate the raw data.  No need to check for
    //  any filters that need this notification as we wouldn't be here otherwise
    //
    //  _cbReadBuffUsed is translated bytes, _cbRecvRaw is untranslated bytes
    //

    fReadAgain = FALSE;

    if ( !NotifyRawReadDataFilters( _pbReadBuff + _cbReadBuffUsed,
                                    _cbRecvRaw,
                                    _cbReadBuff - _cbReadBuffUsed,
                                    &pvOutData,
                                    &cbOutData,
                                    &fRequestFinished,
                                    &fReadAgain ))
    {
        CompletionStatus = GetLastError();
        goto CompleteRequest;
    }

    //
    //  If the filter indicated this connection should be finished, force
    //  a tear down of our connection state
    //

    if ( fRequestFinished )
    {
        CompletionStatus = WSAECONNRESET;
        goto CompleteRequest;
    }

    if ( fReadAgain )
    {
        //
        //  If we need to read the next chunk, make sure it will fit in
        //  our read buffer.  If it won't, then switch to our own buffer
        //  and issue the read from there
        //

        if ( QueryNextReadSize() > (_cbReadBuff - _cbRecvRaw ) )
        {
            if ( !_bufRecvRaw.Resize( _cbRecvRaw + QueryNextReadSize() ))
            {
                CompletionStatus = GetLastError();
                goto CompleteRequest;
            }

            memcpy( _bufRecvRaw.QueryPtr(),
                    _pbReadBuff,
                    _cbRecvRaw );

            _pbReadBuff = (BYTE *) _bufRecvRaw.QueryPtr();
            _cbReadBuff = _bufRecvRaw.QuerySize();
        }

        if ( !_pRequest->ReadFile( _pbReadBuff + _cbRecvRaw,
                                   QueryNextReadSize(),
                                   pcbRead,
                                   (lpo ? (IO_FLAG_ASYNC | IO_FLAG_NO_FILTER) :
                                          (IO_FLAG_SYNC | IO_FLAG_NO_FILTER)) ))
        {
            CompletionStatus = GetLastError();
            goto CompleteRequest;
        }

        //
        //  Operation aborted, set error and return
        //

        if ( pcbRead && *pcbRead == 0 )
        {
            SetLastError( ERROR_OPERATION_ABORTED );
            return FALSE;
        }

        //
        //  If this is a sync request process the additional bytes now
        //

        if ( !lpo )
        {
            cbBytesRead = *pcbRead;
            goto ReadAgain;
        }

        return TRUE;
    }

    //
    //  Adjust the byte counts for the bytes just translated.
    //

    _pRequest->IncrementBytesSeenByRawReadFilter( cbOutData );
    _cbReadBuffUsed += cbOutData;
    _cbRecvRaw      = 0;

    //
    //  If we weren't able to use the original client buffer, copy as many
    //  bytes as possible to the client's buffer now.  We only hit this case
    //  if a filter indicated we need to read again.
    //

    if ( _pbReadBuff != _pbClientBuff )
    {
        cbToCopy = min( cbOutData, _cbClientBuff );

        memcpy( _pbClientBuff,
                pvOutData,
                cbToCopy );

        _cbReadBuffUsed = cbToCopy;

        //
        //  If more bytes were translated then what the client
        //  requested us to read, save those away for the next
        //  read request
        //

        if ( cbOutData > _cbClientBuff )
        {
            _cbRecvTrans = cbOutData - _cbClientBuff;

            if ( !_bufRecvTrans.Resize( _cbRecvTrans ))
            {
                CompletionStatus = GetLastError();
                goto CompleteRequest;
            }

            memcpy( _bufRecvTrans.QueryPtr(),
                    (BYTE *) pvOutData + cbToCopy,
                    _cbRecvTrans );
        }
    }

    if ( pcbRead )
    {
        *pcbRead = _cbReadBuffUsed;
    }

    //
    //  If this was an async request, reset the Atq information and complete
    //  the receive with _cbReadBuffUsed
    //

CompleteRequest:

    if (!fSyncRead)
    {
        //
        //  This is an async completion, the return is not used
        //

        AtqContextSetInfo( QueryAtqContext(),
                           ATQ_INFO_COMPLETION,
                           (ULONG_PTR) _OldAtqCompletion );

        AtqContextSetInfo( QueryAtqContext(),
                           ATQ_INFO_COMPLETION_CONTEXT,
                           (ULONG_PTR) _OldAtqContext );

        _OldAtqCompletion( _OldAtqContext,
                           _cbReadBuffUsed,
                           CompletionStatus,
                           NULL );

        return TRUE;
    }

    return (CompletionStatus == NO_ERROR);
}


BOOL
HTTP_FILTER::SendData(
    LPVOID  lpBuffer,
    DWORD   nBytesToSend,
    DWORD * pnBytesSent,
    DWORD   dwFlags
    )
/*++

Routine Description:

    This method calls the interested filters to do the appropriate data
    transformation then sends the data.

Arguments:

    lpBuffer - Destination buffer
    nBytesToRead - Number of bytes to read
    *pcbBytesRead - Number of bytes read
    dwFlags - IO_FLAG values

Return Value:

    TRUE on success, FALSE on failure (call GetLastError)

--*/
{
    PVOID    pvOutData;
    DWORD    cbOutData;
    DWORD    cbToCopy;
    BOOL     fRequestFinished = FALSE;

    //
    //  We buffer any unsent data so indicate all the data was sent
    //

    if ( pnBytesSent )
        *pnBytesSent = nBytesToSend;

    pvOutData = lpBuffer;
    cbOutData = nBytesToSend;

    if ( IsNotificationNeeded( SF_NOTIFY_SEND_RAW_DATA,
                               QueryReq()->IsSecurePort() ) &&
         !NotifyRawSendDataFilters( lpBuffer,
                                    nBytesToSend,
                                    nBytesToSend,
                                    &pvOutData,
                                    &cbOutData,
                                    &fRequestFinished ))
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[SendData] NotifyRawDataFilters failed with %d\n",
                    GetLastError()));

        return FALSE;
    }

    if ( fRequestFinished )
    {
        //
        //  We have to return an error so the client doesn't think
        //  they will receive an IO completion.  This may have odd
        //  manifestations
        //

        SetLastError( ERROR_OPERATION_ABORTED );
        return FALSE;
    }

    if ( !_pRequest->WriteFile( pvOutData,
                                cbOutData,
                                &nBytesToSend,
                                dwFlags | IO_FLAG_NO_FILTER ))
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[SendData] Send failed with %d\n",
                    GetLastError()));
        if ( pnBytesSent )
        {
            *pnBytesSent = (DWORD)SOCKET_ERROR;
        }
        return FALSE;
    }

    return TRUE;
}

BOOL
HTTP_FILTER::SendFile(
    TS_OPEN_FILE_INFO *     pGetFile,
    HANDLE                  hFile,
    DWORD                   dwOffset,
    DWORD                   nBytesToWrite,
    DWORD                   dwFlags,
    PVOID                   pHead,
    DWORD                   HeadLength,
    PVOID                   pTail,
    DWORD                   TailLength
    )
/*++

Routine Description:

    This method simulates Winsock's TransmitFile with the addition
    of running the data through the interested filters.  This only
    supports async IO.

Arguments:

    pGetFile - Cached TS_OPEN_FILE_INFO to send
    hFile - Overlapped IO file to send (if NULL, pGetFile used to get handle)
    dwOffset - Offset from start of file
    nBytesToWrite - Bytes of file to send, note zero (meaning send the whole
        file) is not supported
    dwFlags - IO_FLAGs
    pHead - Optional pre-data to send
    HeadLength - Number of bytes of pHead
    pTail - Optional post data to send
    TailLength - Number of bytes of pTail

Return Value:

    TRUE on success, FALSE on failure (call GetLastError)

--*/
{
    HANDLE hEvent;

    DBG_ASSERT( (dwFlags & IO_FLAG_ASYNC) );

    //
    //  We assume pHead points to header data.  Send it synchronously, then
    //  we'll do chunk async sends for the file
    //

    if ( HeadLength )
    {
        DWORD cbSent;

        if ( !SendData( pHead,
                        HeadLength,
                        &cbSent,
                        (dwFlags & ~IO_FLAG_ASYNC) | IO_FLAG_SYNC ))
        {
            DBGPRINTF(( DBG_CONTEXT,
                       "[SendFile] SendData failed with %d\n",
                        GetLastError()));

            return FALSE;
        }
    }

    if ( !nBytesToWrite )
    {
        if ( hFile )
        {
            BY_HANDLE_FILE_INFORMATION hfi;

            if ( !GetFileInformationByHandle( hFile, &hfi ))
            {
                return FALSE;
            }

            if ( hfi.nFileSizeHigh )
            {
                SetLastError( ERROR_NOT_SUPPORTED );
                return FALSE;
            }

            nBytesToWrite = hfi.nFileSizeLow;
        }
        else if ( pGetFile )
        {
            LARGE_INTEGER           liSize;
        
            if ( !pGetFile->QuerySize( liSize ) )
            {
                return FALSE;
            }
        
            nBytesToWrite = liSize.LowPart;
        }
        // else -- We are doing a file-less transmit
    }

    //
    //  Set up variables for doing the file transmit
    //

    _hFile                 = hFile;
    _pFilterGetFile        = pGetFile;
    _cbFileBytesToWrite    = nBytesToWrite;
    _cbFileBytesSent       = 0;
    _cbFileData            = 0;
    _dwCompletionStatus    = NO_ERROR;
    _pTail                 = pTail;
    _cbTailLength          = TailLength;
    _dwFlags               = dwFlags;

    //
    //  Save the event handle if we previously created one
    //

    if ( _Overlapped.hEvent )
    {
        hEvent = _Overlapped.hEvent;
    }
    else
    {
        if ( !_Overlapped.hEvent )
        {
            hEvent = IIS_CREATE_EVENT(
                         "HTTP_FILTER::_Overlapped::hEvent",
                         this,
                         TRUE,
                         FALSE
                         );

            if ( !hEvent )
            {
                return FALSE;
            }
        }
    }

    memset( &_Overlapped, 0, sizeof( _Overlapped ));

    _Overlapped.hEvent = hEvent;
    _Overlapped.Offset = dwOffset;

    if ( !_bufFileData.Resize( 8192 ))
    {
        return FALSE;
    }

    //
    //  Hook the ATQ completion routine so the caller only sees a single
    //  completion since we have to chunk the sends for the filters
    //
    //  NOTE: If multiple requests over the same TCP session are being
    //  handled at once (not currently supported by protocol, but may
    //  eventually be) then there is a potential race condition between
    //  setting the context and setting the completion on the ATQ context
    //

    _OldAtqCompletion = (ATQ_COMPLETION) AtqContextSetInfo( QueryAtqContext(),
                                                            ATQ_INFO_COMPLETION,
                                                            (ULONG_PTR) FilterAtqCompletion );

    _OldAtqContext    = (PVOID) AtqContextSetInfo( QueryAtqContext(),
                                                   ATQ_INFO_COMPLETION_CONTEXT,
                                                   (ULONG_PTR) this );

    //
    //  Kick off the first send
    //

    OnAtqCompletion( 0, NO_ERROR, NULL );

    return TRUE;
}

VOID
FilterAtqCompletion(
    PVOID        Context,
    DWORD        BytesWritten,
    DWORD        CompletionStatus,
    OVERLAPPED * lpo
    )
/*++

Routine Description:

    This function substitutes for the normal request's ATQ completion.  It
    is used to simulate an Async transmit file that passes all data through
    the interested filters.

Arguments:

    Context - Pointer to filter object
    BytesWritten - Number of bytes written on last completion
    CompletionStatus - Status of last send
    lpo - Overlapped structure. NULL if error completion, non-null if IO comp.

--*/
{
    HTTP_FILTER * pFilter;
    pFilter = (HTTP_FILTER *) Context;

    ((HTTP_FILTER *)Context)->OnAtqCompletion( BytesWritten,
                                               CompletionStatus,
                                               lpo );
}

VOID
HTTP_FILTER::OnAtqCompletion(
    DWORD        BytesWritten,
    DWORD        CompletionStatus,
    OVERLAPPED * lpo
    )
/*++

Routine Description:

    This method handles ATQ completions.  It is used to simulate an Async
    transmit file that passes all data through the interested filters.

Arguments:

    BytesWritten - Number of bytes written on last completion
    CompletionStatus - Status of last send
    lpo - !NULL if this is a completion from an async IO

--*/
{
    DWORD         dwIoFlag;
    DWORD         BytesRead;
    DWORD         dwToRead;

    IF_DEBUG( CONNECTION )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "HTTP_FILTER::OnAtqCompletion: Last IO error %lu Bytes = %d IsIO = %s\n",
                    CompletionStatus,
                    BytesWritten,
                    lpo != NULL ? "TRUE" : "FALSE" ));
    }

    //
    //  Decrement the outstanding IO count
    //

    if ( lpo )
        _pRequest->Dereference();

    //
    //  If an error occurred on the completion and we haven't previous recorded
    //  an error, then record this one.  This prevents overwriting the real
    //  status code from a cancelled IO error.
    //

    if ( CompletionStatus && !_dwCompletionStatus )
    {
        _dwCompletionStatus = CompletionStatus;
    }

    //
    //  If an error occurred, restore the old ATQ information and forward
    //  the error
    //

    if ( _dwCompletionStatus ||
         _cbFileBytesSent >= _cbFileBytesToWrite )
    {
        // In the file-less case _cbFileBytesSent == 
        // _cbFileBytesToWrite == 0, so we will send the tail
        // if there is no error.

        if ( !_dwCompletionStatus && _cbTailLength )
        {
            DWORD cbSent;

            if ( !SendData( _pTail,
                            _cbTailLength,
                            &cbSent,
                            (_dwFlags & ~IO_FLAG_ASYNC) | IO_FLAG_SYNC ))
            {
                DBGPRINTF(( DBG_CONTEXT,
                           "[SendFile] SendData failed with %d\n",
                            GetLastError()));

                CompletionStatus = GetLastError();
                if ( CompletionStatus && !_dwCompletionStatus )
                    _dwCompletionStatus = CompletionStatus;
            }
        }
        goto ErrorExit;
    }

    //
    //  Read the next chunk of data from the file
    //

    dwToRead = _bufFileData.QuerySize();
    if ( dwToRead > _cbFileBytesToWrite - _cbFileBytesSent ) {
        dwToRead = _cbFileBytesToWrite - _cbFileBytesSent;
    }

    DBG_ASSERT(_dwCompletionStatus == NO_ERROR);

    if ( _pFilterGetFile && _pFilterGetFile->QueryFileBuffer() )
    {
        // Fast path.  We already have a buffer with the file contents, so 
        // read from the buffer directly.
        
        memcpy( (PCHAR) _bufFileData.QueryPtr(),
                _pFilterGetFile->QueryFileBuffer() + _Overlapped.Offset,
                dwToRead );
        
        BytesRead = dwToRead;
    }
    else
    {
        if ( !DoSynchronousReadFile(
                        _pFilterGetFile ? 
                            _pFilterGetFile->QueryFileHandle() : _hFile,
                        (PCHAR)_bufFileData.QueryPtr(),
                        dwToRead,
                        &BytesRead,
                        &_Overlapped ))
        {
            _dwCompletionStatus = GetLastError();

             DBGPRINTF(( DBG_CONTEXT,
                        "[Filter AtqCompletion] ReadFile failed with %d\n",
                         _dwCompletionStatus ));

             goto ErrorExit;
        }
    }

    //
    //  Now send the data through the filters
    //

    dwIoFlag = IO_FLAG_ASYNC;

    _cbFileBytesSent   += BytesRead;
    _Overlapped.Offset += BytesRead;

    if ( !SendData( _bufFileData.QueryPtr(),
                    BytesRead,
                    NULL,
                    dwIoFlag ))
    {
        _dwCompletionStatus = GetLastError();

        DBGPRINTF(( DBG_CONTEXT,
                   "[Filter AtqCompletion] SendData failed with %d\n",
                    CompletionStatus ));

        goto ErrorExit;
    }
    return;

ErrorExit:

    IF_DEBUG( CONNECTION )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "HTTP_FILTER::OnAtqCompletion: Restoring AtqContext, "
                   "Bytes Sent = %d, Status = %d, IsIO = %s\n",
                   _cbFileBytesSent,
                   _dwCompletionStatus,
                   (lpo == NULL ? "FALSE" : "TRUE")));
    }

    AtqContextSetInfo( QueryAtqContext(),
                       ATQ_INFO_COMPLETION,
                       (ULONG_PTR) _OldAtqCompletion );

    AtqContextSetInfo( QueryAtqContext(),
                       ATQ_INFO_COMPLETION_CONTEXT,
                       (ULONG_PTR) _OldAtqContext );

    //
    //  Forward the error onto the old ATQ completion handler.  We always
    //  pass an lpo of NULL because we've already decremented the IO
    //  ref count.
    //
    //  NOTE: 'this' may be deleted in this callback!
    //

    _OldAtqCompletion( _OldAtqContext,
                       _cbFileBytesSent,
                       CompletionStatus,
                       NULL );

    return;
}

BOOL
HTTP_FILTER::DisableNotification(
    IN DWORD dwNotification
)
/*++

Routine Description:

    Called when a filter wants to disable one/more of its own notifications
    for the given request.

Arguments:

    dwNotification - Mask of notifications to disable for this request

--*/
{
    DBG_ASSERT( _pFilterList || _pGlobalFilterList );


    if ( !_fNotificationsDisabled )
    {
        //
        // All subsequent calls to IsNotificationNeeded() and NotifyFilter() must
        // use local copy of flags to determine action.
        //

        _fNotificationsDisabled = TRUE;

        //
        // Copy notification tables created in the FILTER_LIST objects
        //

        if ( !_BuffSecureArray.Resize( QueryFilterList()->QuerySecureArray()->QuerySize() ) ||
             !_BuffNonSecureArray.Resize( QueryFilterList()->QueryNonSecureArray()->QuerySize() ) )
        {
            return FALSE;
        }


        memcpy( _BuffSecureArray.QueryPtr(),
                QueryFilterList()->QuerySecureArray()->QueryPtr(),
                QueryFilterList()->QuerySecureArray()->QuerySize() );

        memcpy( _BuffNonSecureArray.QueryPtr(),
                QueryFilterList()->QueryNonSecureArray()->QueryPtr(),
                QueryFilterList()->QueryNonSecureArray()->QuerySize() );

    }

    //
    // Disable the appropriate filter in our local table
    //

    ((DWORD*)_BuffSecureArray.QueryPtr())[ QueryCurrentDll() ] &=
                                                        ~dwNotification;
    ((DWORD*)_BuffNonSecureArray.QueryPtr())[ QueryCurrentDll() ] &=
                                                        ~dwNotification;

    //
    // Calculate the aggregate notification status for our local scenario
    // NYI:  Might want to defer this operation?
    //

    _dwSecureNotifications = 0;
    _dwNonSecureNotifications = 0;

    for( DWORD i = 0; i < QueryFilterList()->QueryFilterCount(); i++ )
    {
        _dwSecureNotifications |= ((DWORD*)_BuffSecureArray.QueryPtr())[i];
        _dwNonSecureNotifications |= ((DWORD*)_BuffNonSecureArray.QueryPtr())[i];
    }


    return TRUE;
}

BOOL
HTTP_FILTER::SetFilterList(
    IN FILTER_LIST *pFilterList
    )
/*++

Routine Description:

    Sets the filter list associated with this object

Arguments :

   pFilterList - new filter list

Returns:

    Nothing

--*/

{
    DBG_ASSERT( !_pFilterList );
    FILTER_LIST *pOldList = _pFilterList;

    if ( pFilterList )
    {
        //
        // This function is called during HTTP_REQUEST::Parse, when a -global- filter
        // may already have disabled some notifications during READ_RAW processing
        // and thus set the _fNotificationsDisabled flag, so we'll leave the flag
        // as-is [it's reset at the end of each request and connection, so we're
        // not going to be using a stale value].
        //
        // The first time a notification is disabled, we make a local copy
        // of the notification flag arrays from the filter list and make the modifications
        // to our local copy. Hence, when the filter list is (re)set, the notification
        // arrays of the new filter list and the local copies have to be merged, since we
        // want to keep our old flags as well as picking up the flags for the new filters
        // that might be in the list.
        //

        //_fNotificationsDisabled = FALSE; //intentionally commented out !

        pFilterList->Reference();

        if ( _fNotificationsDisabled )
        {
            if ( !MergeNotificationArrays( pFilterList,
                                           TRUE )  ||
                 !MergeNotificationArrays( pFilterList,
                                           FALSE ) )
            {
                pFilterList->Dereference( pFilterList );
                return FALSE;
            }
        }

        _pFilterList = pFilterList;
    }

    return TRUE;
}

BOOL
HTTP_FILTER::MergeNotificationArrays(
    FILTER_LIST *pFilterList,
    BOOL fSecure
    )
/*++

Routine Description:

    This is called when the filter list for this HTTP_FILTER object needs to be replaced
    with a new list and we need to merge the arrays holding the notification flags. See
    comments in HTTP_FILTER::SetFilterList

Arguments:
    pFilterList - filter list whose notification flags need to be incorporated
    fSecure - flag indicating whether secure/nonsecure arrays are to be merged

Returns:
    Nothing

--*/

{
    DWORD dwNotifications = 0;
    DWORD *adwOldNotifArray = NULL;
    DWORD *adwMergedArray = NULL;
    DWORD *adwNewNotifArray = NULL;
    DWORD iOldFilterPos = 0;
    HTTP_FILTER_DLL *pFilterDll = NULL;
    BUFFER BuffMergedArray;

    if ( !BuffMergedArray.Resize( fSecure ?  pFilterList->QuerySecureArray()->QuerySize() :
                                             pFilterList->QueryNonSecureArray()->QuerySize() ) )
    {
        return FALSE;
    }

    if ( fSecure )
    {
        if ( !_BuffSecureArray.Resize( pFilterList->QuerySecureArray()->QuerySize() ) )
        {
            return FALSE;
        }
    }
    else
    {
        if ( !_BuffNonSecureArray.Resize( pFilterList->QueryNonSecureArray()->QuerySize() ) )
        {
            return FALSE;
        }
    }

    adwNewNotifArray = (DWORD *) (fSecure ? pFilterList->QuerySecureArray()->QueryPtr() :
                                            pFilterList->QueryNonSecureArray()->QueryPtr() );

    adwMergedArray = (DWORD *) BuffMergedArray.QueryPtr();

    //
    // This gets a little tricky : we have to walk through the present
    // notification arrays and the notification array for the new filter list
    // and merge them - all the notification flags for the filters in the old
    // list should be preserved, and the notifications for the new filters
    // need to be added to the notification array
    //

    for ( DWORD i = 0; i < pFilterList->QueryFilterCount(); i++ )
    {
        //
        // If the old filter list had an entry for this filter, we want to
        // preserve those notification flags
        //
        if ( ( pFilterDll = QueryFilterList()->HasFilterDll( pFilterList->QueryDll( i ) ) ) )
        {
            adwMergedArray[i] = QueryFilterNotifications( pFilterDll,
                                                          fSecure );
        }
        //
        // New filter
        //
        else
        {
            adwMergedArray[i] = adwNewNotifArray[i];
        }
    }

    //
    // Copy merged array
    //
    memcpy( (fSecure ? _BuffSecureArray.QueryPtr() :
                       _BuffNonSecureArray.QueryPtr() ),
            BuffMergedArray.QueryPtr(),
            BuffMergedArray.QuerySize() );

    return TRUE;
}



BOOL
HTTP_FILTER::ParseSendHeaders(
    VOID
    )
/*++

Routine Description:

    Parses the list of headers the server is about to send and places them
    in the _SendHeaders parameter list for subsequent retrieval by the ISAPI
    Filter

--*/
{
    const CHAR *    pch;
    CHAR            ch;
    DWORD           cbHeaders = strlen( _pchSendHeaders );
    const CHAR *    pchEnd = _pchSendHeaders + cbHeaders;
    const CHAR *    pchHeader;
    const CHAR *    pchEndHeader;
    const CHAR *    pchValue;
    const CHAR *    pchEndValue;
    const CHAR *    pchNext;

    DBG_ASSERT( !_fSendHeadersParsed );

    //
    //  We don't want the PARAM_LIST to canon the headers because it
    //  will collapse the WWW-Authenticate: headers to a comma separated
    //  list which will mess up clients even though technically this is allowed
    //  by the HTTP spec
    //

    _SendHeaders.SetIsCanonicalized( TRUE );

    //
    //  Grab the first line and add it as a special value "status"
    //

    pch = pchNext = (const CHAR *) memchr( _pchSendHeaders, '\n', cbHeaders );

    if ( !pch )
        return TRUE;

    if ( pch > _pchSendHeaders && pch[-1] == '\r' )
    {
        pch--;
    }

    if ( !_SendHeaders.AddEntry( "status",
                                 6,
                                 _pchSendHeaders,
                                 DIFF(pch - _pchSendHeaders) ))
    {
        return FALSE;
    }

    //
    //  Now deal with the rest of the headers
    //

    pchHeader = pchNext + 1;
    cbHeaders = DIFF(pchEnd - pchHeader);

    while ( pchNext = (LPSTR)memchr( pchHeader, '\n', cbHeaders ) )
    {
        if ( pchValue = (LPSTR)memchr( pchHeader, ':', DIFF(pchNext - pchHeader) ) )
        {
            UINT ch = *(PBYTE)++pchValue;

            pchEndHeader = pchValue;
            int cName = DIFF(pchValue - pchHeader);

            if ( _HTTP_IS_LINEAR_SPACE( (CHAR)ch ) )
            {
                while ( _HTTP_IS_LINEAR_SPACE( *(PBYTE)++pchValue ) )
                    ;
            }

            if ( (pchNext > pchValue) && (pchNext[-1] == '\r') )
            {
                pchEndValue = pchNext - 1;
            }
            else
            {
                pchEndValue = pchNext;
            }

            if ( !_SendHeaders.AddEntry( pchHeader,
                                         DIFF(pchEndHeader - pchHeader),
                                         pchValue,
                                         DIFF(pchEndValue - pchValue) ))
            {
                return FALSE;
            }
        }
        else
        {
            if ( (*pchHeader == '\r') || (*pchHeader == '\n') )
            {
                pchHeader = pchNext + 1;

                //
                //  If a body was specified, add it as a special value
                //

                if ( pchEnd - pchHeader > 1 )
                {
                    if ( !_SendHeaders.AddEntry( "body",
                                                 sizeof("body") - 1,
                                                 pchHeader,
                                                 DIFF(pchEnd - pchHeader) ))
                    {
                        return FALSE;
                    }
                }

                break;
            }
        }

        pchHeader = pchNext + 1;
        cbHeaders = DIFF(pchEnd - pchHeader);
    }

    _fSendHeadersParsed = TRUE;

    return TRUE;
}

BOOL
HTTP_FILTER::BuildNewSendHeaders(
    BUFFER * pHeaderBuff
    )
/*++

Routine Description:

    Takes the set of send headers and builds up an HTTP response, only used
    when a header has been changed

Arguments:

    pHeaderBuff - Receives HTTP response headers

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    PVOID   Cookie = NULL;
    CHAR *  pch;
    CHAR *  pszTail;
    CHAR *  pszField;
    DWORD   cbField;
    CHAR *  pszValue;
    DWORD   cbValue;

    pszTail = (CHAR *) pHeaderBuff->QueryPtr();
    *pszTail = '\0';

    pch = _SendHeaders.FindValue( "status", NULL, &cbValue );

    if ( !pch )
    {
        //
        //  Somebody deleted the status or this is a point nine request
        //  Supply a default and assume the filter really wanted to send a
        //  header response
        //

        if (!g_ReplyWith11)
        {
            pch = "HTTP/1.0 200 Ok";
        }
        else
        {
            pch = "HTTP/1.1 200 Ok";
        }

        cbValue = sizeof( "HTTP/1.0 200 Ok" ) - sizeof(CHAR);
    }

    if ( !pHeaderBuff->Resize( cbValue + 1, 512 ))
    {
        return FALSE;
    }
    pszTail = (CHAR *) pHeaderBuff->QueryPtr();

    CopyMemory( pszTail, pch, cbValue + 1 );
    pszTail += cbValue;

    *pszTail = '\r';
    pszTail++;
    *pszTail = '\n';
    pszTail++;

    while ( Cookie = _SendHeaders.NextPair( Cookie,
                                            &pszField,
                                            &cbField,
                                            &pszValue,
                                            &cbValue ))
    {
        //
        //  Ignore "status" and "body"
        //

        if ( !memcmp( pszField, "status", sizeof("status") ) ||
             !memcmp( pszField, "body", sizeof("body") ))
        {
            continue;
        }

        //
        //  Make sure there's room for the space, plus two '\r\n'
        //

        pch = (CHAR *) pHeaderBuff->QueryPtr();

        if ( !pHeaderBuff->Resize( DIFF(pszTail - pch) + cbValue + cbField + 6, 128 ))
        {
            return FALSE;
        }

        //
        //  Watch for pointer shift
        //

        if ( pch != pHeaderBuff->QueryPtr() )
        {
            pszTail = (CHAR *) pHeaderBuff->QueryPtr() + (pszTail - pch);
        }

        CopyMemory( pszTail, pszField, cbField + 1 );

        pszTail += cbField;

        *pszTail = ' ';
        pszTail++;

        CopyMemory( pszTail, pszValue, cbValue + 1 );
        pszTail += cbValue;

        *pszTail = '\r';
        pszTail++;
        *pszTail = '\n';
        pszTail++;
    }

    *pszTail = '\r';
    pszTail++;
    *pszTail = '\n';
    pszTail++;

    //
    //  Add the body if one was specified
    //

    pszValue = _SendHeaders.FindValue( "body", NULL, &cbValue );

    if ( pszValue )
    {
        pch = (CHAR *) pHeaderBuff->QueryPtr();

        if ( !pHeaderBuff->Resize( DIFF(pszTail - pch) + cbValue + 1 ))
        {
            return FALSE;
        }

        if ( pch != pHeaderBuff->QueryPtr() )
        {
            pszTail = (CHAR *) pHeaderBuff->QueryPtr() + (pszTail - pch);
        }

        CopyMemory( pszTail, pszValue, cbValue + 1 );
    }
    else
    {
        *pszTail = '\0';
    }

    return TRUE;
}

DWORD
HTTP_FILTER::QueryFilterNotifications( HTTP_FILTER_DLL *pFilterDll,
                                       BOOL fSecure )
/*++

Routine Description:

    Retrieves the notification flags for a given filter dll

Arguments:

    pFilterDll - filter dll whose notifications are to be retrieved
    fSecure - flag indicating whether secure/insecure port notifications are to be retrieved

Return Value:

    Notification flags for filter; zero if filter isn't found

--*/
{
    FILTER_LIST *pFilterList = QueryFilterList();
    DWORD cFilterCount = pFilterList->QueryFilterCount();

    for ( DWORD i = 0; i < cFilterCount ; i++ )
    {
        if ( pFilterList->QueryDll( i ) == pFilterDll )
        {
            if ( _fNotificationsDisabled )
            {
                return (((DWORD *) (fSecure ? _BuffSecureArray.QueryPtr() :
                                              _BuffNonSecureArray.QueryPtr()))[i]);
            }
            else
            {
                return (fSecure ? pFilterDll->QuerySecureFlags() :
                                  pFilterDll->QueryNonsecureFlags() );
            }
        }
    }

    return 0;
}


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

BOOL
WINAPI
ServerFilterCallback(
    HTTP_FILTER_CONTEXT *         pfc,
    enum SF_REQ_TYPE              sf,
    void *                        pData,
    ULONG_PTR                     ul1,
    ULONG_PTR                     ul2
    )
/*++

Routine Description:

    This method handles a gateway request to a server extension DLL

Arguments:

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    HTTP_REQ_BASE *     pRequest;
    HTTP_FILTER *       pFilter;
    STACK_STR(          str, MAX_PATH);;
    STACK_STR(          strResp, MAX_PATH);;
    STACK_STR(          strURL, MAX_PATH);
    UINT                cb;
    int                 n;
    DWORD               CurrentDll;
    HTTP_FILTER_DLL *   pFilterDll;
    DWORD               dwAuth;
    BOOL                fFinished;
    DWORD               Status;
    DWORD               Win32Status;
    W3_SERVER_INSTANCE* pInst;
    DWORD               dwIOFlags;

    //
    //  Check for valid parameters
    //

    if ( !pfc ||
         !pfc->ServerContext )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[ServerExtensionCallback: Extension passed invalid parameters\r\n"));
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    pFilter = (HTTP_FILTER *) pfc->ServerContext;
    pRequest = pFilter->QueryReq();

    //
    //  Handle the server extension's request
    //

    switch ( sf )
    {
    case SF_REQ_SEND_RESPONSE_HEADER:

        //
        //  Save the client context context because the client may call
        //  Send headers just after changing the context and we're about to
        //  write over it when we do the raw send notifications
        //

        CurrentDll = pFilter->QueryCurrentDll();
        DBG_ASSERT( CurrentDll != INVALID_DLL );

        pFilter->SetClientContext( CurrentDll, pfc->pFilterContext );

        //
        //  If we're doing a send header from a Raw data notification then we need
        //  to notify only the filters down the food chain so save the current
        //  filter and set the start point in the list to the next filter (remember
        //  raw sends walk the list backwards so encryption filters are last)
        //

        dwIOFlags = IO_FLAG_SYNC;

        if ( pFilter->IsInRawNotification() )
        {
            //
            // If this is the first filter in the list, then there's nobody that
            //  needs to be notified
            //

            if ( pFilter->IsFirstFilter( CurrentDll ) )
            {
                dwIOFlags |= IO_FLAG_NO_FILTER;
            }
            else
            {
                pFilter->SetCurrentDll( CurrentDll - 1 );
            }
        }

        if ( pData )
        {
            Status = atoi( (PCSTR)pData );

            if ( Status == HT_DENIED )
            {
                //
                //  Only set the reason as denied_filter if we're not doing
                //  denied access filter processing.
                //

                if ( !pFilter->ProcessingAccessDenied() )
                {
                    pRequest->SetDeniedFlags( SF_DENIED_FILTER );
                }

                Win32Status = ERROR_ACCESS_DENIED;
                pRequest->SetAuthenticationRequested( TRUE );

                //
                // If we do not have Metadata yet but we have an instance
                // then read metadata so that WWW authentication headers can
                // be properly generated
                //

                if ( pRequest->QueryW3Instance() &&
                     !pRequest->QueryMetaData() &&
                     pRequest->QueryHeaderList()->FastMapQueryValue( HM_URL ) )
                {
                    ((HTTP_REQUEST*)pRequest)->OnURL( (LPSTR)pRequest->QueryHeaderList()
                            ->FastMapQueryValue( HM_URL ) );
                }
            }
            else
            {
                Win32Status = NO_ERROR;
            }
        }
        else
        {
            Status = HT_OK;
            Win32Status = NO_ERROR;
        }

        pRequest->SetState( pRequest->QueryState(),
                            Status,
                            Win32Status );


        if ( !pRequest->SendHeader( (CHAR *) pData,
                                    (((CHAR *) ul1) ? ((CHAR *) ul1) : "\r\n"),
                                    dwIOFlags,
                                    &fFinished ))
        {
            pFilter->SetCurrentDll( CurrentDll );
            return FALSE;
        }

        //
        //  CODEWORK - need general method of handling an early finish
        //  indication (have flag and drop subsequent sends in bit bucket?
        //  return an error to filter?)
        //

        DBG_ASSERT( !fFinished );

        pFilter->SetCurrentDll( CurrentDll );
        break;

    case SF_REQ_ADD_HEADERS_ON_DENIAL:
        {
            BOOL fOK = TRUE;
            DWORD cb = 0;
            CHAR *pchHeaders = NULL;

            if ( !pData )
            {
                SetLastError( ERROR_INVALID_PARAMETER );
                return FALSE;
            }

            fOK = pRequest->QueryDenialHeaders()->Append( (CHAR *) pData );

            if ( fOK )
            {
                //
                // Need to make sure that the header ends with a CR-LF
                //
                cb = pRequest->QueryDenialHeaders()->QueryCCH();
                pchHeaders = pRequest->QueryDenialHeaders()->QueryStr();

                if ( cb < 2 ||
                     ( !(pchHeaders[cb - 2] == '\r' && pchHeaders[cb - 1] == '\n' ) ) )
                {
                    fOK = pRequest->QueryDenialHeaders()->Append( (CHAR *) "\r\n" );
                }
            }

            return fOK;
        }

    case SF_REQ_SET_NEXT_READ_SIZE:

        if ( (ul1 == 0) || (ul1 > 0x8000000) )
        {
            SetLastError( ERROR_INVALID_PARAMETER );
            return FALSE;
        }

        pFilter->SetNextReadSize( (DWORD) ul1 );
        return TRUE;

    case SF_REQ_SET_PROXY_INFO:

        //
        //  ul1 contains the proxy flags to set for this request (which right
        //  now is only On or Off).
        //

        pRequest->SetProxyRequest( (DWORD) ul1 & 0x00000001 );
        break;

    case SF_REQ_SET_CERTIFICATE_INFO:

        pFilterDll = pFilter->QueryFilterList()->QueryDll(
                         pFilter->QueryCurrentDll() );

        return pRequest->SetCertificateInfo(
                (PHTTP_FILTER_CERTIFICATE_INFO)pData,
                (CtxtHandle*)ul1,
                (HANDLE)ul2,
                pFilterDll );

    case SF_REQ_GET_PROPERTY:

        pInst = pRequest->QueryW3InstanceAggressively();

        dwAuth = pRequest->QueryMetaData() ? pRequest->QueryAuthentication() : 0;

        switch ( ul1 )
        {
            case SF_PROPERTY_GET_INSTANCE_ID:
                *(LPVOID*)pData = (LPVOID)pInst;
                break;

#if 0 // Unused
            case SF_PROPERTY_CLIENT_CERT_ENABLED:
                *(LPBOOL)pData = !!(dwAuth & INET_INFO_AUTH_CERT_AUTH);
                break;
#endif
            case SF_PROPERTY_MD5_ENABLED:
                *(LPBOOL)pData = !!(dwAuth & INET_INFO_AUTH_MD5_AUTH);
                break;
#if 0 // Unused
            case SF_PROPERTY_DIR_MAP_CERT:
                *(LPBOOL)pData = !!(dwAuth & INET_INFO_CERT_MAP);
                break;
#endif
            
            case SF_PROPERTY_DIGEST_SSP_ENABLED:
                if ( pRequest->QueryMetaData() )
                {
                    *(LPBOOL)pData = pRequest->QueryMetaData()->QueryUseDigestSSP();
                }
                else
                {
                    *(LPBOOL)pData = FALSE;
                }
                break;
            
            case SF_PROPERTY_GET_CERT11_MAPPER:
                if ( pInst )
                {
                    *(LPVOID*)pData = pInst->QueryMapper( MT_CERT11 );
                }
                else
                {
                    SetLastError( ERROR_INVALID_PARAMETER );
                    return FALSE;
                }
                break;

            case SF_PROPERTY_GET_RULE_MAPPER:
                if ( pInst )
                {
                    *(LPVOID*)pData = pInst->QueryMapper( MT_CERTW );
                }
                else
                {
                    SetLastError( ERROR_INVALID_PARAMETER );
                    return FALSE;
                }
                break;

            case SF_PROPERTY_GET_MD5_MAPPER:
                if ( pInst )
                {
                    *(LPVOID*)pData = pInst->QueryMapper( MT_MD5 );
                }
                else
                {
                    SetLastError( ERROR_INVALID_PARAMETER );
                    return FALSE;
                }
                break;

            case SF_PROPERTY_GET_ITA_MAPPER:
                if ( pInst )
                {
                    *(LPVOID*)pData = pInst->QueryMapper( MT_ITA );
                }
                else
                {
                    SetLastError( ERROR_INVALID_PARAMETER );
                    return FALSE;
                }
                break;

            case SF_PROPERTY_MD_IF:
                *(LPVOID*)pData = (LPVOID) g_pInetSvc->QueryMDObject();
                break;

            case SF_PROPERTY_SSL_CTXT:
                *(LPVOID*)pData = pRequest->QueryAuthenticationObj()->QuerySslCtxtHandle();
                break;

            case SF_PROPERTY_INSTANCE_NUM_ID:
                *(DWORD*)pData = pInst ? pInst->QueryInstanceId() : 0;
                break;

            case SF_PROPERTY_MD_PATH:

                    *(const CHAR**)pData = pInst ? pInst->QueryMDPath() :
                        g_pInetSvc->QueryMDPath();

                break;

            default:
                SetLastError( ERROR_INVALID_PARAMETER );
                return FALSE;
        }
        return TRUE;

    case SF_REQ_NORMALIZE_URL:
        return ((HTTP_REQUEST * )pRequest)->NormalizeUrl( (LPSTR)pData );

    case SF_REQ_DONE_RENEGOTIATE:
        ((HTTP_REQUEST * )pRequest)->DoneRenegotiate( *(LPBOOL)pData );
        break;

    case SF_REQ_SET_NOTIFY:
        switch ( ul1 )
        {
            case SF_NOTIFY_MAPPER_MD5_CHANGED:
            case SF_NOTIFY_MAPPER_ITA_CHANGED:
            case SF_NOTIFY_MAPPER_CERT11_CHANGED:
            case SF_NOTIFY_MAPPER_CERTW_CHANGED:
                return SetFlushMapperNotify( (SF_NOTIFY_TYPE)ul1, (PFN_SF_NOTIFY)pData );

            case SF_NOTIFY_MAPPER_SSLKEYS_CHANGED:
                return SetSllKeysNotify( (PFN_SF_NOTIFY)pData );
        }
        return FALSE;

    case SF_REQ_DISABLE_NOTIFICATIONS:
        return pFilter->DisableNotification( (DWORD) ul1 );

    case SF_REQ_COMPRESSION_FILTER_CHECK:
        return CompressionFilterCheck(pfc,pData,ul1,ul2);

    case HSE_REQ_GET_CERT_INFO_EX: {

        //
        //  Descrption:
        //    Returns the first cert in the request's cert-chain,
        //    only used if using an SSPI package
        //
        //  Input:
        //    pData -   ISA-provided struct
        //              NOTE ISA must allocate buffer within struct
        //
        //  Notes:
        //    Works in-proc or out-of-proc
        //

        //
        // cast ISA-provided ptr to our cert struct
        //

        CERT_CONTEXT_EX *  pCertContextEx = reinterpret_cast
                                                <CERT_CONTEXT_EX *>
                                                ( pData );

        if ( pData == NULL ) {

            DBG_ASSERT( FALSE );

            SetLastError( ERROR_INVALID_PARAMETER );
            return FALSE;
        }

        //
        // pass struct members as individual parameters
        //

        return pRequest->QueryAuthenticationObj()->GetClientCertBlob(
                        pCertContextEx->cbAllocated,
                        &( pCertContextEx->CertContext.dwCertEncodingType ),
                        pCertContextEx->CertContext.pbCertEncoded,
                        &( pCertContextEx->CertContext.cbCertEncoded ),
                        &( pCertContextEx->dwCertificateFlags ) );
    } // case HSE_REQ_GET_CERT_INFO_EX:

    default:
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    return TRUE;
}

BOOL
WINAPI
GetServerVariable(
    HTTP_FILTER_CONTEXT * pfc,
    LPSTR                 lpszVariableName,
    LPVOID                lpvBuffer,
    LPDWORD               lpdwSize
    )
/*++

Routine Description:

    Callback for a filter retrieving a server variable.  These are mostly
    CGI type varaibles

Arguments:

    pfc - Pointer to http filter context
    lpszVariableName - Variable to retrieve
    lpvBuffer - Receives value or '\0' if not found
    lpdwSize - Specifies the size of lpvBuffer, gets set to the number of
        bytes transferred including the '\0'

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    BOOL fReturn = TRUE;  // fast path value

    if ( !pfc ||
         !pfc->ServerContext )
    {
        DBGPRINTF(( DBG_CONTEXT,
                    "[GetServerVariable] Filter sent invalid parameters\n"));
        SetLastError( ERROR_INVALID_PARAMETER );
        fReturn = FALSE;
    } else {
        HTTP_FILTER * pFilter =  (HTTP_FILTER *) pfc->ServerContext;
        HTTP_REQ_BASE * pRequest = pFilter->QueryReq();
        CHAR           tmpStr[MAX_PATH];
        STR            str(tmpStr, MAX_PATH);
        BOOL           fFound;

        //
        //  Get the requested variable and copy it into the supplied buffer
        //

                
        if ( fReturn = pRequest->GetInfo( lpszVariableName,
                                          &str,
                                          &fFound ) ) {
        
            DWORD   cb;
        
            if ( !fFound ) {
                SetLastError( ERROR_INVALID_INDEX );
                fReturn = FALSE;
            } else if ( (cb = str.QueryCB() + sizeof(CHAR)) > * lpdwSize) {
                SetLastError( ERROR_INSUFFICIENT_BUFFER );
                *lpdwSize = cb;
                fReturn = FALSE;
            } else {
                *lpdwSize = cb;
                CopyMemory( lpvBuffer, str.QueryStr(), cb );
                DBG_ASSERT( fReturn);
            }
        }
    }

    return (fReturn);

} // GetServerVariable()

BOOL
WINAPI
AddFilterResponseHeaders(
    HTTP_FILTER_CONTEXT * pfc,
    LPSTR                 lpszHeaders,
    DWORD                 dwReserved
    )
/*++

Routine Description:

    Adds headers specified by the client to send with the response

Arguments:

    pfc - Pointer to http filter context
    lpszHeaders - List of '\r\n' terminated headers followed by '\0'
    dwReserved - must be zero

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    HTTP_FILTER * pFilter;

    if ( !pfc ||
         !pfc->ServerContext )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[GetServerVariable] Filter passed invalid parameters\r\n"));
        SetLastError( ERROR_INVALID_PARAMETER );

        return FALSE;
    }

    pFilter = (HTTP_FILTER *) pfc->ServerContext;

    return pFilter->QueryReq()->QueryAdditionalRespHeaders()->
                Append( (CHAR*) lpszHeaders );
}

BOOL
WINAPI
WriteFilterClient(
    HTTP_FILTER_CONTEXT * pfc,
    LPVOID                Buffer,
    LPDWORD               lpdwBytes,
    DWORD                 dwReserved
    )
/*++

Routine Description:

    Callback for writing data to the client

Arguments:

    pfc - Pointer to http filter context
    Buffer - Pointer to data to send
    lpdwBytes - Number of bytes to send, receives number of bytes sent
    dwReserved - Not used

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    HTTP_FILTER *     pFilter;
    STR               str;
    DWORD             cb;
    HTTP_FILTER_DLL * pFilterDll;
    DWORD             CurrentDll;
    DWORD             dwIOFlags = IO_FLAG_SYNC;

    if ( !pfc ||
         !pfc->ServerContext )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[GetServerVariable] Filter passed invalid parameters\r\n"));
        SetLastError( ERROR_INVALID_PARAMETER );

        return FALSE;
    }

    pFilter = (HTTP_FILTER *) pfc->ServerContext;

    //
    //  Ignore zero length sends
    //

    if ( !*lpdwBytes )
    {
        return TRUE;
    }

    //
    //  Save the client context because the client may call
    //  WriteClient just after changing the context and we're about to
    //  write over it when we do the raw send notifications
    //

    CurrentDll = pFilter->QueryCurrentDll();
    pFilter->SetClientContext( CurrentDll, pfc->pFilterContext );

    //
    //  If we're doing a WriteClient from a Raw data notification then we need
    //  to notify only the filters down the food chain so save the current
    //  filter and set the start point in the list to the next filter (remember
    //  raw sends walk the list backwards to encryption filters are last)
    //

    if ( pFilter->IsInRawNotification() )
    {
        //
        //  If this is the first filter in the list, then there's nobody that
        //  needs to be notified
        //

        if ( pFilter->IsFirstFilter( CurrentDll ) )
        {
            dwIOFlags |= IO_FLAG_NO_FILTER;
        }
        else
        {
            pFilter->SetCurrentDll( CurrentDll - 1 );
        }
    }

    if ( !pFilter->QueryReq()->WriteFile( Buffer,
                                          *lpdwBytes,
                                          lpdwBytes,
                                          dwIOFlags ))
    {
        pFilter->SetCurrentDll( CurrentDll );
        return FALSE;
    }

    pFilter->SetCurrentDll( CurrentDll );

    return TRUE;
}

BOOL
WINAPI
GetFilterHeader(
    struct _HTTP_FILTER_CONTEXT * pfc,
    LPSTR                         lpszName,
    LPVOID                        lpvBuffer,
    LPDWORD                       lpdwSize
    )
/*++

Routine Description:

    Callback for retrieving unprocessed headers

Arguments:

    pfc - Pointer to http filter context
    lpszName - Name of header to retrieve ("User-Agent:")
    lpvBuffer - Buffer to receive the value of the header
    lpdwSize - Number of bytes in lpvBuffer, receives number of bytes copied
        including the '\0'

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    HTTP_FILTER *  pFilter;
    HTTP_HEADERS * pHeaderList;
    CHAR *         pszValue;
    DWORD          cbNeeded;

    if ( !pfc ||
         !pfc->ServerContext )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[GetFilterHeader] Extension passed invalid parameters\r\n"
                    ));
        SetLastError( ERROR_INVALID_PARAMETER );

        return FALSE;
    }

    pFilter = (HTTP_FILTER *) pfc->ServerContext;
    pHeaderList = pFilter->QueryReq()->QueryHeaderList();

    //
    //  First, see if the specified header is in the list
    //

    pszValue = pHeaderList->FindValue( lpszName, &cbNeeded );

    //
    //  If not found, terminate the buffer and set the required size to the
    //  terminator
    //

    if ( !pszValue )
    {
        SetLastError( ERROR_INVALID_INDEX );
        return FALSE;
    }

    //
    //  Found the value, copy it if there's space
    //

    if ( ++cbNeeded > *lpdwSize )
    {
        *lpdwSize = cbNeeded;
        SetLastError( ERROR_INSUFFICIENT_BUFFER );
        return FALSE;
    }

    *lpdwSize = cbNeeded;
    memcpy( lpvBuffer, pszValue, cbNeeded );

    return TRUE;
}

BOOL
WINAPI
SetFilterHeader(
    struct _HTTP_FILTER_CONTEXT * pfc,
    LPSTR                         lpszName,
    LPSTR                         lpszValue
    )
/*++

Routine Description:

    The specified header is added to the list with the specified value.  If
    any other occurrences of the header are found, they are removed from the
    list.

    Specifying a blank value will remove the header from the list

    This will generally be used to replace the value of an existing header

Arguments:

    pfc - Pointer to http filter context
    lpszName - Name of header to set ("User-Agent:")
    lpszValue - value of lpszValue

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    HTTP_FILTER * pFilter;
    HTTP_HEADERS *pHeaderList;
    VOID *        pvCookie = NULL;
    CHAR *        pszListHeader;
    CHAR *        pszListValue;

    if ( !pfc ||
         !pfc->ServerContext )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[SetFilterHeader] Extension passed invalid parameters\r\n"));
        SetLastError( ERROR_INVALID_PARAMETER );

        return FALSE;
    }

    pFilter = (HTTP_FILTER *) pfc->ServerContext;
    pHeaderList = pFilter->QueryReq()->QueryHeaderList();

    //
    //  Remove all occurrences of the value, then add the one we want
    //

    pHeaderList->CancelHeader( lpszName );

    //
    //  Only add the value if they specified a replacement
    //

    if ( lpszValue && *lpszValue )
    {
        return pHeaderList->StoreHeader( lpszName, lpszValue );
    }

    return TRUE;
}

BOOL
WINAPI
AddFilterHeader(
    struct _HTTP_FILTER_CONTEXT * pfc,
    LPSTR                         lpszName,
    LPSTR                         lpszValue
    )
/*++

Routine Description:

    The specified header is added to the list with the specified value.

Arguments:

    pfc - Pointer to http filter context
    lpszName - Name of header to set ("User-Agent:")
    lpszValue - value of lpszValue

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    HTTP_FILTER * pFilter;
    HTTP_HEADERS *pHeaderList;
    VOID *        pvCookie = NULL;
    CHAR *        pszListHeader;
    CHAR *        pszListValue;

    if ( !pfc ||
         !pfc->ServerContext )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[AddFilterHeader] Extension passed invalid parameters\r\n"));
        SetLastError( ERROR_INVALID_PARAMETER );

        return FALSE;
    }

    pFilter = (HTTP_FILTER *) pfc->ServerContext;
    pHeaderList = pFilter->QueryReq()->QueryHeaderList();

    return pHeaderList->StoreHeader( lpszName, lpszValue);
} // AddFilterHeader()

BOOL
WINAPI
GetSendHeader(
    struct _HTTP_FILTER_CONTEXT * pfc,
    LPSTR                         lpszName,
    LPVOID                        lpvBuffer,
    LPDWORD                       lpdwSize
    )
/*++

Routine Description:

    Callback for retrieving headers about to be sent to the client

Arguments:

    pfc - Pointer to http filter context
    lpszName - Name of header to retrieve ("User-Agent:")
    lpvBuffer - Buffer to receive the value of the header
    lpdwSize - Number of bytes in lpvBuffer, receives number of bytes copied
        including the '\0'

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    HTTP_FILTER * pFilter;
    CHAR *        pszValue;
    DWORD         cbNeeded = 0;
    PARAM_LIST *  pHeaderList;
    PVOID         Cookie = NULL;
    CHAR *        pszField;
    DWORD         cbField;
    DWORD         cbValue;
    BOOL          fFound = FALSE;
    CHAR *        pszTail = (CHAR *) lpvBuffer;
    HTTP_REQ_BASE*  pRequest;


    if ( !pfc ||
         !pfc->ServerContext )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[GetFilterHeader] Extension passed invalid parameters\r\n"));
        SetLastError( ERROR_INVALID_PARAMETER );

        return FALSE;
    }

    pFilter = (HTTP_FILTER *) pfc->ServerContext;
    DBG_ASSERT( pFilter != NULL );

    pRequest = (HTTP_REQ_BASE*) pFilter->QueryReq();
    DBG_ASSERT( pRequest != NULL );

    if ( pRequest->IsPointNine() )
    {
        SetLastError( ERROR_NOT_SUPPORTED );
        return FALSE;
    }

    if ( !pFilter->AreSendHeadersParsed() &&
         !pFilter->ParseSendHeaders() )
    {
        return FALSE;
    }

    pHeaderList = pFilter->QuerySendHeaders();

    //
    //  First, see if the specified header is in the list
    //

    while ( Cookie = pHeaderList->NextPair( Cookie,
                                            &pszField,
                                            &cbField,
                                            &pszValue,
                                            &cbValue ))
    {
        if ( !_stricmp( pszField, lpszName ))
        {
            //
            //  If there is room, copy it in, note we need a comma separator
            //  after each subsequent entry
            //

            if ( (cbNeeded + cbValue + 1 + (fFound ? 1 : 0)) <= *lpdwSize )
            {
                if ( fFound )
                {
                    *pszTail++ = ',';
                }

                memcpy( pszTail, pszValue, cbValue + 1 );
                pszTail += cbValue;
            }

            cbNeeded += cbValue + (fFound ? 1 : 0);
            fFound = TRUE;
        }
    }

    //
    //  If not found, tell the caller
    //

    if ( !fFound )
    {
        SetLastError( ERROR_INVALID_INDEX );
        return FALSE;
    }

    //
    //  Found the value, copy it if there's space
    //

    if ( ++cbNeeded > *lpdwSize )
    {
        *lpdwSize = cbNeeded;
        SetLastError( ERROR_INSUFFICIENT_BUFFER );
        return FALSE;
    }

    *lpdwSize = cbNeeded;
    return TRUE;
}

BOOL
WINAPI
SetSendHeader(
    struct _HTTP_FILTER_CONTEXT * pfc,
    LPSTR                         lpszName,
    LPSTR                         lpszValue
    )
/*++

Routine Description:

    The specified header is added to the list with the specified value.  If
    any other occurrences of the header are found, they are removed from the
    list.

    Specifying a blank value will remove the header from the list

    This will generally be used to replace the value of an existing header

Arguments:

    pfc - Pointer to http filter context
    lpszName - Name of header to set ("User-Agent:")
    lpszValue - value of lpszValue

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    HTTP_FILTER * pFilter;
    PARAM_LIST *  pHeaderList;
    HTTP_REQ_BASE * pRequest;

    if ( !pfc ||
         !pfc->ServerContext )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[SetFilterHeader] Extension passed invalid parameters\r\n"));
        SetLastError( ERROR_INVALID_PARAMETER );

        return FALSE;
    }

    pFilter = (HTTP_FILTER *) pfc->ServerContext;
    DBG_ASSERT( pFilter != NULL );

    pRequest = (HTTP_REQ_BASE*) pFilter->QueryReq();
    DBG_ASSERT( pRequest != NULL );

    if ( pRequest->IsPointNine() )
    {
        SetLastError( ERROR_NOT_SUPPORTED );
        return FALSE;
    }

    if ( !pFilter->AreSendHeadersParsed() &&
         !pFilter->ParseSendHeaders() )
    {
        return FALSE;
    }

    pHeaderList = pFilter->QuerySendHeaders();

    //
    //  Remove all occurrences of the value, then add the one we want
    //

    pFilter->SetSendHeadersChanged( TRUE );

    pHeaderList->RemoveEntry( lpszName );

    //
    //  Only add the value if they specified a replacement
    //

    if ( lpszValue && *lpszValue )
    {
        return pHeaderList->AddEntry( lpszName, lpszValue );
    }

    return TRUE;
}

BOOL
WINAPI
AddSendHeader(
    struct _HTTP_FILTER_CONTEXT * pfc,
    LPSTR                         lpszName,
    LPSTR                         lpszValue
    )
/*++

Routine Description:

    The specified header is added to the list with the specified value.

Arguments:

    pfc - Pointer to http filter context
    lpszName - Name of header to set ("User-Agent:")
    lpszValue - value of lpszValue

Return Value:

    TRUE on success, FALSE on failure

--*/
{
    HTTP_FILTER * pFilter;
    HTTP_REQ_BASE*  pRequest;

    if ( !pfc ||
         !pfc->ServerContext )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[AddFilterHeader] Extension passed invalid parameters\r\n"));
        SetLastError( ERROR_INVALID_PARAMETER );

        return FALSE;
    }

    pFilter = (HTTP_FILTER *) pfc->ServerContext;
    DBG_ASSERT( pFilter != NULL );

    pRequest = (HTTP_REQ_BASE*) pFilter->QueryReq();
    DBG_ASSERT( pRequest != NULL );

    if ( pRequest->IsPointNine() )
    {
        SetLastError( ERROR_NOT_SUPPORTED );
        return FALSE;
    }

    if ( !pFilter->AreSendHeadersParsed() &&
         !pFilter->ParseSendHeaders() )
    {
        return FALSE;
    }

    pFilter->SetSendHeadersChanged( TRUE );

    return pFilter->QuerySendHeaders()->AddEntry( lpszName, lpszValue );
}

VOID *
WINAPI
AllocFilterMem(
    struct _HTTP_FILTER_CONTEXT * pfc,
    DWORD                         cbSize,
    DWORD                         dwReserved
    )
{
    HTTP_FILTER * pFilter;
    FILTER_POOL_ITEM * pfpi;

    if ( !pfc ||
         !pfc->ServerContext )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[GetFilterHeader] Extension passed invalid parameters\r\n"));
        SetLastError( ERROR_INVALID_PARAMETER );

        return NULL;
    }

    pFilter = (HTTP_FILTER *) pfc->ServerContext;

    pfpi = FILTER_POOL_ITEM::CreateMemPoolItem( cbSize );

    if ( pfpi )
    {
        InsertHeadList( pFilter->QueryPoolHead(), &pfpi->_ListEntry );
        return pfpi->_pvData;
    }

    return NULL;
}

#if 0
PVOID
WINAPI
ServerFilterResize(
    struct _HTTP_FILTER_CONTEXT * pfc,
    DWORD                         cbSize
    )
{
    HTTP_FILTER * pFilter;
    HTTP_FILTER_RAW_DATA * phfrd;

    if ( !pfc ||
         !pfc->ServerContext )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[ServerFilterResize] Extension passed invalid parameters\r\n"));
        SetLastError( ERROR_INVALID_PARAMETER );

        return NULL;
    }

    pFilter = (HTTP_FILTER *) pfc->ServerContext;
    phfrd   = (HTTP_FILTER_RAW_DATA *) pFilter->QueryNotificationStruct();

    //
    //  Only reallocate if necessary
    //

    if ( phfrd->cbOutBuffer < cbSize )
    {
        if ( !pFilter->QueryRecvTrans()->Resize( cbSize ) )
        {
            SetLastError( ERROR_NOT_ENOUGH_MEMORY );
            return NULL;
        }

        phfrd->pvOutData = pFilter->QueryRecvTrans()->QueryPtr();

        phfrd->cbOutBuffer = cbSize;
    }

    return phfrd->pvOutData;
}

#endif

PATQ_CONTEXT
HTTP_FILTER::QueryAtqContext(
    VOID
    ) const
{
    return _pRequest->QueryClientConn()->QueryAtqContext();
}

BOOL
WINAPI
GetUserToken(
    struct _HTTP_FILTER_CONTEXT * pfc,
    HANDLE *                      phToken
)
/*++

Routine Description:

    Get impersonated user token

Arguments:

    pfc - Filter context
    phToken - Filled with impersonation token
    
Return Value:

    TRUE on success, FALSE on failure

--*/
{
    HTTP_FILTER * pFilter;
    HTTP_REQ_BASE*  pRequest;

    if ( !pfc ||
         !pfc->ServerContext ||
         !phToken )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "[AddFilterHeader] Extension passed invalid parameters\r\n"));
        SetLastError( ERROR_INVALID_PARAMETER );

        return FALSE;
    }

    pFilter = (HTTP_FILTER *) pfc->ServerContext;
    DBG_ASSERT( pFilter != NULL );
    
    pRequest = (HTTP_REQ_BASE*) pFilter->QueryReq();
    DBG_ASSERT( pRequest != NULL );
    
    *phToken = pRequest->QueryUserImpersonationHandle();
    
    return TRUE;
}



BOOL
WINAPI
CompressionFilterCheck(
    HTTP_FILTER_CONTEXT *pfc,
    LPVOID              lpszEncodingString,
    ULONG_PTR           lpszVerbString,
    ULONG_PTR           sizesForBuffers
    )
/*++

Routine Description:

    Special server suport function for compression filter to determine the need to 
    compress given request

Arguments:

    pfc - Pointer to http filter context
    lpszEncodingString - String containing accept encoding header if any
    lpszVerbString - String containing method 
    sizesForBuffers - max size for buffers
Return Value:

    TRUE if request is eligible for compression

--*/
{
    BOOL fReturn = FALSE;  // fast path value

    if ( !pfc ||
         !pfc->ServerContext )
    {
        DBGPRINTF(( DBG_CONTEXT,
                    "[CompressionFilterCheck] Filter sent invalid parameters\n"));
        SetLastError( ERROR_INVALID_PARAMETER );
    } else {
        HTTP_FILTER * pFilter =  (HTTP_FILTER *) pfc->ServerContext;
        HTTP_REQ_BASE * pRequest = pFilter->QueryReq();
        HTTP_HEADERS    *pHttpHeaders;
        DWORD           len;
        PCHAR           pHdr_HM_ACE, pHdr_HM_MET;
        
        pHttpHeaders = pRequest->QueryHeaderList();


        *(PCHAR)lpszEncodingString = 0;
        *((PCHAR)lpszVerbString) = 0;


        //
        // Get the Accept-Encoding sent by the client, if any.  If we cannot
        // get this header, or if the size of the header is too large, then
        // we will make no effort to compress this request.
        //



        pHdr_HM_ACE = (PCHAR)pHttpHeaders->FastMapQueryValue (HM_ACE);
        
        if ( pHdr_HM_ACE )
        {
            pHdr_HM_MET = (PCHAR)pHttpHeaders->FastMapQueryValue(HM_MET);

            if (pHdr_HM_MET)
            {
                len = strlen((PCHAR)pHdr_HM_ACE)+1;
                if (len > sizesForBuffers)
                {
                    SetLastError (ERROR_INSUFFICIENT_BUFFER);
                    goto exit_point;
                }
                else
                {
                    strcpy ((PCHAR)lpszEncodingString,pHdr_HM_ACE);
                }


                len = strlen((PCHAR)pHdr_HM_MET)+1;
                if (len > sizesForBuffers)
                {
                    SetLastError (ERROR_INSUFFICIENT_BUFFER);
                    goto exit_point;
                }
                else
                {
                    strcpy ((PCHAR)lpszVerbString,pHdr_HM_MET);
                    fReturn = TRUE;
                }

            }
        }
 
    }

exit_point:
    return fReturn;
}