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.
7840 lines
214 KiB
7840 lines
214 KiB
/*++
|
|
|
|
Copyright (c) 1998-2002 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
sendresponse.c
|
|
|
|
Abstract:
|
|
|
|
This module implements the UlSendHttpResponse() API.
|
|
|
|
CODEWORK: The current implementation is not super performant.
|
|
Specifically, it ends up allocating & freeing a ton of IRPs to send
|
|
a response. There are a number of optimizations that need to be made
|
|
to this code:
|
|
|
|
1. Coalesce contiguious from-memory chunks and send them
|
|
with a single TCP send.
|
|
|
|
2. Defer sending the from-memory chunks until either
|
|
|
|
a) We reach the end of the response
|
|
|
|
b) We reach a from-file chunk, have read the
|
|
(first?) block of data from the file,
|
|
and are ready to send the first block. Also,
|
|
after that last (only?) file block is read and
|
|
subsequent from-memory chunks exist in the response,
|
|
we can attach the from-memory chunks before sending.
|
|
|
|
The end result of these optimizations is that, for the
|
|
common case (one or more from-memory chunks containing
|
|
response headers, followed by one from-file chunk containing
|
|
static file data, followed by zero or more from-memory chunks
|
|
containing footer data) the response can be sent with a single
|
|
TCP send. This is a Good Thing.
|
|
|
|
3. Build a small "IRP pool" in the send tracker structure,
|
|
then use this pool for all IRP allocations. This will
|
|
require a bit of work to determine the maximum IRP stack
|
|
size needed.
|
|
|
|
4. Likewise, build a small "MDL pool" for the MDLs that need
|
|
to be created for the various MDL chains. Keep in mind that
|
|
we cannot chain the MDLs that come directly from the captured
|
|
response structure, nor can we chain the MDLs that come back
|
|
from the file system. In both cases, these MDLs are considered
|
|
"shared resources" and we're not allowed to modify them. We
|
|
can, however, "clone" the MDLs and chain the cloned MDLs
|
|
together. We'll need to run some experiments to determine
|
|
if the overhead for cloning a MDL is worth the effort. I
|
|
strongly suspect it will be.
|
|
|
|
Author:
|
|
|
|
Keith Moore (keithmo) 07-Aug-1998
|
|
|
|
Revision History:
|
|
|
|
Paul McDaniel (paulmcd) 15-Mar-1999 Modified to handle
|
|
multiple sends
|
|
|
|
Michael Courage (mcourage) 15-Jun-1999 Integrated cache functionality
|
|
|
|
Chun Ye (chunye) 08-Jun-2002 Implemented split send
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
#include "sendresponsep.h"
|
|
|
|
|
|
//
|
|
// Private globals.
|
|
//
|
|
|
|
|
|
ULONGLONG g_UlTotalSendBytes = 0;
|
|
UL_EXCLUSIVE_LOCK g_UlTotalSendBytesExLock = UL_EX_LOCK_FREE;
|
|
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text( PAGE, UlCaptureHttpResponse )
|
|
#pragma alloc_text( PAGE, UlCaptureUserLogData )
|
|
#pragma alloc_text( PAGE, UlPrepareHttpResponse )
|
|
#pragma alloc_text( PAGE, UlCleanupHttpResponse )
|
|
#pragma alloc_text( PAGE, UlAllocateLockedMdl )
|
|
#pragma alloc_text( PAGE, UlInitializeAndLockMdl )
|
|
#pragma alloc_text( PAGE, UlSendCachedResponse )
|
|
#pragma alloc_text( PAGE, UlCacheAndSendResponse )
|
|
|
|
#pragma alloc_text( PAGE, UlpEnqueueSendHttpResponse )
|
|
#pragma alloc_text( PAGE, UlpDequeueSendHttpResponse )
|
|
#pragma alloc_text( PAGE, UlpSendHttpResponseWorker )
|
|
#pragma alloc_text( PAGE, UlpMdlSendCompleteWorker )
|
|
#pragma alloc_text( PAGE, UlpMdlReadCompleteWorker )
|
|
#pragma alloc_text( PAGE, UlpCacheMdlReadCompleteWorker )
|
|
#pragma alloc_text( PAGE, UlpFlushMdlRuns )
|
|
#pragma alloc_text( PAGE, UlpFreeMdlRuns )
|
|
#pragma alloc_text( PAGE, UlpCopySend )
|
|
#pragma alloc_text( PAGE, UlpBuildCacheEntry )
|
|
#pragma alloc_text( PAGE, UlpBuildCacheEntryWorker )
|
|
#pragma alloc_text( PAGE, UlpCompleteCacheBuildWorker )
|
|
#pragma alloc_text( PAGE, UlpSendCacheEntry )
|
|
#pragma alloc_text( PAGE, UlpSendCacheEntryWorker )
|
|
#pragma alloc_text( PAGE, UlpAllocateCacheTracker )
|
|
#pragma alloc_text( PAGE, UlpFreeCacheTracker )
|
|
#endif // ALLOC_PRAGMA
|
|
|
|
#if 0
|
|
NOT PAGEABLE -- UlSendHttpResponse
|
|
NOT PAGEABLE -- UlReferenceHttpResponse
|
|
NOT PAGEABLE -- UlDereferenceHttpResponse
|
|
NOT PAGEABLE -- UlFreeLockedMdl
|
|
NOT PAGEABLE -- UlCompleteSendResponse
|
|
NOT PAGEABLE -- UlSetRequestSendsPending
|
|
NOT PAGEABLE -- UlUnsetRequestSendsPending
|
|
|
|
NOT PAGEABLE -- UlpDestroyCapturedResponse
|
|
NOT PAGEABLE -- UlpAllocateChunkTracker
|
|
NOT PAGEABLE -- UlpFreeChunkTracker
|
|
NOT PAGEABLE -- UlpRestartMdlRead
|
|
NOT PAGEABLE -- UlpRestartMdlSend
|
|
NOT PAGEABLE -- UlpRestartCopySend
|
|
NOT PAGEABLE -- UlpIncrementChunkPointer
|
|
NOT PAGEABLE -- UlpRestartCacheMdlRead
|
|
NOT PAGEABLE -- UlpRestartCacheMdlFree
|
|
NOT PAGEABLE -- UlpIssueFileChunkIo
|
|
NOT PAGEABLE -- UlpCompleteCacheBuild
|
|
NOT PAGEABLE -- UlpCompleteSendCacheEntry
|
|
NOT PAGEABLE -- UlpCompleteSendResponseWorker
|
|
NOT PAGEABLE -- UlpCompleteSendCacheEntryWorker
|
|
#endif
|
|
|
|
|
|
//
|
|
// Public functions.
|
|
//
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Sends an HTTP response on the specified connection.
|
|
|
|
Arguments:
|
|
|
|
pConnection - Supplies the HTTP_CONNECTION to send the response on.
|
|
|
|
pResponse - Supplies the HTTP response.
|
|
|
|
pCompletionRoutine - Supplies a pointer to a completion routine to
|
|
invoke after the send has completed.
|
|
|
|
pCompletionContext - Supplies an uninterpreted context value for the
|
|
completion routine.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlSendHttpResponse(
|
|
IN PUL_INTERNAL_REQUEST pRequest,
|
|
IN PUL_INTERNAL_RESPONSE pResponse,
|
|
IN PUL_COMPLETION_ROUTINE pCompletionRoutine,
|
|
IN PVOID pCompletionContext
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_CHUNK_TRACKER pTracker;
|
|
PUL_HTTP_CONNECTION pHttpConn;
|
|
UL_CONN_HDR ConnHeader;
|
|
BOOLEAN Disconnect;
|
|
ULONG VarHeaderGenerated;
|
|
ULONGLONG TotalResponseSize;
|
|
ULONG ContentLengthStringLength;
|
|
UCHAR ContentLength[MAX_ULONGLONG_STR];
|
|
ULONG Flags = pResponse->Flags;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pRequest ) );
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pResponse ) );
|
|
|
|
pHttpConn = pRequest->pHttpConn;
|
|
ASSERT( UL_IS_VALID_HTTP_CONNECTION( pHttpConn ) );
|
|
|
|
TRACE_TIME(
|
|
pRequest->ConnectionId,
|
|
pRequest->RequestId,
|
|
TIME_ACTION_SEND_RESPONSE
|
|
);
|
|
|
|
if (Flags & (~HTTP_SEND_RESPONSE_FLAG_VALID))
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Setup locals so we know how to cleanup on exit.
|
|
//
|
|
|
|
pTracker = NULL;
|
|
|
|
//
|
|
// Should we close the connection?
|
|
//
|
|
|
|
if ((Flags & HTTP_SEND_RESPONSE_FLAG_DISCONNECT))
|
|
{
|
|
//
|
|
// Caller is forcing a disconnect.
|
|
//
|
|
|
|
Disconnect = TRUE;
|
|
}
|
|
else
|
|
{
|
|
Disconnect = UlCheckDisconnectInfo( pRequest );
|
|
|
|
if (0 == (Flags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA))
|
|
{
|
|
//
|
|
// No more data is coming, should we disconnect?
|
|
//
|
|
|
|
if (Disconnect)
|
|
{
|
|
Flags |= HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
|
|
pResponse->Flags = Flags;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// How big is the response?
|
|
//
|
|
|
|
TotalResponseSize = pResponse->ResponseLength;
|
|
|
|
//
|
|
// Generate the Content-Length header if we meet the following conditions:
|
|
// 1. This is a response (not entity body).
|
|
// 2. The app didn't provide Content-Length.
|
|
// 3. The app didn't generate a chunked response.
|
|
//
|
|
|
|
if (pResponse->HeaderLength > 0 &&
|
|
!pResponse->ContentLengthSpecified &&
|
|
!pResponse->ChunkedSpecified &&
|
|
UlNeedToGenerateContentLength(
|
|
pRequest->Verb,
|
|
pResponse->StatusCode,
|
|
Flags
|
|
))
|
|
{
|
|
//
|
|
// Autogenerate a Content-Length header.
|
|
//
|
|
|
|
PCHAR pEnd = UlStrPrintUlonglong(
|
|
(PCHAR) ContentLength,
|
|
pResponse->ResponseLength - pResponse->HeaderLength,
|
|
ANSI_NULL
|
|
);
|
|
ContentLengthStringLength = DIFF(pEnd - (PCHAR) ContentLength);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Either we cannot or do not need to autogenerate a
|
|
// Content-Length header.
|
|
//
|
|
|
|
ContentLength[0] = ANSI_NULL;
|
|
ContentLengthStringLength = 0;
|
|
}
|
|
|
|
//
|
|
// See if user explicitly wants Connection: header removed, or we will
|
|
// choose one for them.
|
|
//
|
|
|
|
if (ConnHdrNone == pResponse->ConnHeader)
|
|
{
|
|
ConnHeader = pResponse->ConnHeader;
|
|
}
|
|
else
|
|
{
|
|
ConnHeader = UlChooseConnectionHeader( pRequest->Version, Disconnect );
|
|
}
|
|
|
|
//
|
|
// Completion info.
|
|
//
|
|
|
|
pResponse->pCompletionRoutine = pCompletionRoutine;
|
|
pResponse->pCompletionContext = pCompletionContext;
|
|
|
|
//
|
|
// Allocate and initialize a tracker for this response.
|
|
//
|
|
|
|
pTracker =
|
|
UlpAllocateChunkTracker(
|
|
UlTrackerTypeSend,
|
|
pHttpConn->pConnection->ConnectionObject.pDeviceObject->StackSize,
|
|
pResponse->MaxFileSystemStackSize,
|
|
TRUE,
|
|
pHttpConn,
|
|
pResponse
|
|
);
|
|
|
|
if (pTracker == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Initialize the first chunk and MDL_RUN for the send.
|
|
//
|
|
|
|
UlpIncrementChunkPointer( pResponse );
|
|
|
|
//
|
|
// Generate var headers, and init the second chunk.
|
|
//
|
|
|
|
if (pResponse->HeaderLength)
|
|
{
|
|
UlGenerateVariableHeaders(
|
|
ConnHeader,
|
|
pResponse->GenDateHeader,
|
|
ContentLength,
|
|
ContentLengthStringLength,
|
|
pResponse->pVariableHeader,
|
|
&VarHeaderGenerated,
|
|
&pResponse->CreationTime
|
|
);
|
|
|
|
ASSERT( VarHeaderGenerated <= g_UlMaxVariableHeaderSize );
|
|
|
|
pResponse->VariableHeaderLength = VarHeaderGenerated;
|
|
|
|
//
|
|
// Increment total size.
|
|
//
|
|
|
|
TotalResponseSize += VarHeaderGenerated;
|
|
|
|
//
|
|
// Build a MDL for it.
|
|
//
|
|
|
|
pResponse->pDataChunks[1].ChunkType = HttpDataChunkFromMemory;
|
|
pResponse->pDataChunks[1].FromMemory.BufferLength = VarHeaderGenerated;
|
|
|
|
pResponse->pDataChunks[1].FromMemory.pMdl =
|
|
UlAllocateMdl(
|
|
pResponse->pVariableHeader, // VirtualAddress
|
|
VarHeaderGenerated, // Length
|
|
FALSE, // SecondaryBuffer
|
|
FALSE, // ChargeQuota
|
|
NULL // Irp
|
|
);
|
|
|
|
if (pResponse->pDataChunks[1].FromMemory.pMdl == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto cleanup;
|
|
}
|
|
|
|
MmBuildMdlForNonPagedPool( pResponse->pDataChunks[1].FromMemory.pMdl );
|
|
}
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"UlSendHttpResponse: tracker %p, response %p\n",
|
|
pTracker,
|
|
pResponse
|
|
));
|
|
|
|
//
|
|
// Adjust SendsPending and while holding the lock, transfer the
|
|
// ownership of pLogData and ResumeParsing information from
|
|
// pResponse to pRequest.
|
|
//
|
|
|
|
UlSetRequestSendsPending(
|
|
pRequest,
|
|
&pResponse->pLogData,
|
|
&pResponse->ResumeParsingType
|
|
);
|
|
|
|
//
|
|
// Start MinBytesPerSecond timer, since we now know TotalResponseSize.
|
|
//
|
|
|
|
UlSetMinBytesPerSecondTimer(
|
|
&pHttpConn->TimeoutInfo,
|
|
TotalResponseSize
|
|
);
|
|
|
|
if (ETW_LOG_MIN() && (0 == (Flags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA)))
|
|
{
|
|
UlEtwTraceEvent(
|
|
&UlTransGuid,
|
|
ETW_TYPE_END,
|
|
(PVOID) &pTracker->pResponse->pRequest->RequestIdCopy,
|
|
sizeof(HTTP_REQUEST_ID),
|
|
(PVOID) &pResponse->StatusCode,
|
|
sizeof(USHORT),
|
|
NULL,
|
|
0
|
|
);
|
|
}
|
|
|
|
//
|
|
// Queue the response to the request's pending response list or start
|
|
// processing the send right away if no other sends are pending. As an
|
|
// optimization, we can call UlpSendHttpResponseWorker directly without
|
|
// applying the queuing logic if no send is pending, if this is from an
|
|
// IOCTL and all of the response's data chunks are FromMemory, in which
|
|
// case we are guaranteed that returning from the current routine will
|
|
// have all the send data pended in TDI.
|
|
//
|
|
|
|
if (pRequest->SendInProgress ||
|
|
pResponse->MaxFileSystemStackSize ||
|
|
pResponse->FromKernelMode)
|
|
{
|
|
pResponse->SendEnqueued = TRUE;
|
|
UlpEnqueueSendHttpResponse( pTracker, pResponse->FromKernelMode );
|
|
}
|
|
else
|
|
{
|
|
pResponse->SendEnqueued = FALSE;
|
|
UlpSendHttpResponseWorker( &pTracker->WorkItem );
|
|
}
|
|
|
|
return STATUS_PENDING;
|
|
|
|
cleanup:
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"UlSendHttpResponse: failure %08lx\n",
|
|
Status
|
|
));
|
|
|
|
ASSERT( !NT_SUCCESS( Status ) );
|
|
|
|
if (pTracker != NULL)
|
|
{
|
|
//
|
|
// Very early termination for the chunk tracker. RefCounting not
|
|
// even started yet. ( Means UlpSendHttpResponseWorker hasn't been
|
|
// called ). Therefore straight cleanup.
|
|
//
|
|
|
|
ASSERT( pTracker->RefCount == 1 );
|
|
|
|
UlpFreeChunkTracker( &pTracker->WorkItem );
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlSendHttpResponse
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Captures a user-mode HTTP response and morphs it into a form suitable
|
|
for kernel-mode.
|
|
|
|
Arguments:
|
|
|
|
pUserResponse - Supplies the user-mode HTTP response.
|
|
|
|
Flags - Supplies zero or more UL_CAPTURE_* flags.
|
|
|
|
pStatusCode - Receives the captured HTTP status code.
|
|
|
|
pKernelResponse - Receives the captured response if successful.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlCaptureHttpResponse(
|
|
IN PUL_APP_POOL_PROCESS pProcess OPTIONAL,
|
|
IN PHTTP_RESPONSE pUserResponse OPTIONAL,
|
|
IN PUL_INTERNAL_REQUEST pRequest,
|
|
IN USHORT ChunkCount,
|
|
IN PHTTP_DATA_CHUNK pUserDataChunks,
|
|
IN UL_CAPTURE_FLAGS Flags,
|
|
IN ULONG SendFlags,
|
|
IN BOOLEAN CaptureCache,
|
|
IN PHTTP_LOG_FIELDS_DATA pUserLogData OPTIONAL,
|
|
OUT PUSHORT pStatusCode,
|
|
OUT PUL_INTERNAL_RESPONSE *ppKernelResponse
|
|
)
|
|
{
|
|
ULONG i;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PUL_INTERNAL_RESPONSE pKeResponse = NULL;
|
|
PUL_HTTP_CONNECTION pHttpConn;
|
|
ULONG AuxBufferLength;
|
|
ULONG CopiedBufferLength;
|
|
ULONG UncopiedBufferLength;
|
|
PUCHAR pBuffer;
|
|
ULONG HeaderLength;
|
|
ULONG VariableHeaderLength = 0;
|
|
ULONG SpaceLength;
|
|
PUL_INTERNAL_DATA_CHUNK pKeDataChunks;
|
|
BOOLEAN FromKernelMode;
|
|
BOOLEAN FromLookaside;
|
|
ULONG KernelChunkCount;
|
|
HTTP_KNOWN_HEADER ETagHeader = { 0, NULL };
|
|
HTTP_KNOWN_HEADER ContentEncodingHeader = { 0, NULL };
|
|
KPROCESSOR_MODE RequestorMode;
|
|
HTTP_VERSION Version;
|
|
HTTP_VERB Verb;
|
|
PHTTP_KNOWN_HEADER pKnownHeaders;
|
|
USHORT RawValueLength;
|
|
PCSTR pRawValue;
|
|
UNICODE_STRING KernelFragmentName;
|
|
UNICODE_STRING UserFragmentName;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pRequest ) );
|
|
ASSERT( pUserDataChunks != NULL || ChunkCount == 0 );
|
|
ASSERT( ppKernelResponse != NULL );
|
|
|
|
Version = pRequest->Version;
|
|
Verb = pRequest->Verb;
|
|
pHttpConn = pRequest->pHttpConn;
|
|
|
|
__try
|
|
{
|
|
FromKernelMode =
|
|
(BOOLEAN) ((Flags & UlCaptureKernelMode) == UlCaptureKernelMode);
|
|
|
|
if (ChunkCount >= UL_MAX_CHUNKS)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (FromKernelMode)
|
|
{
|
|
RequestorMode = (KPROCESSOR_MODE) KernelMode;
|
|
}
|
|
else
|
|
{
|
|
RequestorMode = (KPROCESSOR_MODE) UserMode;
|
|
}
|
|
|
|
if (pUserResponse)
|
|
{
|
|
UlProbeForRead(
|
|
pUserResponse,
|
|
sizeof(HTTP_RESPONSE),
|
|
sizeof(PVOID),
|
|
RequestorMode
|
|
);
|
|
|
|
//
|
|
// Remember the HTTP status code for ETW tracing.
|
|
//
|
|
|
|
if (pStatusCode)
|
|
{
|
|
*pStatusCode = pUserResponse->StatusCode;
|
|
}
|
|
}
|
|
|
|
//
|
|
// ProbeForRead every buffer we will access.
|
|
//
|
|
|
|
Status = UlpProbeHttpResponse(
|
|
pUserResponse,
|
|
ChunkCount,
|
|
pUserDataChunks,
|
|
Flags,
|
|
pUserLogData,
|
|
RequestorMode
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Figure out how much memory we need.
|
|
//
|
|
|
|
Status = UlComputeFixedHeaderSize(
|
|
Version,
|
|
pUserResponse,
|
|
RequestorMode,
|
|
&HeaderLength
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Allocate space for variable headers with fixed headers.
|
|
//
|
|
|
|
if (HeaderLength)
|
|
{
|
|
VariableHeaderLength = g_UlMaxVariableHeaderSize;
|
|
}
|
|
|
|
UlpComputeChunkBufferSizes(
|
|
ChunkCount,
|
|
pUserDataChunks,
|
|
Flags,
|
|
&AuxBufferLength,
|
|
&CopiedBufferLength,
|
|
&UncopiedBufferLength
|
|
);
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"Http!UlCaptureHttpResponse(pUserResponse = %p) "
|
|
" ChunkCount = %d\n"
|
|
" Flags = 0x%x\n"
|
|
" AuxBufferLength = 0x%x\n"
|
|
" UncopiedBufferLength = 0x%x\n",
|
|
pUserResponse,
|
|
ChunkCount,
|
|
Flags,
|
|
AuxBufferLength,
|
|
UncopiedBufferLength
|
|
));
|
|
|
|
//
|
|
// Add two extra chunks for the headers (fixed & variable).
|
|
//
|
|
|
|
if (HeaderLength > 0)
|
|
{
|
|
KernelChunkCount = ChunkCount + HEADER_CHUNK_COUNT;
|
|
}
|
|
else
|
|
{
|
|
KernelChunkCount = ChunkCount;
|
|
}
|
|
|
|
//
|
|
// Compute the space needed for all of our structures.
|
|
//
|
|
|
|
SpaceLength = (KernelChunkCount * sizeof(UL_INTERNAL_DATA_CHUNK))
|
|
+ ALIGN_UP(HeaderLength, sizeof(CHAR))
|
|
+ ALIGN_UP(VariableHeaderLength, sizeof(CHAR))
|
|
+ AuxBufferLength;
|
|
|
|
//
|
|
// Add space for ETag and Content-Encoding if it exists, including space
|
|
// for ANSI_NULL.
|
|
//
|
|
|
|
if (CaptureCache && pUserResponse)
|
|
{
|
|
ETagHeader = pUserResponse->Headers.KnownHeaders[HttpHeaderEtag];
|
|
|
|
if (ETagHeader.RawValueLength)
|
|
{
|
|
SpaceLength += (ETagHeader.RawValueLength + sizeof(CHAR));
|
|
|
|
UlProbeAnsiString(
|
|
ETagHeader.pRawValue,
|
|
ETagHeader.RawValueLength,
|
|
RequestorMode
|
|
);
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"http!UlCaptureHttpResponse(pUserResponse = %p)\n"
|
|
" ETag: %s\n"
|
|
" Length: %d\n",
|
|
pUserResponse,
|
|
ETagHeader.pRawValue,
|
|
ETagHeader.RawValueLength
|
|
));
|
|
}
|
|
|
|
ContentEncodingHeader =
|
|
pUserResponse->Headers.KnownHeaders[HttpHeaderContentEncoding];
|
|
|
|
if (ContentEncodingHeader.RawValueLength)
|
|
{
|
|
SpaceLength += (ContentEncodingHeader.RawValueLength +
|
|
sizeof(CHAR));
|
|
|
|
UlProbeAnsiString(
|
|
ContentEncodingHeader.pRawValue,
|
|
ContentEncodingHeader.RawValueLength,
|
|
RequestorMode
|
|
);
|
|
|
|
//
|
|
// FUTURE: if the app sets the content encoding to "identity",
|
|
// treat it as empty.
|
|
//
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"http!UlCaptureHttpResponse(pUserResponse = %p)\n"
|
|
" ContentEncoding: %s\n"
|
|
" Length: %d\n",
|
|
pUserResponse,
|
|
ContentEncodingHeader.pRawValue,
|
|
ContentEncodingHeader.RawValueLength
|
|
));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Allocate the internal response.
|
|
//
|
|
|
|
if (pUserResponse &&
|
|
g_UlResponseBufferSize >=
|
|
(ALIGN_UP(sizeof(UL_INTERNAL_RESPONSE), PVOID) + SpaceLength))
|
|
{
|
|
pKeResponse = UlPplAllocateResponseBuffer();
|
|
FromLookaside = TRUE;
|
|
}
|
|
else
|
|
{
|
|
pKeResponse = UL_ALLOCATE_STRUCT_WITH_SPACE(
|
|
NonPagedPool,
|
|
UL_INTERNAL_RESPONSE,
|
|
SpaceLength,
|
|
UL_INTERNAL_RESPONSE_POOL_TAG
|
|
);
|
|
FromLookaside = FALSE;
|
|
}
|
|
|
|
if (pKeResponse == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Initialize the fixed fields in the response.
|
|
//
|
|
|
|
pKeResponse->FromLookaside = FromLookaside;
|
|
|
|
pKeResponse->Signature = UL_INTERNAL_RESPONSE_POOL_TAG;
|
|
pKeResponse->ReferenceCount = 1;
|
|
pKeResponse->ChunkCount = KernelChunkCount;
|
|
|
|
//
|
|
// Note that we set the current chunk to just *before* the first
|
|
// chunk, then call the increment function. This allows us to go
|
|
// through the common increment/update path.
|
|
//
|
|
|
|
pKeResponse->CurrentChunk = ULONG_MAX;
|
|
pKeResponse->FileOffset.QuadPart = 0;
|
|
pKeResponse->FileBytesRemaining.QuadPart = 0;
|
|
|
|
RtlZeroMemory(
|
|
pKeResponse->pDataChunks,
|
|
sizeof(UL_INTERNAL_DATA_CHUNK) * KernelChunkCount
|
|
);
|
|
|
|
UL_REFERENCE_INTERNAL_REQUEST( pRequest );
|
|
pKeResponse->pRequest = pRequest;
|
|
|
|
pKeResponse->Flags = SendFlags;
|
|
pKeResponse->SyncRead = FALSE;
|
|
pKeResponse->ContentLengthSpecified = FALSE;
|
|
pKeResponse->ChunkedSpecified = FALSE;
|
|
pKeResponse->CopySend = FALSE;
|
|
pKeResponse->ResponseLength = 0;
|
|
pKeResponse->FromMemoryLength = 0;
|
|
pKeResponse->BytesTransferred = 0;
|
|
pKeResponse->IoStatus.Status = STATUS_SUCCESS;
|
|
pKeResponse->IoStatus.Information = 0;
|
|
pKeResponse->pIrp = NULL;
|
|
pKeResponse->StatusCode = 0;
|
|
pKeResponse->FromKernelMode = FromKernelMode;
|
|
pKeResponse->MaxFileSystemStackSize = 0;
|
|
pKeResponse->CreationTime.QuadPart = 0;
|
|
pKeResponse->ETagLength = 0;
|
|
pKeResponse->pETag = NULL;
|
|
pKeResponse->pContentEncoding = NULL;
|
|
pKeResponse->ContentEncodingLength = 0;
|
|
pKeResponse->GenDateHeader = TRUE;
|
|
pKeResponse->ConnHeader = ConnHdrKeepAlive;
|
|
pKeResponse->pLogData = NULL;
|
|
pKeResponse->pCompletionRoutine = NULL;
|
|
pKeResponse->pCompletionContext = NULL;
|
|
|
|
UlInitializePushLock(
|
|
&pKeResponse->PushLock,
|
|
"UL_INTERNAL_RESPONSE[%p].PushLock",
|
|
pKeResponse,
|
|
UL_INTERNAL_RESPONSE_PUSHLOCK_TAG
|
|
);
|
|
|
|
//
|
|
// Decide whether we need to resume parsing and how. Ideally
|
|
// if we have seen the last response, we should be able to
|
|
// resume parsing right away after the send but before the
|
|
// send completion. When requests are pipelined, this arrangement
|
|
// alleviates the problem of delayed-ACK of 200ms when an odd numbers
|
|
// of TCP frames are sent. The logic is disabled if we have reached
|
|
// the limit of concurrent outstanding pipelined requests we allow.
|
|
//
|
|
|
|
if (0 == (SendFlags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA))
|
|
{
|
|
if (pHttpConn->PipelinedRequests < g_UlMaxPipelinedRequests &&
|
|
0 == pRequest->ContentLength &&
|
|
0 == pRequest->Chunked)
|
|
{
|
|
pKeResponse->ResumeParsingType = UlResumeParsingOnLastSend;
|
|
}
|
|
else
|
|
{
|
|
pKeResponse->ResumeParsingType = UlResumeParsingOnSendCompletion;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pKeResponse->ResumeParsingType = UlResumeParsingNone;
|
|
}
|
|
|
|
RtlZeroMemory(
|
|
&pKeResponse->ContentType,
|
|
sizeof(UL_CONTENT_TYPE)
|
|
);
|
|
|
|
//
|
|
// Point to the header buffer space.
|
|
//
|
|
|
|
pKeResponse->HeaderLength = HeaderLength;
|
|
pKeResponse->pHeaders = (PUCHAR)
|
|
(pKeResponse->pDataChunks + pKeResponse->ChunkCount);
|
|
|
|
//
|
|
// And the variable header buffer space.
|
|
//
|
|
|
|
pKeResponse->VariableHeaderLength = VariableHeaderLength;
|
|
pKeResponse->pVariableHeader = pKeResponse->pHeaders + HeaderLength;
|
|
|
|
//
|
|
// And the aux buffer space.
|
|
//
|
|
|
|
pKeResponse->AuxBufferLength = AuxBufferLength;
|
|
pKeResponse->pAuxiliaryBuffer = (PVOID)
|
|
(pKeResponse->pHeaders + HeaderLength + VariableHeaderLength);
|
|
|
|
//
|
|
// And the ETag and Content-Encoding buffer space plus the ANSI_NULLs.
|
|
//
|
|
|
|
if (ETagHeader.RawValueLength)
|
|
{
|
|
pKeResponse->ETagLength = ETagHeader.RawValueLength + sizeof(CHAR);
|
|
pKeResponse->pETag = (PUCHAR) pKeResponse->pAuxiliaryBuffer +
|
|
AuxBufferLength;
|
|
}
|
|
|
|
if (ContentEncodingHeader.RawValueLength)
|
|
{
|
|
pKeResponse->ContentEncodingLength =
|
|
(ContentEncodingHeader.RawValueLength + sizeof(CHAR));
|
|
pKeResponse->pContentEncoding =
|
|
(PUCHAR) pKeResponse->pAuxiliaryBuffer +
|
|
AuxBufferLength +
|
|
pKeResponse->ETagLength ;
|
|
}
|
|
|
|
//
|
|
// Remember if a Content-Length header was specified.
|
|
//
|
|
|
|
if (pUserResponse != NULL)
|
|
{
|
|
pKeResponse->Verb = Verb;
|
|
pKeResponse->StatusCode = pUserResponse->StatusCode;
|
|
|
|
if (pKeResponse->StatusCode > UL_MAX_HTTP_STATUS_CODE)
|
|
{
|
|
ExRaiseStatus( STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
pKnownHeaders = pUserResponse->Headers.KnownHeaders;
|
|
|
|
//
|
|
// If the response explicitly deletes the Connection: header,
|
|
// make sure we DON'T generate it.
|
|
//
|
|
|
|
RawValueLength = pKnownHeaders[HttpHeaderConnection].RawValueLength;
|
|
pRawValue = pKnownHeaders[HttpHeaderConnection].pRawValue;
|
|
|
|
if (0 == RawValueLength && NULL != pRawValue)
|
|
{
|
|
UlProbeAnsiString(
|
|
pRawValue,
|
|
sizeof(ANSI_NULL),
|
|
RequestorMode
|
|
);
|
|
|
|
if (ANSI_NULL == pRawValue[0])
|
|
{
|
|
pKeResponse->ConnHeader = ConnHdrNone;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Decide if we need to generate a Date: header.
|
|
//
|
|
|
|
RawValueLength = pKnownHeaders[HttpHeaderDate].RawValueLength;
|
|
pRawValue = pKnownHeaders[HttpHeaderDate].pRawValue;
|
|
|
|
if (0 == RawValueLength && NULL != pRawValue)
|
|
{
|
|
UlProbeAnsiString(
|
|
pRawValue,
|
|
sizeof(ANSI_NULL),
|
|
RequestorMode
|
|
);
|
|
|
|
//
|
|
// Only permit non-generation in the "delete" case.
|
|
//
|
|
|
|
if (ANSI_NULL == pRawValue[0])
|
|
{
|
|
pKeResponse->GenDateHeader = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pKeResponse->GenDateHeader = TRUE;
|
|
}
|
|
|
|
if (pKnownHeaders[HttpHeaderContentLength].RawValueLength > 0)
|
|
{
|
|
pKeResponse->ContentLengthSpecified = TRUE;
|
|
}
|
|
|
|
//
|
|
// As long as we're here, also remember if "Chunked"
|
|
// Transfer-Encoding was specified.
|
|
//
|
|
|
|
if (UlpIsChunkSpecified(pKnownHeaders, RequestorMode))
|
|
{
|
|
//
|
|
// NOTE: If a response has a chunked Transfer-Encoding,
|
|
// then it shouldn't have a Content-Length
|
|
//
|
|
|
|
if (pKeResponse->ContentLengthSpecified)
|
|
{
|
|
ExRaiseStatus( STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
pKeResponse->ChunkedSpecified = TRUE;
|
|
}
|
|
|
|
//
|
|
// Only capture the following if we're building a cached response.
|
|
//
|
|
|
|
if (CaptureCache)
|
|
{
|
|
//
|
|
// Capture the ETag and put it on the UL_INTERNAL_RESPONSE.
|
|
//
|
|
|
|
if (ETagHeader.RawValueLength)
|
|
{
|
|
//
|
|
// NOTE: Already probed above
|
|
//
|
|
|
|
RtlCopyMemory(
|
|
pKeResponse->pETag, // Dest
|
|
ETagHeader.pRawValue, // Src
|
|
ETagHeader.RawValueLength // Bytes
|
|
);
|
|
|
|
//
|
|
// Add NULL termination.
|
|
//
|
|
|
|
pKeResponse->pETag[ETagHeader.RawValueLength] = ANSI_NULL;
|
|
}
|
|
|
|
//
|
|
// Capture the ContentType and put it on the
|
|
// UL_INTERNAL_RESPONSE.
|
|
//
|
|
|
|
pRawValue = pKnownHeaders[HttpHeaderContentType].pRawValue;
|
|
RawValueLength =
|
|
pKnownHeaders[HttpHeaderContentType].RawValueLength;
|
|
|
|
if (RawValueLength > 0)
|
|
{
|
|
UlProbeAnsiString(
|
|
pRawValue,
|
|
RawValueLength,
|
|
RequestorMode
|
|
);
|
|
|
|
UlGetTypeAndSubType(
|
|
pRawValue,
|
|
RawValueLength,
|
|
&pKeResponse->ContentType
|
|
);
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"http!UlCaptureHttpResponse(pUserResponse = %p) \n"
|
|
" Content Type: %s \n"
|
|
" Content SubType: %s\n",
|
|
pUserResponse,
|
|
(pKeResponse->ContentType.Type ?
|
|
pKeResponse->ContentType.Type : (PUCHAR)"<none>"),
|
|
(pKeResponse->ContentType.SubType ?
|
|
pKeResponse->ContentType.SubType : (PUCHAR)"<none>")
|
|
));
|
|
}
|
|
|
|
//
|
|
// Capture the Content-Encoding
|
|
//
|
|
|
|
if (ContentEncodingHeader.RawValueLength)
|
|
{
|
|
//
|
|
// NOTE: Already probed above
|
|
//
|
|
|
|
RtlCopyMemory(
|
|
pKeResponse->pContentEncoding, // Dest
|
|
ContentEncodingHeader.pRawValue, // Src
|
|
ContentEncodingHeader.RawValueLength // Bytes
|
|
);
|
|
|
|
//
|
|
// Add NULL termination.
|
|
//
|
|
|
|
pKeResponse->pContentEncoding[
|
|
ContentEncodingHeader.RawValueLength] = ANSI_NULL;
|
|
}
|
|
|
|
//
|
|
// Capture the Last-Modified time (if it exists).
|
|
//
|
|
|
|
pRawValue = pKnownHeaders[HttpHeaderLastModified].pRawValue;
|
|
RawValueLength =
|
|
pKnownHeaders[HttpHeaderLastModified].RawValueLength;
|
|
|
|
if (RawValueLength)
|
|
{
|
|
UlProbeAnsiString(
|
|
pRawValue,
|
|
RawValueLength,
|
|
RequestorMode
|
|
);
|
|
|
|
if (!StringTimeToSystemTime(
|
|
pRawValue,
|
|
RawValueLength,
|
|
&pKeResponse->CreationTime
|
|
))
|
|
{
|
|
ExRaiseStatus( STATUS_INVALID_PARAMETER );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
KeQuerySystemTime( &pKeResponse->CreationTime );
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy the aux bytes from the chunks.
|
|
//
|
|
|
|
pBuffer = (PUCHAR) pKeResponse->pAuxiliaryBuffer;
|
|
|
|
//
|
|
// Skip the header chunks.
|
|
//
|
|
|
|
if (pKeResponse->HeaderLength > 0)
|
|
{
|
|
pKeDataChunks = pKeResponse->pDataChunks + HEADER_CHUNK_COUNT;
|
|
}
|
|
else
|
|
{
|
|
pKeDataChunks = pKeResponse->pDataChunks;
|
|
}
|
|
|
|
for (i = 0 ; i < ChunkCount ; i++)
|
|
{
|
|
pKeDataChunks[i].ChunkType = pUserDataChunks[i].DataChunkType;
|
|
|
|
switch (pUserDataChunks[i].DataChunkType)
|
|
{
|
|
case HttpDataChunkFromMemory:
|
|
|
|
//
|
|
// From-memory chunk. If the caller wants us to copy
|
|
// the data (or if its relatively small), then do it
|
|
// We allocate space for all of the copied data and any
|
|
// filename buffers. Otherwise (it's OK to just lock
|
|
// down the data), then allocate a MDL describing the
|
|
// user's buffer and lock it down. Note that
|
|
// MmProbeAndLockPages() and MmLockPagesSpecifyCache()
|
|
// will raise exceptions if they fail.
|
|
//
|
|
|
|
pKeResponse->FromMemoryLength +=
|
|
pUserDataChunks[i].FromMemory.BufferLength;
|
|
|
|
pKeDataChunks[i].FromMemory.pCopiedBuffer = NULL;
|
|
|
|
if ((Flags & UlCaptureCopyData) ||
|
|
pUserDataChunks[i].FromMemory.BufferLength
|
|
<= g_UlMaxCopyThreshold)
|
|
{
|
|
ASSERT(pKeResponse->AuxBufferLength > 0);
|
|
|
|
pKeDataChunks[i].FromMemory.pUserBuffer =
|
|
pUserDataChunks[i].FromMemory.pBuffer;
|
|
|
|
pKeDataChunks[i].FromMemory.BufferLength =
|
|
pUserDataChunks[i].FromMemory.BufferLength;
|
|
|
|
RtlCopyMemory(
|
|
pBuffer,
|
|
pKeDataChunks[i].FromMemory.pUserBuffer,
|
|
pKeDataChunks[i].FromMemory.BufferLength
|
|
);
|
|
|
|
pKeDataChunks[i].FromMemory.pCopiedBuffer = pBuffer;
|
|
pBuffer += pKeDataChunks[i].FromMemory.BufferLength;
|
|
|
|
//
|
|
// Allocate a new MDL describing our new location
|
|
// in the auxiliary buffer, then build the MDL
|
|
// to properly describe nonpaged pool.
|
|
//
|
|
|
|
pKeDataChunks[i].FromMemory.pMdl =
|
|
UlAllocateMdl(
|
|
pKeDataChunks[i].FromMemory.pCopiedBuffer,
|
|
pKeDataChunks[i].FromMemory.BufferLength,
|
|
FALSE,
|
|
FALSE,
|
|
NULL
|
|
);
|
|
|
|
if (pKeDataChunks[i].FromMemory.pMdl == NULL)
|
|
{
|
|
ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
|
|
break;
|
|
}
|
|
|
|
MmBuildMdlForNonPagedPool(
|
|
pKeDataChunks[i].FromMemory.pMdl
|
|
);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Build a MDL describing the user's buffer.
|
|
//
|
|
|
|
pKeDataChunks[i].FromMemory.BufferLength =
|
|
pUserDataChunks[i].FromMemory.BufferLength;
|
|
|
|
pKeDataChunks[i].FromMemory.pMdl =
|
|
UlAllocateMdl(
|
|
pUserDataChunks[i].FromMemory.pBuffer,
|
|
pUserDataChunks[i].FromMemory.BufferLength,
|
|
FALSE,
|
|
FALSE,
|
|
NULL
|
|
);
|
|
|
|
if (pKeDataChunks[i].FromMemory.pMdl == NULL)
|
|
{
|
|
ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Lock it down.
|
|
//
|
|
|
|
MmProbeAndLockPages(
|
|
pKeDataChunks[i].FromMemory.pMdl, // MDL
|
|
UserMode, // AccessMode
|
|
IoReadAccess // Operation
|
|
);
|
|
|
|
if (CaptureCache)
|
|
{
|
|
MmMapLockedPagesSpecifyCache(
|
|
pKeDataChunks[i].FromMemory.pMdl,
|
|
KernelMode,
|
|
MmCached,
|
|
NULL,
|
|
FALSE,
|
|
LowPagePriority
|
|
);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case HttpDataChunkFromFileHandle:
|
|
|
|
//
|
|
// From handle.
|
|
//
|
|
|
|
pKeDataChunks[i].FromFileHandle.ByteRange =
|
|
pUserDataChunks[i].FromFileHandle.ByteRange;
|
|
|
|
pKeDataChunks[i].FromFileHandle.FileHandle =
|
|
pUserDataChunks[i].FromFileHandle.FileHandle;
|
|
|
|
break;
|
|
|
|
case HttpDataChunkFromFragmentCache:
|
|
|
|
//
|
|
// From fragment cache.
|
|
//
|
|
|
|
if (CaptureCache)
|
|
{
|
|
//
|
|
// Content from fragment cache are meant to be dynamic
|
|
// so they shouldn't go to the static response cache.
|
|
//
|
|
|
|
Status = STATUS_NOT_SUPPORTED;
|
|
goto end;
|
|
}
|
|
|
|
UserFragmentName.Buffer = (PWSTR)
|
|
pUserDataChunks[i].FromFragmentCache.pFragmentName;
|
|
UserFragmentName.Length =
|
|
pUserDataChunks[i].FromFragmentCache.FragmentNameLength;
|
|
UserFragmentName.MaximumLength =
|
|
UserFragmentName.Length;
|
|
|
|
Status = UlProbeAndCaptureUnicodeString(
|
|
&UserFragmentName,
|
|
RequestorMode,
|
|
&KernelFragmentName,
|
|
0
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
Status = UlCheckoutFragmentCacheEntry(
|
|
KernelFragmentName.Buffer,
|
|
KernelFragmentName.Length,
|
|
pProcess,
|
|
&pKeDataChunks[i].FromFragmentCache.pCacheEntry
|
|
);
|
|
|
|
UlFreeCapturedUnicodeString( &KernelFragmentName );
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
ASSERT( pKeDataChunks[i].FromFragmentCache.pCacheEntry );
|
|
|
|
break;
|
|
|
|
default :
|
|
|
|
ExRaiseStatus( STATUS_INVALID_PARAMETER );
|
|
break;
|
|
|
|
} // switch (pUserDataChunks[i].DataChunkType)
|
|
|
|
} // for (i = 0 ; i < ChunkCount ; i++)
|
|
|
|
//
|
|
// Ensure we didn't mess up our buffer calculations.
|
|
//
|
|
|
|
ASSERT( DIFF(pBuffer - (PUCHAR)(pKeResponse->pAuxiliaryBuffer)) ==
|
|
AuxBufferLength );
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"Http!UlCaptureHttpResponse: captured %p from user %p\n",
|
|
pKeResponse,
|
|
pUserResponse
|
|
));
|
|
}
|
|
__except( UL_EXCEPTION_FILTER() )
|
|
{
|
|
Status = UL_CONVERT_EXCEPTION_CODE( GetExceptionCode() );
|
|
}
|
|
|
|
end:
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
if (pKeResponse != NULL)
|
|
{
|
|
UlpDestroyCapturedResponse( pKeResponse );
|
|
pKeResponse = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Return the captured response.
|
|
//
|
|
|
|
*ppKernelResponse = pKeResponse;
|
|
|
|
RETURN( Status );
|
|
|
|
} // UlCaptureHttpResponse
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Captures a user-mode log data to kernel pLogData structure.
|
|
|
|
Arguments:
|
|
|
|
pCapturedUserLogData - Supplies the captured HTTP_LOG_FIELDS_DATA.
|
|
However there are still embedded pointers pointing to user mode
|
|
memory inside this captured structure.
|
|
|
|
pRequest - Supplies the request to capture.
|
|
|
|
ppKernelLogData - Buffer to hold the pointer to the newly allocated
|
|
LogData. WILL be set to null if logging is disabled
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlCaptureUserLogData(
|
|
IN PHTTP_LOG_FIELDS_DATA pCapturedUserLogData,
|
|
IN PUL_INTERNAL_REQUEST pRequest,
|
|
OUT PUL_LOG_DATA_BUFFER *ppKernelLogData
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PUL_CONFIG_GROUP_OBJECT pLoggingConfig;
|
|
BOOLEAN BinaryLoggingEnabled;
|
|
PUL_LOG_DATA_BUFFER pLogData;
|
|
|
|
ASSERT( pCapturedUserLogData );
|
|
ASSERT( ppKernelLogData );
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pRequest ) );
|
|
|
|
//
|
|
// Init the caller's pLogData pointer to NULL.
|
|
//
|
|
|
|
*ppKernelLogData = pLogData = NULL;
|
|
|
|
//
|
|
// Capture the log data. Note that the binary logging takes
|
|
// precedence over the normal logging.
|
|
//
|
|
|
|
BinaryLoggingEnabled = UlBinaryLoggingEnabled(
|
|
pRequest->ConfigInfo.pControlChannel
|
|
);
|
|
|
|
pLoggingConfig = pRequest->ConfigInfo.pLoggingConfig;
|
|
|
|
__try
|
|
{
|
|
//
|
|
// If either type of logging is enabled, then allocate a kernel buffer
|
|
// and capture it down.
|
|
//
|
|
|
|
if (BinaryLoggingEnabled)
|
|
{
|
|
Status = UlCaptureRawLogData(
|
|
pCapturedUserLogData,
|
|
pRequest,
|
|
&pLogData
|
|
);
|
|
}
|
|
else
|
|
if (pLoggingConfig)
|
|
{
|
|
ASSERT( IS_VALID_CONFIG_GROUP( pLoggingConfig ) );
|
|
|
|
switch(pLoggingConfig->LoggingConfig.LogFormat)
|
|
{
|
|
case HttpLoggingTypeW3C:
|
|
|
|
Status = UlCaptureLogFieldsW3C(
|
|
pCapturedUserLogData,
|
|
pRequest,
|
|
&pLogData
|
|
);
|
|
break;
|
|
|
|
case HttpLoggingTypeNCSA:
|
|
|
|
Status = UlCaptureLogFieldsNCSA(
|
|
pCapturedUserLogData,
|
|
pRequest,
|
|
&pLogData
|
|
);
|
|
break;
|
|
|
|
case HttpLoggingTypeIIS:
|
|
|
|
Status = UlCaptureLogFieldsIIS(
|
|
pCapturedUserLogData,
|
|
pRequest,
|
|
&pLogData
|
|
);
|
|
break;
|
|
|
|
default:
|
|
|
|
ASSERT( !"Invalid Text Logging Format!" );
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return STATUS_SUCCESS; // Logging is disabled for this site.
|
|
}
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Success set the callers buffer to point to the freshly
|
|
// allocated/formatted pLogData.
|
|
//
|
|
|
|
ASSERT( IS_VALID_LOG_DATA_BUFFER( pLogData ) );
|
|
*ppKernelLogData = pLogData;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// If the logging capture function returns an error,
|
|
// kernel buffer should not have been allocated.
|
|
//
|
|
|
|
ASSERT( pLogData == NULL );
|
|
}
|
|
}
|
|
__except( UL_EXCEPTION_FILTER() )
|
|
{
|
|
if (pLogData)
|
|
{
|
|
//
|
|
// If the logging capture function raised and exception
|
|
// after allocating a pLogData, we need to cleanup here.
|
|
//
|
|
|
|
UlDestroyLogDataBuffer( pLogData );
|
|
}
|
|
|
|
Status = UL_CONVERT_EXCEPTION_CODE( GetExceptionCode() );
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlCaptureUserLogData
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Probes all the buffers passed to use in a user-mode HTTP response.
|
|
|
|
Arguments:
|
|
|
|
pUserResponse - Supplies the response to probe.
|
|
|
|
ChunkCount - Supplies the number of data chunks.
|
|
|
|
pDataChunks - Supplies the array of data chunks.
|
|
|
|
Flags - Capture flags.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpProbeHttpResponse(
|
|
IN PHTTP_RESPONSE pUserResponse OPTIONAL,
|
|
IN USHORT ChunkCount,
|
|
IN PHTTP_DATA_CHUNK pCapturedDataChunks OPTIONAL,
|
|
IN UL_CAPTURE_FLAGS Flags,
|
|
IN PHTTP_LOG_FIELDS_DATA pCapturedLogData OPTIONAL,
|
|
IN KPROCESSOR_MODE RequestorMode
|
|
)
|
|
{
|
|
USHORT KeyUriLength;
|
|
PCWSTR pKeyUri;
|
|
NTSTATUS Status;
|
|
ULONG i;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
__try
|
|
{
|
|
//
|
|
// Probe the response structure if it exits.
|
|
//
|
|
|
|
if (pUserResponse)
|
|
{
|
|
if (pUserResponse->Flags & ~HTTP_RESPONSE_FLAG_VALID)
|
|
{
|
|
ExRaiseStatus( STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// We don't support trailers for this release.
|
|
//
|
|
|
|
if (pUserResponse->Headers.TrailerCount != 0 ||
|
|
pUserResponse->Headers.pTrailers != NULL)
|
|
{
|
|
ExRaiseStatus( STATUS_INVALID_PARAMETER );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Probe the log data if it exists.
|
|
//
|
|
|
|
if (pCapturedLogData)
|
|
{
|
|
UlProbeLogData( pCapturedLogData, RequestorMode );
|
|
}
|
|
|
|
//
|
|
// And now the body part.
|
|
//
|
|
|
|
if (pCapturedDataChunks != NULL)
|
|
{
|
|
for (i = 0 ; i < ChunkCount ; i++)
|
|
{
|
|
switch (pCapturedDataChunks[i].DataChunkType)
|
|
{
|
|
case HttpDataChunkFromMemory:
|
|
|
|
//
|
|
// From-memory chunk.
|
|
//
|
|
|
|
if (pCapturedDataChunks[i].FromMemory.BufferLength == 0 ||
|
|
pCapturedDataChunks[i].FromMemory.pBuffer == NULL)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if ((Flags & UlCaptureCopyData) ||
|
|
pCapturedDataChunks[i].FromMemory.BufferLength <=
|
|
g_UlMaxCopyThreshold)
|
|
{
|
|
UlProbeForRead(
|
|
pCapturedDataChunks[i].FromMemory.pBuffer,
|
|
pCapturedDataChunks[i].FromMemory.BufferLength,
|
|
sizeof(CHAR),
|
|
RequestorMode
|
|
);
|
|
}
|
|
|
|
break;
|
|
|
|
case HttpDataChunkFromFileHandle:
|
|
|
|
//
|
|
// From handle chunk. the handle will be validated later
|
|
// by the object manager.
|
|
//
|
|
|
|
break;
|
|
|
|
case HttpDataChunkFromFragmentCache:
|
|
|
|
KeyUriLength =
|
|
pCapturedDataChunks[i].FromFragmentCache.FragmentNameLength;
|
|
pKeyUri =
|
|
pCapturedDataChunks[i].FromFragmentCache.pFragmentName;
|
|
|
|
//
|
|
// From-fragment-cache chunk. Probe the KeyUri buffer.
|
|
//
|
|
|
|
UlProbeWideString(
|
|
pKeyUri,
|
|
KeyUriLength,
|
|
RequestorMode
|
|
);
|
|
|
|
break;
|
|
|
|
default :
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
|
|
} // switch (pCapturedDataChunks[i].DataChunkType)
|
|
|
|
} // for (i = 0 ; i < ChunkCount ; i++)
|
|
|
|
} // if (pCapturedDataChunks != NULL)
|
|
}
|
|
__except( UL_EXCEPTION_FILTER() )
|
|
{
|
|
Status = UL_CONVERT_EXCEPTION_CODE( GetExceptionCode() );
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlpProbeHttpResponse
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Figures out how much space we need in the internal response aux buffer.
|
|
The buffer contains copied memory chunks, and names of files to open.
|
|
|
|
CODEWORK: need to be aware of chunk encoding.
|
|
|
|
Arguments:
|
|
|
|
ChunkCount - The number of chunks in the array.
|
|
|
|
pDataChunks - The array of data chunks.
|
|
|
|
Flags - Capture flags.
|
|
|
|
pAuxBufferSize - Returns the size of the aux buffer.
|
|
|
|
pCopiedMemorySize - Returns the size of user buffer that is going to
|
|
be copied.
|
|
|
|
pUncopiedMemorySize - Returns the size of the user buffer that is
|
|
going to be ProbeAndLocked.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpComputeChunkBufferSizes(
|
|
IN ULONG ChunkCount,
|
|
IN PHTTP_DATA_CHUNK pDataChunks,
|
|
IN UL_CAPTURE_FLAGS Flags,
|
|
OUT PULONG pAuxBufferSize,
|
|
OUT PULONG pCopiedMemorySize,
|
|
OUT PULONG pUncopiedMemorySize
|
|
)
|
|
{
|
|
ULONG AuxLength = 0;
|
|
ULONG CopiedLength = 0;
|
|
ULONG UncopiedLength = 0;
|
|
ULONG i;
|
|
|
|
for (i = 0; i < ChunkCount; i++)
|
|
{
|
|
switch (pDataChunks[i].DataChunkType)
|
|
{
|
|
case HttpDataChunkFromMemory:
|
|
|
|
//
|
|
// If we're going to copy the chunk, then make some space in
|
|
// the aux buffer.
|
|
//
|
|
|
|
if ((Flags & UlCaptureCopyData) ||
|
|
pDataChunks[i].FromMemory.BufferLength <= g_UlMaxCopyThreshold)
|
|
{
|
|
AuxLength += pDataChunks[i].FromMemory.BufferLength;
|
|
CopiedLength += pDataChunks[i].FromMemory.BufferLength;
|
|
}
|
|
else
|
|
{
|
|
UncopiedLength += pDataChunks[i].FromMemory.BufferLength;
|
|
}
|
|
|
|
break;
|
|
|
|
case HttpDataChunkFromFileHandle:
|
|
case HttpDataChunkFromFragmentCache:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
//
|
|
// We should have caught this in the probe.
|
|
//
|
|
|
|
ASSERT( !"Invalid chunk type" );
|
|
break;
|
|
}
|
|
}
|
|
|
|
*pAuxBufferSize = AuxLength;
|
|
*pCopiedMemorySize = CopiedLength;
|
|
*pUncopiedMemorySize = UncopiedLength;
|
|
|
|
} // UlpComputeChunkBufferSizes
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Prepares the specified response for sending. This preparation
|
|
consists mostly of opening any files referenced by the response.
|
|
|
|
Arguments:
|
|
|
|
Version - Supplies the version of the user response to prepare.
|
|
|
|
pUserResponse - Supplies the user response to prepare.
|
|
|
|
pResponse - Supplies the kernel response to prepare.
|
|
|
|
AccessMode - Supplies the access mode.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlPrepareHttpResponse(
|
|
IN HTTP_VERSION Version,
|
|
IN PHTTP_RESPONSE pUserResponse,
|
|
IN PUL_INTERNAL_RESPONSE pResponse,
|
|
IN KPROCESSOR_MODE AccessMode
|
|
)
|
|
{
|
|
ULONG i;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PUL_INTERNAL_DATA_CHUNK pInternalChunk;
|
|
PUL_FILE_CACHE_ENTRY pFileCacheEntry;
|
|
ULONG HeaderLength;
|
|
CCHAR MaxStackSize = 0;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"Http!UlPrepareHttpResponse: response %p\n",
|
|
pResponse
|
|
));
|
|
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pResponse ) );
|
|
|
|
//
|
|
// Build the HTTP response protocol part and check that the caller
|
|
// passed in headers to send.
|
|
//
|
|
|
|
if (pResponse->HeaderLength > 0)
|
|
{
|
|
ASSERT( pUserResponse != NULL );
|
|
|
|
//
|
|
// Generate the fixed headers.
|
|
//
|
|
|
|
Status = UlGenerateFixedHeaders(
|
|
Version,
|
|
pUserResponse,
|
|
pResponse->StatusCode,
|
|
pResponse->HeaderLength,
|
|
AccessMode,
|
|
pResponse->pHeaders,
|
|
&HeaderLength
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
|
|
if (HeaderLength < pResponse->HeaderLength)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// It is possible that no headers got generated (0.9 request).
|
|
//
|
|
|
|
if (HeaderLength > 0)
|
|
{
|
|
//
|
|
// Build a MDL for it.
|
|
//
|
|
|
|
pResponse->pDataChunks[0].ChunkType = HttpDataChunkFromMemory;
|
|
pResponse->pDataChunks[0].FromMemory.BufferLength =
|
|
pResponse->HeaderLength;
|
|
|
|
pResponse->pDataChunks[0].FromMemory.pMdl =
|
|
UlAllocateMdl(
|
|
pResponse->pHeaders, // VirtualAddress
|
|
pResponse->HeaderLength, // Length
|
|
FALSE, // SecondaryBuffer
|
|
FALSE, // ChargeQuota
|
|
NULL // Irp
|
|
);
|
|
|
|
if (pResponse->pDataChunks[0].FromMemory.pMdl == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
MmBuildMdlForNonPagedPool(
|
|
pResponse->pDataChunks[0].FromMemory.pMdl
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Scan the chunks looking for "from file" chunks.
|
|
//
|
|
|
|
for (i = 0, pInternalChunk = pResponse->pDataChunks;
|
|
i < pResponse->ChunkCount;
|
|
i++, pInternalChunk++)
|
|
{
|
|
switch (pInternalChunk->ChunkType)
|
|
{
|
|
case HttpDataChunkFromFileHandle:
|
|
|
|
//
|
|
// File chunk.
|
|
//
|
|
|
|
pFileCacheEntry = &pInternalChunk->FromFileHandle.FileCacheEntry;
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"UlPrepareHttpResponse: opening handle %p\n",
|
|
&pInternalChunk->FromFileHandle.FileHandle
|
|
));
|
|
|
|
//
|
|
// Found one. Try to open it.
|
|
//
|
|
|
|
Status = UlCreateFileEntry(
|
|
pInternalChunk->FromFileHandle.FileHandle,
|
|
pFileCacheEntry
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
//
|
|
// Check if this is going to be a sync read. All sync reads
|
|
// goto special thread pools since they can be blocking.
|
|
//
|
|
|
|
if (!pFileCacheEntry->BytesPerSector)
|
|
{
|
|
pResponse->SyncRead = TRUE;
|
|
}
|
|
|
|
if (pFileCacheEntry->pDeviceObject->StackSize > MaxStackSize)
|
|
{
|
|
MaxStackSize = pFileCacheEntry->pDeviceObject->StackSize;
|
|
}
|
|
|
|
//
|
|
// Validate & sanitize the specified byte range.
|
|
//
|
|
|
|
Status = UlSanitizeFileByteRange(
|
|
&pInternalChunk->FromFileHandle.ByteRange,
|
|
&pInternalChunk->FromFileHandle.ByteRange,
|
|
pFileCacheEntry->EndOfFile.QuadPart
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
pResponse->ResponseLength +=
|
|
pInternalChunk->FromFileHandle.ByteRange.Length.QuadPart;
|
|
|
|
break;
|
|
|
|
case HttpDataChunkFromMemory:
|
|
|
|
pResponse->ResponseLength +=
|
|
pInternalChunk->FromMemory.BufferLength;
|
|
|
|
break;
|
|
|
|
case HttpDataChunkFromFragmentCache:
|
|
|
|
pResponse->ResponseLength +=
|
|
pInternalChunk->FromFragmentCache.pCacheEntry->ContentLength;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ASSERT( FALSE );
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
|
|
} // switch (pInternalChunk->ChunkType)
|
|
}
|
|
|
|
pResponse->MaxFileSystemStackSize = MaxStackSize;
|
|
|
|
end:
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
//
|
|
// Undo anything done above.
|
|
//
|
|
|
|
UlCleanupHttpResponse( pResponse );
|
|
}
|
|
|
|
RETURN( Status );
|
|
|
|
} // UlPrepareHttpResponse
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Cleans a response by undoing anything done in UlPrepareHttpResponse().
|
|
|
|
Arguments:
|
|
|
|
pResponse - Supplies the response to cleanup.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlCleanupHttpResponse(
|
|
IN PUL_INTERNAL_RESPONSE pResponse
|
|
)
|
|
{
|
|
ULONG i;
|
|
PUL_INTERNAL_DATA_CHUNK pInternalChunk;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"UlCleanupHttpResponse: response %p\n",
|
|
pResponse
|
|
));
|
|
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pResponse ) );
|
|
|
|
//
|
|
// Scan the chunks looking for "from file" chunks.
|
|
//
|
|
|
|
pInternalChunk = pResponse->pDataChunks;
|
|
|
|
for (i = 0; i < pResponse->ChunkCount; i++, pInternalChunk++)
|
|
{
|
|
if (IS_FROM_FILE_HANDLE(pInternalChunk))
|
|
{
|
|
if (!pInternalChunk->FromFileHandle.FileCacheEntry.pFileObject)
|
|
{
|
|
break;
|
|
}
|
|
|
|
UlDestroyFileCacheEntry(
|
|
&pInternalChunk->FromFileHandle.FileCacheEntry
|
|
);
|
|
|
|
pInternalChunk->FromFileHandle.FileCacheEntry.pFileObject = NULL;
|
|
}
|
|
else
|
|
{
|
|
ASSERT( IS_FROM_MEMORY( pInternalChunk ) ||
|
|
IS_FROM_FRAGMENT_CACHE( pInternalChunk ) );
|
|
}
|
|
}
|
|
|
|
} // UlCleanupHttpResponse
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
References the specified response.
|
|
|
|
Arguments:
|
|
|
|
pResponse - Supplies the response to reference.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlReferenceHttpResponse(
|
|
IN PUL_INTERNAL_RESPONSE pResponse
|
|
REFERENCE_DEBUG_FORMAL_PARAMS
|
|
)
|
|
{
|
|
LONG RefCount;
|
|
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pResponse ) );
|
|
|
|
RefCount = InterlockedIncrement( &pResponse->ReferenceCount );
|
|
|
|
WRITE_REF_TRACE_LOG(
|
|
g_pHttpResponseTraceLog,
|
|
REF_ACTION_REFERENCE_HTTP_RESPONSE,
|
|
RefCount,
|
|
pResponse,
|
|
pFileName,
|
|
LineNumber
|
|
);
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"UlReferenceHttpResponse: response %p refcount %ld\n",
|
|
pResponse,
|
|
RefCount
|
|
));
|
|
|
|
} // UlReferenceHttpResponse
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Dereferences the specified response.
|
|
|
|
Arguments:
|
|
|
|
pResponse - Supplies the response to dereference.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlDereferenceHttpResponse(
|
|
IN PUL_INTERNAL_RESPONSE pResponse
|
|
REFERENCE_DEBUG_FORMAL_PARAMS
|
|
)
|
|
{
|
|
LONG RefCount;
|
|
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pResponse ) );
|
|
|
|
RefCount = InterlockedDecrement( &pResponse->ReferenceCount );
|
|
|
|
WRITE_REF_TRACE_LOG(
|
|
g_pHttpResponseTraceLog,
|
|
REF_ACTION_DEREFERENCE_HTTP_RESPONSE,
|
|
RefCount,
|
|
pResponse,
|
|
pFileName,
|
|
LineNumber
|
|
);
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"UlDereferenceHttpResponse: response %p refcount %ld\n",
|
|
pResponse,
|
|
RefCount
|
|
));
|
|
|
|
if (RefCount == 0)
|
|
{
|
|
UlpDestroyCapturedResponse( pResponse );
|
|
}
|
|
|
|
} // UlDereferenceHttpResponse
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
A helper function that allocates an MDL for a range of memory, and
|
|
locks it down. UlpSendCacheEntry uses these MDLs to make sure the
|
|
(normally paged) cache entries don't get paged out when TDI is
|
|
sending them.
|
|
|
|
Arguments:
|
|
|
|
VirtualAddress - Supplies the address of the memory.
|
|
|
|
Length - Supplies the length of the memory to allocate a MDL for.
|
|
|
|
Operation - Either IoWriteAcess or IoReadAccess.
|
|
|
|
Return Values:
|
|
|
|
Pointer to a MDL if success or NULL otherwise.
|
|
|
|
--***************************************************************************/
|
|
PMDL
|
|
UlAllocateLockedMdl(
|
|
IN PVOID VirtualAddress,
|
|
IN ULONG Length,
|
|
IN LOCK_OPERATION Operation
|
|
)
|
|
{
|
|
PMDL pMdl = NULL;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
__try
|
|
{
|
|
pMdl = UlAllocateMdl(
|
|
VirtualAddress, // VirtualAddress
|
|
Length, // Length
|
|
FALSE, // SecondaryBuffer
|
|
FALSE, // ChargeQuota
|
|
NULL // Irp
|
|
);
|
|
|
|
if (pMdl)
|
|
{
|
|
MmProbeAndLockPages(
|
|
pMdl, // MDL
|
|
KernelMode, // AccessMode
|
|
Operation // Operation
|
|
);
|
|
|
|
}
|
|
}
|
|
__except( UL_EXCEPTION_FILTER() )
|
|
{
|
|
Status = UL_CONVERT_EXCEPTION_CODE( GetExceptionCode() );
|
|
|
|
UlFreeMdl( pMdl );
|
|
pMdl = NULL;
|
|
}
|
|
|
|
return pMdl;
|
|
|
|
} // UlAllocateLockedMdl
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Unlocks and frees an MDL allocated with UlAllocateLockedMdl.
|
|
|
|
Arguments:
|
|
|
|
pMdl - Supplies the MDL to free.
|
|
|
|
Return Values:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlFreeLockedMdl(
|
|
IN PMDL pMdl
|
|
)
|
|
{
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
ASSERT( IS_MDL_LOCKED(pMdl) );
|
|
|
|
MmUnlockPages( pMdl );
|
|
UlFreeMdl( pMdl );
|
|
|
|
} // UlFreeLockedMdl
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
A helper function that initializes an MDL for a range of memory, and
|
|
locks it down. UlpSendCacheEntry uses these MDLs to make sure the
|
|
(normally paged) cache entries don't get paged out when TDI is
|
|
sending them.
|
|
|
|
Arguments:
|
|
|
|
pMdl - Supplies the memory descriptor for the MDL to initialize.
|
|
|
|
VirtualAddress - Supplies the address of the memory.
|
|
|
|
Length - Supplies the length of the memory.
|
|
|
|
Operation - Either IoWriteAcess or IoReadAccess.
|
|
|
|
Return Values:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlInitializeAndLockMdl(
|
|
IN PMDL pMdl,
|
|
IN PVOID VirtualAddress,
|
|
IN ULONG Length,
|
|
IN LOCK_OPERATION Operation
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
__try
|
|
{
|
|
MmInitializeMdl(
|
|
pMdl,
|
|
VirtualAddress,
|
|
Length
|
|
);
|
|
|
|
MmProbeAndLockPages(
|
|
pMdl, // MDL
|
|
KernelMode, // AccessMode
|
|
Operation // Operation
|
|
);
|
|
|
|
}
|
|
__except( UL_EXCEPTION_FILTER() )
|
|
{
|
|
Status = UL_CONVERT_EXCEPTION_CODE( GetExceptionCode() );
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlInitializeAndLockMdl
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Once we've parsed a request, we pass it in here to try and serve
|
|
from the response cache. This function will either send the response,
|
|
or do nothing at all.
|
|
|
|
Arguments:
|
|
|
|
pHttpConn - Supplies the connection with a request to be handled.
|
|
|
|
pSendCacheResult - Result of the cache sent attempt.
|
|
|
|
pResumeParsing - Returns to the parser if parsing needs to be resumed.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlSendCachedResponse(
|
|
IN PUL_HTTP_CONNECTION pHttpConn,
|
|
OUT PUL_SEND_CACHE_RESULT pSendCacheResult,
|
|
OUT PBOOLEAN pResumeParsing
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_URI_CACHE_ENTRY pUriCacheEntry = NULL;
|
|
ULONG Flags;
|
|
ULONG RetCacheControl;
|
|
LONGLONG BytesToSend;
|
|
ULONG SiteId;
|
|
URI_SEARCH_KEY SearchKey;
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
PUL_SITE_COUNTER_ENTRY pCtr;
|
|
ULONG Connections;
|
|
UL_RESUME_PARSING_TYPE ResumeParsingType;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT( UL_IS_VALID_HTTP_CONNECTION( pHttpConn ) );
|
|
ASSERT( UlDbgPushLockOwnedExclusive( &pHttpConn->PushLock ) );
|
|
ASSERT( pSendCacheResult );
|
|
ASSERT( pResumeParsing );
|
|
|
|
pRequest = pHttpConn->pRequest;
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pRequest ) );
|
|
|
|
*pResumeParsing = FALSE;
|
|
|
|
//
|
|
// See if we need to lookup based on Host + IP.
|
|
//
|
|
|
|
if (UlGetHostPlusIpBoundUriCacheCount() > 0)
|
|
{
|
|
Status = UlGenerateRoutingToken( pRequest, FALSE );
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Memory failure bail out. Always set the error code if we are
|
|
// going to fail the request.
|
|
//
|
|
|
|
UlSetErrorCode(
|
|
pRequest,
|
|
UlErrorInternalServer,
|
|
NULL
|
|
);
|
|
|
|
*pSendCacheResult = UlSendCacheFailed;
|
|
return Status;
|
|
}
|
|
|
|
UlpBuildExtendedSearchKey( pRequest, &SearchKey );
|
|
pUriCacheEntry = UlCheckoutUriCacheEntry( &SearchKey );
|
|
|
|
if (pUriCacheEntry)
|
|
{
|
|
ASSERT( pUriCacheEntry->ConfigInfo.SiteUrlType ==
|
|
HttpUrlSite_NamePlusIP );
|
|
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlSendCachedResponse (Host+Ip) "
|
|
"pUriCacheEntry: (%p) Uri: (%S)\n",
|
|
pUriCacheEntry,
|
|
pUriCacheEntry ? pUriCacheEntry->UriKey.pUri : L""
|
|
));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Do not serve from cache if there's a Host + IP bound site
|
|
// in the cgroup.
|
|
//
|
|
|
|
if (pUriCacheEntry == NULL)
|
|
{
|
|
Status = UlLookupHostPlusIPSite( pRequest );
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// There is a Host + IP bound site for this request.
|
|
// This should not be served from cache.
|
|
// Bail out!
|
|
//
|
|
|
|
*pSendCacheResult = UlSendCacheMiss;
|
|
return Status;
|
|
}
|
|
else
|
|
if (STATUS_NO_MEMORY == Status)
|
|
{
|
|
UlSetErrorCode(
|
|
pRequest,
|
|
UlErrorInternalServer,
|
|
NULL
|
|
);
|
|
|
|
*pSendCacheResult = UlSendCacheFailed;
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Do the normal lookup based on the cooked Url.
|
|
//
|
|
|
|
if (pUriCacheEntry == NULL)
|
|
{
|
|
SearchKey.Type = UriKeyTypeNormal;
|
|
SearchKey.Key.Hash = pRequest->CookedUrl.Hash;
|
|
SearchKey.Key.Length = pRequest->CookedUrl.Length;
|
|
SearchKey.Key.pUri = pRequest->CookedUrl.pUrl;
|
|
SearchKey.Key.pPath = NULL;
|
|
|
|
pUriCacheEntry = UlCheckoutUriCacheEntry( &SearchKey );
|
|
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlSendCachedResponse (CookedUrl) "
|
|
"pUriCacheEntry: (%p) Uri: (%S)\n",
|
|
pUriCacheEntry,
|
|
pUriCacheEntry ? pUriCacheEntry->UriKey.pUri : L""
|
|
));
|
|
}
|
|
|
|
if (pUriCacheEntry == NULL)
|
|
{
|
|
//
|
|
// No match in the URI cache, bounce up to user-mode.
|
|
//
|
|
|
|
*pSendCacheResult = UlSendCacheMiss;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Verify the cache entry.
|
|
//
|
|
|
|
ASSERT( IS_VALID_URI_CACHE_ENTRY( pUriCacheEntry ) );
|
|
ASSERT( IS_VALID_URL_CONFIG_GROUP_INFO( &pUriCacheEntry->ConfigInfo ) );
|
|
|
|
if (!pUriCacheEntry->HeaderLength)
|
|
{
|
|
//
|
|
// Treat a match to a headless fragment cache entry as no-match.
|
|
//
|
|
|
|
UlCheckinUriCacheEntry( pUriCacheEntry );
|
|
|
|
*pSendCacheResult = UlSendCacheMiss;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Check "Accept:" header.
|
|
//
|
|
|
|
if (FALSE == pRequest->AcceptWildcard)
|
|
{
|
|
if (FALSE == UlIsAcceptHeaderOk(pRequest, pUriCacheEntry))
|
|
{
|
|
//
|
|
// Cache entry did not match requested accept header; bounce up
|
|
// to user-mode for response.
|
|
//
|
|
|
|
UlCheckinUriCacheEntry( pUriCacheEntry );
|
|
|
|
*pSendCacheResult = UlSendCacheMiss;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check "Accept-Encoding:" header
|
|
//
|
|
|
|
if (FALSE == UlIsContentEncodingOk(pRequest, pUriCacheEntry))
|
|
{
|
|
//
|
|
// Cache entry did not match requested Accept-Encoding
|
|
// header; bounce up to user-mode for response.
|
|
//
|
|
|
|
UlCheckinUriCacheEntry( pUriCacheEntry );
|
|
|
|
*pSendCacheResult = UlSendCacheMiss;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Now from this point on, the response will either be from cache or
|
|
// we will fail/refuse the connection. Always return from end, so that
|
|
// tracing can work.
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// Enforce the connection limit now.
|
|
//
|
|
|
|
if (FALSE == UlCheckSiteConnectionLimit(
|
|
pHttpConn,
|
|
&pUriCacheEntry->ConfigInfo
|
|
))
|
|
{
|
|
//
|
|
// Check in the cache entry back. Connection is refused!
|
|
//
|
|
|
|
UlSetErrorCode(
|
|
pRequest,
|
|
UlErrorConnectionLimit,
|
|
pUriCacheEntry->ConfigInfo.pAppPool
|
|
);
|
|
|
|
UlCheckinUriCacheEntry( pUriCacheEntry );
|
|
|
|
*pSendCacheResult = UlSendCacheConnectionRefused;
|
|
Status = STATUS_INVALID_DEVICE_STATE;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Perf Counters (cached).
|
|
//
|
|
|
|
pCtr = pUriCacheEntry->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 ) );
|
|
|
|
if (pUriCacheEntry->Verb == HttpVerbGET)
|
|
{
|
|
UlIncSiteNonCriticalCounterUlong( pCtr, HttpSiteCounterGetReqs );
|
|
}
|
|
else
|
|
if (pUriCacheEntry->Verb == HttpVerbHEAD)
|
|
{
|
|
UlIncSiteNonCriticalCounterUlong( pCtr, HttpSiteCounterHeadReqs );
|
|
}
|
|
|
|
UlIncSiteNonCriticalCounterUlong( pCtr, HttpSiteCounterAllReqs );
|
|
|
|
if (pCtr != pHttpConn->pPrevSiteCounters)
|
|
{
|
|
if (pHttpConn->pPrevSiteCounters)
|
|
{
|
|
UlDecSiteCounter(
|
|
pHttpConn->pPrevSiteCounters,
|
|
HttpSiteCounterCurrentConns
|
|
);
|
|
DEREFERENCE_SITE_COUNTER_ENTRY( pHttpConn->pPrevSiteCounters );
|
|
}
|
|
|
|
UlIncSiteNonCriticalCounterUlong(
|
|
pCtr,
|
|
HttpSiteCounterConnAttempts
|
|
);
|
|
Connections =
|
|
(ULONG) UlIncSiteCounter( pCtr, HttpSiteCounterCurrentConns );
|
|
UlMaxSiteCounter(
|
|
pCtr,
|
|
HttpSiteCounterMaxConnections,
|
|
Connections
|
|
);
|
|
|
|
//
|
|
// Add ref for new site counters.
|
|
//
|
|
|
|
REFERENCE_SITE_COUNTER_ENTRY( pCtr );
|
|
pHttpConn->pPrevSiteCounters = pCtr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Install a filter if BWT is enabled for this request's site
|
|
// or for the control channel that owns the site. If fails,
|
|
// refuse the connection back (503).
|
|
//
|
|
|
|
Status = UlTcAddFilterForConnection(
|
|
pHttpConn,
|
|
&pUriCacheEntry->ConfigInfo
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
UlSetErrorCode(
|
|
pRequest,
|
|
UlErrorUnavailable,
|
|
pUriCacheEntry->ConfigInfo.pAppPool
|
|
);
|
|
|
|
UlCheckinUriCacheEntry( pUriCacheEntry );
|
|
|
|
*pSendCacheResult = UlSendCacheFailed;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Now we are about to do a cache send, we need to enforce the limit for
|
|
// pipelined requests on the connection. If we return FALSE for resume
|
|
// parsing, the next request on the connection will be parsed after
|
|
// the send completion. Otherwise the HTTP receive logic will kick the
|
|
// parser back into action.
|
|
//
|
|
|
|
if (pHttpConn->PipelinedRequests < g_UlMaxPipelinedRequests)
|
|
{
|
|
ResumeParsingType = UlResumeParsingOnLastSend;
|
|
}
|
|
else
|
|
{
|
|
ResumeParsingType = UlResumeParsingOnSendCompletion;
|
|
}
|
|
|
|
//
|
|
// Set BytesToSend and SiteId since we are reasonably sure this is a
|
|
// cache-hit and so that the 304 code path will get these values too.
|
|
//
|
|
|
|
BytesToSend = pUriCacheEntry->ContentLength + pUriCacheEntry->HeaderLength;
|
|
SiteId = pUriCacheEntry->ConfigInfo.SiteId;
|
|
|
|
//
|
|
// Cache-Control: Check the If-* headers to see if we can/should skip
|
|
// sending of the cached response. This does a passive syntax check on
|
|
// the Etags in the request's If-* headers. This call will issue a send
|
|
// if the return code is 304.
|
|
//
|
|
|
|
RetCacheControl =
|
|
UlCheckCacheControlHeaders(
|
|
pRequest,
|
|
pUriCacheEntry,
|
|
(BOOLEAN) (UlResumeParsingOnSendCompletion == ResumeParsingType)
|
|
);
|
|
|
|
if (RetCacheControl)
|
|
{
|
|
//
|
|
// Check-in cache entry, since completion won't run.
|
|
//
|
|
|
|
UlCheckinUriCacheEntry( pUriCacheEntry );
|
|
|
|
switch (RetCacheControl)
|
|
{
|
|
case 304:
|
|
|
|
//
|
|
// Resume parsing only if we have sent a 304. In other cases,
|
|
// the request is deliverd to the user or the connection is reset.
|
|
//
|
|
|
|
*pResumeParsing =
|
|
(BOOLEAN) (UlResumeParsingOnLastSend == ResumeParsingType);
|
|
|
|
//
|
|
// Mark as "served from cache".
|
|
//
|
|
|
|
*pSendCacheResult = UlSendCacheServedFromCache;
|
|
break;
|
|
|
|
case 412:
|
|
|
|
//
|
|
// Indicate that the parser should send error 412 (Precondition
|
|
// Failed). Just the send the error response but do not close
|
|
// the connection.
|
|
//
|
|
|
|
UlSetErrorCode( pRequest, UlErrorPreconditionFailed, NULL );
|
|
|
|
*pSendCacheResult = UlSendCachePreconditionFailed;
|
|
Status = STATUS_INVALID_DEVICE_STATE;
|
|
break;
|
|
|
|
case 400:
|
|
default:
|
|
|
|
//
|
|
// Indicate that the parser should send error 400 (Bad Request).
|
|
//
|
|
|
|
UlSetErrorCode( pRequest, UlError, NULL );
|
|
|
|
*pSendCacheResult = UlSendCacheFailed;
|
|
Status = STATUS_INVALID_DEVICE_STATE;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Return success.
|
|
//
|
|
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Figure out correct flags.
|
|
//
|
|
|
|
if (UlCheckDisconnectInfo(pHttpConn->pRequest))
|
|
{
|
|
Flags = HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
|
|
}
|
|
else
|
|
{
|
|
Flags = 0;
|
|
}
|
|
|
|
//
|
|
// Start the MinBytesPerSecond timer, since the data length
|
|
// is in the UL_URI_CACHE_ENTRY.
|
|
//
|
|
|
|
UlSetMinBytesPerSecondTimer(
|
|
&pHttpConn->TimeoutInfo,
|
|
BytesToSend
|
|
);
|
|
|
|
Status = UlpSendCacheEntry(
|
|
pHttpConn, // pHttpConnection
|
|
Flags, // Flags
|
|
pUriCacheEntry, // pUriCacheEntry
|
|
NULL, // pCompletionRoutine
|
|
NULL, // pCompletionContext
|
|
NULL, // pLogData
|
|
ResumeParsingType // ResumeParsingType
|
|
);
|
|
|
|
//
|
|
// Check in cache entry on failure since our completion
|
|
// routine won't run.
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status) )
|
|
{
|
|
//
|
|
// Return failure so that the request doesn't bounce back to
|
|
// user mode.
|
|
//
|
|
|
|
UlSetErrorCode(
|
|
pRequest,
|
|
UlErrorUnavailable,
|
|
pUriCacheEntry->ConfigInfo.pAppPool
|
|
);
|
|
|
|
UlCheckinUriCacheEntry( pUriCacheEntry );
|
|
*pSendCacheResult = UlSendCacheFailed;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Success!
|
|
//
|
|
|
|
*pSendCacheResult = UlSendCacheServedFromCache;
|
|
}
|
|
|
|
end:
|
|
|
|
//
|
|
// If the request is served from cache, fire the ETW end event here.
|
|
//
|
|
|
|
if (ETW_LOG_MIN() && (*pSendCacheResult == UlSendCacheServedFromCache))
|
|
{
|
|
UlEtwTraceEvent(
|
|
&UlTransGuid,
|
|
ETW_TYPE_CACHED_END,
|
|
(PVOID) &pRequest,
|
|
sizeof(PVOID),
|
|
&SiteId,
|
|
sizeof(ULONG),
|
|
&BytesToSend,
|
|
sizeof(ULONG),
|
|
NULL,
|
|
0
|
|
);
|
|
}
|
|
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlSendCachedResponse(httpconn = %p) ServedFromCache = (%s),"
|
|
"Status = %x\n",
|
|
pHttpConn,
|
|
TRANSLATE_SEND_CACHE_RESULT(*pSendCacheResult),
|
|
Status
|
|
));
|
|
|
|
return Status;
|
|
|
|
} // UlSendCachedResponse
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
If the response is cacheable, then this routine starts building a
|
|
cache entry for it. When the entry is complete it will be sent to
|
|
the client and may be added to the hash table.
|
|
|
|
Arguments:
|
|
|
|
pRequest - Supplies the initiating request.
|
|
|
|
pResponse - Supplies the generated response.
|
|
|
|
pProcess - Supplies the WP that is sending the response.
|
|
|
|
Flags - UlSendHttpResponse flags.
|
|
|
|
Policy - Supplies the cache policy for this response.
|
|
|
|
pCompletionRoutine - Supplies the completion routine to be called
|
|
after entry is sent.
|
|
|
|
pCompletionContext - Supplies the context passed to pCompletionRoutine.
|
|
|
|
pServedFromCache - Always set. TRUE if we'll handle sending response.
|
|
FALSE indicates that the caller should send it.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlCacheAndSendResponse(
|
|
IN PUL_INTERNAL_REQUEST pRequest,
|
|
IN PUL_INTERNAL_RESPONSE pResponse,
|
|
IN PUL_APP_POOL_PROCESS pProcess,
|
|
IN HTTP_CACHE_POLICY Policy,
|
|
IN PUL_COMPLETION_ROUTINE pCompletionRoutine,
|
|
IN PVOID pCompletionContext,
|
|
OUT PBOOLEAN pServedFromCache
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG Flags = pResponse->Flags;
|
|
USHORT StatusCode = pResponse->StatusCode;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT( pServedFromCache );
|
|
|
|
//
|
|
// Should we close the connection?
|
|
//
|
|
|
|
if (UlCheckDisconnectInfo(pRequest))
|
|
{
|
|
Flags |= HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
|
|
pResponse->Flags = Flags;
|
|
}
|
|
|
|
//
|
|
// Do the real work.
|
|
//
|
|
|
|
if (UlCheckCacheResponseConditions(pRequest, pResponse, Flags, Policy))
|
|
{
|
|
Status = UlpBuildCacheEntry(
|
|
pRequest,
|
|
pResponse,
|
|
pProcess,
|
|
Policy,
|
|
pCompletionRoutine,
|
|
pCompletionContext
|
|
);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
*pServedFromCache = TRUE;
|
|
}
|
|
else
|
|
{
|
|
*pServedFromCache = FALSE;
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*pServedFromCache = FALSE;
|
|
}
|
|
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlCacheAndSendResponse ServedFromCache = %d\n",
|
|
*pServedFromCache
|
|
));
|
|
|
|
//
|
|
// We will record this as cache miss since the original request
|
|
// was a miss.
|
|
//
|
|
|
|
if (ETW_LOG_MIN() && *pServedFromCache)
|
|
{
|
|
UlEtwTraceEvent(
|
|
&UlTransGuid,
|
|
ETW_TYPE_CACHE_AND_SEND,
|
|
(PVOID) &pRequest->RequestIdCopy,
|
|
sizeof(HTTP_REQUEST_ID),
|
|
(PVOID) &StatusCode,
|
|
sizeof(USHORT),
|
|
NULL,
|
|
0
|
|
);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlCacheAndSendResponse
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Completes a "send response" represented by a send tracker.
|
|
UlCompleteSendResponse takes the ownership of the tracker reference.
|
|
|
|
Arguments:
|
|
|
|
pTracker - Supplies the tracker to complete.
|
|
|
|
Status - Supplies the completion status.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlCompleteSendResponse(
|
|
IN PUL_CHUNK_TRACKER pTracker,
|
|
IN NTSTATUS Status
|
|
)
|
|
{
|
|
//
|
|
// Although the chunk tracker will be around until all the outstanding
|
|
// Read/Send IRPs are complete, we should only complete the send once.
|
|
//
|
|
|
|
if (FALSE != InterlockedExchange(&pTracker->Terminated, TRUE))
|
|
{
|
|
return;
|
|
}
|
|
|
|
UlTrace(SEND_RESPONSE,(
|
|
"UlCompleteSendResponse: tracker %p, status %08lx\n",
|
|
pTracker,
|
|
Status
|
|
));
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
|
|
pTracker->IoStatus.Status = Status;
|
|
|
|
UL_QUEUE_WORK_ITEM(
|
|
&pTracker->WorkItem,
|
|
UlpCompleteSendResponseWorker
|
|
);
|
|
|
|
} // UlCompleteSendResponse
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Increment pRequest->SendsPending in a lock and decide if we need to
|
|
transfer the logging and resume parsing information to pRequest.
|
|
|
|
Arguments:
|
|
|
|
pRequest - Supplies the pointer to a UL_INTERNAL_REQUEST structure
|
|
that SendsPending needs incremented.
|
|
|
|
ppLogData - Supplies the pointer to a PUL_LOG_DATA_BUFFER structure
|
|
that we need to transfer to pRequest.
|
|
|
|
pResumeParsingType - Supplies the pointer to UL_RESUME_PARSING_TYPE
|
|
that we need to transfer to pRequest.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlSetRequestSendsPending(
|
|
IN PUL_INTERNAL_REQUEST pRequest,
|
|
IN OUT PUL_LOG_DATA_BUFFER * ppLogData,
|
|
IN OUT PUL_RESUME_PARSING_TYPE pResumeParsingType
|
|
)
|
|
{
|
|
KIRQL OldIrql;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
ASSERT( PASSIVE_LEVEL == KeGetCurrentIrql() );
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pRequest ) );
|
|
ASSERT( ppLogData );
|
|
ASSERT( NULL == *ppLogData || IS_VALID_LOG_DATA_BUFFER( *ppLogData ) );
|
|
ASSERT( pResumeParsingType );
|
|
|
|
UlAcquireSpinLock( &pRequest->SpinLock, &OldIrql );
|
|
|
|
pRequest->SendsPending++;
|
|
|
|
if (*ppLogData)
|
|
{
|
|
ASSERT( NULL == pRequest->pLogData );
|
|
|
|
pRequest->pLogData = *ppLogData;
|
|
*ppLogData = NULL;
|
|
}
|
|
|
|
if (UlResumeParsingOnSendCompletion == *pResumeParsingType)
|
|
{
|
|
ASSERT( FALSE == pRequest->ResumeParsing );
|
|
|
|
pRequest->ResumeParsing = TRUE;
|
|
*pResumeParsingType = UlResumeParsingNone;
|
|
}
|
|
|
|
UlReleaseSpinLock( &pRequest->SpinLock, OldIrql );
|
|
|
|
} // UlSetRequestSendsPending
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Decrement pRequest->SendsPending in a lock and decide if we need to
|
|
log and resume parsing. The caller then should either log and/or
|
|
resume parsing depending on the values returned. It is assumed here
|
|
the values for both *ppLogData and *pResumeParsing are initialized
|
|
when entering this function.
|
|
|
|
Arguments:
|
|
|
|
pRequest - Supplies the pointer to a UL_INTERNAL_REQUEST structure
|
|
that SendsPending needs decremented.
|
|
|
|
ppLogData - Supplies the pointer to a PUL_LOG_DATA_BUFFER structure
|
|
to receive the logging information.
|
|
|
|
pResumeParsing - Supplies the pointer to a BOOLEAN to receive the
|
|
resume parsing information.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlUnsetRequestSendsPending(
|
|
IN PUL_INTERNAL_REQUEST pRequest,
|
|
OUT PUL_LOG_DATA_BUFFER * ppLogData,
|
|
OUT PBOOLEAN pResumeParsing
|
|
)
|
|
{
|
|
KIRQL OldIrql;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
ASSERT( PASSIVE_LEVEL == KeGetCurrentIrql() );
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pRequest ) );
|
|
ASSERT( ppLogData );
|
|
ASSERT( NULL == *ppLogData );
|
|
ASSERT( pResumeParsing );
|
|
ASSERT( FALSE == *pResumeParsing );
|
|
|
|
UlAcquireSpinLock( &pRequest->SpinLock, &OldIrql );
|
|
|
|
pRequest->SendsPending--;
|
|
|
|
if (0 == pRequest->SendsPending)
|
|
{
|
|
if (pRequest->pLogData)
|
|
{
|
|
*ppLogData = pRequest->pLogData;
|
|
pRequest->pLogData = NULL;
|
|
}
|
|
|
|
if (pRequest->ResumeParsing)
|
|
{
|
|
*pResumeParsing = pRequest->ResumeParsing;
|
|
pRequest->ResumeParsing = FALSE;
|
|
}
|
|
}
|
|
|
|
UlReleaseSpinLock( &pRequest->SpinLock, OldIrql );
|
|
|
|
} // UlUnsetRequestSendsPending
|
|
|
|
|
|
//
|
|
// Private functions.
|
|
//
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Destroys an internal HTTP response captured by UlCaptureHttpResponse().
|
|
This involves closing open files, unlocking memory, and releasing any
|
|
resources allocated to the response.
|
|
|
|
Arguments:
|
|
|
|
pResponse - Supplies the internal response to destroy.
|
|
|
|
Return Values:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpDestroyCapturedResponse(
|
|
IN PUL_INTERNAL_RESPONSE pResponse
|
|
)
|
|
{
|
|
PUL_INTERNAL_DATA_CHUNK pDataChunk;
|
|
PIRP pIrp;
|
|
PIO_STACK_LOCATION pIrpSp;
|
|
ULONG i;
|
|
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pResponse ) );
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pResponse->pRequest ) );
|
|
|
|
UlTrace(SEND_RESPONSE,(
|
|
"UlpDestroyCapturedResponse: response %p\n",
|
|
pResponse
|
|
));
|
|
|
|
UlDeletePushLock( &pResponse->PushLock );
|
|
|
|
//
|
|
// Scan the chunks.
|
|
//
|
|
|
|
for (i = 0; i < pResponse->ChunkCount; ++i)
|
|
{
|
|
pDataChunk = &pResponse->pDataChunks[i];
|
|
|
|
if (IS_FROM_MEMORY(pDataChunk))
|
|
{
|
|
//
|
|
// It's from memory. If necessary, unlock the pages, then
|
|
// free the MDL.
|
|
//
|
|
|
|
if (pDataChunk->FromMemory.pMdl != NULL)
|
|
{
|
|
if (IS_MDL_LOCKED(pDataChunk->FromMemory.pMdl))
|
|
{
|
|
MmUnlockPages( pDataChunk->FromMemory.pMdl );
|
|
}
|
|
|
|
UlFreeMdl( pDataChunk->FromMemory.pMdl );
|
|
pDataChunk->FromMemory.pMdl = NULL;
|
|
}
|
|
}
|
|
else
|
|
if (IS_FROM_FRAGMENT_CACHE(pDataChunk))
|
|
{
|
|
//
|
|
// It's a fragment chunk. If there is a cache entry checked
|
|
// out, check it back in.
|
|
//
|
|
|
|
if (pDataChunk->FromFragmentCache.pCacheEntry != NULL)
|
|
{
|
|
UlCheckinUriCacheEntry(
|
|
pDataChunk->FromFragmentCache.pCacheEntry
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// It's a file chunk. If there is an associated file cache
|
|
// entry, then dereference it.
|
|
//
|
|
|
|
ASSERT( IS_FROM_FILE_HANDLE( pDataChunk ) );
|
|
|
|
if (pDataChunk->FromFileHandle.FileCacheEntry.pFileObject != NULL)
|
|
{
|
|
UlDestroyFileCacheEntry(
|
|
&pDataChunk->FromFileHandle.FileCacheEntry
|
|
);
|
|
pDataChunk->FromFileHandle.FileCacheEntry.pFileObject = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// We should clean up the log buffer here if nobody has cleaned it up yet.
|
|
// Unless there's an error during capture, the log buffer will be cleaned
|
|
// up when send tracker's (cache/chunk) are completed in their respective
|
|
// routines.
|
|
//
|
|
|
|
if (pResponse->pLogData)
|
|
{
|
|
UlDestroyLogDataBuffer( pResponse->pLogData );
|
|
}
|
|
|
|
//
|
|
// Complete the IRP if we have one.
|
|
//
|
|
|
|
pIrp = pResponse->pIrp;
|
|
|
|
if (pIrp)
|
|
{
|
|
//
|
|
// Uncheck either ConnectionSendBytes or GlobalSendBytes.
|
|
//
|
|
|
|
UlUncheckSendLimit(
|
|
pResponse->pRequest->pHttpConn,
|
|
pResponse->ConnectionSendBytes,
|
|
pResponse->GlobalSendBytes
|
|
);
|
|
|
|
//
|
|
// Unset the Type3InputBuffer since we are completing the IRP.
|
|
//
|
|
|
|
pIrpSp = IoGetCurrentIrpStackLocation( pIrp );
|
|
pIrpSp->Parameters.DeviceIoControl.Type3InputBuffer = NULL;
|
|
|
|
pIrp->IoStatus = pResponse->IoStatus;
|
|
UlCompleteRequest( pIrp, IO_NETWORK_INCREMENT );
|
|
}
|
|
|
|
UL_DEREFERENCE_INTERNAL_REQUEST( pResponse->pRequest );
|
|
|
|
if (pResponse->FromLookaside)
|
|
{
|
|
UlPplFreeResponseBuffer( pResponse );
|
|
}
|
|
else
|
|
{
|
|
UL_FREE_POOL_WITH_SIG( pResponse, UL_INTERNAL_RESPONSE_POOL_TAG );
|
|
}
|
|
|
|
} // UlpDestroyCapturedResponse
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Process the UL_INTERNAL_RESPONSE we have created. If no other sends
|
|
are being processed, start processing the current one by scheduling
|
|
a worker item; otherwise, queue the current response in the pending
|
|
response queue of the request. In the case where the request has been
|
|
cleaned up (UlCancelRequestIo has been called), we immediately complete
|
|
the response with STATUS_CANCELLED.
|
|
|
|
Arguments:
|
|
|
|
pTracker - Supplies the tracker to send the response.
|
|
|
|
FromKernelMode - If this comes from UlSendErrorResponse or not.
|
|
|
|
Return Values:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpEnqueueSendHttpResponse(
|
|
IN PUL_CHUNK_TRACKER pTracker,
|
|
IN BOOLEAN FromKernelMode
|
|
)
|
|
{
|
|
PUL_INTERNAL_RESPONSE pResponse;
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
BOOLEAN ProcessCurrentResponse = FALSE;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pTracker->pResponse ) );
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pTracker->pResponse->pRequest ) );
|
|
|
|
pResponse = pTracker->pResponse;
|
|
pRequest = pResponse->pRequest;
|
|
|
|
if (!FromKernelMode)
|
|
{
|
|
UlAcquirePushLockExclusive( &pRequest->pHttpConn->PushLock );
|
|
}
|
|
|
|
if (!pRequest->InCleanup)
|
|
{
|
|
if (pRequest->SendInProgress)
|
|
{
|
|
InsertTailList(
|
|
&pRequest->ResponseHead,
|
|
&pTracker->ListEntry
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ASSERT( IsListEmpty( &pRequest->ResponseHead ) );
|
|
|
|
//
|
|
// Start the send process (and set the SendInProgress flag) if
|
|
// there are no other sends in progress. The SendInProgress flag
|
|
// is removed from the list when the last piece of the data is
|
|
// pended in TDI. If an error occurs, the connection gets reset
|
|
// so UlCancelRequestIo will eventually cancel all pending sends.
|
|
//
|
|
|
|
pRequest->SendInProgress = 1;
|
|
ProcessCurrentResponse = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UlCompleteSendResponse( pTracker, STATUS_CONNECTION_RESET );
|
|
}
|
|
|
|
if (!FromKernelMode)
|
|
{
|
|
UlReleasePushLockExclusive( &pRequest->pHttpConn->PushLock );
|
|
|
|
if (ProcessCurrentResponse)
|
|
{
|
|
//
|
|
// Call UlpSendHttpResponseWorker directly if this comes from the
|
|
// IOCTL. This is to reduce potential contentions on the
|
|
// HttpConnection push lock when sends are overlapped since an
|
|
// application semantically can't really send again until the
|
|
// previous call returns (not necessarily completes). Calling
|
|
// UlpSendHttpResponseWorker directly will cleanup the
|
|
// SendInProgress flag most of the time when the send returns
|
|
// unless the send requires disk I/O.
|
|
//
|
|
|
|
UlpSendHttpResponseWorker( &pTracker->WorkItem );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ProcessCurrentResponse)
|
|
{
|
|
//
|
|
// But if this is called from kernel mode, we need to queue a
|
|
// work item to be safe because TDI can complete a send inline
|
|
// resulting UlResumeParsing getting called with the
|
|
// HttpConnection push lock held.
|
|
//
|
|
|
|
UlpQueueResponseWorkItem(
|
|
&pTracker->WorkItem,
|
|
UlpSendHttpResponseWorker,
|
|
pTracker->pResponse->SyncRead
|
|
);
|
|
}
|
|
}
|
|
|
|
} // UlpEnqueueSendHttpResponse
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Unset SendInProgress flag and the try to remove the next response from
|
|
the request's response list. If there are more responses pending, start
|
|
processing them as well.
|
|
|
|
Arguments:
|
|
|
|
pRequest - Supplies the request that has a list of responses queued.
|
|
|
|
Return Values:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpDequeueSendHttpResponse(
|
|
IN PUL_INTERNAL_REQUEST pRequest
|
|
)
|
|
{
|
|
PLIST_ENTRY pEntry;
|
|
IN PUL_CHUNK_TRACKER pTracker;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pRequest ) );
|
|
ASSERT( pRequest->SendInProgress );
|
|
|
|
UlAcquirePushLockExclusive( &pRequest->pHttpConn->PushLock );
|
|
|
|
if (!pRequest->InCleanup && !IsListEmpty(&pRequest->ResponseHead))
|
|
{
|
|
pEntry = RemoveHeadList( &pRequest->ResponseHead );
|
|
|
|
pTracker = CONTAINING_RECORD(
|
|
pEntry,
|
|
UL_CHUNK_TRACKER,
|
|
ListEntry
|
|
);
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pTracker->pResponse ) );
|
|
|
|
//
|
|
// Start the send process for the next response in the request's
|
|
// response list.
|
|
//
|
|
|
|
UlpQueueResponseWorkItem(
|
|
&pTracker->WorkItem,
|
|
UlpSendHttpResponseWorker,
|
|
pTracker->pResponse->SyncRead
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ASSERT( IsListEmpty( &pRequest->ResponseHead ) );
|
|
|
|
//
|
|
// No more pending send IRPs. This means we can take the fast send
|
|
// path if asked so.
|
|
//
|
|
|
|
pRequest->SendInProgress = 0;
|
|
}
|
|
|
|
UlReleasePushLockExclusive( &pRequest->pHttpConn->PushLock );
|
|
|
|
} // UlpDequeueSendHttpResponse
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Worker routine for managing an in-progress UlSendHttpResponse().
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - Supplies a pointer to the work item queued. This should
|
|
point to the WORK_ITEM structure embedded in a UL_CHUNK_TRACKER.
|
|
|
|
Return Values:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpSendHttpResponseWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
PUL_CHUNK_TRACKER pTracker;
|
|
NTSTATUS Status;
|
|
PUL_INTERNAL_DATA_CHUNK pCurrentChunk;
|
|
PUL_FILE_CACHE_ENTRY pFileCacheEntry;
|
|
PUL_FILE_BUFFER pFileBuffer;
|
|
PMDL pNewMdl;
|
|
ULONG RunCount;
|
|
ULONG BytesToRead;
|
|
BOOLEAN ResumeParsing = FALSE;
|
|
PUL_URI_CACHE_ENTRY pFragmentCacheEntry;
|
|
PUL_INTERNAL_RESPONSE pResponse;
|
|
PUL_MDL_RUN pMdlRuns;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pTracker = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_CHUNK_TRACKER,
|
|
WorkItem
|
|
);
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"UlpSendHttpResponseWorker: tracker %p\n",
|
|
pTracker
|
|
));
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
|
|
pResponse = pTracker->pResponse;
|
|
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pResponse ) );
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
pMdlRuns = &pTracker->SendInfo.MdlRuns[0];
|
|
|
|
while (TRUE)
|
|
{
|
|
//
|
|
// Capture the current chunk pointer, then check for end of
|
|
// response.
|
|
//
|
|
|
|
pCurrentChunk = &pResponse->pDataChunks[pResponse->CurrentChunk];
|
|
|
|
if (IS_SEND_COMPLETE(pResponse))
|
|
{
|
|
ASSERT( Status == STATUS_SUCCESS );
|
|
break;
|
|
}
|
|
|
|
RunCount = pTracker->SendInfo.MdlRunCount;
|
|
|
|
//
|
|
// Determine the chunk type.
|
|
//
|
|
|
|
if (IS_FROM_MEMORY(pCurrentChunk) ||
|
|
IS_FROM_FRAGMENT_CACHE(pCurrentChunk))
|
|
{
|
|
//
|
|
// It's from a locked-down memory buffer or fragment cache.
|
|
// Since these are always handled in-line (never pended) we can
|
|
// go ahead and adjust the current chunk pointer in the
|
|
// tracker.
|
|
//
|
|
|
|
UlpIncrementChunkPointer( pResponse );
|
|
|
|
if (IS_FROM_MEMORY(pCurrentChunk))
|
|
{
|
|
//
|
|
// Ignore empty buffers.
|
|
//
|
|
|
|
if (pCurrentChunk->FromMemory.BufferLength == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Clone the incoming MDL.
|
|
//
|
|
|
|
ASSERT( pCurrentChunk->FromMemory.pMdl->Next == NULL );
|
|
pNewMdl = UlCloneMdl(
|
|
pCurrentChunk->FromMemory.pMdl,
|
|
MmGetMdlByteCount(pCurrentChunk->FromMemory.pMdl)
|
|
);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Build a partial MDL for the cached data.
|
|
//
|
|
|
|
pFragmentCacheEntry =
|
|
pCurrentChunk->FromFragmentCache.pCacheEntry;
|
|
|
|
//
|
|
// Ignore cached HEAD responses.
|
|
//
|
|
|
|
if (pFragmentCacheEntry->ContentLength == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pNewMdl = UlCloneMdl(
|
|
pFragmentCacheEntry->pMdl,
|
|
pFragmentCacheEntry->ContentLength
|
|
);
|
|
}
|
|
|
|
if (pNewMdl == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Update the buffered byte count and append the cloned MDL
|
|
// onto our MDL chain.
|
|
//
|
|
|
|
pTracker->SendInfo.BytesBuffered += MmGetMdlByteCount( pNewMdl );
|
|
(*pTracker->SendInfo.pMdlLink) = pNewMdl;
|
|
pTracker->SendInfo.pMdlLink = &pNewMdl->Next;
|
|
|
|
//
|
|
// Add the MDL to the run list. As an optimization, if the
|
|
// last run in the list was "from memory", we can just
|
|
// append the MDL to the last run. A "from fragment cache"
|
|
// chunk is similar to "from memory".
|
|
//
|
|
|
|
if (RunCount == 0 ||
|
|
IS_FILE_BUFFER_IN_USE(&pMdlRuns[RunCount - 1].FileBuffer))
|
|
{
|
|
//
|
|
// Create a new run.
|
|
//
|
|
|
|
pMdlRuns[RunCount].pMdlTail = pNewMdl;
|
|
pTracker->SendInfo.MdlRunCount++;
|
|
|
|
pFileBuffer = &pMdlRuns[RunCount].FileBuffer;
|
|
RtlZeroMemory( pFileBuffer, sizeof(*pFileBuffer) );
|
|
|
|
//
|
|
// If we have exhausted our static MDL run array,
|
|
// then we'll need to initiate a flush.
|
|
//
|
|
|
|
if (UL_MAX_MDL_RUNS == pTracker->SendInfo.MdlRunCount)
|
|
{
|
|
ASSERT( Status == STATUS_SUCCESS );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Append to the last run in the list.
|
|
//
|
|
|
|
pMdlRuns[RunCount - 1].pMdlTail->Next = pNewMdl;
|
|
pMdlRuns[RunCount - 1].pMdlTail = pNewMdl;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// It's a filesystem MDL.
|
|
//
|
|
|
|
ASSERT( IS_FROM_FILE_HANDLE( pCurrentChunk ) );
|
|
|
|
//
|
|
// Ignore 0 bytes read.
|
|
//
|
|
|
|
if (pResponse->FileBytesRemaining.QuadPart == 0)
|
|
{
|
|
UlpIncrementChunkPointer( pResponse );
|
|
continue;
|
|
}
|
|
|
|
pFileCacheEntry = &pCurrentChunk->FromFileHandle.FileCacheEntry;
|
|
ASSERT( IS_VALID_FILE_CACHE_ENTRY( pFileCacheEntry ) );
|
|
|
|
pFileBuffer = &pMdlRuns[RunCount].FileBuffer;
|
|
|
|
ASSERT( pFileBuffer->pMdl == NULL );
|
|
ASSERT( pFileBuffer->pFileData == NULL );
|
|
|
|
RtlZeroMemory( pFileBuffer, sizeof(*pFileBuffer) );
|
|
|
|
//
|
|
// Initiate file read.
|
|
//
|
|
|
|
BytesToRead = MIN(
|
|
g_UlMaxBytesPerRead,
|
|
(ULONG) pResponse->FileBytesRemaining.QuadPart
|
|
);
|
|
|
|
//
|
|
// Initialize the UL_FILE_BUFFER.
|
|
//
|
|
|
|
pFileBuffer->pFileCacheEntry = pFileCacheEntry;
|
|
pFileBuffer->FileOffset = pResponse->FileOffset;
|
|
pFileBuffer->Length = BytesToRead;
|
|
pFileBuffer->pCompletionRoutine = UlpRestartMdlRead;
|
|
pFileBuffer->pContext = pTracker;
|
|
|
|
//
|
|
// Bump up the tracker refcount before starting the Read I/O.
|
|
// In case Send operation later on will complete before the read,
|
|
// we still want the tracker around until UlpRestartMdlRead
|
|
// finishes its business. It will be released when
|
|
// UlpRestartMdlRead gets called back.
|
|
//
|
|
|
|
UL_REFERENCE_CHUNK_TRACKER( pTracker );
|
|
|
|
//
|
|
// Issue the I/O.
|
|
//
|
|
|
|
Status = UlReadFileEntry(
|
|
pFileBuffer,
|
|
pTracker->pIrp
|
|
);
|
|
|
|
//
|
|
// If the read isn't pending, then deref the tracker since
|
|
// UlpRestartMdlRead isn't going to get called.
|
|
//
|
|
|
|
if (Status != STATUS_PENDING)
|
|
{
|
|
UL_DEREFERENCE_CHUNK_TRACKER( pTracker );
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we fell out of the above loop with status == STATUS_SUCCESS,
|
|
// then the last send we issued was buffered and needs to be flushed.
|
|
// Otherwise, if the status is anything but STATUS_PENDING, then we
|
|
// hit an in-line failure and need to complete the original request.
|
|
//
|
|
|
|
if (Status == STATUS_SUCCESS)
|
|
{
|
|
if (IS_SEND_COMPLETE(pResponse) &&
|
|
UlResumeParsingOnLastSend == pResponse->ResumeParsingType)
|
|
{
|
|
ResumeParsing = TRUE;
|
|
}
|
|
|
|
if (pTracker->SendInfo.BytesBuffered > 0)
|
|
{
|
|
//
|
|
// Flush the send.
|
|
//
|
|
|
|
Status = UlpFlushMdlRuns( pTracker );
|
|
}
|
|
else
|
|
if (IS_DISCONNECT_TIME(pResponse))
|
|
{
|
|
//
|
|
// Increment up until connection close is complete.
|
|
//
|
|
|
|
UL_REFERENCE_CHUNK_TRACKER( pTracker );
|
|
|
|
Status = UlDisconnectHttpConnection(
|
|
pTracker->pHttpConnection,
|
|
UlpCloseConnectionComplete,
|
|
pTracker
|
|
);
|
|
|
|
ASSERT( Status == STATUS_PENDING );
|
|
}
|
|
|
|
//
|
|
// Kick the parser into action if this is the last send for the
|
|
// keep-alive. Resuming parsing here improves latency when incoming
|
|
// requests are pipelined.
|
|
//
|
|
|
|
if (ResumeParsing)
|
|
{
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlpSendHttpResponseWorker(pHttpConn = %p), "
|
|
"RequestVerb=%d, ResponseStatusCode=%hu\n",
|
|
pResponse->pRequest->pHttpConn,
|
|
pResponse->pRequest->Verb,
|
|
pResponse->StatusCode
|
|
));
|
|
|
|
UlResumeParsing(
|
|
pResponse->pRequest->pHttpConn,
|
|
FALSE,
|
|
(BOOLEAN) (pResponse->Flags & HTTP_SEND_RESPONSE_FLAG_DISCONNECT)
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Did everything complete?
|
|
//
|
|
|
|
if (Status != STATUS_PENDING)
|
|
{
|
|
//
|
|
// Nope, something went wrong!
|
|
//
|
|
|
|
UlCompleteSendResponse( pTracker, Status );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Release our grab on the tracker we are done with it.
|
|
//
|
|
|
|
UL_DEREFERENCE_CHUNK_TRACKER( pTracker );
|
|
}
|
|
|
|
} // UlpSendHttpResponseWorker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Completion handler for UlCloseConnection().
|
|
|
|
Arguments:
|
|
|
|
pCompletionContext - Supplies an uninterpreted context value
|
|
as passed to the asynchronous API. This is actually a
|
|
PUL_CHUNK_TRACKER pointer.
|
|
|
|
Status - Supplies the final completion status of the
|
|
asynchronous API.
|
|
|
|
Information - Optionally supplies additional information about
|
|
the completed operation, such as the number of bytes
|
|
transferred. This field is unused for UlCloseConnection().
|
|
|
|
Return Values:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpCloseConnectionComplete(
|
|
IN PVOID pCompletionContext,
|
|
IN NTSTATUS Status,
|
|
IN ULONG_PTR Information
|
|
)
|
|
{
|
|
PUL_CHUNK_TRACKER pTracker;
|
|
|
|
UNREFERENCED_PARAMETER( Information );
|
|
|
|
//
|
|
// Snag the context.
|
|
//
|
|
|
|
pTracker = (PUL_CHUNK_TRACKER) pCompletionContext;
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"UlpCloseConnectionComplete: tracker %p\n",
|
|
pTracker
|
|
));
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
|
|
UlCompleteSendResponse( pTracker, Status );
|
|
|
|
} // UlpCloseConnectionComplete
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Allocates a new send tracker. The newly created tracker must eventually
|
|
be freed with UlpFreeChunkTracker().
|
|
|
|
Arguments:
|
|
|
|
SendIrpStackSize - Supplies the stack size for the network send IRPs.
|
|
|
|
ReadIrpStackSize - Supplies the stack size for the file system read
|
|
IRPs.
|
|
|
|
Return Value:
|
|
|
|
PUL_CHUNK_TRACKER - The new send tracker if successful, NULL otherwise.
|
|
|
|
--***************************************************************************/
|
|
PUL_CHUNK_TRACKER
|
|
UlpAllocateChunkTracker(
|
|
IN UL_TRACKER_TYPE TrackerType,
|
|
IN CCHAR SendIrpStackSize,
|
|
IN CCHAR ReadIrpStackSize,
|
|
IN BOOLEAN FirstResponse,
|
|
IN PUL_HTTP_CONNECTION pHttpConnection,
|
|
IN PUL_INTERNAL_RESPONSE pResponse
|
|
)
|
|
{
|
|
PUL_CHUNK_TRACKER pTracker;
|
|
CCHAR MaxIrpStackSize;
|
|
USHORT MaxIrpSize;
|
|
ULONG ChunkTrackerSize;
|
|
|
|
ASSERT( TrackerType == UlTrackerTypeSend ||
|
|
TrackerType == UlTrackerTypeBuildUriEntry
|
|
);
|
|
|
|
MaxIrpStackSize = MAX(SendIrpStackSize, ReadIrpStackSize);
|
|
|
|
//
|
|
// Try to allocate from the lookaside list if possible.
|
|
//
|
|
|
|
if (MaxIrpStackSize > DEFAULT_MAX_IRP_STACK_SIZE)
|
|
{
|
|
MaxIrpSize = (USHORT) ALIGN_UP(IoSizeOfIrp(MaxIrpStackSize), PVOID);
|
|
|
|
ChunkTrackerSize = ALIGN_UP(sizeof(UL_CHUNK_TRACKER), PVOID) +
|
|
MaxIrpSize;
|
|
|
|
pTracker = (PUL_CHUNK_TRACKER) UL_ALLOCATE_POOL(
|
|
NonPagedPool,
|
|
ChunkTrackerSize,
|
|
UL_CHUNK_TRACKER_POOL_TAG
|
|
);
|
|
|
|
if (pTracker)
|
|
{
|
|
pTracker->Signature = UL_CHUNK_TRACKER_POOL_TAG;
|
|
pTracker->IrpContext.Signature = UL_IRP_CONTEXT_SIGNATURE;
|
|
pTracker->FromLookaside = FALSE;
|
|
|
|
//
|
|
// Set up the IRP.
|
|
//
|
|
|
|
pTracker->pIrp = (PIRP)
|
|
((PCHAR)pTracker + ALIGN_UP(sizeof(UL_CHUNK_TRACKER), PVOID));
|
|
|
|
IoInitializeIrp(
|
|
pTracker->pIrp,
|
|
MaxIrpSize,
|
|
MaxIrpStackSize
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pTracker = UlPplAllocateChunkTracker();
|
|
}
|
|
|
|
if (pTracker != NULL)
|
|
{
|
|
pTracker->Type = TrackerType;
|
|
pTracker->FirstResponse = FirstResponse;
|
|
|
|
//
|
|
// RefCounting is necessary since we might have two Aysnc (Read & Send)
|
|
// Io Operation on the same tracker along the way.
|
|
//
|
|
|
|
pTracker->RefCount = 1;
|
|
pTracker->Terminated = 0;
|
|
|
|
//
|
|
// Tracker will keep a reference to the connection.
|
|
//
|
|
|
|
UL_REFERENCE_HTTP_CONNECTION( pHttpConnection );
|
|
pTracker->pHttpConnection = pHttpConnection;
|
|
|
|
//
|
|
// Response info.
|
|
//
|
|
|
|
UL_REFERENCE_INTERNAL_RESPONSE( pResponse );
|
|
pTracker->pResponse = pResponse;
|
|
|
|
//
|
|
// Zero the remaining fields.
|
|
//
|
|
|
|
UlInitializeWorkItem( &pTracker->WorkItem );
|
|
|
|
RtlZeroMemory(
|
|
(PUCHAR)pTracker + FIELD_OFFSET(UL_CHUNK_TRACKER, IoStatus),
|
|
sizeof(*pTracker) - FIELD_OFFSET(UL_CHUNK_TRACKER, IoStatus)
|
|
);
|
|
|
|
if (TrackerType == UlTrackerTypeSend)
|
|
{
|
|
UlpInitMdlRuns( pTracker );
|
|
}
|
|
}
|
|
|
|
if (TrackerType == UlTrackerTypeSend)
|
|
{
|
|
UlTrace(SEND_RESPONSE, (
|
|
"Http!UlpAllocateChunkTracker: tracker %p (send)\n",
|
|
pTracker
|
|
));
|
|
}
|
|
else
|
|
{
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlpAllocateChunkTracker: tracker %p (build uri)\n",
|
|
pTracker
|
|
));
|
|
}
|
|
|
|
return pTracker;
|
|
|
|
} // UlpAllocateChunkTracker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Frees a chunk tracker allocated with UlpAllocateChunkTracker().
|
|
If this is a send tracker, also free the MDL_RUNs attached to it.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - Supplies the work item embedded in UL_CHUNK_TRACKER.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpFreeChunkTracker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
PUL_CHUNK_TRACKER pTracker;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pTracker = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_CHUNK_TRACKER,
|
|
WorkItem
|
|
);
|
|
|
|
ASSERT( pTracker );
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
ASSERT( pTracker->Type == UlTrackerTypeSend ||
|
|
pTracker->Type == UlTrackerTypeBuildUriEntry
|
|
);
|
|
|
|
//
|
|
// Free the MDLs attached if this is a send tracker.
|
|
//
|
|
|
|
if (pTracker->Type == UlTrackerTypeSend)
|
|
{
|
|
UlTrace(SEND_RESPONSE, (
|
|
"Http!UlpFreeChunkTracker: tracker %p (send)\n",
|
|
pTracker
|
|
));
|
|
|
|
UlpFreeMdlRuns( pTracker );
|
|
}
|
|
else
|
|
{
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlpFreeChunkTracker: tracker %p (build uri)\n",
|
|
pTracker
|
|
));
|
|
}
|
|
|
|
#if DBG
|
|
//
|
|
// There should be no file buffer hanging around at this time.
|
|
//
|
|
|
|
if (pTracker->Type == UlTrackerTypeSend)
|
|
{
|
|
ULONG i;
|
|
PUL_MDL_RUN pMdlRun = &pTracker->SendInfo.MdlRuns[0];
|
|
|
|
for (i = 0; i < UL_MAX_MDL_RUNS; i++)
|
|
{
|
|
if (pMdlRun->FileBuffer.pFileCacheEntry)
|
|
{
|
|
ASSERT( pMdlRun->FileBuffer.pMdl == NULL );
|
|
ASSERT( pMdlRun->FileBuffer.pFileData == NULL );
|
|
}
|
|
pMdlRun++;
|
|
}
|
|
}
|
|
#endif // DBG
|
|
|
|
//
|
|
// Release our ref to the connection and response.
|
|
//
|
|
|
|
UL_DEREFERENCE_HTTP_CONNECTION( pTracker->pHttpConnection );
|
|
UL_DEREFERENCE_INTERNAL_RESPONSE( pTracker->pResponse );
|
|
|
|
if (pTracker->FromLookaside)
|
|
{
|
|
UlPplFreeChunkTracker( pTracker );
|
|
}
|
|
else
|
|
{
|
|
UL_FREE_POOL_WITH_SIG( pTracker, UL_CHUNK_TRACKER_POOL_TAG );
|
|
}
|
|
|
|
} // UlpFreeChunkTracker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Increments the reference count on the chunk tracker.
|
|
|
|
Arguments:
|
|
|
|
pTracker - Supplies the chunk trucker to the reference.
|
|
|
|
pFileName (REFERENCE_DEBUG only) - Supplies the name of the file
|
|
containing the calling function.
|
|
|
|
LineNumber (REFERENCE_DEBUG only) - Supplies the line number of
|
|
the calling function.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlReferenceChunkTracker(
|
|
IN PUL_CHUNK_TRACKER pTracker
|
|
REFERENCE_DEBUG_FORMAL_PARAMS
|
|
)
|
|
{
|
|
LONG RefCount;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
|
|
//
|
|
// Reference it.
|
|
//
|
|
|
|
RefCount = InterlockedIncrement( &pTracker->RefCount );
|
|
ASSERT( RefCount > 1 );
|
|
|
|
//
|
|
// Keep the logs updated.
|
|
//
|
|
|
|
WRITE_REF_TRACE_LOG(
|
|
g_pChunkTrackerTraceLog,
|
|
REF_ACTION_REFERENCE_CHUNK_TRACKER,
|
|
RefCount,
|
|
pTracker,
|
|
pFileName,
|
|
LineNumber
|
|
);
|
|
|
|
UlTrace(SEND_RESPONSE,(
|
|
"Http!UlReferenceChunkTracker: tracker %p RefCount %ld\n",
|
|
pTracker,
|
|
RefCount
|
|
));
|
|
|
|
} // UlReferenceChunkTracker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Decrements the reference count on the specified chunk tracker.
|
|
|
|
Arguments:
|
|
|
|
pTracker - Supplies the chunk trucker to the reference.
|
|
|
|
pFileName (REFERENCE_DEBUG only) - Supplies the name of the file
|
|
containing the calling function.
|
|
|
|
LineNumber (REFERENCE_DEBUG only) - Supplies the line number of
|
|
the calling function.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlDereferenceChunkTracker(
|
|
IN PUL_CHUNK_TRACKER pTracker
|
|
REFERENCE_DEBUG_FORMAL_PARAMS
|
|
)
|
|
{
|
|
LONG RefCount;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
|
|
//
|
|
// Dereference it.
|
|
//
|
|
|
|
RefCount = InterlockedDecrement( &pTracker->RefCount );
|
|
ASSERT(RefCount >= 0);
|
|
|
|
//
|
|
// Keep the logs updated.
|
|
//
|
|
|
|
WRITE_REF_TRACE_LOG(
|
|
g_pChunkTrackerTraceLog,
|
|
REF_ACTION_DEREFERENCE_CHUNK_TRACKER,
|
|
RefCount,
|
|
pTracker,
|
|
pFileName,
|
|
LineNumber
|
|
);
|
|
|
|
UlTrace(SEND_RESPONSE,(
|
|
"Http!UlDereferenceChunkTracker: tracker %p RefCount %ld\n",
|
|
pTracker,
|
|
RefCount
|
|
));
|
|
|
|
if (RefCount == 0)
|
|
{
|
|
//
|
|
// The final reference to the chunk tracker has been removed,
|
|
// so it's time to free-up the ChunkTracker.
|
|
//
|
|
|
|
UL_CALL_PASSIVE(
|
|
&pTracker->WorkItem,
|
|
UlpFreeChunkTracker
|
|
);
|
|
}
|
|
|
|
} // UlDereferenceChunkTracker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Closes the connection if neccessary, cleans up trackers, and completes
|
|
the response.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - Supplies the work item embedded in our UL_CHUNK_TRACKER.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpCompleteSendResponseWorker(
|
|
PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
PUL_CHUNK_TRACKER pTracker;
|
|
PUL_COMPLETION_ROUTINE pCompletionRoutine;
|
|
PVOID pCompletionContext;
|
|
PUL_HTTP_CONNECTION pHttpConnection;
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
PUL_INTERNAL_RESPONSE pResponse;
|
|
NTSTATUS Status;
|
|
ULONGLONG BytesTransferred;
|
|
KIRQL OldIrql;
|
|
BOOLEAN ResumeParsing;
|
|
BOOLEAN InDisconnect;
|
|
HTTP_VERB RequestVerb;
|
|
USHORT ResponseStatusCode;
|
|
PUL_LOG_DATA_BUFFER pLogData;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pTracker = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_CHUNK_TRACKER,
|
|
WorkItem
|
|
);
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
|
|
pResponse = pTracker->pResponse;
|
|
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pResponse ) );
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pResponse->pRequest ) );
|
|
ASSERT( !pResponse->pLogData );
|
|
ASSERT( pResponse->ResumeParsingType != UlResumeParsingOnSendCompletion );
|
|
|
|
//
|
|
// Pull info from the tracker.
|
|
//
|
|
|
|
RequestVerb = pResponse->pRequest->Verb;
|
|
pCompletionRoutine = pResponse->pCompletionRoutine;
|
|
pCompletionContext = pResponse->pCompletionContext;
|
|
pRequest = pResponse->pRequest;
|
|
BytesTransferred = pResponse->BytesTransferred;
|
|
ResponseStatusCode = pResponse->StatusCode;
|
|
Status = pTracker->IoStatus.Status;
|
|
pHttpConnection = pTracker->pHttpConnection;
|
|
ResumeParsing = FALSE;
|
|
pLogData = NULL;
|
|
InDisconnect = (BOOLEAN)
|
|
(pResponse->Flags & HTTP_SEND_RESPONSE_FLAG_DISCONNECT);
|
|
|
|
TRACE_TIME(
|
|
pHttpConnection->ConnectionId,
|
|
pRequest->RequestId,
|
|
TIME_ACTION_SEND_COMPLETE
|
|
);
|
|
|
|
//
|
|
// Reset the connection if there was an error.
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
UlCloseConnection(
|
|
pHttpConnection->pConnection,
|
|
TRUE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
//
|
|
// Adjust the bytes sent and send status on the request.
|
|
//
|
|
|
|
UlInterlockedAdd64(
|
|
(PLONGLONG) &pRequest->BytesSent,
|
|
BytesTransferred
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status) && NT_SUCCESS(pRequest->LogStatus))
|
|
{
|
|
pRequest->LogStatus = Status;
|
|
}
|
|
|
|
IF_DEBUG(LOGBYTES)
|
|
{
|
|
TIME_FIELDS RcvdTimeFields;
|
|
|
|
RtlTimeToTimeFields( &pRequest->TimeStamp, &RcvdTimeFields );
|
|
|
|
UlTrace(LOGBYTES, (
|
|
"Http!UlpCompleteSendResponseWorker: [Rcvd @ %02d:%02d:%02d] "
|
|
"Bytes %010I64u Total %010I64u Status %08lX\n",
|
|
RcvdTimeFields.Hour,
|
|
RcvdTimeFields.Minute,
|
|
RcvdTimeFields.Second,
|
|
BytesTransferred,
|
|
pRequest->BytesSent,
|
|
Status
|
|
));
|
|
}
|
|
|
|
//
|
|
// Stop MinBytesPerSecond timer and start Connection Idle timer.
|
|
//
|
|
|
|
UlLockTimeoutInfo(
|
|
&pHttpConnection->TimeoutInfo,
|
|
&OldIrql
|
|
);
|
|
|
|
//
|
|
// Turn off MinBytesPerSecond timer if there are no outstanding sends.
|
|
//
|
|
|
|
UlResetConnectionTimer(
|
|
&pHttpConnection->TimeoutInfo,
|
|
TimerMinBytesPerSecond
|
|
);
|
|
|
|
if (0 == (pResponse->Flags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA) &&
|
|
pRequest->ParseState >= ParseDoneState)
|
|
{
|
|
//
|
|
// Turn on Idle Timer if there's no more response data AND all of
|
|
// the request data has been received.
|
|
//
|
|
|
|
UlSetConnectionTimer(
|
|
&pHttpConnection->TimeoutInfo,
|
|
TimerConnectionIdle
|
|
);
|
|
}
|
|
|
|
UlUnlockTimeoutInfo(
|
|
&pHttpConnection->TimeoutInfo,
|
|
OldIrql
|
|
);
|
|
|
|
UlEvaluateTimerState(
|
|
&pHttpConnection->TimeoutInfo
|
|
);
|
|
|
|
//
|
|
// Adjust SendsPending and if that drops to zero, see if we need to log
|
|
// and resume parsing.
|
|
//
|
|
|
|
UlUnsetRequestSendsPending(
|
|
pRequest,
|
|
&pLogData,
|
|
&ResumeParsing
|
|
);
|
|
|
|
if (pLogData)
|
|
{
|
|
UlLogHttpResponse( pRequest, pLogData );
|
|
}
|
|
|
|
//
|
|
// Unlink the request from process if we are done with all sends.
|
|
//
|
|
|
|
if (0 == (pResponse->Flags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA) &&
|
|
0 == pRequest->ContentLength &&
|
|
0 == pRequest->Chunked &&
|
|
pRequest->ConfigInfo.pAppPool)
|
|
{
|
|
ASSERT( pRequest->SentLast );
|
|
|
|
UlUnlinkRequestFromProcess(
|
|
pRequest->ConfigInfo.pAppPool,
|
|
pRequest
|
|
);
|
|
}
|
|
|
|
//
|
|
// Complete the send response IRP.
|
|
//
|
|
|
|
ASSERT( pCompletionRoutine != NULL );
|
|
|
|
(pCompletionRoutine)(
|
|
pCompletionContext,
|
|
Status,
|
|
(ULONG) MIN(BytesTransferred, MAXULONG)
|
|
);
|
|
|
|
//
|
|
// Kick the parser on the connection and release our hold.
|
|
//
|
|
|
|
if (ResumeParsing && STATUS_SUCCESS == Status)
|
|
{
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlpCompleteSendResponseWorker(pHttpConn = %p), "
|
|
"RequestVerb=%d, ResponseStatusCode=%hu\n",
|
|
pHttpConnection,
|
|
RequestVerb,
|
|
ResponseStatusCode
|
|
));
|
|
|
|
UlResumeParsing( pHttpConnection, FALSE, InDisconnect );
|
|
}
|
|
|
|
//
|
|
// Deref the tracker that we have bumped up before queueing this worker
|
|
// function. This has to be done after UlResumeParsing since the tracker
|
|
// holds a reference on the HTTP connection.
|
|
//
|
|
|
|
UL_DEREFERENCE_CHUNK_TRACKER( pTracker );
|
|
|
|
} // UlpCompleteSendResponseWorker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Completion handler for MDL READ IRPs used for reading file data.
|
|
|
|
Arguments:
|
|
|
|
pDeviceObject - Supplies the device object for the IRP being
|
|
completed.
|
|
|
|
pIrp - Supplies the IRP being completed.
|
|
|
|
pContext - Supplies the context associated with this request.
|
|
This is actually a PUL_CHUNK_TRACKER.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - STATUS_SUCCESS if IO should continue processing this
|
|
IRP, STATUS_MORE_PROCESSING_REQUIRED if IO should stop processing
|
|
this IRP.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpRestartMdlRead(
|
|
IN PDEVICE_OBJECT pDeviceObject,
|
|
IN PIRP pIrp,
|
|
IN PVOID pContext
|
|
)
|
|
{
|
|
PUL_CHUNK_TRACKER pTracker;
|
|
|
|
UNREFERENCED_PARAMETER( pDeviceObject );
|
|
|
|
pTracker = (PUL_CHUNK_TRACKER) pContext;
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"UlpRestartMdlRead: tracker %p\n",
|
|
pTracker
|
|
));
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
|
|
pTracker->IoStatus = pIrp->IoStatus;
|
|
|
|
UlpQueueResponseWorkItem(
|
|
&pTracker->WorkItem,
|
|
UlpMdlReadCompleteWorker,
|
|
pTracker->pResponse->SyncRead
|
|
);
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
|
|
} // UlpRestartMdlRead
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
The worker routine for UlpRestartMdlRead since we can potentially call
|
|
into UlResumeParsing which requires PASSIVE.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - Supplies the work item embedded in UL_CHUNK_TRACKER.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpMdlReadCompleteWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_CHUNK_TRACKER pTracker;
|
|
PUL_INTERNAL_RESPONSE pResponse;
|
|
ULONG BytesRead;
|
|
PMDL pMdl;
|
|
PMDL pMdlTail;
|
|
BOOLEAN ResumeParsing = FALSE;
|
|
PUL_MDL_RUN pMdlRun;
|
|
PUL_FILE_BUFFER pFileBuffer;
|
|
ULONG RunCount;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pTracker = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_CHUNK_TRACKER,
|
|
WorkItem
|
|
);
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"UlpMdlReadCompleteWorker: tracker %p\n",
|
|
pTracker
|
|
));
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
|
|
pResponse = pTracker->pResponse;
|
|
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pResponse ) );
|
|
|
|
//
|
|
// Get the last MdlRun from the tracker.
|
|
//
|
|
|
|
RunCount = pTracker->SendInfo.MdlRunCount;
|
|
pMdlRun = &pTracker->SendInfo.MdlRuns[RunCount];
|
|
|
|
Status = pTracker->IoStatus.Status;
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
BytesRead = (ULONG) pTracker->IoStatus.Information;
|
|
|
|
if (BytesRead)
|
|
{
|
|
pFileBuffer = &pMdlRun->FileBuffer;
|
|
pMdl = pFileBuffer->pMdl;
|
|
|
|
ASSERT( pMdl );
|
|
|
|
//
|
|
// Update the buffered byte count and append the new MDL onto
|
|
// our MDL chain.
|
|
//
|
|
|
|
pMdlTail = UlFindLastMdlInChain( pMdl );
|
|
|
|
pTracker->SendInfo.BytesBuffered += BytesRead;
|
|
(*pTracker->SendInfo.pMdlLink) = pMdl;
|
|
pTracker->SendInfo.pMdlLink = &pMdlTail->Next;
|
|
|
|
pMdlRun->pMdlTail = pMdlTail;
|
|
pTracker->SendInfo.MdlRunCount++;
|
|
|
|
//
|
|
// Update the file offset & bytes remaining. If we've
|
|
// finished this file chunk (bytes remaining is now zero)
|
|
// then advance to the next chunk.
|
|
//
|
|
|
|
pResponse->FileOffset.QuadPart += (ULONGLONG) BytesRead;
|
|
pResponse->FileBytesRemaining.QuadPart -= (ULONGLONG) BytesRead;
|
|
}
|
|
else
|
|
{
|
|
ASSERT( !"Status success but zero bytes received!" );
|
|
}
|
|
|
|
if (pResponse->FileBytesRemaining.QuadPart == 0)
|
|
{
|
|
UlpIncrementChunkPointer( pResponse );
|
|
}
|
|
|
|
//
|
|
// If we've not exhausted our static MDL run array,
|
|
// we've exceeded the maximum number of bytes we want to
|
|
// buffer, then we'll need to initiate a flush.
|
|
//
|
|
|
|
if (IS_SEND_COMPLETE(pResponse) ||
|
|
UL_MAX_MDL_RUNS == pTracker->SendInfo.MdlRunCount ||
|
|
pTracker->SendInfo.BytesBuffered >= g_UlMaxBytesPerSend)
|
|
{
|
|
if (IS_SEND_COMPLETE(pResponse) &&
|
|
UlResumeParsingOnLastSend == pResponse->ResumeParsingType)
|
|
{
|
|
ResumeParsing = TRUE;
|
|
}
|
|
|
|
Status = UlpFlushMdlRuns( pTracker );
|
|
|
|
//
|
|
// Kick the parser into action if this is the last send for the
|
|
// keep-alive. Resuming parsing here improves latency when incoming
|
|
// requests are pipelined.
|
|
//
|
|
|
|
if (ResumeParsing)
|
|
{
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlpRestartMdlRead(pHttpConn = %p), "
|
|
"RequestVerb=%d, ResponseStatusCode=%hu\n",
|
|
pResponse->pRequest->pHttpConn,
|
|
pResponse->pRequest->Verb,
|
|
pResponse->StatusCode
|
|
));
|
|
|
|
UlResumeParsing(
|
|
pResponse->pRequest->pHttpConn,
|
|
FALSE,
|
|
(BOOLEAN) (pResponse->Flags & HTTP_SEND_RESPONSE_FLAG_DISCONNECT)
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// RefCount the chunk tracker up for the UlpSendHttpResponseWorker.
|
|
// It will DeRef it when it's done with the chunk tracker itself.
|
|
// Since this is a passive call we had to increment the refcount
|
|
// for this guy to make sure that tracker is around until it wakes
|
|
// up. Other places makes calls to UlpSendHttpResponseWorker has
|
|
// also been updated as well.
|
|
//
|
|
|
|
UL_REFERENCE_CHUNK_TRACKER( pTracker );
|
|
|
|
UlpSendHttpResponseWorker( &pTracker->WorkItem );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Do not increment the MdlRunCount, as we are not able to update the
|
|
// MDL Links. Instead cleanup the last allocated MDL Run for the read.
|
|
//
|
|
|
|
UlpFreeFileMdlRun( pTracker, pMdlRun );
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
UlCompleteSendResponse( pTracker, Status );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Read I/O has been completed release our refcount
|
|
// on the chunk tracker.
|
|
//
|
|
|
|
UL_DEREFERENCE_CHUNK_TRACKER( pTracker );
|
|
}
|
|
|
|
} // UlpMdlReadCompleteWorker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Completion handler for UlSendData().
|
|
|
|
Arguments:
|
|
|
|
pCompletionContext - Supplies an uninterpreted context value
|
|
as passed to the asynchronous API. This is actually a
|
|
pointer to a UL_CHUNK_TRACKER structure.
|
|
|
|
Status - Supplies the final completion status of the
|
|
asynchronous API.
|
|
|
|
Information - Optionally supplies additional information about
|
|
the completed operation, such as the number of bytes
|
|
transferred.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpRestartMdlSend(
|
|
IN PVOID pCompletionContext,
|
|
IN NTSTATUS Status,
|
|
IN ULONG_PTR Information
|
|
)
|
|
{
|
|
PUL_CHUNK_TRACKER pTracker;
|
|
LONG SendCount;
|
|
|
|
pTracker = (PUL_CHUNK_TRACKER) pCompletionContext;
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"UlpRestartMdlSend: tracker %p\n",
|
|
pTracker
|
|
));
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
|
|
//
|
|
// Handle the completion in a work item. We need to get to passive
|
|
// level and we also need to prevent a recursive loop on filtered
|
|
// connections or any other case where our sends might all be
|
|
// completing in-line.
|
|
//
|
|
|
|
if (pTracker->SendInfo.pMdlToSplit)
|
|
{
|
|
//
|
|
// This is the split send.
|
|
//
|
|
|
|
SendCount = InterlockedDecrement( &pTracker->SendInfo.SendCount );
|
|
ASSERT( SendCount >= 0 );
|
|
|
|
if (0 == SendCount)
|
|
{
|
|
//
|
|
// Simply drops the reference on the tracker if this is the
|
|
// second part of the split send.
|
|
//
|
|
|
|
UL_DEREFERENCE_CHUNK_TRACKER( pTracker );
|
|
}
|
|
else
|
|
{
|
|
pTracker->IoStatus.Status = Status;
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Report the bytes transferred for the whole send in the
|
|
// success case since we may have split into 2 TDI calls.
|
|
//
|
|
|
|
pTracker->IoStatus.Information = pTracker->SendInfo.BytesBuffered;
|
|
}
|
|
else
|
|
{
|
|
pTracker->IoStatus.Information = Information;
|
|
}
|
|
|
|
UlpQueueResponseWorkItem(
|
|
&pTracker->WorkItem,
|
|
UlpMdlSendCompleteWorker,
|
|
pTracker->pResponse->SyncRead
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This is the normal send.
|
|
//
|
|
|
|
ASSERT( -1 == pTracker->SendInfo.SendCount );
|
|
|
|
pTracker->IoStatus.Status = Status;
|
|
pTracker->IoStatus.Information = Information;
|
|
|
|
UlpQueueResponseWorkItem(
|
|
&pTracker->WorkItem,
|
|
UlpMdlSendCompleteWorker,
|
|
pTracker->pResponse->SyncRead
|
|
);
|
|
}
|
|
|
|
} // UlpRestartMdlSend
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Deferred handler for UlpRestartMdlSend.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - Supplies a pointer to the work item queued. This should
|
|
point to the WORK_ITEM structure embedded in a UL_CHUNK_TRACKER.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpMdlSendCompleteWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
PUL_CHUNK_TRACKER pTracker;
|
|
PUL_CHUNK_TRACKER pSendTracker;
|
|
PUL_INTERNAL_RESPONSE pResponse;
|
|
PUL_HTTP_CONNECTION pHttpConn;
|
|
PDEVICE_OBJECT pConnectionObject;
|
|
NTSTATUS Status;
|
|
BOOLEAN DerefChunkTracker = TRUE;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pTracker = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_CHUNK_TRACKER,
|
|
WorkItem
|
|
);
|
|
|
|
UlTrace(SEND_RESPONSE, (
|
|
"UlpMdlSendCompleteWorker: tracker %p\n",
|
|
pTracker
|
|
));
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pTracker->pResponse ) );
|
|
|
|
pResponse = pTracker->pResponse;
|
|
|
|
//
|
|
// If the chunk completed successfully, then update the bytes
|
|
// transferred and queue another work item for the next chunk if
|
|
// there's more work to do. Otherwise, just complete the request now.
|
|
//
|
|
|
|
Status = pTracker->IoStatus.Status;
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
pResponse->BytesTransferred += pTracker->IoStatus.Information;
|
|
|
|
if (!IS_SEND_COMPLETE(pResponse))
|
|
{
|
|
//
|
|
// Allocate a new send tracker for the next round of MDL_RUNs.
|
|
//
|
|
|
|
pHttpConn = pTracker->pHttpConnection;
|
|
pConnectionObject =
|
|
pHttpConn->pConnection->ConnectionObject.pDeviceObject;
|
|
|
|
pSendTracker =
|
|
UlpAllocateChunkTracker(
|
|
UlTrackerTypeSend,
|
|
pConnectionObject->StackSize,
|
|
pResponse->MaxFileSystemStackSize,
|
|
FALSE,
|
|
pHttpConn,
|
|
pResponse
|
|
);
|
|
|
|
if (pSendTracker)
|
|
{
|
|
UlpSendHttpResponseWorker( &pSendTracker->WorkItem );
|
|
goto end;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Reset the connection since we hit an internal error.
|
|
//
|
|
|
|
UlCloseConnection(
|
|
pHttpConn->pConnection,
|
|
TRUE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
Status = STATUS_NO_MEMORY;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// All done.
|
|
//
|
|
|
|
UlCompleteSendResponse( pTracker, Status );
|
|
|
|
//
|
|
// UlCompleteSendResponse takes ownership of the CHUNK_TRACKER so no extra
|
|
// dereference is required.
|
|
//
|
|
|
|
DerefChunkTracker = FALSE;
|
|
|
|
end:
|
|
|
|
//
|
|
// Release our grab on the Tracker. Send I/O is done for this MDL run.
|
|
//
|
|
|
|
if (DerefChunkTracker)
|
|
{
|
|
UL_DEREFERENCE_CHUNK_TRACKER( pTracker );
|
|
}
|
|
|
|
} // UlpMdlSendCompleteWorker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Flush the MDL_RUNs we have built so far.
|
|
|
|
Arguments:
|
|
|
|
pTracker - Supplies the send tracker to flush.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpFlushMdlRuns(
|
|
IN PUL_CHUNK_TRACKER pTracker
|
|
)
|
|
{
|
|
PUL_INTERNAL_RESPONSE pResponse;
|
|
PMDL pMdlToSplit = NULL;
|
|
PMDL pMdlPrevious;
|
|
PMDL pMdlSplitFirst;
|
|
PMDL pMdlSplitSecond = NULL;
|
|
PMDL pMdlHead = NULL;
|
|
ULONG BytesToSplit = 0;
|
|
ULONG BytesBuffered;
|
|
ULONG BytesPart1;
|
|
ULONG BytesPart2;
|
|
NTSTATUS Status;
|
|
BOOLEAN SendComplete;
|
|
BOOLEAN CopySend = FALSE;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pTracker->pResponse ) );
|
|
ASSERT( pTracker->SendInfo.pMdlHead );
|
|
|
|
pResponse = pTracker->pResponse;
|
|
SendComplete = (BOOLEAN) IS_SEND_COMPLETE( pResponse );
|
|
|
|
//
|
|
// We may need to split the send into 2 TDI calls if the send is *large*
|
|
// and it is not filtered.
|
|
//
|
|
|
|
if (!pTracker->pHttpConnection->pConnection->FilterInfo.pFilterChannel &&
|
|
pTracker->SendInfo.BytesBuffered > g_UlMaxCopyThreshold &&
|
|
(!SendComplete || pResponse->CopySend))
|
|
{
|
|
//
|
|
// These many bytes go to the first part of the MDL chain after split.
|
|
//
|
|
|
|
if (!SendComplete)
|
|
{
|
|
BytesToSplit = pTracker->SendInfo.BytesBuffered / 2;
|
|
}
|
|
else
|
|
{
|
|
ASSERT( pResponse->CopySend );
|
|
|
|
CopySend = TRUE;
|
|
BytesToSplit = pTracker->SendInfo.BytesBuffered -
|
|
g_UlMaxCopyThreshold;
|
|
}
|
|
|
|
//
|
|
// Find the first MDL starting from pMdlHead that has more than
|
|
// or equal to BytesToSplit bytes buffered.
|
|
//
|
|
|
|
pMdlPrevious = NULL;
|
|
pMdlToSplit = pTracker->SendInfo.pMdlHead;
|
|
BytesBuffered = 0;
|
|
|
|
while (pMdlToSplit->Next)
|
|
{
|
|
if ((BytesBuffered + pMdlToSplit->ByteCount) >= BytesToSplit)
|
|
{
|
|
//
|
|
// So the current MDL splits the chain.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
BytesBuffered += pMdlToSplit->ByteCount;
|
|
pMdlPrevious = pMdlToSplit;
|
|
pMdlToSplit = pMdlToSplit->Next;
|
|
}
|
|
|
|
ASSERT( pMdlToSplit );
|
|
ASSERT( (BytesBuffered + pMdlToSplit->ByteCount) >= BytesToSplit );
|
|
|
|
if ((BytesBuffered + pMdlToSplit->ByteCount) == BytesToSplit)
|
|
{
|
|
//
|
|
// There is no need to build partial MDLs of the split MDL. The
|
|
// whole MDL chain up to and including pMdlToSplit goes to the
|
|
// first half the splitted chain and the MDL chain starting from
|
|
// pMdlToSplit->Next goes to the second half.
|
|
//
|
|
|
|
ASSERT( pMdlToSplit->Next );
|
|
|
|
pMdlHead = pTracker->SendInfo.pMdlHead;
|
|
pMdlSplitFirst = pMdlToSplit;
|
|
pMdlSplitSecond = pMdlToSplit->Next;
|
|
pMdlToSplit->Next = NULL;
|
|
}
|
|
else
|
|
{
|
|
BytesPart2 = BytesBuffered + pMdlToSplit->ByteCount - BytesToSplit;
|
|
BytesPart1 = pMdlToSplit->ByteCount - BytesPart2;
|
|
|
|
ASSERT( BytesPart1 );
|
|
ASSERT( BytesPart2 );
|
|
|
|
pMdlSplitFirst =
|
|
UlAllocateMdl(
|
|
(PCHAR) MmGetMdlVirtualAddress(pMdlToSplit),
|
|
BytesPart1,
|
|
FALSE,
|
|
FALSE,
|
|
NULL
|
|
);
|
|
|
|
if (!pMdlSplitFirst)
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
pMdlSplitSecond =
|
|
UlAllocateMdl(
|
|
(PCHAR) MmGetMdlVirtualAddress(pMdlToSplit) + BytesPart1,
|
|
BytesPart2,
|
|
FALSE,
|
|
FALSE,
|
|
NULL
|
|
);
|
|
|
|
if (!pMdlSplitSecond)
|
|
{
|
|
UlFreeMdl( pMdlSplitFirst );
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
IoBuildPartialMdl(
|
|
pMdlToSplit,
|
|
pMdlSplitFirst,
|
|
(PCHAR) MmGetMdlVirtualAddress(pMdlToSplit),
|
|
BytesPart1
|
|
);
|
|
|
|
IoBuildPartialMdl(
|
|
pMdlToSplit,
|
|
pMdlSplitSecond,
|
|
(PCHAR) MmGetMdlVirtualAddress(pMdlToSplit) + BytesPart1,
|
|
BytesPart2
|
|
);
|
|
|
|
//
|
|
// Relink the MDL chains after the split.
|
|
//
|
|
|
|
if (pMdlPrevious)
|
|
{
|
|
pMdlHead = pTracker->SendInfo.pMdlHead;
|
|
pMdlPrevious->Next = pMdlSplitFirst;
|
|
}
|
|
else
|
|
{
|
|
ASSERT( pMdlToSplit == pTracker->SendInfo.pMdlHead );
|
|
pMdlHead = pMdlSplitFirst;
|
|
}
|
|
|
|
pMdlSplitSecond->Next = pMdlToSplit->Next;
|
|
}
|
|
|
|
//
|
|
// Remember how we have split the send.
|
|
//
|
|
|
|
pTracker->SendInfo.pMdlToSplit = pMdlToSplit;
|
|
pTracker->SendInfo.pMdlPrevious = pMdlPrevious;
|
|
pTracker->SendInfo.pMdlSplitFirst = pMdlSplitFirst;
|
|
pTracker->SendInfo.pMdlSplitSecond = pMdlSplitSecond;
|
|
}
|
|
|
|
//
|
|
// Make sure there are no other sends in progress on this response.
|
|
// Wait if this is the case. Since it is possible for the first part
|
|
// of the split send to complete inline, it can start a new MDL run
|
|
// and proceed to flush *before* the second part of the split send
|
|
// has a chance to pend the data in TDI. Of course, this logic is not
|
|
// needed if we know the current flush is both the first and the last
|
|
// of MDL runs.
|
|
//
|
|
|
|
if (!SendComplete || !pTracker->FirstResponse)
|
|
{
|
|
UlAcquirePushLockExclusive( &pResponse->PushLock );
|
|
}
|
|
|
|
//
|
|
// Increment the reference on tracker for each Send I/O.
|
|
// UlpMdlSendCompleteWorker will release it later.
|
|
//
|
|
|
|
UL_REFERENCE_CHUNK_TRACKER( pTracker );
|
|
|
|
if (pMdlToSplit)
|
|
{
|
|
//
|
|
// We need to issue 2 TDI calls since we have split the send.
|
|
//
|
|
|
|
pTracker->SendInfo.SendCount = 2;
|
|
|
|
Status = UlSendData(
|
|
pTracker->pHttpConnection->pConnection,
|
|
pMdlHead,
|
|
BytesToSplit,
|
|
UlpRestartMdlSend,
|
|
pTracker,
|
|
pTracker->pIrp,
|
|
&pTracker->IrpContext,
|
|
FALSE,
|
|
FALSE
|
|
);
|
|
|
|
ASSERT( Status == STATUS_PENDING);
|
|
|
|
//
|
|
// Increment the extra reference on tracker for the Split Send I/O.
|
|
// UlpMdlSendCompleteWorker will release it later.
|
|
//
|
|
|
|
UL_REFERENCE_CHUNK_TRACKER( pTracker );
|
|
|
|
if (CopySend)
|
|
{
|
|
Status = UlpCopySend(
|
|
pTracker,
|
|
pMdlSplitSecond,
|
|
pTracker->SendInfo.BytesBuffered - BytesToSplit,
|
|
(BOOLEAN) (SendComplete &&
|
|
IS_DISCONNECT_TIME(pResponse)),
|
|
(BOOLEAN) (pResponse->pRequest->ParseState >=
|
|
ParseDoneState)
|
|
);
|
|
}
|
|
else
|
|
{
|
|
Status = UlSendData(
|
|
pTracker->pHttpConnection->pConnection,
|
|
pMdlSplitSecond,
|
|
pTracker->SendInfo.BytesBuffered - BytesToSplit,
|
|
UlpRestartMdlSend,
|
|
pTracker,
|
|
NULL,
|
|
NULL,
|
|
(BOOLEAN) (SendComplete &&
|
|
IS_DISCONNECT_TIME(pResponse)),
|
|
(BOOLEAN) (pResponse->pRequest->ParseState >=
|
|
ParseDoneState)
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Use -1 so we know we haven't done any split on this send.
|
|
//
|
|
|
|
pTracker->SendInfo.SendCount = -1;
|
|
|
|
//
|
|
// If this the last send to be issued for this response, we can ask
|
|
// UlSendData to initiate a disconnect on our behalf if appropriate.
|
|
//
|
|
|
|
Status = UlSendData(
|
|
pTracker->pHttpConnection->pConnection,
|
|
pTracker->SendInfo.pMdlHead,
|
|
pTracker->SendInfo.BytesBuffered,
|
|
UlpRestartMdlSend,
|
|
pTracker,
|
|
pTracker->pIrp,
|
|
&pTracker->IrpContext,
|
|
(BOOLEAN) (SendComplete &&
|
|
IS_DISCONNECT_TIME(pResponse)),
|
|
(BOOLEAN) (pResponse->pRequest->ParseState >=
|
|
ParseDoneState)
|
|
);
|
|
}
|
|
|
|
//
|
|
// Pave the way for a new UlpFlushMdlRuns to proceed.
|
|
//
|
|
|
|
if (!SendComplete || !pTracker->FirstResponse)
|
|
{
|
|
UlReleasePushLockExclusive( &pResponse->PushLock );
|
|
}
|
|
|
|
//
|
|
// Start the next response in the pending response list if exists.
|
|
// The tracker should still have one reference held by the caller
|
|
// so it is safe to touch its fields here.
|
|
//
|
|
|
|
if (pResponse->SendEnqueued && SendComplete)
|
|
{
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pResponse->pRequest ) );
|
|
UlpDequeueSendHttpResponse( pResponse->pRequest );
|
|
}
|
|
|
|
ASSERT( Status == STATUS_PENDING);
|
|
|
|
return Status;
|
|
|
|
} // UlpFlushMdlRuns
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Cleans the MDL_RUNs in the specified tracker and prepares the
|
|
tracker for reuse.
|
|
|
|
Arguments:
|
|
|
|
pTracker - Supplies the tracker to clean.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpFreeMdlRuns(
|
|
IN PUL_CHUNK_TRACKER pTracker
|
|
)
|
|
{
|
|
PMDL pMdlHead;
|
|
PMDL pMdlNext;
|
|
PMDL pMdlTmp;
|
|
PMDL pMdlToSplit;
|
|
PMDL pMdlPrevious;
|
|
PMDL pMdlSplitFirst;
|
|
PMDL pMdlSplitSecond;
|
|
PUL_MDL_RUN pMdlRun;
|
|
ULONG RunCount;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
|
|
//
|
|
// Restore the original MDL chain and ByteCount if we have splitted
|
|
// this send.
|
|
//
|
|
|
|
pMdlToSplit = pTracker->SendInfo.pMdlToSplit;
|
|
|
|
if (pMdlToSplit)
|
|
{
|
|
pMdlPrevious = pTracker->SendInfo.pMdlPrevious;
|
|
pMdlSplitFirst = pTracker->SendInfo.pMdlSplitFirst;
|
|
pMdlSplitSecond = pTracker->SendInfo.pMdlSplitSecond;
|
|
|
|
ASSERT( pMdlSplitFirst );
|
|
ASSERT( pMdlSplitSecond );
|
|
|
|
if (pMdlSplitFirst == pMdlToSplit)
|
|
{
|
|
//
|
|
// No partial MDL involved. Simply link back the MDLs.
|
|
//
|
|
|
|
pMdlSplitFirst->Next = pMdlSplitSecond;
|
|
}
|
|
else
|
|
{
|
|
ASSERT( pMdlToSplit->Next == pMdlSplitSecond->Next );
|
|
|
|
if (pMdlPrevious)
|
|
{
|
|
pMdlPrevious->Next = pMdlToSplit;
|
|
}
|
|
else
|
|
{
|
|
ASSERT( pMdlToSplit == pTracker->SendInfo.pMdlHead );
|
|
}
|
|
|
|
//
|
|
// Free the partial MDLs we have built for the split send.
|
|
//
|
|
|
|
UlFreeMdl( pMdlSplitFirst );
|
|
UlFreeMdl( pMdlSplitSecond );
|
|
}
|
|
}
|
|
|
|
pMdlHead = pTracker->SendInfo.pMdlHead;
|
|
pMdlRun = &pTracker->SendInfo.MdlRuns[0];
|
|
RunCount = pTracker->SendInfo.MdlRunCount;
|
|
|
|
while (RunCount > 0)
|
|
{
|
|
ASSERT( pMdlHead != NULL );
|
|
ASSERT( pMdlRun->pMdlTail != NULL );
|
|
|
|
pMdlNext = pMdlRun->pMdlTail->Next;
|
|
pMdlRun->pMdlTail->Next = NULL;
|
|
|
|
if (pMdlRun->FileBuffer.pFileCacheEntry == NULL)
|
|
{
|
|
//
|
|
// It's a memory/cache run; just walk & free the MDL chain.
|
|
// UlFreeMdl unmaps the data for partial MDLs so no need to
|
|
// unmap here.
|
|
//
|
|
|
|
while (pMdlHead != NULL)
|
|
{
|
|
pMdlTmp = pMdlHead->Next;
|
|
UlFreeMdl( pMdlHead );
|
|
pMdlHead = pMdlTmp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// It's a file run, free the Mdl.
|
|
//
|
|
|
|
UlpFreeFileMdlRun( pTracker, pMdlRun );
|
|
}
|
|
|
|
pMdlHead = pMdlNext;
|
|
pMdlRun++;
|
|
RunCount--;
|
|
}
|
|
|
|
} // UlpFreeMdlRuns
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Clean up the specified Read File MDL_RUN.
|
|
|
|
Arguments:
|
|
|
|
pTracker - Supplies the UL_CHUNK_TRACKER to clean up.
|
|
|
|
pMdlRun - Supplies the Read File MDL_RUN.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpFreeFileMdlRun(
|
|
IN OUT PUL_CHUNK_TRACKER pTracker,
|
|
IN OUT PUL_MDL_RUN pMdlRun
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_FILE_BUFFER pFileBuffer;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
|
|
//
|
|
// It should be a file run.
|
|
//
|
|
|
|
pFileBuffer = &pMdlRun->FileBuffer;
|
|
|
|
ASSERT( pFileBuffer->pFileCacheEntry );
|
|
|
|
Status = UlReadCompleteFileEntryFast( pFileBuffer );
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Fast path failed, we'll need an IRP which has been pre-built.
|
|
// We need to do this synchronously as the read IRP can be used by
|
|
// the next UlpFreeFileMdlRun. UlReadCompleteFileEntry will complete
|
|
// synchronously if we set pCompletionRoutine to NULL.
|
|
//
|
|
|
|
pFileBuffer->pCompletionRoutine = NULL;
|
|
pFileBuffer->pContext = NULL;
|
|
|
|
Status = UlReadCompleteFileEntry(
|
|
pFileBuffer,
|
|
pTracker->pIrp
|
|
);
|
|
|
|
ASSERT( STATUS_SUCCESS == Status );
|
|
}
|
|
|
|
} // UlpFreeFileMdlRun
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Copy the data from the MDL chain starting from pMdl and send it to TDI.
|
|
|
|
Arguments:
|
|
|
|
pTracker - Supplies the tracker to send.
|
|
|
|
pMdl - Supplies the MDL chain to send.
|
|
|
|
Length - Supplies the total length of the MDL chain.
|
|
|
|
InitiateDisconnect - Supplies the disconnect flag passed to TDI.
|
|
|
|
RequestComplete - Supplies the request-complete flag passed to TDI.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpCopySend(
|
|
IN PUL_CHUNK_TRACKER pTracker,
|
|
IN PMDL pMdl,
|
|
IN ULONG Length,
|
|
IN BOOLEAN InitiateDisconnect,
|
|
IN BOOLEAN RequestComplete
|
|
)
|
|
{
|
|
PMDL pMdlCopied = NULL;
|
|
PUCHAR pDataCopied = NULL;
|
|
PUCHAR pData;
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT( pMdl );
|
|
ASSERT( g_UlMaxCopyThreshold == Length );
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pTracker->pResponse ) );
|
|
|
|
//
|
|
// Allocate memory and MDL that can hold the whole incoming MDL chain.
|
|
//
|
|
|
|
pDataCopied = (PUCHAR) UL_ALLOCATE_POOL(
|
|
NonPagedPool,
|
|
Length,
|
|
UL_COPY_SEND_DATA_POOL_TAG
|
|
);
|
|
|
|
if (!pDataCopied)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
pMdlCopied = UlAllocateMdl(
|
|
pDataCopied,
|
|
Length,
|
|
FALSE,
|
|
FALSE,
|
|
NULL
|
|
);
|
|
|
|
if (!pMdlCopied)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
MmBuildMdlForNonPagedPool( pMdlCopied );
|
|
|
|
//
|
|
// Copy the data from the MDL chain starting pMdl to pMdlCopied.
|
|
//
|
|
|
|
while (pMdl)
|
|
{
|
|
pData = MmGetSystemAddressForMdlSafe(
|
|
pMdl,
|
|
LowPagePriority
|
|
);
|
|
|
|
if (!pData)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(
|
|
pDataCopied,
|
|
pData,
|
|
MmGetMdlByteCount(pMdl)
|
|
);
|
|
|
|
pDataCopied += MmGetMdlByteCount(pMdl);
|
|
pMdl = pMdl->Next;
|
|
}
|
|
|
|
//
|
|
// Send pMdlCopied if everything is ok so far.
|
|
//
|
|
|
|
Status = UlSendData(
|
|
pTracker->pHttpConnection->pConnection,
|
|
pMdlCopied,
|
|
Length,
|
|
UlpRestartCopySend,
|
|
pMdlCopied,
|
|
NULL,
|
|
NULL,
|
|
InitiateDisconnect,
|
|
RequestComplete
|
|
);
|
|
|
|
ASSERT( Status == STATUS_PENDING);
|
|
|
|
end:
|
|
|
|
//
|
|
// Return pending from here since we always complete the send
|
|
// inline in both error and success cases.
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (pDataCopied)
|
|
{
|
|
UL_FREE_POOL( pDataCopied, UL_COPY_SEND_DATA_POOL_TAG );
|
|
}
|
|
|
|
if (pMdlCopied)
|
|
{
|
|
UlFreeMdl( pMdlCopied );
|
|
}
|
|
|
|
UlpRestartMdlSend( pTracker, Status, 0 );
|
|
}
|
|
else
|
|
{
|
|
UlpRestartMdlSend( pTracker, STATUS_SUCCESS, Length );
|
|
}
|
|
|
|
return STATUS_PENDING;
|
|
|
|
} // UlpCopySend
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Completion for the second half of a copy send.
|
|
|
|
Arguments:
|
|
|
|
pCompletionContext - Supplies an uninterpreted context value
|
|
as passed to the asynchronous API.
|
|
|
|
Status - Supplies the final completion status of the
|
|
asynchronous API.
|
|
|
|
Information - Optionally supplies additional information about
|
|
the completed operation, such as the number of bytes
|
|
transferred.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpRestartCopySend(
|
|
IN PVOID pCompletionContext,
|
|
IN NTSTATUS Status,
|
|
IN ULONG_PTR Information
|
|
)
|
|
{
|
|
PMDL pMdl = (PMDL) pCompletionContext;
|
|
|
|
UNREFERENCED_PARAMETER( Status );
|
|
UNREFERENCED_PARAMETER( Information );
|
|
|
|
UL_FREE_POOL(
|
|
MmGetMdlVirtualAddress( pMdl ),
|
|
UL_COPY_SEND_DATA_POOL_TAG
|
|
);
|
|
|
|
UlFreeMdl( pMdl );
|
|
|
|
} // UlpRestartCopySend
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Increments the current chunk pointer in the tracker and initializes
|
|
some of the "from file" related tracker fields if necessary.
|
|
|
|
Arguments:
|
|
|
|
pTracker - Supplies the UL_CHUNK_TRACKER to manipulate.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpIncrementChunkPointer(
|
|
IN OUT PUL_INTERNAL_RESPONSE pResponse
|
|
)
|
|
{
|
|
PUL_INTERNAL_DATA_CHUNK pCurrentChunk;
|
|
|
|
//
|
|
// Bump the data chunk. If the request is still incomplete, then
|
|
// check the new current chunk. If it's "from file", then
|
|
// initialize the file offset & bytes remaining from the
|
|
// supplied byte range.
|
|
//
|
|
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pResponse ) );
|
|
ASSERT( pResponse->CurrentChunk == ULONG_MAX ||
|
|
pResponse->CurrentChunk < pResponse->ChunkCount );
|
|
|
|
if (ULONG_MAX == pResponse->CurrentChunk)
|
|
{
|
|
pResponse->CurrentChunk = 0;
|
|
}
|
|
else
|
|
{
|
|
pResponse->CurrentChunk++;
|
|
}
|
|
|
|
if (!IS_SEND_COMPLETE(pResponse))
|
|
{
|
|
pCurrentChunk = &pResponse->pDataChunks[pResponse->CurrentChunk];
|
|
|
|
if (IS_FROM_FILE_HANDLE(pCurrentChunk))
|
|
{
|
|
pResponse->FileOffset =
|
|
pCurrentChunk->FromFileHandle.ByteRange.StartingOffset;
|
|
pResponse->FileBytesRemaining =
|
|
pCurrentChunk->FromFileHandle.ByteRange.Length;
|
|
}
|
|
else
|
|
{
|
|
ASSERT( IS_FROM_MEMORY(pCurrentChunk) ||
|
|
IS_FROM_FRAGMENT_CACHE(pCurrentChunk) );
|
|
}
|
|
}
|
|
|
|
} // UlpIncrementChunkPointer
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Creates a cache entry for the given response. This routine actually
|
|
allocates the entry and partly initializes it. Then it allocates
|
|
a UL_CHUNK_TRACKER to keep track of filesystem reads.
|
|
|
|
Arguments:
|
|
|
|
pRequest - Supplies the initiating request.
|
|
|
|
pResponse - Supplies the generated response.
|
|
|
|
pProcess - UL_APP_POOL_PROCESS that is building this cache entry.
|
|
|
|
Flags - UlSendHttpResponse flags.
|
|
|
|
CachePolicy - Supplies the cache policy to be enforced on the cache entry.
|
|
|
|
pCompletionRoutine - Supplies the completion routine to be called after
|
|
entry is sent.
|
|
|
|
pCompletionContext - Supplies the completion context passed to
|
|
pCompletionRoutine.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpBuildCacheEntry(
|
|
IN PUL_INTERNAL_REQUEST pRequest,
|
|
IN PUL_INTERNAL_RESPONSE pResponse,
|
|
IN PUL_APP_POOL_PROCESS pProcess,
|
|
IN HTTP_CACHE_POLICY CachePolicy,
|
|
IN PUL_COMPLETION_ROUTINE pCompletionRoutine,
|
|
IN PVOID pCompletionContext
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PUL_URI_CACHE_ENTRY pEntry = NULL;
|
|
PUL_CHUNK_TRACKER pTracker = NULL;
|
|
ULONG SpaceLength = 0;
|
|
USHORT LogDataLength = 0;
|
|
ULONG ContentLength;
|
|
PUL_LOG_DATA_BUFFER pLogData = NULL;
|
|
ULONG CookedUrlLength = pRequest->CookedUrl.Length;
|
|
LONG AbsPathLength;
|
|
ULONG i;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
if (HttpCachePolicyTimeToLive == CachePolicy.Policy &&
|
|
0 == CachePolicy.SecondsToLive )
|
|
{
|
|
//
|
|
// A TTL of 0 seconds doesn't make sense. Bail out.
|
|
//
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
ContentLength =
|
|
(ULONG) (pResponse->ResponseLength - pResponse->HeaderLength);
|
|
|
|
//
|
|
// See if we need to store any logging data. If so, we need to
|
|
// calculate the required cache space for the logging data.
|
|
//
|
|
|
|
if (pResponse->pLogData)
|
|
{
|
|
pLogData = pResponse->pLogData;
|
|
ASSERT( IS_VALID_LOG_DATA_BUFFER( pLogData ) );
|
|
|
|
LogDataLength = UlComputeCachedLogDataLength( pLogData );
|
|
}
|
|
|
|
if (pRequest->ConfigInfo.SiteUrlType == HttpUrlSite_NamePlusIP)
|
|
{
|
|
//
|
|
// RoutingToken + AbsPath goes to cache.
|
|
//
|
|
|
|
ASSERT( DIFF(pRequest->CookedUrl.pAbsPath - pRequest->CookedUrl.pUrl) > 0 );
|
|
|
|
CookedUrlLength -=
|
|
DIFF(pRequest->CookedUrl.pAbsPath - pRequest->CookedUrl.pUrl)
|
|
* sizeof(WCHAR);
|
|
|
|
CookedUrlLength += pRequest->CookedUrl.RoutingTokenLength;
|
|
}
|
|
|
|
SpaceLength =
|
|
CookedUrlLength + sizeof(WCHAR) + // Space for Hash key +
|
|
pResponse->ETagLength + // ETag +
|
|
pResponse->ContentEncodingLength + // Content-Encoding +
|
|
LogDataLength; // Logging
|
|
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlpBuildCacheEntry allocating UL_URI_CACHE_ENTRY, "
|
|
"0x%x bytes of data\n"
|
|
" Url.Length = 0x%x, aligned Length = 0x%x\n"
|
|
" ContentLength=0x%x, %d\n"
|
|
"\n",
|
|
SpaceLength,
|
|
CookedUrlLength,
|
|
ALIGN_UP(CookedUrlLength, WCHAR),
|
|
ContentLength,
|
|
ContentLength
|
|
));
|
|
|
|
//
|
|
// Allocate a cache entry.
|
|
//
|
|
|
|
pEntry = UlAllocateCacheEntry(
|
|
SpaceLength,
|
|
ContentLength + pResponse->HeaderLength
|
|
);
|
|
|
|
if (pEntry)
|
|
{
|
|
//
|
|
// Initialize the entry.
|
|
//
|
|
|
|
if (pRequest->ConfigInfo.SiteUrlType == HttpUrlSite_NamePlusIP)
|
|
{
|
|
AbsPathLength =
|
|
pRequest->CookedUrl.Length
|
|
- (DIFF(pRequest->CookedUrl.pAbsPath
|
|
- pRequest->CookedUrl.pUrl) * sizeof(WCHAR));
|
|
|
|
ASSERT( AbsPathLength > 0 );
|
|
|
|
UlInitCacheEntry(
|
|
pEntry,
|
|
pRequest->CookedUrl.RoutingHash,
|
|
(ULONG) AbsPathLength,
|
|
pRequest->CookedUrl.pAbsPath,
|
|
NULL,
|
|
pRequest->CookedUrl.pRoutingToken,
|
|
pRequest->CookedUrl.RoutingTokenLength
|
|
);
|
|
}
|
|
else
|
|
{
|
|
UlInitCacheEntry(
|
|
pEntry,
|
|
pRequest->CookedUrl.Hash,
|
|
pRequest->CookedUrl.Length,
|
|
pRequest->CookedUrl.pUrl,
|
|
pRequest->CookedUrl.pAbsPath,
|
|
NULL,
|
|
0
|
|
);
|
|
}
|
|
|
|
//
|
|
// Copy the ETag from the response (for If-* headers).
|
|
//
|
|
|
|
pEntry->pETag =
|
|
(((PUCHAR) pEntry->UriKey.pUri) + // Start of URI +
|
|
pEntry->UriKey.Length + sizeof(WCHAR)); // Length of URI
|
|
|
|
pEntry->ETagLength = pResponse->ETagLength;
|
|
|
|
if (pEntry->ETagLength)
|
|
{
|
|
RtlCopyMemory(
|
|
pEntry->pETag,
|
|
pResponse->pETag,
|
|
pEntry->ETagLength
|
|
);
|
|
}
|
|
|
|
//
|
|
// Capture Content-Encoding so we can verify the Accept-Encoding header
|
|
// on requests.
|
|
//
|
|
|
|
pEntry->pContentEncoding = pEntry->pETag + pEntry->ETagLength;
|
|
pEntry->ContentEncodingLength = pResponse->ContentEncodingLength;
|
|
|
|
if (pEntry->ContentEncodingLength)
|
|
{
|
|
RtlCopyMemory(
|
|
pEntry->pContentEncoding,
|
|
pResponse->pContentEncoding,
|
|
pEntry->ContentEncodingLength
|
|
);
|
|
}
|
|
|
|
//
|
|
// Capture Content-Type so we can verify the Accept: header on requests.
|
|
//
|
|
|
|
if (pResponse->ContentType.Type &&
|
|
pResponse->ContentType.SubType )
|
|
{
|
|
RtlCopyMemory(
|
|
&pEntry->ContentType,
|
|
&pResponse->ContentType,
|
|
sizeof(UL_CONTENT_TYPE)
|
|
);
|
|
}
|
|
|
|
//
|
|
// Get the System Time of the Date: header (for If-* headers).
|
|
//
|
|
|
|
pEntry->CreationTime.QuadPart = pResponse->CreationTime.QuadPart;
|
|
pEntry->ContentLengthSpecified = pResponse->ContentLengthSpecified;
|
|
pEntry->StatusCode = pResponse->StatusCode;
|
|
pEntry->Verb = pRequest->Verb;
|
|
pEntry->CachePolicy = CachePolicy;
|
|
|
|
if (CachePolicy.Policy == HttpCachePolicyTimeToLive)
|
|
{
|
|
ASSERT( 0 != CachePolicy.SecondsToLive );
|
|
|
|
KeQuerySystemTime( &pEntry->ExpirationTime );
|
|
|
|
if (CachePolicy.SecondsToLive > C_SECS_PER_YEAR)
|
|
{
|
|
//
|
|
// Maximum TTL is 1 year.
|
|
//
|
|
|
|
pEntry->CachePolicy.SecondsToLive = C_SECS_PER_YEAR;
|
|
}
|
|
|
|
//
|
|
// Convert seconds to 100 nanosecond intervals (x * 10^7).
|
|
//
|
|
|
|
pEntry->ExpirationTime.QuadPart +=
|
|
pEntry->CachePolicy.SecondsToLive * C_NS_TICKS_PER_SEC;
|
|
|
|
}
|
|
else
|
|
{
|
|
pEntry->ExpirationTime.QuadPart = 0;
|
|
}
|
|
|
|
//
|
|
// Capture the Config Info from the request.
|
|
//
|
|
|
|
ASSERT( IS_VALID_URL_CONFIG_GROUP_INFO( &pRequest->ConfigInfo ) );
|
|
|
|
UlConfigGroupInfoDeepCopy(
|
|
&pRequest->ConfigInfo,
|
|
&pEntry->ConfigInfo
|
|
);
|
|
|
|
//
|
|
// Remember who created us.
|
|
//
|
|
|
|
pEntry->pProcess = pProcess;
|
|
|
|
//
|
|
// Generate the content and fixed headers.
|
|
//
|
|
|
|
if (NULL == pEntry->pMdl)
|
|
{
|
|
Status = STATUS_NO_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
pEntry->HeaderLength = pResponse->HeaderLength;
|
|
|
|
if (FALSE == UlCacheEntrySetData(
|
|
pEntry, // Dest CacheEntry
|
|
pResponse->pHeaders, // Buffer to copy
|
|
pResponse->HeaderLength, // Length to copy
|
|
ContentLength // Offset in Dest MDL
|
|
))
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Generate the content body.
|
|
//
|
|
|
|
pEntry->ContentLength = ContentLength;
|
|
|
|
//
|
|
// Copy over the log data.
|
|
//
|
|
|
|
if (pLogData)
|
|
{
|
|
//
|
|
// There may be no field to save in the cache entry but the logging
|
|
// might still be enabled for those fields we generate later such as
|
|
// date and time.
|
|
//
|
|
|
|
pEntry->LoggingEnabled = TRUE;
|
|
pEntry->LogDataLength = LogDataLength;
|
|
pEntry->pLogData = pEntry->pContentEncoding +
|
|
pEntry->ContentEncodingLength;
|
|
|
|
//
|
|
// Copy over the partially complete log line excluding the date and
|
|
// time fields to the cache entry. Also remember the length of the
|
|
// data.
|
|
//
|
|
|
|
UlCopyCachedLogData(
|
|
pLogData,
|
|
LogDataLength,
|
|
pEntry
|
|
);
|
|
}
|
|
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlpBuildCacheEntry\n"
|
|
" entry = %p\n"
|
|
" pUri = %p '%ls'\n"
|
|
" pMdl = %p (%d bytes)\n"
|
|
" pETag = %p\n"
|
|
" pContentEncoding = %p\n"
|
|
" pLogData = %p\n"
|
|
" end = %p\n",
|
|
pEntry,
|
|
pEntry->UriKey.pUri, pEntry->UriKey.pUri,
|
|
pEntry->pMdl, pEntry->ContentLength + pEntry->HeaderLength,
|
|
pEntry->pETag,
|
|
pEntry->pContentEncoding,
|
|
pEntry->pLogData,
|
|
((PUCHAR)pEntry->UriKey.pUri) + SpaceLength
|
|
));
|
|
|
|
//
|
|
// Completion info.
|
|
//
|
|
|
|
pResponse->pCompletionRoutine = pCompletionRoutine;
|
|
pResponse->pCompletionContext = pCompletionContext;
|
|
|
|
pTracker = UlpAllocateChunkTracker(
|
|
UlTrackerTypeBuildUriEntry,
|
|
0,
|
|
pResponse->MaxFileSystemStackSize,
|
|
TRUE,
|
|
pRequest->pHttpConn,
|
|
pResponse
|
|
);
|
|
|
|
if (pTracker)
|
|
{
|
|
//
|
|
// Initialize the first chunk for the cache build.
|
|
//
|
|
|
|
UlpIncrementChunkPointer( pResponse );
|
|
|
|
//
|
|
// Init the tracker's BuildInfo.
|
|
//
|
|
|
|
pTracker->BuildInfo.pUriEntry = pEntry;
|
|
pTracker->BuildInfo.Offset = 0;
|
|
|
|
RtlZeroMemory(
|
|
&pTracker->BuildInfo.FileBuffer,
|
|
sizeof(pTracker->BuildInfo.FileBuffer)
|
|
);
|
|
|
|
//
|
|
// Skip over the header chunks because we already
|
|
// got that stuff.
|
|
//
|
|
|
|
for (i = 0; i < HEADER_CHUNK_COUNT; i++)
|
|
{
|
|
ASSERT( !IS_SEND_COMPLETE( pResponse ) );
|
|
UlpIncrementChunkPointer( pResponse );
|
|
}
|
|
|
|
//
|
|
// Let the worker do the dirty work, no reason to queue off,
|
|
// it will queue the first time it needs to do I/O.
|
|
//
|
|
|
|
UlpBuildCacheEntryWorker( &pTracker->WorkItem );
|
|
|
|
Status = STATUS_PENDING;
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_NO_MEMORY;
|
|
}
|
|
|
|
cleanup:
|
|
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlpBuildCacheEntry Status = %x, pEntry = %x\n",
|
|
Status,
|
|
pEntry
|
|
));
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (pEntry)
|
|
{
|
|
UlFreeCacheEntry( pEntry );
|
|
}
|
|
|
|
if (pTracker)
|
|
{
|
|
UL_FREE_POOL_WITH_SIG( pTracker, UL_CHUNK_TRACKER_POOL_TAG );
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlpBuildCacheEntry
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Worker routine for managing an in-progress UlpBuildCacheEntry().
|
|
This routine iterates through all the chunks in the response
|
|
and copies the data into the cache entry.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - Supplies a pointer to the work item queued. This should
|
|
point to the WORK_ITEM structure embedded in a UL_CHUNK_TRACKER.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpBuildCacheEntryWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
PUL_CHUNK_TRACKER pTracker;
|
|
NTSTATUS Status;
|
|
PUL_INTERNAL_DATA_CHUNK pCurrentChunk;
|
|
PUCHAR pBuffer;
|
|
ULONG BufferLength;
|
|
PUL_FILE_BUFFER pFileBuffer;
|
|
PUL_INTERNAL_RESPONSE pResponse;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pTracker = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_CHUNK_TRACKER,
|
|
WorkItem
|
|
);
|
|
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlpBuildCacheEntryWorker: tracker %p\n",
|
|
pTracker
|
|
));
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
|
|
pResponse = pTracker->pResponse;
|
|
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pResponse ) );
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
while (TRUE)
|
|
{
|
|
//
|
|
// Capture the current chunk pointer, then check for end of
|
|
// response.
|
|
//
|
|
|
|
pCurrentChunk = &pResponse->pDataChunks[pResponse->CurrentChunk];
|
|
|
|
if (IS_SEND_COMPLETE(pResponse))
|
|
{
|
|
ASSERT( Status == STATUS_SUCCESS );
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Determine the chunk type.
|
|
//
|
|
|
|
if (IS_FROM_MEMORY(pCurrentChunk))
|
|
{
|
|
//
|
|
// It's from a locked-down memory buffer. Since these
|
|
// are always handled in-line (never pended) we can
|
|
// go ahead and adjust the current chunk pointer in the
|
|
// tracker.
|
|
//
|
|
|
|
UlpIncrementChunkPointer( pResponse );
|
|
|
|
//
|
|
// Ignore empty buffers.
|
|
//
|
|
|
|
if (pCurrentChunk->FromMemory.BufferLength == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Copy the incoming memory.
|
|
//
|
|
|
|
ASSERT( pCurrentChunk->FromMemory.pMdl->Next == NULL );
|
|
|
|
pBuffer = (PUCHAR) MmGetMdlVirtualAddress(
|
|
pCurrentChunk->FromMemory.pMdl
|
|
);
|
|
BufferLength = MmGetMdlByteCount( pCurrentChunk->FromMemory.pMdl );
|
|
|
|
UlTrace(LARGE_MEM, (
|
|
"Http!UlpBuildCacheEntryWorker: "
|
|
"copy range %u (%x) -> %u\n",
|
|
pTracker->BuildInfo.Offset,
|
|
BufferLength,
|
|
pTracker->BuildInfo.Offset + BufferLength
|
|
));
|
|
|
|
if (FALSE == UlCacheEntrySetData(
|
|
pTracker->BuildInfo.pUriEntry,
|
|
pBuffer,
|
|
BufferLength,
|
|
pTracker->BuildInfo.Offset
|
|
))
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
pTracker->BuildInfo.Offset += BufferLength;
|
|
ASSERT( pTracker->BuildInfo.Offset <=
|
|
pTracker->BuildInfo.pUriEntry->ContentLength );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// It's a filesystem MDL.
|
|
//
|
|
|
|
ASSERT( IS_FROM_FILE_HANDLE( pCurrentChunk ) );
|
|
|
|
//
|
|
// Ignore empty file ranges.
|
|
//
|
|
|
|
if (pCurrentChunk->FromFileHandle.ByteRange.Length.QuadPart == 0)
|
|
{
|
|
UlpIncrementChunkPointer( pResponse );
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Do the read.
|
|
//
|
|
|
|
pFileBuffer = &pTracker->BuildInfo.FileBuffer;
|
|
|
|
pFileBuffer->pFileCacheEntry =
|
|
&pCurrentChunk->FromFileHandle.FileCacheEntry;
|
|
|
|
pFileBuffer->FileOffset = pResponse->FileOffset;
|
|
pFileBuffer->Length =
|
|
MIN(
|
|
(ULONG) pResponse->FileBytesRemaining.QuadPart,
|
|
g_UlMaxBytesPerRead
|
|
);
|
|
|
|
pFileBuffer->pCompletionRoutine = UlpRestartCacheMdlRead;
|
|
pFileBuffer->pContext = pTracker;
|
|
|
|
Status = UlReadFileEntry(
|
|
pFileBuffer,
|
|
pTracker->pIrp
|
|
);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Did everything complete?
|
|
//
|
|
|
|
if (Status != STATUS_PENDING)
|
|
{
|
|
//
|
|
// Yep, complete the response.
|
|
//
|
|
|
|
UlpCompleteCacheBuild( pTracker, Status );
|
|
}
|
|
|
|
} // UlpBuildCacheEntryWorker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Completion handler for MDL READ IRPs used for reading file data.
|
|
|
|
Arguments:
|
|
|
|
pDeviceObject - Supplies the device object for the IRP being
|
|
completed.
|
|
|
|
pIrp - Supplies the IRP being completed.
|
|
|
|
pContext - Supplies the context associated with this request.
|
|
This is actually a PUL_CHUNK_TRACKER.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - STATUS_SUCCESS if IO should continue processing this
|
|
IRP, STATUS_MORE_PROCESSING_REQUIRED if IO should stop processing
|
|
this IRP.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpRestartCacheMdlRead(
|
|
IN PDEVICE_OBJECT pDeviceObject,
|
|
IN PIRP pIrp,
|
|
IN PVOID pContext
|
|
)
|
|
{
|
|
PUL_CHUNK_TRACKER pTracker = (PUL_CHUNK_TRACKER) pContext;
|
|
|
|
UNREFERENCED_PARAMETER( pDeviceObject );
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
|
|
pTracker->IoStatus = pIrp->IoStatus;
|
|
|
|
UlpQueueResponseWorkItem(
|
|
&pTracker->WorkItem,
|
|
UlpCacheMdlReadCompleteWorker,
|
|
pTracker->pResponse->SyncRead
|
|
);
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
|
|
} // UlpRestartCacheMdlRead
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
The worker routine for UlpRestartCacheMdlRead since UlpRestartCacheMdlRead
|
|
can be called at DISPATH_LEVEL but the cache entry is from PagedPool.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - Supplies the work item embedded in UL_CHUNK_TRACKER.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpCacheMdlReadCompleteWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
NTSTATUS TempStatus;
|
|
PUL_CHUNK_TRACKER pTracker;
|
|
PMDL pMdl;
|
|
PUCHAR pData;
|
|
ULONG DataLen;
|
|
PUL_FILE_BUFFER pFileBuffer;
|
|
|
|
PAGED_CODE();
|
|
|
|
pTracker = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_CHUNK_TRACKER,
|
|
WorkItem
|
|
);
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlpCacheMdlReadCompleteWorker: tracker %p, status %x info %Iu\n",
|
|
pTracker,
|
|
pTracker->IoStatus.Status,
|
|
pTracker->IoStatus.Information
|
|
));
|
|
|
|
Status = pTracker->IoStatus.Status;
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Copy read data into the cache buffer.
|
|
//
|
|
|
|
pMdl = pTracker->BuildInfo.FileBuffer.pMdl;
|
|
|
|
while (pMdl)
|
|
{
|
|
//
|
|
// The MDL chain returned by CacheManager is only guranteed to be
|
|
// locked but not mapped so we have to map before using them.
|
|
// However, there is no need to unmap after using the MDLs as
|
|
// IRP_MN_COMPLETE calls MmUnlockPages which automatically unmaps
|
|
// MDLs if they are mapped into system space.
|
|
//
|
|
|
|
pData = MmGetSystemAddressForMdlSafe(
|
|
pMdl,
|
|
LowPagePriority
|
|
);
|
|
|
|
if (!pData)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
DataLen = MmGetMdlByteCount( pMdl );
|
|
|
|
UlTrace(LARGE_MEM, (
|
|
"Http!UlpRestartCacheMdlRead: "
|
|
"copy range %u (%x) -> %u\n",
|
|
pTracker->BuildInfo.Offset,
|
|
DataLen,
|
|
pTracker->BuildInfo.Offset + DataLen
|
|
));
|
|
|
|
if (FALSE == UlCacheEntrySetData(
|
|
pTracker->BuildInfo.pUriEntry,
|
|
pData,
|
|
DataLen,
|
|
pTracker->BuildInfo.Offset
|
|
))
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
pTracker->BuildInfo.Offset += DataLen;
|
|
ASSERT( pTracker->BuildInfo.Offset <=
|
|
pTracker->BuildInfo.pUriEntry->ContentLength );
|
|
|
|
pMdl = pMdl->Next;
|
|
}
|
|
|
|
//
|
|
// Free the MDLs.
|
|
//
|
|
|
|
pFileBuffer = &pTracker->BuildInfo.FileBuffer;
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
pFileBuffer->pCompletionRoutine = UlpRestartCacheMdlFree;
|
|
pFileBuffer->pContext = pTracker;
|
|
|
|
Status = UlReadCompleteFileEntry(
|
|
pFileBuffer,
|
|
pTracker->pIrp
|
|
);
|
|
|
|
ASSERT( STATUS_PENDING == Status );
|
|
}
|
|
else
|
|
{
|
|
pFileBuffer->pCompletionRoutine = NULL;
|
|
pFileBuffer->pContext = NULL;
|
|
|
|
TempStatus = UlReadCompleteFileEntry(
|
|
pFileBuffer,
|
|
pTracker->pIrp
|
|
);
|
|
|
|
ASSERT( STATUS_SUCCESS == TempStatus );
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
UlpCompleteCacheBuild( pTracker, Status );
|
|
}
|
|
|
|
} // UlpRestartCacheMdlRead
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Completion handler for MDL free IRPs used after reading/copying file data.
|
|
|
|
Arguments:
|
|
|
|
pDeviceObject - Supplies the device object for the IRP being
|
|
completed.
|
|
|
|
pIrp - Supplies the IRP being completed.
|
|
|
|
pContext - Supplies the context associated with this request.
|
|
This is actually a PUL_CHUNK_TRACKER.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - STATUS_SUCCESS if IO should continue processing this
|
|
IRP, STATUS_MORE_PROCESSING_REQUIRED if IO should stop processing
|
|
this IRP.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpRestartCacheMdlFree(
|
|
IN PDEVICE_OBJECT pDeviceObject,
|
|
IN PIRP pIrp,
|
|
IN PVOID pContext
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_CHUNK_TRACKER pTracker;
|
|
PUL_INTERNAL_RESPONSE pResponse;
|
|
|
|
UNREFERENCED_PARAMETER( pDeviceObject );
|
|
|
|
pTracker = (PUL_CHUNK_TRACKER) pContext;
|
|
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlpRestartCacheMdlFree: tracker %p, status %x info %Iu\n",
|
|
pTracker,
|
|
pIrp->IoStatus.Status,
|
|
pIrp->IoStatus.Information
|
|
));
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pTracker->pResponse ) );
|
|
|
|
pResponse = pTracker->pResponse;
|
|
|
|
Status = pIrp->IoStatus.Status;
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Update the file offset & bytes remaining. If we've
|
|
// finished this file chunk (bytes remaining is now zero)
|
|
// then advance to the next chunk.
|
|
//
|
|
|
|
pResponse->FileOffset.QuadPart += pIrp->IoStatus.Information;
|
|
pResponse->FileBytesRemaining.QuadPart -= pIrp->IoStatus.Information;
|
|
|
|
if (pResponse->FileBytesRemaining.QuadPart == 0 )
|
|
{
|
|
UlpIncrementChunkPointer( pResponse );
|
|
}
|
|
|
|
//
|
|
// Go back into the loop if there's more to read
|
|
//
|
|
|
|
if (IS_SEND_COMPLETE(pResponse))
|
|
{
|
|
UlpCompleteCacheBuild( pTracker, Status );
|
|
}
|
|
else
|
|
{
|
|
UlpQueueResponseWorkItem(
|
|
&pTracker->WorkItem,
|
|
UlpBuildCacheEntryWorker,
|
|
pTracker->pResponse->SyncRead
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// MDL free should never fail.
|
|
//
|
|
|
|
ASSERT( FALSE );
|
|
|
|
UlpCompleteCacheBuild( pTracker, Status );
|
|
}
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
|
|
} // UlpRestartCacheMdlFree
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine gets called when we finish building a cache entry.
|
|
|
|
Arguments:
|
|
|
|
pTracker - Supplies the tracker to complete.
|
|
|
|
Status - Supplies the completion status.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpCompleteCacheBuild(
|
|
IN PUL_CHUNK_TRACKER pTracker,
|
|
IN NTSTATUS Status
|
|
)
|
|
{
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlpCompleteCacheBuild: tracker %p, status %08lx\n",
|
|
pTracker,
|
|
Status
|
|
));
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
|
|
pTracker->IoStatus.Status = Status;
|
|
|
|
UL_CALL_PASSIVE(
|
|
&pTracker->WorkItem,
|
|
UlpCompleteCacheBuildWorker
|
|
);
|
|
|
|
} // UlpCompleteCacheBuild
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Called when we finish building a cache entry. If the entry was
|
|
built successfully, we send the response down the wire.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - Supplies a pointer to the work item queued. This should
|
|
point to the WORK_ITEM structure embedded in a UL_CHUNK_TRACKER.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpCompleteCacheBuildWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
PUL_CHUNK_TRACKER pTracker;
|
|
PUL_URI_CACHE_ENTRY pUriCacheEntry;
|
|
PUL_HTTP_CONNECTION pHttpConnection;
|
|
PUL_COMPLETION_ROUTINE pCompletionRoutine;
|
|
PVOID pCompletionContext;
|
|
ULONG Flags;
|
|
PUL_LOG_DATA_BUFFER pLogData;
|
|
LONGLONG BytesToSend;
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pTracker = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_CHUNK_TRACKER,
|
|
WorkItem
|
|
);
|
|
|
|
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
|
|
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pTracker->pResponse ) );
|
|
|
|
pUriCacheEntry = pTracker->BuildInfo.pUriEntry;
|
|
ASSERT( IS_VALID_URI_CACHE_ENTRY( pUriCacheEntry ) );
|
|
|
|
pHttpConnection = pTracker->pHttpConnection;
|
|
Flags = pTracker->pResponse->Flags;
|
|
pCompletionRoutine = pTracker->pResponse->pCompletionRoutine;
|
|
pCompletionContext = pTracker->pResponse->pCompletionContext;
|
|
Status = pTracker->IoStatus.Status;
|
|
|
|
//
|
|
// Save the logging data pointer before releasing the tracker and
|
|
// its response pointer.
|
|
//
|
|
|
|
pLogData = pTracker->pResponse->pLogData;
|
|
|
|
if (pLogData)
|
|
{
|
|
//
|
|
// To prevent SendResponse to free our log buffer.
|
|
//
|
|
|
|
pTracker->pResponse->pLogData = NULL;
|
|
|
|
//
|
|
// Give the sign that this log data buffer is ready and later
|
|
// there's no need to refresh its content from cache again.
|
|
//
|
|
|
|
pLogData->Flags.CacheAndSendResponse = TRUE;
|
|
}
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Try to put the entry into the hash table.
|
|
//
|
|
|
|
UlAddCacheEntry( pUriCacheEntry );
|
|
|
|
//
|
|
// Start the MinBytesPerSecond timer, since the data length
|
|
// is in the UL_URI_CACHE_ENTRY.
|
|
//
|
|
|
|
BytesToSend = pUriCacheEntry->ContentLength +
|
|
pUriCacheEntry->HeaderLength;
|
|
|
|
UlSetMinBytesPerSecondTimer(
|
|
&pHttpConnection->TimeoutInfo,
|
|
BytesToSend
|
|
);
|
|
|
|
//
|
|
// Grab the connection lock because UlpSendCacheEntry assumes you
|
|
// have it.
|
|
//
|
|
|
|
UlAcquirePushLockExclusive( &pHttpConnection->PushLock );
|
|
|
|
//
|
|
// Send the cache entry.
|
|
//
|
|
// We never pipeline requests for build & send path. We will defer
|
|
// resume parsing until send completion.
|
|
//
|
|
|
|
Status = UlpSendCacheEntry(
|
|
pHttpConnection,
|
|
Flags,
|
|
pUriCacheEntry,
|
|
pCompletionRoutine,
|
|
pCompletionContext,
|
|
pLogData,
|
|
UlResumeParsingOnSendCompletion
|
|
);
|
|
|
|
//
|
|
// Get rid of the cache entry if it didn't work.
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
UlCheckinUriCacheEntry( pUriCacheEntry );
|
|
}
|
|
|
|
//
|
|
// Done with the connection lock.
|
|
//
|
|
|
|
UlReleasePushLockExclusive( &pHttpConnection->PushLock );
|
|
}
|
|
|
|
//
|
|
// We assume the ownership of the original log buffer.
|
|
// If send didn't go through, then we have to clean it
|
|
// up here.
|
|
//
|
|
|
|
if (pLogData && !NT_SUCCESS(Status))
|
|
{
|
|
UlDestroyLogDataBuffer( pLogData );
|
|
}
|
|
|
|
//
|
|
// Free the read tracker. Do this after calling UlpSendCacheEntry
|
|
// as to hold onto the HTTP connection reference.
|
|
//
|
|
|
|
UlpFreeChunkTracker( &pTracker->WorkItem );
|
|
|
|
//
|
|
// If it's not STATUS_PENDING for some reason, complete the request.
|
|
//
|
|
|
|
if (Status != STATUS_PENDING && pCompletionRoutine != NULL)
|
|
{
|
|
(pCompletionRoutine)(
|
|
pCompletionContext,
|
|
Status,
|
|
0
|
|
);
|
|
}
|
|
|
|
} // UlpCompleteCacheBuildWorker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Sends a cache entry down the wire.
|
|
|
|
The logging related part of this function below, surely depends on
|
|
the fact that pCompletionContext will be null if this is called for
|
|
pure cache hits (in other words from UlSendCachedResponse) otherwise
|
|
pointer to Irp will be passed down as the pCompletionContext.
|
|
|
|
Arguments:
|
|
|
|
pHttpConnection - Supplies the UL_HTTP_CONNECTION to send the cached
|
|
response.
|
|
|
|
Flags - HTTP_SEND_RESPONSE flags.
|
|
|
|
pUriCacheEntry - Supplies the cache entry to send the response.
|
|
|
|
pCompletionRoutine - Supplies the completion routine to call upon
|
|
send completion.
|
|
|
|
pCompletionContext - Passed to pCompletionRoutine.
|
|
|
|
pLogData - Supplies the log data (only in the build cache case).
|
|
|
|
ResumeParsingType - Tells whether resume parsing happens on send completion
|
|
or after send but before send completion.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpSendCacheEntry(
|
|
PUL_HTTP_CONNECTION pHttpConnection,
|
|
ULONG Flags,
|
|
PUL_URI_CACHE_ENTRY pUriCacheEntry,
|
|
PUL_COMPLETION_ROUTINE pCompletionRoutine,
|
|
PVOID pCompletionContext,
|
|
PUL_LOG_DATA_BUFFER pLogData,
|
|
UL_RESUME_PARSING_TYPE ResumeParsingType
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PUL_FULL_TRACKER pTracker;
|
|
CCHAR SendIrpStackSize;
|
|
UL_CONN_HDR ConnHeader;
|
|
ULONG VarHeaderGenerated;
|
|
ULONG ContentLengthStringLength;
|
|
UCHAR ContentLength[MAX_ULONGLONG_STR];
|
|
LARGE_INTEGER CreationTime;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT( UL_IS_VALID_HTTP_CONNECTION( pHttpConnection ) );
|
|
ASSERT( IS_VALID_URI_CACHE_ENTRY( pUriCacheEntry ) );
|
|
ASSERT( UlDbgPushLockOwnedExclusive( &pHttpConnection->PushLock ) );
|
|
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlpSendCacheEntry(httpconn %p, flags %x, uri %p, ...)\n",
|
|
pHttpConnection,
|
|
Flags,
|
|
pUriCacheEntry
|
|
));
|
|
|
|
//
|
|
// Init vars so we can cleanup correctly if we jump to the end.
|
|
//
|
|
|
|
pTracker = NULL;
|
|
|
|
//
|
|
// Make sure we're still connected.
|
|
//
|
|
|
|
if (pHttpConnection->UlconnDestroyed)
|
|
{
|
|
Status = STATUS_CONNECTION_ABORTED;
|
|
goto cleanup;
|
|
}
|
|
|
|
ASSERT( pHttpConnection->pRequest );
|
|
|
|
if (!pUriCacheEntry->ContentLengthSpecified &&
|
|
UlNeedToGenerateContentLength(
|
|
pUriCacheEntry->Verb,
|
|
pUriCacheEntry->StatusCode,
|
|
Flags
|
|
))
|
|
{
|
|
//
|
|
// Autogenerate a Content-Length header.
|
|
//
|
|
|
|
PCHAR pEnd = UlStrPrintUlonglong(
|
|
(PCHAR) ContentLength,
|
|
(ULONGLONG) pUriCacheEntry->ContentLength,
|
|
ANSI_NULL
|
|
);
|
|
ContentLengthStringLength = DIFF(pEnd - (PCHAR) ContentLength);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Either we cannot or do not need to autogenerate a
|
|
// Content-Length header.
|
|
//
|
|
|
|
ContentLength[0] = ANSI_NULL;
|
|
ContentLengthStringLength = 0;
|
|
}
|
|
|
|
ConnHeader = UlChooseConnectionHeader(
|
|
pHttpConnection->pRequest->Version,
|
|
(BOOLEAN) (Flags & HTTP_SEND_RESPONSE_FLAG_DISCONNECT)
|
|
);
|
|
|
|
//
|
|
// Create a cache tracker.
|
|
//
|
|
|
|
SendIrpStackSize =
|
|
pHttpConnection->pConnection->ConnectionObject.pDeviceObject->StackSize;
|
|
|
|
if (SendIrpStackSize > DEFAULT_MAX_IRP_STACK_SIZE)
|
|
{
|
|
pTracker = UlpAllocateCacheTracker( SendIrpStackSize );
|
|
}
|
|
else
|
|
{
|
|
pTracker = pHttpConnection->pRequest->pTracker;
|
|
}
|
|
|
|
if (pTracker)
|
|
{
|
|
//
|
|
// Init the tracker.
|
|
//
|
|
|
|
UL_REFERENCE_HTTP_CONNECTION( pHttpConnection );
|
|
UL_REFERENCE_INTERNAL_REQUEST( pHttpConnection->pRequest );
|
|
|
|
pTracker->pUriEntry = pUriCacheEntry;
|
|
pTracker->pHttpConnection = pHttpConnection;
|
|
pTracker->pRequest = pHttpConnection->pRequest;
|
|
pTracker->pCompletionRoutine = pCompletionRoutine;
|
|
pTracker->pCompletionContext = pCompletionContext;
|
|
pTracker->Flags = Flags;
|
|
pTracker->pLogData = NULL;
|
|
|
|
//
|
|
// Resume parse if this cache response comes from
|
|
// the UlpCompleteCacheBuildWorker path, or if we are at maximum
|
|
// pipelined requests.
|
|
//
|
|
|
|
pTracker->ResumeParsingType = ResumeParsingType;
|
|
|
|
//
|
|
// Build MDLs for send.
|
|
//
|
|
|
|
ASSERT( pUriCacheEntry->pMdl != NULL );
|
|
|
|
MmInitializeMdl(
|
|
pTracker->pMdlFixedHeaders,
|
|
(PCHAR) MmGetMdlVirtualAddress(pUriCacheEntry->pMdl) +
|
|
pUriCacheEntry->ContentLength,
|
|
pUriCacheEntry->HeaderLength
|
|
);
|
|
|
|
IoBuildPartialMdl(
|
|
pUriCacheEntry->pMdl,
|
|
pTracker->pMdlFixedHeaders,
|
|
(PCHAR) MmGetMdlVirtualAddress(pUriCacheEntry->pMdl) +
|
|
pUriCacheEntry->ContentLength,
|
|
pUriCacheEntry->HeaderLength
|
|
);
|
|
|
|
//
|
|
// Generate the variable headers and build a MDL for them.
|
|
//
|
|
|
|
UlGenerateVariableHeaders(
|
|
ConnHeader,
|
|
TRUE,
|
|
ContentLength,
|
|
ContentLengthStringLength,
|
|
pTracker->pAuxiliaryBuffer,
|
|
&VarHeaderGenerated,
|
|
&CreationTime
|
|
);
|
|
|
|
ASSERT( VarHeaderGenerated <= g_UlMaxVariableHeaderSize );
|
|
ASSERT( VarHeaderGenerated <= pTracker->AuxilaryBufferLength );
|
|
|
|
pTracker->pMdlVariableHeaders->ByteCount = VarHeaderGenerated;
|
|
pTracker->pMdlFixedHeaders->Next = pTracker->pMdlVariableHeaders;
|
|
|
|
//
|
|
// Build a MDL for the body.
|
|
//
|
|
|
|
if (pUriCacheEntry->ContentLength)
|
|
{
|
|
MmInitializeMdl(
|
|
pTracker->pMdlContent,
|
|
MmGetMdlVirtualAddress(pUriCacheEntry->pMdl),
|
|
pUriCacheEntry->ContentLength
|
|
);
|
|
|
|
IoBuildPartialMdl(
|
|
pUriCacheEntry->pMdl,
|
|
pTracker->pMdlContent,
|
|
MmGetMdlVirtualAddress(pUriCacheEntry->pMdl),
|
|
pUriCacheEntry->ContentLength
|
|
);
|
|
|
|
pTracker->pMdlVariableHeaders->Next = pTracker->pMdlContent;
|
|
}
|
|
else
|
|
{
|
|
pTracker->pMdlVariableHeaders->Next = NULL;
|
|
}
|
|
|
|
//
|
|
// Check whether we have to log this cache hit or not.
|
|
//
|
|
|
|
if (pUriCacheEntry->LoggingEnabled)
|
|
{
|
|
//
|
|
// If logging data is provided use it rather than allocating
|
|
// a new one. Because the log buffer already gets allocated
|
|
// for build & send cache hits.
|
|
//
|
|
|
|
if (pLogData)
|
|
{
|
|
ASSERT( pCompletionContext != NULL );
|
|
pTracker->pLogData = pLogData;
|
|
}
|
|
|
|
//
|
|
// Or else, LogData will get allocated when send completion
|
|
// happens just before we do the logging.
|
|
//
|
|
}
|
|
|
|
//
|
|
// Go go go!
|
|
//
|
|
|
|
UL_QUEUE_WORK_ITEM(
|
|
&pTracker->WorkItem,
|
|
UlpSendCacheEntryWorker
|
|
);
|
|
|
|
Status = STATUS_PENDING;
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
cleanup:
|
|
|
|
//
|
|
// Clean up the tracker if we don't need it.
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (pTracker)
|
|
{
|
|
UL_DEREFERENCE_INTERNAL_REQUEST( pTracker->pRequest );
|
|
pTracker->pRequest = NULL;
|
|
|
|
UL_DEREFERENCE_HTTP_CONNECTION( pTracker->pHttpConnection );
|
|
pTracker->pHttpConnection = NULL;
|
|
|
|
UlpFreeCacheTracker( pTracker );
|
|
}
|
|
}
|
|
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlpSendCacheEntry status = %08x\n",
|
|
Status
|
|
));
|
|
|
|
return Status;
|
|
|
|
} // UlpSendCacheEntry
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Called to send a cached response. This is done in a worker to avoid
|
|
holding the connection resource for too long. This also prevents
|
|
recursion if we keep on hitting pipelined cache-hit responses.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - Supplies a pointer to the work item queued. This should
|
|
point to the WORK_ITEM structure embedded in a UL_FULL_TRACKER.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpSendCacheEntryWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
PUL_FULL_TRACKER pTracker;
|
|
NTSTATUS Status;
|
|
PUL_HTTP_CONNECTION pHttpConnection = NULL;
|
|
BOOLEAN ResumeParsing = FALSE;
|
|
BOOLEAN InDisconnect = FALSE;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pTracker = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_FULL_TRACKER,
|
|
WorkItem
|
|
);
|
|
|
|
ASSERT( IS_VALID_FULL_TRACKER( pTracker ) );
|
|
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlpSendCacheEntryWorker(pTracker %p)\n",
|
|
pTracker
|
|
));
|
|
|
|
//
|
|
// Take an extra reference for the pHttpConnection if we are going to
|
|
// resume parsing inline because UlpCompleteSendCacheEntry can be
|
|
// called when UlSendData returns.
|
|
//
|
|
|
|
if (UlResumeParsingOnLastSend == pTracker->ResumeParsingType)
|
|
{
|
|
pHttpConnection = pTracker->pHttpConnection;
|
|
UL_REFERENCE_HTTP_CONNECTION( pHttpConnection );
|
|
|
|
ResumeParsing = TRUE;
|
|
}
|
|
|
|
if (pTracker->Flags & HTTP_SEND_RESPONSE_FLAG_DISCONNECT)
|
|
{
|
|
InDisconnect = TRUE;
|
|
}
|
|
|
|
Status = UlSendData(
|
|
pTracker->pHttpConnection->pConnection,
|
|
pTracker->pMdlFixedHeaders,
|
|
MmGetMdlByteCount(pTracker->pMdlFixedHeaders) +
|
|
MmGetMdlByteCount(pTracker->pMdlVariableHeaders) +
|
|
pTracker->pUriEntry->ContentLength,
|
|
UlpCompleteSendCacheEntry,
|
|
pTracker,
|
|
pTracker->pSendIrp,
|
|
&pTracker->IrpContext,
|
|
InDisconnect,
|
|
TRUE
|
|
);
|
|
|
|
if (ResumeParsing)
|
|
{
|
|
//
|
|
// pHttpConnection is safe to use since we have already added an
|
|
// extra reference for it.
|
|
//
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
UlResumeParsing( pHttpConnection, TRUE, InDisconnect );
|
|
}
|
|
|
|
UL_DEREFERENCE_HTTP_CONNECTION( pHttpConnection );
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (pTracker->pLogData)
|
|
{
|
|
UlDestroyLogDataBuffer( pTracker->pLogData );
|
|
pTracker->pLogData = NULL;
|
|
}
|
|
|
|
UL_DEREFERENCE_INTERNAL_REQUEST( pTracker->pRequest );
|
|
UL_DEREFERENCE_HTTP_CONNECTION( pTracker->pHttpConnection );
|
|
|
|
UlpFreeCacheTracker( pTracker );
|
|
}
|
|
|
|
} // UlpSendCacheEntryWorker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Called when we finish sending data to the client. Just queues to
|
|
a worker that runs at passive level.
|
|
|
|
Arguments:
|
|
|
|
pCompletionContext - Supplies a pointer to UL_FULL_TRACKER.
|
|
|
|
Status - Status of the send.
|
|
|
|
Information - Bytes transferred.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpCompleteSendCacheEntry(
|
|
IN PVOID pCompletionContext,
|
|
IN NTSTATUS Status,
|
|
IN ULONG_PTR Information
|
|
)
|
|
{
|
|
PUL_FULL_TRACKER pTracker;
|
|
|
|
pTracker = (PUL_FULL_TRACKER) pCompletionContext;
|
|
|
|
pTracker->IoStatus.Status = Status;
|
|
pTracker->IoStatus.Information = Information;
|
|
|
|
UlTrace(URI_CACHE, (
|
|
"UlpCompleteSendCacheEntry: "
|
|
"tracker=%p, status = %x, transferred %d bytes\n",
|
|
pTracker,
|
|
Status,
|
|
(LONG) Information
|
|
));
|
|
|
|
IF_DEBUG(LOGBYTES)
|
|
{
|
|
TIME_FIELDS RcvdTimeFields;
|
|
|
|
RtlTimeToTimeFields( &pTracker->pRequest->TimeStamp, &RcvdTimeFields );
|
|
|
|
UlTrace(LOGBYTES,
|
|
("Http!UlpCompleteSendCacheEntry : [Rcvd @ %02d:%02d:%02d] "
|
|
"Bytes %010I64u Status %08lx\n",
|
|
RcvdTimeFields.Hour,
|
|
RcvdTimeFields.Minute,
|
|
RcvdTimeFields.Second,
|
|
(ULONGLONG) pTracker->IoStatus.Information,
|
|
Status
|
|
));
|
|
}
|
|
|
|
UL_QUEUE_WORK_ITEM(
|
|
&pTracker->WorkItem,
|
|
UlpCompleteSendCacheEntryWorker
|
|
);
|
|
|
|
} // UlpCompleteSendCacheEntry
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Called when we finish sending cached data to the client. This routine
|
|
frees the UL_FULL_TRACKER, and calls the completion routine originally
|
|
passed to UlCacheAndSendResponse.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - Supplies a pointer to the work item queued. This should
|
|
point to the WORK_ITEM structure embedded in a UL_FULL_TRACKER.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpCompleteSendCacheEntryWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
PUL_FULL_TRACKER pTracker;
|
|
PUL_HTTP_CONNECTION pHttpConnection;
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
ULONG Flags;
|
|
PUL_URI_CACHE_ENTRY pUriCacheEntry;
|
|
NTSTATUS Status;
|
|
KIRQL OldIrql;
|
|
BOOLEAN ResumeParsing;
|
|
HTTP_VERB RequestVerb;
|
|
USHORT ResponseStatusCode;
|
|
BOOLEAN FromCache;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pTracker = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_FULL_TRACKER,
|
|
WorkItem
|
|
);
|
|
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlpCompleteSendCacheEntryWorker(pTracker %p)\n",
|
|
pTracker
|
|
));
|
|
|
|
//
|
|
// Pull context out of the tracker.
|
|
//
|
|
|
|
pHttpConnection = pTracker->pHttpConnection;
|
|
pRequest = pTracker->pRequest;
|
|
RequestVerb = pTracker->RequestVerb;
|
|
ResponseStatusCode = pTracker->ResponseStatusCode;
|
|
Flags = pTracker->Flags;
|
|
pUriCacheEntry = pTracker->pUriEntry;
|
|
|
|
ASSERT( UL_IS_VALID_HTTP_CONNECTION( pHttpConnection ) );
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pRequest ) );
|
|
ASSERT( IS_VALID_URI_CACHE_ENTRY( pUriCacheEntry ) );
|
|
|
|
Status = pTracker->IoStatus.Status;
|
|
|
|
//
|
|
// If the send failed, then initiate an *abortive* disconnect.
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlpCompleteSendCacheEntryWorker(pTracker %p) "
|
|
"Closing connection\n",
|
|
pTracker
|
|
));
|
|
|
|
UlCloseConnection(
|
|
pHttpConnection->pConnection,
|
|
TRUE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
//
|
|
// Stop MinBytesPerSecond timer and start Connection Idle timer.
|
|
//
|
|
|
|
UlLockTimeoutInfo(
|
|
&pHttpConnection->TimeoutInfo,
|
|
&OldIrql
|
|
);
|
|
|
|
UlResetConnectionTimer(
|
|
&pHttpConnection->TimeoutInfo,
|
|
TimerMinBytesPerSecond
|
|
);
|
|
|
|
UlSetConnectionTimer(
|
|
&pHttpConnection->TimeoutInfo,
|
|
TimerConnectionIdle
|
|
);
|
|
|
|
UlUnlockTimeoutInfo(
|
|
&pHttpConnection->TimeoutInfo,
|
|
OldIrql
|
|
);
|
|
|
|
UlEvaluateTimerState(
|
|
&pHttpConnection->TimeoutInfo
|
|
);
|
|
|
|
//
|
|
// Unmap the FixedHeaders and Content MDLs if necessary.
|
|
//
|
|
|
|
if (pTracker->pMdlFixedHeaders->MdlFlags & MDL_MAPPED_TO_SYSTEM_VA)
|
|
{
|
|
MmUnmapLockedPages(
|
|
pTracker->pMdlFixedHeaders->MappedSystemVa,
|
|
pTracker->pMdlFixedHeaders
|
|
);
|
|
}
|
|
|
|
if (pTracker->pMdlVariableHeaders->Next &&
|
|
(pTracker->pMdlContent->MdlFlags & MDL_MAPPED_TO_SYSTEM_VA))
|
|
{
|
|
ASSERT( pTracker->pMdlVariableHeaders->Next == pTracker->pMdlContent );
|
|
|
|
MmUnmapLockedPages(
|
|
pTracker->pMdlContent->MappedSystemVa,
|
|
pTracker->pMdlContent
|
|
);
|
|
}
|
|
|
|
//
|
|
// Do the logging before cleaning up the tracker.
|
|
//
|
|
|
|
if (pUriCacheEntry->LoggingEnabled)
|
|
{
|
|
if (pUriCacheEntry->BinaryLogged)
|
|
{
|
|
UlRawLogHttpCacheHit( pTracker );
|
|
}
|
|
else
|
|
{
|
|
UlLogHttpCacheHit( pTracker );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Unlink the request from process if we are done with all sends.
|
|
//
|
|
|
|
if (0 == (pTracker->Flags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA) &&
|
|
0 == pRequest->ContentLength &&
|
|
0 == pRequest->Chunked &&
|
|
pRequest->ConfigInfo.pAppPool)
|
|
{
|
|
ASSERT( pRequest->SentLast );
|
|
|
|
UlUnlinkRequestFromProcess(
|
|
pRequest->ConfigInfo.pAppPool,
|
|
pRequest
|
|
);
|
|
}
|
|
|
|
//
|
|
// Kick the parser back into action, if resume parsing is set in tracker.
|
|
//
|
|
|
|
ResumeParsing = (BOOLEAN)
|
|
(UlResumeParsingOnSendCompletion == pTracker->ResumeParsingType);
|
|
|
|
//
|
|
// Invoke completion routine.
|
|
//
|
|
|
|
if (pTracker->pCompletionRoutine != NULL)
|
|
{
|
|
(pTracker->pCompletionRoutine)(
|
|
pTracker->pCompletionContext,
|
|
Status,
|
|
pTracker->IoStatus.Information
|
|
);
|
|
}
|
|
|
|
//
|
|
// Clean up tracker.
|
|
//
|
|
|
|
FromCache = (BOOLEAN) (pTracker->pCompletionContext == NULL);
|
|
UlpFreeCacheTracker( pTracker );
|
|
|
|
//
|
|
// Deref the cache entry.
|
|
//
|
|
|
|
UlCheckinUriCacheEntry( pUriCacheEntry );
|
|
|
|
//
|
|
// Deref the internal request.
|
|
//
|
|
|
|
UL_DEREFERENCE_INTERNAL_REQUEST( pRequest );
|
|
|
|
if (ResumeParsing)
|
|
{
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlpCompleteSendCacheEntryWorker(pHttpConn = %p), "
|
|
"RequestVerb=%d, ResponseStatusCode=%hu\n",
|
|
pHttpConnection,
|
|
RequestVerb,
|
|
ResponseStatusCode
|
|
));
|
|
|
|
UlResumeParsing(
|
|
pHttpConnection,
|
|
FromCache,
|
|
(BOOLEAN) (Flags & HTTP_SEND_RESPONSE_FLAG_DISCONNECT)
|
|
);
|
|
}
|
|
|
|
//
|
|
// Deref the HTTP connection.
|
|
//
|
|
|
|
UL_DEREFERENCE_HTTP_CONNECTION( pHttpConnection );
|
|
|
|
} // UlpCompleteSendCacheEntryWorker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Allocates a non-paged UL_FULL_TRACKER used as context for sending
|
|
cached content to the client.
|
|
|
|
CODEWORK: this routine should probably do all tracker init.
|
|
|
|
Arguments:
|
|
|
|
SendIrpStackSize - Size of the stack for the send IRP.
|
|
|
|
Return Values:
|
|
|
|
Either a pointer to a UL_FULL_TRACKER, or NULL if it couldn't be made.
|
|
|
|
--***************************************************************************/
|
|
PUL_FULL_TRACKER
|
|
UlpAllocateCacheTracker(
|
|
IN CCHAR SendIrpStackSize
|
|
)
|
|
{
|
|
PUL_FULL_TRACKER pTracker;
|
|
USHORT SendIrpSize;
|
|
ULONG CacheTrackerSize;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT( SendIrpStackSize > DEFAULT_MAX_IRP_STACK_SIZE );
|
|
|
|
SendIrpSize = (USHORT) ALIGN_UP(IoSizeOfIrp(SendIrpStackSize), PVOID);
|
|
|
|
//
|
|
// No need to allocate space for the entire auxiliary buffer in this
|
|
// case since this is one-time deal only.
|
|
//
|
|
|
|
CacheTrackerSize = ALIGN_UP(sizeof(UL_FULL_TRACKER), PVOID) +
|
|
SendIrpSize +
|
|
g_UlMaxVariableHeaderSize +
|
|
g_UlFixedHeadersMdlLength +
|
|
g_UlVariableHeadersMdlLength +
|
|
g_UlContentMdlLength;
|
|
|
|
pTracker = (PUL_FULL_TRACKER) UL_ALLOCATE_POOL(
|
|
NonPagedPool,
|
|
CacheTrackerSize,
|
|
UL_FULL_TRACKER_POOL_TAG
|
|
);
|
|
|
|
if (pTracker)
|
|
{
|
|
pTracker->Signature = UL_FULL_TRACKER_POOL_TAG;
|
|
pTracker->FromLookaside = FALSE;
|
|
pTracker->FromRequest = FALSE;
|
|
pTracker->AuxilaryBufferLength = g_UlMaxVariableHeaderSize;
|
|
pTracker->RequestVerb = HttpVerbInvalid;
|
|
pTracker->ResponseStatusCode = 200; // OK
|
|
|
|
UlInitializeFullTrackerPool( pTracker, SendIrpStackSize );
|
|
}
|
|
|
|
UlTrace( URI_CACHE, (
|
|
"Http!UlpAllocateCacheTracker: tracker %p\n",
|
|
pTracker
|
|
));
|
|
|
|
return pTracker;
|
|
|
|
} // UlpAllocateCacheTracker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Frees a UL_FULL_TRACKER.
|
|
|
|
Arguments:
|
|
|
|
pTracker - Specifies the UL_FULL_TRACKER to free.
|
|
|
|
Return Values:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpFreeCacheTracker(
|
|
IN PUL_FULL_TRACKER pTracker
|
|
)
|
|
{
|
|
UlTrace(URI_CACHE, (
|
|
"Http!UlpFreeCacheTracker: tracker %p\n",
|
|
pTracker
|
|
));
|
|
|
|
ASSERT( IS_VALID_FULL_TRACKER( pTracker ) );
|
|
|
|
pTracker->pHttpConnection = NULL;
|
|
|
|
if (pTracker->FromRequest == FALSE)
|
|
{
|
|
if (pTracker->FromLookaside)
|
|
{
|
|
UlPplFreeFullTracker( pTracker );
|
|
}
|
|
else
|
|
{
|
|
UL_FREE_POOL_WITH_SIG( pTracker, UL_FULL_TRACKER_POOL_TAG );
|
|
}
|
|
}
|
|
|
|
} // UlpFreeCacheTracker
|
|
|