Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3633 lines
95 KiB

/*++
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"
#define SERVER_HEADER_NAME "Server"
#define SERVER_HEADER_VALUE "Microsoft-IIS/6.0"
//
// 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 HttpStatusEntityTooLarge = { 413, REASON("Request Entity Too Large") };
HTTP_STATUS HttpStatusUrlTooLong = { 414, REASON("URL Too Long") };
HTTP_STATUS HttpStatusRangeNotSatisfiable={ 416, REASON("Requested Range Not Satisfiable") };
HTTP_STATUS HttpStatusExpectationFailed = { 417, REASON("Expectation Failed") };
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 Http403AppPoolDenied = { MD_ERROR_SUB403_APPPOOL_DENIED, IDS_APPPOOL_DENIED };
HTTP_SUB_ERROR Http403InsufficientPrivilegeForCgi = { MD_ERROR_SUB403_INSUFFICIENT_PRIVILEGE_FOR_CGI, IDS_INSUFFICIENT_PRIVILEGE_FOR_CGI };
HTTP_SUB_ERROR Http403PassportLoginFailure = { MD_ERROR_SUB403_PASSPORT_LOGIN_FAILURE, 0 };
HTTP_SUB_ERROR Http404DeniedByPolicy = { MD_ERROR_SUB404_DENIED_BY_POLICY, 0 };
HTTP_SUB_ERROR Http404DeniedByMimeMap = { MD_ERROR_SUB404_DENIED_BY_MIMEMAP, 0 };
HTTP_SUB_ERROR Http500UNCAccess = { MD_ERROR_SUB500_UNC_ACCESS, 0 };
HTTP_SUB_ERROR Http500BadMetadata = { MD_ERROR_SUB500_BAD_METADATA, 0 };
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 };
//
// static variables
//
DWORD W3_RESPONSE::sm_dwSendRawDataBufferSize = 2048;
HRESULT
SendEntityBodyAndLogDataHelper(
W3_CONTEXT *pW3Context,
ULATQ_CONTEXT pContext,
BOOL fAsync,
DWORD dwFlags,
USHORT cChunks,
HTTP_DATA_CHUNK * pChunks,
DWORD *pcbSent
)
/*++
Routine Description:
Simple wrapper of the UlAtqSendEntityBody function
Make sure the log data gets passed on correctly if it is the final send
Also update bytes seen
Arguments:
pW3Context - W3 Context
pContext - ULATQ context
fAsync - Is the send async?
dwFlags - HTTP.SYS response flags to be used
cChunks - Count of chunks to send
pChunks - Pointer to array of chunks
pcbSent - If sync, filled in with bytes sent upon return
Return Value:
HRESULT
--*/
{
BOOL fDoLogging = FALSE;
HRESULT hr;
if ( pW3Context == NULL ||
pContext == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
if (!(dwFlags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA) &&
pW3Context->QueryDoUlLogging())
{
fDoLogging = TRUE;
if ( FAILED( hr = pW3Context->QueryMainContext()->CollectLoggingData( TRUE ) ) )
{
return hr;
}
}
pW3Context->QueryMainContext()->UpdateSkipped( pChunks,
cChunks );
return UlAtqSendEntityBody(pContext,
fAsync,
dwFlags,
cChunks,
pChunks,
pcbSent,
fDoLogging ? pW3Context->QueryUlLogData() : NULL);
}
HRESULT
SendHttpResponseAndLogDataHelper(
W3_CONTEXT *pW3Context,
ULATQ_CONTEXT pContext,
BOOL fAsync,
DWORD dwFlags,
HTTP_RESPONSE * pResponse,
HTTP_CACHE_POLICY * pCachePolicy,
DWORD * pcbSent
)
/*++
Routine Description:
Simple wrapper of the UlAtqSendHttpResponse function
Make sure the log data gets passed on correctly if it is the final send
Arguments:
pW3Context - W3 Context
pContext - ULATQ context
fAsync - Is the send async?
dwFlags - HTTP.SYS response flags to be used
pResponse - HTTP_RESPONSE to send
pCachePolicy - Cache policy (optional) for response
pcbSent - If sync, filled in with bytes sent upon return
Return Value:
HRESULT
--*/
{
BOOL fDoLogging = FALSE;
HRESULT hr;
if ( pW3Context == NULL ||
pContext == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
if (!(dwFlags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA) &&
pW3Context->QueryDoUlLogging())
{
fDoLogging = TRUE;
if ( FAILED( hr = pW3Context->QueryMainContext()->CollectLoggingData( TRUE ) ) )
{
return hr;
}
}
return UlAtqSendHttpResponse(pContext,
fAsync,
dwFlags,
pResponse,
pCachePolicy,
pcbSent,
fDoLogging ? pW3Context->QueryUlLogData() : NULL);
}
HRESULT
W3_RESPONSE::SetHeader(
DWORD ulResponseHeaderIndex,
CHAR * pszHeaderValue,
USHORT 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
fAppend - Should we append the header (otherwise we replace)
Return Value:
HRESULT
--*/
{
STACK_STRA ( strNewHeaderValue, 32);
CHAR * pszNewHeaderValue = NULL;
HRESULT hr;
DBG_ASSERT( ulResponseHeaderIndex < HttpHeaderResponseMaximum );
if ( _responseMode == RESPONSE_MODE_RAW )
{
hr = SwitchToParsedMode();
if ( FAILED( hr ) )
{
return NULL;
}
DBG_ASSERT( _responseMode == RESPONSE_MODE_PARSED );
}
if ( fAppend )
{
LPCSTR 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;
}
if ( strNewHeaderValue.QueryCCH() > MAXUSHORT )
{
return HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
}
cchHeaderValue = (USHORT)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,
USHORT cchHeaderName,
CHAR * pszHeaderValue,
USHORT cchHeaderValue,
BOOL fAppend,
BOOL fForceParsed,
BOOL fAlwaysAddUnknown,
BOOL fAppendAsDupHeader
)
/*++
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
fAppendAsDupHeader - Adds duplicate header instead of comma delimiting
Return Value:
HRESULT
--*/
{
DWORD cHeaders;
HTTP_UNKNOWN_HEADER* pHeader;
CHAR * pszNewName = NULL;
CHAR * pszNewValue = NULL;
HRESULT hr;
ULONG ulHeaderIndex;
STACK_STRA( strOldHeaderValue, 128 );
//
// 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 && !fAppendAsDupHeader )
{
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;
}
if ( strOldHeaderValue.QueryCCH() > MAXUSHORT )
{
return HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
}
cchHeaderValue = (USHORT)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 && !fAppendAsDupHeader ) ? 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 );
}
}
if ( ( strchr( pszNewValue, '\r' ) != NULL ) ||
( strchr( pszNewValue, '\n' ) != NULL ) )
{
return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
}
//
// 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 + 1;
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.UnknownHeaderCount++;
_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 = cchHeaderValue;
_fResponseTouched = TRUE;
return S_OK;
}
HRESULT
W3_RESPONSE::SetHeaderByReference(
DWORD ulResponseHeaderIndex,
CHAR * pszHeaderValue,
USHORT 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;
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.KnownHeaders[ ulResponseHeaderIndex ]);
if ( cchHeaderValue == 0 )
{
pHeader->pRawValue = NULL;
}
else
{
if ( ( strchr( pszHeaderValue, '\r' ) != NULL ) ||
( strchr( pszHeaderValue, '\n' ) != NULL ) )
{
return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
}
pHeader->pRawValue = pszHeaderValue;
_fResponseTouched = TRUE;
}
pHeader->RawValueLength = 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.KnownHeaders[ ulHeaderIndex ].pRawValue = "";
_ulHttpResponse.Headers.KnownHeaders[ 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;
}
VOID
W3_RESPONSE::FindStringId(
VOID
)
/*++
Routine Description:
Make the SendCustomError() ISAPI call a little more friendly. If the
user specifies a status/substatus, then try to find the matching
string ID so that we can send a built in error
Arguments:
None
Return Value:
None
--*/
{
if ( QueryStatusCode() == 403 )
{
switch( _subError.mdSubError )
{
case MD_ERROR_SUB403_EXECUTE_ACCESS_DENIED:
_subError.dwStringId = IDS_EXECUTE_ACCESS_DENIED;
break;
case MD_ERROR_SUB403_READ_ACCESS_DENIED:
_subError.dwStringId = IDS_READ_ACCESS_DENIED;
break;
case MD_ERROR_SUB403_WRITE_ACCESS_DENIED:
_subError.dwStringId = IDS_WRITE_ACCESS_DENIED;
break;
case MD_ERROR_SUB403_SSL_REQUIRED:
_subError.dwStringId = IDS_SSL_REQUIRED;
break;
case MD_ERROR_SUB403_SSL128_REQUIRED:
_subError.dwStringId = IDS_SSL128_REQUIRED;
break;
case MD_ERROR_SUB403_ADDR_REJECT:
_subError.dwStringId = IDS_ADDR_REJECT;
break;
case MD_ERROR_SUB403_CERT_REQUIRED:
_subError.dwStringId = IDS_CERT_REQUIRED;
break;
case MD_ERROR_SUB403_TOO_MANY_USERS:
_subError.dwStringId = IDS_TOO_MANY_USERS;
break;
case MD_ERROR_SUB403_PWD_CHANGE:
_subError.dwStringId = IDS_PWD_CHANGE;
break;
case MD_ERROR_SUB403_MAPPER_DENY_ACCESS:
_subError.dwStringId = IDS_MAPPER_DENY_ACCESS;
break;
case MD_ERROR_SUB403_CERT_REVOKED:
_subError.dwStringId = IDS_CERT_REVOKED;
break;
case MD_ERROR_SUB403_DIR_LIST_DENIED:
_subError.dwStringId = IDS_DIR_LIST_DENIED;
break;
case MD_ERROR_SUB403_CERT_BAD:
_subError.dwStringId = IDS_CERT_BAD;
break;
case MD_ERROR_SUB403_CERT_TIME_INVALID:
_subError.dwStringId = IDS_CERT_TIME_INVALID;
break;
case MD_ERROR_SUB403_APPPOOL_DENIED:
_subError.dwStringId = IDS_APPPOOL_DENIED;
break;
case MD_ERROR_SUB403_INSUFFICIENT_PRIVILEGE_FOR_CGI:
_subError.dwStringId = IDS_INSUFFICIENT_PRIVILEGE_FOR_CGI;
break;
}
}
else if ( QueryStatusCode() == 502 &&
_subError.mdSubError == MD_ERROR_SUB502_TIMEOUT )
{
_subError.dwStringId = IDS_CGI_APP_TIMEOUT;
}
else if ( QueryStatusCode() == 502 &&
_subError.mdSubError == MD_ERROR_SUB502_PREMATURE_EXIT )
{
_subError.dwStringId = IDS_BAD_CGI_APP;
}
}
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;
if ( strReason.QueryCCH() > MAXUSHORT )
{
return HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
}
hr = _HeaderBuffer.AllocateSpace( strReason.QueryStr(),
strReason.QueryCCH(),
&pszNewStatus );
if ( FAILED( hr ) )
{
return hr;
}
_ulHttpResponse.StatusCode = statusCode;
_ulHttpResponse.pReason = pszNewStatus;
_ulHttpResponse.ReasonLength = (USHORT) 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 );
}
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;
DWORD i;
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 ( i = 0; i < _ulHttpResponse.Headers.UnknownHeaderCount; i++ )
{
pUnknownHeader = &(_ulHttpResponse.Headers.pUnknownHeaders[ i ]);
DBG_ASSERT( pUnknownHeader != NULL );
if ( _stricmp( pszHeaderName,
pUnknownHeader->pName ) == 0 )
{
if ( !fFound )
{
hr = pstrHeaderValue->Copy( pUnknownHeader->pRawValue,
pUnknownHeader->RawValueLength );
if ( FAILED( hr ) )
{
return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
}
}
else
{
hr = pstrHeaderValue->Append( "," );
if ( FAILED( hr ) )
{
return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
}
hr = pstrHeaderValue->Append( pUnknownHeader->pRawValue,
pUnknownHeader->RawValueLength );
if ( FAILED( hr ) )
{
return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
}
}
fFound = TRUE;
}
}
if ( fFound )
{
return NO_ERROR;
}
else
{
return HRESULT_FROM_WIN32( ERROR_INVALID_INDEX );
}
}
else
{
pKnownHeader = &(_ulHttpResponse.Headers.KnownHeaders[ ulHeaderIndex ]);
if ( pKnownHeader->pRawValue != NULL &&
pKnownHeader->RawValueLength != 0 )
{
hr = pstrHeaderValue->Copy( pKnownHeader->pRawValue,
pKnownHeader->RawValueLength );
//
// Still need to loop through the unknown headers, in case
// this header has been added there.
//
for ( i = 0; i < _ulHttpResponse.Headers.UnknownHeaderCount; i++ )
{
pUnknownHeader = &(_ulHttpResponse.Headers.pUnknownHeaders[ i ]);
DBG_ASSERT( pUnknownHeader != NULL );
if ( _stricmp( pszHeaderName,
pUnknownHeader->pName ) == 0 )
{
hr = pstrHeaderValue->Append( "," );
if ( FAILED( hr ) )
{
return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
}
hr = pstrHeaderValue->Append( pUnknownHeader->pRawValue,
pUnknownHeader->RawValueLength );
if ( FAILED( hr ) )
{
return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
}
}
}
return hr;
}
else
{
return HRESULT_FROM_WIN32( ERROR_INVALID_INDEX );
}
}
}
VOID
W3_RESPONSE::ClearHeaders(
VOID
)
/*++
Routine Description:
Clear headers
Arguments:
None
Return Value:
None
--*/
{
HTTP_UNKNOWN_HEADER * pHeader;
memset( &(_ulHttpResponse.Headers),
0,
sizeof( _ulHttpResponse.Headers ) );
//
// Set the known "server" header to blank and add
// an unknown header called "server". This is to
// prevent munging of the server header by http.sys.
//
_ulHttpResponse.Headers.KnownHeaders[ HttpHeaderServer ].pRawValue = "";
_ulHttpResponse.Headers.KnownHeaders[ HttpHeaderServer ].RawValueLength = 0;
_ulHttpResponse.Headers.UnknownHeaderCount = 1;
_ulHttpResponse.Headers.pUnknownHeaders = (HTTP_UNKNOWN_HEADER*)_bufUnknownHeaders.QueryPtr();
pHeader = &(_ulHttpResponse.Headers.pUnknownHeaders[ 0 ]);
pHeader->pName = SERVER_HEADER_NAME;
pHeader->NameLength = sizeof( SERVER_HEADER_NAME ) - 1;
pHeader->pRawValue = SERVER_HEADER_VALUE;
pHeader->RawValueLength = sizeof( SERVER_HEADER_VALUE ) - 1;
}
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::AddFragmentChunk(
WCHAR * pszFragmentName,
USHORT dwNameLength
)
/*++
Routine Description:
Add fragment chunk to response
Arguments:
pszFragmentName - name of the fragment
dwNameLength - length of the name
Return Value:
HRESULT
--*/
{
HTTP_DATA_CHUNK DataChunk;
_fResponseTouched = TRUE;
DataChunk.DataChunkType = HttpDataChunkFromFragmentCache;
DataChunk.FromFragmentCache.pFragmentName = pszFragmentName;
DataChunk.FromFragmentCache.FragmentNameLength = dwNameLength;
return InsertDataChunk( &DataChunk, -1 );
}
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::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 = _cChunks - _cFirstEntityChunk;
_cFirstEntityChunk = 0;
}
//
// Cool. Now we are in parsed mode
//
_responseMode = RESPONSE_MODE_PARSED;
return NO_ERROR;
}
HRESULT
W3_RESPONSE::SwitchToRawMode(
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:
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();
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,
HTTP_CACHE_POLICY * pCachePolicy,
DWORD * pcbSent
)
/*++
Routine Description:
Send a W3_RESPONSE to the client.
Arguments:
pW3Context - W3 context (contains amongst other things ULATQ context)
dwResponseFlags - W3_RESPONSE* flags
pCachePolicy - Cache-policy controlling cachability of the response
pcbSent - Filled with number of bytes sent (if sync)
Return Value:
HRESULT
--*/
{
HRESULT hr;
DWORD dwFlags = 0;
HTTP_DATA_CHUNK * pStartChunk;
BOOL fAsync;
BOOL fDoCompression = FALSE;
if ( pW3Context == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
DBG_ASSERT( CheckSignature() );
if ( dwResponseFlags & W3_RESPONSE_SUPPRESS_HEADERS )
{
if (_responseMode == RESPONSE_MODE_RAW)
{
_cChunks = _cChunks - _cFirstEntityChunk;
memmove( QueryChunks(),
QueryChunks() + _cFirstEntityChunk,
_cChunks * sizeof( HTTP_DATA_CHUNK ) );
_cFirstEntityChunk = 0;
}
return SendEntity( pW3Context,
dwResponseFlags,
pcbSent );
}
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 any headers which are needed for dynamic compression, if
// supported for this request
//
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 )))
{
return hr;
}
if (pW3Context->QueryCompressionContext() != NULL)
{
fDoCompression = TRUE;
}
}
}
//
// 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 ||
fDoCompression )
{
if ( _responseMode == RESPONSE_MODE_PARSED )
{
hr = GenerateAutomaticHeaders( pW3Context,
&dwFlags );
if ( FAILED( hr ) )
{
return hr;
}
hr = SwitchToRawMode( _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;
}
}
//
// From now on, we must send any future responses raw
//
_fResponseSent = TRUE;
//
// Async?
//
fAsync = dwResponseFlags & W3_RESPONSE_ASYNC ? TRUE : FALSE;
//
// If we have a send raw filter, start processing the chunks now.
//
if ( pW3Context->IsNotificationNeeded( SF_NOTIFY_SEND_RAW_DATA ) ||
fDoCompression )
{
_cCurrentChunk = 0;
_cbCurrentOffset = 0;
_cbTotalCompletion = 0;
_dwSendFlags = dwFlags;
return ResumeResponseTransfer( pW3Context,
FALSE,
0,
ERROR_SUCCESS,
fAsync,
pcbSent );
}
//
// The simple non-filter case, just send out the darn response
//
if ( _responseMode == RESPONSE_MODE_RAW )
{
if ( dwResponseFlags & W3_RESPONSE_SUPPRESS_ENTITY )
{
_cChunks = _cFirstEntityChunk;
}
hr = SendEntityBodyAndLogDataHelper(
pW3Context,
pW3Context->QueryUlatqContext(),
fAsync,
dwFlags | HTTP_SEND_RESPONSE_FLAG_RAW_HEADER,
_cChunks,
_cChunks ? QueryChunks() : NULL,
pcbSent );
}
else
{
if ( dwResponseFlags & W3_RESPONSE_SUPPRESS_ENTITY )
{
_cChunks = 0;
}
_ulHttpResponse.EntityChunkCount = _cChunks;
_ulHttpResponse.pEntityChunks = _cChunks ? QueryChunks() : NULL;
hr = SendHttpResponseAndLogDataHelper(
pW3Context,
pW3Context->QueryUlatqContext(),
fAsync,
dwFlags,
&(_ulHttpResponse),
pCachePolicy,
pcbSent );
}
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
// unless the failure was due to a fragment missing from the send
//
if (hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND))
{
_fResponseSent = FALSE;
}
}
return hr;
}
HRESULT
W3_RESPONSE::SendEntity(
W3_CONTEXT * pW3Context,
DWORD dwResponseFlags,
DWORD * pcbSent
)
/*++
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)
Return Value:
Win32 Error indicating status
--*/
{
HRESULT hr = NO_ERROR;
DWORD dwFlags = 0;
BOOL fAsync;
BOOL fDoCompression = FALSE;
BOOL fOldResponseSent;
if ( pW3Context == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
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)
//
fOldResponseSent = _fResponseSent;
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;
}
//
// Do we need to do compression?
//
if ( pW3Context->QueryUrlContext() != NULL )
{
W3_METADATA *pMetaData;
pMetaData = pW3Context->QueryUrlContext()->QueryMetaData();
DBG_ASSERT( pMetaData != NULL);
if (!pW3Context->QueryDoneWithCompression() &&
pMetaData->QueryDoDynamicCompression())
{
fDoCompression = TRUE;
}
}
//
// 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;
//
// Send chunks to be processed if filtering if needed (and there are
// chunks available)
//
if ( pW3Context->IsNotificationNeeded( SF_NOTIFY_SEND_RAW_DATA ) ||
fDoCompression )
{
_cCurrentChunk = _cFirstEntityChunk;
_cbCurrentOffset = 0;
_cbTotalCompletion = 0;
_dwSendFlags = dwFlags;
return ResumeResponseTransfer( pW3Context,
FALSE,
0,
ERROR_SUCCESS,
fAsync,
pcbSent );
}
//
// Finally, send stuff out
//
hr = SendEntityBodyAndLogDataHelper(
pW3Context,
pW3Context->QueryUlatqContext(),
fAsync,
dwFlags,
_cChunks,
_cChunks ? QueryChunks() : NULL,
pcbSent );
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
// unless the failure was due to a fragment missing from the send
//
if (hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND))
{
_fResponseSent = fOldResponseSent;
}
}
return hr;
}
HRESULT
W3_RESPONSE::GenerateAutomaticHeaders(
W3_CONTEXT * pW3Context,
DWORD * pdwFlags
)
/*++
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
pdwFlags - On input, the flags passed to UL,
On output, the new flags to be passed to UL
Return Value:
HRESULT
--*/
{
HTTP_KNOWN_HEADER * pHeader;
CHAR * pszHeaderValue;
CHAR achDate[ 128 ];
CHAR achNum[ 64 ];
USHORT cchDate;
USHORT cchNum;
SYSTEMTIME systemTime;
HTTP_VERSION httpVersion;
HRESULT hr;
BOOL fCreateContentLength;
BOOL fDisconnecting = FALSE;
if ( pdwFlags == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
DBG_ASSERT( _responseMode == RESPONSE_MODE_PARSED );
//
// Server:
//
pHeader = &(_ulHttpResponse.Headers.KnownHeaders[ HttpHeaderServer ]);
if ( pHeader->pRawValue == NULL )
{
pHeader->pRawValue = SERVER_SOFTWARE_STRING;
pHeader->RawValueLength = sizeof( SERVER_SOFTWARE_STRING ) - 1;
}
//
// Date:
//
pHeader = &(_ulHttpResponse.Headers.KnownHeaders[ HttpHeaderDate ]);
if ( pHeader->pRawValue == NULL )
{
if(!IISGetCurrentTimeAsSystemTime(&systemTime))
{
GetSystemTime( &systemTime );
}
if ( !SystemTimeToGMT( systemTime,
achDate,
sizeof(achDate) ) )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
cchDate = (USHORT)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 ( *pdwFlags & HTTP_SEND_RESPONSE_FLAG_DISCONNECT ||
( pW3Context != NULL &&
( pW3Context->QueryDisconnect() ||
pW3Context->QueryRequest()->QueryClientWantsDisconnect() ) ) )
{
fDisconnecting = TRUE;
}
//
// If disconnecting, indicate that in flags
//
if ( fDisconnecting )
{
*pdwFlags |= HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
}
//
// Connection:
//
pHeader = &(_ulHttpResponse.Headers.KnownHeaders[ 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 ( *pdwFlags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA )
{
fCreateContentLength = FALSE;
}
if ( fCreateContentLength &&
( QueryStatusCode() / 100 == 1 ||
QueryStatusCode() == 204 ||
QueryStatusCode() == 304 ) )
{
fCreateContentLength = FALSE;
}
if ( fCreateContentLength )
{
LPCSTR pszXferEncoding = GetHeader(HttpHeaderTransferEncoding);
if ( pszXferEncoding != NULL &&
_stricmp( pszXferEncoding, "chunked" ) == 0 )
{
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.KnownHeaders[ HttpHeaderContentLength ]);
if ( pHeader->pRawValue == NULL )
{
_ui64toa( QueryContentLength(),
achNum,
10 );
cchNum = (USHORT)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(
VOID
)
/*++
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:
None
Return Value:
HRESULT
--*/
{
HRESULT hr = NO_ERROR;
CHAR achNumber[ 32 ];
HTTP_KNOWN_HEADER * pKnownHeader;
HTTP_UNKNOWN_HEADER * pUnknownHeader;
CHAR * pszHeaderName;
DWORD cchHeaderName;
DWORD i;
_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.KnownHeaders[ 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 = (DWORD)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;
HRESULT hr;
BOOL fFinished = FALSE;
DWORD dwFlags = HTTP_SEND_RESPONSE_FLAG_MORE_DATA |
HTTP_SEND_RESPONSE_FLAG_RAW_HEADER;
_fResponseTouched = TRUE;
_fResponseSent = TRUE;
rawStream.pvInData = pvData;
rawStream.cbInData = cbData;
rawStream.cbInBuffer = cbData;
//
// If there are send raw filters to be notified, do so now
//
if ( pW3Context->IsNotificationNeeded( SF_NOTIFY_SEND_RAW_DATA ) )
{
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;
}
}
hr = HTTP_COMPRESSION::DoDynamicCompression( pW3Context,
TRUE,
&rawStream );
if (FAILED(hr))
{
return hr;
}
dataChunk.DataChunkType = HttpDataChunkFromMemory;
dataChunk.FromMemory.pBuffer = rawStream.pvInData;
dataChunk.FromMemory.BufferLength = rawStream.cbInData;
//
// Manage up the thread count since we might be doing a really long
// send
//
ThreadPoolSetInfo( ThreadPoolIncMaxPoolThreads, 0 );
hr = SendEntityBodyAndLogDataHelper(
pW3Context,
pW3Context->QueryUlatqContext(),
FALSE, // sync
dwFlags,
dataChunk.FromMemory.BufferLength == 0 ? 0 : 1,
dataChunk.FromMemory.BufferLength == 0 ? NULL : &dataChunk,
&cbSent );
ThreadPoolSetInfo( ThreadPoolDecMaxPoolThreads, 0 );
return hr;
}
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 * pszRawAdditionalIsapiHeaders = NULL;
DWORD cchRawAdditionalIsapiHeaders = 0;
CHAR * pszRawAdditionalIsapiEntity = NULL;
DWORD cchRawAdditionalIsapiEntity = 0;
DWORD dwFlags;
HTTP_DATA_CHUNK DataChunk;
Clear();
_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)
//
dwFlags = HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
hr = GenerateAutomaticHeaders( pW3Context,
&dwFlags );
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 = (DWORD)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( 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;
//
// Should we be intercepting completions?
//
_fHandleCompletion = 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
//
if ( !_bufChunks.Resize( (_cChunks + 1) * sizeof( HTTP_DATA_CHUNK ),
512 ) )
{
return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
}
cOriginalChunkCount = _cChunks++;
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 > (DWORD)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,
(DWORD)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(),
(DWORD)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;
}
if ( strHeaderName.QueryCCH() > MAXUSHORT ||
strHeaderValue.QueryCCH() > MAXUSHORT )
{
hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
goto Finished;
}
if (strHeaderName.QueryCCH() > MAXUSHORT ||
strHeaderValue.QueryCCH() > MAXUSHORT)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
goto Finished;
}
//
// Add the header to the response
//
hr = SetHeader( strHeaderName.QueryStr(),
(USHORT)strHeaderName.QueryCCH(),
strHeaderValue.QueryStr(),
(USHORT)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;
}
HRESULT
W3_RESPONSE::ResumeResponseTransfer(
W3_CONTEXT * pW3Context,
BOOL fCompletion,
DWORD cbCompletion,
DWORD dwCompletionStatus,
BOOL fAsync,
DWORD * pcbSent
)
/*++
Routine Description:
Resume the send of a response/entity chunk by chunk
Arguments:
pW3Context - Context used to filter with
fCompletion - Is this called as a completion
cbCompletion - if fCompletion==TRUE, # of bytes of last completion
dwCompletionStatus - if fCompletion==TRUE, last completion status
fAsync - Is the send async?
pcbSent - Filled with # of bytes sent
Return Value:
HRESULT
--*/
{
HTTP_DATA_CHUNK * pChunk;
HTTP_FILTER_RAW_DATA origStream;
DWORD cbFilled = 0;
DWORD cbToCopy = 0;
BOOL fRet;
BOOL fFinished = FALSE;
HRESULT hr;
DWORD cbSent;
DWORD cbRead;
DWORD dwError;
HTTP_DATA_CHUNK dataChunk;
OVERLAPPED overlapped;
if ( pW3Context == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
//
// If this is a completion, then first reset our requirement to pick up
// the next completion
//
if ( fCompletion )
{
_fHandleCompletion = FALSE;
_cbTotalCompletion += cbCompletion;
}
//
// Prepare our 2K buffer (if it isn't already prepared)
//
if ( !_bufRawChunk.Resize( sm_dwSendRawDataBufferSize ) )
{
DBG_ASSERT( !fCompletion );
return HRESULT_FROM_WIN32( GetLastError() );
}
Restart:
//
// Keep track of how much of the 2K buffer we have filled
//
cbFilled = 0;
//
// Start with the current chunk, continue until we hit the end. In the
// interim, we may do async sends which cause us to keep state and
// resume appropriately
//
while ( _cCurrentChunk < _cChunks )
{
pChunk = &( QueryChunks()[ _cCurrentChunk ] );
if ( pChunk->DataChunkType == HttpDataChunkFromMemory )
{
//
// Copy as much of the chunk as we can
//
cbToCopy = min( _bufRawChunk.QuerySize() - cbFilled,
pChunk->FromMemory.BufferLength - (DWORD) _cbCurrentOffset );
memcpy( ( (PBYTE) _bufRawChunk.QueryPtr() ) + cbFilled,
(PBYTE) pChunk->FromMemory.pBuffer + (DWORD) _cbCurrentOffset,
cbToCopy );
cbFilled += cbToCopy;
/* DBGPRINTF(( DBG_CONTEXT,
"Processing chunk %d, bytes (%d,%d)\n",
_cCurrentChunk,
(DWORD) _cbCurrentOffset,
(DWORD) _cbCurrentOffset + cbToCopy ));
*/
//
// If we have completely handled the chunk, then update our
// state
//
if ( _cbCurrentOffset + cbToCopy >= pChunk->FromMemory.BufferLength )
{
_cCurrentChunk++;
_cbCurrentOffset = 0;
}
else
{
//
// Keep the current chunk the same, but update the offset
//
_cbCurrentOffset += cbToCopy;
}
}
else if ( pChunk->DataChunkType == HttpDataChunkFromFileHandle )
{
LARGE_INTEGER liOffset;
if ( pChunk->FromFileHandle.ByteRange.Length.QuadPart == HTTP_BYTE_RANGE_TO_EOF )
{
//
// We need to read the length ourselves
//
if ( !GetFileSizeEx( pChunk->FromFileHandle.FileHandle,
(PLARGE_INTEGER) &(pChunk->FromFileHandle.ByteRange.Length) ) )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
goto Failure;
}
pChunk->FromFileHandle.ByteRange.Length.QuadPart -= pChunk->FromFileHandle.ByteRange.StartingOffset.QuadPart;
}
cbToCopy = (DWORD) min( (ULONGLONG) ( _bufRawChunk.QuerySize() - cbFilled ),
pChunk->FromFileHandle.ByteRange.Length.QuadPart -
_cbCurrentOffset );
//
// Read the appropriate amount from the file
//
liOffset.QuadPart = _cbCurrentOffset;
liOffset.QuadPart += pChunk->FromFileHandle.ByteRange.StartingOffset.QuadPart;
ZeroMemory( &overlapped, sizeof( OVERLAPPED ) );
overlapped.Offset = liOffset.LowPart;
overlapped.OffsetHigh = liOffset.HighPart;
fRet = ReadFile( pChunk->FromFileHandle.FileHandle,
(PBYTE) _bufRawChunk.QueryPtr() + cbFilled,
cbToCopy,
&cbRead,
&overlapped );
if ( !fRet )
{
dwError = GetLastError();
if ( dwError == ERROR_IO_PENDING )
{
fRet = GetOverlappedResult( pChunk->FromFileHandle.FileHandle,
&overlapped,
&cbRead,
TRUE );
if ( !fRet )
{
dwError = GetLastError();
if ( dwError == ERROR_HANDLE_EOF )
{
fRet = TRUE;
}
}
}
else if ( dwError == ERROR_HANDLE_EOF )
{
fRet = TRUE;
}
}
if ( !fRet )
{
//
// Couldn't read from the file.
//
hr = HRESULT_FROM_WIN32( dwError );
goto Failure;
}
if ( cbRead != cbToCopy )
{
hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
goto Failure;
}
cbFilled += cbToCopy;
//
// What is the current chunk?
//
if ( _cbCurrentOffset + cbToCopy >= pChunk->FromFileHandle.ByteRange.Length.QuadPart )
{
//
// We've read the entire chunk. Move on.
//
_cCurrentChunk++;
_cbCurrentOffset = 0;
}
else
{
_cbCurrentOffset += cbToCopy;
}
}
//
// Have we filled a buffer, if so then bail.
//
if ( cbFilled >= _bufRawChunk.QuerySize() )
{
break;
}
}
origStream.pvInData = _bufRawChunk.QueryPtr();
origStream.cbInData = cbFilled;
origStream.cbInBuffer = _bufRawChunk.QuerySize();
//
// Start filtering the buffer
//
if ( cbFilled > 0 &&
pW3Context->IsNotificationNeeded( SF_NOTIFY_SEND_RAW_DATA ) )
{
fFinished = FALSE;
fRet = pW3Context->NotifyFilters( SF_NOTIFY_SEND_RAW_DATA,
&origStream,
&fFinished );
if ( !fRet || fFinished )
{
hr = fRet ? NO_ERROR : HRESULT_FROM_WIN32( GetLastError() );
goto Failure;
}
}
//
// Compress the chunk (if any), unless this is the final chance to compress
//
if ( origStream.cbInData > 0 ||
( _cCurrentChunk >= _cChunks &&
!( _dwSendFlags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA ) ) )
{
hr = HTTP_COMPRESSION::DoDynamicCompression( pW3Context,
(_cCurrentChunk < _cChunks) ? TRUE : (_dwSendFlags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA),
&origStream );
if ( FAILED( hr ) )
{
goto Failure;
}
}
//
// If the filter or compression ate up all the data, just continue
//
if ( origStream.cbInData == 0 &&
( _cCurrentChunk < _cChunks ||
( _dwSendFlags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA ) ) )
{
if ( _cCurrentChunk < _cChunks )
{
goto Restart;
}
if ( fAsync )
{
ThreadPoolPostCompletion( _cbTotalCompletion,
W3_MAIN_CONTEXT::OnPostedCompletion,
(OVERLAPPED*) pW3Context->QueryMainContext() );
}
return NO_ERROR;
}
//
// Send the chunk we have
//
dataChunk.DataChunkType = HttpDataChunkFromMemory;
dataChunk.FromMemory.pBuffer = origStream.pvInData;
dataChunk.FromMemory.BufferLength = origStream.cbInData;
//
// If we're sending async, then funnel the completion back to the
// response so we can continue
//
// BUT if this is going to be the final send, then we don't need
// to handle the completion
//
if ( fAsync &&
_cCurrentChunk < _cChunks )
{
_fHandleCompletion = TRUE;
}
hr = SendEntityBodyAndLogDataHelper(
pW3Context,
pW3Context->QueryUlatqContext(),
fAsync,
_dwSendFlags | HTTP_SEND_RESPONSE_FLAG_RAW_HEADER |
((_cCurrentChunk < _cChunks) ? HTTP_SEND_RESPONSE_FLAG_MORE_DATA : 0), // If it is not the last chunk, add more data flag
dataChunk.FromMemory.BufferLength == 0 ? 0 : 1,
dataChunk.FromMemory.BufferLength == 0 ? NULL : &dataChunk,
&cbSent );
if ( FAILED( hr ) )
{
_fHandleCompletion = FALSE;
goto Failure;
}
//
// If we sent this synchronously, then we can continue filtering
//
if ( !fAsync )
{
*pcbSent += cbSent;
if ( _cCurrentChunk < _cChunks )
{
goto Restart;
}
}
return NO_ERROR;
Failure:
if ( !fAsync )
{
SendEntityBodyAndLogDataHelper(
pW3Context,
pW3Context->QueryUlatqContext(),
FALSE,
_dwSendFlags |
HTTP_SEND_RESPONSE_FLAG_RAW_HEADER |
HTTP_SEND_RESPONSE_FLAG_DISCONNECT,
0,
NULL,
&cbSent );
return hr;
}
else
{
HRESULT hrDisconnect;
//
// If async, try the disconnect async
//
hrDisconnect = SendEntityBodyAndLogDataHelper(
pW3Context,
pW3Context->QueryUlatqContext(),
TRUE,
_dwSendFlags |
HTTP_SEND_RESPONSE_FLAG_RAW_HEADER |
HTTP_SEND_RESPONSE_FLAG_DISCONNECT,
0,
NULL,
&cbSent );
if ( FAILED( hrDisconnect ) )
{
//
// If this is the first call to ResumeResponseTransfer() then we
// can just return the original error here
//
if ( !fCompletion )
{
return hr;
}
else
{
//
// We're going to have to fake a completion
//
POST_MAIN_COMPLETION( pW3Context->QueryMainContext() );
return NO_ERROR;
}
}
else
{
return NO_ERROR;
}
}
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
}
//static
HRESULT
W3_RESPONSE::Initialize(
VOID
)
/*++
Routine Description:
Enable W3_RESPONSE globals
Arguments:
None
Return Value:
HRESULT
--*/
{
HKEY w3Params;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
W3_PARAMETERS_KEY,
0,
KEY_READ,
&w3Params) == NO_ERROR)
{
DWORD dwType;
DWORD cbData = sizeof DWORD;
if ((RegQueryValueEx(w3Params,
L"SendRawDataBufferSize",
NULL,
&dwType,
(LPBYTE)&sm_dwSendRawDataBufferSize,
&cbData) != NO_ERROR) ||
(dwType != REG_DWORD))
{
sm_dwSendRawDataBufferSize = 2048;
}
RegCloseKey(w3Params);
}
return RESPONSE_HEADER_HASH::Initialize();
}
//static
VOID
W3_RESPONSE::Terminate(
VOID
)
{
RESPONSE_HEADER_HASH::Terminate();
}
CONTEXT_STATUS
W3_STATE_RESPONSE::DoWork(
W3_MAIN_CONTEXT * pMainContext,
DWORD,
DWORD
)
/*++
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;
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 if the response has not been touched, then we should sent
// back an empty response that just closes the connection. This
// latter case is most likely the result of an ISAPI that doesn't
// send anything. Such an ISAPI expects that no response will
// reach the client.
//
if ( !pResponse->QueryResponseTouched() )
{
DWORD cbSent;
HTTP_CACHE_POLICY cachePolicy;
cachePolicy.Policy = HttpCachePolicyNocache;
hr = pResponse->SendResponse(
pMainContext,
W3_RESPONSE_ASYNC | W3_RESPONSE_SUPPRESS_HEADERS | W3_RESPONSE_DISCONNECT,
&cachePolicy,
&cbSent
);
if ( FAILED( hr ) )
{
pResponse->SetStatus( HttpStatusServerError );
}
else
{
return CONTEXT_STATUS_PENDING;
}
}
//
// 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 *,
DWORD,
DWORD
)
/*++
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;
}