/*++ 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; } }