/*++

   Copyright    (c)    1999    Microsoft Corporation

   Module Name :

        w3response.cxx

   Abstract:

        W3_RESPONSE object (a friendly wrapper of UL_HTTP_RESPONSE)
        
   Author:

        Bilal Alam (BAlam)      10-Dec-99

   Project:

        ULW3.DLL
--*/

#include "precomp.hxx"

//
// HTTP Status codes
//

HTTP_STATUS HttpStatusOk                = { 200, REASON("OK") };
HTTP_STATUS HttpStatusPartialContent    = { 206, REASON("Partial Content") };
HTTP_STATUS HttpStatusMultiStatus       = { 207, REASON("Multi Status") };
HTTP_STATUS HttpStatusMovedPermanently  = { 301, REASON("Moved Permanently") };
HTTP_STATUS HttpStatusRedirect          = { 302, REASON("Redirect") };
HTTP_STATUS HttpStatusMovedTemporarily  = { 307, REASON("Moved Temporarily") };
HTTP_STATUS HttpStatusNotModified       = { 304, REASON("Not Modified") };
HTTP_STATUS HttpStatusBadRequest        = { 400, REASON("Bad Request") };
HTTP_STATUS HttpStatusUnauthorized      = { 401, REASON("Unauthorized") };
HTTP_STATUS HttpStatusForbidden         = { 403, REASON("Forbidden") };
HTTP_STATUS HttpStatusNotFound          = { 404, REASON("Not Found") };
HTTP_STATUS HttpStatusMethodNotAllowed  = { 405, REASON("Method Not Allowed") };
HTTP_STATUS HttpStatusNotAcceptable     = { 406, REASON("Not Acceptable") };
HTTP_STATUS HttpStatusProxyAuthRequired = { 407, REASON("Proxy Authorization Required") };
HTTP_STATUS HttpStatusPreconditionFailed= { 412, REASON("Precondition Failed") };
HTTP_STATUS HttpStatusUrlTooLong        = { 414, REASON("URL Too Long") };
HTTP_STATUS HttpStatusRangeNotSatisfiable={ 416, REASON("Requested Range Not Satisfiable") };
HTTP_STATUS HttpStatusLockedError       = { 423, REASON("Locked Error") };
HTTP_STATUS HttpStatusServerError       = { 500, REASON("Internal Server Error") };
HTTP_STATUS HttpStatusNotImplemented    = { 501, REASON("Not Implemented") };
HTTP_STATUS HttpStatusBadGateway        = { 502, REASON("Bad Gateway") };
HTTP_STATUS HttpStatusServiceUnavailable= { 503, REASON("Service Unavailable") };
HTTP_STATUS HttpStatusGatewayTimeout    = { 504, REASON("Gateway Timeout") };

//
// HTTP SubErrors.  This goo is used in determining the proper default error
// message to send to the client when an applicable custom error is not 
// configured
// 
// As you can see, some sub errors have no corresponding resource string. 
// (signified by a 0 index) 
//

HTTP_SUB_ERROR HttpNoSubError           = { 0, 0 };
HTTP_SUB_ERROR Http401BadLogon          = { MD_ERROR_SUB401_LOGON, 0 };
HTTP_SUB_ERROR Http401Config            = { MD_ERROR_SUB401_LOGON_CONFIG, 0 };
HTTP_SUB_ERROR Http401Resource          = { MD_ERROR_SUB401_LOGON_ACL, 0 };
HTTP_SUB_ERROR Http401Filter            = { MD_ERROR_SUB401_FILTER, 0 };
HTTP_SUB_ERROR Http401Application       = { MD_ERROR_SUB401_APPLICATION, 0 };
HTTP_SUB_ERROR Http403ExecAccessDenied  = { MD_ERROR_SUB403_EXECUTE_ACCESS_DENIED, IDS_EXECUTE_ACCESS_DENIED };
HTTP_SUB_ERROR Http403ReadAccessDenied  = { MD_ERROR_SUB403_READ_ACCESS_DENIED, IDS_READ_ACCESS_DENIED };
HTTP_SUB_ERROR Http403WriteAccessDenied = { MD_ERROR_SUB403_WRITE_ACCESS_DENIED, IDS_WRITE_ACCESS_DENIED };
HTTP_SUB_ERROR Http403SSLRequired       = { MD_ERROR_SUB403_SSL_REQUIRED, IDS_SSL_REQUIRED };
HTTP_SUB_ERROR Http403SSL128Required    = { MD_ERROR_SUB403_SSL128_REQUIRED, IDS_SSL128_REQUIRED };
HTTP_SUB_ERROR Http403IPAddressReject   = { MD_ERROR_SUB403_ADDR_REJECT, IDS_ADDR_REJECT };
HTTP_SUB_ERROR Http403CertRequired      = { MD_ERROR_SUB403_CERT_REQUIRED, IDS_CERT_REQUIRED };
HTTP_SUB_ERROR Http403SiteAccessDenied  = { MD_ERROR_SUB403_SITE_ACCESS_DENIED, IDS_SITE_ACCESS_DENIED };      
HTTP_SUB_ERROR Http403TooManyUsers      = { MD_ERROR_SUB403_TOO_MANY_USERS, IDS_TOO_MANY_USERS };          
HTTP_SUB_ERROR Http403PasswordChange    = { MD_ERROR_SUB403_PWD_CHANGE, IDS_PWD_CHANGE };
HTTP_SUB_ERROR Http403MapperDenyAccess  = { MD_ERROR_SUB403_MAPPER_DENY_ACCESS, IDS_MAPPER_DENY_ACCESS };     
HTTP_SUB_ERROR Http403CertRevoked       = { MD_ERROR_SUB403_CERT_REVOKED, IDS_CERT_REVOKED };
HTTP_SUB_ERROR Http403DirBrowsingDenied = { MD_ERROR_SUB403_DIR_LIST_DENIED, IDS_DIR_LIST_DENIED };        
HTTP_SUB_ERROR Http403CertInvalid       = { MD_ERROR_SUB403_CERT_BAD, IDS_CERT_BAD };               
HTTP_SUB_ERROR Http403CertTimeInvalid   = { MD_ERROR_SUB403_CERT_TIME_INVALID, IDS_CERT_TIME_INVALID };
HTTP_SUB_ERROR Http502Timeout           = { MD_ERROR_SUB502_TIMEOUT, IDS_CGI_APP_TIMEOUT };
HTTP_SUB_ERROR Http502PrematureExit     = { MD_ERROR_SUB502_PREMATURE_EXIT, IDS_BAD_CGI_APP };

ALLOC_CACHE_HANDLER * SEND_RAW_BUFFER::sm_pachSendRawBuffers;

HRESULT
W3_RESPONSE::SetHeader(
    DWORD           ulResponseHeaderIndex,
    CHAR *          pszHeaderValue,
    DWORD           cchHeaderValue,
    BOOL            fAppend
)
/*++

Routine Description:
    
    Set a response based on known header index

Arguments:

    ulResponseHeaderIndex - index
    pszHeaderValue - Header value
    cchHeaderValue - Number of characters (without \0) in pszHeaderValue
    
Return Value:

    HRESULT

--*/
{
    STACK_STRA    ( strNewHeaderValue, 32);
    CHAR *        pszNewHeaderValue = NULL;
    HRESULT       hr;

    //
    // If value is too long, reject now
    //
    if (cchHeaderValue > MAXUSHORT)
    {
        return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
    }
    
    DBG_ASSERT( ulResponseHeaderIndex < HttpHeaderResponseMaximum );

    if ( _responseMode == RESPONSE_MODE_RAW )
    {
        hr = SwitchToParsedMode();
        if ( FAILED( hr ) )
        {
            return NULL;
        }
            
        DBG_ASSERT( _responseMode == RESPONSE_MODE_PARSED );
    }
    
    if ( fAppend )
    {
        CHAR * headerValue = GetHeader( ulResponseHeaderIndex );
        if ( headerValue == NULL )
        {
            fAppend = FALSE;
        }
        else 
        {
            hr = strNewHeaderValue.Append( headerValue );
            if ( FAILED( hr ) )
            {
                return hr;
            }

            hr = strNewHeaderValue.Append( ",", 1 );
            if ( FAILED( hr ) )
            {
                return hr;
            }

            hr = strNewHeaderValue.Append( pszHeaderValue,
                                           cchHeaderValue );
            if ( FAILED( hr ) )
            {
                return hr;
            }

            cchHeaderValue = strNewHeaderValue.QueryCCH();
        }
    }

    //
    // Regardless of the "known"osity of the header, we will have to 
    // copy the value.  Do so now.
    //

    hr = _HeaderBuffer.AllocateSpace( fAppend ? strNewHeaderValue.QueryStr() :
                                                pszHeaderValue,
                                      cchHeaderValue,
                                      &pszNewHeaderValue );
    if ( FAILED( hr ) )
    {
        return hr;
    }

    //
    // Set the header
    //
    
    return SetHeaderByReference( ulResponseHeaderIndex,
                                 pszNewHeaderValue,
                                 cchHeaderValue );
}

