mirror of https://github.com/tongzx/nt5src
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.
5222 lines
138 KiB
5222 lines
138 KiB
/*++
|
|
|
|
Copyright (c) 1998-2001 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
httprcv.cxx
|
|
|
|
Abstract:
|
|
|
|
Contains core HTTP receive code.
|
|
|
|
Author:
|
|
|
|
Henry Sanders (henrysa) 10-Jun-1998
|
|
|
|
Revision History:
|
|
|
|
Paul McDaniel (paulmcd) 01-Mar-1999
|
|
|
|
massively rewrote it to handle request spanning tdi packets.
|
|
moved all http parsing to PASSIVE irql (from DISPATCH).
|
|
also merged tditest into this module.
|
|
|
|
Eric Stenson (EricSten) 11-Sep-2000
|
|
|
|
Added support for sending "100 Continue" responses to PUT
|
|
and POST requests. Added #pragma's for PAGED -vs- Non-PAGED
|
|
functions.
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
#include "httprcvp.h"
|
|
|
|
|
|
//
|
|
// Declare pageable and non-pageable functions
|
|
//
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
// Init
|
|
#pragma alloc_text( INIT, UlInitializeHttpRcv )
|
|
#pragma alloc_text( PAGE, UlTerminateHttpRcv )
|
|
|
|
// Public
|
|
#pragma alloc_text( PAGE, UlResumeParsing )
|
|
#pragma alloc_text( PAGE, UlGetCGroupForRequest )
|
|
#pragma alloc_text( PAGE, UlSendSimpleStatus )
|
|
#pragma alloc_text( PAGE, UlSendErrorResponse )
|
|
|
|
// Private
|
|
#pragma alloc_text( PAGE, UlpDeliverHttpRequest )
|
|
#pragma alloc_text( PAGE, UlpProcessBufferQueue )
|
|
#pragma alloc_text( PAGE, UlpCancelEntityBodyWorker )
|
|
|
|
#if DBG
|
|
#pragma alloc_text( PAGE, UlpIsValidRequestBufferList )
|
|
#endif // DBG
|
|
|
|
#endif // ALLOC_PRAGMA
|
|
|
|
#if 0 // Non-Pageable Functions
|
|
// Public
|
|
NOT PAGEABLE -- UlHttpReceive
|
|
NOT PAGEABLE -- UlConnectionRequest
|
|
NOT PAGEABLE -- UlConnectionComplete
|
|
NOT PAGEABLE -- UlConnectionDisconnect
|
|
NOT PAGEABLE -- UlConnectionDestroyed
|
|
NOT PAGEABLE -- UlReceiveEntityBody
|
|
|
|
// Private
|
|
NOT PAGEABLE -- UlpHandleRequest
|
|
NOT PAGEABLE -- UlpParseNextRequest
|
|
NOT PAGEABLE -- UlpInsertBuffer
|
|
NOT PAGEABLE -- UlpMergeBuffers
|
|
NOT PAGEABLE -- UlpAdjustBuffers
|
|
NOT PAGEABLE -- UlpConsumeBytesFromConnection
|
|
NOT PAGEABLE -- UlpCancelEntityBody
|
|
NOT PAGEABLE -- UlpCompleteSendResponse
|
|
NOT PAGEABLE -- UlpRestartSendSimpleResponse
|
|
NOT PAGEABLE -- UlpSendSimpleCleanupWorker
|
|
|
|
#endif // Non-Pageable Functions
|
|
|
|
|
|
/*++
|
|
|
|
Paul McDaniel (paulmcd) 26-May-1999
|
|
|
|
here is a brief description of the data structures used by this module:
|
|
|
|
the connection keeps track of all buffers received by TDI into a list anchored
|
|
by HTTP_CONNECTION::BufferHead. this list is sequenced and sorted. the
|
|
buffers are refcounted.
|
|
|
|
HTTP_REQUEST(s) keep pointers into these buffers for the parts they consume.
|
|
HTTP_REQUEST::pHeaderBuffer and HTTP_REQUEST::pChunkBuffer.
|
|
|
|
the buffers fall off the list as they are no longer needed. the connection
|
|
only keeps a reference at HTTP_CONNECTION::pCurrentBuffer. so as it completes
|
|
the processing of a buffer, if no other objects kept that buffer, it will be
|
|
released.
|
|
|
|
here is a brief description of the functions in this module, and how they
|
|
are used:
|
|
|
|
|
|
UlHttpReceive - the TDI data indication handler. copies buffers and queues to
|
|
UlpHandleRequest.
|
|
|
|
UlpHandleRequest - the main processing function for connections.
|
|
|
|
UlCreateHttpConnectionId - creates the connections opaque id.
|
|
|
|
UlpInsertBuffer - inserts the buffer into pConnection->BufferHead - sorted.
|
|
|
|
UlpAdjustBuffers - determines where in BufferHead the current connection
|
|
should be parsing. handle buffer merging and copying if a protocol
|
|
token spans buffers
|
|
|
|
UlParseHttp - the main http parser. expects that no protocol tokens span
|
|
a buffer. will return a status code if it does.
|
|
|
|
UlpProcessBufferQueue - handles entity body buffer processing.
|
|
synchronizes access to BufferHead at pRequest->pChunkBuffer with
|
|
UlReceiveEntityBody.
|
|
|
|
UlConnectionRequest - called when a new connection comes in. allocates a new
|
|
HTTP_CONNECTION. does not create the opaque id.
|
|
|
|
UlConnectionComplete - called if the client is happy with our accept.
|
|
closes the connection if error status.
|
|
|
|
UlConnectionDisconnect - called when the client disconnects. it calls tdi to
|
|
close the server end. always a graceful close.
|
|
|
|
UlConnectionDestroyed - called when the connection is dropped. both sides have
|
|
closed it. deletes all opaque ids . removes the tdi reference on the
|
|
HTTP_CONNECTION (and hopefully vice versa) .
|
|
|
|
UlReceiveEntityBody - called by user mode to read entity body. pends the irp
|
|
to pRequest->IrpHead and calls UlpProcessBufferQueue .
|
|
|
|
|
|
--*/
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The main http receive routine.
|
|
|
|
Arguments:
|
|
|
|
pHttpConn - Pointer to HTTP connection on which data was received.
|
|
pBuffer - Pointer to data received.
|
|
BufferLength - Length of data pointed to by pBuffer.
|
|
UnreceivedLength- Bytes that the transport has, but aren't in pBuffer
|
|
pBytesTaken - Pointer to where to return bytes taken.
|
|
|
|
Return Value:
|
|
|
|
Status of receive.
|
|
|
|
--*/
|
|
NTSTATUS
|
|
UlHttpReceive(
|
|
IN PVOID pListeningContext,
|
|
IN PVOID pConnectionContext,
|
|
IN PVOID pVoidBuffer,
|
|
IN ULONG BufferLength,
|
|
IN ULONG UnreceivedLength,
|
|
OUT PULONG pBytesTaken
|
|
)
|
|
{
|
|
PUL_REQUEST_BUFFER pRequestBuffer;
|
|
PUL_HTTP_CONNECTION pConnection;
|
|
BOOLEAN DrainAfterDisconnect = FALSE;
|
|
ULONG SpaceAvailable;
|
|
KIRQL OldIrql;
|
|
|
|
ASSERT(BufferLength != 0);
|
|
ASSERT(pConnectionContext != NULL);
|
|
|
|
pConnection = (PUL_HTTP_CONNECTION)pConnectionContext;
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pConnection));
|
|
|
|
//
|
|
// Make sure we are not buffering too much data.
|
|
// Need to adjust the BufferLength to be no more
|
|
// than the number of bytes we can accept at this time.
|
|
//
|
|
|
|
//
|
|
// PerfBug: need to get rid of this lock
|
|
//
|
|
|
|
UlAcquireSpinLock(
|
|
&pConnection->BufferingInfo.BufferingSpinLock,
|
|
&OldIrql
|
|
);
|
|
|
|
DrainAfterDisconnect = pConnection->BufferingInfo.DrainAfterDisconnect;
|
|
|
|
if (DrainAfterDisconnect)
|
|
{
|
|
pConnection->BufferingInfo.TransportBytesNotTaken += UnreceivedLength;
|
|
}
|
|
else
|
|
{
|
|
if ((pConnection->BufferingInfo.BytesBuffered + BufferLength) > g_UlMaxBufferedBytes)
|
|
{
|
|
SpaceAvailable = g_UlMaxBufferedBytes - pConnection->BufferingInfo.BytesBuffered;
|
|
pConnection->BufferingInfo.TransportBytesNotTaken += (BufferLength - SpaceAvailable);
|
|
BufferLength = SpaceAvailable;
|
|
}
|
|
|
|
pConnection->BufferingInfo.BytesBuffered += BufferLength;
|
|
pConnection->BufferingInfo.TransportBytesNotTaken += UnreceivedLength;
|
|
}
|
|
|
|
UlReleaseSpinLock(
|
|
&pConnection->BufferingInfo.BufferingSpinLock,
|
|
OldIrql
|
|
);
|
|
|
|
if (BufferLength && DrainAfterDisconnect == FALSE)
|
|
{
|
|
|
|
//
|
|
// get a new request buffer
|
|
//
|
|
|
|
pRequestBuffer = UlCreateRequestBuffer(
|
|
BufferLength,
|
|
pConnection->NextBufferNumber
|
|
);
|
|
|
|
if (pRequestBuffer == NULL)
|
|
{
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
//
|
|
// increment our buffer number counter
|
|
//
|
|
|
|
pConnection->NextBufferNumber += 1;
|
|
|
|
//
|
|
// copy the tdi buffer
|
|
//
|
|
|
|
RtlCopyMemory(pRequestBuffer->pBuffer, pVoidBuffer, BufferLength);
|
|
|
|
pRequestBuffer->UsedBytes = BufferLength;
|
|
|
|
//
|
|
// Add backpointer to connection.
|
|
//
|
|
pRequestBuffer->pConnection = pConnection;
|
|
|
|
UlTrace( PARSER, (
|
|
"*** Request Buffer %p has connection %p\n",
|
|
pRequestBuffer,
|
|
pConnection
|
|
));
|
|
|
|
IF_DEBUG2(HTTP_IO, VERBOSE)
|
|
{
|
|
UlTraceVerbose( HTTP_IO, (
|
|
"<<<< Request(%p), "
|
|
"RequestBuffer[%d] %p, %d bytes, "
|
|
"Conn %p.\n",
|
|
pConnection->pRequest,
|
|
pRequestBuffer->BufferNumber, pRequestBuffer, BufferLength,
|
|
pConnection
|
|
));
|
|
|
|
UlDbgPrettyPrintBuffer(pRequestBuffer->pBuffer, BufferLength);
|
|
|
|
UlTraceVerbose( HTTP_IO, (">>>>\n"));
|
|
}
|
|
|
|
//
|
|
// Queue a work item to handle the data.
|
|
//
|
|
// Reference the connection so it doesn't go
|
|
// away while we're waiting for our work item
|
|
// to run. UlpHandleRequest will release the ref.
|
|
//
|
|
|
|
UL_REFERENCE_HTTP_CONNECTION(pConnection);
|
|
|
|
UL_QUEUE_WORK_ITEM(
|
|
&(pRequestBuffer->WorkItem),
|
|
&UlpHandleRequest
|
|
);
|
|
}
|
|
else if ( DrainAfterDisconnect && UnreceivedLength != 0 )
|
|
{
|
|
// Handle the case where we are in drain state and there's
|
|
// unreceived data indicated but not available by the tdi.
|
|
|
|
UlpDiscardBytesFromConnection( pConnection );
|
|
}
|
|
|
|
//
|
|
// Tell the caller how many bytes we consumed.
|
|
//
|
|
|
|
*pBytesTaken = BufferLength;
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // UlHttpReceive
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
links the buffer into the connection and processes the list.
|
|
|
|
starts http request parsing.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - points to a UL_REQUEST_BUFFER
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpHandleRequest(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_REQUEST_BUFFER pRequestBuffer;
|
|
PUL_HTTP_CONNECTION pConnection;
|
|
KIRQL OldIrql;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pRequestBuffer = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_REQUEST_BUFFER,
|
|
WorkItem
|
|
);
|
|
|
|
ASSERT( UL_IS_VALID_REQUEST_BUFFER(pRequestBuffer) );
|
|
|
|
pConnection = pRequestBuffer->pConnection;
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pConnection));
|
|
|
|
//
|
|
// grab the lock
|
|
//
|
|
|
|
UlAcquireResourceExclusive(&(pConnection->Resource), TRUE);
|
|
|
|
//
|
|
// if the connection is going down, just bail out.
|
|
//
|
|
|
|
if (pConnection->UlconnDestroyed) {
|
|
UlFreeRequestBuffer(pRequestBuffer);
|
|
Status = STATUS_SUCCESS;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// stop the Connection Timeout timer
|
|
// and start the Header Wait timer
|
|
//
|
|
|
|
UlLockTimeoutInfo(
|
|
&pConnection->TimeoutInfo,
|
|
&OldIrql
|
|
);
|
|
|
|
UlResetConnectionTimer(
|
|
&pConnection->TimeoutInfo,
|
|
TimerConnectionIdle
|
|
);
|
|
|
|
UlSetConnectionTimer(
|
|
&pConnection->TimeoutInfo,
|
|
TimerHeaderWait
|
|
);
|
|
|
|
UlUnlockTimeoutInfo(
|
|
&pConnection->TimeoutInfo,
|
|
OldIrql
|
|
);
|
|
|
|
UlEvaluateTimerState(
|
|
&pConnection->TimeoutInfo
|
|
);
|
|
|
|
|
|
//
|
|
// Make sure it has an id already created.
|
|
//
|
|
// This is done here because the opaqueid stuff likes to run
|
|
// at PASSIVE_LEVEL.
|
|
//
|
|
|
|
if (HTTP_IS_NULL_ID(&(pConnection->ConnectionId)))
|
|
{
|
|
Status = UlCreateHttpConnectionId(pConnection);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
UlFreeRequestBuffer(pRequestBuffer);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
//
|
|
// insert it into the list
|
|
//
|
|
|
|
ASSERT( 0 != pRequestBuffer->UsedBytes );
|
|
|
|
UlTraceVerbose( PARSER, (
|
|
"http!UlpHandleRequest: conn = %p, Req = %p: "
|
|
"about to insert buffer %p\n",
|
|
pConnection,
|
|
pConnection->pRequest,
|
|
pRequestBuffer
|
|
));
|
|
|
|
UlpInsertBuffer(pConnection, pRequestBuffer);
|
|
|
|
//
|
|
// Kick off the parser
|
|
//
|
|
|
|
UlTraceVerbose( PARSER, (
|
|
"http!UlpHandleRequest: conn = %p, Req = %p: "
|
|
"about to parse next request\n",
|
|
pConnection,
|
|
pConnection->pRequest
|
|
));
|
|
|
|
Status = UlpParseNextRequest(pConnection);
|
|
|
|
end:
|
|
|
|
UlTraceVerbose( PARSER, (
|
|
"http!UlpHandleRequest: status %08lx, pConnection %p, pRequest %p\n",
|
|
Status,
|
|
pConnection,
|
|
pConnection->pRequest
|
|
));
|
|
|
|
if (!NT_SUCCESS(Status) && pConnection->pRequest != NULL)
|
|
{
|
|
UlTrace( PARSER, (
|
|
"*** status %08lx, pConnection %p, pRequest %p\n",
|
|
Status,
|
|
pConnection,
|
|
pConnection->pRequest
|
|
));
|
|
|
|
//
|
|
// An error happened, most propably during parsing.
|
|
// Send an error back if user hasn't send one yet.
|
|
// E.g. We have received a request,then delivered
|
|
// it to the WP, therefore WaitingForResponse is
|
|
// set. And then encountered an error when dealing
|
|
// with entity body.
|
|
//
|
|
|
|
UlSendErrorResponse( pConnection );
|
|
}
|
|
|
|
//
|
|
// done with the lock
|
|
//
|
|
|
|
UlReleaseResource(&(pConnection->Resource));
|
|
|
|
//
|
|
// and release the connection
|
|
//
|
|
|
|
UL_DEREFERENCE_HTTP_CONNECTION(pConnection);
|
|
|
|
CHECK_STATUS(Status);
|
|
|
|
} // UlpHandleRequest
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
When we finish sending a response we call into this function to
|
|
kick the parser back into action.
|
|
|
|
Arguments:
|
|
|
|
pConnection - the connection on which to resume
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlResumeParsing(
|
|
IN PUL_HTTP_CONNECTION pConnection
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PLIST_ENTRY pEntry;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
PAGED_CODE();
|
|
ASSERT( UL_IS_VALID_HTTP_CONNECTION( pConnection ) );
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlResumeParsing(pHttpConn = %p)\n",
|
|
pConnection
|
|
));
|
|
|
|
//
|
|
// grab the lock
|
|
//
|
|
|
|
UlAcquireResourceExclusive(&(pConnection->Resource), TRUE);
|
|
|
|
//
|
|
// if the connection is going down, just bail out.
|
|
//
|
|
if (!pConnection->UlconnDestroyed)
|
|
{
|
|
//
|
|
// Forget about the old request.
|
|
//
|
|
UlCleanupHttpConnection( pConnection );
|
|
|
|
//
|
|
// Kick off the parser
|
|
//
|
|
Status = UlpParseNextRequest(pConnection);
|
|
|
|
if (!NT_SUCCESS(Status) && pConnection->pRequest != NULL)
|
|
{
|
|
//
|
|
// Uh oh, something bad happened, send back an error
|
|
//
|
|
|
|
UlSendErrorResponse( pConnection );
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// done with the lock
|
|
//
|
|
|
|
UlReleaseResource(&(pConnection->Resource));
|
|
|
|
CHECK_STATUS(Status);
|
|
} // UlResumeParsing
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Tries to parse data attached to the connection into a request. If
|
|
a complete request header is parsed, the request will be dispatched
|
|
to an Application Pool.
|
|
|
|
This function assumes the caller is holding the connection resource!
|
|
|
|
Arguments:
|
|
|
|
pConnection - the HTTP_CONNECTION with data to parse.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpParseNextRequest(
|
|
IN PUL_HTTP_CONNECTION pConnection
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_INTERNAL_REQUEST pRequest = NULL;
|
|
ULONG BytesTaken;
|
|
ULONG BufferLength;
|
|
BOOLEAN ResponseSent;
|
|
KIRQL OldIrql;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT( UL_IS_VALID_HTTP_CONNECTION( pConnection ) );
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
UlTrace(HTTP_IO, ("http!UlpParseNextRequest(httpconn = %p)\n", pConnection));
|
|
|
|
//
|
|
// Only parse the next request if
|
|
//
|
|
// We haven't dispatched the current request yet
|
|
// OR
|
|
// The current request has unparsed entity body or trailers.
|
|
//
|
|
|
|
if ((pConnection->pRequest == NULL)
|
|
|| ( !pConnection->WaitingForResponse )
|
|
|| (pConnection->pRequest->ParseState == ParseEntityBodyState)
|
|
|| (pConnection->pRequest->ParseState == ParseTrailerState))
|
|
{
|
|
|
|
//
|
|
// loop consuming the buffer, we will make multiple iterations
|
|
// if a single request spans multiple buffers.
|
|
//
|
|
|
|
for (;;)
|
|
{
|
|
ASSERT( UlpIsValidRequestBufferList( pConnection ) );
|
|
Status = UlpAdjustBuffers(pConnection);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (Status == STATUS_MORE_PROCESSING_REQUIRED)
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Since BufferLength is a ULONG, it can never be negative.
|
|
// So, if UsedBytes is greater than ParsedBytes, BufferLength
|
|
// will be very large, and non-zero.
|
|
//
|
|
|
|
ASSERT( pConnection->pCurrentBuffer->UsedBytes >
|
|
pConnection->pCurrentBuffer->ParsedBytes );
|
|
|
|
BufferLength = pConnection->pCurrentBuffer->UsedBytes -
|
|
pConnection->pCurrentBuffer->ParsedBytes;
|
|
|
|
//
|
|
// do we need to create a request object?
|
|
//
|
|
|
|
if (pConnection->pRequest == NULL)
|
|
{
|
|
//
|
|
// First shot at reading a request, allocate a request object
|
|
//
|
|
|
|
Status = UlpCreateHttpRequest(pConnection, &pConnection->pRequest);
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pConnection->pRequest));
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlpParseNextRequest created pRequest = %p for httpconn = %p\n",
|
|
pConnection->pRequest,
|
|
pConnection
|
|
));
|
|
|
|
//
|
|
// To be exact precise about the life-time of this
|
|
// request, copy the starting TIMESTAMP from connection
|
|
// pointer. But that won't work since we may get hit by
|
|
// multiple requests to the same connection. So we won't
|
|
// be that much precise.
|
|
//
|
|
|
|
KeQuerySystemTime( &(pConnection->pRequest->TimeStamp) );
|
|
|
|
TRACE_TIME(
|
|
pConnection->ConnectionId,
|
|
pConnection->pRequest->RequestId,
|
|
TIME_ACTION_CREATE_REQUEST
|
|
);
|
|
|
|
WRITE_REF_TRACE_LOG2(
|
|
g_pHttpConnectionTraceLog,
|
|
pConnection->pTraceLog,
|
|
REF_ACTION_INSERT_REQUEST,
|
|
pConnection->RefCount,
|
|
pConnection->pRequest,
|
|
__FILE__,
|
|
__LINE__
|
|
);
|
|
}
|
|
|
|
UlTrace( PARSER, (
|
|
"*** pConn %p, pReq %p, ParseState %lu\n",
|
|
pConnection,
|
|
pConnection->pRequest,
|
|
pConnection->pRequest->ParseState
|
|
));
|
|
|
|
PARSE_STATE OldState = pConnection->pRequest->ParseState;
|
|
|
|
switch (pConnection->pRequest->ParseState)
|
|
{
|
|
|
|
case ParseVerbState:
|
|
case ParseUrlState:
|
|
case ParseVersionState:
|
|
case ParseHeadersState:
|
|
case ParseCookState:
|
|
|
|
pRequest = pConnection->pRequest;
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
//
|
|
// parse it !
|
|
//
|
|
|
|
Status = UlParseHttp(
|
|
pRequest,
|
|
GET_REQUEST_BUFFER_POS(pConnection->pCurrentBuffer),
|
|
BufferLength,
|
|
&BytesTaken
|
|
);
|
|
|
|
ASSERT(BytesTaken <= BufferLength);
|
|
|
|
UlTraceVerbose(PARSER, (
|
|
"UlpParseNextRequest(pRequest = %p) "
|
|
"UlParseHttp: states (Vb/U/Ver/Hd/Ck) "
|
|
"%d -> %d, %d bytes taken\n",
|
|
pRequest, OldState, pConnection->pRequest->ParseState,
|
|
BytesTaken
|
|
));
|
|
|
|
pConnection->pCurrentBuffer->ParsedBytes += BytesTaken;
|
|
BufferLength -= BytesTaken;
|
|
|
|
//
|
|
// Need some accounting for Logging
|
|
//
|
|
pRequest->BytesReceived += BytesTaken;
|
|
|
|
//
|
|
// did we consume any of the data? if so, give the request
|
|
// a pointer to the buffer
|
|
//
|
|
|
|
if (BytesTaken > 0)
|
|
{
|
|
if (pRequest->pHeaderBuffer == NULL)
|
|
{
|
|
//
|
|
// store its location, for later release
|
|
//
|
|
pRequest->pHeaderBuffer = pConnection->pCurrentBuffer;
|
|
}
|
|
|
|
pRequest->pLastHeaderBuffer = pConnection->pCurrentBuffer;
|
|
|
|
if (!UlpReferenceBuffers(
|
|
pRequest,
|
|
pConnection->pCurrentBuffer
|
|
))
|
|
{
|
|
Status = STATUS_NO_MEMORY;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Tell the connection how many bytes we consumed.
|
|
// HTTP header bytes are "consumed" as soon as we
|
|
// parse them.
|
|
//
|
|
|
|
UlpConsumeBytesFromConnection(
|
|
pConnection,
|
|
BytesTaken
|
|
);
|
|
|
|
}
|
|
|
|
//
|
|
// did everything work out ok?
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (Status == STATUS_MORE_PROCESSING_REQUIRED)
|
|
{
|
|
ULONG FullBytesReceived;
|
|
|
|
FullBytesReceived = (ULONG)(
|
|
(pRequest->BytesReceived + BufferLength));
|
|
|
|
if (FullBytesReceived < g_UlMaxRequestBytes)
|
|
{
|
|
//
|
|
// we need more transport data
|
|
//
|
|
|
|
pConnection->NeedMoreData = 1;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The request has grown too large. Send back
|
|
// an error.
|
|
//
|
|
|
|
if (pRequest->ParseState == ParseUrlState)
|
|
{
|
|
pRequest->ErrorCode = UlErrorUrlLength;
|
|
|
|
UlTrace(PARSER, (
|
|
"UlpParseNextRequest(pRequest = %p)"
|
|
" ERROR: URL is too big\n",
|
|
pRequest
|
|
));
|
|
}
|
|
else
|
|
{
|
|
pRequest->ErrorCode = UlErrorRequestLength;
|
|
|
|
UlTrace(PARSER, (
|
|
"UlpParseNextRequest(pRequest = %p)"
|
|
" ERROR: request is too big\n",
|
|
pRequest
|
|
));
|
|
}
|
|
|
|
pRequest->ParseState = ParseErrorState;
|
|
Status = STATUS_SECTION_TOO_BIG;
|
|
|
|
goto end;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// some other bad error!
|
|
//
|
|
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
//
|
|
// if we're not done parsing the request, we need more data.
|
|
// it's not bad enough to set NeedMoreData as nothing important
|
|
// spanned buffer boundaries (header values, etc..) . it was
|
|
// a clean split. no buffer merging is necessary. simply skip
|
|
// to the next buffer.
|
|
//
|
|
|
|
if (pRequest->ParseState <= ParseCookState)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// all done, mark the sequence number on this request
|
|
//
|
|
|
|
pRequest->RecvNumber = pConnection->NextRecvNumber;
|
|
pConnection->NextRecvNumber += 1;
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlpParseNextRequest(httpconn = %p) built request %p\n",
|
|
pConnection,
|
|
pRequest
|
|
));
|
|
|
|
//
|
|
// Stop the Header Wait timer
|
|
//
|
|
|
|
UlLockTimeoutInfo(
|
|
&pConnection->TimeoutInfo,
|
|
&OldIrql
|
|
);
|
|
|
|
UlResetConnectionTimer(
|
|
&pConnection->TimeoutInfo,
|
|
TimerHeaderWait
|
|
);
|
|
|
|
UlUnlockTimeoutInfo(
|
|
&pConnection->TimeoutInfo,
|
|
OldIrql
|
|
);
|
|
|
|
UlEvaluateTimerState(
|
|
&pConnection->TimeoutInfo
|
|
);
|
|
|
|
//
|
|
// check protocol compliance
|
|
//
|
|
|
|
Status = UlCheckProtocolCompliance(pConnection, pRequest);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// This request is bad. Send a 400.
|
|
//
|
|
|
|
pRequest->ParseState = ParseErrorState;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
Status = UlpDeliverHttpRequest( pConnection, &ResponseSent );
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto end;
|
|
}
|
|
|
|
if (ResponseSent)
|
|
{
|
|
//
|
|
// We have hit the cache entry and sent the response.
|
|
// There is no more use for the request anymore so
|
|
// unlink it from the connection and try parsing the
|
|
// next request immediately.
|
|
//
|
|
|
|
UlCleanupHttpConnection(pConnection);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// if we're done parsing the request break out
|
|
// of the loop. Otherwise keep going around
|
|
// so we can pick up the entity body.
|
|
//
|
|
|
|
if (pRequest->ParseState == ParseDoneState)
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// done with protocol parsing. keep looping.
|
|
//
|
|
|
|
break;
|
|
|
|
case ParseEntityBodyState:
|
|
case ParseTrailerState:
|
|
|
|
pRequest = pConnection->pRequest;
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
//
|
|
// is there anything for us to parse?
|
|
//
|
|
|
|
UlTraceVerbose(PARSER, (
|
|
"UlpParseNextRequest(pRequest=%p, httpconn=%p): "
|
|
"ChunkBytesToParse = %d.\n",
|
|
pRequest, pConnection, pRequest->ChunkBytesToParse
|
|
));
|
|
|
|
if (pRequest->ChunkBytesToParse > 0)
|
|
{
|
|
ULONG BytesToSkip;
|
|
|
|
//
|
|
// Set/bump the Entity Body Receive timer
|
|
//
|
|
|
|
UlLockTimeoutInfo(
|
|
&pConnection->TimeoutInfo,
|
|
&OldIrql
|
|
);
|
|
|
|
UlSetConnectionTimer(
|
|
&pConnection->TimeoutInfo,
|
|
TimerEntityBody
|
|
);
|
|
|
|
UlUnlockTimeoutInfo(
|
|
&pConnection->TimeoutInfo,
|
|
OldIrql
|
|
);
|
|
|
|
UlEvaluateTimerState(
|
|
&pConnection->TimeoutInfo
|
|
);
|
|
|
|
//
|
|
// is this the first chunk we've parsed?
|
|
//
|
|
|
|
if (pRequest->pChunkBuffer == NULL)
|
|
{
|
|
//
|
|
// store its location, this is where to start reading
|
|
//
|
|
|
|
pRequest->pChunkBuffer = pConnection->pCurrentBuffer;
|
|
pRequest->pChunkLocation = GET_REQUEST_BUFFER_POS(
|
|
pConnection->pCurrentBuffer
|
|
);
|
|
}
|
|
|
|
//
|
|
// how much should we parse?
|
|
//
|
|
|
|
BytesToSkip = (ULONG)(
|
|
MIN(
|
|
pRequest->ChunkBytesToParse,
|
|
BufferLength
|
|
)
|
|
);
|
|
|
|
//
|
|
// update that we parsed this piece
|
|
//
|
|
|
|
pRequest->ChunkBytesToParse -= BytesToSkip;
|
|
pRequest->ChunkBytesParsed += BytesToSkip;
|
|
|
|
pConnection->pCurrentBuffer->ParsedBytes += BytesToSkip;
|
|
BufferLength -= BytesToSkip;
|
|
|
|
//
|
|
// Need some accounting info for Logging
|
|
// ??? I'm not sure about this well enough
|
|
//
|
|
pRequest->BytesReceived += BytesToSkip;
|
|
}
|
|
|
|
//
|
|
// process any irp's waiting for entity body
|
|
//
|
|
|
|
//
|
|
// CODEWORK. this doesn't need to be called after each chunk.
|
|
//
|
|
|
|
UlTraceVerbose(PARSER, (
|
|
"UlpParseNextRequest(pRequest=%p, httpconn=%p): "
|
|
"about to process buffer queue\n",
|
|
pRequest, pConnection
|
|
));
|
|
|
|
UlpProcessBufferQueue(pRequest);
|
|
|
|
//
|
|
// check to see there is another chunk
|
|
//
|
|
|
|
UlTraceVerbose(PARSER, (
|
|
"UlpParseNextRequest(pRequest=%p, httpconn=%p): "
|
|
"checking to see if another chunk.\n",
|
|
pRequest, pConnection
|
|
));
|
|
|
|
Status = UlParseHttp(
|
|
pRequest,
|
|
GET_REQUEST_BUFFER_POS(pConnection->pCurrentBuffer),
|
|
BufferLength,
|
|
&BytesTaken
|
|
);
|
|
|
|
UlTraceVerbose(PARSER, (
|
|
"UlpParseNextRequest(pRequest = %p)"
|
|
" UlParseHttp: states (EB/T) %d -> %d, %d bytes taken\n",
|
|
pRequest, OldState, pConnection->pRequest->ParseState,
|
|
BytesTaken
|
|
));
|
|
|
|
pConnection->pCurrentBuffer->ParsedBytes += BytesTaken;
|
|
BufferLength -= BytesTaken;
|
|
|
|
//
|
|
// Need some accounting info for Logging
|
|
//
|
|
pRequest->BytesReceived += BytesTaken;
|
|
|
|
//
|
|
// was there enough in the buffer to please?
|
|
//
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
if (Status == STATUS_MORE_PROCESSING_REQUIRED)
|
|
{
|
|
//
|
|
// we need more transport data
|
|
//
|
|
|
|
pConnection->NeedMoreData = 1;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// some other bad error !
|
|
//
|
|
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
//
|
|
// are we all done parsing it ?
|
|
//
|
|
|
|
if (pRequest->ParseState == ParseDoneState)
|
|
{
|
|
ASSERT( pConnection->WaitingForResponse == 1 );
|
|
|
|
//
|
|
// Once more, with feeling. Check to see if there
|
|
// are any remaining buffers to be processed or irps
|
|
// to be completed (e.g., catch a solo zero-length
|
|
// chunk)
|
|
//
|
|
|
|
UlpProcessBufferQueue(pRequest);
|
|
|
|
//
|
|
// Stop entity body receive timer
|
|
//
|
|
|
|
UlLockTimeoutInfo(
|
|
&pConnection->TimeoutInfo,
|
|
&OldIrql
|
|
);
|
|
|
|
UlResetConnectionTimer(
|
|
&pConnection->TimeoutInfo,
|
|
TimerEntityBody
|
|
);
|
|
|
|
UlUnlockTimeoutInfo(
|
|
&pConnection->TimeoutInfo,
|
|
OldIrql
|
|
);
|
|
|
|
UlEvaluateTimerState(
|
|
&pConnection->TimeoutInfo
|
|
);
|
|
|
|
UlTraceVerbose(PARSER, (
|
|
"UlpParseNextRequest(pRequest = %p) all done\n",
|
|
pRequest
|
|
));
|
|
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// keep looping.
|
|
//
|
|
|
|
break;
|
|
|
|
case ParseErrorState:
|
|
|
|
//
|
|
// ignore this buffer
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
goto end;
|
|
|
|
case ParseDoneState:
|
|
default:
|
|
//
|
|
// this should never happen
|
|
//
|
|
Status = STATUS_INVALID_DEVICE_STATE;
|
|
goto end;
|
|
|
|
} // switch (pConnection->pRequest->ParseState)
|
|
|
|
} // for(;;)
|
|
}
|
|
|
|
end:
|
|
return Status;
|
|
} // UlpParseNextRequest
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
DeliverHttpRequest may want to get the cgroup info for the request if it's
|
|
not a cache hit. Similarly sendresponse may want to get this info - later-
|
|
even if it's cache hit, when logging is enabled on the hit. Therefore we
|
|
have created a new function for this to easily maintain the functionality.
|
|
|
|
Arguments:
|
|
|
|
pConnection - The connection whose request we are to deliver.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlGetCGroupForRequest(
|
|
IN PUL_INTERNAL_REQUEST pRequest
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
BOOLEAN OptionsStar;
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
|
|
PAGED_CODE();
|
|
Status = STATUS_SUCCESS;
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
//
|
|
// Lookup the config group information for this url .
|
|
//
|
|
// don't include the query string in the lookup.
|
|
// route OPTIONS * as though it were OPTIONS /
|
|
//
|
|
|
|
if (pRequest->CookedUrl.pQueryString != NULL)
|
|
{
|
|
pRequest->CookedUrl.pQueryString[0] = UNICODE_NULL;
|
|
}
|
|
|
|
if ((pRequest->Verb == HttpVerbOPTIONS)
|
|
&& (pRequest->CookedUrl.pAbsPath[0] == '*')
|
|
&& (pRequest->CookedUrl.pAbsPath[1] == UNICODE_NULL))
|
|
{
|
|
pRequest->CookedUrl.pAbsPath[0] = '/';
|
|
OptionsStar = TRUE;
|
|
} else {
|
|
OptionsStar = FALSE;
|
|
}
|
|
|
|
//
|
|
// Get the Url Config Info
|
|
//
|
|
Status = UlGetConfigGroupInfoForUrl(
|
|
pRequest->CookedUrl.pUrl,
|
|
pRequest->pHttpConn,
|
|
&pRequest->ConfigInfo
|
|
);
|
|
|
|
if (pRequest->CookedUrl.pQueryString != NULL)
|
|
{
|
|
pRequest->CookedUrl.pQueryString[0] = L'?';
|
|
}
|
|
|
|
//
|
|
// restore the * in the path
|
|
//
|
|
if (OptionsStar) {
|
|
pRequest->CookedUrl.pAbsPath[0] = '*';
|
|
}
|
|
|
|
return Status;
|
|
} // UlGetCGroupForRequest
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Takes a parsed http request and tries to deliver it to something
|
|
that can send a response.
|
|
|
|
First we try the cache. If there is no cache entry we try to route
|
|
to an app pool.
|
|
|
|
We send back an auto response if the control channel
|
|
or config group is inactive. If we can't do any of those things we
|
|
set an error code in the HTTP_REQUEST and return a failure status.
|
|
The caller will take care of sending the error.
|
|
|
|
Arguments:
|
|
|
|
pConnection - The connection whose request we are to deliver.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpDeliverHttpRequest(
|
|
IN PUL_HTTP_CONNECTION pConnection,
|
|
OUT PBOOLEAN pResponseSent
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
BOOLEAN ServedFromCache;
|
|
BOOLEAN OptionsStar;
|
|
BOOLEAN ConnectionRefused;
|
|
HTTP_ENABLED_STATE CurrentState;
|
|
PUL_INTERNAL_RESPONSE pAutoResponse;
|
|
ULONG Connections;
|
|
PUL_SITE_COUNTER_ENTRY pCtr;
|
|
PUL_CONFIG_GROUP_OBJECT pMaxBandwidth;
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pConnection));
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pConnection->pRequest));
|
|
|
|
pRequest = pConnection->pRequest;
|
|
|
|
*pResponseSent = FALSE;
|
|
ConnectionRefused = FALSE;
|
|
pMaxBandwidth = NULL;
|
|
|
|
//
|
|
// Do we have a cache hit?
|
|
// Set WaitingForResponse to 1 before calling UlSendCachedResponse
|
|
// because the send may be completed before we return.
|
|
//
|
|
|
|
pConnection->WaitingForResponse = 1;
|
|
|
|
UlTrace( PARSER, (
|
|
"***3 pConnection %p->WaitingForResponse = 1\n",
|
|
pConnection
|
|
));
|
|
|
|
pRequest->CachePreconditions = UlCheckCachePreconditions(
|
|
pRequest,
|
|
pConnection
|
|
);
|
|
|
|
if (pRequest->CachePreconditions)
|
|
{
|
|
Status = UlSendCachedResponse(
|
|
pConnection,
|
|
&ServedFromCache,
|
|
&ConnectionRefused
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) && ServedFromCache && !ConnectionRefused)
|
|
{
|
|
//
|
|
// All done with this request. Wait for response
|
|
// before going on.
|
|
//
|
|
|
|
*pResponseSent = TRUE;
|
|
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// If a cache precondition failed during SendCacheResponse,
|
|
// then bail out.
|
|
//
|
|
|
|
if ( UlErrorPreconditionFailed == pRequest->ErrorCode )
|
|
{
|
|
ASSERT( STATUS_INVALID_DEVICE_STATE == Status );
|
|
|
|
pConnection->WaitingForResponse = 0;
|
|
|
|
UlTrace( PARSER, (
|
|
"***3 pConnection %p->WaitingForResponse = 0\n",
|
|
pConnection
|
|
));
|
|
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We didn't do a send from the cache, so we are not
|
|
// yet WaitingForResponse.
|
|
//
|
|
|
|
pConnection->WaitingForResponse = 0;
|
|
|
|
UlTrace( PARSER, (
|
|
"***3 pConnection %p->WaitingForResponse = 0\n",
|
|
pConnection
|
|
));
|
|
|
|
//
|
|
// If connection refused during SendCacheResponse because of a connection
|
|
// limit then refuse the request.
|
|
//
|
|
|
|
if (ConnectionRefused)
|
|
{
|
|
pRequest->ParseState = ParseErrorState;
|
|
pRequest->ErrorCode = UlErrorConnectionLimit;
|
|
|
|
Status = STATUS_INVALID_DEVICE_STATE;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Allocate request ID here since we didn't do it in UlCreateHttpRequest.
|
|
//
|
|
|
|
Status = UlAllocateRequestId(pRequest);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
pRequest->ParseState = ParseErrorState;
|
|
pRequest->ErrorCode = UlErrorInternalServer;
|
|
|
|
goto end;
|
|
}
|
|
|
|
UL_REFERENCE_INTERNAL_REQUEST( pRequest );
|
|
|
|
pRequest->RequestIdCopy = pRequest->RequestId;
|
|
|
|
//
|
|
// Previous code fragment here to get the cgroup info
|
|
// has been moved to a seperate function, as logging
|
|
// related code in sendresponse requires this info
|
|
// as well.
|
|
//
|
|
|
|
Status = UlGetCGroupForRequest( pRequest );
|
|
|
|
//
|
|
// CODEWORK+BUGBUG: need to check the port's actually matched
|
|
//
|
|
|
|
//
|
|
// check that the config group tree lookup matched
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status) || pRequest->ConfigInfo.pAppPool == NULL)
|
|
{
|
|
//
|
|
// Could not route to a listening url, send
|
|
// back an http error. Always return error 400
|
|
// to show that host not found. This will also
|
|
// make us to be compliant with HTTP1.1 / 5.2
|
|
//
|
|
|
|
// REVIEW: What do we do about the site counter(s)
|
|
// REVIEW: when we can't route to a site? i.e., Connection Attempts?
|
|
|
|
pRequest->ParseState = ParseErrorState;
|
|
pRequest->ErrorCode = UlErrorHost;
|
|
|
|
Status = STATUS_INVALID_DEVICE_STATE;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Check to see if there's a connection timeout value override
|
|
//
|
|
|
|
if (0L != pRequest->ConfigInfo.ConnectionTimeout)
|
|
{
|
|
UlSetPerSiteConnectionTimeoutValue(
|
|
&pRequest->pHttpConn->TimeoutInfo,
|
|
pRequest->ConfigInfo.ConnectionTimeout
|
|
);
|
|
}
|
|
|
|
//
|
|
// Check the connection limit of the site.
|
|
//
|
|
if (UlCheckSiteConnectionLimit(pConnection, &pRequest->ConfigInfo) == FALSE)
|
|
{
|
|
// If exceeding the site limit, send back 403 error and disconnect.
|
|
// NOTE: This code depend on the fact that UlSendErrorResponse always
|
|
// NOTE: disconnect. Otherwise we need a force disconnect here.
|
|
|
|
pRequest->ParseState = ParseErrorState;
|
|
pRequest->ErrorCode = UlErrorConnectionLimit;
|
|
|
|
Status = STATUS_INVALID_DEVICE_STATE;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Perf Counters (non-cached)
|
|
//
|
|
pCtr = pRequest->ConfigInfo.pSiteCounters;
|
|
if (pCtr)
|
|
{
|
|
// NOTE: pCtr may be NULL if the SiteId was never set on the root-level
|
|
// NOTE: Config Group for the site. BVTs may need to be updated.
|
|
|
|
ASSERT(IS_VALID_SITE_COUNTER_ENTRY(pCtr));
|
|
|
|
UlIncSiteNonCriticalCounterUlong(pCtr, HttpSiteCounterConnAttempts);
|
|
|
|
UlIncSiteNonCriticalCounterUlong(pCtr, HttpSiteCounterAllReqs);
|
|
|
|
if (pCtr != pConnection->pPrevSiteCounters)
|
|
{
|
|
if (pConnection->pPrevSiteCounters)
|
|
{
|
|
// Decrement old site's counters & release ref count
|
|
|
|
UlDecSiteCounter(
|
|
pConnection->pPrevSiteCounters,
|
|
HttpSiteCounterCurrentConns
|
|
);
|
|
DEREFERENCE_SITE_COUNTER_ENTRY(pConnection->pPrevSiteCounters);
|
|
}
|
|
|
|
Connections = (ULONG) UlIncSiteCounter(pCtr, HttpSiteCounterCurrentConns);
|
|
UlMaxSiteCounter(
|
|
pCtr,
|
|
HttpSiteCounterMaxConnections,
|
|
Connections
|
|
);
|
|
|
|
// add ref for new site counters
|
|
REFERENCE_SITE_COUNTER_ENTRY(pCtr);
|
|
pConnection->pPrevSiteCounters = pCtr;
|
|
|
|
}
|
|
}
|
|
|
|
// Try to get the corresponding cgroup for the bw settings
|
|
pMaxBandwidth = pRequest->ConfigInfo.pMaxBandwidth;
|
|
|
|
//
|
|
// Install a filter if BWT is enabled for this request's site.
|
|
//
|
|
if (pMaxBandwidth != NULL &&
|
|
pMaxBandwidth->MaxBandwidth.Flags.Present != 0 &&
|
|
pMaxBandwidth->MaxBandwidth.MaxBandwidth != HTTP_LIMIT_INFINITE)
|
|
{
|
|
// Call TCI to do the filter addition
|
|
UlTcAddFilter( pConnection, pMaxBandwidth );
|
|
}
|
|
else
|
|
{
|
|
// Attempt to add the filter to the global flow
|
|
if (UlTcGlobalThrottlingEnabled())
|
|
{
|
|
UlTcAddFilter( pConnection, NULL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// the routing matched, let's check and see if we are active.
|
|
//
|
|
|
|
//
|
|
// first check the control channel.
|
|
//
|
|
|
|
if (pRequest->ConfigInfo.pControlChannel->State != HttpEnabledStateActive)
|
|
{
|
|
UlTrace(HTTP_IO, ("http!UlpDeliverHttpRequest Control Channel is inactive\n"));
|
|
|
|
CurrentState = HttpEnabledStateInactive;
|
|
|
|
pAutoResponse =
|
|
pRequest->ConfigInfo.pControlChannel->pAutoResponse;
|
|
}
|
|
// now check the cgroup
|
|
else if (pRequest->ConfigInfo.CurrentState != HttpEnabledStateActive)
|
|
{
|
|
UlTrace(HTTP_IO, ("http!UlpDeliverHttpRequest Config Group is inactive\n"));
|
|
|
|
CurrentState = HttpEnabledStateInactive;
|
|
pAutoResponse = pRequest->ConfigInfo.pAutoResponse;
|
|
}
|
|
else
|
|
{
|
|
CurrentState = HttpEnabledStateActive;
|
|
pAutoResponse = NULL;
|
|
}
|
|
|
|
|
|
//
|
|
// well, are we active?
|
|
//
|
|
if (CurrentState == HttpEnabledStateActive)
|
|
{
|
|
|
|
//
|
|
// it's a normal request. Deliver to
|
|
// app pool (aka client)
|
|
//
|
|
Status = UlDeliverRequestToProcess(
|
|
pRequest->ConfigInfo.pAppPool,
|
|
pRequest
|
|
);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
//
|
|
// All done with this request. Wait for response
|
|
// before going on.
|
|
//
|
|
|
|
pConnection->WaitingForResponse = 1;
|
|
|
|
UlTrace( PARSER, (
|
|
"***4 pConnection %p->WaitingForResponse = 1\n",
|
|
pConnection
|
|
));
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// we are not active. Send an autoresponse if we have one.
|
|
//
|
|
if (pAutoResponse != NULL)
|
|
{
|
|
//
|
|
// send it and return
|
|
//
|
|
Status = UlSendHttpResponse(
|
|
pRequest,
|
|
pAutoResponse,
|
|
0,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We have to fall back to a hardcoded response
|
|
//
|
|
|
|
pRequest->ParseState = ParseErrorState;
|
|
pRequest->ErrorCode = UlErrorUnavailable;
|
|
|
|
Status = STATUS_INVALID_DEVICE_STATE;
|
|
}
|
|
}
|
|
|
|
|
|
end:
|
|
return Status;
|
|
} // UlpDeliverHttpRequest
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
links the buffer into the sorted connection list.
|
|
|
|
Arguments:
|
|
|
|
pConnection - the connection to insert into
|
|
|
|
pRequestBuffer - the buffer to link in
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpInsertBuffer(
|
|
PUL_HTTP_CONNECTION pConnection,
|
|
PUL_REQUEST_BUFFER pRequestBuffer
|
|
)
|
|
{
|
|
PLIST_ENTRY pEntry;
|
|
PUL_REQUEST_BUFFER pListBuffer = NULL;
|
|
|
|
ASSERT( UL_IS_VALID_HTTP_CONNECTION(pConnection) );
|
|
ASSERT( UlDbgResourceOwnedExclusive( &pConnection->Resource ) );
|
|
ASSERT( UL_IS_VALID_REQUEST_BUFFER(pRequestBuffer) );
|
|
ASSERT( pRequestBuffer->UsedBytes != 0 );
|
|
|
|
//
|
|
// figure out where to insert the buffer into our
|
|
// sorted queue (we need to enforce FIFO by number -
|
|
// head is the first in). optimize for ordered inserts by
|
|
// searching tail to head.
|
|
//
|
|
|
|
pEntry = pConnection->BufferHead.Blink;
|
|
while (pEntry != &(pConnection->BufferHead))
|
|
{
|
|
pListBuffer = CONTAINING_RECORD(
|
|
pEntry,
|
|
UL_REQUEST_BUFFER,
|
|
ListEntry
|
|
);
|
|
|
|
ASSERT( UL_IS_VALID_REQUEST_BUFFER(pListBuffer) );
|
|
|
|
//
|
|
// if the number is less than, put it here, we are
|
|
// searching in reverse sort order
|
|
//
|
|
|
|
if (pListBuffer->BufferNumber < pRequestBuffer->BufferNumber)
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// go on to the next one
|
|
//
|
|
|
|
pEntry = pEntry->Blink;
|
|
}
|
|
|
|
UlTrace(
|
|
HTTP_IO, (
|
|
"http!UlpInsertBuffer(conn=%p): inserting %p(%d) after %p(%d)\n",
|
|
pConnection,
|
|
pRequestBuffer,
|
|
pRequestBuffer->BufferNumber,
|
|
pListBuffer,
|
|
(pEntry == &(pConnection->BufferHead)) ?
|
|
-1 : pListBuffer->BufferNumber
|
|
)
|
|
);
|
|
|
|
//
|
|
// and insert it
|
|
//
|
|
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pConnection));
|
|
|
|
InsertHeadList(
|
|
pEntry,
|
|
&(pRequestBuffer->ListEntry)
|
|
);
|
|
|
|
WRITE_REF_TRACE_LOG2(
|
|
g_pHttpConnectionTraceLog,
|
|
pConnection->pTraceLog,
|
|
REF_ACTION_INSERT_BUFFER,
|
|
pConnection->RefCount,
|
|
pRequestBuffer,
|
|
__FILE__,
|
|
__LINE__
|
|
);
|
|
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pConnection));
|
|
|
|
} // UlpInsertBuffer
|
|
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Merges the unparsed bytes on a source buffer to a destination buffer.
|
|
Assumes that there is space in the buffer.
|
|
|
|
Arguments:
|
|
|
|
pDest - the buffer the gets the bytes
|
|
pSrc - the buffer that gives the bytes
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpMergeBuffers(
|
|
PUL_REQUEST_BUFFER pDest,
|
|
PUL_REQUEST_BUFFER pSrc
|
|
)
|
|
{
|
|
ASSERT( UL_IS_VALID_REQUEST_BUFFER( pDest ) );
|
|
ASSERT( UL_IS_VALID_REQUEST_BUFFER( pSrc ) );
|
|
ASSERT( pDest->AllocBytes - pDest->UsedBytes >= UNPARSED_BUFFER_BYTES( pSrc ) );
|
|
ASSERT( UlpIsValidRequestBufferList( pSrc->pConnection ) );
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlpMergeBuffers(pDest = %p(#%d), pSrc = %p(#%d))\n"
|
|
" Copying %d bytes from pSrc.\n"
|
|
" pDest->AllocBytes (%d) - pDest->UsedBytes(%d) = %d available\n",
|
|
pDest,
|
|
pDest->BufferNumber,
|
|
pSrc,
|
|
pSrc->BufferNumber,
|
|
UNPARSED_BUFFER_BYTES( pSrc ),
|
|
pDest->AllocBytes,
|
|
pDest->UsedBytes,
|
|
pDest->AllocBytes - pDest->UsedBytes
|
|
));
|
|
|
|
//
|
|
// copy the unparsed bytes
|
|
//
|
|
RtlCopyMemory(
|
|
pDest->pBuffer + pDest->UsedBytes,
|
|
GET_REQUEST_BUFFER_POS( pSrc ),
|
|
UNPARSED_BUFFER_BYTES( pSrc )
|
|
);
|
|
|
|
//
|
|
// adjust buffer byte counters to match the transfer
|
|
//
|
|
pDest->UsedBytes += UNPARSED_BUFFER_BYTES( pSrc );
|
|
pSrc->UsedBytes = pSrc->ParsedBytes;
|
|
|
|
ASSERT( pDest->UsedBytes != 0 );
|
|
ASSERT( pDest->UsedBytes <= pDest->AllocBytes );
|
|
} // UlpMergeBuffers
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
sets up pCurrentBuffer to the proper location, merging any blocks
|
|
as needed.
|
|
|
|
Arguments:
|
|
|
|
pConnection - the connection to adjust buffers for
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpAdjustBuffers(
|
|
PUL_HTTP_CONNECTION pConnection
|
|
)
|
|
{
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pConnection));
|
|
ASSERT(UlDbgResourceOwnedExclusive(&pConnection->Resource));
|
|
|
|
//
|
|
// do we have a starting buffer?
|
|
//
|
|
|
|
if (pConnection->pCurrentBuffer == NULL)
|
|
{
|
|
//
|
|
// the list can't be empty, this is the FIRST time in
|
|
// pCurrentBuffer is NULL
|
|
//
|
|
|
|
ASSERT(IsListEmpty(&(pConnection->BufferHead)) == FALSE);
|
|
ASSERT(pConnection->NextBufferToParse == 0);
|
|
|
|
//
|
|
// pop from the head
|
|
//
|
|
|
|
pConnection->pCurrentBuffer = CONTAINING_RECORD(
|
|
pConnection->BufferHead.Flink,
|
|
UL_REQUEST_BUFFER,
|
|
ListEntry
|
|
);
|
|
|
|
ASSERT( UL_IS_VALID_REQUEST_BUFFER(pConnection->pCurrentBuffer) );
|
|
|
|
//
|
|
// is this the right number?
|
|
//
|
|
|
|
if (pConnection->pCurrentBuffer->BufferNumber !=
|
|
pConnection->NextBufferToParse)
|
|
{
|
|
pConnection->pCurrentBuffer = NULL;
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
pConnection->NextBufferToParse += 1;
|
|
|
|
pConnection->NeedMoreData = 0;
|
|
}
|
|
|
|
//
|
|
// did we need more transport data?
|
|
//
|
|
|
|
if (pConnection->NeedMoreData == 1)
|
|
{
|
|
PUL_REQUEST_BUFFER pNextBuffer;
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlpAdjustBuffers(pHttpConn %p) NeedMoreData == 1\n",
|
|
pConnection
|
|
));
|
|
|
|
//
|
|
// is it there?
|
|
//
|
|
|
|
if (pConnection->pCurrentBuffer->ListEntry.Flink ==
|
|
&(pConnection->BufferHead))
|
|
{
|
|
//
|
|
// need to wait for more
|
|
//
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlpAdjustBuffers(pHttpConn %p) NeedMoreData == 1\n"
|
|
" No new buffer available yet\n",
|
|
pConnection
|
|
));
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
pNextBuffer = CONTAINING_RECORD(
|
|
pConnection->pCurrentBuffer->ListEntry.Flink,
|
|
UL_REQUEST_BUFFER,
|
|
ListEntry
|
|
);
|
|
|
|
ASSERT( UL_IS_VALID_REQUEST_BUFFER(pNextBuffer) );
|
|
|
|
//
|
|
// is the next buffer really the 'next' buffer?
|
|
//
|
|
|
|
if (pNextBuffer->BufferNumber != pConnection->NextBufferToParse)
|
|
{
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlpAdjustBuffers(pHttpConn %p) NeedMoreData == 1\n"
|
|
" Buffer %d available, but we're waiting for %d\n",
|
|
pConnection,
|
|
pNextBuffer->BufferNumber,
|
|
pConnection->NextBufferToParse
|
|
));
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
//
|
|
// is there space to merge the blocks?
|
|
//
|
|
|
|
if (pNextBuffer->UsedBytes <
|
|
(pConnection->pCurrentBuffer->AllocBytes -
|
|
pConnection->pCurrentBuffer->UsedBytes))
|
|
{
|
|
//
|
|
// merge 'em .. copy the next buffer into this buffer
|
|
//
|
|
|
|
UlpMergeBuffers(
|
|
pConnection->pCurrentBuffer, // dest
|
|
pNextBuffer // src
|
|
);
|
|
|
|
//
|
|
// remove the next (now empty) buffer
|
|
//
|
|
|
|
ASSERT( pNextBuffer->UsedBytes == 0 );
|
|
UlFreeRequestBuffer(pNextBuffer);
|
|
|
|
ASSERT( UlpIsValidRequestBufferList( pConnection ) );
|
|
|
|
//
|
|
// skip the buffer sequence number as we deleted that next buffer
|
|
// placing the data in the current buffer. the "new" next buffer
|
|
// will have a 1 higher sequence number.
|
|
//
|
|
|
|
pConnection->NextBufferToParse += 1;
|
|
|
|
//
|
|
// reset the signal for more data needed
|
|
//
|
|
|
|
pConnection->NeedMoreData = 0;
|
|
|
|
}
|
|
else
|
|
{
|
|
PUL_REQUEST_BUFFER pNewBuffer;
|
|
|
|
//
|
|
// allocate a new buffer with space for the remaining stuff
|
|
// from the old buffer, and everything in the new buffer.
|
|
//
|
|
// this new buffer is replacing pNextBuffer so gets its
|
|
// BufferNumber.
|
|
//
|
|
|
|
pNewBuffer = UlCreateRequestBuffer(
|
|
(pConnection->pCurrentBuffer->UsedBytes -
|
|
pConnection->pCurrentBuffer->ParsedBytes) +
|
|
pNextBuffer->UsedBytes,
|
|
pNextBuffer->BufferNumber
|
|
);
|
|
|
|
if (pNewBuffer == NULL)
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
pNewBuffer->pConnection = pConnection;
|
|
|
|
UlTrace( PARSER, (
|
|
"*** Request Buffer %p has connection %p\n",
|
|
pNewBuffer,
|
|
pConnection
|
|
));
|
|
|
|
//
|
|
// copy the unused portion into the start of this
|
|
// buffer
|
|
//
|
|
|
|
UlpMergeBuffers(
|
|
pNewBuffer, // dest
|
|
pConnection->pCurrentBuffer // src
|
|
);
|
|
|
|
if ( 0 == pConnection->pCurrentBuffer->UsedBytes )
|
|
{
|
|
//
|
|
// Whoops! Accidently ate everything...zap this buffer!
|
|
// This happens when we're ahead of the parser and there
|
|
// are 0 ParsedBytes.
|
|
//
|
|
|
|
ASSERT( 0 == pConnection->pCurrentBuffer->ParsedBytes );
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlpAdjustBuffers: Zapping pConnection->pCurrentBuffer (%p)\n",
|
|
pConnection->pCurrentBuffer
|
|
));
|
|
|
|
UlFreeRequestBuffer( pConnection->pCurrentBuffer );
|
|
pConnection->pCurrentBuffer = NULL;
|
|
}
|
|
|
|
//
|
|
// merge the next block into this one
|
|
//
|
|
|
|
UlpMergeBuffers(
|
|
pNewBuffer, // dest
|
|
pNextBuffer // src
|
|
);
|
|
|
|
|
|
//
|
|
// Dispose of the now empty next buffer
|
|
//
|
|
|
|
ASSERT(pNextBuffer->UsedBytes == 0);
|
|
UlFreeRequestBuffer(pNextBuffer);
|
|
pNextBuffer = NULL;
|
|
|
|
//
|
|
// link in the new buffer
|
|
//
|
|
|
|
ASSERT(pNewBuffer->UsedBytes != 0 );
|
|
UlpInsertBuffer(pConnection, pNewBuffer);
|
|
|
|
ASSERT( UlpIsValidRequestBufferList( pConnection ) );
|
|
|
|
//
|
|
// this newly created (larger) buffer is still the next
|
|
// buffer to parse
|
|
//
|
|
|
|
ASSERT(pNewBuffer->BufferNumber == pConnection->NextBufferToParse);
|
|
|
|
//
|
|
// so make it the current buffer now
|
|
//
|
|
|
|
pConnection->pCurrentBuffer = pNewBuffer;
|
|
|
|
//
|
|
// and advance the sequence checker
|
|
//
|
|
|
|
pConnection->NextBufferToParse += 1;
|
|
|
|
//
|
|
// now reset the signal for more data needed
|
|
//
|
|
|
|
pConnection->NeedMoreData = 0;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
//
|
|
// is this buffer drained?
|
|
//
|
|
|
|
if (pConnection->pCurrentBuffer->UsedBytes ==
|
|
pConnection->pCurrentBuffer->ParsedBytes)
|
|
{
|
|
PUL_REQUEST_BUFFER pOldBuffer;
|
|
|
|
//
|
|
// are there any more buffers?
|
|
//
|
|
|
|
if (pConnection->pCurrentBuffer->ListEntry.Flink ==
|
|
&(pConnection->BufferHead))
|
|
{
|
|
|
|
//
|
|
// need to wait for more.
|
|
//
|
|
// we leave this empty buffer around refcount'd
|
|
// in pCurrentBuffer until a new buffer shows up,
|
|
// or the connection is dropped.
|
|
//
|
|
// this is so we don't lose our place
|
|
// and have to search the sorted queue
|
|
//
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlpAdjustBuffers(pHttpConn = %p) NeedMoreData == 0\n"
|
|
" buffer %p(%d) is drained, more required\n",
|
|
pConnection,
|
|
pConnection->pCurrentBuffer,
|
|
pConnection->pCurrentBuffer->BufferNumber
|
|
));
|
|
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
// else
|
|
|
|
//
|
|
// grab the next buffer
|
|
//
|
|
|
|
pOldBuffer = pConnection->pCurrentBuffer;
|
|
|
|
pConnection->
|
|
pCurrentBuffer = CONTAINING_RECORD(
|
|
pConnection->
|
|
pCurrentBuffer->ListEntry.Flink,
|
|
UL_REQUEST_BUFFER,
|
|
ListEntry
|
|
);
|
|
|
|
ASSERT( UL_IS_VALID_REQUEST_BUFFER(pConnection->pCurrentBuffer) );
|
|
|
|
//
|
|
// is it the 'next' buffer?
|
|
//
|
|
|
|
if (pConnection->pCurrentBuffer->BufferNumber !=
|
|
pConnection->NextBufferToParse)
|
|
{
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlpAdjustBuffers(pHttpConn %p) NeedMoreData == 0\n"
|
|
" Buffer %d available, but we're waiting for %d\n",
|
|
pConnection,
|
|
pConnection->pCurrentBuffer->BufferNumber,
|
|
pConnection->NextBufferToParse
|
|
));
|
|
|
|
pConnection->pCurrentBuffer = pOldBuffer;
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
|
|
}
|
|
|
|
//
|
|
// bump up the buffer number
|
|
//
|
|
|
|
pConnection->NextBufferToParse += 1;
|
|
|
|
pConnection->NeedMoreData = 0;
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // UlpAdjustBuffers
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Routine invoked after an incoming TCP/MUX connection has been
|
|
received (but not yet accepted).
|
|
|
|
Arguments:
|
|
|
|
pListeningContext - Supplies an uninterpreted context value as
|
|
passed to the UlCreateListeningEndpoint() API.
|
|
|
|
pConnection - Supplies the connection being established.
|
|
|
|
pRemoteAddress - Supplies the remote (client-side) address
|
|
requesting the connection.
|
|
|
|
RemoteAddressLength - Supplies the total byte length of the
|
|
pRemoteAddress structure.
|
|
|
|
ppConnectionContext - Receives a pointer to an uninterpreted
|
|
context value to be associated with the new connection if
|
|
accepted. If the new connection is not accepted, this
|
|
parameter is ignored.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if the connection was accepted, FALSE if not.
|
|
|
|
--***************************************************************************/
|
|
BOOLEAN
|
|
UlConnectionRequest(
|
|
IN PVOID pListeningContext,
|
|
IN PUL_CONNECTION pConnection,
|
|
IN PTRANSPORT_ADDRESS pRemoteAddress,
|
|
IN ULONG RemoteAddressLength,
|
|
OUT PVOID *ppConnectionContext
|
|
)
|
|
{
|
|
PUL_HTTP_CONNECTION pHttpConnection;
|
|
NTSTATUS status;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
ASSERT( IS_VALID_CONNECTION( pConnection ) );
|
|
|
|
UlTrace(HTTP_IO,("UlConnectionRequest: conn %p\n",pConnection));
|
|
|
|
//
|
|
// Check the global connection limit. If it's reached then
|
|
// enforce it by refusing the connection request. The TDI will
|
|
// return STATUS_CONNECTION_REFUSED when we return FALSE here
|
|
//
|
|
|
|
if (UlAcceptGlobalConnection() == FALSE)
|
|
{
|
|
UlTrace(LIMITS,
|
|
("UlConnectionRequest: conn %p refused global limit is reached.\n",
|
|
pConnection
|
|
));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Create a new HTTP connection.
|
|
//
|
|
|
|
status = UlCreateHttpConnection( &pHttpConnection, pConnection );
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
//
|
|
// We the HTTP_CONNECTION pointer as our connection context,
|
|
// ULTDI now owns a reference (from the create).
|
|
//
|
|
|
|
*ppConnectionContext = pHttpConnection;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Failed to create new connection.
|
|
//
|
|
|
|
UlTrace(HTTP_IO,
|
|
("UlpTestConnectionRequest: cannot create new conn, error %08lx\n",
|
|
status
|
|
));
|
|
|
|
return FALSE;
|
|
|
|
} // UlConnectionRequest
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Routine invoked after an incoming TCP/MUX connection has been
|
|
fully accepted.
|
|
|
|
This routine is also invoked if an incoming connection was not
|
|
accepted *after* PUL_CONNECTION_REQUEST returned TRUE. In other
|
|
words, if PUL_CONNECTION_REQUEST indicated that the connection
|
|
should be accepted but a fatal error occurred later, then
|
|
PUL_CONNECTION_COMPLETE is invoked.
|
|
|
|
Arguments:
|
|
|
|
pListeningContext - Supplies an uninterpreted context value
|
|
as passed to the UlCreateListeningEndpoint() API.
|
|
|
|
pConnectionContext - Supplies the uninterpreted context value
|
|
as returned by PUL_CONNECTION_REQUEST.
|
|
|
|
Status - Supplies the completion status. If this value is
|
|
STATUS_SUCCESS, then the connection is now fully accepted.
|
|
Otherwise, the connection has been aborted.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlConnectionComplete(
|
|
IN PVOID pListeningContext,
|
|
IN PVOID pConnectionContext,
|
|
IN NTSTATUS Status
|
|
)
|
|
{
|
|
PUL_CONNECTION pConnection;
|
|
PUL_HTTP_CONNECTION pHttpConnection;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
pHttpConnection = (PUL_HTTP_CONNECTION)pConnectionContext;
|
|
pConnection = pHttpConnection->pConnection;
|
|
ASSERT( IS_VALID_CONNECTION( pConnection ) );
|
|
|
|
UlTrace(HTTP_IO,("UlConnectionComplete: http %p conn %p status %08lx\n",
|
|
pHttpConnection,
|
|
pConnection,
|
|
Status
|
|
));
|
|
|
|
//
|
|
// Blow away our HTTP connection if the connect failed.
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
UL_DEREFERENCE_HTTP_CONNECTION( pHttpConnection );
|
|
}
|
|
|
|
} // UlConnectionComplete
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Routine invoked after an established TCP/MUX connection has been
|
|
disconnected by the remote (client) side.
|
|
|
|
This indication is now obsolete no longer get called from TDI.
|
|
|
|
Arguments:
|
|
|
|
pListeningContext - Supplies an uninterpreted context value
|
|
as passed to the UlCreateListeningEndpoint() API.
|
|
|
|
pConnectionContext - Supplies the uninterpreted context value
|
|
as returned by PUL_CONNECTION_REQUEST.
|
|
|
|
Status - Supplies the termination status.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlConnectionDisconnect(
|
|
IN PVOID pListeningContext,
|
|
IN PVOID pConnectionContext,
|
|
IN NTSTATUS Status
|
|
)
|
|
{
|
|
PUL_CONNECTION pConnection;
|
|
PUL_HTTP_CONNECTION pHttpConnection;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
pHttpConnection = (PUL_HTTP_CONNECTION)pConnectionContext;
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pHttpConnection));
|
|
|
|
pConnection = pHttpConnection->pConnection;
|
|
ASSERT( IS_VALID_CONNECTION( pConnection ) );
|
|
|
|
UlTrace(HTTP_IO,("UlConnectionDisconnect: http %p conn %p\n",
|
|
pHttpConnection,
|
|
pConnection
|
|
));
|
|
|
|
//
|
|
// We are responsible for closing the server side of the tcp connection.
|
|
// No reason to do an abortive disconnect. this indication is that the
|
|
// client has ALREADY disconnected.
|
|
//
|
|
|
|
Status = UlCloseConnection(pConnection, FALSE, NULL, NULL);
|
|
|
|
#if DBG
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DbgPrint(
|
|
"UlConnectionDisconnect: cannot close, error %08lx\n",
|
|
Status
|
|
);
|
|
}
|
|
#endif
|
|
|
|
} // UlConnectionDisconnect
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Worker function to do cleanup work that shouldn't happen above DPC level.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem -- a pointer to a UL_WORK_ITEM DisconnectWorkItem
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlConnectionDisconnectCompleteWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
PUL_HTTP_CONNECTION pConnection;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pWorkItem);
|
|
|
|
pConnection = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_HTTP_CONNECTION,
|
|
DisconnectWorkItem
|
|
);
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlConnectionDisconnectCompleteWorker (%p) pConnection (%p)\n",
|
|
pWorkItem,
|
|
pConnection
|
|
));
|
|
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pConnection));
|
|
|
|
//
|
|
// If connection is already get destroyed just bail out !
|
|
//
|
|
|
|
WRITE_REF_TRACE_LOG2(
|
|
g_pTdiTraceLog,
|
|
pConnection->pConnection->pTraceLog,
|
|
REF_ACTION_DRAIN_UL_CONNECTION_DISCONNECT_COMPLETE,
|
|
pConnection->pConnection->ReferenceCount,
|
|
pConnection->pConnection,
|
|
__FILE__,
|
|
__LINE__
|
|
);
|
|
|
|
//
|
|
// Check to see if we have to draine out or not.
|
|
//
|
|
|
|
UlpDiscardBytesFromConnection( pConnection );
|
|
|
|
//
|
|
// Deref the http connection
|
|
//
|
|
|
|
UL_DEREFERENCE_HTTP_CONNECTION( pConnection );
|
|
|
|
} // UlConnectionDisconnectCompleteWorker
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Routine invoked after an established TCP/MUX connection has been
|
|
disconnected by us (server side) we make a final check here to see
|
|
if we have to drain the connection or not.
|
|
|
|
Arguments:
|
|
|
|
pListeningContext - Supplies an uninterpreted context value
|
|
as passed to the UlCreateListeningEndpoint() API.
|
|
|
|
pConnectionContext - Supplies the uninterpreted context value
|
|
as returned by PUL_CONNECTION_REQUEST.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlConnectionDisconnectComplete(
|
|
IN PVOID pListeningContext,
|
|
IN PVOID pConnectionContext
|
|
)
|
|
{
|
|
PUL_HTTP_CONNECTION pConnection;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
pConnection = (PUL_HTTP_CONNECTION)pConnectionContext;
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pConnection));
|
|
|
|
UlTrace( HTTP_IO,("UlConnectionDisconnectComplete: pConnection %p \n",
|
|
pConnection
|
|
));
|
|
|
|
UL_REFERENCE_HTTP_CONNECTION( pConnection );
|
|
|
|
UL_QUEUE_WORK_ITEM(
|
|
&pConnection->DisconnectWorkItem,
|
|
&UlConnectionDisconnectCompleteWorker
|
|
);
|
|
|
|
} // UlConnectionDisconnectComplete
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Routine invoked after an established TCP/MUX connection has been
|
|
destroyed.
|
|
|
|
Arguments:
|
|
|
|
pListeningContext - Supplies an uninterpreted context value
|
|
as passed to the UlCreateListeningEndpoint() API.
|
|
|
|
pConnectionContext - Supplies the uninterpreted context value
|
|
as returned by PUL_CONNECTION_REQUEST.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlConnectionDestroyed(
|
|
IN PVOID pListeningContext,
|
|
IN PVOID pConnectionContext
|
|
)
|
|
{
|
|
PUL_CONNECTION pConnection;
|
|
PUL_HTTP_CONNECTION pHttpConnection;
|
|
NTSTATUS status;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
pHttpConnection = (PUL_HTTP_CONNECTION)pConnectionContext;
|
|
pConnection = pHttpConnection->pConnection;
|
|
ASSERT( IS_VALID_CONNECTION( pConnection ) );
|
|
|
|
UlTrace(
|
|
HTTP_IO, (
|
|
"http!UlConnectionDestroyed: http %p conn %p\n",
|
|
pHttpConnection,
|
|
pConnection
|
|
)
|
|
);
|
|
|
|
//
|
|
// Remove the CONNECTION and REQUEST opaque id entries and the ULTDI
|
|
// reference
|
|
//
|
|
|
|
UL_QUEUE_WORK_ITEM(
|
|
&pHttpConnection->WorkItem,
|
|
UlConnectionDestroyedWorker
|
|
);
|
|
|
|
} // UlConnectionDestroyed
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
handles retrieving entity body from the http request and placing into
|
|
user mode buffers.
|
|
|
|
Arguments:
|
|
|
|
pRequest - the request to receive from.
|
|
|
|
pIrp - the user irp to copy it into. this will be pended, always.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlReceiveEntityBody(
|
|
IN PUL_APP_POOL_PROCESS pProcess,
|
|
IN PUL_INTERNAL_REQUEST pRequest,
|
|
IN PIRP pIrp
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PIO_STACK_LOCATION pIrpSp;
|
|
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pRequest->pHttpConn));
|
|
|
|
//
|
|
// get the current stack location (a macro)
|
|
//
|
|
|
|
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlReceiveEntityBody: process=%p, req=%p, irp=%p, irpsp=%p\n",
|
|
pProcess, pRequest, pIrp, pIrpSp
|
|
));
|
|
|
|
//
|
|
// is there any recv buffer?
|
|
//
|
|
|
|
if (pIrpSp->Parameters.DeviceIoControl.OutputBufferLength == 0)
|
|
{
|
|
//
|
|
// nope, shortcircuit this
|
|
//
|
|
|
|
Status = STATUS_PENDING;
|
|
pIrp->IoStatus.Information = 0;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// grab our lock
|
|
//
|
|
|
|
UlAcquireResourceExclusive(&(pRequest->pHttpConn->Resource), TRUE);
|
|
|
|
//
|
|
// Make sure we're not cleaning up the request before queuing an
|
|
// IRP on it.
|
|
//
|
|
|
|
if (pRequest->InCleanup)
|
|
{
|
|
|
|
Status = STATUS_CONNECTION_DISCONNECTED;
|
|
|
|
UlReleaseResource(&(pRequest->pHttpConn->Resource));
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlReceiveEntityBody(%p): Cleaning up request, status=0x%x\n",
|
|
pRequest, Status
|
|
));
|
|
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// is there any data to read? either
|
|
//
|
|
// 1) there were no entity chunks OR
|
|
//
|
|
// 2) there were and :
|
|
//
|
|
// 2b) we've are done parsing all of them AND
|
|
//
|
|
// 2c) we've read all we parsed
|
|
//
|
|
// 3) we have encountered an error when parsing
|
|
// the entity body. Therefore parser was in the
|
|
// error state.
|
|
//
|
|
|
|
if ((pRequest->ContentLength == 0 && pRequest->Chunked == 0) ||
|
|
(pRequest->ParseState > ParseEntityBodyState &&
|
|
pRequest->ChunkBytesRead == pRequest->ChunkBytesParsed) ||
|
|
(pRequest->ParseState == ParseErrorState)
|
|
)
|
|
{
|
|
if ( pRequest->ParseState == ParseErrorState )
|
|
{
|
|
//
|
|
// Do not route up the entity body if we have
|
|
// encountered an error when parsing it.
|
|
//
|
|
|
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// nope, complete right away
|
|
//
|
|
|
|
Status = STATUS_END_OF_FILE;
|
|
}
|
|
|
|
UlReleaseResource(&(pRequest->pHttpConn->Resource));
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlReceiveEntityBody(%p): No data to read, status=0x%x\n",
|
|
pRequest, Status
|
|
));
|
|
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// queue the irp
|
|
//
|
|
|
|
IoMarkIrpPending(pIrp);
|
|
|
|
//
|
|
// handle 100 continue message reponses
|
|
//
|
|
|
|
if ( HTTP_GREATER_EQUAL_VERSION(pRequest->Version, 1, 1) )
|
|
{
|
|
//
|
|
// if this is a HTTP/1.1 PUT or POST request,
|
|
// send "100 Continue" response.
|
|
//
|
|
|
|
if ( (HttpVerbPUT == pRequest->Verb) ||
|
|
(HttpVerbPOST == pRequest->Verb) )
|
|
{
|
|
//
|
|
// Only send continue once...
|
|
//
|
|
|
|
if ( (0 == pRequest->SentContinue) &&
|
|
(0 == pRequest->SentResponse) )
|
|
{
|
|
ULONG BytesSent;
|
|
|
|
BytesSent = UlSendSimpleStatus(pRequest, UlStatusContinue);
|
|
pRequest->SentContinue = 1;
|
|
|
|
// Update the server to client bytes sent.
|
|
// The logging & perf counters will use it.
|
|
|
|
pRequest->BytesSent += BytesSent;
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlReceiveEntityBody(%p): sent \"100 Continue\", "
|
|
"bytes sent = %d\n",
|
|
pRequest, pRequest->BytesSent
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// give it a pointer to the request object
|
|
//
|
|
|
|
pIrpSp->Parameters.DeviceIoControl.Type3InputBuffer = pRequest;
|
|
|
|
UL_REFERENCE_INTERNAL_REQUEST(pRequest);
|
|
|
|
//
|
|
// set to these to null just in case the cancel routine runs
|
|
//
|
|
|
|
pIrp->Tail.Overlay.ListEntry.Flink = NULL;
|
|
pIrp->Tail.Overlay.ListEntry.Blink = NULL;
|
|
|
|
IoSetCancelRoutine(pIrp, &UlpCancelEntityBody);
|
|
|
|
//
|
|
// cancelled?
|
|
//
|
|
|
|
if (pIrp->Cancel)
|
|
{
|
|
//
|
|
// darn it, need to make sure the irp get's completed
|
|
//
|
|
|
|
if (IoSetCancelRoutine( pIrp, NULL ) != NULL)
|
|
{
|
|
//
|
|
// we are in charge of completion, IoCancelIrp didn't
|
|
// see our cancel routine (and won't). ioctl wrapper
|
|
// will complete it
|
|
//
|
|
|
|
UlReleaseResource(&(pRequest->pHttpConn->Resource));
|
|
|
|
//
|
|
// let go of the request reference
|
|
//
|
|
|
|
UL_DEREFERENCE_INTERNAL_REQUEST(
|
|
(PUL_INTERNAL_REQUEST)(pIrpSp->Parameters.DeviceIoControl.Type3InputBuffer)
|
|
);
|
|
|
|
pIrpSp->Parameters.DeviceIoControl.Type3InputBuffer = NULL;
|
|
|
|
pIrp->IoStatus.Information = 0;
|
|
|
|
UlUnmarkIrpPending( pIrp );
|
|
Status = STATUS_CANCELLED;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// our cancel routine will run and complete the irp,
|
|
// don't touch it
|
|
//
|
|
|
|
//
|
|
// STATUS_PENDING will cause the ioctl wrapper to
|
|
// not complete (or touch in any way) the irp
|
|
//
|
|
|
|
Status = STATUS_PENDING;
|
|
|
|
UlReleaseResource(&(pRequest->pHttpConn->Resource));
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// now we are safe to queue it
|
|
//
|
|
|
|
//
|
|
// queue the irp on the request
|
|
//
|
|
|
|
InsertHeadList(&(pRequest->IrpHead), &(pIrp->Tail.Overlay.ListEntry));
|
|
|
|
//
|
|
// Remember IrpHead has been touched so we need to call UlCancelRequestIo.
|
|
//
|
|
|
|
pRequest->IrpsPending = TRUE;
|
|
|
|
//
|
|
// all done
|
|
//
|
|
|
|
Status = STATUS_PENDING;
|
|
|
|
//
|
|
// Process the buffer queue (which might process the irp we just queued)
|
|
//
|
|
|
|
ASSERT( UlpIsValidRequestBufferList( pRequest->pHttpConn ) );
|
|
UlpProcessBufferQueue(pRequest);
|
|
|
|
UlReleaseResource(&(pRequest->pHttpConn->Resource));
|
|
|
|
//
|
|
// all done
|
|
//
|
|
|
|
end:
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlReceiveEntityBody(%p): returning status=0x%x\n",
|
|
pRequest, Status
|
|
));
|
|
|
|
RETURN(Status);
|
|
|
|
} // UlReceiveEntityBody
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
processes the pending irp queue and buffered body. copying data from the
|
|
buffers into the irps, releasing the buffers and completing the irps
|
|
|
|
you must already have the resource locked exclusive on the request prior
|
|
to calling this procedure.
|
|
|
|
Arguments:
|
|
|
|
pRequest - the request which we should process.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpProcessBufferQueue(
|
|
PUL_INTERNAL_REQUEST pRequest
|
|
)
|
|
{
|
|
ULONG OutputBufferLength;
|
|
PUCHAR pOutputBuffer;
|
|
PIRP pIrp;
|
|
PIO_STACK_LOCATION pIrpSp;
|
|
PLIST_ENTRY pEntry;
|
|
ULONG BytesToCopy;
|
|
ULONG BufferLength;
|
|
ULONG TotalBytesConsumed;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
ASSERT(UlDbgResourceOwnedExclusive(&pRequest->pHttpConn->Resource));
|
|
|
|
//
|
|
// now let's pop some buffers off the list
|
|
//
|
|
|
|
OutputBufferLength = 0;
|
|
TotalBytesConsumed = 0;
|
|
pIrp = NULL;
|
|
|
|
while (TRUE)
|
|
{
|
|
//
|
|
// is there any more entity body to read?
|
|
//
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(req=%p): "
|
|
"ParseState=%d, ChunkBytesRead=%d, ChunkBytesParsed=%d, "
|
|
"ChunkBuffer=%p\n",
|
|
pRequest, pRequest->ParseState,
|
|
pRequest->ChunkBytesRead, pRequest->ChunkBytesParsed,
|
|
pRequest->pChunkBuffer
|
|
));
|
|
|
|
if (pRequest->ParseState > ParseEntityBodyState &&
|
|
pRequest->ChunkBytesRead == pRequest->ChunkBytesParsed)
|
|
{
|
|
//
|
|
// nope, let's loop through all of the irp's, completing 'em
|
|
//
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(req=%p): no more EntityBody\n",
|
|
pRequest
|
|
));
|
|
|
|
BufferLength = 0;
|
|
}
|
|
|
|
//
|
|
// Do we have data ready to be read ?
|
|
//
|
|
// we have not recieved the first chunk from the parser? OR
|
|
// the parser has not parsed any more data, we've read it all so far
|
|
//
|
|
|
|
else if (pRequest->pChunkBuffer == NULL ||
|
|
pRequest->ChunkBytesRead == pRequest->ChunkBytesParsed)
|
|
{
|
|
//
|
|
// Wait for the parser .... UlpParseNextRequest will call
|
|
// this function when it has seen more .
|
|
//
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(req=%p): pChunkBuffer=%p, "
|
|
"ChunkBytesRead=0x%I64x, ChunkBytesParsed=0x%I64x.\n",
|
|
pRequest, pRequest->pChunkBuffer,
|
|
pRequest->ChunkBytesRead, pRequest->ChunkBytesParsed
|
|
));
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// We are ready to process !
|
|
//
|
|
|
|
else
|
|
{
|
|
BufferLength = pRequest->pChunkBuffer->UsedBytes -
|
|
DIFF(pRequest->pChunkLocation -
|
|
pRequest->pChunkBuffer->pBuffer);
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(req=%p): BufferLength=0x%x\n",
|
|
pRequest, BufferLength
|
|
));
|
|
}
|
|
|
|
//
|
|
// do we need a fresh irp?
|
|
//
|
|
|
|
if (OutputBufferLength == 0)
|
|
{
|
|
|
|
//
|
|
// need to complete the current in-used irp first
|
|
//
|
|
|
|
if (pIrp != NULL)
|
|
{
|
|
//
|
|
// let go of the request reference
|
|
//
|
|
|
|
UL_DEREFERENCE_INTERNAL_REQUEST(
|
|
(PUL_INTERNAL_REQUEST)pIrpSp->Parameters.
|
|
DeviceIoControl.Type3InputBuffer
|
|
);
|
|
|
|
pIrpSp->Parameters.DeviceIoControl.Type3InputBuffer = NULL;
|
|
|
|
//
|
|
// complete the used irp
|
|
//
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(req=%p): "
|
|
"completing Irp %p, Status=0x%x\n",
|
|
pRequest, pIrp,
|
|
pIrp->IoStatus.Status
|
|
));
|
|
|
|
UlCompleteRequest(pIrp, g_UlPriorityBoost);
|
|
|
|
pIrp = NULL;
|
|
|
|
}
|
|
|
|
//
|
|
// dequeue an irp
|
|
//
|
|
|
|
while (IsListEmpty(&(pRequest->IrpHead)) == FALSE)
|
|
{
|
|
pEntry = RemoveTailList(&(pRequest->IrpHead));
|
|
pEntry->Blink = pEntry->Flink = NULL;
|
|
|
|
pIrp = CONTAINING_RECORD(pEntry, IRP, Tail.Overlay.ListEntry);
|
|
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
|
|
|
|
//
|
|
// pop the cancel routine
|
|
//
|
|
|
|
if (IoSetCancelRoutine(pIrp, NULL) == NULL)
|
|
{
|
|
//
|
|
// IoCancelIrp pop'd it first
|
|
//
|
|
// ok to just ignore this irp, it's been pop'd off the
|
|
// queue and will be completed in the cancel routine.
|
|
//
|
|
// keep looking for a irp to use
|
|
//
|
|
|
|
}
|
|
else if (pIrp->Cancel)
|
|
{
|
|
//
|
|
// we pop'd it first. but the irp is being cancelled
|
|
// and our cancel routine will never run. lets be
|
|
// nice and complete the irp now (vs. using it
|
|
// then completing it - which would also be legal).
|
|
//
|
|
|
|
//
|
|
// let go of the request reference
|
|
//
|
|
|
|
UL_DEREFERENCE_INTERNAL_REQUEST(
|
|
(PUL_INTERNAL_REQUEST)pIrpSp->Parameters.
|
|
DeviceIoControl.Type3InputBuffer
|
|
);
|
|
|
|
pIrpSp->Parameters.DeviceIoControl.Type3InputBuffer = NULL;
|
|
|
|
//
|
|
// complete the irp
|
|
//
|
|
|
|
pIrp->IoStatus.Status = STATUS_CANCELLED;
|
|
pIrp->IoStatus.Information = 0;
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(req=%p): "
|
|
"completing cancelled Irp %p, Status=0x%x\n",
|
|
pRequest, pIrp,
|
|
pIrp->IoStatus.Status
|
|
));
|
|
|
|
UlCompleteRequest(pIrp, g_UlPriorityBoost);
|
|
|
|
pIrp = NULL;
|
|
}
|
|
else
|
|
{
|
|
|
|
//
|
|
// we are free to use this irp !
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
} // while (IsListEmpty(&(pRequest->IrpHead)) == FALSE)
|
|
|
|
//
|
|
// did we get an irp?
|
|
//
|
|
|
|
if (pIrp == NULL)
|
|
{
|
|
//
|
|
// stop looping
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(req=%p): found Irp %p\n",
|
|
pRequest, pIrp
|
|
));
|
|
|
|
//
|
|
// CODEWORK: we could release the request now.
|
|
//
|
|
|
|
OutputBufferLength =
|
|
pIrpSp->Parameters.DeviceIoControl.OutputBufferLength;
|
|
|
|
pOutputBuffer = (PUCHAR) MmGetSystemAddressForMdlSafe(
|
|
pIrp->MdlAddress,
|
|
NormalPagePriority
|
|
);
|
|
|
|
if ( pOutputBuffer == NULL )
|
|
{
|
|
pIrp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
pIrp->IoStatus.Information = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// fill in the IO_STATUS_BLOCK
|
|
//
|
|
|
|
pIrp->IoStatus.Status = STATUS_SUCCESS;
|
|
pIrp->IoStatus.Information = 0;
|
|
|
|
} // if (OutputBufferLength == 0)
|
|
|
|
|
|
UlTrace(
|
|
HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(req=%p): pChunkBuffer=%p(%d)\n",
|
|
pRequest,
|
|
pRequest->pChunkBuffer,
|
|
pRequest->pChunkBuffer == NULL ?
|
|
-1 :
|
|
pRequest->pChunkBuffer->BufferNumber
|
|
)
|
|
);
|
|
|
|
//
|
|
// how much of it can we copy? min of both buffer sizes
|
|
// and the chunk size
|
|
//
|
|
|
|
BytesToCopy = MIN(BufferLength, OutputBufferLength);
|
|
BytesToCopy = (ULONG)(MIN(
|
|
(ULONGLONG)(BytesToCopy),
|
|
pRequest->ChunkBytesToRead
|
|
));
|
|
|
|
if (BytesToCopy > 0)
|
|
{
|
|
ASSERT(pRequest->pChunkBuffer != NULL) ;
|
|
|
|
//
|
|
// copy the buffer
|
|
//
|
|
|
|
RtlCopyMemory(
|
|
pOutputBuffer,
|
|
pRequest->pChunkLocation,
|
|
BytesToCopy
|
|
);
|
|
|
|
IF_DEBUG2(HTTP_IO, VERBOSE)
|
|
{
|
|
UlTraceVerbose( HTTP_IO, (
|
|
">>>> http!UlpProcessBufferQueue(req=%p): %d bytes\n",
|
|
pRequest, BytesToCopy
|
|
));
|
|
|
|
UlDbgPrettyPrintBuffer(pOutputBuffer, BytesToCopy);
|
|
|
|
UlTraceVerbose( HTTP_IO, ("<<<<\n"));
|
|
}
|
|
|
|
pRequest->pChunkLocation += BytesToCopy;
|
|
BufferLength -= BytesToCopy;
|
|
|
|
pRequest->ChunkBytesToRead -= BytesToCopy;
|
|
pRequest->ChunkBytesRead += BytesToCopy;
|
|
|
|
pOutputBuffer += BytesToCopy;
|
|
OutputBufferLength -= BytesToCopy;
|
|
|
|
pIrp->IoStatus.Information += BytesToCopy;
|
|
|
|
TotalBytesConsumed += BytesToCopy;
|
|
|
|
}
|
|
else
|
|
{
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(req=%p): BytesToCopy=0\n",
|
|
pRequest
|
|
));
|
|
}
|
|
|
|
|
|
//
|
|
// are we all done with body?
|
|
|
|
//
|
|
// when the parser is all done, and we caught up with the parser
|
|
// we are all done.
|
|
//
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(req=%p): "
|
|
"ParseState=%d, ChunkBytesRead=%d, BytesParsed=%d, "
|
|
"BytesToRead=%d, BufferLength=%d\n",
|
|
pRequest, pRequest->ParseState,
|
|
pRequest->ChunkBytesRead, pRequest->ChunkBytesParsed,
|
|
pRequest->ChunkBytesToRead, BufferLength
|
|
));
|
|
|
|
if (pRequest->ParseState > ParseEntityBodyState &&
|
|
pRequest->ChunkBytesRead == pRequest->ChunkBytesParsed)
|
|
{
|
|
//
|
|
// we are done buffering, mark this irp's return status
|
|
// if we didn't copy any data into it
|
|
//
|
|
|
|
if (pIrp->IoStatus.Information == 0)
|
|
{
|
|
pIrp->IoStatus.Status = STATUS_END_OF_FILE;
|
|
}
|
|
|
|
//
|
|
// force it to complete at the top of the loop
|
|
//
|
|
|
|
OutputBufferLength = 0;
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(req=%p): "
|
|
"set Irp %p status to EOF\n",
|
|
pRequest, pIrp
|
|
));
|
|
}
|
|
|
|
//
|
|
// need to do buffer management? three cases to worry about:
|
|
//
|
|
// 1) consumed the buffer, but more chunk bytes exist
|
|
//
|
|
// 2) consumed the buffer, and no more chunk bytes exist
|
|
//
|
|
// 3) did not consume the buffer, but no more chunk bytes exist
|
|
//
|
|
|
|
else if (BufferLength == 0)
|
|
{
|
|
PUL_REQUEST_BUFFER pNewBuffer;
|
|
PLIST_ENTRY pNextEntry;
|
|
|
|
//
|
|
// consumed the buffer, has the parser already seen another?
|
|
//
|
|
|
|
//
|
|
// end of the list?
|
|
//
|
|
|
|
if (pRequest->pChunkBuffer->ListEntry.Flink !=
|
|
&(pRequest->pHttpConn->BufferHead))
|
|
{
|
|
pNewBuffer = CONTAINING_RECORD(
|
|
pRequest->pChunkBuffer->ListEntry.Flink,
|
|
UL_REQUEST_BUFFER,
|
|
ListEntry
|
|
);
|
|
|
|
ASSERT( UL_IS_VALID_REQUEST_BUFFER(pNewBuffer) );
|
|
|
|
//
|
|
// There had better be some bytes in this buffer
|
|
//
|
|
ASSERT( 0 != pNewBuffer->UsedBytes );
|
|
|
|
}
|
|
else
|
|
{
|
|
pNewBuffer = NULL;
|
|
}
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(req=%p): "
|
|
"pNewBuffer = %p, %d parsed bytes\n",
|
|
pRequest, pNewBuffer, (pNewBuffer ? pNewBuffer->ParsedBytes : 0)
|
|
));
|
|
|
|
//
|
|
// the flink buffer is a "next buffer" (the list is circular)
|
|
// AND that buffer has been consumed by the parser,
|
|
//
|
|
// then we too can move on to it and start consuming.
|
|
//
|
|
|
|
if (pNewBuffer != NULL && pNewBuffer->ParsedBytes > 0)
|
|
{
|
|
PUL_REQUEST_BUFFER pOldBuffer;
|
|
|
|
//
|
|
// remember the old buffer
|
|
//
|
|
|
|
pOldBuffer = pRequest->pChunkBuffer;
|
|
|
|
ASSERT(pNewBuffer->BufferNumber > pOldBuffer->BufferNumber);
|
|
|
|
//
|
|
// use it the new one
|
|
//
|
|
|
|
pRequest->pChunkBuffer = pNewBuffer;
|
|
ASSERT( UL_IS_VALID_REQUEST_BUFFER(pRequest->pChunkBuffer) );
|
|
|
|
//
|
|
// update our current location in the buffer and record
|
|
// its length
|
|
//
|
|
|
|
pRequest->pChunkLocation = pRequest->pChunkBuffer->pBuffer;
|
|
|
|
BufferLength = pRequest->pChunkBuffer->UsedBytes;
|
|
|
|
//
|
|
// did the chunk end on that buffer boundary and there are
|
|
// more chunks ?
|
|
//
|
|
|
|
if (pRequest->ChunkBytesToRead == 0)
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG BytesTaken = 0L;
|
|
|
|
//
|
|
// we know there are more chunk buffers,
|
|
// thus we must be chunk encoded
|
|
//
|
|
|
|
ASSERT(pRequest->Chunked == 1);
|
|
|
|
//
|
|
// the chunk length is not allowed to span buffers,
|
|
// let's parse it
|
|
//
|
|
|
|
Status = UlParseChunkLength(
|
|
pRequest,
|
|
pRequest->pChunkLocation,
|
|
BufferLength,
|
|
&BytesTaken,
|
|
&(pRequest->ChunkBytesToRead)
|
|
);
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(pReq=%p): Status=0x%x. "
|
|
"Chunk length (a): %d bytes taken, "
|
|
"0x%I64x bytes to read.\n",
|
|
pRequest, Status,
|
|
BytesTaken, pRequest->ChunkBytesToRead
|
|
));
|
|
|
|
//
|
|
// this can't fail, the only failure case from
|
|
// ParseChunkLength spanning buffers, which the parser
|
|
// would have fixed in HandleRequest
|
|
//
|
|
|
|
ASSERT(NT_SUCCESS(Status) && BytesTaken > 0);
|
|
ASSERT(pRequest->ChunkBytesToRead > 0);
|
|
|
|
ASSERT(BytesTaken <= BufferLength);
|
|
|
|
pRequest->pChunkLocation += BytesTaken;
|
|
BufferLength -= BytesTaken;
|
|
|
|
} // if (pRequest->ChunkBytesToRead == 0)
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(pRequest = %p)\n"
|
|
" finished with pOldBuffer = %p(%d)\n"
|
|
" moved on to pChunkBuffer = %p(%d)\n"
|
|
" pConn(%p)->pCurrentBuffer = %p(%d)\n"
|
|
" pRequest->pLastHeaderBuffer = %p(%d)\n",
|
|
pRequest,
|
|
pOldBuffer,
|
|
pOldBuffer->BufferNumber,
|
|
pRequest->pChunkBuffer,
|
|
pRequest->pChunkBuffer ? pRequest->pChunkBuffer->BufferNumber : -1,
|
|
pRequest->pHttpConn,
|
|
pRequest->pHttpConn->pCurrentBuffer,
|
|
pRequest->pHttpConn->pCurrentBuffer->BufferNumber,
|
|
pRequest->pLastHeaderBuffer,
|
|
pRequest->pLastHeaderBuffer->BufferNumber
|
|
));
|
|
|
|
//
|
|
// let the old buffer go if it doesn't contain any header
|
|
// data. We're done with it.
|
|
//
|
|
|
|
if (pOldBuffer != pRequest->pLastHeaderBuffer)
|
|
{
|
|
//
|
|
// the connection should be all done using this, the only
|
|
// way we get here is if the parser has seen this buffer
|
|
// thus pCurrentBuffer points at least to pNewBuffer.
|
|
//
|
|
|
|
ASSERT(pRequest->pHttpConn->pCurrentBuffer != pOldBuffer);
|
|
|
|
UlFreeRequestBuffer(pOldBuffer);
|
|
pOldBuffer = NULL;
|
|
}
|
|
|
|
} // if (pNewBuffer != NULL && pNewBuffer->ParsedBytes > 0)
|
|
|
|
} // else if (BufferLength == 0)
|
|
|
|
//
|
|
// ok, there's more bytes in the buffer, but how about the chunk?
|
|
//
|
|
|
|
//
|
|
// Have we taken all of the current chunk?
|
|
//
|
|
|
|
else if (pRequest->ChunkBytesToRead == 0)
|
|
{
|
|
|
|
//
|
|
// Are we are still behind the parser?
|
|
//
|
|
|
|
if (pRequest->ChunkBytesRead < pRequest->ChunkBytesParsed)
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG BytesTaken;
|
|
|
|
ASSERT(pRequest->Chunked == 1);
|
|
|
|
//
|
|
// the chunk length is not allowed to span buffers,
|
|
// let's parse it
|
|
//
|
|
|
|
Status = UlParseChunkLength(
|
|
pRequest,
|
|
pRequest->pChunkLocation,
|
|
BufferLength,
|
|
&BytesTaken,
|
|
&(pRequest->ChunkBytesToRead)
|
|
);
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(pRequest=%p): Status=0x%x. "
|
|
"chunk length (b): %d bytes taken, "
|
|
"0x%I64x bytes to read.\n",
|
|
pRequest, Status,
|
|
BytesTaken, pRequest->ChunkBytesToRead
|
|
));
|
|
|
|
//
|
|
// this can't fail, the only failure case from
|
|
// ParseChunkLength spanning buffers, which the parser
|
|
// would have fixed in HandleRequest
|
|
//
|
|
|
|
ASSERT(NT_SUCCESS(Status) && BytesTaken > 0);
|
|
ASSERT(pRequest->ChunkBytesToRead > 0);
|
|
|
|
ASSERT(BytesTaken <= BufferLength);
|
|
|
|
pRequest->pChunkLocation += BytesTaken;
|
|
BufferLength -= BytesTaken;
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Need to wait for the parser to parse more
|
|
//
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(pRequest = %p): "
|
|
"need to parse more\n",
|
|
pRequest
|
|
));
|
|
|
|
break;
|
|
}
|
|
} // else if (pRequest->ChunkBytesToRead == 0)
|
|
|
|
|
|
//
|
|
// next irp or buffer
|
|
//
|
|
|
|
} // while (TRUE)
|
|
|
|
//
|
|
// complete the irp we put partial data in
|
|
//
|
|
|
|
if (pIrp != NULL)
|
|
{
|
|
|
|
//
|
|
// let go of the request reference
|
|
//
|
|
|
|
UL_DEREFERENCE_INTERNAL_REQUEST(
|
|
(PUL_INTERNAL_REQUEST)pIrpSp->Parameters.DeviceIoControl.Type3InputBuffer
|
|
);
|
|
|
|
pIrpSp->Parameters.DeviceIoControl.Type3InputBuffer = NULL;
|
|
|
|
//
|
|
// complete the used irp
|
|
//
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(req=%p): "
|
|
"completing used Irp %p, Status=0x%x\n",
|
|
pRequest,
|
|
pIrp, pIrp->IoStatus.Status
|
|
));
|
|
|
|
UlCompleteRequest(pIrp, g_UlPriorityBoost);
|
|
|
|
pIrp = NULL;
|
|
}
|
|
else
|
|
{
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(req=%p): no irp with partial data\n",
|
|
pRequest
|
|
));
|
|
}
|
|
|
|
//
|
|
// Tell the connection how many bytes we consumed. This
|
|
// may allow us to restart receive indications.
|
|
//
|
|
|
|
UlTraceVerbose(HTTP_IO, (
|
|
"http!UlpProcessBufferQueue(req=%p, httpconn=%p): "
|
|
"%u bytes consumed\n",
|
|
pRequest, pRequest->pHttpConn, TotalBytesConsumed
|
|
));
|
|
|
|
if (TotalBytesConsumed)
|
|
{
|
|
UlpConsumeBytesFromConnection(
|
|
pRequest->pHttpConn,
|
|
TotalBytesConsumed
|
|
);
|
|
}
|
|
|
|
//
|
|
// all done
|
|
//
|
|
|
|
} // UlpProcessBufferQueue
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This function subtracts from the total number of bytes currently buffered
|
|
on the UL_HTTP_CONNECTION object. If there are bytes from the transport
|
|
that we previously refused, this function may issue a receive to restart
|
|
the flow of data from TCP.
|
|
|
|
Arguments:
|
|
|
|
pConnection - the connection on which the bytes came in
|
|
BytesCount - the number of bytes consumed
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpConsumeBytesFromConnection(
|
|
IN PUL_HTTP_CONNECTION pConnection,
|
|
IN ULONG ByteCount
|
|
)
|
|
{
|
|
KIRQL oldIrql;
|
|
ULONG SpaceAvailable;
|
|
ULONG BytesToRead;
|
|
BOOLEAN IssueReadIrp;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pConnection));
|
|
ASSERT(ByteCount != 0);
|
|
|
|
//
|
|
// Set up locals.
|
|
//
|
|
|
|
BytesToRead = 0;
|
|
IssueReadIrp = FALSE;
|
|
|
|
|
|
//
|
|
// Consume the bytes.
|
|
//
|
|
|
|
UlAcquireSpinLock(
|
|
&pConnection->BufferingInfo.BufferingSpinLock,
|
|
&oldIrql
|
|
);
|
|
|
|
ASSERT(ByteCount <= pConnection->BufferingInfo.BytesBuffered);
|
|
if (ByteCount > pConnection->BufferingInfo.BytesBuffered)
|
|
{
|
|
//
|
|
// This should never happen, but if it does then make sure
|
|
// we don't subtract more BufferedBytes than we have.
|
|
//
|
|
ByteCount = pConnection->BufferingInfo.BytesBuffered;
|
|
}
|
|
|
|
//
|
|
// Compute the new number of buffered bytes.
|
|
//
|
|
|
|
pConnection->BufferingInfo.BytesBuffered -= ByteCount;
|
|
|
|
//
|
|
// Trace.
|
|
//
|
|
if (g_UlMaxBufferedBytes > pConnection->BufferingInfo.BytesBuffered)
|
|
{
|
|
SpaceAvailable = g_UlMaxBufferedBytes
|
|
- pConnection->BufferingInfo.BytesBuffered;
|
|
}
|
|
else
|
|
{
|
|
SpaceAvailable = 0;
|
|
}
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"UlpConsumeBytesFromConnection(pconn = %p, bytes = %ld)\n"
|
|
" Space = %ld, buffered %ld, not taken = %ld\n",
|
|
pConnection,
|
|
ByteCount,
|
|
SpaceAvailable,
|
|
pConnection->BufferingInfo.BytesBuffered,
|
|
pConnection->BufferingInfo.TransportBytesNotTaken
|
|
));
|
|
|
|
|
|
//
|
|
// See if we need to issue a receive to restart the flow of data.
|
|
//
|
|
|
|
if ((SpaceAvailable > 0) &&
|
|
(pConnection->BufferingInfo.TransportBytesNotTaken > 0) &&
|
|
(!pConnection->BufferingInfo.ReadIrpPending))
|
|
{
|
|
|
|
|
|
//
|
|
// Remember that we issued an IRP.
|
|
//
|
|
|
|
pConnection->BufferingInfo.ReadIrpPending = TRUE;
|
|
|
|
//
|
|
// Issue the Read IRP outside the spinlock.
|
|
//
|
|
|
|
IssueReadIrp = TRUE;
|
|
BytesToRead = pConnection->BufferingInfo.TransportBytesNotTaken;
|
|
|
|
//
|
|
// Don't read more bytes than we want to buffer.
|
|
//
|
|
|
|
BytesToRead = MIN(BytesToRead, SpaceAvailable);
|
|
}
|
|
|
|
UlReleaseSpinLock(
|
|
&pConnection->BufferingInfo.BufferingSpinLock,
|
|
oldIrql
|
|
);
|
|
|
|
if (IssueReadIrp)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_REQUEST_BUFFER pRequestBuffer;
|
|
|
|
//
|
|
// get a new request buffer, but initialize it
|
|
// with a bogus number. We have to allocate it now,
|
|
// but we want to set the number when the data
|
|
// arrives in the completion routine (like UlHttpReceive
|
|
// does) to avoid synchronization trouble.
|
|
//
|
|
|
|
pRequestBuffer = UlCreateRequestBuffer(
|
|
BytesToRead,
|
|
(ULONG)-1 // BufferNumber
|
|
);
|
|
|
|
|
|
if (pRequestBuffer)
|
|
{
|
|
|
|
//
|
|
// Add a backpointer to the connection.
|
|
//
|
|
|
|
pRequestBuffer->pConnection = pConnection;
|
|
|
|
//
|
|
// We've got the buffer. Issue the receive.
|
|
// Reference the connection so it doesn't
|
|
// go away while we're waiting. The reference
|
|
// will be removed after the completion.
|
|
//
|
|
|
|
UL_REFERENCE_HTTP_CONNECTION( pConnection );
|
|
|
|
Status = UlReceiveData(
|
|
pConnection->pConnection,
|
|
pRequestBuffer->pBuffer,
|
|
BytesToRead,
|
|
&UlpRestartHttpReceive,
|
|
pRequestBuffer
|
|
);
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We're out of memory. Nothing we can do.
|
|
//
|
|
Status = STATUS_NO_MEMORY;
|
|
|
|
}
|
|
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Couldn't issue the read. Close the connection.
|
|
//
|
|
|
|
UlCloseConnection(
|
|
pConnection->pConnection,
|
|
TRUE, // AbortiveDisconnect
|
|
NULL, // pCompletionRoutine
|
|
NULL // pCompletionContext
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
} // UlpConsumeBytesFromConnection
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Once a connection get disconnected gracefully and there's still unreceived
|
|
data on it. We have to drain this extra bytes to expect the tdi disconnect
|
|
indication. We have to drain this data because we need the disconnect indi
|
|
cation to clean up the connection. And we cannot simply abort it. If we do
|
|
not do this we will leak this connection object and finally it will cause
|
|
shutdown failures.
|
|
|
|
Arguments:
|
|
|
|
pConnection - stuck connection we have to drain out to complete the
|
|
gracefull disconnect.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpDiscardBytesFromConnection(
|
|
IN PUL_HTTP_CONNECTION pConnection
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_REQUEST_BUFFER pRequestBuffer;
|
|
KIRQL OldIrql;
|
|
ULONG BytesToRead;
|
|
|
|
//
|
|
// Sanity check and init
|
|
//
|
|
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pConnection));
|
|
|
|
Status = STATUS_SUCCESS;
|
|
BytesToRead = 0;
|
|
pRequestBuffer = NULL;
|
|
|
|
//
|
|
// Mark the drain state and restart receive if necessary.
|
|
//
|
|
|
|
UlAcquireSpinLock(
|
|
&pConnection->BufferingInfo.BufferingSpinLock,
|
|
&OldIrql
|
|
);
|
|
|
|
pConnection->BufferingInfo.DrainAfterDisconnect = TRUE;
|
|
|
|
//
|
|
// Even if ReadIrp is pending, it does not matter as we will just discard
|
|
// the indications from now on. We indicate this by marking the above flag
|
|
//
|
|
|
|
if ( pConnection->BufferingInfo.ReadIrpPending ||
|
|
pConnection->BufferingInfo.TransportBytesNotTaken == 0
|
|
)
|
|
{
|
|
UlReleaseSpinLock(
|
|
&pConnection->BufferingInfo.BufferingSpinLock,
|
|
OldIrql
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// As soon as we enter this "DrainAfterDisconnect" state we will not be
|
|
// processing and inserting request buffers anymore. For any new receive
|
|
// indications, we will just mark the whole available data as taken and
|
|
// don't do nothing about it.
|
|
//
|
|
|
|
WRITE_REF_TRACE_LOG2(
|
|
g_pTdiTraceLog,
|
|
pConnection->pConnection->pTraceLog,
|
|
REF_ACTION_DRAIN_UL_CONNECTION_START,
|
|
pConnection->pConnection->ReferenceCount,
|
|
pConnection->pConnection,
|
|
__FILE__,
|
|
__LINE__
|
|
);
|
|
|
|
//
|
|
// We need to issue a receive to restart the flow of data again. Therefore
|
|
// we can drain.
|
|
//
|
|
|
|
pConnection->BufferingInfo.ReadIrpPending = TRUE;
|
|
|
|
BytesToRead = pConnection->BufferingInfo.TransportBytesNotTaken;
|
|
|
|
UlReleaseSpinLock(
|
|
&pConnection->BufferingInfo.BufferingSpinLock,
|
|
OldIrql
|
|
);
|
|
|
|
//
|
|
// Do not try to drain more than g_UlMaxBufferedBytes. If necessary we will
|
|
// issue another receive later.
|
|
//
|
|
|
|
BytesToRead = MIN( BytesToRead, g_UlMaxBufferedBytes );
|
|
|
|
UlTrace(HTTP_IO,(
|
|
"UlpDiscardBytesFromConnection: pConnection (%p) consuming %d\n",
|
|
pConnection,
|
|
BytesToRead
|
|
));
|
|
|
|
//
|
|
// Issue the Read IRP outside the spinlock. Issue the receive. Reference
|
|
// the connection so it doesn't go away while we're waiting. The reference
|
|
// will be removed after the completion.
|
|
//
|
|
|
|
pRequestBuffer = UlCreateRequestBuffer( BytesToRead,
|
|
(ULONG)-1
|
|
);
|
|
if (pRequestBuffer)
|
|
{
|
|
//
|
|
// We won't use this buffer but simply discard it when completion happens.
|
|
// Lets still set the pConnection so that completion function doesn't
|
|
// complain
|
|
//
|
|
|
|
pRequestBuffer->pConnection = pConnection;
|
|
|
|
UL_REFERENCE_HTTP_CONNECTION( pConnection );
|
|
|
|
Status = UlReceiveData(pConnection->pConnection,
|
|
pRequestBuffer->pBuffer,
|
|
BytesToRead,
|
|
&UlpRestartHttpReceive,
|
|
pRequestBuffer
|
|
);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We're out of memory. Nothing we can do.
|
|
//
|
|
|
|
Status = STATUS_NO_MEMORY;
|
|
}
|
|
|
|
if ( !NT_SUCCESS(Status) )
|
|
{
|
|
//
|
|
// Couldn't issue the receive. ABORT the connection.
|
|
//
|
|
// CODEWORK: We need a real abort here. If connection is
|
|
// previously gracefully disconnected and a fatal failure
|
|
// happened during drain after disconnect. This abort will
|
|
// be discarded by the Close handler. We have to provide a
|
|
// way to do a forceful abort here.
|
|
//
|
|
|
|
UlCloseConnection(
|
|
pConnection->pConnection,
|
|
TRUE, // Abortive
|
|
NULL, // pCompletionRoutine
|
|
NULL // pCompletionContext
|
|
);
|
|
}
|
|
|
|
} // UlpDiscardBytesFromConnection
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Called on a read completion. This happens when we had stopped
|
|
data indications for some reason and then restarted them. This
|
|
function mirrors UlHttpReceive.
|
|
|
|
Arguments:
|
|
|
|
pContext - pointer to the FilterRawRead IRP
|
|
Status - Status from UlReceiveData
|
|
Information - bytes transferred
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpRestartHttpReceive(
|
|
IN PVOID pContext,
|
|
IN NTSTATUS Status,
|
|
IN ULONG_PTR Information
|
|
)
|
|
{
|
|
PUL_HTTP_CONNECTION pConnection;
|
|
PUL_REQUEST_BUFFER pRequestBuffer;
|
|
KIRQL oldIrql;
|
|
ULONG TransportBytesNotTaken;
|
|
|
|
pRequestBuffer = (PUL_REQUEST_BUFFER)pContext;
|
|
ASSERT(UL_IS_VALID_REQUEST_BUFFER(pRequestBuffer));
|
|
|
|
pConnection = pRequestBuffer->pConnection;
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pConnection));
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// update our stats
|
|
//
|
|
UlAcquireSpinLock(
|
|
&pConnection->BufferingInfo.BufferingSpinLock,
|
|
&oldIrql
|
|
);
|
|
|
|
ASSERT(Information <= pConnection->BufferingInfo.TransportBytesNotTaken);
|
|
|
|
//
|
|
// We've now read they bytes from the transport and
|
|
// buffered them.
|
|
//
|
|
pConnection->BufferingInfo.TransportBytesNotTaken -= (ULONG)Information;
|
|
pConnection->BufferingInfo.BytesBuffered += (ULONG)Information;
|
|
|
|
pConnection->BufferingInfo.ReadIrpPending = FALSE;
|
|
|
|
if ( pConnection->BufferingInfo.DrainAfterDisconnect )
|
|
{
|
|
//
|
|
// Just free the memory and restart the receive if necessary.
|
|
//
|
|
|
|
TransportBytesNotTaken = pConnection->BufferingInfo.TransportBytesNotTaken;
|
|
|
|
UlReleaseSpinLock(
|
|
&pConnection->BufferingInfo.BufferingSpinLock,
|
|
oldIrql
|
|
);
|
|
|
|
WRITE_REF_TRACE_LOG2(
|
|
g_pTdiTraceLog,
|
|
pConnection->pConnection->pTraceLog,
|
|
REF_ACTION_DRAIN_UL_CONNECTION_RESTART,
|
|
pConnection->pConnection->ReferenceCount,
|
|
pConnection->pConnection,
|
|
__FILE__,
|
|
__LINE__
|
|
);
|
|
|
|
if ( TransportBytesNotTaken )
|
|
{
|
|
//
|
|
// Keep draining ...
|
|
//
|
|
|
|
UlpDiscardBytesFromConnection( pConnection );
|
|
}
|
|
|
|
UlTrace(HTTP_IO,(
|
|
"UlpRestartHttpReceive(d): pConnection (%p) drained %d remaining %d\n",
|
|
pConnection,
|
|
Information,
|
|
TransportBytesNotTaken
|
|
));
|
|
|
|
//
|
|
// Free the request buffer. And release our reference.
|
|
//
|
|
|
|
UlFreeRequestBuffer( pRequestBuffer );
|
|
UL_DEREFERENCE_HTTP_CONNECTION( pConnection );
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Get the request buffer ready to be inserted.
|
|
//
|
|
pRequestBuffer->UsedBytes = (ULONG) Information;
|
|
ASSERT( 0 != pRequestBuffer->UsedBytes );
|
|
|
|
pRequestBuffer->BufferNumber = pConnection->NextBufferNumber;
|
|
pConnection->NextBufferNumber++;
|
|
|
|
UlReleaseSpinLock(
|
|
&pConnection->BufferingInfo.BufferingSpinLock,
|
|
oldIrql
|
|
);
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"UlpRestartHttpReceive(pconn = %p, %x, %ld)\n"
|
|
" buffered = %ld, not taken = %ld\n",
|
|
pConnection,
|
|
Status,
|
|
(ULONG)Information,
|
|
pConnection->BufferingInfo.BytesBuffered,
|
|
pConnection->BufferingInfo.TransportBytesNotTaken
|
|
));
|
|
|
|
//
|
|
// queue it off
|
|
//
|
|
|
|
UlTrace( PARSER, (
|
|
"*** Request Buffer %p has connection %p\n",
|
|
pRequestBuffer,
|
|
pConnection
|
|
));
|
|
|
|
UL_QUEUE_WORK_ITEM(
|
|
&(pRequestBuffer->WorkItem),
|
|
&UlpHandleRequest
|
|
);
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
UlCloseConnection(
|
|
pConnection->pConnection,
|
|
TRUE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
//
|
|
// Release the reference we added to the connection
|
|
// before issuing the read. Normally this ref would
|
|
// be released in UlpHandleRequest.
|
|
//
|
|
UL_DEREFERENCE_HTTP_CONNECTION(pConnection);
|
|
|
|
//
|
|
// free the request buffer.
|
|
//
|
|
|
|
UlFreeRequestBuffer(pRequestBuffer);
|
|
}
|
|
} // UlpRestartHttpReceive
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
cancels the pending user mode irp which was to receive entity body. this
|
|
routine ALWAYS results in the irp being completed.
|
|
|
|
note: we queue off to cancel in order to process the cancellation at lower
|
|
irql.
|
|
|
|
Arguments:
|
|
|
|
pDeviceObject - the device object
|
|
|
|
pIrp - the irp to cancel
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpCancelEntityBody(
|
|
IN PDEVICE_OBJECT pDeviceObject,
|
|
IN PIRP pIrp
|
|
)
|
|
{
|
|
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
|
|
|
|
ASSERT(pIrp != NULL);
|
|
|
|
//
|
|
// release the cancel spinlock. This means the cancel routine
|
|
// must be the one completing the irp (to avoid the race of
|
|
// completion + reuse prior to the cancel routine running).
|
|
//
|
|
|
|
IoReleaseCancelSpinLock(pIrp->CancelIrql);
|
|
|
|
//
|
|
// queue the cancel to a worker to ensure passive irql.
|
|
//
|
|
|
|
UL_CALL_PASSIVE(
|
|
UL_WORK_ITEM_FROM_IRP( pIrp ),
|
|
&UlpCancelEntityBodyWorker
|
|
);
|
|
|
|
} // UlpCancelEntityBody
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Actually performs the cancel for the irp.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - the work item to process.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpCancelEntityBodyWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
PIRP pIrp;
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// grab the irp off the work item
|
|
//
|
|
|
|
pIrp = UL_WORK_ITEM_TO_IRP( pWorkItem );
|
|
|
|
ASSERT(IS_VALID_IRP(pIrp));
|
|
|
|
//
|
|
// grab the request off the irp
|
|
//
|
|
|
|
pRequest = (PUL_INTERNAL_REQUEST)(
|
|
IoGetCurrentIrpStackLocation(pIrp)->
|
|
Parameters.DeviceIoControl.Type3InputBuffer
|
|
);
|
|
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
//
|
|
// grab the lock protecting the queue'd irp
|
|
//
|
|
|
|
UlAcquireResourceExclusive(&(pRequest->pHttpConn->Resource), TRUE);
|
|
|
|
//
|
|
// does it need to be dequeue'd ?
|
|
//
|
|
|
|
if (pIrp->Tail.Overlay.ListEntry.Flink != NULL)
|
|
{
|
|
//
|
|
// remove it
|
|
//
|
|
|
|
RemoveEntryList(&(pIrp->Tail.Overlay.ListEntry));
|
|
|
|
pIrp->Tail.Overlay.ListEntry.Flink = NULL;
|
|
pIrp->Tail.Overlay.ListEntry.Blink = NULL;
|
|
|
|
}
|
|
|
|
//
|
|
// let the lock go
|
|
//
|
|
|
|
UlReleaseResource(&(pRequest->pHttpConn->Resource));
|
|
|
|
//
|
|
// let our reference go
|
|
//
|
|
|
|
IoGetCurrentIrpStackLocation(pIrp)->
|
|
Parameters.DeviceIoControl.Type3InputBuffer = NULL;
|
|
|
|
UL_DEREFERENCE_INTERNAL_REQUEST(pRequest);
|
|
|
|
//
|
|
// complete the irp
|
|
//
|
|
|
|
pIrp->IoStatus.Status = STATUS_CANCELLED;
|
|
pIrp->IoStatus.Information = 0;
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"UlpCancelEntityBodyWorker(pIrp=%p): Status=0x%x.\n",
|
|
pIrp, pIrp->IoStatus.Status
|
|
));
|
|
|
|
UlCompleteRequest( pIrp, g_UlPriorityBoost );
|
|
|
|
} // UlpCancelEntityBodyWorker
|
|
|
|
|
|
//
|
|
// types and functions for sending error responses
|
|
//
|
|
|
|
typedef struct _UL_HTTP_ERROR_ENTRY
|
|
{
|
|
USHORT StatusCode;
|
|
ULONG ReasonLength;
|
|
PSTR pReason;
|
|
ULONG BodyLength;
|
|
PSTR pBody;
|
|
} UL_HTTP_ERROR_ENTRY, PUL_HTTP_ERROR_ENTRY;
|
|
|
|
#define HTTP_ERROR_ENTRY(StatusCode, pReason, pBody) \
|
|
{ \
|
|
(StatusCode), \
|
|
sizeof((pReason))-sizeof(CHAR), \
|
|
(pReason), \
|
|
sizeof((pBody))-sizeof(CHAR), \
|
|
(pBody) \
|
|
}
|
|
|
|
//
|
|
// ErrorTable[] must match the order of the UL_HTTP_ERROR enum
|
|
// in httptypes.h
|
|
//
|
|
|
|
const
|
|
UL_HTTP_ERROR_ENTRY ErrorTable[] =
|
|
{
|
|
//
|
|
// UlError
|
|
//
|
|
HTTP_ERROR_ENTRY(400, "Bad Request", "<H1>Bad Request</H1>"),
|
|
//
|
|
// UlErrorVerb
|
|
//
|
|
HTTP_ERROR_ENTRY(400, "Bad Request", "<H1>Bad Request (invalid verb)</H1>"),
|
|
//
|
|
// UlErrorUrl
|
|
//
|
|
HTTP_ERROR_ENTRY(400, "Bad Request", "<H1>Bad Request (invalid url)</H1>"),
|
|
//
|
|
// UlErrorHeader
|
|
//
|
|
HTTP_ERROR_ENTRY(400, "Bad Request", "<H1>Bad Request (invalid header name)</H1>"),
|
|
//
|
|
// UlErrorHost
|
|
//
|
|
HTTP_ERROR_ENTRY(400, "Bad Request", "<H1>Bad Request (invalid hostname)</H1>"),
|
|
//
|
|
// UlErrorCRLF
|
|
//
|
|
HTTP_ERROR_ENTRY(400, "Bad Request", "<H1>Bad Request (invalid CR or LF)</H1>"),
|
|
//
|
|
// UlErrorNum
|
|
//
|
|
HTTP_ERROR_ENTRY(400, "Bad Request", "<H1>Bad Request (invalid number)</H1>"),
|
|
//
|
|
// UlErrorFieldLength
|
|
//
|
|
HTTP_ERROR_ENTRY(400, "Bad Request", "<H1>Bad Request (Header Field Too Long)</H1>"),
|
|
//
|
|
// UlErrorUrlLength
|
|
//
|
|
HTTP_ERROR_ENTRY(414, "Bad Request", "<H1>Url Too Long</H1>"),
|
|
//
|
|
// UlErrorRequestLength
|
|
//
|
|
HTTP_ERROR_ENTRY(400, "Bad Request", "<H1>Bad Request (Request Header Too Long)</H1>"),
|
|
//
|
|
// UlErrorVersion
|
|
//
|
|
HTTP_ERROR_ENTRY(505, "HTTP Version not supported", "<H1>HTTP Version not supported</H1>"),
|
|
//
|
|
// UlErrorUnavailable
|
|
//
|
|
HTTP_ERROR_ENTRY(503, "Service Unavailable", "<H1>Service Unavailable</H1>"),
|
|
//
|
|
// UlErrorContentLength
|
|
//
|
|
HTTP_ERROR_ENTRY(411, "Length required", "<H1>Length required</H1>"),
|
|
//
|
|
// UlErrorEntityTooLarge
|
|
//
|
|
HTTP_ERROR_ENTRY(413, "Request Entity Too Large", "<H1>Request Entity Too Large</H1>"),
|
|
//
|
|
// UlErrorConnectionLimit
|
|
//
|
|
HTTP_ERROR_ENTRY(403, "Forbidden", "<H1>Forbidden - Too many users</H1>"),
|
|
//
|
|
// UlErrorNotImplemented
|
|
//
|
|
HTTP_ERROR_ENTRY(501, "Not Implemented", "<H1>Not Implemented</H1>"),
|
|
|
|
// UlErrorInternalServer
|
|
//
|
|
HTTP_ERROR_ENTRY(500, "Internal Server Error", "<H1>Internal Server Error</H1>"),
|
|
//
|
|
// UlErrorPreconditionFailed
|
|
//
|
|
HTTP_ERROR_ENTRY(412, "Precondition Failed", "<H1>Precondition Failed</H1>"),
|
|
//
|
|
// UlErrorForbiddenUrl
|
|
//
|
|
HTTP_ERROR_ENTRY(403, "Forbidden", "<H1>Forbidden (Invalid URL)</H1>"),
|
|
|
|
}; // ErrorTable[]
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
You should hold the connection Resource before calling this
|
|
function.
|
|
|
|
Arguments:
|
|
|
|
self explanatory
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlSendErrorResponse(
|
|
PUL_HTTP_CONNECTION pConnection
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
HTTP_RESPONSE Response;
|
|
HTTP_DATA_CHUNK DataChunk;
|
|
PUL_INTERNAL_RESPONSE pKeResponse = NULL;
|
|
CHAR ContentType[] = "text/html";
|
|
USHORT ContentTypeLength = sizeof(ContentType) - sizeof(CHAR);
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pConnection));
|
|
ASSERT(UlDbgResourceOwnedExclusive(&pConnection->Resource));
|
|
|
|
pRequest = pConnection->pRequest;
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
//
|
|
// To prevent sending back double responses. We will
|
|
// check if user (WP) has already sent one.
|
|
//
|
|
|
|
pConnection->WaitingForResponse = 1;
|
|
|
|
UlTrace( PARSER, (
|
|
"*** pConnection %p->WaitingForResponse = 1\n",
|
|
pConnection
|
|
));
|
|
|
|
//
|
|
// We will send a response back. won't we ?
|
|
// An error response.
|
|
//
|
|
|
|
if (1 == InterlockedCompareExchange(
|
|
(PLONG)&pRequest->SentResponse,
|
|
1,
|
|
0
|
|
))
|
|
{
|
|
UlTrace( PARSER, (
|
|
"*** pConnection %p, pRequest %p, skipping SendError.\n",
|
|
pConnection,
|
|
pRequest
|
|
));
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Proceed with constructing and sending the error
|
|
// back to the client
|
|
//
|
|
|
|
RtlZeroMemory(&Response, sizeof(Response));
|
|
|
|
if (pRequest->ErrorCode >= DIMENSION(ErrorTable))
|
|
{
|
|
pRequest->ErrorCode = UlError;
|
|
}
|
|
|
|
Response.StatusCode = ErrorTable[pRequest->ErrorCode].StatusCode;
|
|
Response.ReasonLength = ErrorTable[pRequest->ErrorCode].ReasonLength;
|
|
Response.pReason = ErrorTable[pRequest->ErrorCode].pReason;
|
|
|
|
Response.Headers.pKnownHeaders[HttpHeaderContentType].RawValueLength =
|
|
ContentTypeLength;
|
|
Response.Headers.pKnownHeaders[HttpHeaderContentType].pRawValue =
|
|
ContentType;
|
|
|
|
//
|
|
// generate a body
|
|
//
|
|
DataChunk.DataChunkType = HttpDataChunkFromMemory;
|
|
DataChunk.FromMemory.pBuffer = ErrorTable[pRequest->ErrorCode].pBody;
|
|
DataChunk.FromMemory.BufferLength = ErrorTable[pRequest->ErrorCode].BodyLength;
|
|
|
|
Status = UlCaptureHttpResponse(
|
|
&Response,
|
|
pRequest,
|
|
pRequest->Version,
|
|
pRequest->Verb,
|
|
1,
|
|
&DataChunk,
|
|
UlCaptureCopyDataInKernelMode,
|
|
FALSE,
|
|
NULL,
|
|
&pKeResponse
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
Status = UlPrepareHttpResponse(
|
|
pRequest->Version,
|
|
&Response,
|
|
pKeResponse
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
Status = UlSendHttpResponse(
|
|
pRequest,
|
|
pKeResponse,
|
|
HTTP_SEND_RESPONSE_FLAG_DISCONNECT,
|
|
&UlpCompleteSendResponse,
|
|
pKeResponse
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
ASSERT(Status == STATUS_PENDING);
|
|
|
|
end:
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
if (pKeResponse != NULL)
|
|
{
|
|
UL_DEREFERENCE_INTERNAL_RESPONSE(pKeResponse);
|
|
}
|
|
|
|
//
|
|
// Abort the connection
|
|
//
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlSendErrorResponse(%p): Failed to send error response\n",
|
|
pConnection
|
|
));
|
|
|
|
//
|
|
// cancel any pending io
|
|
//
|
|
UlCancelRequestIo(pRequest);
|
|
|
|
//
|
|
// abort the connection this request is associated with
|
|
//
|
|
|
|
UlCloseConnection(
|
|
pRequest->pHttpConn->pConnection,
|
|
TRUE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
}
|
|
|
|
} // UlSendErrorResponse
|
|
|
|
|
|
|
|
VOID
|
|
UlpCompleteSendResponse(
|
|
IN PVOID pCompletionContext,
|
|
IN NTSTATUS Status,
|
|
IN ULONG_PTR Information
|
|
)
|
|
{
|
|
|
|
//
|
|
// release the response
|
|
//
|
|
|
|
if (pCompletionContext != NULL)
|
|
{
|
|
UL_DEREFERENCE_INTERNAL_RESPONSE(
|
|
(PUL_INTERNAL_RESPONSE)(pCompletionContext)
|
|
);
|
|
}
|
|
} // UlpCompleteSendResponse
|
|
|
|
|
|
//
|
|
// Types and functions for sending simple status responses
|
|
//
|
|
// REVIEW: Does this status code need to be localized?
|
|
// REVIEW: Do we need to load this as a localized resource?
|
|
//
|
|
|
|
typedef struct _UL_SIMPLE_STATUS_ITEM
|
|
{
|
|
UL_WORK_ITEM WorkItem;
|
|
|
|
ULONG Length;
|
|
PCHAR pMessage;
|
|
PMDL pMdl;
|
|
|
|
PUL_HTTP_CONNECTION pHttpConn;
|
|
|
|
} UL_SIMPLE_STATUS_ITEM, *PUL_SIMPLE_STATUS_ITEM;
|
|
|
|
typedef struct _UL_HTTP_SIMPLE_STATUS_ENTRY
|
|
{
|
|
USHORT StatusCode; // HTTP Status
|
|
ULONG Length; // size (bytes) of response in pResponse, minus trailing NULL
|
|
PSTR pResponse; // header line only with trailing <CRLF><CRLF>
|
|
PMDL pMdl; // MDL allocated at startup
|
|
|
|
} UL_HTTP_SIMPLE_STATUS_ENTRY, *PUL_HTTP_SIMPLE_STATUS_ENTRY;
|
|
|
|
|
|
#define HTTP_SIMPLE_STATUS_ENTRY(StatusCode, pResp) \
|
|
{ \
|
|
(StatusCode), \
|
|
sizeof((pResp))-sizeof(CHAR), \
|
|
(pResp), \
|
|
NULL \
|
|
}
|
|
|
|
//
|
|
// This must match the order of UL_HTTP_SIMPLE_STATUS in httptypes.h
|
|
//
|
|
UL_HTTP_SIMPLE_STATUS_ENTRY g_SimpleStatusTable[] =
|
|
{
|
|
//
|
|
// UlStatusContinue
|
|
//
|
|
HTTP_SIMPLE_STATUS_ENTRY( 100, "HTTP/1.1 100 Continue\r\n\r\n" ),
|
|
|
|
//
|
|
// UlStatusNoContent
|
|
//
|
|
HTTP_SIMPLE_STATUS_ENTRY( 204, "HTTP/1.1 204 No Content\r\n\r\n" ),
|
|
|
|
//
|
|
// UlStatusNotModified (must add Date:)
|
|
//
|
|
HTTP_SIMPLE_STATUS_ENTRY( 304, "HTTP/1.1 304 Not Modified\r\nDate:" ),
|
|
|
|
};
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Sends a "Simple" status response: one which does not have a body and is
|
|
terminated by the first empty line after the header field(s).
|
|
See RFC 2616, Section 4.4 for more info.
|
|
|
|
Notes:
|
|
|
|
According to RFC 2616, Section 8.2.3 [Use of the 100 (Continue)
|
|
Status], "An origin server that sends a 100 (Continue) response
|
|
MUST ultimately send a final status code, once the request body is
|
|
received and processed, unless it terminates the transport
|
|
connection prematurely."
|
|
|
|
The connection will not be closed after the response is sent. Caller
|
|
is responsible for cleanup.
|
|
|
|
Arguments:
|
|
|
|
pRequest a valid pointer to an internal request object
|
|
|
|
Response the status code for the simple response to send
|
|
|
|
Return
|
|
|
|
ULONG the number of bytes sent for this simple response
|
|
if not successfull returns zero
|
|
|
|
--***************************************************************************/
|
|
|
|
ULONG
|
|
UlSendSimpleStatus(
|
|
PUL_INTERNAL_REQUEST pRequest,
|
|
UL_HTTP_SIMPLE_STATUS Response
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG BytesSent;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pRequest->pHttpConn));
|
|
|
|
ASSERT( (Response >= 0) && (Response < UlStatusMaxStatus) );
|
|
|
|
BytesSent = 0;
|
|
|
|
if ( UlStatusNotModified == Response )
|
|
{
|
|
PUL_SIMPLE_STATUS_ITEM pItem;
|
|
ULONG Length;
|
|
PCHAR pTemp;
|
|
CHAR DateBuffer[DATE_HDR_LENGTH + 1];
|
|
LARGE_INTEGER liNow;
|
|
|
|
// 304 MUST include a "Date:" header, which is
|
|
// present on the cached item.
|
|
|
|
// TODO: Add the ETag as well.
|
|
|
|
// Calc size of buffer to send
|
|
Length = g_SimpleStatusTable[Response].Length + // Pre-formed message
|
|
1 + // space
|
|
DATE_HDR_LENGTH + // size of date field
|
|
(2 * CRLF_SIZE) + // size of two <CRLF> sequences
|
|
1 ; // trailing NULL (for nifty debug printing)
|
|
|
|
|
|
// Alloc some non-page buffer for the response
|
|
pItem = UL_ALLOCATE_STRUCT_WITH_SPACE(
|
|
NonPagedPool,
|
|
UL_SIMPLE_STATUS_ITEM,
|
|
Length,
|
|
UL_SIMPLE_STATUS_ITEM_TAG
|
|
);
|
|
if (!pItem)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
// We need to hold a ref to the connection while we send.
|
|
UL_REFERENCE_HTTP_CONNECTION(pRequest->pHttpConn);
|
|
pItem->pHttpConn = pRequest->pHttpConn;
|
|
|
|
pItem->Length = Length - 1; // Don't include the NULL in the outbound message
|
|
pItem->pMessage = (PCHAR) (pItem + 1);
|
|
|
|
// Get date buffer
|
|
GenerateDateHeader(
|
|
(PUCHAR) DateBuffer,
|
|
&liNow
|
|
);
|
|
|
|
// Copy the chunks into the Message buffer
|
|
pTemp = UlStrPrintStr(
|
|
pItem->pMessage,
|
|
g_SimpleStatusTable[Response].pResponse,
|
|
' '
|
|
);
|
|
|
|
pTemp = UlStrPrintStr(
|
|
pTemp,
|
|
DateBuffer,
|
|
'\r' // this is a Nifty Trick(tm) to get a "\r\n\r\n"
|
|
); // sequence at the end of the buffer.
|
|
|
|
pTemp = UlStrPrintStr(
|
|
pTemp,
|
|
"\n\r\n",
|
|
'\0'
|
|
);
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!SendSimpleStatus: %s\n",
|
|
pItem->pMessage
|
|
));
|
|
|
|
// Construct MDL for buffer
|
|
pItem->pMdl = UlAllocateMdl(
|
|
pItem->pMessage,
|
|
pItem->Length,
|
|
FALSE,
|
|
FALSE,
|
|
NULL
|
|
);
|
|
|
|
MmBuildMdlForNonPagedPool(pItem->pMdl);
|
|
|
|
BytesSent = pItem->Length;
|
|
|
|
//
|
|
// Call UlSendData, passing pMdl as the completion context
|
|
// (so the completion routine can release it...)
|
|
//
|
|
Status = UlSendData(
|
|
pRequest->pHttpConn->pConnection,
|
|
pItem->pMdl,
|
|
pItem->Length,
|
|
UlpRestartSendSimpleStatus,
|
|
pItem, // Completion Context
|
|
NULL, // Own IRP
|
|
NULL, // Own IRP Context
|
|
FALSE // Initiate Disconnect
|
|
);
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Proceed with constructing and sending the simple response
|
|
// back to the client. Assumes caller will deref both the
|
|
// UL_INTERNAL_REQUEST and the UL_HTTP_CONNECTION
|
|
//
|
|
|
|
Status = UlSendData(
|
|
pRequest->pHttpConn->pConnection,
|
|
g_SimpleStatusTable[Response].pMdl,
|
|
g_SimpleStatusTable[Response].Length,
|
|
UlpRestartSendSimpleStatus,
|
|
NULL, // Completion Context
|
|
NULL, // Own IRP
|
|
NULL, // Own IRP Context
|
|
FALSE // Initiate Disconnect
|
|
);
|
|
|
|
BytesSent = g_SimpleStatusTable[Response].Length;
|
|
}
|
|
|
|
end:
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
//
|
|
// Abort the connection
|
|
//
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!SendSimpleStatus(%p, %d): aborting request\n",
|
|
pRequest,
|
|
Response
|
|
));
|
|
|
|
//
|
|
// cancel any pending io
|
|
//
|
|
UlCancelRequestIo(pRequest);
|
|
|
|
//
|
|
// abort the connection this request is associated with
|
|
//
|
|
|
|
UlCloseConnection(
|
|
pRequest->pHttpConn->pConnection,
|
|
TRUE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return BytesSent;
|
|
}
|
|
} // UlSendSimpleStatus
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Callback for when UlSendData completes sending a UL_SIMPLE_STATUS message
|
|
|
|
Arguments:
|
|
|
|
pCompletionContext (OPTIONAL) -- If non-NULL, a pointer to a
|
|
UL_SIMPLE_STATUS_ITEM.
|
|
|
|
Status -- Ignored.
|
|
|
|
Information -- Ignored.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpRestartSendSimpleStatus(
|
|
IN PVOID pCompletionContext,
|
|
IN NTSTATUS Status,
|
|
IN ULONG_PTR Information
|
|
)
|
|
{
|
|
PUL_SIMPLE_STATUS_ITEM pItem;
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!SendSimpleStatusCompletionRoutine: \n"
|
|
" pCompletionContext: %p\n"
|
|
" Status: 0x%08X\n"
|
|
" Information: %p\n",
|
|
pCompletionContext,
|
|
Status,
|
|
Information
|
|
));
|
|
|
|
if ( pCompletionContext )
|
|
{
|
|
pItem = (PUL_SIMPLE_STATUS_ITEM) pCompletionContext;
|
|
|
|
// Queue up work item for passive level
|
|
UL_QUEUE_WORK_ITEM(
|
|
&pItem->WorkItem,
|
|
&UlpSendSimpleCleanupWorker
|
|
);
|
|
|
|
}
|
|
|
|
} // UlpRestartSendSimpleStatus
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Worker function to do cleanup work that shouldn't happen above DPC level.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem -- If non-NULL, a pointer to a UL_WORK_ITEM
|
|
contained within a UL_SIMPLE_STATUS_ITEM.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpSendSimpleCleanupWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
KIRQL OldIrql;
|
|
|
|
PAGED_CODE();
|
|
ASSERT(pWorkItem);
|
|
|
|
PUL_SIMPLE_STATUS_ITEM pItem;
|
|
|
|
pItem = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_SIMPLE_STATUS_ITEM,
|
|
WorkItem
|
|
);
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!SendSimpleStatusCleanupWorker (%p) \n",
|
|
pWorkItem
|
|
));
|
|
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pItem->pHttpConn));
|
|
|
|
//
|
|
// start the Connection Timeout timer
|
|
//
|
|
UlLockTimeoutInfo(
|
|
&(pItem->pHttpConn->TimeoutInfo),
|
|
&OldIrql
|
|
);
|
|
|
|
UlSetConnectionTimer(
|
|
&(pItem->pHttpConn->TimeoutInfo),
|
|
TimerConnectionIdle
|
|
);
|
|
|
|
UlUnlockTimeoutInfo(
|
|
&(pItem->pHttpConn->TimeoutInfo),
|
|
OldIrql
|
|
);
|
|
|
|
UlEvaluateTimerState(
|
|
&(pItem->pHttpConn->TimeoutInfo)
|
|
);
|
|
|
|
//
|
|
// deref http connection
|
|
//
|
|
UL_DEREFERENCE_HTTP_CONNECTION( pItem->pHttpConn );
|
|
|
|
// if the pCompletionContext is non-NULL< it's a struct which holds the MDL
|
|
// and the memory allocated for the 304 (Not Modified) response. Release both.
|
|
UlFreeMdl( pItem->pMdl );
|
|
UL_FREE_POOL( pItem, UL_SIMPLE_STATUS_ITEM_TAG );
|
|
|
|
} // UlpSendSimpleCleanupWorker
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Alloc & Init the MDL used by the UlpSendContinue function.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlInitializeHttpRcv()
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_HTTP_SIMPLE_STATUS_ENTRY pSE;
|
|
int i;
|
|
|
|
for ( i = 0; i < UlStatusMaxStatus; i++ )
|
|
{
|
|
pSE = &g_SimpleStatusTable[i];
|
|
|
|
// Create a MDL for the response header
|
|
pSE->pMdl = UlAllocateMdl(
|
|
pSE->pResponse,
|
|
pSE->Length,
|
|
FALSE,
|
|
FALSE,
|
|
NULL
|
|
);
|
|
|
|
if (!pSE->pMdl)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
MmBuildMdlForNonPagedPool(pSE->pMdl);
|
|
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end:
|
|
|
|
if ( STATUS_SUCCESS != Status )
|
|
{
|
|
//
|
|
// FAILED: cleanup any allocated MDLs
|
|
//
|
|
for ( i = 0; i < UlStatusMaxStatus; i++ )
|
|
{
|
|
pSE = &g_SimpleStatusTable[i];
|
|
|
|
if (pSE->pMdl)
|
|
{
|
|
UlFreeMdl( pSE->pMdl );
|
|
pSE->pMdl = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
} // UlInitializeHttpRcv
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Free the MDL used by the UlpSendContinue function.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlTerminateHttpRcv()
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_HTTP_SIMPLE_STATUS_ENTRY pSE;
|
|
int i;
|
|
|
|
for ( i = 0; i < UlStatusMaxStatus; i++ )
|
|
{
|
|
pSE = &g_SimpleStatusTable[i];
|
|
|
|
if (pSE->pMdl)
|
|
{
|
|
ASSERT(0 == ((pSE->pMdl->MdlFlags) & MDL_PAGES_LOCKED));
|
|
UlFreeMdl( pSE->pMdl );
|
|
}
|
|
}
|
|
|
|
} // UlTerminateHttpRcv
|
|
|
|
|
|
#if DBG
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Invasive assert predicate. DEBUG ONLY!!! Use this only inside an
|
|
ASSERT() macro.
|
|
|
|
--***************************************************************************/
|
|
|
|
BOOLEAN
|
|
UlpIsValidRequestBufferList(
|
|
PUL_HTTP_CONNECTION pHttpConn
|
|
)
|
|
{
|
|
PLIST_ENTRY pEntry;
|
|
PUL_REQUEST_BUFFER pReqBuf;
|
|
ULONG LastSeqNum = 0;
|
|
BOOLEAN fRet = TRUE;
|
|
|
|
|
|
PAGED_CODE();
|
|
ASSERT( pHttpConn );
|
|
|
|
//
|
|
// pop from the head
|
|
//
|
|
|
|
pEntry = pHttpConn->BufferHead.Flink;
|
|
while ( pEntry != &(pHttpConn->BufferHead) )
|
|
{
|
|
pReqBuf = CONTAINING_RECORD( pEntry,
|
|
UL_REQUEST_BUFFER,
|
|
ListEntry
|
|
);
|
|
|
|
ASSERT( UL_IS_VALID_REQUEST_BUFFER(pReqBuf) );
|
|
ASSERT( pReqBuf->UsedBytes != 0 );
|
|
|
|
if ( 0 == pReqBuf->UsedBytes )
|
|
{
|
|
fRet = FALSE;
|
|
}
|
|
|
|
//
|
|
// ignore case when BufferNumber is zero (0).
|
|
//
|
|
if ( pReqBuf->BufferNumber && (LastSeqNum >= pReqBuf->BufferNumber) )
|
|
{
|
|
fRet = FALSE;
|
|
}
|
|
|
|
LastSeqNum = pReqBuf->BufferNumber;
|
|
pEntry = pEntry->Flink;
|
|
|
|
}
|
|
|
|
return fRet;
|
|
|
|
}
|
|
#endif // DBG
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Add a reference of the request buffer in the internal request.
|
|
|
|
--***************************************************************************/
|
|
|
|
BOOLEAN
|
|
UlpReferenceBuffers(
|
|
IN PUL_INTERNAL_REQUEST pRequest,
|
|
IN PUL_REQUEST_BUFFER pRequestBuffer
|
|
)
|
|
{
|
|
PUL_REQUEST_BUFFER * pNewRefBuffers;
|
|
|
|
if (pRequest->UsedRefBuffers >= pRequest->AllocRefBuffers)
|
|
{
|
|
ASSERT( pRequest->UsedRefBuffers == pRequest->AllocRefBuffers );
|
|
|
|
pNewRefBuffers = UL_ALLOCATE_ARRAY(
|
|
NonPagedPool,
|
|
PUL_REQUEST_BUFFER,
|
|
pRequest->AllocRefBuffers + ALLOC_REQUEST_BUFFER_INCREMENT,
|
|
UL_REF_REQUEST_BUFFER_POOL_TAG
|
|
);
|
|
|
|
if (!pNewRefBuffers)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
RtlCopyMemory(
|
|
pNewRefBuffers,
|
|
pRequest->pRefBuffers,
|
|
pRequest->UsedRefBuffers * sizeof(PUL_REQUEST_BUFFER)
|
|
);
|
|
|
|
if (pRequest->AllocRefBuffers > 1)
|
|
{
|
|
UL_FREE_POOL(
|
|
pRequest->pRefBuffers,
|
|
UL_REF_REQUEST_BUFFER_POOL_TAG
|
|
);
|
|
}
|
|
|
|
pRequest->AllocRefBuffers += ALLOC_REQUEST_BUFFER_INCREMENT;
|
|
pRequest->pRefBuffers = pNewRefBuffers;
|
|
}
|
|
|
|
pRequest->pRefBuffers[pRequest->UsedRefBuffers] = pRequestBuffer;
|
|
pRequest->UsedRefBuffers++;
|
|
UL_REFERENCE_REQUEST_BUFFER(pRequestBuffer);
|
|
|
|
return TRUE;
|
|
}
|
|
|