Source code of Windows XP (NT5)
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.
 
 
 
 
 
 

6239 lines
170 KiB

/*++
Copyright (c) 1998-2001 Microsoft Corporation
Module Name:
sendresponse.cxx
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
--*/
#include "precomp.h"
#include "iiscnfg.h"
#include "sendresponsep.h"
//
// Private globals.
//
#ifdef ALLOC_PRAGMA
#pragma alloc_text( PAGE, UlCaptureHttpResponse )
#pragma alloc_text( PAGE, UlPrepareHttpResponse )
#pragma alloc_text( PAGE, UlCleanupHttpResponse )
#pragma alloc_text( PAGE, UlpSendHttpResponseWorker )
#pragma alloc_text( PAGE, UlpSendCompleteWorker )
#pragma alloc_text( PAGE, UlpFreeMdlRuns )
#pragma alloc_text( PAGE, UlSendCachedResponse )
#pragma alloc_text( PAGE, UlCacheAndSendResponse )
#pragma alloc_text( PAGE, UlpBuildCacheEntry )
#pragma alloc_text( PAGE, UlpBuildCacheEntryWorker )
#pragma alloc_text( PAGE, UlpBuildBuildTrackerWorker )
#pragma alloc_text( PAGE, UlpCompleteCacheBuildWorker )
#pragma alloc_text( PAGE, UlpSendCacheEntry )
#pragma alloc_text( PAGE, UlpAllocateCacheTracker )
#pragma alloc_text( PAGE, UlpFreeCacheTracker )
#pragma alloc_text( PAGE, UlpAllocateLockedMdl )
#pragma alloc_text( PAGE, UlpInitializeAndLockMdl )
#endif // ALLOC_PRAGMA
#if 0
NOT PAGEABLE -- UlSendHttpResponse
NOT PAGEABLE -- UlReferenceHttpResponse
NOT PAGEABLE -- UlDereferenceHttpResponse
NOT PAGEABLE -- UlpDestroyCapturedResponse
NOT PAGEABLE -- UlpAllocateChunkTracker
NOT PAGEABLE -- UlpFreeChunkTracker
NOT PAGEABLE -- UlpCompleteSendRequest
NOT PAGEABLE -- UlpRestartMdlRead
NOT PAGEABLE -- UlpRestartMdlReadComplete
NOT PAGEABLE -- UlpRestartMdlSend
NOT PAGEABLE -- UlpIncrementChunkPointer
NOT PAGEABLE -- UlpRestartCacheMdlRead
NOT PAGEABLE -- UlpRestartCacheMdlFree
NOT PAGEABLE -- UlpIssueFileChunkIo
NOT PAGEABLE -- UlpCompleteCacheBuild
NOT PAGEABLE -- UlpCompleteSendCacheEntry
NOT PAGEABLE -- UlpCheckCacheControlHeaders
NOT PAGEABLE -- UlpCompleteSendRequestWorker
NOT PAGEABLE -- UlpCompleteSendCacheEntryWorker
NOT PAGEABLE -- UlpFreeLockedMdl
NOT PAGEABLE -- UlpIsAcceptHeaderOk
NOT PAGEABLE -- UlpGetTypeAndSubType
#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 ULONG Flags,
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];
BOOLEAN CompleteEarly;
pHttpConn = pRequest->pHttpConn;
ASSERT( UL_IS_VALID_HTTP_CONNECTION( pHttpConn ) );
//
// do a little tracing
//
TRACE_TIME(
pRequest->ConnectionId,
pRequest->RequestId,
TIME_ACTION_SEND_RESPONSE
);
//
// Setup locals so we know how to cleanup on exit.
//
pTracker = NULL;
CompleteEarly = FALSE;
//
// Tracker will keep a reference to the connection
//
UL_REFERENCE_HTTP_CONNECTION(pHttpConn);
//
// Should we close the connection?
//
Disconnect = FALSE;
if ((Flags & HTTP_SEND_RESPONSE_FLAG_DISCONNECT) == 0)
{
if ((Flags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA) != HTTP_SEND_RESPONSE_FLAG_MORE_DATA)
{
//
// No more data is coming, should we disconnect?
//
if ( UlCheckDisconnectInfo(pRequest) ) {
Disconnect = TRUE;
Flags |= HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
}
}
}
else
{
//
// Caller is forcing a disconnect.
//
Disconnect = TRUE;
}
//
// how big is the response? (keep track for early complete)
//
TotalResponseSize = pResponse->ResponseLength;
//
// figure out what space we need for variable headers
//
if ((pResponse->HeaderLength > 0) && // response (not entity body)
!pResponse->ContentLengthSpecified && // app didn't provide content length
!pResponse->ChunkedSpecified && // app didn't generate a chunked response
UlNeedToGenerateContentLength(
pRequest->Verb,
pResponse->StatusCode,
Flags
))
{
//
// Autogenerate a content-length header.
//
PCHAR pszEnd = UlStrPrintUlonglong(
(PCHAR) contentLength,
pResponse->ResponseLength - pResponse->HeaderLength,
'\0');
contentLengthStringLength = DIFF(pszEnd - (PCHAR) contentLength);
}
else
{
//
// Either we cannot or do not need to autogenerate a
// content-length header.
//
contentLength[0] = '\0';
contentLengthStringLength = 0;
}
ConnHeader = UlChooseConnectionHeader( pRequest->Version, Disconnect );
//
// Allocate and initialize a tracker for this request.
//
pTracker =
UlpAllocateChunkTracker(
UlTrackerTypeSend,
pHttpConn->pConnection->ConnectionObject.pDeviceObject->StackSize,
pResponse->MaxFileSystemStackSize,
pHttpConn,
Flags,
pRequest,
pResponse,
pCompletionRoutine,
pCompletionContext
);
if (pTracker == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
goto cleanup;
}
UlpInitMdlRuns( pTracker );
//
// generate var headers, and init the second chunk
//
if (pResponse->HeaderLength)
{
UlGenerateVariableHeaders(
ConnHeader,
contentLength,
contentLengthStringLength,
pTracker->pVariableHeader,
&VarHeaderGenerated,
&pResponse->CreationTime
);
ASSERT( VarHeaderGenerated <= g_UlMaxVariableHeaderSize );
pTracker->VariableHeaderLength = VarHeaderGenerated;
//
// increment total size
//
TotalResponseSize += VarHeaderGenerated;
//
// build an mdl for it
//
pResponse->pDataChunks[1].ChunkType = HttpDataChunkFromMemory;
pResponse->pDataChunks[1].FromMemory.BufferLength = VarHeaderGenerated;
pResponse->pDataChunks[1].FromMemory.pMdl =
UlAllocateMdl(
pTracker->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
);
}
//
// see if we're supposed to do an early completion call
//
if (pResponse->CompleteIrpEarly &&
(TotalResponseSize < MAX_BYTES_BUFFERED) &&
(pResponse->ChunkCount < MAX_MDL_RUNS))
{
//
// NULL out the tracker's completion routine so it
// won't try to complete the request later.
//
pTracker->pCompletionRoutine = NULL;
//
// Remember that we're supposed to complete early
// but don't do it until after we've initiated
// the actual TCP send.
//
CompleteEarly = TRUE;
}
IF_DEBUG( SEND_RESPONSE )
{
KdPrint((
"UlSendHttpResponse: tracker %p, response %p\n",
pTracker,
pResponse
));
}
//
// Start MinKBSec timer, since we now know TotalResponseSize
//
UlSetMinKBSecTimer(
&pHttpConn->TimeoutInfo,
TotalResponseSize
);
//
// RefCount the chunk tracker up for the UlpSendHttpResponseWorker.
// It will DeRef it when it's done with the chunk tracker itself.
//
UL_REFERENCE_CHUNK_TRACKER( pTracker );
//
// Let the worker do the dirty work, no reason to queue off
// it will queue the first time it needs to do blocking i/o
//
UlpSendHttpResponseWorker(&pTracker->WorkItem);
//
// If we're supposed to complete early, do it now.
//
if (CompleteEarly)
{
//
// do the completion
//
UlpCompleteSendIrpEarly(
pCompletionRoutine,
pCompletionContext,
STATUS_SUCCESS,
TotalResponseSize
);
}
//
// Release the original reference on the chunk tracker. So that it get
// freed up as soon as all the outstanding IO initiated by the
// UlpSendHttpResponseWorker is complete. This dereference matches with
// our allocation.
//
UL_DEREFERENCE_CHUNK_TRACKER( pTracker );
return STATUS_PENDING;
cleanup:
IF_DEBUG( SEND_RESPONSE )
{
KdPrint((
"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 );
}
//
// Tracker doesn't have a reference after all..
//
UL_DEREFERENCE_HTTP_CONNECTION(pHttpConn);
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.
pKernelResponse - Receives the captured response if successful.
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlCaptureHttpResponse(
IN PHTTP_RESPONSE pUserResponse OPTIONAL,
IN PUL_INTERNAL_REQUEST pRequest,
IN HTTP_VERSION Version,
IN HTTP_VERB Verb,
IN ULONG ChunkCount,
IN PHTTP_DATA_CHUNK pUserDataChunks,
IN UL_CAPTURE_FLAGS Flags,
IN BOOLEAN CaptureCache,
IN PHTTP_LOG_FIELDS_DATA pUserLogData OPTIONAL,
OUT PUL_INTERNAL_RESPONSE *ppKernelResponse
)
{
ULONG i;
NTSTATUS Status = STATUS_SUCCESS;
PUL_INTERNAL_RESPONSE pKeResponse = NULL;
ULONG AuxBufferLength;
ULONG CopiedBufferLength;
ULONG UncopiedBufferLength;
ULONGLONG FromFileLength;
BOOLEAN CompleteEarly;
PUCHAR pBuffer;
USHORT FileLength;
ULONG HeaderLength;
ULONG SpaceLength;
PUL_INTERNAL_DATA_CHUNK pKeDataChunks;
BOOLEAN FromKernelMode;
BOOLEAN IsFromLookaside;
BOOLEAN BufferedSend = TRUE;
ULONG KernelChunkCount;
PHTTP_KNOWN_HEADER pETagHeader = NULL;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pUserDataChunks != NULL || ChunkCount == 0);
ASSERT(ppKernelResponse != NULL);
__try
{
FromKernelMode = ((Flags & UlCaptureKernelMode) == UlCaptureKernelMode);
//
// ProbeTestForRead every buffer we will access
//
if (!FromKernelMode) {
Status = UlpProbeHttpResponse(
pUserResponse,
ChunkCount,
pUserDataChunks,
Flags,
pUserLogData
);
if (!NT_SUCCESS(Status))
{
goto end;
}
}
//
// figure out how much memory we need
//
Status = UlComputeFixedHeaderSize(
Version,
pUserResponse,
&HeaderLength
);
if (!NT_SUCCESS(Status))
{
goto end;
}
UlpComputeChunkBufferSizes(
ChunkCount, // number of chunks
pUserDataChunks, // array of chunks
Flags, // capture flags
&AuxBufferLength,
&CopiedBufferLength,
&UncopiedBufferLength,
&FromFileLength
);
//
// check if we can still buffer sends
//
if ((Flags & UlCaptureCopyData) == 0 &&
(CopiedBufferLength + pRequest->pHttpConn->SendBufferedBytes)
> g_UlMaxSendBufferedBytes)
{
BufferedSend = FALSE;
}
//
// see if we can complete the IRP early
//
if (BufferedSend && (UncopiedBufferLength + FromFileLength) == 0)
{
//
// we've got all the data in the kernel, so
// we can just tell user mode it's done.
//
CompleteEarly = TRUE;
}
else
{
//
// we're using a handle or buffer from user space
// so they have to wait for us to finish for real.
//
CompleteEarly = FALSE;
}
UlTrace(SEND_RESPONSE, (
"Http!UlCaptureHttpResponse(pUserResponse = %p) "
"CompleteEarly = %s\n"
" ChunkCount = %d\n"
" Flags = 0x%x\n"
" AuxBufferLength = 0x%x\n"
" UncopiedBufferLength = 0x%x\n"
" FromFileLength = 0x%I64x\n",
pUserResponse,
CompleteEarly ? "TRUE" : "FALSE",
ChunkCount,
Flags,
AuxBufferLength,
UncopiedBufferLength,
FromFileLength
));
//
// 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))
+ AuxBufferLength;
//
// Add space for ETag, if it exists.
//
if (CaptureCache &&
pUserResponse &&
pUserResponse->Headers.pKnownHeaders[HttpHeaderEtag].RawValueLength)
{
pETagHeader = &pUserResponse->Headers.pKnownHeaders[HttpHeaderEtag];
SpaceLength += (pETagHeader->RawValueLength + sizeof(CHAR)); // Add space for NULL
UlTrace(SEND_RESPONSE, (
"ul!UlCaptureHttpResponse(pUserResponse = %p) \n"
" ETag: %s \n"
" Length: %d\n",
pUserResponse,
pETagHeader->pRawValue,
pETagHeader->RawValueLength
));
}
//
// allocate the internal response.
//
if (g_UlResponseBufferSize
< (ALIGN_UP(sizeof(UL_INTERNAL_RESPONSE), PVOID) + SpaceLength)
)
{
pKeResponse = UL_ALLOCATE_STRUCT_WITH_SPACE(
NonPagedPool,
UL_INTERNAL_RESPONSE,
SpaceLength,
UL_INTERNAL_RESPONSE_POOL_TAG
);
IsFromLookaside = FALSE;
}
else
{
pKeResponse = UlPplAllocateResponseBuffer();
IsFromLookaside = TRUE;
}
if (pKeResponse == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto end;
}
//
// Initialize the fixed fields in the response.
//
pKeResponse->IsFromLookaside = IsFromLookaside;
pKeResponse->Signature = UL_INTERNAL_RESPONSE_POOL_TAG;
pKeResponse->ReferenceCount = 1;
pKeResponse->ChunkCount = KernelChunkCount;
RtlZeroMemory(
pKeResponse->pDataChunks,
sizeof(UL_INTERNAL_DATA_CHUNK) * KernelChunkCount
);
pKeResponse->ContentLengthSpecified = FALSE;
pKeResponse->ChunkedSpecified = FALSE;
pKeResponse->ResponseLength = 0;
pKeResponse->MaxFileSystemStackSize = 0;
pKeResponse->CreationTime.QuadPart = 0;
pKeResponse->ETagLength = 0;
pKeResponse->pETag = NULL;
RtlZeroMemory(
&pKeResponse->ContentType,
sizeof(UL_CONTENT_TYPE)
);
//
// Remember how much we have buffered.
//
if (BufferedSend)
{
pKeResponse->SendBufferedBytes = CopiedBufferLength;
}
else
{
pKeResponse->SendBufferedBytes = 0;
}
//
// Remember if it's ok to do early IRP completion.
//
pKeResponse->CompleteIrpEarly = CompleteEarly;
//
// point to the header buffer space
//
pKeResponse->HeaderLength = HeaderLength;
pKeResponse->pHeaders =
(PUCHAR)(pKeResponse->pDataChunks + pKeResponse->ChunkCount);
//
// and the aux buffer space
//
pKeResponse->AuxBufferLength = AuxBufferLength;
pKeResponse->pAuxiliaryBuffer = (PVOID)(
pKeResponse->pHeaders +
ALIGN_UP(HeaderLength, sizeof(CHAR))
);
//
// and the ETag buffer space
//
if (pETagHeader)
{
pKeResponse->ETagLength = pETagHeader->RawValueLength + 1; // Add space for NULL
pKeResponse->pETag = ((PUCHAR)pKeResponse->pAuxiliaryBuffer) +
AuxBufferLength;
}
//
// Capture the logging data if it exists. Allocate an internal
// log data buffer. This buffer will be released later when we
// are done with logging the stuff, either before freeing the
// chunk trucker (in UlpCompleteSendRequestWorker) or before
// freeing the cache tracker (in UlpCompleteSendCacheEntryWorker)
// please see the definition of UlLogHttpHit.
//
ASSERT( pRequest != NULL || pUserLogData == NULL);
if (NULL != pUserLogData &&
1 == pRequest->SentLast &&
pRequest->ConfigInfo.pLoggingConfig)
{
pKeResponse->pLogData = &pRequest->LogData;
Status = UlAllocateLogDataBuffer(
pKeResponse->pLogData,
pRequest,
pRequest->ConfigInfo.pLoggingConfig
);
ASSERT(NT_SUCCESS(Status));
Status = UlCaptureLogFields(
pUserLogData,
Version,
pKeResponse->pLogData
);
if (!NT_SUCCESS(Status))
{
goto end;
}
}
else
{
//
// User didn't pass down a log buffer so no need to
// do logging for this response. Or it's possible that
// user passed down log buffer but logging wasn't enabled
//
pKeResponse->pLogData = NULL;
}
//
// User didn't pass down a log buffer so no need to
// do logging for this response. Or it's possible that
// user passed down log buffer but logging wasn't enabled
//
//
// Remember if a Content-Length header was specified.
//
if (pUserResponse != NULL)
{
pKeResponse->StatusCode = pUserResponse->StatusCode;
pKeResponse->Verb = Verb;
if (pUserResponse->Headers.pKnownHeaders[HttpHeaderContentLength].RawValueLength > 0)
{
pKeResponse->ContentLengthSpecified = TRUE;
}
//
// As long as we're here, also remember if "Chunked"
// Transfer-Encoding was specified.
//
if (pUserResponse->Headers.pKnownHeaders[HttpHeaderTransferEncoding].RawValueLength > 0 &&
(0 == _strnicmp(pUserResponse->Headers.pKnownHeaders[HttpHeaderTransferEncoding].pRawValue,
"chunked",
sizeof("chunked")-1)))
{
// NOTE: If a response has a chunked Transfer-Encoding,
// then it shouldn't have a Content-Length
ASSERT(!pKeResponse->ContentLengthSpecified);
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 (pETagHeader)
{
RtlCopyMemory(
pKeResponse->pETag, // Dest
pETagHeader->pRawValue, // Src
pKeResponse->ETagLength // Bytes
);
// Add NULL termination
//
pKeResponse->pETag[pETagHeader->RawValueLength] = '\0';
}
//
// Capture the ContentType and put it on the UL_INTERNAL_RESPONSE
//
if (pUserResponse->Headers.pKnownHeaders[HttpHeaderContentType].RawValueLength > 0)
{
UlpGetTypeAndSubType(
pUserResponse->Headers.pKnownHeaders[HttpHeaderContentType].pRawValue,
pUserResponse->Headers.pKnownHeaders[HttpHeaderContentType].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.SubType
));
}
//
// Capture the Last-Modified time (if it exists)
//
if (pUserResponse->Headers.pKnownHeaders[HttpHeaderLastModified].RawValueLength)
{
StringTimeToSystemTime(
(const PSTR) pUserResponse->Headers.pKnownHeaders[HttpHeaderLastModified].pRawValue,
&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.
//
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, // VirtualAddress
pKeDataChunks[i].FromMemory.BufferLength, // Length
FALSE, // SecondaryBuffer
FALSE, // ChargeQuota
NULL // Irp
);
if (pKeDataChunks[i].FromMemory.pMdl == NULL)
{
ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
break;
}
MmBuildMdlForNonPagedPool(pKeDataChunks[i].FromMemory.pMdl);
}
else
{
//
// build an mdl describing the user's buffer
//
pKeDataChunks[i].FromMemory.BufferLength =
pUserDataChunks[i].FromMemory.BufferLength;
pKeDataChunks[i].FromMemory.pMdl =
UlAllocateMdl(
pUserDataChunks[i].FromMemory.pBuffer, // VirtualAddress
pUserDataChunks[i].FromMemory.BufferLength, // Length
FALSE, // SecondaryBuffer
FALSE, // ChargeQuota
NULL // Irp
);
if (pKeDataChunks[i].FromMemory.pMdl == NULL)
{
ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
break;
}
//
// lock it down
//
MmProbeAndLockPages(
pKeDataChunks[i].FromMemory.pMdl, // MDL
UserMode, // AccessMode
IoReadAccess // Operation
);
MmMapLockedPagesSpecifyCache(
pKeDataChunks[i].FromMemory.pMdl, // MDL
KernelMode, // AccessMode
MmCached, // CacheType
NULL, // BaseAddress
FALSE, // BugCheckOnFailure
LowPagePriority // Priority
);
}
break;
case HttpDataChunkFromFileName:
ASSERT(pKeResponse->AuxBufferLength > 0);
//
// It's a filename. buffer's already been probed
//
FileLength = pUserDataChunks[i].FromFileName.FileNameLength;
pKeDataChunks[i].FromFile.FileName.Buffer = (PWSTR)pBuffer;
pKeDataChunks[i].FromFile.FileName.Length = FileLength;
pKeDataChunks[i].FromFile.FileName.MaximumLength =
FileLength + sizeof(WCHAR);
pKeDataChunks[i].FromFile.ByteRange =
pUserDataChunks[i].FromFileName.ByteRange;
pKeDataChunks[i].FromFile.pFileCacheEntry = NULL;
//
// have to inline convert this fully qualified win32 filename
// into an nt filename.
//
// CODEWORK: need to handle UNC's.
//
RtlCopyMemory(
pBuffer,
L"\\??\\",
sizeof(L"\\??\\") - sizeof(WCHAR)
);
pBuffer += sizeof(L"\\??\\") - sizeof(WCHAR);
//
// copy the win32 filename (including the terminator)
//
RtlCopyMemory(
pBuffer,
pUserDataChunks[i].FromFileName.pFileName,
FileLength + sizeof(WCHAR)
);
pBuffer += FileLength + sizeof(WCHAR);
//
// adjust the string sizes for the new prefix
//
pKeDataChunks[i].FromFile.FileName.Length +=
sizeof(L"\\??\\") - sizeof(WCHAR);
pKeDataChunks[i].FromFile.FileName.MaximumLength +=
sizeof(L"\\??\\") - sizeof(WCHAR);
break;
case HttpDataChunkFromFileHandle:
//
// From handle.
//
pKeDataChunks[i].FromFile.ByteRange =
pUserDataChunks[i].FromFileHandle.ByteRange;
pKeDataChunks[i].FromFile.FileHandle =
pUserDataChunks[i].FromFileHandle.FileHandle;
break;
default :
ExRaiseStatus( STATUS_INVALID_PARAMETER );
break;
} // switch (pUserDataChunks[i].DataChunkType)
} // for (i = 0 ; i < ChunkCount ; i++)
}
__except( UL_EXCEPTION_FILTER() )
{
Status = UL_CONVERT_EXCEPTION_CODE(GetExceptionCode());
}
if (NT_SUCCESS(Status) == FALSE)
goto end;
//
// 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
));
end:
if (NT_SUCCESS(Status) == FALSE)
{
if (pKeResponse != NULL)
{
UlpDestroyCapturedResponse( pKeResponse );
pKeResponse = NULL;
}
}
//
// Return the captured response.
//
*ppKernelResponse = pKeResponse;
RETURN(Status);
} // UlCaptureHttpResponse
/***************************************************************************++
Routine Description:
Probes all the buffers passed to use in a user mode http response.
Arguments:
pUserResponse - the response to probe
ChunkCount - the number of data chunks
pDataChunks - the array of data chunks
Flags - Capture flags
--***************************************************************************/
NTSTATUS
UlpProbeHttpResponse(
IN PHTTP_RESPONSE pUserResponse,
IN ULONG ChunkCount,
IN PHTTP_DATA_CHUNK pUserDataChunks,
IN ULONG Flags,
IN PHTTP_LOG_FIELDS_DATA pUserLogData
)
{
NTSTATUS Status;
ULONG i;
PHTTP_UNKNOWN_HEADER pUnknownHeaders;
Status = STATUS_SUCCESS;
__try
{
//
// Validate the response.
//
if (pUserResponse != NULL)
{
if ((pUserResponse->Flags & ~HTTP_RESPONSE_FLAG_VALID) ||
(pUserResponse->StatusCode > 999))
{
ExRaiseStatus( STATUS_INVALID_PARAMETER );
}
}
//
// Probe the log data if it exists
//
if (pUserLogData)
{
Status = UlProbeLogData( pUserLogData );
if (!NT_SUCCESS(Status))
{
ExRaiseStatus( Status );
}
}
//
// first count the headers part
//
if (pUserResponse != NULL)
{
// check the response structure
ProbeTestForRead(
pUserResponse,
sizeof(HTTP_RESPONSE),
sizeof(USHORT)
);
// check the reason string
ProbeTestForRead(
pUserResponse->pReason,
pUserResponse->ReasonLength,
sizeof(CHAR)
);
//
// Loop through the known headers.
//
for (i = 0; i < HttpHeaderResponseMaximum; ++i)
{
USHORT RawValueLength
= pUserResponse->Headers.pKnownHeaders[i].RawValueLength;
if (RawValueLength > 0)
{
ProbeTestForRead(
pUserResponse->Headers.pKnownHeaders[i].pRawValue,
RawValueLength,
sizeof(CHAR)
);
}
}
//
// And the unknown headers (this might throw an exception).
//
pUnknownHeaders = pUserResponse->Headers.pUnknownHeaders;
if (pUnknownHeaders != NULL)
{
ProbeTestForRead(
pUnknownHeaders,
sizeof(HTTP_UNKNOWN_HEADER)
* pUserResponse->Headers.UnknownHeaderCount,
sizeof(ULONG)
);
for (i = 0 ; i < pUserResponse->Headers.UnknownHeaderCount; ++i)
{
USHORT Length;
if (pUnknownHeaders[i].NameLength > 0)
{
ProbeTestForRead(
pUnknownHeaders[i].pName,
pUnknownHeaders[i].NameLength,
sizeof(CHAR)
);
ProbeTestForRead(
pUnknownHeaders[i].pRawValue,
pUnknownHeaders[i].RawValueLength,
sizeof(CHAR)
);
}
}
}
}
//
// and now the body part
//
if (pUserDataChunks != NULL)
{
ProbeTestForRead(
pUserDataChunks,
sizeof(HTTP_DATA_CHUNK) * ChunkCount,
sizeof(PVOID)
);
for (i = 0 ; i < ChunkCount ; i++)
{
switch (pUserDataChunks[i].DataChunkType)
{
case HttpDataChunkFromMemory:
//
// From-memory chunk.
//
if (pUserDataChunks[i].FromMemory.BufferLength == 0 ||
pUserDataChunks[i].FromMemory.pBuffer == NULL)
{
ExRaiseStatus( STATUS_INVALID_PARAMETER );
}
if ((Flags & UlCaptureCopyData) ||
(pUserDataChunks[i].FromMemory.BufferLength <= g_UlMaxCopyThreshold))
{
ProbeTestForRead(
pUserDataChunks[i].FromMemory.pBuffer,
pUserDataChunks[i].FromMemory.BufferLength,
sizeof(CHAR)
);
}
break;
case HttpDataChunkFromFileName:
//
// From-file chunk. probe the filename buffer.
//
//
// better be a filename there
//
if (pUserDataChunks[i].FromFileName.FileNameLength == 0 ||
pUserDataChunks[i].FromFileName.pFileName == NULL)
{
ExRaiseStatus( STATUS_INVALID_PARAMETER );
}
ProbeTestForRead(
pUserDataChunks[i].FromFileName.pFileName,
pUserDataChunks[i].FromFileName.FileNameLength + sizeof(WCHAR),
sizeof(WCHAR)
);
break;
case HttpDataChunkFromFileHandle:
//
// From handle chunk. the handle will be validated later
// by the object manager.
//
break;
default :
ExRaiseStatus( STATUS_INVALID_PARAMETER );
break;
} // switch (pUserDataChunks[i].DataChunkType)
} // for (i = 0 ; i < ChunkCount ; i++)
} // if (pUserDataChunks != NULL)
}
__except( UL_EXCEPTION_FILTER() )
{
Status = UL_CONVERT_EXCEPTION_CODE(GetExceptionCode());
}
return Status;
}
/***************************************************************************++
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 - number of chunks in the array
pDataChunks - the array of data chunks
Flags - capture flags
--***************************************************************************/
VOID
UlpComputeChunkBufferSizes(
IN ULONG ChunkCount,
IN PHTTP_DATA_CHUNK pDataChunks,
IN ULONG Flags,
OUT PULONG pAuxBufferSize,
OUT PULONG pCopiedMemorySize,
OUT PULONG pUncopiedMemorySize,
OUT PULONGLONG pFromFileSize
)
{
ULONG AuxLength = 0;
ULONG CopiedLength = 0;
ULONG UncopiedLength = 0;
ULONGLONG FromFileLength = 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 HttpDataChunkFromFileName:
//
// add up the string length
//
AuxLength += sizeof(L"\\??\\");
AuxLength += pDataChunks[i].FromFileName.FileNameLength;
FromFileLength += pDataChunks[i].FromFileName.ByteRange.Length.QuadPart;
break;
case HttpDataChunkFromFileHandle:
FromFileLength += pDataChunks[i].FromFileHandle.ByteRange.Length.QuadPart;
break;
default:
// we should have caught this in the probe
ASSERT(!"Invalid chunk type");
break;
} // switch
}
*pAuxBufferSize = AuxLength;
*pCopiedMemorySize = CopiedLength;
*pUncopiedMemorySize = UncopiedLength;
*pFromFileSize = FromFileLength;
}
/***************************************************************************++
Routine Description:
Prepares the specified response for sending. This preparation
consists mostly of opening any files referenced by the response.
Arguments:
pResponse - Supplies the response to prepare.
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlPrepareHttpResponse(
IN HTTP_VERSION Version,
IN PHTTP_RESPONSE pUserResponse,
IN PUL_INTERNAL_RESPONSE pResponse
)
{
ULONG i;
NTSTATUS Status = STATUS_SUCCESS;
PUL_INTERNAL_DATA_CHUNK internalChunk;
PUL_FILE_CACHE_ENTRY pFileCacheEntry;
ULONGLONG offset;
ULONGLONG length;
ULONGLONG fileLength;
CCHAR maxStackSize;
//
// 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
//
// check that the caller passed in headers to send
//
if (pResponse->HeaderLength > 0)
{
ULONG HeaderLength;
ASSERT(pUserResponse != NULL);
//
// generate the response
//
Status = UlGenerateFixedHeaders(
Version,
pUserResponse,
pResponse->HeaderLength,
pResponse->pHeaders,
&HeaderLength
);
if (!NT_SUCCESS(Status))
goto end;
//
// it is possible that no headers got generated (0.9 request) .
//
if (HeaderLength > 0)
{
//
// build an 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.
//
internalChunk = pResponse->pDataChunks - 1;
maxStackSize = 0;
for (i = 0 ; i < pResponse->ChunkCount ; i++)
{
internalChunk++;
switch (internalChunk->ChunkType)
{
case HttpDataChunkFromFileHandle:
case HttpDataChunkFromFileName:
if (IS_FROM_FILE_HANDLE(internalChunk))
{
IF_DEBUG( SEND_RESPONSE )
{
KdPrint((
"UlPrepareHttpResponse: opening handle %p\n",
&internalChunk->FromFile.FileHandle
));
}
//
// Found one. Try to open it.
//
Status = UlCreateFileEntry(
NULL,
internalChunk->FromFile.FileHandle,
UserMode,
&pFileCacheEntry
);
if (NT_SUCCESS(Status) == FALSE)
goto end;
}
else
{
ASSERT(IS_FROM_FILE_NAME(internalChunk));
IF_DEBUG( SEND_RESPONSE )
{
KdPrint((
"UlPrepareHttpResponse: opening %wZ\n",
&internalChunk->FromFile.FileName
));
}
//
// Found one. Try to open it.
//
Status = UlCreateFileEntry(
&internalChunk->FromFile.FileName,
NULL,
UserMode,
&pFileCacheEntry
);
} // if (IS_FROM_FILE_HANDLE(internalChunk))
if (NT_SUCCESS(Status) == FALSE)
goto end;
internalChunk->FromFile.pFileCacheEntry = pFileCacheEntry;
if (pFileCacheEntry->pDeviceObject->StackSize > maxStackSize)
{
maxStackSize = pFileCacheEntry->pDeviceObject->StackSize;
}
//
// Validate & sanitize the specified byte range.
//
fileLength = pFileCacheEntry->FileInfo.EndOfFile.QuadPart;
offset = internalChunk->FromFile.ByteRange.StartingOffset.QuadPart;
length = internalChunk->FromFile.ByteRange.Length.QuadPart;
if (offset >= fileLength)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// The offset looks good. If the user is asking for
// "to eof", then calculate the number of bytes in the
// file beyond the specified offset.
//
if (length == HTTP_BYTE_RANGE_TO_EOF)
{
length = fileLength - offset;
}
if ((offset + length) > fileLength)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// The specified length is good. Sanitize the byte range.
//
internalChunk->FromFile.ByteRange.StartingOffset.QuadPart = offset;
internalChunk->FromFile.ByteRange.Length.QuadPart = length;
pResponse->ResponseLength += length;
break;
case HttpDataChunkFromMemory:
pResponse->ResponseLength += internalChunk->FromMemory.BufferLength;
break;
default:
ASSERT(FALSE);
Status = STATUS_INVALID_PARAMETER;
goto end;
} // switch (internalChunk->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.
--***************************************************************************/
VOID
UlCleanupHttpResponse(
IN PUL_INTERNAL_RESPONSE pResponse
)
{
ULONG i;
NTSTATUS status;
PUL_INTERNAL_DATA_CHUNK internalChunk;
//
// Sanity check.
//
PAGED_CODE();
IF_DEBUG( SEND_RESPONSE )
{
KdPrint((
"UlCleanupHttpResponse: response %p\n",
pResponse
));
}
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE( pResponse ) );
//
// paulmcd(9/27/99) removed. can't do this anymore. since handle chunks
// don't use any auxbuffer, it's possible that we have some chunks to
// look through
//
#if 0
//
// If this response does not have an attached file name buffer,
// then we know there are no embedded "from file" chunks and
// we can just bail quickly.
//
if (pResponse->AuxBufferLength == 0)
{
return;
}
#endif
//
// Scan the chunks looking for "from file" chunks.
//
internalChunk = pResponse->pDataChunks - 1;
for (i = 0 ; i < pResponse->ChunkCount ; i++)
{
internalChunk++;
if (IS_FROM_FILE(internalChunk))
{
if (internalChunk->FromFile.pFileCacheEntry == NULL)
{
break;
}
DereferenceCachedFile(
internalChunk->FromFile.pFileCacheEntry
);
internalChunk->FromFile.pFileCacheEntry = NULL;
}
else
{
ASSERT( IS_FROM_MEMORY(internalChunk) );
}
}
} // UlCleanupHttpResponse
/***************************************************************************++
Routine Description:
References the specified response.
Arguments:
pResponse - Supplies the response to reference.
--***************************************************************************/
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
);
IF_DEBUG( SEND_RESPONSE )
{
KdPrint((
"UlReferenceHttpResponse: response %p refcount %ld\n",
pResponse,
refCount
));
}
} // UlReferenceHttpResponse
/***************************************************************************++
Routine Description:
Dereferences the specified response.
Arguments:
pResponse - Supplies the response to dereference.
--***************************************************************************/
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
);
IF_DEBUG( SEND_RESPONSE )
{
KdPrint((
"UlDereferenceHttpResponse: response %p refcount %ld\n",
pResponse,
refCount
));
}
if (refCount == 0)
{
UlpDestroyCapturedResponse( pResponse );
}
} // UlDereferenceHttpResponse
//
// 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.
--***************************************************************************/
VOID
UlpDestroyCapturedResponse(
IN PUL_INTERNAL_RESPONSE pResponse
)
{
ULONG i;
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE(pResponse) );
IF_DEBUG( SEND_RESPONSE )
{
KdPrint((
"UlpDestroyCapturedResponse: response %p\n",
pResponse
));
}
//
// Scan the chunks.
//
for (i = 0; i < pResponse->ChunkCount ; ++i)
{
if (IS_FROM_MEMORY(&(pResponse->pDataChunks[i])))
{
//
// It's from memory. If necessary, unlock the pages, then
// free the MDL.
//
if (pResponse->pDataChunks[i].FromMemory.pMdl != NULL)
{
if (IS_MDL_LOCKED(pResponse->pDataChunks[i].FromMemory.pMdl))
{
MmUnlockPages( pResponse->pDataChunks[i].FromMemory.pMdl );
}
UlFreeMdl( pResponse->pDataChunks[i].FromMemory.pMdl );
pResponse->pDataChunks[i].FromMemory.pMdl = NULL;
}
}
else
{
//
// It's a filename. If there is an associated file cache
// entry, then dereference it.
//
ASSERT( IS_FROM_FILE(&(pResponse->pDataChunks[i])) );
if (pResponse->pDataChunks[i].FromFile.pFileCacheEntry != NULL)
{
DereferenceCachedFile(
pResponse->pDataChunks[i].FromFile.pFileCacheEntry
);
pResponse->pDataChunks[i].FromFile.pFileCacheEntry = 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 );
}
pResponse->Signature = MAKE_FREE_SIGNATURE(UL_INTERNAL_RESPONSE_POOL_TAG);
if (pResponse->IsFromLookaside)
{
UlPplFreeResponseBuffer(pResponse);
}
else
{
UL_FREE_POOL_WITH_SIG( pResponse, UL_INTERNAL_RESPONSE_POOL_TAG );
}
} // UlpDestroyCapturedResponse
/***************************************************************************++
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.
--***************************************************************************/
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;
PMDL pMdlTail;
PIRP pIrp;
PIO_STACK_LOCATION pIrpSp;
//
// Sanity check.
//
PAGED_CODE();
pTracker = CONTAINING_RECORD(
pWorkItem,
UL_CHUNK_TRACKER,
WorkItem
);
IF_DEBUG( SEND_RESPONSE )
{
KdPrint((
"UlpSendHttpResponseWorker: tracker %p\n",
pTracker
));
}
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
status = STATUS_SUCCESS;
while( TRUE )
{
//
// Capture the current chunk pointer, then check for end of
// response.
//
pCurrentChunk = pTracker->pCurrentChunk;
if (IS_REQUEST_COMPLETE(pTracker))
{
ASSERT( status == STATUS_SUCCESS );
break;
}
runCount = pTracker->SendInfo.MdlRunCount;
//
// 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( pTracker );
//
// ignore empty buffers
//
if (pCurrentChunk->FromMemory.BufferLength == 0)
{
continue;
}
//
// Clone the incoming MDL.
//
ASSERT( pCurrentChunk->FromMemory.pMdl->Next == NULL );
pNewMdl = UlCloneMdl( pCurrentChunk->FromMemory.pMdl );
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.
//
if (runCount == 0 ||
IS_FILE_BUFFER_IN_USE(&(pTracker->SendInfo.MdlRuns[runCount-1].FileBuffer)))
{
//
// Create a new run.
//
pTracker->SendInfo.MdlRuns[runCount].pMdlTail = pNewMdl;
pTracker->SendInfo.MdlRunCount++;
pFileBuffer = &(pTracker->SendInfo.MdlRuns[runCount].FileBuffer);
INITIALIZE_FILE_BUFFER(pFileBuffer);
//
// If we've not exhausted our static MDL run array,
// then we'll need to initiate a flush.
//
if (pTracker->SendInfo.MdlRunCount == MAX_MDL_RUNS)
{
ASSERT( status == STATUS_SUCCESS );
break;
}
}
else
{
//
// Append to the last run in the list.
//
pTracker->SendInfo.MdlRuns[runCount-1].pMdlTail->Next = pNewMdl;
pTracker->SendInfo.MdlRuns[runCount-1].pMdlTail = pNewMdl;
}
//
// If we've now exceeded the maximum number of bytes we
// want to buffer, then initiate a flush.
//
if (pTracker->SendInfo.BytesBuffered >= MAX_BYTES_BUFFERED)
{
ASSERT( status == STATUS_SUCCESS );
break;
}
}
else
{
//
// It's a filesystem MDL.
//
ASSERT( IS_FROM_FILE(pCurrentChunk) );
pFileCacheEntry = pCurrentChunk->FromFile.pFileCacheEntry;
ASSERT( IS_VALID_FILE_CACHE_ENTRY( pFileCacheEntry ) );
pFileBuffer = &(pTracker->SendInfo.MdlRuns[runCount].FileBuffer);
INITIALIZE_FILE_BUFFER(pFileBuffer);
//
// Initiate file read
//
if (pTracker->FileBytesRemaining.QuadPart > MAX_BYTES_PER_READ)
{
//
// Don't read too much at once.
//
bytesToRead = MAX_BYTES_PER_READ;
}
else if (pTracker->FileBytesRemaining.QuadPart == 0)
{
//
// Don't try to read zero bytes.
//
UlpIncrementChunkPointer( pTracker );
continue;
}
else
{
//
// Looks like a reasonable number of bytes. Go for it.
//
bytesToRead = (ULONG)pTracker->FileBytesRemaining.QuadPart;
}
//
// Initialize the UL_FILE_BUFFER.
//
pFileBuffer->pFileCacheEntry = pFileCacheEntry;
pFileBuffer->FileOffset = pTracker->FileOffset;
pFileBuffer->Length = bytesToRead;
pFileBuffer->pFileCacheEntry =
pCurrentChunk->FromFile.pFileCacheEntry;
pFileBuffer->pCompletionRoutine = UlpRestartMdlRead;
pFileBuffer->pContext = pTracker;
//
// BumpUp 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 got called
// back.
//
UL_REFERENCE_CHUNK_TRACKER( pTracker );
//
// issue the I/O
//
status = UlReadFileEntry(
pFileBuffer,
pTracker->pReadIrp
);
//
// 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 (pTracker->SendInfo.BytesBuffered > 0)
{
BOOLEAN initiateDisconnect = FALSE;
//
// Flush the send. Since this the last (only?) send to be
// issued for this response, we can ask UlSendData() to
// initiate a disconnect on our behalf if appropriate.
//
if (IS_REQUEST_COMPLETE(pTracker) &&
IS_DISCONNECT_TIME(pTracker))
{
initiateDisconnect = TRUE;
}
//
// Increment the RefCount on Tracker for Send I/O.
// UlpSendCompleteWorker will release it later.
//
UL_REFERENCE_CHUNK_TRACKER( pTracker );
//
// Adjust SendBufferedBytes.
//
InterlockedExchangeAdd(
&pTracker->pHttpConnection->SendBufferedBytes,
pTracker->pResponse->SendBufferedBytes
);
status = UlSendData(
pTracker->pConnection,
pTracker->SendInfo.pMdlHead,
pTracker->SendInfo.BytesBuffered,
&UlpRestartMdlSend,
pTracker,
pTracker->pSendIrp,
&pTracker->IrpContext,
initiateDisconnect
);
}
else
if (IS_DISCONNECT_TIME(pTracker))
{
PUL_CONNECTION pConnection;
//
// Nothing to send, but we need to issue a disconnect.
//
pConnection = pTracker->pConnection;
pTracker->pConnection = NULL;
//
// Increment up until connection close is complete
//
UL_REFERENCE_CHUNK_TRACKER( pTracker );
status = UlCloseConnection(
pConnection,
FALSE,
&UlpCloseConnectionComplete,
pTracker
);
ASSERT( status == STATUS_PENDING );
}
}
//
// did everything complete?
//
if (status != STATUS_PENDING)
{
//
// Nope, something went wrong !
//
UlpCompleteSendRequest( pTracker, status );
}
//
// 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().
--***************************************************************************/
VOID
UlpCloseConnectionComplete(
IN PVOID pCompletionContext,
IN NTSTATUS Status,
IN ULONG_PTR Information
)
{
PUL_CHUNK_TRACKER pTracker;
//
// Snag the context.
//
pTracker = (PUL_CHUNK_TRACKER)pCompletionContext;
IF_DEBUG( SEND_RESPONSE )
{
KdPrint((
"UlpCloseConnectionComplete: tracker %p\n",
pTracker
));
}
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
ASSERT( pTracker->pConnection == NULL );
UlpCompleteSendRequest( pTracker, Status );
UL_DEREFERENCE_CHUNK_TRACKER( pTracker );
} // 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 PUL_HTTP_CONNECTION pHttpConnection,
IN ULONG Flags,
IN PUL_INTERNAL_REQUEST pRequest,
IN PUL_INTERNAL_RESPONSE pResponse,
IN PUL_COMPLETION_ROUTINE pCompletionRoutine,
IN PVOID pCompletionContext
)
{
PUL_CHUNK_TRACKER pTracker;
CCHAR MaxIrpStackSize;
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)
{
ULONG ChunkTrackerSize;
USHORT ReadIrpSize;
USHORT SendIrpSize;
ReadIrpSize = (USHORT)ALIGN_UP(IoSizeOfIrp(ReadIrpStackSize), PVOID);
SendIrpSize = (USHORT)ALIGN_UP(IoSizeOfIrp(SendIrpStackSize), PVOID);
ChunkTrackerSize = ALIGN_UP(sizeof(UL_CHUNK_TRACKER), PVOID) +
ReadIrpSize +
SendIrpSize +
g_UlMaxVariableHeaderSize;
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->IsFromLookaside = FALSE;
//
// Set up the IRP.
//
pTracker->pReadIrp =
(PIRP)((PCHAR)pTracker + ALIGN_UP(sizeof(UL_CHUNK_TRACKER), PVOID));
IoInitializeIrp(
pTracker->pReadIrp,
ReadIrpSize,
ReadIrpStackSize
);
pTracker->pSendIrp =
(PIRP)((PCHAR)pTracker->pReadIrp + ReadIrpSize);
IoInitializeIrp(
pTracker->pSendIrp,
SendIrpSize,
SendIrpStackSize
);
//
// Set up the variable header pointer.
//
pTracker->pVariableHeader =
(PUCHAR)((PCHAR)pTracker->pSendIrp + SendIrpSize);
}
}
else
{
pTracker = UlPplAllocateChunkTracker();
if (pTracker)
{
ASSERT(pTracker->Signature == MAKE_FREE_TAG(UL_CHUNK_TRACKER_POOL_TAG));
pTracker->Signature = UL_CHUNK_TRACKER_POOL_TAG;
}
}
if (pTracker != NULL)
{
pTracker->Type = TrackerType;
//
// 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;
//
// RefCounting for the pHttpConnection will be handled by our caller
// "UlSendHttpresponse". Not to worry about it.
//
pTracker->pHttpConnection = pHttpConnection;
pTracker->pConnection = pHttpConnection->pConnection;
pTracker->Flags = Flags;
//
// Completion info.
//
pTracker->pCompletionRoutine = pCompletionRoutine;
pTracker->pCompletionContext = pCompletionContext;
//
// Response and request info.
//
UL_REFERENCE_INTERNAL_RESPONSE( pResponse );
pTracker->pResponse = pResponse;
UL_REFERENCE_INTERNAL_REQUEST( pRequest );
pTracker->pRequest = pRequest;
//
// 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.
//
pTracker->pCurrentChunk = pResponse->pDataChunks - 1;
//
// Do this prior to calling UlpIncrementChunkPointer because it
// expects pLastChunk to be initialized.
//
pTracker->pLastChunk = (pTracker->pCurrentChunk + 1) + pResponse->ChunkCount;
//
// Zero the remaining fields.
//
RtlZeroMemory(
(PUCHAR)pTracker + FIELD_OFFSET(UL_CHUNK_TRACKER, WorkItem),
sizeof(*pTracker) - FIELD_OFFSET(UL_CHUNK_TRACKER, WorkItem)
);
UlpIncrementChunkPointer( 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 send tracker allocated with UlpAllocateChunkTracker().
Arguments:
pTracker - Supplies the send tracker to free.
--***************************************************************************/
VOID
UlpFreeChunkTracker(
IN PUL_CHUNK_TRACKER pTracker
)
{
ASSERT( pTracker );
ASSERT( IS_VALID_CHUNK_TRACKER(pTracker) );
ASSERT( pTracker->Type == UlTrackerTypeSend ||
pTracker->Type == UlTrackerTypeBuildUriEntry
);
if (pTracker->Type == UlTrackerTypeSend) {
UlTrace(SEND_RESPONSE, (
"Http!UlpFreeChunkTracker: tracker %p (send)\n",
pTracker
));
} else {
UlTrace(URI_CACHE, (
"Http!UlpFreeChunkTracker: tracker %p (build uri)\n",
pTracker
));
}
//
// Release our ref to the response and request.
//
UL_DEREFERENCE_INTERNAL_RESPONSE( pTracker->pResponse );
UL_DEREFERENCE_INTERNAL_REQUEST( pTracker->pRequest );
pTracker->Signature = MAKE_FREE_TAG(UL_CHUNK_TRACKER_POOL_TAG);
if (pTracker->IsFromLookaside)
{
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.
Used by Send & Read IRPs
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.
--***************************************************************************/
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.
--***************************************************************************/
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 FreeUp the ChunkTracker
//
UlpFreeChunkTracker(pTracker);
}
} // UlDereferenceChunkTracker
/***************************************************************************++
Routine Description:
Completes a "send response" represented by a send tracker.
Arguments:
pTracker - Supplies the tracker to complete.
Status - Supplies the completion status.
--***************************************************************************/
VOID
UlpCompleteSendRequest(
IN PUL_CHUNK_TRACKER pTracker,
IN NTSTATUS Status
)
{
PUL_COMPLETION_ROUTINE pCompletionRoutine;
PVOID pCompletionContext;
//
// Although the chunk tracker will be around until all the outstanding
// Read/Send IRPs are complete. We should only complete the send request
// once.
//
if (FALSE != InterlockedExchange(&pTracker->Terminated, TRUE))
return;
IF_DEBUG( SEND_RESPONSE )
{
KdPrint((
"UlpCompleteSendRequest: tracker %p, status %08lx\n",
pTracker,
Status
));
}
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
pTracker->IoStatus.Status = Status;
UL_REFERENCE_CHUNK_TRACKER( pTracker );
UL_CALL_PASSIVE(
&pTracker->WorkItem,
&UlpCompleteSendRequestWorker
);
}
/***************************************************************************++
Routine Description:
Closes the connection if neccessary, cleans up trackers, and completes
the request.
Arguments:
pWorkItem - embedded in our UL_CHUNK_TRACKER
--***************************************************************************/
VOID
UlpCompleteSendRequestWorker(
PUL_WORK_ITEM pWorkItem
)
{
PUL_CHUNK_TRACKER pTracker;
PUL_COMPLETION_ROUTINE pCompletionRoutine;
PVOID pCompletionContext;
NTSTATUS Status;
ULONGLONG BytesTransferred;
KIRQL OldIrql;
//
// Sanity check
//
PAGED_CODE();
pTracker = CONTAINING_RECORD(
pWorkItem,
UL_CHUNK_TRACKER,
WorkItem
);
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pTracker->pRequest ) );
//
// Pull info from the tracker
//
pCompletionRoutine = pTracker->pCompletionRoutine;
pCompletionContext = pTracker->pCompletionContext;
Status = pTracker->IoStatus.Status;
BytesTransferred = pTracker->BytesTransferred;
//
// do some tracing
//
TRACE_TIME(
pTracker->pHttpConnection->ConnectionId,
pTracker->pHttpConnection->pRequest->RequestId,
TIME_ACTION_SEND_COMPLETE
);
//
// Free the MDLs attached to the tracker.
//
UlpFreeMdlRuns( pTracker );
//
// Updates the BytesSent counter in the request. For a single request
// we might receive multiple sendresponse ioctl calls, i.e. cgi requests.
// Multiple internal responses will get allocated and as well as a new
// chunk trucker for each response. Therefore the correct place to hold
// the Bytes send information should be in request. On the other hand
// keeping the request around until the send is done is yet another
// concern here. An outstanding bug #189327 will solve that issue as well.
//
UlInterlockedAdd64(
(PLONGLONG)&pTracker->pRequest->BytesSent,
BytesTransferred
);
if ( (pTracker->Flags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0 )
{
//
// Stop MinKBSec timer and start Connection Idle timer
//
UlLockTimeoutInfo(
&(pTracker->pHttpConnection->TimeoutInfo),
&OldIrql
);
UlResetConnectionTimer(
&(pTracker->pHttpConnection->TimeoutInfo),
TimerMinKBSec
);
UlSetConnectionTimer(
&(pTracker->pHttpConnection->TimeoutInfo),
TimerConnectionIdle
);
UlUnlockTimeoutInfo(
&(pTracker->pHttpConnection->TimeoutInfo),
OldIrql
);
UlEvaluateTimerState(
&(pTracker->pHttpConnection->TimeoutInfo)
);
}
//
// If this is the last response for this request and
// there was a log data passed down by the user then now
// its time to log.
//
if ( pTracker->pResponse && pTracker->pResponse->pLogData )
{
UlLogHttpHit( pTracker->pResponse->pLogData );
}
//
// complete the request
//
if (pCompletionRoutine != NULL)
{
(pCompletionRoutine)(
pCompletionContext,
Status,
BytesTransferred > MAXULONG ? MAXULONG : (ULONG)BytesTransferred
);
}
//
// Kick the parser on the connection and release our hold.
//
if ( ((pTracker->Flags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
&& ((pTracker->Flags & HTTP_SEND_RESPONSE_FLAG_DISCONNECT) == 0)
&& (Status == STATUS_SUCCESS) )
{
UlResumeParsing( pTracker->pHttpConnection );
}
UL_DEREFERENCE_HTTP_CONNECTION( pTracker->pHttpConnection );
//
// DeRef the trucker that we have bumped up before queueing this worker
// function.
//
UL_DEREFERENCE_CHUNK_TRACKER( pTracker );
} // UlpCompleteSendRequestWorker
/***************************************************************************++
Routine Description:
This function sends back a fake early completion to the caller, but
doesn't clean up any response sending structures.
Arguments:
pCompletionRoutine - the routine to call
pCompletionContext - the context given by the caller
Status - the status code with which to complete
BytesTransferred - size of the transfer
--***************************************************************************/
VOID
UlpCompleteSendIrpEarly(
PUL_COMPLETION_ROUTINE pCompletionRoutine,
PVOID pCompletionContext,
NTSTATUS Status,
ULONGLONG BytesTransferred
)
{
UlTrace(SEND_RESPONSE, (
"Http!UlpCompleteSendIrpEarly(\n"
" pCompletionRoutine = %p\n"
" pCompletionContext = %p\n"
" Status = %08x\n"
" BytesTransferred = %I64x)\n",
pCompletionRoutine,
pCompletionContext,
Status,
BytesTransferred
));
if (pCompletionRoutine != NULL)
{
(pCompletionRoutine)(
pCompletionContext,
Status,
BytesTransferred > MAXULONG ? MAXULONG : (ULONG)BytesTransferred
);
}
}
/***************************************************************************++
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
)
{
NTSTATUS status;
PUL_CHUNK_TRACKER pTracker;
ULONG bytesRead;
PMDL pMdl;
PMDL pMdlTail;
BOOLEAN initiateDisconnect = FALSE;
pTracker = (PUL_CHUNK_TRACKER)pContext;
IF_DEBUG( SEND_RESPONSE )
{
KdPrint((
"UlpRestartMdlRead: tracker %p\n",
pTracker
));
}
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
status = pIrp->IoStatus.Status;
if (NT_SUCCESS(status))
{
bytesRead = (ULONG)pIrp->IoStatus.Information;
if (bytesRead)
{
PUL_FILE_BUFFER pFileBuffer;
ULONG runCount;
runCount = pTracker->SendInfo.MdlRunCount;
pFileBuffer = &(pTracker->SendInfo.MdlRuns[runCount].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;
pTracker->SendInfo.MdlRuns[runCount].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.
//
pTracker->FileOffset.QuadPart += (ULONGLONG)bytesRead;
pTracker->FileBytesRemaining.QuadPart -= (ULONGLONG)bytesRead;
}
if (pTracker->FileBytesRemaining.QuadPart == 0 )
{
UlpIncrementChunkPointer( pTracker );
//
// If we're finished with the response (in other words, the
// call to UlSendData() below will be the last send for this
// response) and we are to initiate a disconnect, then ask
// UlSendResponse() to initiate the disconnect for us.
//
if (IS_REQUEST_COMPLETE(pTracker) &&
IS_DISCONNECT_TIME(pTracker))
{
initiateDisconnect = TRUE;
}
}
//
// 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_REQUEST_COMPLETE(pTracker) ||
pTracker->SendInfo.MdlRunCount == MAX_MDL_RUNS ||
pTracker->SendInfo.BytesBuffered >= MAX_BYTES_BUFFERED)
{
//
// Increment the RefCount on Tracker for Send I/O.
// UlpSendCompleteWorker will release it later.
//
UL_REFERENCE_CHUNK_TRACKER( pTracker );
//
// Adjust SendBufferedBytes.
//
InterlockedExchangeAdd(
&pTracker->pHttpConnection->SendBufferedBytes,
pTracker->pResponse->SendBufferedBytes
);
status = UlSendData(
pTracker->pConnection,
pTracker->SendInfo.pMdlHead,
pTracker->SendInfo.BytesBuffered,
&UlpRestartMdlSend,
pTracker,
pTracker->pSendIrp,
&pTracker->IrpContext,
initiateDisconnect
);
}
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 );
UL_CALL_PASSIVE(
&pTracker->WorkItem,
&UlpSendHttpResponseWorker
);
}
}
if (!NT_SUCCESS(status))
{
UlpCompleteSendRequest( pTracker, status );
}
//
// Read I/O Has been completed release our refcount
// on the chunk tracker.
//
UL_DEREFERENCE_CHUNK_TRACKER( pTracker );
return STATUS_MORE_PROCESSING_REQUIRED;
} // UlpRestartMdlRead
/***************************************************************************++
Routine Description:
Completion handler for MDL READ COMPLETE IRPs used for returning
MDLs back to the file system.
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 PFILE_OBJECT.
Return Value:
NTSTATUS - STATUS_SUCCESS if IO should continue processing this
IRP, STATUS_MORE_PROCESSING_REQUIRED if IO should stop processing
this IRP.
--***************************************************************************/
NTSTATUS
UlpRestartMdlReadComplete(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp,
IN PVOID pContext
)
{
PUL_CHUNK_TRACKER pTracker = (PUL_CHUNK_TRACKER)pContext;
ASSERT(IS_VALID_CHUNK_TRACKER(pTracker));
UL_DEREFERENCE_CHUNK_TRACKER(pTracker);
UlFreeIrp( pIrp );
return STATUS_MORE_PROCESSING_REQUIRED;
} // UlpRestartMdlReadComplete
/***************************************************************************++
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.
--***************************************************************************/
VOID
UlpRestartMdlSend(
IN PVOID pCompletionContext,
IN NTSTATUS Status,
IN ULONG_PTR Information
)
{
PUL_CHUNK_TRACKER pTracker;
pTracker = (PUL_CHUNK_TRACKER)pCompletionContext;
IF_DEBUG( SEND_RESPONSE )
{
KdPrint((
"UlpRestartMdlSend: tracker %p\n",
pTracker
));
}
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
//
// Adjust SendBufferedBytes.
//
InterlockedExchangeAdd(
&pTracker->pHttpConnection->SendBufferedBytes,
- pTracker->pResponse->SendBufferedBytes
);
//
// Disconnect if there was an error, and we didn't disconnect already.
//
if ((pTracker->pConnection != NULL) &&
(!NT_SUCCESS(Status)) &&
(!IS_DISCONNECT_TIME(pTracker)))
{
NTSTATUS TempStatus;
PUL_CONNECTION pConnection;
pConnection = pTracker->pConnection;
pTracker->pConnection = NULL;
TempStatus = UlCloseConnection(
pConnection,
TRUE, // AbortiveDisconnect
NULL, // pCompletionRoutine
NULL // pCompletionContext
);
}
//
// 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.
//
pTracker->IoStatus.Status = Status;
pTracker->IoStatus.Information = Information;
UL_QUEUE_WORK_ITEM(
&pTracker->WorkItem,
&UlpSendCompleteWorker
);
} // UlpRestartMdlSend
/***************************************************************************++
Routine Description:
Deferred handler for completed sends.
Arguments:
pWorkItem - Supplies a pointer to the work item queued. This should
point to the WORK_ITEM structure embedded in a UL_CHUNK_TRACKER.
--***************************************************************************/
VOID
UlpSendCompleteWorker(
IN PUL_WORK_ITEM pWorkItem
)
{
PUL_CHUNK_TRACKER pTracker;
NTSTATUS status;
//
// Sanity check.
//
PAGED_CODE();
pTracker = CONTAINING_RECORD(
pWorkItem,
UL_CHUNK_TRACKER,
WorkItem
);
IF_DEBUG( SEND_RESPONSE )
{
KdPrint((
"UlpSendCompleteWorker: tracker %p\n",
pTracker
));
}
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
//
// 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))
{
pTracker->BytesTransferred += pTracker->IoStatus.Information;
if (!IS_REQUEST_COMPLETE(pTracker))
{
//
// Free the MDLs attached to the tracker.
//
UlpFreeMdlRuns( pTracker );
//
// RefCount the chunk tracker up for the UlpSendHttpResponseWorker.
// It will DeRef it when it's done with the chunk tracker itself.
//
UL_REFERENCE_CHUNK_TRACKER( pTracker );
UlpSendHttpResponseWorker(&pTracker->WorkItem);
goto end;
}
}
//
// All done.
//
UlpCompleteSendRequest( pTracker, status );
end:
//
// Release our grab on the Tracker, Send I/O is done
//
UL_DEREFERENCE_CHUNK_TRACKER( pTracker );
} // UlpSendCompleteWorker
/***************************************************************************++
Routine Description:
Cleans the MDL_RUNs in the specified tracker and prepares the
tracker for reuse.
Arguments:
pTracker - Supplies the tracker to clean.
--***************************************************************************/
VOID
UlpFreeMdlRuns(
IN OUT PUL_CHUNK_TRACKER pTracker
)
{
PMDL pMdlHead;
PMDL pMdlNext;
PMDL pMdlTmp;
PMDL_RUN pMdlRun;
ULONG runCount;
NTSTATUS status;
PIRP pIrp;
PIO_STACK_LOCATION pIrpSp;
PUL_FILE_CACHE_ENTRY pFileCacheEntry;
//
// Sanity check.
//
PAGED_CODE();
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
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;
pFileCacheEntry = pMdlRun->FileBuffer.pFileCacheEntry;
if (pFileCacheEntry == NULL)
{
//
// It's a memory run; just walk & free the MDL chain.
//
while (pMdlHead != NULL)
{
pMdlTmp = pMdlHead->Next;
UlFreeMdl( pMdlHead );
pMdlHead = pMdlTmp;
}
}
else
{
//
// It's a file run; try the fast path.
//
status = UlReadCompleteFileEntryFast(
&pMdlRun->FileBuffer
);
if (!NT_SUCCESS(status))
{
//
// Fast path failed, we'll need an IRP.
//
pIrp = UlAllocateIrp(
pFileCacheEntry->pDeviceObject->StackSize,
FALSE
);
if (pIrp == NULL)
{
ASSERT( !"HANDLE NULL IRP!" );
}
else
{
pMdlRun->FileBuffer.pCompletionRoutine =
UlpRestartMdlReadComplete;
pMdlRun->FileBuffer.pContext = pTracker;
UL_REFERENCE_CHUNK_TRACKER(pTracker);
status = UlReadCompleteFileEntry(
&pMdlRun->FileBuffer,
pIrp
);
if (!NT_SUCCESS(status))
{
UL_DEREFERENCE_CHUNK_TRACKER(pTracker);
}
}
}
}
pMdlHead = pMdlNext;
pMdlRun++;
runCount--;
}
UlpInitMdlRuns( pTracker );
} // UlpFreeMdlRuns
/***************************************************************************++
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.
--***************************************************************************/
VOID
UlpIncrementChunkPointer(
IN OUT PUL_CHUNK_TRACKER pTracker
)
{
//
// Bump the pointer. 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( pTracker->pCurrentChunk < pTracker->pLastChunk );
pTracker->pCurrentChunk++;
if (!IS_REQUEST_COMPLETE(pTracker) )
{
if (IS_FROM_FILE(pTracker->pCurrentChunk))
{
pTracker->FileOffset =
pTracker->pCurrentChunk->FromFile.ByteRange.StartingOffset;
pTracker->FileBytesRemaining =
pTracker->pCurrentChunk->FromFile.ByteRange.Length;
}
else
{
ASSERT( IS_FROM_MEMORY(pTracker->pCurrentChunk) );
}
}
} // UlpIncrementChunkPointer
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/***************************************************************************++
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 - the connection with a req to be handled
pServedFromCache - we set TRUE if we handled the request
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlSendCachedResponse(
PUL_HTTP_CONNECTION pHttpConn,
PBOOLEAN pServedFromCache,
PBOOLEAN pConnectionRefused
)
{
NTSTATUS Status = STATUS_SUCCESS;
PUL_URI_CACHE_ENTRY pUriCacheEntry;
ULONG Flags;
PUL_CONFIG_GROUP_OBJECT pMaxBandwidth = NULL;
ULONG RetCacheControl;
LONGLONG BytesToSend;
KIRQL OldIrql;
//
// Sanity check.
//
PAGED_CODE();
ASSERT( pHttpConn );
ASSERT( pServedFromCache );
*pConnectionRefused = FALSE;
pUriCacheEntry = UlCheckoutUriCacheEntry(pHttpConn->pRequest);
//
// Enforce the connection limit
//
if (pUriCacheEntry &&
UlCheckSiteConnectionLimit(pHttpConn, &pUriCacheEntry->ConfigInfo) == FALSE)
{
*pConnectionRefused = TRUE;
}
if (pUriCacheEntry && *pConnectionRefused == FALSE) {
PUL_SITE_COUNTER_ENTRY pCtr;
ULONG Connections;
//
// Perf Counters (cached)
//
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
ASSERT(IS_VALID_URL_CONFIG_GROUP_INFO(&pUriCacheEntry->ConfigInfo));
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);
UlIncSiteNonCriticalCounterUlong(pCtr, HttpSiteCounterConnAttempts);
if (pCtr != pHttpConn->pPrevSiteCounters)
{
if (pHttpConn->pPrevSiteCounters)
{
// Decrement old site's counters & release ref count
UlDecSiteCounter(
pHttpConn->pPrevSiteCounters,
HttpSiteCounterCurrentConns
);
DEREFERENCE_SITE_COUNTER_ENTRY(pHttpConn->pPrevSiteCounters);
}
Connections = (ULONG) UlIncSiteCounter(pCtr, HttpSiteCounterCurrentConns);
UlMaxSiteCounter(
pCtr,
HttpSiteCounterMaxConnections,
Connections
);
// add ref for new site counters
REFERENCE_SITE_COUNTER_ENTRY(pCtr);
pHttpConn->pPrevSiteCounters = pCtr;
}
}
//
// Check "Accept:" header.
//
if ( FALSE == pHttpConn->pRequest->AcceptWildcard)
{
if ( FALSE == UlpIsAcceptHeaderOk( pHttpConn->pRequest, pUriCacheEntry ) )
{
//
// Cache entry did not match requested accept header; bounce up
// to user-mode for response.
//
UlCheckinUriCacheEntry(pUriCacheEntry);
*pServedFromCache = FALSE;
goto end;
}
}
//
// Cache-Control: Check the If-* headers to see if we can/should skip
// sending of the cached response.
//
RetCacheControl = UlpCheckCacheControlHeaders(
pHttpConn->pRequest,
pUriCacheEntry );
if ( RetCacheControl )
{
// check-in cache entry, since completion won't run.
UlCheckinUriCacheEntry(pUriCacheEntry);
if ( 304 == RetCacheControl )
{
// Mark as "served from cache"
*pServedFromCache = TRUE;
}
else
{
// We failed it.
ASSERT(412 == RetCacheControl);
//
// Indicate that the parser should send error 412 (Precondition Failed)
//
pHttpConn->pRequest->ParseState = ParseErrorState;
pHttpConn->pRequest->ErrorCode = UlErrorPreconditionFailed;
*pServedFromCache = FALSE;
Status = STATUS_INVALID_DEVICE_STATE;
}
// return success.
goto end;
}
// Try to get the corresponding cgroup for the bw settings
if (pUriCacheEntry)
{
pMaxBandwidth = pUriCacheEntry->ConfigInfo.pMaxBandwidth;
}
//
// Install a filter if BWT is enabled for this request's site.
//
if (pMaxBandwidth != NULL &&
pMaxBandwidth->MaxBandwidth.Flags.Present != 0 &&
pMaxBandwidth->MaxBandwidth.MaxBandwidth != HTTP_LIMIT_INFINITE )
{
// Call TCI to do the filter addition
UlTcAddFilter( pHttpConn, pMaxBandwidth );
}
else
{
// Attempt to add the filter to the global flow
if (UlTcGlobalThrottlingEnabled())
{
UlTcAddFilter( pHttpConn, NULL );
}
}
//
// figure out correct flags
//
Flags = 0;
if ( UlCheckDisconnectInfo(pHttpConn->pRequest) ) {
Flags |= HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
}
//
// Start the MinKBSec timer, since the data length
// is in the UL_URI_CACHE_ENTRY
//
BytesToSend = pUriCacheEntry->ContentLength + pUriCacheEntry->HeaderLength;
UlSetMinKBSecTimer(
&pHttpConn->TimeoutInfo,
BytesToSend
);
// send from the cache
Status = UlpSendCacheEntry(
pHttpConn, // connection
Flags, // send flags
pUriCacheEntry, // cache entry
NULL, // completion routine
NULL, // completion context
NULL
);
*pServedFromCache = TRUE;
// check in cache entry on failure since our completion
// routine won't run.
if ( !NT_SUCCESS(Status) ) {
UlCheckinUriCacheEntry(pUriCacheEntry);
}
} else {
if (*pConnectionRefused)
{
// check in the cache entry if connection is refused
UlCheckinUriCacheEntry(pUriCacheEntry);
}
*pServedFromCache = FALSE;
}
end:
UlTrace(URI_CACHE, (
"Http!UlSendCachedResponse(httpconn = %p) ServedFromCache = %d, Status = %x\n",
pHttpConn,
*pServedFromCache,
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 - the initiating request
pResponse - the generated response
Flags - UlSendHttpResponse flags
pCompletionRoutine - called after entry is sent
pCompletionContext - passed to pCompletionRoutine
pServedFromCache - always set. TRUE if we'll handle sending response.
FALSE indicates that the caller should send it.
--***************************************************************************/
NTSTATUS
UlCacheAndSendResponse(
IN PUL_INTERNAL_REQUEST pRequest,
IN PUL_INTERNAL_RESPONSE pResponse,
IN PUL_APP_POOL_PROCESS pProcess,
IN ULONG Flags,
IN HTTP_CACHE_POLICY Policy,
IN PUL_COMPLETION_ROUTINE pCompletionRoutine,
IN PVOID pCompletionContext,
OUT PBOOLEAN pServedFromCache
)
{
NTSTATUS Status = STATUS_SUCCESS;
//
// Sanity check
//
PAGED_CODE();
ASSERT( pServedFromCache );
//
// should we close the connection ?
//
if ( UlCheckDisconnectInfo(pRequest) )
{
Flags |= HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
}
//
// do the real work
//
if (UlCheckCacheResponseConditions(pRequest, pResponse, Flags, Policy)) {
Status = UlpBuildCacheEntry(
pRequest,
pResponse,
pProcess,
Flags,
Policy,
pCompletionRoutine,
pCompletionContext
);
if (NT_SUCCESS(Status)) {
*pServedFromCache = TRUE;
} else {
*pServedFromCache = FALSE;
}
} else {
*pServedFromCache = FALSE;
}
UlTrace(URI_CACHE, (
"Http!UlCacheAndSendResponse ServedFromCache = %d\n",
*pServedFromCache
));
return Status;
} // UlCacheAndSendResponse
/***************************************************************************++
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 - the initiating request
pResponse - the generated response
Flags - UlSendHttpResponse flags
pCompletionRoutine - called after entry is sent
pCompletionContext - passed to pCompletionRoutine
--***************************************************************************/
NTSTATUS
UlpBuildCacheEntry(
IN PUL_INTERNAL_REQUEST pRequest,
IN PUL_INTERNAL_RESPONSE pResponse,
IN PUL_APP_POOL_PROCESS pProcess,
IN ULONG Flags,
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 = (ULONG)(pResponse->ResponseLength - pResponse->HeaderLength);
//
// Sanity check
//
PAGED_CODE();
//
// See if we need to store any logging data. If we need calculate the
// required cache space for the logging data per format
//
if ( pResponse->pLogData )
{
switch( pResponse->pLogData->Format )
{
case HttpLoggingTypeW3C:
{
// The fields until ServerPort will go to the cache entry.
// Reserved space for date & time will not be copied.
LogDataLength = pResponse->pLogData->UsedOffset2
- pResponse->pLogData->UsedOffset1;
}
break;
case HttpLoggingTypeNCSA:
{
// Only a small fragment of NCSA log line goes to the cache
// entry. This fragment is located between offset2 and 1
// excluding the space reserved for date & time fields
LogDataLength = pResponse->pLogData->UsedOffset2
- pResponse->pLogData->UsedOffset1
- NCSA_FIX_DATE_AND_TIME_FIELD_SIZE;
}
break;
case HttpLoggingTypeIIS:
{
// Only the fragments two and three go to the
// cache entry
LogDataLength = (USHORT)pResponse->pLogData->Used +
pResponse->pLogData->UsedOffset2;
}
break;
default:
ASSERT(!"Unknown Log Format.\n");
}
}
//
// allocate a cache entry
//
SpaceLength =
pRequest->CookedUrl.Length + sizeof(WCHAR) + // space for hash key
pResponse->ETagLength + // space for ETag
LogDataLength; // space for 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"
"\n",
SpaceLength,
pRequest->CookedUrl.Length,
ALIGN_UP(pRequest->CookedUrl.Length, WCHAR)
));
UlTrace(URI_CACHE, (
" ContentLength=0x%x, %d\n", ContentLength, ContentLength));
pEntry = UL_ALLOCATE_STRUCT_WITH_SPACE(
PagedPool,
UL_URI_CACHE_ENTRY,
SpaceLength,
UL_URI_CACHE_ENTRY_POOL_TAG
);
if (pEntry) {
//
// init entry
//
UlInitCacheEntry(
pEntry,
pRequest->CookedUrl.Hash,
pRequest->CookedUrl.Length,
pRequest->CookedUrl.pUrl
);
//
// 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-Type so we can verify the Accept: header on requests.
//
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)
{
KeQuerySystemTime(&pEntry->ExpirationTime);
//
// convert seconds to 100 nanosecond intervals (x * 10^7)
//
pEntry->ExpirationTime.QuadPart +=
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));
UlpConfigGroupInfoDeepCopy(&pRequest->ConfigInfo, &pEntry->ConfigInfo);
//
// remember who created us
//
pEntry->pProcess = pProcess;
//
// generate the content and fixed headers
//
pEntry->pResponseMdl = UlLargeMemAllocate(
ContentLength + pResponse->HeaderLength,
&pEntry->LongTermCacheable
);
if (NULL == pEntry->pResponseMdl)
{
Status = STATUS_NO_MEMORY;
goto cleanup;
}
pEntry->HeaderLength = pResponse->HeaderLength;
if (FALSE == UlLargeMemSetData(
pEntry->pResponseMdl, // Dest MDL
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 ( pResponse->pLogData == NULL )
{
pEntry->LoggingEnabled = FALSE;
pEntry->LogDataLength = 0;
pEntry->MaxLength = 0;
pEntry->pLogData = NULL;
pEntry->UsedOffset1 = 0;
pEntry->UsedOffset2 = 0;
}
else
{
//
// There could be no field to save in the cache entry but the logging
// might still be enabled for those fields we generate later i.e.
// logging enabled with fields date & time.
//
pEntry->LoggingEnabled = TRUE;
pEntry->MaxLength = pResponse->pLogData->Length;
//
// Copy over the partially complete log line not including the date & time
// fields to the cache entry. Also remember the length of the data.
//
if ( LogDataLength )
{
pEntry->LogDataLength = LogDataLength;
pEntry->pLogData =
pEntry->pETag +
pEntry->ETagLength;
switch( pResponse->pLogData->Format )
{
case HttpLoggingTypeW3C:
{
// Discard the date,time,username fields at the beginning of
// the log line when storing the cache entry.
pEntry->UsedOffset1 = pResponse->pLogData->UsedOffset1;
pEntry->UsedOffset2 = pResponse->pLogData->UsedOffset2;
// Copy the middle fragment
RtlCopyMemory(
pEntry->pLogData,
&pResponse->pLogData->Line[pEntry->UsedOffset1],
LogDataLength
);
}
break;
case HttpLoggingTypeNCSA:
{
// Calculate the start of the middle fragment.
pEntry->UsedOffset1 = pResponse->pLogData->UsedOffset1
+ NCSA_FIX_DATE_AND_TIME_FIELD_SIZE;
pEntry->UsedOffset2 = 0;
// Copy the middle fragment
RtlCopyMemory(
pEntry->pLogData,
&pResponse->pLogData->Line[pEntry->UsedOffset1],
LogDataLength
);
}
break;
case HttpLoggingTypeIIS:
{
// UsedOffset1 specifies the second fragment's size.
// UsedOffset2 specifies the third's size.
pEntry->UsedOffset1 = pResponse->pLogData->UsedOffset2;
pEntry->UsedOffset2 = LogDataLength - pEntry->UsedOffset1;
// Copy over the fragments two and three
RtlCopyMemory(
pEntry->pLogData,
&pResponse->pLogData->Line[IIS_LOG_LINE_SECOND_FRAGMENT_OFFSET],
pEntry->UsedOffset1
);
RtlCopyMemory(
&pEntry->pLogData[pEntry->UsedOffset1],
&pResponse->pLogData->Line[IIS_LOG_LINE_THIRD_FRAGMENT_OFFSET],
pEntry->UsedOffset2
);
}
break;
default:
ASSERT(!"Unknown Log Format.\n");
}
}
else
{
pEntry->LogDataLength = 0;
pEntry->pLogData = NULL;
pEntry->UsedOffset1 = 0;
pEntry->UsedOffset2 = 0;
}
}
UlTrace(URI_CACHE, (
"Http!UlpBuildCacheEntry\n"
" entry = %p\n"
" pUri = %p '%ls'\n"
" pResponseMdl = %p (%d bytes)\n"
" pETag = %p\n"
" pLogData = %p\n"
" end = %p\n",
pEntry,
pEntry->UriKey.pUri, pEntry->UriKey.pUri,
pEntry->pResponseMdl, pEntry->ContentLength + pEntry->HeaderLength,
pEntry->pETag,
pEntry->pLogData,
((PUCHAR)pEntry->UriKey.pUri) + SpaceLength
));
pTracker =
UlpAllocateChunkTracker(
UlTrackerTypeBuildUriEntry, // tracker type
0, // send irp size
pResponse->MaxFileSystemStackSize, // read irp size
pRequest->pHttpConn,
Flags,
pRequest,
pResponse,
pCompletionRoutine,
pCompletionContext
);
if (pTracker) {
ULONG i;
//
// init tracker BuildInfo
//
pTracker->BuildInfo.pUriEntry = pEntry;
pTracker->BuildInfo.Offset = 0;
UlTrace(LARGE_MEM, ("UlpBuildCacheEntry: init tracker BuildInfo\n"));
INITIALIZE_FILE_BUFFER(&pTracker->BuildInfo.FileBuffer);
//
// skip over the header chunks because we already
// got that stuff
//
for (i = 0; i < HEADER_CHUNK_COUNT; i++) {
ASSERT( !IS_REQUEST_COMPLETE( pTracker ) );
UlpIncrementChunkPointer( pTracker );
}
//
// finish initialization on a system thread so that MDLs
// will get charged to the System process instead of the
// current process.
//
UlpBuildBuildTrackerWorker(&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) {
if (pEntry->pResponseMdl) {
UlLargeMemFree(pEntry->pResponseMdl);
}
UL_FREE_POOL_WITH_SIG(pEntry, UL_URI_CACHE_ENTRY_POOL_TAG);
}
if (pTracker) {
UL_FREE_POOL_WITH_SIG(pTracker, UL_CHUNK_TRACKER_POOL_TAG);
}
}
return Status;
} // UlpBuildCacheEntry
/***************************************************************************++
Routine Description:
Worker Routine used to initialize the UL_CHUNK_TRACKER for
UlpBuildCacheEntry. We need to have this function so that when
we make a MDL inside the tracker, the page will be charged to the
System process instead of the current process.
Arguments:
pWorkItem - Supplies a pointer to the work item queued. This should
point to the WORK_ITEM structure embedded in a UL_CHUNK_TRACKER.
--***************************************************************************/
VOID
UlpBuildBuildTrackerWorker(
IN PUL_WORK_ITEM pWorkItem
)
{
PUL_CHUNK_TRACKER pTracker;
//
// Sanity check.
//
PAGED_CODE();
pTracker = CONTAINING_RECORD(
pWorkItem,
UL_CHUNK_TRACKER,
WorkItem
);
//
// init tracker BuildInfo
//
UlTrace(LARGE_MEM, ("UlpBuildBuildTrackerWorker\n"));
//
// add reference to connection for tracker
//
UL_REFERENCE_HTTP_CONNECTION( pTracker->pHttpConnection );
//
// Let the worker do the dirty work, no reason to queue off
//
// it will queue the first time it needs to do blocking i/o
//
UlpBuildCacheEntryWorker(&pTracker->WorkItem);
} // UlpBuildBuildTrackerWorker
/***************************************************************************++
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.
--***************************************************************************/
VOID
UlpBuildCacheEntryWorker(
IN PUL_WORK_ITEM pWorkItem
)
{
PUL_CHUNK_TRACKER pTracker;
NTSTATUS status;
PUL_INTERNAL_DATA_CHUNK pCurrentChunk;
PUL_FILE_CACHE_ENTRY pFileCacheEntry;
PIRP pIrp;
PIO_STACK_LOCATION pIrpSp;
PUCHAR pBuffer;
ULONG BufferLength;
//
// 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 ) );
status = STATUS_SUCCESS;
while( TRUE )
{
//
// Capture the current chunk pointer, then check for end of
// response.
//
pCurrentChunk = pTracker->pCurrentChunk;
if (IS_REQUEST_COMPLETE(pTracker))
{
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( pTracker );
//
// 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 == UlLargeMemSetData(
pTracker->BuildInfo.pUriEntry->pResponseMdl,
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(pCurrentChunk) );
//
// ignore empty file ranges
//
if (pCurrentChunk->FromFile.ByteRange.Length.QuadPart == 0)
{
UlpIncrementChunkPointer( pTracker );
continue;
}
//
// Do the read.
//
pTracker->BuildInfo.FileBuffer.pFileCacheEntry =
pCurrentChunk->FromFile.pFileCacheEntry;
pTracker->BuildInfo.FileBuffer.FileOffset = pTracker->FileOffset;
pTracker->BuildInfo.FileBuffer.Length =
MIN(
(ULONG)pTracker->FileBytesRemaining.QuadPart,
MAX_BYTES_PER_READ
);
pTracker->BuildInfo.FileBuffer.pCompletionRoutine =
UlpRestartCacheMdlRead;
pTracker->BuildInfo.FileBuffer.pContext = pTracker;
status = UlReadFileEntry(
&pTracker->BuildInfo.FileBuffer,
pTracker->pReadIrp
);
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
)
{
NTSTATUS status;
PUL_CHUNK_TRACKER pTracker;
PMDL pMdl;
PUCHAR pData;
ULONG DataLen;
pTracker = (PUL_CHUNK_TRACKER)pContext;
UlTrace(URI_CACHE, (
"Http!UlpRestartCacheMdlRead: tracker %p, status %x info %d\n",
pTracker,
pIrp->IoStatus.Status,
(ULONG) pIrp->IoStatus.Information
));
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
status = pIrp->IoStatus.Status;
if (NT_SUCCESS(status))
{
//
// copy read data into cache buffer
//
pMdl = pTracker->BuildInfo.FileBuffer.pMdl;
while (pMdl) {
pData = (PUCHAR) MmGetMdlVirtualAddress(pMdl);
DataLen = MmGetMdlByteCount(pMdl);
UlTrace(LARGE_MEM, (
"Http!UlpRestartCacheMdlRead: "
"copy range %u (%x) -> %u\n",
pTracker->BuildInfo.Offset,
DataLen,
pTracker->BuildInfo.Offset
+ DataLen
));
if (FALSE == UlLargeMemSetData(
pTracker->BuildInfo.pUriEntry->pResponseMdl,
pData,
DataLen,
pTracker->BuildInfo.Offset
))
{
status = STATUS_INSUFFICIENT_RESOURCES;
goto end;
}
pTracker->BuildInfo.Offset += DataLen;
ASSERT(pTracker->BuildInfo.Offset <= pTracker->BuildInfo.pUriEntry->ContentLength);
pMdl = pMdl->Next;
}
//
// free the MDLs
//
pTracker->BuildInfo.FileBuffer.pCompletionRoutine =
UlpRestartCacheMdlFree;
pTracker->BuildInfo.FileBuffer.pContext = pTracker;
status = UlReadCompleteFileEntry(
&pTracker->BuildInfo.FileBuffer,
pTracker->pReadIrp
);
}
end:
if (!NT_SUCCESS(status))
{
UlpCompleteCacheBuild( pTracker, status );
}
return STATUS_MORE_PROCESSING_REQUIRED;
} // UlpRestartCacheMdlRead
/***************************************************************************++
Routine Description:
Completion handler for MDL free IRPs used after 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
UlpRestartCacheMdlFree(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp,
IN PVOID pContext
)
{
NTSTATUS status;
PUL_CHUNK_TRACKER pTracker;
pTracker = (PUL_CHUNK_TRACKER)pContext;
UlTrace(URI_CACHE, (
"Http!UlpRestartCacheMdlFree: tracker %p, status %x info %d\n",
pTracker,
pIrp->IoStatus.Status,
(ULONG) pIrp->IoStatus.Information
));
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
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.
//
pTracker->FileOffset.QuadPart += pIrp->IoStatus.Information;
pTracker->FileBytesRemaining.QuadPart -= pIrp->IoStatus.Information;
if (pTracker->FileBytesRemaining.QuadPart == 0 )
{
UlpIncrementChunkPointer( pTracker );
}
//
// Go back into the loop if there's more to read
//
if (IS_REQUEST_COMPLETE(pTracker)) {
UlpCompleteCacheBuild( pTracker, status );
} else {
UL_CALL_PASSIVE(
&pTracker->WorkItem,
&UlpBuildCacheEntryWorker
);
}
}
if (!NT_SUCCESS(status))
{
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.
--***************************************************************************/
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 ) );
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.
--***************************************************************************/
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;
NTSTATUS Status;
//
// Sanity check.
//
PAGED_CODE();
pTracker = CONTAINING_RECORD(
pWorkItem,
UL_CHUNK_TRACKER,
WorkItem
);
ASSERT( IS_VALID_CHUNK_TRACKER( pTracker ) );
pUriCacheEntry = pTracker->BuildInfo.pUriEntry;
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
Flags = pTracker->Flags;
pHttpConnection = pTracker->pHttpConnection;
pCompletionRoutine = pTracker->pCompletionRoutine;
pCompletionContext = pTracker->pCompletionContext;
//
// 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->CacheAndSendResponse = TRUE;
}
//
// free the read tracker
//
UlpFreeChunkTracker( pTracker );
//
// try to put the entry into the hash table
//
UlAddCacheEntry(pUriCacheEntry);
//
// grab the connection lock (because UlpSendCacheEntry
// assumes you have it)
//
UlAcquireResourceExclusive(&(pHttpConnection->Resource), TRUE);
//
// Send the cache entry
//
Status = UlpSendCacheEntry(
pHttpConnection, // connection
Flags, // flags
pUriCacheEntry, // cache entry
pCompletionRoutine, // completion routine
pCompletionContext, // completion context
pLogData // corresponding log data
);
//
// get rid of the entry if it didn't work
//
if ( !NT_SUCCESS(Status) )
{
UlCheckinUriCacheEntry(pUriCacheEntry);
//
// Free up the log data buffer (if we passed it
// into UlpSendCacheEntry)
//
if ( pLogData )
{
UlDestroyLogDataBuffer(pLogData);
}
}
//
// done with the connection lock
//
UlReleaseResource(&(pHttpConnection->Resource));
//
// now that UL_FULL_TRACKER has a reference, we can release
// our hold on the connection.
//
UL_DEREFERENCE_HTTP_CONNECTION(pHttpConnection);
//
// 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 depend 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:
All the time
--***************************************************************************/
NTSTATUS
UlpSendCacheEntry(
PUL_HTTP_CONNECTION pHttpConnection,
ULONG Flags,
PUL_URI_CACHE_ENTRY pUriCacheEntry,
PUL_COMPLETION_ROUTINE pCompletionRoutine,
PVOID pCompletionContext,
PUL_LOG_DATA_BUFFER pLogData
)
{
NTSTATUS Status = STATUS_SUCCESS;
PUL_FULL_TRACKER pTracker;
CCHAR SendIrpStackSize;
UL_CONN_HDR ConnHeader;
ULONG VarHeaderGenerated;
ULONG TotalLength;
ULONG contentLengthStringLength;
UCHAR contentLength[MAX_ULONGLONG_STR];
LARGE_INTEGER liCreationTime;
//
// Sanity check
//
PAGED_CODE();
ASSERT( UL_IS_VALID_HTTP_CONNECTION(pHttpConnection) );
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
ASSERT( UlDbgResourceOwnedExclusive(&pHttpConnection->Resource) );
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 );
//
// figure out how much space we need for variable headers
//
if (!pUriCacheEntry->ContentLengthSpecified &&
UlNeedToGenerateContentLength(
pUriCacheEntry->Verb,
pUriCacheEntry->StatusCode,
Flags
))
{
//
// Autogenerate a content-length header.
//
PCHAR pszEnd = UlStrPrintUlonglong(
(PCHAR) contentLength,
(ULONGLONG) pUriCacheEntry->ContentLength,
'\0');
contentLengthStringLength = DIFF(pszEnd - (PCHAR) contentLength);
}
else
{
//
// Either we cannot or do not need to autogenerate a
// content-length header.
//
contentLength[0] = '\0';
contentLengthStringLength = 0;
}
ConnHeader = UlChooseConnectionHeader(
pHttpConnection->pRequest->Version,
(BOOLEAN)(Flags & HTTP_SEND_RESPONSE_FLAG_DISCONNECT)
);
//
// create 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 tracker
//
pTracker->pUriEntry = pUriCacheEntry;
UL_REFERENCE_HTTP_CONNECTION(pHttpConnection);
pTracker->pHttpConnection = pHttpConnection;
UL_REFERENCE_INTERNAL_REQUEST(pHttpConnection->pRequest);
pTracker->pRequest = pHttpConnection->pRequest;
pTracker->pCompletionRoutine = pCompletionRoutine;
pTracker->pCompletionContext = pCompletionContext;
pTracker->Flags = Flags;
pTracker->pLogData = NULL;
//
// build MDLs for send
//
ASSERT(pUriCacheEntry->pResponseMdl != NULL);
MmInitializeMdl(
pTracker->pMdlFixedHeaders,
(PCHAR) MmGetMdlVirtualAddress(pUriCacheEntry->pResponseMdl) + pUriCacheEntry->ContentLength,
pUriCacheEntry->HeaderLength
);
IoBuildPartialMdl(
pUriCacheEntry->pResponseMdl,
pTracker->pMdlFixedHeaders,
(PCHAR) MmGetMdlVirtualAddress(pUriCacheEntry->pResponseMdl) + pUriCacheEntry->ContentLength,
pUriCacheEntry->HeaderLength
);
//
// generate variable headers
// and build a MDL for them
//
UlGenerateVariableHeaders(
ConnHeader,
contentLength,
contentLengthStringLength,
pTracker->pAuxiliaryBuffer,
&VarHeaderGenerated,
&liCreationTime
);
ASSERT( VarHeaderGenerated <= g_UlMaxVariableHeaderSize );
ASSERT( VarHeaderGenerated <= pTracker->AuxilaryBufferLength );
if (0 == pUriCacheEntry->CreationTime.QuadPart)
{
//
// If we were unable to capture the Last-Modified time from the
// original item, use the time we created this cache entry.
//
pUriCacheEntry->CreationTime.QuadPart = liCreationTime.QuadPart;
}
pTracker->pMdlVariableHeaders->ByteCount = VarHeaderGenerated;
pTracker->pMdlFixedHeaders->Next = pTracker->pMdlVariableHeaders;
//
// build MDL for body
//
if (pUriCacheEntry->ContentLength)
{
MmInitializeMdl(
pTracker->pMdlContent,
MmGetMdlVirtualAddress(pUriCacheEntry->pResponseMdl),
pUriCacheEntry->ContentLength
);
IoBuildPartialMdl(
pUriCacheEntry->pResponseMdl,
pTracker->pMdlContent,
MmGetMdlVirtualAddress(pUriCacheEntry->pResponseMdl),
pUriCacheEntry->ContentLength
);
pTracker->pMdlVariableHeaders->Next = pTracker->pMdlContent;
}
else
{
pTracker->pMdlVariableHeaders->Next = NULL;
}
//
// We have to log this cache hit. time to allocate a log
// data buffer and set its request pointer. But only if we
// had the logging data cached before.
//
if (pUriCacheEntry->LoggingEnabled)
{
//
// If this was a user call (UlCacheAndSendResponse) then
// pCompletionContext will be the pIrp pointer otherwise
// NULL. As for the build&send cache requests we already
// allocated a log buffer let's use that one to prevent
// unnecessary reallocation and copying. Also the pLogData
// pointer will be NULL if this is a pure cache hit but not
// CacheAndSend.
//
// REVIEW: AliTu mentioned that checking there might be a better
// REVIEW: way to check and see if this is a "build cache entry"
// REVIEW: case than checking if pCompletionContext is non-NULL.
//
if (pCompletionContext)
{
ASSERT(pLogData);
pTracker->pLogData = pLogData;
}
else
{
PUL_INTERNAL_REQUEST pRequest = pHttpConnection->pRequest;
//
// Pure cache hit
//
ASSERT(pLogData==NULL);
pTracker->pLogData = &pRequest->LogData;
Status = UlAllocateLogDataBuffer(
pTracker->pLogData,
pRequest,
pUriCacheEntry->ConfigInfo.pLoggingConfig
);
ASSERT(NT_SUCCESS(Status));
}
}
//
// go go go!
//
TotalLength = pUriCacheEntry->HeaderLength;
TotalLength += pUriCacheEntry->ContentLength;
TotalLength += VarHeaderGenerated;
Status = UlSendData(
pTracker->pHttpConnection->pConnection,
pTracker->pMdlFixedHeaders,
TotalLength,
&UlpCompleteSendCacheEntry,
pTracker,
pTracker->pSendIrp,
&pTracker->IrpContext,
(BOOLEAN)((Flags & HTTP_SEND_RESPONSE_FLAG_DISCONNECT) != 0)
);
}
else
{
Status = STATUS_INSUFFICIENT_RESOURCES;
}
cleanup:
//
// clean up the tracker if we don't need it
//
if (!NT_SUCCESS(Status)) {
if (pTracker) {
if (pLogData == NULL && pTracker->pLogData)
{
//
// if we allocated a Log Data Buffer, we need to free it;
// Caller will free if it was passed in.
//
UlDestroyLogDataBuffer(pTracker->pLogData);
}
UlpFreeCacheTracker(pTracker);
UL_DEREFERENCE_HTTP_CONNECTION(pHttpConnection);
UL_DEREFERENCE_INTERNAL_REQUEST(pHttpConnection->pRequest);
}
}
UlTrace(URI_CACHE, (
"Http!UlpSendCacheEntry status = %08x\n",
Status
));
return Status;
} // UlpSendCacheEntry
/***************************************************************************++
Routine Description:
Called when we finish sending data to the client. Just queues to
a worker that runs at passive level.
Arguments:
pCompletionContext - pointer to UL_FULL_TRACKER
Status - status of send
Information - Bytes transferred.
--***************************************************************************/
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, (int) Information));
//
// invoke completion routine
//
if (pTracker->pCompletionRoutine != NULL)
{
(pTracker->pCompletionRoutine)(
pTracker->pCompletionContext,
Status,
Information
);
}
UL_QUEUE_WORK_ITEM(
&pTracker->WorkItem,
&UlpCompleteSendCacheEntryWorker
);
}
/***************************************************************************++
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.
--***************************************************************************/
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;
//
// 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 tracker
//
pHttpConnection = pTracker->pHttpConnection;
ASSERT( UL_IS_VALID_HTTP_CONNECTION(pHttpConnection) );
pRequest = pTracker->pRequest;
ASSERT( UL_IS_VALID_INTERNAL_REQUEST(pRequest) );
Flags = pTracker->Flags;
pUriCacheEntry = pTracker->pUriEntry;
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
Status = pTracker->IoStatus.Status;
//
// If the send failed and we did't ask UlSendData() to disconnect
// the connection, then initiate an *abortive* disconnect.
//
if ((NT_SUCCESS(Status) == FALSE) &&
((pTracker->Flags & HTTP_SEND_RESPONSE_FLAG_DISCONNECT) == 0))
{
UlTrace(URI_CACHE, (
"Http!UlpCompleteSendCacheEntryWorker(pTracker %p) Closing connection\n",
pTracker
));
UlCloseConnection(
pHttpConnection->pConnection,
TRUE,
NULL,
NULL
);
}
//
// Stop MinKBSec timer and start Connection Idle timer
//
UlLockTimeoutInfo(
&pHttpConnection->TimeoutInfo,
&OldIrql
);
UlResetConnectionTimer(
&pHttpConnection->TimeoutInfo,
TimerMinKBSec
);
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->pMdlContent->MdlFlags & MDL_MAPPED_TO_SYSTEM_VA)
{
MmUnmapLockedPages(
pTracker->pMdlContent->MappedSystemVa,
pTracker->pMdlContent
);
}
//
// Do the logging before cleaning up the tracker.
//
if (pTracker->pLogData)
{
//
// Call the cache logger. It will copy over the
// cached logging data for us, and log it out.
//
UlLogHttpCacheHit( pTracker );
}
//
// Kick the parser into action only if this cache response comes from
// the UlpCompleteCacheBuildWorker path, in which case UlpSendCacheEntry
// is called with a non-null pCompletionContext.
//
if (pTracker->pCompletionContext)
{
UlResumeParsing(pHttpConnection);
}
//
// clean up tracker
//
UlpFreeCacheTracker(pTracker);
//
// deref cache entry
//
UlCheckinUriCacheEntry(pUriCacheEntry);
//
// deref the internal request
//
UL_DEREFERENCE_INTERNAL_REQUEST(pRequest);
//
// deref 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->IsFromLookaside = FALSE;
pTracker->IsFromRequest = FALSE;
pTracker->AuxilaryBufferLength = g_UlMaxVariableHeaderSize;
UlInitializeFullTrackerPool(pTracker, SendIrpStackSize);
}
UlTrace( URI_CACHE, (
"Http!UlpAllocateCacheTracker: tracker %p\n",
pTracker
));
return pTracker;
} // UlpAllocateCacheTracker
/***************************************************************************++
Routine Description:
Frees a UL_FULL_TRACKER.
CODEWORK: this routine should probably do all the destruction
Arguments:
pTracker - Specifies the UL_FULL_TRACKER to free.
--***************************************************************************/
VOID
UlpFreeCacheTracker(
IN PUL_FULL_TRACKER pTracker
)
{
UlTrace(URI_CACHE, (
"Http!UlpFreeCacheTracker: tracker %p\n",
pTracker
));
ASSERT(pTracker);
ASSERT(IS_VALID_FULL_TRACKER(pTracker));
if (pTracker->IsFromRequest == FALSE)
{
if (pTracker->IsFromLookaside)
{
pTracker->Signature = MAKE_FREE_TAG(UL_FULL_TRACKER_POOL_TAG);
UlPplFreeFullTracker( pTracker );
}
else
{
UL_FREE_POOL_WITH_SIG( pTracker, UL_FULL_TRACKER_POOL_TAG );
}
}
}
/***************************************************************************++
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 - address of the memory
Length - length of the memory
Operation - either IoWriteAcess or IoReadAccess
--***************************************************************************/
PMDL
UlpAllocateLockedMdl(
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() )
{
//
// Can this really happen?
//
Status = UL_CONVERT_EXCEPTION_CODE(GetExceptionCode());
UlFreeMdl(pMdl);
pMdl = NULL;
}
if (!pMdl || !NT_SUCCESS(Status)) {
UlTrace(URI_CACHE, (
"Http!UlpAllocateLockedMdl failed %08x\n",
Status
));
}
return pMdl;
}
/***************************************************************************++
Routine Description:
Unlocks and frees an MDL allocated with UlpAllocateLockedMdl.
Arguments:
pMdl - the MDL to free
--***************************************************************************/
VOID
UlpFreeLockedMdl(
PMDL pMdl
)
{
//
// Sanity check
//
ASSERT( IS_MDL_LOCKED(pMdl) );
MmUnlockPages(pMdl);
UlFreeMdl(pMdl);
}
/***************************************************************************++
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 - memory descriptor for the MDL to initialize
VirtualAddress - address of the memory
Length - length of the memory
Operation - either IoWriteAcess or IoReadAccess
--***************************************************************************/
NTSTATUS
UlpInitializeAndLockMdl(
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() )
{
//
// Can this really happen?
//
Status = UL_CONVERT_EXCEPTION_CODE(GetExceptionCode());
UlTrace(URI_CACHE, (
"UL!UlpInitializeAndLockMdl failed %08x\n",
Status
));
}
return Status;
}
/***************************************************************************++
Routine Description:
Checks the request to see if it has any of the following headers:
If-Modified-Since:
If-Match:
If-None-Match:
If so, we see if we can skip the sending of the full item. If we can skip,
we send back the apropriate response of either 304 (not modified) or
set the parser state to send back a 412 (precondition not met).
Arguments:
pRequest - The request to check
pUriCacheEntry - The cache entry being requested
Returns:
0 Send cannot be skipped; continue with sending the cache entry.
304 Send can be skipped. 304 response sent. NOTE: pRequest may be
invalid on return.
412 Send can be skipped. pRequest->ParseState set to ParseErrorState with
pRequest->ErrorCode set to UlErrorPreconditionFailed (412)
--***************************************************************************/
ULONG
UlpCheckCacheControlHeaders(
PUL_INTERNAL_REQUEST pRequest,
PUL_URI_CACHE_ENTRY pUriCacheEntry
)
{
ULONG RetStatus = 0; // Assume can't skip.
BOOLEAN fIfNoneMatchPassed = TRUE;
BOOLEAN fSkipIfModifiedSince = FALSE;
LARGE_INTEGER liModifiedSince;
LARGE_INTEGER liUnmodifiedSince;
LARGE_INTEGER liNow;
ULONG BytesSent = 0;
ASSERT( UL_IS_VALID_INTERNAL_REQUEST(pRequest) );
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
//
// 1. Check If-Match
//
if ( pRequest->HeaderValid[HttpHeaderIfMatch] )
{
if ( !FindInETagList( pUriCacheEntry->pETag,
pRequest->Headers[HttpHeaderIfMatch].pHeader,
FALSE) )
{
// Match failed.
goto PreconditionFailed;
}
}
//
// 2. Check If-None-Match
//
if ( pRequest->HeaderValid[HttpHeaderIfNoneMatch] )
{
if ( FindInETagList( pUriCacheEntry->pETag,
pRequest->Headers[HttpHeaderIfNoneMatch].pHeader,
TRUE) )
{
// ETag found on list.
fIfNoneMatchPassed = FALSE;
}
else
{
//
// Header present and ETag not found on list. This modifies
// the semantic of the If-Modified-Since header; Namely,
// If-None-Match takes precidence over If-Modified-Since.
//
fSkipIfModifiedSince = TRUE;
}
}
//
// 3. Check If-Modified-Since
//
if ( !fSkipIfModifiedSince &&
pRequest->HeaderValid[HttpHeaderIfModifiedSince] )
{
if ( StringTimeToSystemTime(
(const PSTR) pRequest->Headers[HttpHeaderIfModifiedSince].pHeader,
&liModifiedSince) )
{
//
// If the cache entry was created before the
// time specified in the If-Modified-Since header, we
// can return a 304 (Not Modified) status.
//
if ( pUriCacheEntry->CreationTime.QuadPart <= liModifiedSince.QuadPart )
{
//
// Check if the time specified in the request is
// greater than the current time (i.e., Invalid). If it is,
// ignore the If-Modified-Since header.
//
KeQuerySystemTime(&liNow);
if ( liModifiedSince.QuadPart < liNow.QuadPart )
{
// Valid time.
goto NotModified;
}
}
}
//
// If-Modified-Since overrides If-None-Match.
//
fIfNoneMatchPassed = TRUE;
}
if ( !fIfNoneMatchPassed )
{
//
// We could either skip the If-Modified-Since header, or it
// was not present, AND we did not pass the If-None-Match
// predicate. Since this is a "GET" or "HEAD" request (because
// that's all we cache, we should return 304. If this were
// any other verb, we should return 412.
//
ASSERT( (HttpVerbGET == pRequest->Verb) || (HttpVerbHEAD == pRequest->Verb) );
goto NotModified;
}
//
// 4. Check If-Unmodified-Since
//
if ( pRequest->HeaderValid[HttpHeaderIfUnmodifiedSince] )
{
if ( StringTimeToSystemTime(
(const PSTR) pRequest->Headers[HttpHeaderIfUnmodifiedSince].pHeader,
&liUnmodifiedSince) )
{
//
// If the cache entry was created after the time
// specified in the If-Unmodified-Since header, we
// MUST return a 412 (Precondition Failed) status.
//
if ( pUriCacheEntry->CreationTime.QuadPart > liUnmodifiedSince.QuadPart )
{
goto PreconditionFailed;
}
}
}
Cleanup:
return RetStatus;
NotModified:
RetStatus = 304;
//
// Send 304 (Not Modified) response
//
BytesSent =
UlSendSimpleStatus(
pRequest,
UlStatusNotModified
);
//
// Update the server to client bytes sent.
// The logging & perf counters will use it.
//
pRequest->BytesSent += BytesSent;
goto Cleanup;
PreconditionFailed:
RetStatus = 412;
goto Cleanup;
}
/***************************************************************************++
Routine Description:
Checks the cached response against the "Accept:" header in the request
to see if it can satisfy the requested Content-Type(s).
(Yes, I know this is really gross...I encourage anyone to find a better
way to parse this! --EricSten)
Arguments:
pRequest - The request to check.
pUriCacheEntry - The cache entry that might possibly match.
Returns:
TRUE At least one of the possible formats matched the Content-Type
of the cached entry.
FALSE None of the requested types matched the Content-Type of the
cached entry.
--***************************************************************************/
BOOLEAN
UlpIsAcceptHeaderOk(
PUL_INTERNAL_REQUEST pRequest,
PUL_URI_CACHE_ENTRY pUriCacheEntry
)
{
BOOLEAN bRet = TRUE;
ULONG Len;
PUCHAR pHdr;
PUCHAR pSubType;
PUCHAR pTmp;
PUL_CONTENT_TYPE pContentType;
if ( pRequest->HeaderValid[HttpHeaderAccept] &&
(pRequest->Headers[HttpHeaderAccept].HeaderLength > 0) )
{
Len = pRequest->Headers[HttpHeaderAccept].HeaderLength;
pHdr = pRequest->Headers[HttpHeaderAccept].pHeader;
pContentType = &pUriCacheEntry->ContentType;
//
// First, do "fast-path" check; see if "*/*" is anywhere in the header.
//
pTmp = (PUCHAR) strstr( (const char*) pHdr, "*/*" );
//
// If we found "*/*" and its either at the beginning of the line,
// the end of the line, or surrounded by either ' ' or ',', then
// it's really a wildcard.
//
if ((pTmp != NULL) &&
((pTmp == pHdr) ||
IS_HTTP_LWS(pTmp[-1]) ||
(pTmp[-1] == ',')) &&
((pTmp[3] == '\0') ||
IS_HTTP_LWS(pTmp[3]) ||
(pTmp[3] == ',')))
{
goto end;
}
//
// Wildcard not found; continue with slow path
//
while (Len)
{
if (pContentType->TypeLen > Len)
{
// Bad! No more string left...Bail out.
bRet = FALSE;
goto end;
}
if ( (pContentType->TypeLen == RtlCompareMemory(
pHdr,
pContentType->Type,
pContentType->TypeLen
)) &&
( '/' == pHdr[pContentType->TypeLen] ) )
{
//
// Found matching type; check subtype
//
pSubType = &pHdr[pContentType->TypeLen + 1];
if ( '*' == *pSubType )
{
// Subtype wildcard match!
goto end;
}
else
{
if ( pContentType->SubTypeLen >
(Len - ( pContentType->TypeLen + 1 )) )
{
// Bad! No more string left...Bail out.
bRet = FALSE;
goto end;
}
if ( pContentType->SubTypeLen == RtlCompareMemory(
pSubType,
pContentType->SubType,
pContentType->SubTypeLen
) &&
!IS_HTTP_TOKEN(pSubType[pContentType->SubTypeLen]) )
{
// Subtype exact match!
goto end;
}
}
}
//
// Didn't match this one; advance to next Content-Type in the Accept field
//
pTmp = (PUCHAR) strchr( (const char *) pHdr, ',' );
if (pTmp)
{
// Found a comma; step over it and any whitespace.
ASSERT ( Len > DIFF(pTmp - pHdr));
Len -= (DIFF(pTmp - pHdr) +1);
pHdr = (pTmp+1);
while( Len && IS_HTTP_LWS(*pHdr) )
{
pHdr++;
Len--;
}
} else
{
// No more content-types; bail.
bRet = FALSE;
goto end;
}
} // walk list of things
//
// Walked all Accept items and didn't find a match.
//
bRet = FALSE;
}
end:
return bRet;
} // UlpIsAcceptHeaderOk
/***************************************************************************++
Routine Description:
parses a content-type into its type and subtype components.
Arguments:
pStr String containing valid content type
StrLen Length of string (in bytes)
pContentType pointer to user provided UL_CONTENT_TYPE structure
--***************************************************************************/
VOID
UlpGetTypeAndSubType(
PSTR pStr,
ULONG StrLen,
PUL_CONTENT_TYPE pContentType
)
{
PCHAR pSlash;
ASSERT(pStr && StrLen);
ASSERT(pContentType);
pSlash = strchr(pStr, '/');
if (NULL == pSlash)
{
//
// BAD! content types should always have a slash!
//
ASSERT( NULL != pSlash );
return;
}
pContentType->TypeLen = (ULONG) MIN( (pSlash - pStr), MAX_TYPE_LENGTH );
RtlCopyMemory(
pContentType->Type,
pStr,
pContentType->TypeLen
);
ASSERT( StrLen > (pContentType->TypeLen + 1) );
pContentType->SubTypeLen = MIN( (StrLen - (pContentType->TypeLen + 1)), MAX_SUBTYPE_LENGTH );
RtlCopyMemory(
pContentType->SubType,
pSlash+1,
pContentType->SubTypeLen
);
} // UlpGetTypeAndSubType