HRESULT
W3_RESPONSE::SetHeader(
    CHAR *              pszHeaderName,
    DWORD               cchHeaderName,
    CHAR *              pszHeaderValue,
    DWORD               cchHeaderValue,
    BOOL                fAppend,
    BOOL                fForceParsed,
    BOOL                fAlwaysAddUnknown
)
/*++

Routine Description:
    
    Set a response based on header name

Arguments:

    pszHeaderName - Points to header name
    cchHeaderName - Number of characters (without \0) in pszHeaderName
    pszHeaderValue - Points to the header value
    cchHeaderValue - Number of characters (without \0) in pszHeaderValue
    fAppend - Should we remove any existing value
    fForceParsed - Regardless of mode, set the header structurally
    fAlwaysAddUnknown - Add as a unknown header always
    
Return Value:

    HRESULT

--*/
{
    DWORD                   cHeaders;
    HTTP_UNKNOWN_HEADER*    pHeader;
    CHAR *                  pszNewName = NULL;
    CHAR *                  pszNewValue = NULL;
    HRESULT                 hr;
    ULONG                   ulHeaderIndex;
    STACK_STRA(             strOldHeaderValue, 128 );

    //
    // If name/value is too long, reject now
    //
    if (cchHeaderName > MAXUSHORT ||
        cchHeaderValue > MAXUSHORT)
    {
        return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
    }

    //
    // Try to stay in raw mode if we're already in that mode
    // 
    // If we are not appending, that means we are just adding a new header
    // so we can just append 
    //
    
    if ( !fForceParsed )
    {
        if ( _responseMode == RESPONSE_MODE_RAW &&
             !fAppend )
        {
            DBG_ASSERT( QueryChunks()->DataChunkType == HttpDataChunkFromMemory );
            DBG_ASSERT( QueryChunks()->FromMemory.pBuffer == _strRawCoreHeaders.QueryStr() );
        
            hr = _strRawCoreHeaders.Append( pszHeaderName, cchHeaderName );
            if ( FAILED( hr ) )
            {
                return hr;
            }
        
            hr = _strRawCoreHeaders.Append( ": ", 2 );
            if ( FAILED( hr ) )
            {
                return hr;
            }
        
            hr = _strRawCoreHeaders.Append( pszHeaderValue, cchHeaderValue );
            if ( FAILED( hr ) )
            {
                return hr;
            }
        
            hr = _strRawCoreHeaders.Append( "\r\n", 2 );
            if ( FAILED( hr ) )
            {
                return hr;
            }
            
            //
            // Patch the headers back in
            //
            
            QueryChunks()->FromMemory.pBuffer = _strRawCoreHeaders.QueryStr();
            QueryChunks()->FromMemory.BufferLength = _strRawCoreHeaders.QueryCB();
        
            return NO_ERROR;
        }
        else 
        {
            //
            // No luck.  We'll have to parse the headers and switch into parsed
            // mode.
            //
        
            if ( _responseMode == RESPONSE_MODE_RAW )
            {
                hr = SwitchToParsedMode();
                if ( FAILED( hr ) )
                {
                    return hr;
                }
            }
            
            DBG_ASSERT( _responseMode == RESPONSE_MODE_PARSED );
        }
    }

    //
    // If we're appending, then get the old header value (if any) and 
    // append the new value (with a comma delimiter)
    //

    if ( fAppend )
    {
        hr = GetHeader( pszHeaderName,
                        &strOldHeaderValue );
        if ( FAILED( hr ) )
        {
            fAppend = FALSE;
            hr = NO_ERROR;
        }
        else 
        {
            hr = strOldHeaderValue.Append( ",", 1 );
            if ( FAILED( hr ) )
            {
                return hr;
            }
            
            hr = strOldHeaderValue.Append( pszHeaderValue,
                                           cchHeaderValue );
            if ( FAILED( hr ) )
            {
                return hr;
            }

            cchHeaderValue = strOldHeaderValue.QueryCCH();
            
            DeleteHeader( pszHeaderName );
        }
    }

    //
    // Regardless of the "known"osity of the header, we will have to 
    // copy the value.  Do so now.
    //
    
    hr = _HeaderBuffer.AllocateSpace( fAppend ? strOldHeaderValue.QueryStr() :
                                                pszHeaderValue,
                                      cchHeaderValue,
                                      &pszNewValue );
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    //
    // Is this a known header?  If so, we can just set by reference now
    // since we have copied the header value
    // 

    if ( !fAlwaysAddUnknown )
    {
        ulHeaderIndex = RESPONSE_HEADER_HASH::GetIndex( pszHeaderName );
        if ( ulHeaderIndex != UNKNOWN_INDEX )
        {
            DBG_ASSERT( ulHeaderIndex < HttpHeaderResponseMaximum );
    
            return SetHeaderByReference( ulHeaderIndex,
                                         pszNewValue,
                                         cchHeaderValue,
                                         fForceParsed );
        }
    }
    
    //
    // OK.  This is an unknown header.  Make a copy of the header name as
    // well and proceed the long way.
    //
    
    hr = _HeaderBuffer.AllocateSpace( pszHeaderName,
                                      cchHeaderName,
                                      &pszNewName );
    if ( FAILED( hr ) )
    {
        return hr;
    }

    cHeaders = ++_ulHttpResponse.Headers.UnknownHeaderCount;
    
    if ( cHeaders * sizeof( HTTP_UNKNOWN_HEADER ) 
          > _bufUnknownHeaders.QuerySize() )
    {
        if ( !_bufUnknownHeaders.Resize( cHeaders * 
                                         sizeof( HTTP_UNKNOWN_HEADER ),
                                         512 ) )
        {
            return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
        }
    }
    _ulHttpResponse.Headers.pUnknownHeaders = (HTTP_UNKNOWN_HEADER*)
                                              _bufUnknownHeaders.QueryPtr();
    
    //
    // We should have a place to put the header now!
    //
    
    pHeader = &(_ulHttpResponse.Headers.pUnknownHeaders[ cHeaders - 1 ]);
    pHeader->pName = pszNewName;
    pHeader->NameLength = cchHeaderName;
    pHeader->pRawValue = pszNewValue;
    pHeader->RawValueLength = (USHORT)cchHeaderValue;
    
    _fResponseTouched = TRUE;
    
    return S_OK;
}

HRESULT
W3_RESPONSE::SetHeaderByReference(
    DWORD           ulResponseHeaderIndex,
    CHAR *          pszHeaderValue,
    DWORD           cchHeaderValue,
    BOOL            fForceParsed
)
/*++

Routine Description:
    
    Set a header value by reference.  In other words, the caller takes the
    reponsibility of managing the memory referenced.  The other setheader
    methods copy the header values to a private buffer.

Arguments:

    ulResponseHeaderIndex - index
    pszHeaderValue - Header value
    cbHeaderValue - Size of header value in characters (without 0 terminator)
    fForceParsed - Set to TRUE if we should always used parsed
    
Return Value:

    HRESULT

--*/
{
    HTTP_KNOWN_HEADER *         pHeader;
    HRESULT                     hr;

    //
    // If value is too long, reject now
    //
    if (cchHeaderValue > MAXUSHORT)
    {
        return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
    }
    
    DBG_ASSERT( ulResponseHeaderIndex < HttpHeaderResponseMaximum );
    DBG_ASSERT( pszHeaderValue != NULL || cchHeaderValue == 0 );

    if ( !fForceParsed )
    {
        if ( _responseMode == RESPONSE_MODE_RAW )
        {
            hr = SwitchToParsedMode();
            if ( FAILED( hr ) )
            {
                return hr;
            }
            
            DBG_ASSERT( _responseMode == RESPONSE_MODE_PARSED );
        }
    }

    //
    // Set the header
    //
    
    pHeader = &(_ulHttpResponse.Headers.pKnownHeaders[ ulResponseHeaderIndex ]);

    if ( cchHeaderValue == 0 )
    {
        pHeader->pRawValue = NULL;
    }
    else
    {
        pHeader->pRawValue = pszHeaderValue;
        _fResponseTouched = TRUE;
    }
    pHeader->RawValueLength = (USHORT)cchHeaderValue;

    return NO_ERROR;
}

HRESULT
W3_RESPONSE::DeleteHeader(
    CHAR *             pszHeaderName
)
/*++

Routine Description:
    
    Delete a response header

Arguments:

    pszHeaderName - Header to delete
    
Return Value:

    HRESULT

--*/
{
    ULONG                   ulHeaderIndex;
    HRESULT                 hr;
    HTTP_UNKNOWN_HEADER *   pUnknownHeader;
    DWORD                   i;

    if ( _responseMode == RESPONSE_MODE_RAW )
    {
        hr = SwitchToParsedMode();
        if ( FAILED( hr ) )
        {
            return hr;
        }
    }
    DBG_ASSERT( _responseMode == RESPONSE_MODE_PARSED );
    
    //
    // Is this a known header?  If so, we can just set by reference now
    // since we have copied the header value
    // 
    
    ulHeaderIndex = RESPONSE_HEADER_HASH::GetIndex( pszHeaderName );
    if ( ulHeaderIndex != UNKNOWN_INDEX && 
         ulHeaderIndex < HttpHeaderResponseMaximum )
    {
        _ulHttpResponse.Headers.pKnownHeaders[ ulHeaderIndex ].pRawValue = "";
        _ulHttpResponse.Headers.pKnownHeaders[ ulHeaderIndex ].RawValueLength = 0;
    }
    else
    {
        //
        // Unknown header.  First check if it exists
        //
            
        for ( i = 0;
              i < _ulHttpResponse.Headers.UnknownHeaderCount;
              i++ )
        {
            pUnknownHeader = &(_ulHttpResponse.Headers.pUnknownHeaders[ i ]);
            DBG_ASSERT( pUnknownHeader != NULL );
            
            if ( _stricmp( pUnknownHeader->pName, pszHeaderName ) == 0 )
            {
                break;
            }
        }
        
        if ( i < _ulHttpResponse.Headers.UnknownHeaderCount )
        {
            //
            // Now shrink the array to remove the header
            //
            
            memmove( _ulHttpResponse.Headers.pUnknownHeaders + i,
                     _ulHttpResponse.Headers.pUnknownHeaders + i + 1,
                     ( _ulHttpResponse.Headers.UnknownHeaderCount - i - 1 ) * 
                     sizeof( HTTP_UNKNOWN_HEADER ) );
        
            _ulHttpResponse.Headers.UnknownHeaderCount--;
        }
    }
    
    return NO_ERROR;
}

HRESULT
W3_RESPONSE::SetStatus(
    USHORT              statusCode,
    STRA &              strReason,
    HTTP_SUB_ERROR &    subError
)
/*++

Routine Description:
    
    Set the status/reason of the response

Arguments:

    status - Status code
    strReason - Reason string
    subError - Optional (default 0)
    
Return Value:

    HRESULT

--*/
{
    HRESULT             hr;
    CHAR *              pszNewStatus;
    
    hr = _HeaderBuffer.AllocateSpace( strReason.QueryStr(),
                                      strReason.QueryCCH(),
                                      &pszNewStatus );
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    _ulHttpResponse.StatusCode = statusCode;
    _ulHttpResponse.pReason = pszNewStatus;
    _ulHttpResponse.ReasonLength = strReason.QueryCCH();
    _subError = subError;
    
    return NO_ERROR;
}

