Windows NT 4.0 source code leak
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.
 
 
 
 
 
 

1134 lines
27 KiB

/**********************************************************************/
/** Microsoft Windows NT **/
/** Copyright(c) Microsoft Corp., 1994 **/
/**********************************************************************/
/*
httpio.cxx
This module contains the IO related http class methods
FILE HISTORY:
Johnl 09-Feb-1995 Created
*/
#include <w3p.hxx>
#include <issperr.h>
//
// This is the number of extra bytes beyond the specified Content-Length to
// read while reading client entity data. Most browsers send an extra
// CRLF after the end of their entity data, this makes sure that data is
// picked up.
//
#define POST_SLOP 4
//
// Size of read during cert renegotiation phase
//
#define CERT_RENEGO_READ_SIZE 1024
BOOL
HTTP_REQ_BASE::StartNewRequest(
PVOID pvInitialBuff,
DWORD cbInitialBuff
)
/*++
Routine Description:
Sets up this request object for reading a new request and issues the async
read to kick things off.
Arguments:
--*/
{
//
// Set our initial state and variables for a new request
//
Reset();
//
// Prepare a buffer to receive the client's request
//
if ( !_bufClientRequest.Resize( max( W3_DEFAULT_BUFFSIZE, cbInitialBuff )))
{
TCP_PRINT(( DBG_CONTEXT,
"[StartNewRequest] failed to allocate buffer, error %lu\n",
GetLastError()));
return FALSE;
}
//
// Make the IO request if an inital buffer wasn't supplied
//
if ( pvInitialBuff )
{
memcpy( _bufClientRequest.QueryPtr(),
pvInitialBuff,
cbInitialBuff );
_cbBytesWritten = cbInitialBuff;
}
else
{
SetState( HTR_READING_CLIENT_REQUEST );
IF_DEBUG( CONNECTION )
{
TCP_PRINT(( DBG_CONTEXT,
"[StartNewRequest] Issuing initial read, Conn = %lx, AtqCont = %lx\n",
QueryClientConn(),
QueryClientConn()->QueryAtqContext() ));
}
//
// Do the initial read. We don't go through any filters at this
// point. They'll get notified on the read completion as part of
// the raw data notification.
//
if ( !ReadFile( _bufClientRequest.QueryPtr(),
_bufClientRequest.QuerySize(),
NULL,
IO_FLAG_ASYNC | IO_FLAG_NO_FILTER))
{
TCP_PRINT(( DBG_CONTEXT,
"[StartNewRequest] ReadFile failed, error %lu\n",
GetLastError() ));
return FALSE;
}
}
return TRUE;
}
/*******************************************************************
NAME: HTTP_REQ_BASE::OnFillClientReq
SYNOPSIS: Waits for the full client request packet then decides
the course of action
ENTRY: pfCompleteRequest - Set to TRUE if we've received a full
client request and we can start processing the request
pfFinished - Set to TRUE if no further processing is requred
RETURNS: TRUE if processing should continue, FALSE to abort the
this connection
HISTORY:
Johnl 22-Aug-1994 Created
********************************************************************/
BOOL
HTTP_REQ_BASE::OnFillClientReq(
BOOL * pfCompleteRequest,
BOOL * pfFinished
)
{
BYTE * pbData = NULL;
*pfCompleteRequest = FALSE;
_cbClientRequest += QueryBytesWritten();
//
// If no bytes were read on the last request, then the socket has been
// closed, so abort everything and get out
//
if ( QueryBytesWritten() == 0 )
{
TCP_PRINT(( DBG_CONTEXT,
"[OnFillClientReq] Client socket closed while reading request (Conn = %lx)\n",
QueryClientConn() ));
SetKeepConn( FALSE );
*pfFinished = TRUE;
return TRUE;
}
if ( !UnWrapRequest( pfCompleteRequest,
pfFinished ))
{
return FALSE;
}
if ( *pfCompleteRequest || *pfFinished )
{
return TRUE;
}
//
// We still don't have a complete header, so keep reading
//
if ( !ReadFile( (BYTE *)_bufClientRequest.QueryPtr() + _cbClientRequest,
_bufClientRequest.QuerySize() - _cbClientRequest,
NULL,
IO_FLAG_ASYNC | IO_FLAG_NO_FILTER ))
{
TCP_PRINT(( DBG_CONTEXT,
"[OnFillClientReq] ReadFile failed, error %lu\n",
GetLastError() ));
return FALSE;
}
return TRUE;
}
BOOL
HTTP_REQ_BASE::HandleCertRenegotiation(
BOOL * pfFinished,
DWORD cbData
)
/*++
Routine Description:
Handle a read notification while renegotiating a certificate
Arguments:
pfFinished - No further processing is required for this request
Return Value:
TRUE on success, FALSE on failure
--*/
{
BYTE * pbData = NULL;
TCHAR * pchOutRequest;
DWORD cbOutRequest;
DWORD cbProcessed;
BOOL fTerminated;
BOOL fReadAgain = FALSE;
TCHAR * pchNewData;
//
// If no bytes were read on the last request, then the socket has been
// closed, so abort everything and get out
//
if ( QueryBytesWritten() == 0 )
{
TCP_PRINT(( DBG_CONTEXT,
"[HandleCertRenegotiation] Client socket closed while reading request (Conn = %lx)\n",
QueryClientConn() ));
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
}
//
// Notify any opaque filters of the incoming data
//
// The parsed HTTP header is still in the buffer, along
// with zero or more bytes of the message body.
//
cbProcessed = _cbClientRequest + _cbGatewayData;
pchNewData = (LPSTR)_bufClientRequest.QueryPtr() + cbProcessed;
if ( !HTTP_FILTER::NotifyRawDataFilters(
&_Filter,
SF_NOTIFY_READ_RAW_DATA,
pchNewData,
_cbOldData + cbData - cbProcessed,
_bufClientRequest.QuerySize() -
cbProcessed, // Usable buffer size
(PVOID *) &pchOutRequest,
&cbOutRequest,
pfFinished,
&fReadAgain ))
{
return FALSE;
}
if ( *pfFinished )
{
return TRUE;
}
//
// If the output buffer is different, then we need to copy
// the data to our output buffer
//
// CODEWORK: Get rid of this buffer copy - there are assumptions the
// incoming data is contained in _bufClientRequest
//
if ( pchOutRequest != NULL &&
pchOutRequest != pchNewData )
{
if ( !_bufClientRequest.Resize( cbOutRequest + cbProcessed + 1 ))
return FALSE;
memcpy( pchNewData,
pchOutRequest,
cbOutRequest );
}
if ( fReadAgain )
{
_cbOldData = cbProcessed + cbOutRequest;
goto nextread;
}
//
// A filter may have changed the size of our effective input buffer
//
_cbGatewayData += cbOutRequest;
_cbOldData = _cbClientRequest + _cbGatewayData;
if ( _dwRenegotiated )
{
cbData = _cbGatewayData;
_cbBytesWritten = cbData;
_cbGatewayData = 0;
return OnRestartRequest( (LPSTR)_bufClientRequest.QueryPtr(),
cbData,
pfFinished );
}
nextread:
DWORD cbNextRead = CERT_RENEGO_READ_SIZE;
if ( !_bufClientRequest.Resize( _cbOldData + cbNextRead ))
{
return FALSE;
}
if ( !ReadFile( (BYTE *) _bufClientRequest.QueryPtr() + _cbOldData,
cbNextRead,
NULL,
IO_FLAG_ASYNC|IO_FLAG_NO_FILTER ))
{
return FALSE;
}
return TRUE;
}
BOOL
HTTP_REQ_BASE::UnWrapRequest(
BOOL * pfCompleteRequest,
BOOL * pfFinished
)
/*++
Routine Description:
Calls the installed filters to unwrap the client request
Arguments:
pfCompleteRequest - Set to TRUE if we've received a full
client request and we can start processing the request
pfFinished - No further processing is required for this request
Return Value:
TRUE on success, FALSE on failure
--*/
{
BOOL fHandled;
BYTE * pbData = NULL;
TCHAR * pchOutRequest;
DWORD cbOutRequest;
DWORD cbData;
BOOL fTerminated;
BOOL fReadAgain;
//
// Notify any opaque filters of the incoming data
//
if ( fAnyFilters )
{
//
// At this point _cbClientRequest contains the chunk of data just
// received, so adjust for that unless a filter indicated they want
// to see the data again
//
CHAR * pchNewData = (CHAR *) _bufClientRequest.QueryPtr() +
_cbOldData;
if ( !HTTP_FILTER::NotifyRawDataFilters(
&_Filter,
SF_NOTIFY_READ_RAW_DATA,
pchNewData,
_cbClientRequest - _cbOldData,
_bufClientRequest.QuerySize() -
_cbOldData, // Usable buffer size
(PVOID *) &pchOutRequest,
&cbOutRequest,
&fHandled,
&fReadAgain ))
{
return FALSE;
}
if ( fHandled )
{
return TRUE;
}
//
// If the output buffer is different, then we need to copy
// the data to our output buffer
//
// CODEWORK: Get rid of this buffer copy - there are assumptions the
// incoming data is contained in _bufClientRequest
//
if ( pchOutRequest != NULL &&
pchOutRequest != pchNewData )
{
if ( !_bufClientRequest.Resize( cbOutRequest + _cbOldData + 1 ))
return FALSE;
memcpy( pchNewData,
pchOutRequest,
cbOutRequest );
}
//
// A filter may have changed the size of our effective input buffer
//
_cbClientRequest = cbOutRequest + _cbOldData;
//
// Can we continue processing this request? The message just received
// may have been a session negotiation message and we have yet to
// receive the real HTTP request.
//
if ( fReadAgain )
{
//
// Resize the read buffer and issue an async read to get the next
// chunk for the filter. UnwrapRequest uses the size of
// this buffer as the size of data to read
//
if ( !_bufClientRequest.Resize( _cbClientRequest +
_Filter.QueryNextReadSize() +
POST_SLOP ))
{
return FALSE;
}
return TRUE;
}
}
//
// Remember how much data the filter has already seen so we don't
// renotify them with the same data in case we don't have a full
// set of headers
//
_cbOldData = _cbClientRequest;
if ( !CheckForTermination( &fTerminated,
&_bufClientRequest,
_cbClientRequest,
&pbData,
&cbData,
W3_DEFAULT_BUFFSIZE ) )
{
return FALSE;
}
//
// We assume a valid request will be at least 4 bytes long - the IsPOintNine()
// function will return true for just '\r\n' which causes problems with POST slop
//
if ( !fTerminated &&
(_cbClientRequest <= 4 ||
!::IsPointNine( (CHAR *) _bufClientRequest.QueryPtr())) )
{
//
// We don't have a complete request, read more data
//
return TRUE;
}
//
// If we picked up some gateway data in the headers, adjust for that
// now
//
_cbClientRequest -= cbData;
*pfCompleteRequest = TRUE;
_cbOldData = 0;
return OnCompleteRequest( (CHAR *) _bufClientRequest.QueryPtr(),
pbData,
cbData,
pfFinished );
}
/*******************************************************************
NAME: HTTP_REQ_BASE::ReadGatewayData
SYNOPSIS: Attempts to retrieve gateway data from the remote
client
ENTRY: pfDone - Set to TRUE when _cbContentLength bytes have
been read
fFirstRead - TRUE if this is the first read, FALSE on
subsequent reads.
HISTORY:
Johnl 03-Oct-1994 Created
********************************************************************/
BOOL
HTTP_REQ_BASE::ReadGatewayData(
BOOL *pfDone,
BOOL fFirstRead
)
{
DWORD cbNextRead;
_cbGatewayData += QueryBytesWritten();
if ( _cbGatewayData >= _cbContentLength ||
_cbGatewayData >= g_cbUploadReadAhead )
{
*pfDone = TRUE;
return TRUE;
}
//
// If no bytes were read on the last request, then the socket has been
// closed, so abort everything and get out
//
if ( !fFirstRead && QueryBytesWritten() == 0 )
{
TCP_PRINT(( DBG_CONTEXT,
"[ReadGatewayData] Client socket closed while reading request (Conn = %lx)\n",
QueryClientConn() ));
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
}
*pfDone = FALSE;
cbNextRead = min( (g_cbUploadReadAhead - _cbGatewayData),
(_cbContentLength - _cbGatewayData + POST_SLOP));
if ( !_bufClientRequest.Resize( _cbClientRequest + _cbGatewayData + cbNextRead ))
{
return FALSE;
}
if ( !ReadFile( (BYTE *) _bufClientRequest.QueryPtr() + _cbGatewayData +
_cbClientRequest,
cbNextRead,
NULL,
IO_FLAG_ASYNC ))
{
return FALSE;
}
return TRUE;
}
BOOL
HTTP_REQ_BASE::DenyAccess(
DWORD cbData
)
{
DWORD cbRecv;
int sockerr;
fd_set fdset;
timeval timeout;
FD_ZERO( &fdset );
FD_SET( QueryClientConn()->QuerySocket(), &fdset );
timeout.tv_sec = 60;
timeout.tv_usec = 0;
//
// Eat the entity body the client might have sent because the server
// is expecting the HTTP request and blob in the next client request
//
while ( QueryClientContentLength() > cbData )
{
DBGPRINTF(( DBG_CONTEXT,
"[OnCompleteRequest] Eating entity body on NTLM Request - Total %d, Left %d\n",
QueryClientContentLength(),
QueryClientContentLength() - cbData ));
//
// Make sure there is data to receive before blocking on the
// synchronous read. select() returns the number of sockets in
// the "readable" state, 0 on timeout or a socket error.
//
sockerr = select( 0, &fdset, NULL, NULL, &timeout );
if ( sockerr != 1 ||
!ReadFile( _bufClientRequest.QueryPtr(),
min( QueryClientContentLength() - cbData,
_bufClientRequest.QuerySize()),
&cbRecv,
IO_FLAG_SYNC ) ||
!cbRecv )
{
break;
}
cbData += cbRecv;
}
//
// An access denied error automatically sends the next part
// of the authentication conversation
//
SetLastError( ERROR_ACCESS_DENIED );
return FALSE;
}
BOOL
HTTP_REQ_BASE::OnCompleteRequest(
TCHAR * pchRequest,
BYTE * pbData,
DWORD cbData,
BOOL * pfFinished
)
/*++
Routine Description:
This method takes a complete HTTP 1.0 request and handles the results
of the parsing method
Arguments:
pchRequest - Pointer to first byte of request header
pbData - Pointer to first byte of additional data following the header
cbData - Number of data bytes pbData points to
pfFinished - Set to TRUE if no further processing is needed
Return Value:
TRUE on success, FALSE on failure
--*/
{
BOOL fRet;
//
// Parse the request
//
fRet = Parse( pchRequest,
pfFinished );
if ( !fRet )
{
DWORD hterr;
DWORD winerr = NO_ERROR;
TCP_PRINT(( DBG_CONTEXT,
"[OnFillClientReq] httpReq.Parse or httpLogonUser failed, error %lu\n",
GetLastError() ));
switch ( GetLastError() )
{
case ERROR_INVALID_PARAMETER:
//
// If the request is bad, then indicate that to the client
//
hterr = HT_BAD_REQUEST;
break;
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
hterr = HT_NOT_FOUND;
winerr= ::GetLastError();
break;
case ERROR_ACCESS_DENIED:
case ERROR_LOGON_FAILURE:
return DenyAccess( cbData );
default:
//
// Some other fatal error occurred
//
hterr = HT_SERVER_ERROR;
winerr = ::GetLastError();
break;
}
SetState( HTR_DONE, hterr, winerr );
Disconnect( hterr,
winerr );
//
// Since we handled the error ourselves (by issuing a disconnect),
// we will return success (otherwise another disconnect will
// occur)
//
return TRUE;
}
if ( *pfFinished )
return TRUE;
//
// Check to see if encryption is required before we do any processing
//
if ( ( ((HTTP_REQUEST*)this)->GetRootMask() & VROOT_MASK_SSL )
&& !IsSecurePort() )
{
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
Disconnect( HT_FORBIDDEN, IDS_SSL_REQUIRED );
return TRUE;
}
//
// Check to see if encryption is required before we do any processing
//
if ( ( ((HTTP_REQUEST*)this)->GetRootMask() & VROOT_MASK_SSL )
&& !IsSecurePort() )
{
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
Disconnect( HT_FORBIDDEN, IDS_SSL_REQUIRED );
return TRUE;
}
return OnRestartRequest( pchRequest, cbData, pfFinished );
}
BOOL
HTTP_REQ_BASE::OnRestartRequest(
TCHAR * pchRequest,
DWORD cbData,
BOOL * pfFinished
)
/*++
Routine Description:
This method takes a complete HTTP 1.0 request and handles the results
of the parsing method
Arguments:
pchRequest - Pointer to first byte of request header
cbData - Number of data bytes added to the message body
pfFinished - Set to TRUE if no further processing is needed
Return Value:
TRUE on success, FALSE on failure
--*/
{
BOOL fAccepted = FALSE;
DWORD cbNextRead;
//
// Check if cert renegotiation to be requested
//
if ( QueryState() != HTR_CERT_RENEGOTIATE )
{
if ( !((HTTP_REQUEST*)this)->RequestRenegotiate( &fAccepted ) )
{
if ( GetLastError() == SEC_E_INCOMPLETE_MESSAGE )
{
fAccepted = FALSE;
}
else
{
return FALSE;
}
}
}
//
// If requested, begin reading data. Notification will be handled
// by HandleCertRenegotiation()
//
if ( fAccepted )
{
_cbGatewayData = cbData;
_cbOldData = _cbClientRequest + cbData;
cbNextRead = CERT_RENEGO_READ_SIZE;
if ( !_bufClientRequest.Resize( _cbOldData + cbNextRead ))
{
return FALSE;
}
if ( !ReadFile( (BYTE *) _bufClientRequest.QueryPtr() + _cbOldData,
cbNextRead,
NULL,
IO_FLAG_ASYNC|IO_FLAG_NO_FILTER ))
{
return FALSE;
}
return TRUE;
}
if ( _dwRenegotiated != CERT_NEGO_SUCCESS &&
(((HTTP_REQUEST*)this)->GetRootMask()&VROOT_MASK_NEGO_MANDATORY) )
{
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
Disconnect( HT_FORBIDDEN );
return TRUE;
}
//
// If we're having an authentication conversation, then we send an access denied
// response with the next authentication blob. The client returns the next blob
// to us in an HTTP request.
//
if ( IsAuthenticating() )
{
//
// If no blob to send to client then handle this as
// a 401 notification with disconnect
//
if ( _strAuthInfo.IsEmpty() )
{
SetDeniedFlags( SF_DENIED_LOGON );
SetKeepConn( FALSE );
_fAuthenticating = FALSE;
}
DoAuthentication:
return DenyAccess( cbData );
}
//
// If we have all the authentication information we need and we're
// not already logged on, try to log the user on
//
if ( !IsLoggedOn() && !LogonUser( pfFinished ) )
{
if ( (GetLastError() == ERROR_ACCESS_DENIED) ||
(GetLastError() == ERROR_LOGON_FAILURE))
{
goto DoAuthentication;
}
return FALSE;
}
if ( *pfFinished )
return TRUE;
//
// Check to see if the client specified any additional data
// that we need to pickup
//
if ( QueryClientContentLength() )
{
BOOL fDone;
//
// Now let's pickup the rest of the data. We reset bytes written
// to pickup the entity that was sent with the headers
//
SetState( HTR_READING_GATEWAY_DATA );
_cbBytesWritten = cbData;
if ( !ReadGatewayData( &fDone, TRUE ))
{
return FALSE;
}
if ( !fDone )
return TRUE;
//
// else Fall through as we have all of the gateway data
//
}
SetState( HTR_DOVERB );
return TRUE;
}
BOOL
HTTP_REQ_BASE::ReadFile(
LPVOID lpBuffer,
DWORD nBytesToRead,
DWORD * pnBytesRead,
DWORD dwFlags )
{
//
// If no filters are installed, do the normal thing
//
if ( !fAnyFilters || dwFlags & IO_FLAG_NO_FILTER )
{
if ( dwFlags & IO_FLAG_ASYNC )
{
return _pClientConn->ReadFile( lpBuffer,
nBytesToRead );
}
else
{
*pnBytesRead = recv( _pClientConn->QuerySocket(),
(char *) lpBuffer,
nBytesToRead,
0 );
return *pnBytesRead != SOCKET_ERROR;
}
}
else
{
//
// We don't need to up the ref-count because the filter
// will eventually post an async-completion with the connection
// object
//
if ( _Filter.ReadData( lpBuffer,
nBytesToRead,
pnBytesRead,
dwFlags ))
{
return TRUE;
}
return FALSE;
}
}
BOOL
HTTP_REQ_BASE::WriteFile(
LPVOID lpBuffer,
DWORD nBytesToWrite,
DWORD * pnBytesWritten,
DWORD dwFlags )
{
//
// Don't count filter bytes
//
if ( !(dwFlags & IO_FLAG_NO_FILTER))
{
_cbBytesSent += nBytesToWrite;
}
if ( !fAnyFilters || dwFlags & IO_FLAG_NO_FILTER )
{
if ( dwFlags & IO_FLAG_ASYNC )
{
return _pClientConn->WriteFile( lpBuffer,
nBytesToWrite );
}
else
{
*pnBytesWritten = send( _pClientConn->QuerySocket(),
(const char *) lpBuffer,
nBytesToWrite,
0 );
return *pnBytesWritten != SOCKET_ERROR;
}
}
else
{
//
// We don't need to up the ref-count because the filter
// will eventually post an async-completion with the connection
// object
//
if ( _Filter.SendData( lpBuffer,
nBytesToWrite,
pnBytesWritten,
dwFlags ))
{
return TRUE;
}
return FALSE;
}
}
BOOL
HTTP_REQ_BASE::TransmitFile(
HANDLE hFile,
DWORD BytesToWrite,
DWORD dwFlags,
PVOID pHead,
DWORD HeadLength,
PVOID pTail,
DWORD TailLength
)
{
//
// File sends must always be async
//
TCP_ASSERT( !(dwFlags & IO_FLAG_SYNC));
//
// Don't count filter bytes
//
if ( !(dwFlags & IO_FLAG_NO_FILTER ))
{
_cbBytesSent += BytesToWrite + HeadLength + TailLength;
_cFilesSent++;
}
if ( !fAnyFilters || dwFlags & IO_FLAG_NO_FILTER )
{
return _pClientConn->TransmitFile( hFile,
BytesToWrite,
dwFlags,
pHead,
HeadLength,
pTail,
TailLength );
}
else
{
if ( _Filter.SendFile( hFile,
BytesToWrite,
dwFlags,
pHead,
HeadLength,
pTail,
TailLength ))
{
return TRUE;
}
return FALSE;
}
}
BOOL
HTTP_REQ_BASE::TransmitFileEx(
HANDLE hFile,
DWORD Offset,
DWORD BytesToWrite,
DWORD dwFlags,
PVOID pHead,
DWORD HeadLength,
PVOID pTail,
DWORD TailLength
)
{
//
// File sends must always be async
//
TCP_ASSERT( !(dwFlags & IO_FLAG_SYNC));
//
// Don't count filter bytes
//
if ( !(dwFlags & IO_FLAG_NO_FILTER ))
{
_cbBytesSent += BytesToWrite + HeadLength + TailLength;
_cFilesSent++;
}
if ( !fAnyFilters || dwFlags & IO_FLAG_NO_FILTER )
{
return _pClientConn->TransmitFileEx( hFile,
Offset,
BytesToWrite,
dwFlags,
pHead,
HeadLength,
pTail,
TailLength );
}
else
{
if ( _Filter.SendFileEx( hFile,
Offset,
BytesToWrite,
dwFlags,
pHead,
HeadLength,
pTail,
TailLength ))
{
return TRUE;
}
return FALSE;
}
}
BOOL
HTTP_REQ_BASE::PostCompletionStatus(
DWORD cbBytesTransferred
)
{
return _pClientConn->PostCompletionStatus( cbBytesTransferred );
}