HRESULT
W3_RESPONSE::GetStatusLine(
    STRA *              pstrStatusLine
)
/*++

Routine Description:
    
    What a stupid little function.  Here we generate what the response's
    status line will be

Arguments:

    pstrStatusLine - Filled with status like

Return Value:

    HRESULT

--*/
{
    HRESULT             hr = NO_ERROR;
    CHAR                achNum[ 32 ];
    
    if ( pstrStatusLine == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }
    
    // BUGBUG
    hr = pstrStatusLine->Copy( "HTTP/1.1 " );
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    _itoa( _ulHttpResponse.StatusCode, achNum, 10 );
    
    hr = pstrStatusLine->Append( achNum );
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    hr = pstrStatusLine->Append( " ", 1 );
    if ( FAILED( hr ) )
    {
        return hr;
    }

    hr = pstrStatusLine->Append( _ulHttpResponse.pReason,
                                 _ulHttpResponse.ReasonLength );

    return hr;
} 

HRESULT
W3_RESPONSE::GetHeader(
    CHAR *                  pszHeaderName,
    STRA *                  pstrHeaderValue
)
/*++

Routine Description:
    
    Get a response header

Arguments:

    pszHeaderName - Header to retrieve
    pstrHeaderValue - Filled with header value
    
Return Value:

    HRESULT

--*/
{
    ULONG                       ulHeaderIndex;
    HTTP_UNKNOWN_HEADER *       pUnknownHeader;
    HTTP_KNOWN_HEADER *         pKnownHeader;
    HRESULT                     hr;
    BOOL                        fFound = FALSE;

    if ( pstrHeaderValue == NULL ||
         pszHeaderName == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }

    if ( _responseMode == RESPONSE_MODE_RAW )
    {
        hr = SwitchToParsedMode();
        if ( FAILED( hr ) )
        {
            return hr;
        }
    }
    DBG_ASSERT( _responseMode == RESPONSE_MODE_PARSED );

    ulHeaderIndex = RESPONSE_HEADER_HASH::GetIndex( pszHeaderName );
    if ( ulHeaderIndex == UNKNOWN_INDEX )
    {
        //
        // Unknown header
        //
        
        for ( DWORD i = 0; i < _ulHttpResponse.Headers.UnknownHeaderCount; i++ )
        {
            pUnknownHeader = &(_ulHttpResponse.Headers.pUnknownHeaders[ i ]);
            DBG_ASSERT( pUnknownHeader != NULL );
            
            if ( _stricmp( pszHeaderName,
                           pUnknownHeader->pName ) == 0 )
            {
                fFound = TRUE;
                break;
            } 
        }

        if ( fFound )
        {
            return pstrHeaderValue->Copy( pUnknownHeader->pRawValue,
                                          pUnknownHeader->RawValueLength );
        }
        else
        {
            return HRESULT_FROM_WIN32( ERROR_INVALID_INDEX );
        }
    }
    else
    {
        //
        // Known header
        //
        // If a filter wanted the Content-Length response header, then we should
        // generate it now (lazily)
        //
        
        if ( ulHeaderIndex == HttpHeaderContentLength )
        {
            CHAR           achNum[ 32 ];

            _ui64toa( QueryContentLength(),
                      achNum,
                      10 );

            hr = SetHeader( HttpHeaderContentLength,
                            achNum,
                            strlen( achNum ) );
            if ( FAILED( hr ) )
            {
                return hr;
            }
        }

        pKnownHeader = &(_ulHttpResponse.Headers.pKnownHeaders[ ulHeaderIndex ]);
        if ( pKnownHeader->pRawValue != NULL &&
             pKnownHeader->RawValueLength != 0 )
        {
            return pstrHeaderValue->Copy( pKnownHeader->pRawValue,
                                          pKnownHeader->RawValueLength );
        }
        else
        {
            return HRESULT_FROM_WIN32( ERROR_INVALID_INDEX );
        }
    }
}

VOID
W3_RESPONSE::ClearHeaders(
    VOID
)
/*++

Routine Description:
    
    Clear headers

Arguments:
    
    None
    
Return Value:

    None

--*/
{
    memset( &(_ulHttpResponse.Headers),
            0,
            sizeof( _ulHttpResponse.Headers ) );
}

HRESULT
W3_RESPONSE::AddFileHandleChunk(
    HANDLE                  hFile,
    ULONGLONG               cbOffset,
    ULONGLONG               cbLength
)
/*++

Routine Description:
    
    Add file handle chunk to response

Arguments:
    
    hFile - File handle
    cbOffset - Offset in file
    cbLength - Length of chunk
    
Return Value:

    HRESULT

--*/
{
    HTTP_DATA_CHUNK         DataChunk;
    HRESULT                 hr;

    _fResponseTouched = TRUE;
    
    DataChunk.DataChunkType = HttpDataChunkFromFileHandle;
    DataChunk.FromFileHandle.ByteRange.StartingOffset.QuadPart = cbOffset;
    DataChunk.FromFileHandle.ByteRange.Length.QuadPart = cbLength;
    DataChunk.FromFileHandle.FileHandle = hFile;
    
    hr = InsertDataChunk( &DataChunk, -1 );
    if ( FAILED( hr ) )
    {
        return hr;
    }

    //
    // Update content length count
    //
    
    _cbContentLength += cbLength;
    
    return NO_ERROR;
}

HRESULT
W3_RESPONSE::AddMemoryChunkByReference(
    PVOID                   pvBuffer,
    DWORD                   cbBuffer
)
/*++

Routine Description:
    
    Add memory chunk to W3_RESPONSE.  Don't copy the memory -> we assume
    the caller will manage the memory lifetime

Arguments:
   
    pvBuffer - Memory buffer
    cbBuffer - Size of memory buffer 
    
Return Value:

    HRESULT

--*/
{
    HTTP_DATA_CHUNK         DataChunk;
    HRESULT                 hr;

    _fResponseTouched = TRUE;
    
    DataChunk.DataChunkType = HttpDataChunkFromMemory;
    DataChunk.FromMemory.pBuffer = pvBuffer;
    DataChunk.FromMemory.BufferLength = cbBuffer;
    
    hr = InsertDataChunk( &DataChunk, -1 );
    if ( FAILED( hr ) )
    {
        return hr;
    }

    //
    // Update content length count
    //
    
    _cbContentLength += cbBuffer;

    return NO_ERROR;
}

HRESULT
W3_RESPONSE::GetChunks(
    OUT BUFFER *chunkBuffer,
    OUT DWORD  *pdwNumChunks)
{
    if ( !chunkBuffer->Resize( _cChunks * sizeof( HTTP_DATA_CHUNK ) ) )
    {
        return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
    }

    memcpy( chunkBuffer->QueryPtr(),
            _bufChunks.QueryPtr(),
            _cChunks * sizeof( HTTP_DATA_CHUNK ) );

    *pdwNumChunks = _cChunks;

    Clear( TRUE );

    return S_OK;
}

HRESULT
W3_RESPONSE::Clear(
    BOOL                    fClearEntityOnly
)
/*++

Routine Description:
    
    Clear response

Arguments:

    fEntityOnly - Set to TRUE to clear only entity
    
Return Value:

    HRESULT

--*/
{
    HRESULT             hr = NO_ERROR;
    
    if ( !fClearEntityOnly )
    {
        //
        // Must we send the response in raw mode?
        //
    
        _fIncompleteHeaders = FALSE;

        //
        // Raw mode management
        //    
    
        _strRawCoreHeaders.Reset();
        _cFirstEntityChunk  = 0;
    
        //
        // Always start in parsed mode
        //
    
        _responseMode = RESPONSE_MODE_PARSED;

        //
        // Clear headers/status
        //
        
        ClearHeaders();
    }

    _cChunks = _cFirstEntityChunk;
    _cbContentLength = 0;    
    
    return hr;
}

HRESULT
W3_RESPONSE::SwitchToParsedMode(
    VOID
)
/*++

Routine Description:

    Switch to parsed mode

Arguments:

    None
    
Return Value:

    HRESULT

--*/
{
    HRESULT                 hr;
    CHAR *                  pszHeaders;
    HTTP_DATA_CHUNK *       pCurrentChunk;
    DWORD                   i;
    
    DBG_ASSERT( _responseMode == RESPONSE_MODE_RAW );
   
    //
    // Loop thru all header chunks and parse them out
    //
    
    for ( i = 0;
          i < _cFirstEntityChunk;
          i++ )
    {
        pCurrentChunk = &(QueryChunks()[ i ]);
        
        DBG_ASSERT( pCurrentChunk->DataChunkType == HttpDataChunkFromMemory );
        
        pszHeaders = (CHAR*) pCurrentChunk->FromMemory.pBuffer;        
        
        if ( i == 0 )
        {
            //
            // The first header chunk contains core headers plus status line
            //
            // (remember to skip the status line)
            //
            
            pszHeaders = strstr( pszHeaders, "\r\n" );
            DBG_ASSERT( pszHeaders != NULL );
            
            pszHeaders += 2;
            DBG_ASSERT( *pszHeaders != '\0' );
        }
        
        hr = ParseHeadersFromStream( pszHeaders );
        if ( FAILED( hr ) )
        {
            return hr;
        }
    }
    
    _strRawCoreHeaders.Reset();
    
    //
    // Any chunks in the response which are actually headers should be 
    // removed
    //
    
    if ( _cFirstEntityChunk != 0 )
    {
        memmove( QueryChunks(),
                 QueryChunks() + _cFirstEntityChunk,
                 ( _cChunks - _cFirstEntityChunk ) * sizeof( HTTP_DATA_CHUNK ) );
        
        _cChunks -= _cFirstEntityChunk;
        _cFirstEntityChunk = 0;
    }
    
    //
    // Cool.  Now we are in parsed mode
    //
    
    _responseMode = RESPONSE_MODE_PARSED;
    
    return NO_ERROR;
}

HRESULT
W3_RESPONSE::SwitchToRawMode(
    W3_CONTEXT *            pW3Context,
    CHAR *                  pszAdditionalHeaders,
    DWORD                   cchAdditionalHeaders
)
/*++

Routine Description:

    Switch into raw mode.
    Builds a raw response for use by raw data filters and/or ISAPI.  This
    raw response will be a set of chunks which contact the entire response
    including serialized headers.
    
Arguments:

    pW3Context - W3 context
    pszAdditionalHeaders - Additional raw headers to add
    cchAdditionalHeaders - Size of additional headers
    
Return Value:

    HRESULT

--*/
{
    HRESULT                 hr = NO_ERROR;
    HTTP_DATA_CHUNK         dataChunk;
    
    DBG_ASSERT( _responseMode == RESPONSE_MODE_PARSED );

    //
    // Generate raw core headers
    // 
    
    hr = BuildRawCoreHeaders( pW3Context );
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    //
    // Now fix up the chunks so the raw stream headers are in the right place
    //
    
    //
    // First chunk is the raw core headers (includes the status line)
    //
    
    dataChunk.DataChunkType = HttpDataChunkFromMemory;
    dataChunk.FromMemory.pBuffer = _strRawCoreHeaders.QueryStr();
    dataChunk.FromMemory.BufferLength = _strRawCoreHeaders.QueryCB();
    
    hr = InsertDataChunk( &dataChunk, 0 );
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    //
    // Remember the beginning of real entity
    //
    
    _cFirstEntityChunk = 1;

    //
    // Now add any additional header stream
    // 
    
    if ( cchAdditionalHeaders != 0 )
    {
        dataChunk.DataChunkType = HttpDataChunkFromMemory;
        dataChunk.FromMemory.pBuffer = pszAdditionalHeaders;
        dataChunk.FromMemory.BufferLength = cchAdditionalHeaders;
        
        hr = InsertDataChunk( &dataChunk, 1 );
        if ( FAILED( hr ) )
        {
            return hr;
        }
        
        _cFirstEntityChunk++;
    }
    
    //
    // We're now in raw mode
    //
    
    _responseMode = RESPONSE_MODE_RAW;
    
    return NO_ERROR;
}

HRESULT
W3_RESPONSE::SendResponse(
    W3_CONTEXT *            pW3Context,
    DWORD                   dwResponseFlags,
    DWORD *                 pcbSent,
    HTTP_LOG_FIELDS_DATA *  pUlLogData
)
/*++

Routine Description:
    
    Send a W3_RESPONSE to the client.  This is a very simple wrapper
    of UlAtqSendHttpResponse.

Arguments:

    pW3Context - W3 context (contains amongst other things ULATQ context)
    dwResponseFlags - W3_RESPONSE* flags
    pcbSent - Filled with number of bytes sent (if sync)
    pUlLogData - Log data
    
Return Value:

    HRESULT

--*/
{
    HRESULT                 hr;
    DWORD                   dwFlags = 0;
    HTTP_CACHE_POLICY       cachePolicy;
    HTTP_DATA_CHUNK *       pStartChunk;
    BOOL                    fFinished = FALSE;
    BOOL                    fAsync;

    DBG_ASSERT( CheckSignature() );

    if ( dwResponseFlags & W3_RESPONSE_SUPPRESS_HEADERS )
    {
        if (_responseMode == RESPONSE_MODE_RAW)
        {
            _cChunks -= _cFirstEntityChunk;

            memmove( QueryChunks(),
                     QueryChunks() + _cFirstEntityChunk,
                     _cChunks * sizeof( HTTP_DATA_CHUNK ) );

            _cFirstEntityChunk = 0;
        }

        return SendEntity( pW3Context,
                           dwResponseFlags,
                           pcbSent,
                           pUlLogData );
    }
    
    if ( dwResponseFlags & W3_RESPONSE_MORE_DATA )
    {
        //
        // More data follows this response?
        //

        dwFlags |= HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
    }
    
    //
    // UL needs to see the disconnect flag on the initial response
    // so that it knows to send the proper connection header to the
    // client.  This needs to happen even if the more data flag is
    // set.
    //

    if ( dwResponseFlags & W3_RESPONSE_DISCONNECT )
    {
        //
        // Disconnect or not?
        // 
    
        dwFlags |= HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
    }
    
    //
    // Setup cache policy
    //

    if ( dwResponseFlags & W3_RESPONSE_UL_CACHEABLE )
    {
        cachePolicy.Policy = HttpCachePolicyUserInvalidates;
    }    
    else
    {
        cachePolicy.Policy = HttpCachePolicyNocache;
    }

    //
    // Convert to raw if filtering is needed 
    //
    // OR if an ISAPI once gave us incomplete headers and thus we need to
    // go back to raw mode (without terminating headers) 
    //
    // OR an ISAPI has called WriteClient() before sending a response
    //
    
    if ( pW3Context->IsNotificationNeeded( SF_NOTIFY_SEND_RAW_DATA ) || 
         _fIncompleteHeaders ||
         _fResponseSent )
    {
        if ( _responseMode == RESPONSE_MODE_PARSED )
        {
            hr = GenerateAutomaticHeaders( pW3Context,
                                           dwFlags );
            if ( FAILED( hr ) )
            {
                return hr;
            }

            hr = SwitchToRawMode( pW3Context, 
                                  _fIncompleteHeaders ? "" : "\r\n",
                                  _fIncompleteHeaders ? 0 : 2 );
            if ( FAILED( hr ) )
            {
                return hr;
            }
        }

        DBG_ASSERT( _responseMode == RESPONSE_MODE_RAW );

        //
        // OK.  This is a little lame.  But the _strRawCoreHeaders may have
        // changed a bit since we last setup the chunks for the header
        // stream.  Just adjust it here
        //

        pStartChunk = QueryChunks();
        pStartChunk[0].FromMemory.pBuffer = _strRawCoreHeaders.QueryStr();
        pStartChunk[0].FromMemory.BufferLength = _strRawCoreHeaders.QueryCB();

        //
        // If we're going to be kill entity and/or headers, do so now before
        // calling into the filter
        //

        if ( dwResponseFlags & W3_RESPONSE_SUPPRESS_ENTITY )
        {
            _cChunks = _cFirstEntityChunk;
        }

        //
        // Filter the chunks if needed
        //

        if ( pW3Context->IsNotificationNeeded( SF_NOTIFY_SEND_RAW_DATA ) )
        {
            hr = ProcessRawChunks( pW3Context, &fFinished );
            if ( FAILED( hr ) )
            {
                return hr;
            }
            
            if ( fFinished )
            {
                dwFlags |= HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
                _cChunks = 0;
            }
        }
    }

    //
    // Take care of any compression now
    //

    if ( !_fIncompleteHeaders &&
         pW3Context->QueryUrlContext() != NULL )
    {
        W3_METADATA *           pMetaData;

        pMetaData = pW3Context->QueryUrlContext()->QueryMetaData();
        DBG_ASSERT( pMetaData != NULL );

        if ( !pW3Context->QueryDoneWithCompression() &&
             pMetaData->QueryDoDynamicCompression() )
        {
            if (FAILED(hr = HTTP_COMPRESSION::OnSendResponse(
                    pW3Context,
                    !!( dwResponseFlags & W3_RESPONSE_MORE_DATA ) ) ) )   
            {
                return hr;
            }
        }
    }
    
    //
    // From now on, we must send any future responses raw
    //
    
    _fResponseSent = TRUE;
    
    //
    // Async?
    //
    
    fAsync = dwResponseFlags & W3_RESPONSE_ASYNC ? TRUE : FALSE;

    if ( _responseMode == RESPONSE_MODE_RAW )
    {
        if ( dwResponseFlags & W3_RESPONSE_SUPPRESS_ENTITY )
        {
            _cChunks = _cFirstEntityChunk;
        }

        hr = UlAtqSendEntityBody( pW3Context->QueryUlatqContext(),
                                  fAsync,
                                  dwFlags | HTTP_SEND_RESPONSE_FLAG_RAW_HEADER,
                                  _cChunks,                                  
                                  _cChunks ? QueryChunks() : NULL,
                                  pcbSent,
                                  pUlLogData );
    }
    else
    {
        if ( dwResponseFlags & W3_RESPONSE_SUPPRESS_ENTITY )
        {
            _cChunks = 0;
        } 

        _ulHttpResponse.EntityChunkCount = _cChunks;
        _ulHttpResponse.pEntityChunks = _cChunks ? QueryChunks() : NULL;

        hr = UlAtqSendHttpResponse( pW3Context->QueryUlatqContext(),
                                    fAsync,
                                    dwFlags,
                                    &(_ulHttpResponse),
                                    &cachePolicy,
                                    pcbSent,
                                    pUlLogData );
    }

    if ( FAILED( hr ) )
    {
        //
        // If we couldn't send the response thru UL, then this is really bad.
        // Do not reset _fSendRawData since no response will get thru
        //
    }
        
    return hr;
}

HRESULT
W3_RESPONSE::SendEntity(
    W3_CONTEXT *            pW3Context,
    DWORD                   dwResponseFlags,
    DWORD *                 pcbSent,
    HTTP_LOG_FIELDS_DATA *  pUlLogData
)
/*++

Routine Description:
    
    Send entity to the client

Arguments:

    pMainContext - Main context (contains amongst other things ULATQ context)
    dwResponseFlags - W3_REPSONSE flags
    pcbSent - Number of bytes sent (when sync)
    pUlLogData - Log data for the response (this entity is part of response)
    
Return Value:

    Win32 Error indicating status

--*/
{
    HRESULT                 hr = NO_ERROR;
    DWORD                   dwFlags = 0;
    BOOL                    fAsync;
    BOOL                    fFinished = FALSE;

    DBG_ASSERT( CheckSignature() );
    
    //
    // If we get to here and a response hasn't yet been sent, then we must
    // call HttpSendEntity first (not that HTTP.SYS lets us do that)
    //
    
    if ( !_fResponseSent )
    {
        _fResponseSent = TRUE;
        dwFlags |= HTTP_SEND_RESPONSE_FLAG_RAW_HEADER;
    }
    
    //
    // Note that both HTTP_SEND_RESPONSE_FLAG_MORE_DATA and 
    // HTTP_SEND_RESPONSE_FLAG_DISCONNECT cannot be set at the same time
    //

    if ( dwResponseFlags & W3_RESPONSE_MORE_DATA )
    {
        dwFlags |= HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
    }
    else if ( dwResponseFlags & W3_RESPONSE_DISCONNECT )
    {
        dwFlags |= HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
    }

    //
    // Send chunks to be processed if filtering if needed (and there are
    // chunks available)
    //
        
    if ( _cChunks &&
         !( dwFlags & W3_RESPONSE_SUPPRESS_ENTITY ) &&
         pW3Context->IsNotificationNeeded( SF_NOTIFY_SEND_RAW_DATA ) )
    {
        hr = ProcessRawChunks( pW3Context, &fFinished );
        if ( FAILED( hr ) )
        {
            return hr;
        }
            
        if ( fFinished )
        {
            dwFlags |= HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
            _cChunks = 0;
        }
    }
    
    //
    // Take care of any compression now
    //
    if ( pW3Context->QueryUrlContext() != NULL )
    {
        W3_METADATA *pMetaData;
        pMetaData = pW3Context->QueryUrlContext()->QueryMetaData();
        DBG_ASSERT( pMetaData != NULL);

        if (!pW3Context->QueryDoneWithCompression() &&
            pMetaData->QueryDoDynamicCompression())
        {
            if (FAILED(hr = HTTP_COMPRESSION::DoDynamicCompression(
                            pW3Context,
                            dwResponseFlags & W3_RESPONSE_MORE_DATA ? TRUE : FALSE )))
            {
                return hr;
            }
        }
    }

    //
    // If we are suppressing entity (in case of HEAD for example) do it
    // now by clearing the chunk count
    //

    if ( dwResponseFlags & W3_RESPONSE_SUPPRESS_ENTITY )
    {
        _cChunks = 0;
    }
    
    fAsync = ( dwResponseFlags & W3_RESPONSE_ASYNC ) ? TRUE : FALSE;

    //
    // Finally, send stuff out
    //

    hr = UlAtqSendEntityBody(pW3Context->QueryUlatqContext(),
                             fAsync,
                             dwFlags,
                             _cChunks,
                             _cChunks ? QueryChunks() : NULL,
                             pcbSent,
                             pUlLogData);

    return hr;
}

HRESULT
W3_RESPONSE::GenerateAutomaticHeaders(
    W3_CONTEXT *                pW3Context,
    DWORD                       dwFlags
)
/*++

Routine Description:

    Parse-Mode only function
    
    Generate headers which UL normally generates on our behalf.  This means
    
    Server:
    Connection:
    Content-Length:
    Date:

Arguments:

    pW3Context - Helps us build the core headers (since we need to look at 
                 the request).  Can be NULL to indicate to use defaults
    dwFlags - Flags we would have passed to UL
    
Return Value:

    HRESULT

--*/
{
    HTTP_KNOWN_HEADER *         pHeader;
    CHAR *                      pszHeaderValue;
    CHAR                        achDate[ 128 ];
    CHAR                        achNum[ 64 ];
    DWORD                       cchDate;
    DWORD                       cchNum;
    SYSTEMTIME                  systemTime;
    HTTP_VERSION                httpVersion;
    HRESULT                     hr;
    BOOL                        fCreateContentLength;
    BOOL                        fDisconnecting = FALSE;
    
    DBG_ASSERT( _responseMode == RESPONSE_MODE_PARSED );
    
    //
    // Server:
    //
    
    pHeader = &(_ulHttpResponse.Headers.pKnownHeaders[ HttpHeaderServer ]);
    if ( pHeader->pRawValue == NULL )
    {
        pHeader->pRawValue = SERVER_SOFTWARE_STRING;
        pHeader->RawValueLength = sizeof( SERVER_SOFTWARE_STRING ) - 1;
    }
    
    //
    // Date:
    //
    
    pHeader = &(_ulHttpResponse.Headers.pKnownHeaders[ HttpHeaderDate ]);
    if ( pHeader->pRawValue == NULL )
    {
        
        if(!IISGetCurrentTimeAsSystemTime(&systemTime))
        {
            GetSystemTime( &systemTime );
        }
        if ( !SystemTimeToGMT( systemTime, 
                               achDate, 
                               sizeof(achDate) ) ) 
        {
            return HRESULT_FROM_WIN32( GetLastError() );
        }
 
        
        cchDate = strlen( achDate );
        
        hr = _HeaderBuffer.AllocateSpace( achDate,
                                          cchDate,
                                          &pszHeaderValue );
        
        if ( FAILED( hr ) )
        {
            return hr;
        }
        
        DBG_ASSERT( pszHeaderValue != NULL );
        
        pHeader->pRawValue = pszHeaderValue;
        pHeader->RawValueLength = cchDate;
    }
    
    //
    // Are we going to be disconnecting?
    //
    
    if ( pW3Context != NULL &&
         ( pW3Context->QueryDisconnect() ||
           pW3Context->QueryRequest()->QueryClientWantsDisconnect() ) )
    {
        fDisconnecting = TRUE;
    }    

    //
    // Connection:
    //
    
    pHeader = &(_ulHttpResponse.Headers.pKnownHeaders[ HttpHeaderConnection ] );
    if ( pHeader->pRawValue == NULL )
    {
        if ( pW3Context == NULL )
        {
            HTTP_SET_VERSION( httpVersion, 1, 0 );
        }
        else
        {
            httpVersion = pW3Context->QueryRequest()->QueryVersion();
        }
    
        if ( fDisconnecting )
        {
            if ( HTTP_GREATER_EQUAL_VERSION( httpVersion, 1, 0 ) )
            {
                pHeader->pRawValue = "close";
                pHeader->RawValueLength = sizeof( "close" ) - 1;
            }   
        }
        else
        {
            if ( HTTP_EQUAL_VERSION( httpVersion, 1, 0 ) )
            {
                pHeader->pRawValue = "keep-alive";
                pHeader->RawValueLength = sizeof( "keep-alive" ) - 1;
            }
        }
    }
    
    //
    // Should we generate content length?
    //
    
    fCreateContentLength = TRUE;
    
    if ( dwFlags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA )
    {
        fCreateContentLength = FALSE;
    }
    
    if ( fCreateContentLength &&
         QueryStatusCode() / 100 == 1 ||
         QueryStatusCode() == 204 ||
         QueryStatusCode() == 304 )
    {
        fCreateContentLength = FALSE;
    }
    
    if ( fCreateContentLength &&
         pW3Context != NULL &&
         pW3Context->QueryMainContext()->QueryShouldGenerateContentLength() )
    {
        fCreateContentLength = FALSE;
    }
    
    //
    // Now generate if needed
    //
    
    if ( fCreateContentLength )
    {
        //
        // Generate a content length header if needed
        //
    
        pHeader = &(_ulHttpResponse.Headers.pKnownHeaders[ HttpHeaderContentLength ]);
        if ( pHeader->pRawValue == NULL )
        {
            _ui64toa( QueryContentLength(),
                      achNum,
                      10 );

            cchNum = strlen( achNum );
             
            pszHeaderValue = NULL;
                  
            hr = _HeaderBuffer.AllocateSpace( achNum,
                                              cchNum,
                                              &pszHeaderValue );
            if ( FAILED( hr ) )
            {
                return hr;
            }
        
            DBG_ASSERT( pszHeaderValue != NULL );
            
            pHeader->pRawValue = pszHeaderValue;
            pHeader->RawValueLength = cchNum;
        }
    }
    
    return NO_ERROR;
}

HRESULT
W3_RESPONSE::BuildRawCoreHeaders(
    W3_CONTEXT *                pW3Context
)
/*++

Routine Description:

    Build raw header stream for the core headers that UL normally generates
    on our behalf.  This means structured headers and some special 
    "automatic" ones like Connection:, Date:, Server:, etc.

Arguments:

    pW3Context - Helps us build core header (in particular the Connection:)
    
Return Value:

    HRESULT

--*/
{
    HRESULT                 hr = NO_ERROR;
    CHAR                    achNumber[ 32 ];
    HTTP_KNOWN_HEADER *     pKnownHeader;
    HTTP_UNKNOWN_HEADER *   pUnknownHeader;
    CHAR *                  pszHeaderName;
    DWORD                   cchHeaderName;
    DWORD                   i;

    if ( pW3Context == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }
    
    _strRawCoreHeaders.Reset();
    
    //
    // Build a status line
    //
    
    hr = _strRawCoreHeaders.Copy( "HTTP/1.1 " );
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    _itoa( _ulHttpResponse.StatusCode,
           achNumber,
           10 );
    
    hr = _strRawCoreHeaders.Append( achNumber );
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    hr = _strRawCoreHeaders.Append( " " );
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    hr = _strRawCoreHeaders.Append( _ulHttpResponse.pReason );
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    hr = _strRawCoreHeaders.Append( "\r\n" );
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    //
    // Iterate thru all the headers in our structured response and set
    // append them to the stream.  
    //
    // Start with the known headers
    //

    for ( i = 0;
          i < HttpHeaderResponseMaximum;
          i++ )
    {
        pKnownHeader = &(_ulHttpResponse.Headers.pKnownHeaders[ i ]);
        
        if ( pKnownHeader->pRawValue != NULL &&
             pKnownHeader->pRawValue[ 0 ] != '\0' )
        {
            pszHeaderName = RESPONSE_HEADER_HASH::GetString( i, &cchHeaderName );
            DBG_ASSERT( pszHeaderName != NULL );
            
            hr = _strRawCoreHeaders.Append( pszHeaderName, cchHeaderName );
            if ( FAILED( hr ) )
            {
                return hr;
            }
            
            hr = _strRawCoreHeaders.Append( ": ", 2 );
            if ( FAILED( hr ) )
            {
                return hr;
            }
            
            hr = _strRawCoreHeaders.Append( pKnownHeader->pRawValue,
                                            pKnownHeader->RawValueLength );
            if ( FAILED( hr ) )
            {
                return hr;
            }
            
            hr = _strRawCoreHeaders.Append( "\r\n", 2 );
            if ( FAILED( hr ) )
            {
                return hr;
            }
            
            //
            // Now clear the header
            //
            
            pKnownHeader->pRawValue = NULL;
            pKnownHeader->RawValueLength = 0;
        }
    }   
    
    //
    // Next, the unknown headers
    //
    
    for ( i = 0;
          i < _ulHttpResponse.Headers.UnknownHeaderCount;
          i++ )
    {
        pUnknownHeader = &(_ulHttpResponse.Headers.pUnknownHeaders[ i ]);

        hr = _strRawCoreHeaders.Append( pUnknownHeader->pName,
                                        pUnknownHeader->NameLength );
        if ( FAILED( hr ) )
        {
            return hr;
        } 
        
        hr = _strRawCoreHeaders.Append( ": ", 2 );
        if ( FAILED( hr ) )
        {
            return hr;
        }
        
        hr = _strRawCoreHeaders.Append( pUnknownHeader->pRawValue,
                                        pUnknownHeader->RawValueLength );
        if ( FAILED( hr ) )
        {
            return hr;
        }
        
        hr = _strRawCoreHeaders.Append( "\r\n", 2 );
        if ( FAILED( hr ) )
        {
            return hr;
        }
    }
    
    //
    // Clear the unknown headers
    //
    
    _ulHttpResponse.Headers.UnknownHeaderCount = 0;
        
    return NO_ERROR;
}

HRESULT
W3_RESPONSE::AppendResponseHeaders(
    STRA &                  strHeaders
)
/*++

Routine Description:

    Add response headers (an ISAPI filter special)

Arguments:

    strHeaders - Additional headers to add
                 (may contain entity -> LAAAAAAMMMMME)
    
Return Value:

    HRESULT

--*/
{
    HRESULT             hr;
    LPSTR               pszEntity;
    HTTP_DATA_CHUNK     DataChunk;

    if ( strHeaders.IsEmpty() )
    {
        return NO_ERROR;
    }

    if ( _responseMode == RESPONSE_MODE_RAW && 
         QueryChunks()->FromMemory.pBuffer == _strRawCoreHeaders.QueryStr() )
    {
        DBG_ASSERT( QueryChunks()->DataChunkType == HttpDataChunkFromMemory );
        DBG_ASSERT( QueryChunks()->FromMemory.pBuffer == _strRawCoreHeaders.QueryStr() );
        
        hr = _strRawCoreHeaders.Append( strHeaders );
        if ( FAILED( hr ) )
        {
            return hr;
        }
        
        //
        // Patch first chunk since point may have changed
        //
        
        QueryChunks()->FromMemory.pBuffer = _strRawCoreHeaders.QueryStr();
        QueryChunks()->FromMemory.BufferLength = _strRawCoreHeaders.QueryCB();
    }
    else
    {
        hr = ParseHeadersFromStream( strHeaders.QueryStr() );
        if ( FAILED( hr ) )
        {
            return hr;
        }

        //
        // Look for entity body in headers
        //

        pszEntity = strstr( strHeaders.QueryStr(), "\r\n\r\n" );
        if ( pszEntity != NULL )
        {
            DataChunk.DataChunkType = HttpDataChunkFromMemory;
            DataChunk.FromMemory.pBuffer = pszEntity + ( sizeof( "\r\n\r\n" ) - 1 );
            DataChunk.FromMemory.BufferLength = strlen( (LPSTR) DataChunk.FromMemory.pBuffer );

            hr = InsertDataChunk( &DataChunk, 0 );
            if ( FAILED( hr ) )
            {
                return hr;
            }
        }
    }

    return NO_ERROR;
}

HRESULT
W3_RESPONSE::BuildStatusFromIsapi(
    CHAR *              pszStatus
)
/*++

Routine Description:

    Build up status for response given raw status line from ISAPI

Arguments:

    pszStatus - Status line for response
    
Return Value:

    HRESULT

--*/
{
    USHORT                  status;
    STACK_STRA(             strReason, 32 );
    CHAR *                  pszCursor = NULL;
    HRESULT                 hr = NO_ERROR;
    
    DBG_ASSERT( pszStatus != NULL );
    
    status = (USHORT) atoi( pszStatus );
    if ( status >= 100 &&
         status <= 999 )
    {
        //
        // Need to find the reason string
        //
        
        pszCursor = pszStatus;
        while ( isdigit( *pszCursor ) ) 
        {
            pszCursor++;
        }
            
        if ( *pszCursor == ' ' )
        {
            hr = strReason.Copy( pszCursor + 1 );
            if ( FAILED( hr ) )
            {
                return hr;
            }
        }
        
        hr = SetStatus( (USHORT)status, strReason );
        if ( FAILED( hr ) )
        {
            return hr;
        }
    }
    
    return NO_ERROR;
}

HRESULT
W3_RESPONSE::FilterWriteClient(
    W3_CONTEXT *                pW3Context,
    PVOID                       pvData,
    DWORD                       cbData
)
/*++

Routine Description:

    A non-intrusive WriteClient() for use with filters.  Non-intrusive means
    the current response structure (chunks/headers) is not reset/effected
    by sending this data (think of a WriteClient() done in a SEND_RESPONSE
    filter notification)
    
Arguments:
    
    pW3Context - W3 Context used to help build core response header
    pvData - Pointer to data sent
    cbData - Size of data to send
    
Return Value:

    HRESULT

--*/
{
    HTTP_DATA_CHUNK         dataChunk;
    DWORD                   cbSent;
    HTTP_FILTER_RAW_DATA    rawStream;
    BOOL                    fRet;
    BOOL                    fFinished = FALSE;
    DWORD                   dwFlags = HTTP_SEND_RESPONSE_FLAG_MORE_DATA |
                                      HTTP_SEND_RESPONSE_FLAG_RAW_HEADER;
    
    dataChunk.DataChunkType = HttpDataChunkFromMemory;
    dataChunk.FromMemory.pBuffer = pvData;
    dataChunk.FromMemory.BufferLength = cbData;
    
    _fResponseTouched = TRUE;
    
    _fResponseSent = TRUE;

    //
    // If there are send raw filters to be notified, do so now
    //
    
    if ( pW3Context->IsNotificationNeeded( SF_NOTIFY_SEND_RAW_DATA ) )
    {
        rawStream.pvInData = pvData;
        rawStream.cbInData = cbData;
        rawStream.cbInBuffer = cbData;

        fRet = pW3Context->NotifyFilters( SF_NOTIFY_SEND_RAW_DATA,
                                          &rawStream,
                                          &fFinished );
        if ( !fRet )
        {
            return HRESULT_FROM_WIN32( GetLastError() );
        }
        
        if ( fFinished )
        {
            rawStream.cbInData = 0;
            rawStream.cbInBuffer = 0;
            dwFlags = HTTP_SEND_RESPONSE_FLAG_DISCONNECT |
                      HTTP_SEND_RESPONSE_FLAG_RAW_HEADER;
        }
        
        dataChunk.FromMemory.pBuffer = rawStream.pvInData;
        dataChunk.FromMemory.BufferLength = rawStream.cbInData;
    }
    
    return UlAtqSendEntityBody( pW3Context->QueryUlatqContext(),
                                FALSE,          // sync
                                dwFlags,
                                1,
                                &dataChunk,
                                &cbSent,
                                NULL ); 
}

HRESULT
W3_RESPONSE::BuildResponseFromIsapi(
    W3_CONTEXT *            pW3Context,
    LPSTR                   pszStatusStream,
    LPSTR                   pszHeaderStream,
    DWORD                   cchHeaderStream
)
/*++

Routine Description:
    
    Shift this response into raw mode since we want to hold onto the
    streams from ISAPI and use them for the response if possible

Arguments:
    
    pW3Context - W3 Context used to help build core response header
                 (can be NULL)
    pszStatusStream - Status stream
    pszHeaderStream - Header stream
    cchHeaderStream - Size of above
    
Return Value:

    HRESULT

--*/
{
    HRESULT             hr = NO_ERROR;
    CHAR *              pszEndOfHeaders = NULL;
    CHAR *              pszCursor;
    CHAR *              pszRawAdditionalIsapiHeaders = NULL;
    DWORD               cchRawAdditionalIsapiHeaders = 0;
    CHAR *              pszRawAdditionalIsapiEntity = NULL;
    DWORD               cchRawAdditionalIsapiEntity = 0;
    HTTP_DATA_CHUNK     DataChunk;

    _fResponseTouched = TRUE;

    //
    // First parse the status line.  We do this before switching into raw
    // mode because we want the _strRawCoreHeader string to contain the 
    // correct status line and reason
    //
    
    if ( pszStatusStream != NULL &&
         *pszStatusStream != '\0' )
    {
        hr = BuildStatusFromIsapi( pszStatusStream );
        if ( FAILED( hr ) )
        {
            return hr;
        }
    }

    //
    // If there is no ISAPI header stream set, then we're done
    //
    
    if ( pszHeaderStream == NULL )
    {
        return NO_ERROR;
    }

    //
    // Create automatic headers if necessary (but no content-length)
    //
    
    hr = GenerateAutomaticHeaders( pW3Context,
                                   HTTP_SEND_RESPONSE_FLAG_MORE_DATA );
    if ( FAILED( hr ) )
    {
        return hr;
    }

    //
    // The ISAPI set some headers.  Store them now
    //
    
    pszRawAdditionalIsapiHeaders = pszHeaderStream;
    cchRawAdditionalIsapiHeaders = cchHeaderStream;
    
    //
    // If there is additional entity body (after ISAPI headers), then add it
    // Look for a complete set of additional headers.  Complete means that
    // we can find a "\r\n\r\n".  
    //
    
    pszEndOfHeaders = strstr( pszHeaderStream, "\r\n\r\n" );
    if ( pszEndOfHeaders != NULL )
    {
        pszEndOfHeaders += 4;       // go past the \r\n\r\n

        //
        // Update the header length since there is entity tacked on
        //
        
        cchRawAdditionalIsapiHeaders = DIFF( pszEndOfHeaders - pszHeaderStream );
        
        if ( *pszEndOfHeaders != '\0' )
        {
            pszRawAdditionalIsapiEntity = pszEndOfHeaders;
            cchRawAdditionalIsapiEntity = cchHeaderStream - cchRawAdditionalIsapiHeaders;
        }
    }  
    else
    {
        //
        // ISAPI didn't complete the headers.  That means the ISAPI will
        // be completing the headers later.  What this means for us is we 
        // must send the headers in the raw form with out adding our own
        // \r\n\r\n
        //
        
        _fIncompleteHeaders = TRUE;
    }
    
    //
    // Switch into raw mode if we're not already in it
    //
    
    if ( _responseMode == RESPONSE_MODE_PARSED )
    {
        hr = SwitchToRawMode( pW3Context,
                              pszRawAdditionalIsapiHeaders,
                              cchRawAdditionalIsapiHeaders );
        if ( FAILED( hr ) )
        {
            return hr;
        }
    }
 
    DBG_ASSERT( _responseMode == RESPONSE_MODE_RAW );

    //
    // Now add the additional ISAPI entity
    //
    
    if ( cchRawAdditionalIsapiEntity != 0 )
    {
        DataChunk.DataChunkType = HttpDataChunkFromMemory;
        DataChunk.FromMemory.pBuffer = pszRawAdditionalIsapiEntity;
        DataChunk.FromMemory.BufferLength = cchRawAdditionalIsapiEntity;
     
        hr = InsertDataChunk( &DataChunk, -1 );
        if ( FAILED( hr ) )
        {
            return hr;
        }
    }
    
    return NO_ERROR;
}

HRESULT
W3_RESPONSE::GetRawResponseStream(
    STRA *                  pstrResponseStream
)
/*++

Routine Description:

    Fill in the raw response stream for use by raw data filter code
    
Arguments:

    pstrResponseStream - Filled with response stream
    
Return Value:

    HRESULT

--*/
{
    HRESULT             hr;
    DWORD               i;
    CHAR *              pszChunk;
    HTTP_DATA_CHUNK *   pChunks;
    
    if ( pstrResponseStream == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }
    
    DBG_ASSERT( _responseMode == RESPONSE_MODE_RAW );

    pChunks = QueryChunks();

    for ( i = 0;
          i < _cFirstEntityChunk;
          i++ )
    {
        DBG_ASSERT( pChunks[ i ].DataChunkType == HttpDataChunkFromMemory );
        
        pszChunk = (CHAR*) pChunks[ i ].FromMemory.pBuffer;
        
        DBG_ASSERT( pszChunk != NULL );
        
        hr = pstrResponseStream->Append( pszChunk );
        if ( FAILED( hr ) ) 
        {
            return hr;
        }
    }
    
    return NO_ERROR;
}

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

Routine Description:
    
    Initialization of a W3_RESPONSE

Arguments:

    None
    
Return Value:

    None

--*/
{
    _ulHttpResponse.Flags       = 0;

    Clear();

    //
    // Set status to 200 (default)
    //
    SetStatus( HttpStatusOk );

    //
    // Keep track of whether the response has been touched (augmented).  This
    // is useful when determining whether an response was intended
    //
    
    _fResponseTouched   = FALSE;
    
    //
    // This response hasn't been sent yet
    //
    
    _fResponseSent      = FALSE;
}

HRESULT
W3_RESPONSE::InsertDataChunk(
    HTTP_DATA_CHUNK *   pNewChunk,
    LONG                cPosition
)
/*++

Routine Description:

    Insert given data chunk into list of chunks.  The position is determined
    by cPosition.  If a chunk occupies the given spot, it (along with all
    remaining) are shifted forward.

Arguments:
    
    pNewChunk - Chunk to insert
    cPosition - Position of new chunk (0 prepends, -1 appends)
    
Return Value:

    HRESULT

--*/
{
    HTTP_DATA_CHUNK *           pChunks = NULL;
    DWORD                       cOriginalChunkCount;
    
    if ( pNewChunk == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }

    //
    // Must be real position or -1
    //
    
    DBG_ASSERT( cPosition >= -1 );
    
    //
    // Allocate the new chunk if needed
    //
    
    cOriginalChunkCount = _cChunks;
    _cChunks += 1;
    
    if ( !_bufChunks.Resize( _cChunks * sizeof( HTTP_DATA_CHUNK ),
                             512 ) )
    {
        return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
    }

    pChunks = QueryChunks();
        
    //
    // If we're appending then this is simple.  Otherwise we must shift
    // 
    
    if ( cPosition == -1 )
    {
        memcpy( pChunks + cOriginalChunkCount,
                pNewChunk,
                sizeof( HTTP_DATA_CHUNK ) );
    }
    else
    {
        if ( cOriginalChunkCount > cPosition )
        {
            memmove( pChunks + cPosition + 1,
                     pChunks + cPosition,
                     sizeof( HTTP_DATA_CHUNK ) * ( cOriginalChunkCount - cPosition ) );
        }                    

        memcpy( pChunks + cPosition,
                pNewChunk,
                sizeof( HTTP_DATA_CHUNK ) );
    }
    
    return NO_ERROR;
}

HRESULT
W3_RESPONSE::ParseHeadersFromStream(
    CHAR *                  pszStream
)
/*++

Routine Description:

    Parse raw headers from ISAPI into the HTTP_RESPONSE

Arguments:

    pszStream - Stream of headers in form (Header: Value\r\nHeader2: value2\r\n)
    
Return Value:

    HRESULT

--*/
{
    CHAR *              pszCursor;
    CHAR *              pszEnd;
    CHAR *              pszColon;
    HRESULT             hr = NO_ERROR;
    STACK_STRA(         strHeaderLine, 128 );
    STACK_STRA(         strHeaderName, 32 );
    STACK_STRA(         strHeaderValue, 64 );
    
    if ( pszStream == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }
    
    //
    // \r\n delimited
    //
    
    pszCursor = pszStream;
    while ( pszCursor != NULL && *pszCursor != '\0' )
    {
        //
        // Check to see if pszCursor points to the "\r\n"
        // that separates the headers from the entity body
        // of the response and add a memory chunk for any
        // data that exists after it.
        //
        // This is to support ISAPI's that do something like
        // SEND_RESPONSE_HEADER with "head1: value1\r\n\r\nEntity"
        //

        if ( *pszCursor == '\r' && *(pszCursor + 1) == '\n' )
        {
            break;
        }
        
        pszEnd = strstr( pszCursor, "\r\n" );
        if ( pszEnd == NULL )
        {
            break;
        }
        
        //
        // Split out a line and convert to unicode
        //
         
        hr = strHeaderLine.Copy( pszCursor, 
                                 DIFF(pszEnd - pszCursor) );
        if ( FAILED( hr ) )
        {
            goto Finished;
        }        

        //
        // Advance the cursor the right after the \r\n
        //

        pszCursor = pszEnd + 2;
        
        //
        // Split the line above into header:value
        //
        
        pszColon = strchr( strHeaderLine.QueryStr(), ':' );
        if ( pszColon == NULL )
        {
            continue;
        }
        else
        {
            if ( pszColon == strHeaderLine.QueryStr() )
            {
                strHeaderName.Reset();
            }
            else
            {
                hr = strHeaderName.Copy( strHeaderLine.QueryStr(),
                                         DIFF(pszColon - strHeaderLine.QueryStr()) );
            }
        }
        
        if ( FAILED( hr ) )
        {
            goto Finished;
        }
        
        //
        // Skip the first space after the : if there is one
        //
        
        if ( pszColon[ 1 ] == ' ' )
        {
            pszColon++;
        }
        
        hr = strHeaderValue.Copy( pszColon + 1 );
        if ( FAILED( hr ) )
        {
            goto Finished;
        }

        //
        // Add the header to the response
        //

        hr = SetHeader( strHeaderName.QueryStr(),
                        strHeaderName.QueryCCH(),
                        strHeaderValue.QueryStr(),
                        strHeaderValue.QueryCCH(),
                        FALSE,
                        TRUE );
        if ( FAILED( hr ) )
        {
            goto Finished;
        }
    }

Finished:

    if ( FAILED( hr ) )
    {
        //
        // Don't allow the response to get into a quasi-bogus-state
        //

        Clear();
    }
    return hr;
}

//static
HRESULT
W3_RESPONSE::ReadFileIntoBuffer(
    HANDLE                  hFile,
    SEND_RAW_BUFFER *       pSendBuffer,
    ULONGLONG               cbCurrentFileOffset
)
/*++

Routine Description:

    Read contents of file into buffer

Arguments:

    hFile - File to read 
    pSendBuffer - Buffer to read into
    cbCurrentFileOffset - Offset to read from
    
Return Value:

    HRESULT

--*/
{
    OVERLAPPED              overlapped;
    LARGE_INTEGER           liOffset;
    BOOL                    fRet;
    DWORD                   cbRead;
    DWORD                   dwError;
    
    if ( hFile == NULL ||
         pSendBuffer == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }
    
    pSendBuffer->SetLen( 0 );
    
    liOffset.QuadPart = cbCurrentFileOffset;
    
    //
    // Setup overlapped with offset
    //

    ZeroMemory( &overlapped, sizeof( overlapped ) );
    
    overlapped.Offset = liOffset.LowPart;
    overlapped.OffsetHigh = liOffset.HighPart;

    //
    // Do the read
    //
    
    fRet = ReadFile( hFile,
                     pSendBuffer->QueryPtr(),
                     pSendBuffer->QuerySize(),
                     &cbRead,
                     &overlapped );
    if ( !fRet )
    {
        dwError = GetLastError();
        
        if ( dwError == ERROR_IO_PENDING )
        {
            fRet = GetOverlappedResult( hFile,
                                        &overlapped,
                                        &cbRead,
                                        TRUE );
    
            if ( !fRet )
            {
                dwError = GetLastError();
                
                if ( dwError == ERROR_HANDLE_EOF )
                {
                    fRet = TRUE;
                }
            }
        }
        else if ( dwError == ERROR_HANDLE_EOF )
        {
            fRet = TRUE;
        }
    }
    
    //
    // If there was an error, bail
    //
    
    if ( !fRet )
    {
        return HRESULT_FROM_WIN32( dwError );
    }
    else
    {
        pSendBuffer->SetLen( cbRead );
        return NO_ERROR;
    }
}

HRESULT
W3_RESPONSE::ProcessRawChunks(
    W3_CONTEXT *                pW3Context,
    BOOL *                      pfFinished
)
/*++

Routine Description:

    Iterate thru chunks, serializing and filtering as needed

Arguments:

    pW3Context - Context used to filter with
    pfFinished - Set to TRUE if filter wanted to finish
    
Return Value:

    HRESULT

--*/
{
    DWORD                   cCurrentChunk = 0;
    DWORD                   cCurrentInsertPos = 0;
    DWORD                   cFileChunkCount = 0;
    HTTP_DATA_CHUNK *       pChunk;
    HTTP_DATA_CHUNK         DataChunk;
    HTTP_FILTER_RAW_DATA    origStream;
    HTTP_FILTER_RAW_DATA    currStream;
    HRESULT                 hr = NO_ERROR;
    BOOL                    fRet;
    LONGLONG                cbCurrentFileOffset;
    LONGLONG                cbTarget;
    SEND_RAW_BUFFER *       pSendBuffer = NULL;
    HANDLE                  hFile;
    
    DBG_ASSERT( pfFinished != NULL );
    DBG_ASSERT( pW3Context != NULL );
    
    *pfFinished = FALSE;
    
    //
    // Start iterating thru chunks
    //
    
    for ( cCurrentChunk = 0;
          cCurrentChunk < _cChunks;
          cCurrentChunk++ )
    {
        pChunk = &(QueryChunks()[ cCurrentChunk ]);

        //
        // First remember this chunk incase filter changed it
        //
        
        //
        // If memory chunk, this is easy.  Otherwise we need to read the file
        // handle and build up memory chunks
        //
        
        if ( pChunk->DataChunkType == HttpDataChunkFromMemory )
        {
            origStream.pvInData = pChunk->FromMemory.pBuffer;
            origStream.cbInData = pChunk->FromMemory.BufferLength;
            origStream.cbInBuffer = pChunk->FromMemory.BufferLength;
       
            memcpy( &currStream, &origStream, sizeof( currStream ) );
       
            fRet = pW3Context->NotifyFilters( SF_NOTIFY_SEND_RAW_DATA,
                                              &currStream,
                                              pfFinished );
            if ( !fRet )
            {
                hr = HRESULT_FROM_WIN32( GetLastError() );
                break;
            }
            
            if ( *pfFinished )
            {
                goto Finished;
            }
            
            if ( currStream.pvInData == origStream.pvInData )
            {
                //
                // Just adjust the chunk
                //
                
                pChunk->FromMemory.BufferLength = currStream.cbInData;
            }
            else
            {
                //
                // Allocate new buffer and tweak chunk to refer to it
                //
                
                pSendBuffer = new SEND_RAW_BUFFER;
                if ( pSendBuffer == NULL )
                {
                    hr = HRESULT_FROM_WIN32( GetLastError() );
                    break;
                }
                
                hr = pSendBuffer->Resize( currStream.cbInData );
                if ( FAILED( hr ) )
                {
                    break;
                }
                
                memcpy( pSendBuffer->QueryPtr(),
                        currStream.pvInData,
                        currStream.cbInData );
                        
                pChunk->FromMemory.BufferLength = currStream.cbInData;
                pChunk->FromMemory.pBuffer = pSendBuffer->QueryPtr();
                
                InsertHeadList( &_SendRawBufferHead, 
                                &(pSendBuffer->_listEntry) );
                
                pSendBuffer = NULL;            
            }
        }
        else if ( pChunk->DataChunkType == HttpDataChunkFromFileHandle )
        {
            //
            // Reset file offsets
            //
            
            cbCurrentFileOffset = 0;
            
            //
            // How many bytes are we reading?
            // 
            
            cbTarget = pChunk->FromFileHandle.ByteRange.Length.QuadPart;
            
            //
            // Remember where to start inserting memory chunks
            //
            
            cCurrentInsertPos = cCurrentChunk;

            //
            // First memory replacement chunk can use file handle chunk
            //
            
            cFileChunkCount = 0; 
            
            //
            // Remember the handle now since we will be overwriting this
            // chunk with a memory chunk
            //
            
            hFile = pChunk->FromFileHandle.FileHandle;
            
            while ( cbCurrentFileOffset < cbTarget )
            {
                //
                // Allocate a buffer
                //
                
                pSendBuffer = new SEND_RAW_BUFFER;
                if ( pSendBuffer == NULL )
                {   
                    hr = HRESULT_FROM_WIN32( GetLastError() );
                    break;
                }
                
                //
                // Read some of the file into buffer
                //
                
                hr = ReadFileIntoBuffer( hFile,
                                         pSendBuffer,
                                         cbCurrentFileOffset );
                
                if ( FAILED( hr ) )
                {
                    break;
                }
                
                //
                // Update offset
                //
                
                cbCurrentFileOffset += pSendBuffer->QueryCB();
                
                //
                // Setup memory chunk to filter
                //
                
                origStream.pvInData = pSendBuffer->QueryPtr();
                origStream.cbInBuffer = pSendBuffer->QuerySize();
                origStream.cbInData = pSendBuffer->QueryCB();

                memcpy( &currStream, &origStream, sizeof( currStream ) );

                //
                // Filter the data
                //
                
                fRet = pW3Context->NotifyFilters( SF_NOTIFY_SEND_RAW_DATA,
                                                  &currStream,
                                                  pfFinished );
                
                if ( !fRet )
                {
                    hr = HRESULT_FROM_WIN32( GetLastError() );
                    break;
                }
                
                if ( *pfFinished )
                {
                    goto Finished;
                }
                
                //
                // Should we keep the file content chunk
                //
                
                if ( currStream.pvInData != origStream.pvInData )
                {   
                    //
                    // We have a new memory address containing data
                    //
                    
                    if ( currStream.cbInData > pSendBuffer->QuerySize() )
                    {
                        hr = pSendBuffer->Resize( currStream.cbInData );
                        if ( FAILED( hr ) )
                        {
                            break;
                        }
                    }
                        
                    memcpy( pSendBuffer->QueryPtr(),
                            currStream.pvInData,
                            currStream.cbInData );
                }
                
                //
                // Add the chunk.  If the first chunk, we can replace
                // the original file handle chunk
                //
                
                if ( cFileChunkCount == 0 )
                {
                    pChunk->DataChunkType = HttpDataChunkFromMemory;
                    pChunk->FromMemory.pBuffer = pSendBuffer->QueryPtr();
                    pChunk->FromMemory.BufferLength = currStream.cbInData;
                    
                    cCurrentInsertPos++;
                }
                else
                {
                    DataChunk.DataChunkType = HttpDataChunkFromMemory;
                    DataChunk.FromMemory.pBuffer = pSendBuffer->QueryPtr();
                    DataChunk.FromMemory.BufferLength = currStream.cbInData;

                    hr = InsertDataChunk( &DataChunk,
                                          cCurrentInsertPos++ );
                    if ( FAILED( hr ) )
                    {
                        break;
                    }
                }
                
                InsertHeadList( &_SendRawBufferHead, 
                                &(pSendBuffer->_listEntry) );
                
                pSendBuffer = NULL;            
                
                cFileChunkCount++;
            }
            
            //
            // If we're here because of failure, bail
            //
            
            if ( FAILED( hr ) )
            {
                break;
            }
            
            //
            // Update current chunk to be processed
            //
            
            cCurrentChunk = cCurrentInsertPos;
        }
        else
        {
            //
            // Only support file-handle and memory chunks
            //
            
            DBG_ASSERT( FALSE );
        }
    }

Finished:

    if ( pSendBuffer != NULL )
    {
        delete pSendBuffer;
    }
    
    return hr;
}

//static
HRESULT
W3_RESPONSE::Initialize(
    VOID
)
/*++

Routine Description:

    Enable W3_RESPONSE globals

Arguments:

    None
    
Return Value:

    HRESULT

--*/
{
    HRESULT                 hr;
    
    hr = RESPONSE_HEADER_HASH::Initialize();
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    hr = SEND_RAW_BUFFER::Initialize();
    if ( FAILED( hr ) )
    {
        RESPONSE_HEADER_HASH::Terminate();
    }
   
    return hr;
}

//static
VOID
W3_RESPONSE::Terminate(
    VOID
)
{
    SEND_RAW_BUFFER::Terminate();

    RESPONSE_HEADER_HASH::Terminate();
}

CONTEXT_STATUS
W3_STATE_RESPONSE::DoWork(
    W3_MAIN_CONTEXT *       pMainContext,
    DWORD                   cbCompletion,
    DWORD                   dwCompletionStatus
)
/*++

Routine Description:

    This state is responsible for ensuring that a response does get sent
    back to the client.  We hope/expect that the handlers will do their
    thing -> but if they don't we will catch that here and send a response

Arguments:

    pMainContext - Context
    cbCompletion - Number of bytes in an async completion
    dwCompletionStatus - Error status of a completion
    
Return Value:

    CONTEXT_STATUS_PENDING or CONTEXT_STATUS_CONTINUE

--*/
{
    W3_RESPONSE*            pResponse;
    DWORD                   dwOldState;
    HRESULT                 hr;
    
    pResponse = pMainContext->QueryResponse();
    DBG_ASSERT( pResponse != NULL );
    
    //
    // Has a response been sent?  If not, bail
    //
    
    if ( pMainContext->QueryResponseSent() )
    {
        return CONTEXT_STATUS_CONTINUE;
    }
    
    //
    // If the response has been touched, then just send that response.  Else
    // send a 500 error
    //
    
    if ( !pResponse->QueryResponseTouched() )
    {
        pResponse->SetStatus( HttpStatusServerError );
    }

    //
    // Send it out
    //

    hr = pMainContext->SendResponse( W3_FLAG_ASYNC );
    if ( FAILED( hr ) )
    {
        pMainContext->SetErrorStatus( hr );
        return CONTEXT_STATUS_CONTINUE;
    }
    else
    {
        return CONTEXT_STATUS_PENDING;
    }
}

CONTEXT_STATUS
W3_STATE_RESPONSE::OnCompletion(
    W3_MAIN_CONTEXT *       pW3Context,
    DWORD                   cbCompletion,
    DWORD                   dwCompletionStatus
)
/*++

Routine Description:

    Subsequent completions in this state

Arguments:

    pW3Context - Context
    cbCompletion - Number of bytes in an async completion
    dwCompletionStatus - Error status of a completion
    
Return Value:

    CONTEXT_STATUS_PENDING or CONTEXT_STATUS_CONTINUE

--*/
{
    //
    // We received an IO completion.  Just advance since we have nothing to
    // cleanup
    //
    
    return CONTEXT_STATUS_CONTINUE;
}  

//static
HRESULT
SEND_RAW_BUFFER::Initialize(
    VOID
)
/*++

Routine Description:

    Global initialization routine for SEND_RAW_BUFFERs

Arguments:

    None
    
Return Value:

    HRESULT

--*/
{
    ALLOC_CACHE_CONFIGURATION       acConfig;
    HRESULT                         hr = NO_ERROR;

    //
    // Setup allocation lookaside
    //
    
    acConfig.nConcurrency = 1;
    acConfig.nThreshold = 100;
    acConfig.cbSize = sizeof( SEND_RAW_BUFFER );

    DBG_ASSERT( sm_pachSendRawBuffers == NULL );
    
    sm_pachSendRawBuffers = new ALLOC_CACHE_HANDLER( "SEND_RAW_BUFFER",  
                                                      &acConfig );

    if ( sm_pachSendRawBuffers == NULL )
    {
        return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
    }
    
    return NO_ERROR;
}

//static
VOID
SEND_RAW_BUFFER::Terminate(
    VOID
)
/*++

Routine Description:

    Terminate MAIN_CONTEXT globals

Arguments:

    None
    
Return Value:

    None

--*/
{
    if ( sm_pachSendRawBuffers != NULL )
    {
        delete sm_pachSendRawBuffers;
        sm_pachSendRawBuffers = NULL;
    }
}