mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1461 lines
36 KiB
1461 lines
36 KiB
/*++
|
|
|
|
Copyright (c) 2000-2001 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
fastio.cxx
|
|
|
|
Abstract:
|
|
|
|
This module implements the fast I/O logic of HTTP.SYS.
|
|
|
|
Author:
|
|
|
|
Chun Ye (chunye) 09-Dec-2000
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
#include "precomp.h"
|
|
|
|
|
|
//
|
|
// Private globals.
|
|
//
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text( PAGE, UlFastIoDeviceControl )
|
|
#pragma alloc_text( PAGE, UlSendHttpResponseFastIo )
|
|
#pragma alloc_text( PAGE, UlReceiveHttpRequestFastIo )
|
|
#pragma alloc_text( PAGE, UlpFastSendHttpResponse )
|
|
#pragma alloc_text( PAGE, UlpFastReceiveHttpRequest )
|
|
#pragma alloc_text( PAGE, UlpFastCopyHttpRequest )
|
|
#endif // ALLOC_PRAGMA
|
|
|
|
#if 0
|
|
NOT PAGEABLE -- UlpRestartFastSendHttpResponse
|
|
NOT PAGEABLE -- UlpFastSendCompleteWorker
|
|
#endif
|
|
|
|
|
|
FAST_IO_DISPATCH UlFastIoDispatch =
|
|
{
|
|
sizeof(FAST_IO_DISPATCH), // SizeOfFastIoDispatch
|
|
NULL, // FastIoCheckIfPossible
|
|
NULL, // FastIoRead
|
|
NULL, // FastIoWrite
|
|
NULL, // FastIoQueryBasicInfo
|
|
NULL, // FastIoQueryStandardInfo
|
|
NULL, // FastIoLock
|
|
NULL, // FastIoUnlockSingle
|
|
NULL, // FastIoUnlockAll
|
|
NULL, // FastIoUnlockAllByKey
|
|
UlFastIoDeviceControl // FastIoDeviceControl
|
|
};
|
|
|
|
|
|
//
|
|
// Public functions.
|
|
//
|
|
|
|
BOOLEAN
|
|
UlFastIoDeviceControl(
|
|
IN PFILE_OBJECT pFileObject,
|
|
IN BOOLEAN Wait,
|
|
IN PVOID pInputBuffer OPTIONAL,
|
|
IN ULONG InputBufferLength,
|
|
OUT PVOID pOutputBuffer OPTIONAL,
|
|
IN ULONG OutputBufferLength,
|
|
IN ULONG IoControlCode,
|
|
OUT PIO_STATUS_BLOCK pIoStatus,
|
|
IN PDEVICE_OBJECT pDeviceObject
|
|
)
|
|
{
|
|
if (IoControlCode == IOCTL_HTTP_SEND_HTTP_RESPONSE ||
|
|
IoControlCode == IOCTL_HTTP_SEND_ENTITY_BODY)
|
|
{
|
|
return UlSendHttpResponseFastIo(
|
|
pFileObject,
|
|
Wait,
|
|
pInputBuffer,
|
|
InputBufferLength,
|
|
pOutputBuffer,
|
|
OutputBufferLength,
|
|
pIoStatus,
|
|
pDeviceObject,
|
|
IoControlCode == IOCTL_HTTP_SEND_ENTITY_BODY? TRUE: FALSE
|
|
);
|
|
}
|
|
else
|
|
if (IoControlCode == IOCTL_HTTP_RECEIVE_HTTP_REQUEST)
|
|
{
|
|
return UlReceiveHttpRequestFastIo(
|
|
pFileObject,
|
|
Wait,
|
|
pInputBuffer,
|
|
InputBufferLength,
|
|
pOutputBuffer,
|
|
OutputBufferLength,
|
|
pIoStatus,
|
|
pDeviceObject
|
|
);
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
} // UlFastIoDeviceControl
|
|
|
|
|
|
BOOLEAN
|
|
UlSendHttpResponseFastIo(
|
|
IN PFILE_OBJECT pFileObject,
|
|
IN BOOLEAN Wait,
|
|
IN PVOID pInputBuffer,
|
|
IN ULONG InputBufferLength,
|
|
OUT PVOID pOutputBuffer,
|
|
IN ULONG OutputBufferLength,
|
|
OUT PIO_STATUS_BLOCK pIoStatus,
|
|
IN PDEVICE_OBJECT pDeviceObject,
|
|
IN BOOLEAN RawResponse
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PHTTP_SEND_HTTP_RESPONSE_INFO pSendInfo;
|
|
PUL_INTERNAL_REQUEST pRequest = NULL;
|
|
LONG BufferLength = 0;
|
|
ULONG BytesSent;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Initialize IoStatus in the failure case.
|
|
//
|
|
|
|
pIoStatus->Information = 0;
|
|
|
|
//
|
|
// Ensure this is really an app pool, not a control channel.
|
|
//
|
|
|
|
if (IS_APP_POOL(pFileObject) == FALSE)
|
|
{
|
|
//
|
|
// Not an app pool.
|
|
//
|
|
|
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Ensure the input buffer is large enough.
|
|
//
|
|
|
|
if (pInputBuffer == NULL || InputBufferLength < sizeof(*pSendInfo))
|
|
{
|
|
//
|
|
// Input buffer too small.
|
|
//
|
|
|
|
Status = STATUS_BUFFER_TOO_SMALL;
|
|
goto end;
|
|
}
|
|
|
|
pSendInfo = (PHTTP_SEND_HTTP_RESPONSE_INFO)pInputBuffer;
|
|
|
|
//
|
|
// Check if fast path can be taken for this response.
|
|
//
|
|
|
|
__try
|
|
{
|
|
ProbeTestForRead(
|
|
pSendInfo,
|
|
sizeof(*pSendInfo),
|
|
sizeof(PVOID)
|
|
);
|
|
|
|
//
|
|
// Fast path is only enabled if the response can be buffered.
|
|
//
|
|
|
|
if (RawResponse == FALSE &&
|
|
pSendInfo->CachePolicy.Policy != HttpCachePolicyNocache &&
|
|
g_UriCacheConfig.EnableCache)
|
|
{
|
|
//
|
|
// Take the slow path if we need to build a cache entry.
|
|
//
|
|
|
|
Status = STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
else
|
|
if (pSendInfo->EntityChunkCount == 0)
|
|
{
|
|
//
|
|
// Fast path should have no problem handling zero chunk responses.
|
|
// Validate the response type.
|
|
//
|
|
|
|
if (RawResponse)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PHTTP_DATA_CHUNK pEntityChunks = pSendInfo->pEntityChunks;
|
|
ULONG i;
|
|
|
|
//
|
|
// Make sure all chunks are from memory and their total size
|
|
// is <= g_UlMaxCopyThreshold. Take the slow path if this is
|
|
// not the case.
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
for (i = 0; i < pSendInfo->EntityChunkCount; i++)
|
|
{
|
|
ProbeTestForRead(
|
|
pEntityChunks,
|
|
sizeof(HTTP_DATA_CHUNK),
|
|
sizeof(PVOID)
|
|
);
|
|
|
|
if (pEntityChunks->DataChunkType != HttpDataChunkFromMemory)
|
|
{
|
|
Status = STATUS_NOT_SUPPORTED;
|
|
break;
|
|
}
|
|
|
|
BufferLength += pEntityChunks->FromMemory.BufferLength;
|
|
|
|
if (BufferLength > (LONG)g_UlMaxCopyThreshold)
|
|
{
|
|
Status = STATUS_NOT_SUPPORTED;
|
|
break;
|
|
}
|
|
|
|
pEntityChunks++;
|
|
}
|
|
}
|
|
}
|
|
__except( UL_EXCEPTION_FILTER() )
|
|
{
|
|
Status = UL_CONVERT_EXCEPTION_CODE( GetExceptionCode() );
|
|
}
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
// BUGBUG: should close connection
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// SendHttpResponse *must* take a PHTTP_RESPONSE. This will
|
|
// protect us from those whackos that attempt to build their own
|
|
// raw response headers. This is ok for SendEntityBody. The
|
|
// two cases are differentiated by the RawResponse flag.
|
|
//
|
|
|
|
if (RawResponse == FALSE && pSendInfo->pHttpResponse == NULL)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Now get the request from the request ID. This gives us a reference
|
|
// to the request.
|
|
//
|
|
|
|
pRequest = UlGetRequestFromId( pSendInfo->RequestId );
|
|
|
|
if (pRequest == NULL)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pRequest ) );
|
|
ASSERT( UL_IS_VALID_HTTP_CONNECTION( pRequest->pHttpConn ) );
|
|
|
|
//
|
|
// Check if we have exceeded maximum SendBufferedBytes limit.
|
|
//
|
|
|
|
if ((BufferLength + pRequest->pHttpConn->SendBufferedBytes) >
|
|
(LONG)g_UlMaxSendBufferedBytes)
|
|
{
|
|
Status = STATUS_ALLOTTED_SPACE_EXCEEDED;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Check if a response has already been sent on this request. We can test
|
|
// this without acquiring the request resource, since the flag is only
|
|
// set (never reset).
|
|
//
|
|
|
|
if (RawResponse == FALSE)
|
|
{
|
|
//
|
|
// Make sure only one response header goes back.
|
|
//
|
|
|
|
if (1 == InterlockedCompareExchange(
|
|
(PLONG)&pRequest->SentResponse,
|
|
1,
|
|
0
|
|
))
|
|
{
|
|
Status = STATUS_INVALID_DEVICE_STATE;
|
|
goto end;
|
|
}
|
|
}
|
|
else
|
|
if (pRequest->SentResponse == 0)
|
|
{
|
|
//
|
|
// Ensure a response has already been sent. If the application is
|
|
// sending entity without first having sent a response header, check
|
|
// the HTTP_SEND_RESPONSE_FLAG_RAW_HEADER flag.
|
|
//
|
|
|
|
if ((pSendInfo->Flags & HTTP_SEND_RESPONSE_FLAG_RAW_HEADER))
|
|
{
|
|
//
|
|
// Make sure only one response header goes back.
|
|
//
|
|
|
|
if (1 == InterlockedCompareExchange(
|
|
(PLONG)&pRequest->SentResponse,
|
|
1,
|
|
0
|
|
))
|
|
{
|
|
Status = STATUS_INVALID_DEVICE_STATE;
|
|
goto end;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_INVALID_DEVICE_STATE;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Also ensure that all previous calls to SendHttpResponse
|
|
// and SendEntityBody had the MORE_DATA flag set.
|
|
//
|
|
|
|
if ((pSendInfo->Flags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
|
|
{
|
|
//
|
|
// Set if we have sent the last response, but bail out if the flag
|
|
// is already set since there can be only one last response.
|
|
//
|
|
|
|
if (1 == InterlockedCompareExchange(
|
|
(PLONG)&pRequest->SentLast,
|
|
1,
|
|
0
|
|
))
|
|
{
|
|
Status = STATUS_INVALID_DEVICE_STATE;
|
|
goto end;
|
|
}
|
|
}
|
|
else
|
|
if (pRequest->SentLast == 1)
|
|
{
|
|
Status = STATUS_INVALID_DEVICE_STATE;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// OK, we have the connection. Now capture the incoming HTTP_RESPONSE
|
|
// structure, map it to our internal format and send the response.
|
|
//
|
|
|
|
Status = UlpFastSendHttpResponse(
|
|
pSendInfo->pHttpResponse,
|
|
pSendInfo->pLogData,
|
|
pSendInfo->pEntityChunks,
|
|
pSendInfo->EntityChunkCount,
|
|
pSendInfo->Flags,
|
|
pRequest,
|
|
NULL,
|
|
&BytesSent
|
|
);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Record the number of bytes we have sent successfully.
|
|
//
|
|
|
|
pIoStatus->Information = BytesSent;
|
|
}
|
|
|
|
end:
|
|
|
|
if (pRequest != NULL)
|
|
{
|
|
UL_DEREFERENCE_INTERNAL_REQUEST( pRequest );
|
|
}
|
|
|
|
//
|
|
// Complete the fast I/O.
|
|
//
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// If we took the fast path, always return success even if completion
|
|
// routine returns failure later.
|
|
//
|
|
|
|
pIoStatus->Status = STATUS_SUCCESS;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
pIoStatus->Status = Status;
|
|
return FALSE;
|
|
}
|
|
} // UlSendHttpResponseFastIo
|
|
|
|
|
|
BOOLEAN
|
|
UlReceiveHttpRequestFastIo(
|
|
IN PFILE_OBJECT pFileObject,
|
|
IN BOOLEAN Wait,
|
|
IN PVOID pInputBuffer,
|
|
IN ULONG InputBufferLength,
|
|
OUT PVOID pOutputBuffer,
|
|
IN ULONG OutputBufferLength,
|
|
OUT PIO_STATUS_BLOCK pIoStatus,
|
|
IN PDEVICE_OBJECT pDeviceObject
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PHTTP_RECEIVE_REQUEST_INFO pRequestInfo;
|
|
ULONG BytesRead;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Ensure this is really an app pool, not a control channel.
|
|
//
|
|
|
|
if (IS_APP_POOL(pFileObject) || NULL != pInputBuffer)
|
|
{
|
|
if (NULL != pOutputBuffer &&
|
|
OutputBufferLength >= sizeof(HTTP_REQUEST) &&
|
|
InputBufferLength == sizeof(HTTP_RECEIVE_REQUEST_INFO))
|
|
{
|
|
pRequestInfo = (PHTTP_RECEIVE_REQUEST_INFO) pInputBuffer;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
__try
|
|
{
|
|
ProbeTestForRead(
|
|
pRequestInfo,
|
|
sizeof(*pRequestInfo),
|
|
sizeof(PVOID)
|
|
);
|
|
}
|
|
__except( UL_EXCEPTION_FILTER() )
|
|
{
|
|
Status = UL_CONVERT_EXCEPTION_CODE( GetExceptionCode() );
|
|
}
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = UlpFastReceiveHttpRequest(
|
|
pRequestInfo->RequestId,
|
|
GET_APP_POOL_PROCESS(pFileObject),
|
|
pOutputBuffer,
|
|
OutputBufferLength,
|
|
&BytesRead
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
|
|
//
|
|
// Complete the fast I/O.
|
|
//
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
pIoStatus->Status = STATUS_SUCCESS;
|
|
pIoStatus->Information = BytesRead;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
pIoStatus->Status = Status;
|
|
pIoStatus->Information = 0;
|
|
return FALSE;
|
|
}
|
|
} // UlReceiveHttpRequestFastIo
|
|
|
|
|
|
//
|
|
// Private functions.
|
|
//
|
|
|
|
NTSTATUS
|
|
UlpFastSendHttpResponse(
|
|
IN PHTTP_RESPONSE pUserResponse OPTIONAL,
|
|
IN PHTTP_LOG_FIELDS_DATA pUserLogData OPTIONAL,
|
|
IN PHTTP_DATA_CHUNK pUserEntityDataChunk,
|
|
IN ULONG ChunkCount,
|
|
IN ULONG Flags,
|
|
IN PUL_INTERNAL_REQUEST pRequest,
|
|
IN PIRP pUserIrp OPTIONAL,
|
|
OUT PULONG BytesSent
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUCHAR pResponseBuffer;
|
|
ULONG ResponseBufferLength;
|
|
ULONG HeaderLength;
|
|
ULONG FixedHeaderLength;
|
|
ULONG VarHeaderLength;
|
|
ULONG ContentLength = 0;
|
|
ULONG TotalResponseSize;
|
|
ULONG ContentLengthStringLength;
|
|
CHAR ContentLengthString[MAX_ULONGLONG_STR];
|
|
PUL_FULL_TRACKER pTracker = NULL;
|
|
PUL_HTTP_CONNECTION pHttpConnection = NULL;
|
|
PUL_CONNECTION pConnection;
|
|
CCHAR SendIrpStackSize;
|
|
BOOLEAN Disconnect;
|
|
UL_CONN_HDR ConnHeader;
|
|
LARGE_INTEGER TimeSent;
|
|
BOOLEAN LastResponse;
|
|
PMDL pSendMdl;
|
|
ULONG i;
|
|
KIRQL OldIrql;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
__try
|
|
{
|
|
//
|
|
// Setup locals so we know how to cleanup on exit.
|
|
//
|
|
|
|
Status = UlProbeHttpHeaders( pUserResponse );
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
// BUGBUG: 389145: should close the connection
|
|
goto end;
|
|
}
|
|
|
|
for (i = 0; i < ChunkCount; i++)
|
|
{
|
|
ContentLength += pUserEntityDataChunk[i].FromMemory.BufferLength;
|
|
}
|
|
|
|
ASSERT( pUserIrp != NULL || ContentLength <= g_UlMaxCopyThreshold );
|
|
ASSERT( pUserIrp == NULL || ContentLength <= MAX_BYTES_BUFFERED );
|
|
|
|
//
|
|
// Allocate a fast tracker to send the response.
|
|
//
|
|
|
|
pConnection = pRequest->pHttpConn->pConnection;
|
|
SendIrpStackSize = pConnection->ConnectionObject.pDeviceObject->StackSize;
|
|
LastResponse = (0 == (Flags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA));
|
|
|
|
if (LastResponse && SendIrpStackSize <= DEFAULT_MAX_IRP_STACK_SIZE)
|
|
{
|
|
pTracker = pRequest->pTracker;
|
|
}
|
|
else
|
|
{
|
|
pTracker = UlpAllocateFastTracker( 0, SendIrpStackSize );
|
|
}
|
|
|
|
if (pTracker == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Try to generate the fixed header within the default fixed header
|
|
// buffer first. If this fails, take the normal path.
|
|
//
|
|
|
|
pResponseBuffer = pTracker->pAuxiliaryBuffer;
|
|
|
|
if (pUserIrp != NULL)
|
|
{
|
|
ResponseBufferLength =
|
|
g_UlMaxFixedHeaderSize + g_UlMaxCopyThreshold;
|
|
}
|
|
else
|
|
{
|
|
ResponseBufferLength =
|
|
g_UlMaxFixedHeaderSize + g_UlMaxCopyThreshold - ContentLength;
|
|
}
|
|
|
|
if (pUserResponse != NULL)
|
|
{
|
|
Status = UlGenerateFixedHeaders(
|
|
pRequest->Version,
|
|
pUserResponse,
|
|
ResponseBufferLength,
|
|
pResponseBuffer,
|
|
&FixedHeaderLength
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
// Either the buffer was too small or an exception was thrown.
|
|
if (Status != STATUS_INSUFFICIENT_RESOURCES)
|
|
goto end;
|
|
|
|
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
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Calculate the fixed header size.
|
|
//
|
|
|
|
Status = UlComputeFixedHeaderSize(
|
|
pRequest->Version,
|
|
pUserResponse,
|
|
&HeaderLength
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
|
|
ASSERT( HeaderLength > ResponseBufferLength );
|
|
|
|
pTracker = UlpAllocateFastTracker(
|
|
HeaderLength,
|
|
SendIrpStackSize
|
|
);
|
|
|
|
if (pTracker == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
pResponseBuffer = pTracker->pAuxiliaryBuffer;
|
|
|
|
Status = UlGenerateFixedHeaders(
|
|
pRequest->Version,
|
|
pUserResponse,
|
|
HeaderLength,
|
|
pResponseBuffer,
|
|
&FixedHeaderLength
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
|
|
ASSERT( HeaderLength == FixedHeaderLength );
|
|
}
|
|
|
|
pResponseBuffer += FixedHeaderLength;
|
|
}
|
|
else
|
|
{
|
|
FixedHeaderLength = 0;
|
|
}
|
|
|
|
//
|
|
// Take a reference of HTTP connection for the tracker.
|
|
//
|
|
|
|
pHttpConnection = pRequest->pHttpConn;
|
|
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pHttpConnection));
|
|
|
|
UL_REFERENCE_HTTP_CONNECTION( pHttpConnection );
|
|
|
|
//
|
|
// Take a reference of the request too because logging needs it.
|
|
//
|
|
|
|
UL_REFERENCE_INTERNAL_REQUEST( pRequest );
|
|
|
|
//
|
|
// Initialization.
|
|
//
|
|
|
|
pTracker->Signature = UL_FULL_TRACKER_POOL_TAG;
|
|
pTracker->pRequest = pRequest;
|
|
pTracker->pHttpConnection = pHttpConnection;
|
|
pTracker->Flags = Flags;
|
|
pTracker->pLogData = NULL;
|
|
pTracker->pUserIrp = pUserIrp;
|
|
pTracker->SendBufferedBytes = 0;
|
|
|
|
//
|
|
// Capture the log data.
|
|
//
|
|
|
|
if (NULL != pUserLogData &&
|
|
LastResponse &&
|
|
pRequest->ConfigInfo.pLoggingConfig)
|
|
{
|
|
Status = UlProbeLogData( pUserLogData );
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
pTracker->pLogData = &pRequest->LogData;
|
|
|
|
Status = UlAllocateLogDataBuffer(
|
|
pTracker->pLogData,
|
|
pRequest,
|
|
pRequest->ConfigInfo.pLoggingConfig
|
|
);
|
|
ASSERT( NT_SUCCESS( Status ) );
|
|
|
|
Status = UlCaptureLogFields(
|
|
pUserLogData,
|
|
pRequest->Version,
|
|
pTracker->pLogData
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Generate the variable header.
|
|
//
|
|
|
|
if (FixedHeaderLength > 0)
|
|
{
|
|
//
|
|
// Should we close the connection?
|
|
//
|
|
|
|
Disconnect = FALSE;
|
|
|
|
if ((Flags & HTTP_SEND_RESPONSE_FLAG_DISCONNECT) != 0)
|
|
{
|
|
//
|
|
// Caller is forcing a disconnect.
|
|
//
|
|
|
|
Disconnect = TRUE;
|
|
}
|
|
else
|
|
if (LastResponse)
|
|
{
|
|
//
|
|
// No more data is coming, should we disconnect?
|
|
//
|
|
|
|
if (UlCheckDisconnectInfo(pRequest))
|
|
{
|
|
Disconnect = TRUE;
|
|
pTracker->Flags |= HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Choose the connection header to send back.
|
|
//
|
|
|
|
ConnHeader = UlChooseConnectionHeader( pRequest->Version, Disconnect );
|
|
|
|
//
|
|
// Decide if we need to generate a content-length header.
|
|
//
|
|
|
|
if (IS_LENGTH_SPECIFIED(pUserResponse->Headers.pKnownHeaders) == FALSE &&
|
|
IS_CHUNK_SPECIFIED(pUserResponse->Headers.pKnownHeaders) == FALSE &&
|
|
UlNeedToGenerateContentLength(
|
|
pRequest->Verb,
|
|
pUserResponse->StatusCode,
|
|
pTracker->Flags
|
|
))
|
|
{
|
|
//
|
|
// Autogenerate a content-length header. We can use our own fast
|
|
// generator here because we know the content-length is <= 64k.
|
|
//
|
|
|
|
ContentLengthStringLength = UlpFastGenerateContentLength(
|
|
ContentLengthString,
|
|
ContentLength
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ContentLengthString[0] = ANSI_NULL;
|
|
ContentLengthStringLength = 0;
|
|
}
|
|
|
|
//
|
|
// Now generate the variable header.
|
|
//
|
|
|
|
UlGenerateVariableHeaders(
|
|
ConnHeader,
|
|
(PUCHAR)ContentLengthString,
|
|
ContentLengthStringLength,
|
|
pResponseBuffer,
|
|
&VarHeaderLength,
|
|
&TimeSent
|
|
);
|
|
|
|
ASSERT( VarHeaderLength <= g_UlMaxVariableHeaderSize );
|
|
|
|
pResponseBuffer += VarHeaderLength;
|
|
}
|
|
else
|
|
{
|
|
VarHeaderLength = 0;
|
|
}
|
|
|
|
pTracker->pMdlAuxiliary->ByteCount
|
|
= FixedHeaderLength + VarHeaderLength;
|
|
|
|
//
|
|
// If this routine is called from the fast I/O path, copy the content
|
|
// to the auxilary buffer inside the tracker and set up the send MDL.
|
|
// Otherwise, we need to MmProbeAndLock the user buffer into another
|
|
// separate MDL.
|
|
//
|
|
|
|
if (pUserIrp == NULL)
|
|
{
|
|
PUCHAR pAuxiliaryBuffer = pResponseBuffer;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
__try
|
|
{
|
|
for (i = 0; i < ChunkCount; i++)
|
|
{
|
|
if (pUserEntityDataChunk[i].FromMemory.BufferLength == 0 ||
|
|
pUserEntityDataChunk[i].FromMemory.pBuffer == NULL)
|
|
{
|
|
ExRaiseStatus( STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
ProbeTestForRead(
|
|
pUserEntityDataChunk[i].FromMemory.pBuffer,
|
|
pUserEntityDataChunk[i].FromMemory.BufferLength,
|
|
sizeof(CHAR)
|
|
);
|
|
|
|
RtlCopyMemory(
|
|
pAuxiliaryBuffer,
|
|
pUserEntityDataChunk[i].FromMemory.pBuffer,
|
|
pUserEntityDataChunk[i].FromMemory.BufferLength
|
|
);
|
|
|
|
pAuxiliaryBuffer
|
|
+= pUserEntityDataChunk[i].FromMemory.BufferLength;
|
|
}
|
|
}
|
|
__except( UL_EXCEPTION_FILTER() )
|
|
{
|
|
Status = UL_CONVERT_EXCEPTION_CODE( GetExceptionCode() );
|
|
}
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
pTracker->pMdlAuxiliary->ByteCount += ContentLength;
|
|
pTracker->pMdlAuxiliary->Next = NULL;
|
|
}
|
|
else
|
|
{
|
|
ASSERT( ChunkCount == 1 );
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
__try
|
|
{
|
|
if (pUserEntityDataChunk->FromMemory.BufferLength == 0 ||
|
|
pUserEntityDataChunk->FromMemory.pBuffer == NULL)
|
|
{
|
|
ExRaiseStatus( STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
ProbeTestForRead(
|
|
pUserEntityDataChunk->FromMemory.pBuffer,
|
|
pUserEntityDataChunk->FromMemory.BufferLength,
|
|
sizeof(CHAR)
|
|
);
|
|
|
|
Status = UlpInitializeAndLockMdl(
|
|
pTracker->pMdlUserBuffer,
|
|
pUserEntityDataChunk->FromMemory.pBuffer,
|
|
ContentLength,
|
|
IoReadAccess
|
|
);
|
|
}
|
|
__except( UL_EXCEPTION_FILTER() )
|
|
{
|
|
Status = UL_CONVERT_EXCEPTION_CODE( GetExceptionCode() );
|
|
}
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
pTracker->pMdlAuxiliary->Next = pTracker->pMdlUserBuffer;
|
|
}
|
|
|
|
TotalResponseSize = FixedHeaderLength + VarHeaderLength + ContentLength;
|
|
|
|
//
|
|
// Fail the zero length response which can be generated in the case
|
|
// where the request is 0.9 and there is no body attached to the
|
|
// response. We have to take this code path to possibly force a
|
|
// disconnect here because the slow path may not go far enough to the
|
|
// disconnect logic since we may have flags such as SentLast or
|
|
// SentResponse set.
|
|
//
|
|
|
|
if (TotalResponseSize == 0)
|
|
{
|
|
if (IS_DISCONNECT_TIME(pTracker))
|
|
{
|
|
UlCloseConnection(
|
|
pTracker->pHttpConnection->pConnection,
|
|
FALSE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Add to MinKBSec watch list, since we now know TotalResponseSize.
|
|
//
|
|
|
|
UlSetMinKBSecTimer(
|
|
&pHttpConnection->TimeoutInfo,
|
|
TotalResponseSize
|
|
);
|
|
|
|
//
|
|
// Mark the IRP as pending before the send as we are guaranteed to
|
|
// return pending from this point on.
|
|
//
|
|
|
|
if (pUserIrp != NULL)
|
|
{
|
|
IoMarkIrpPending( pUserIrp );
|
|
}
|
|
|
|
//
|
|
// Skip the zero length MDL if created one.
|
|
//
|
|
|
|
pSendMdl = pTracker->pMdlAuxiliary;
|
|
|
|
if (pSendMdl->ByteCount == 0)
|
|
{
|
|
pSendMdl = pSendMdl->Next;
|
|
}
|
|
|
|
ASSERT( pSendMdl != NULL );
|
|
ASSERT( pSendMdl->ByteCount != 0 );
|
|
|
|
//
|
|
// Adjust SendBufferedBytes for the fast I/O path only.
|
|
//
|
|
|
|
if (pUserIrp == NULL)
|
|
{
|
|
pTracker->SendBufferedBytes = ContentLength;
|
|
|
|
InterlockedExchangeAdd(
|
|
&pHttpConnection->SendBufferedBytes,
|
|
ContentLength
|
|
);
|
|
}
|
|
|
|
//
|
|
// Send the response. Notice the logic to disconnect the connection is
|
|
// different from sending back a disconnect header.
|
|
//
|
|
|
|
Status = UlSendData(
|
|
pHttpConnection->pConnection,
|
|
pSendMdl,
|
|
TotalResponseSize,
|
|
&UlpRestartFastSendHttpResponse,
|
|
pTracker,
|
|
pTracker->pSendIrp,
|
|
&pTracker->IrpContext,
|
|
IS_DISCONNECT_TIME(pTracker)
|
|
);
|
|
|
|
ASSERT( Status == STATUS_PENDING );
|
|
|
|
if (BytesSent != NULL)
|
|
{
|
|
*BytesSent = TotalResponseSize;
|
|
}
|
|
|
|
return STATUS_PENDING;
|
|
}
|
|
__except( UL_EXCEPTION_FILTER() )
|
|
{
|
|
Status = UL_CONVERT_EXCEPTION_CODE( GetExceptionCode() );
|
|
}
|
|
|
|
|
|
end:
|
|
|
|
//
|
|
// Cleanup.
|
|
//
|
|
|
|
if (pTracker)
|
|
{
|
|
UlpFreeFastTracker( pTracker );
|
|
|
|
//
|
|
// Let the references go.
|
|
//
|
|
|
|
if (pHttpConnection != NULL)
|
|
{
|
|
UL_DEREFERENCE_HTTP_CONNECTION( pHttpConnection );
|
|
UL_DEREFERENCE_INTERNAL_REQUEST( pRequest );
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
} // UlpFastSendHttpResponse
|
|
|
|
|
|
VOID
|
|
UlpRestartFastSendHttpResponse(
|
|
IN PVOID pCompletionContext,
|
|
IN NTSTATUS Status,
|
|
IN ULONG_PTR Information
|
|
)
|
|
{
|
|
PUL_FULL_TRACKER pTracker;
|
|
PIRP pIrp;
|
|
|
|
pTracker = (PUL_FULL_TRACKER)pCompletionContext;
|
|
|
|
ASSERT( IS_VALID_FULL_TRACKER( pTracker ) );
|
|
|
|
//
|
|
// Set status and bytes transferred fields as returned.
|
|
//
|
|
|
|
pTracker->IoStatus.Status = Status;
|
|
pTracker->IoStatus.Information = Information;
|
|
|
|
if (NT_SUCCESS(Status) == FALSE &&
|
|
IS_DISCONNECT_TIME(pTracker) == FALSE)
|
|
{
|
|
//
|
|
// Disconnect if there was an error and we didn't disconnect already.
|
|
//
|
|
|
|
UlCloseConnection(
|
|
pTracker->pHttpConnection->pConnection,
|
|
TRUE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
//
|
|
// Complete the orignal user send IRP if set.
|
|
//
|
|
|
|
pIrp = pTracker->pUserIrp;
|
|
|
|
if (pIrp != NULL)
|
|
{
|
|
//
|
|
// Don't forget to unlock the user buffer.
|
|
//
|
|
|
|
ASSERT( pTracker->pMdlAuxiliary->Next != NULL );
|
|
ASSERT( pTracker->pMdlAuxiliary->Next == pTracker->pMdlUserBuffer );
|
|
|
|
MmUnlockPages( pTracker->pMdlUserBuffer );
|
|
|
|
pIrp->IoStatus.Status = Status;
|
|
pIrp->IoStatus.Information = Information;
|
|
|
|
UlCompleteRequest( pIrp, g_UlPriorityBoost );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Adjust SendBufferedBytes.
|
|
//
|
|
|
|
InterlockedExchangeAdd(
|
|
&pTracker->pHttpConnection->SendBufferedBytes,
|
|
- pTracker->SendBufferedBytes
|
|
);
|
|
}
|
|
|
|
UL_QUEUE_WORK_ITEM(
|
|
&pTracker->WorkItem,
|
|
&UlpFastSendCompleteWorker
|
|
);
|
|
} // UlpRestartFastSendHttpResponse
|
|
|
|
|
|
VOID
|
|
UlpFastSendCompleteWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
PUL_FULL_TRACKER pTracker;
|
|
PUL_HTTP_CONNECTION pHttpConnection;
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
NTSTATUS Status;
|
|
KIRQL OldIrql;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pTracker = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_FULL_TRACKER,
|
|
WorkItem
|
|
);
|
|
|
|
ASSERT( IS_VALID_FULL_TRACKER( pTracker ) );
|
|
|
|
Status = pTracker->IoStatus.Status;
|
|
|
|
pHttpConnection = pTracker->pHttpConnection;
|
|
ASSERT( UL_IS_VALID_HTTP_CONNECTION( pHttpConnection ) );
|
|
|
|
pRequest = pTracker->pRequest;
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pRequest ) );
|
|
|
|
//
|
|
// Update the BytesSent counter in the request.
|
|
//
|
|
|
|
UlInterlockedAdd64(
|
|
(PLONGLONG)&pRequest->BytesSent,
|
|
pTracker->IoStatus.Information
|
|
);
|
|
|
|
if ((pTracker->Flags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
|
|
{
|
|
//
|
|
// Stop the MinKBSec timer and start Idle timer
|
|
//
|
|
|
|
UlLockTimeoutInfo(
|
|
&pHttpConnection->TimeoutInfo,
|
|
&OldIrql
|
|
);
|
|
|
|
UlResetConnectionTimer(
|
|
&pHttpConnection->TimeoutInfo,
|
|
TimerMinKBSec
|
|
);
|
|
|
|
UlSetConnectionTimer(
|
|
&pHttpConnection->TimeoutInfo,
|
|
TimerConnectionIdle
|
|
);
|
|
|
|
UlUnlockTimeoutInfo(
|
|
&pHttpConnection->TimeoutInfo,
|
|
OldIrql
|
|
);
|
|
|
|
UlEvaluateTimerState(
|
|
&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 (NULL != pTracker->pLogData)
|
|
{
|
|
UlLogHttpHit( pTracker->pLogData );
|
|
}
|
|
|
|
//
|
|
// Kick the parser on the connection and release our hold.
|
|
//
|
|
|
|
if (Status == STATUS_SUCCESS &&
|
|
(pTracker->Flags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0 &&
|
|
(pTracker->Flags & HTTP_SEND_RESPONSE_FLAG_DISCONNECT) == 0)
|
|
{
|
|
UlResumeParsing( pHttpConnection );
|
|
}
|
|
|
|
//
|
|
// Cleanup.
|
|
//
|
|
|
|
UlpFreeFastTracker( pTracker );
|
|
|
|
UL_DEREFERENCE_HTTP_CONNECTION( pHttpConnection );
|
|
UL_DEREFERENCE_INTERNAL_REQUEST( pRequest );
|
|
} // UlpFastSendCompleteWorker
|
|
|
|
|
|
NTSTATUS
|
|
UlpFastReceiveHttpRequest(
|
|
IN HTTP_REQUEST_ID RequestId,
|
|
IN PUL_APP_POOL_PROCESS pProcess,
|
|
IN PVOID pOutputBuffer,
|
|
IN ULONG OutputBufferLength,
|
|
OUT PULONG pBytesRead
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_INTERNAL_REQUEST pRequest = NULL;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( IS_VALID_AP_PROCESS(pProcess) );
|
|
ASSERT( IS_VALID_AP_OBJECT(pProcess->pAppPool) );
|
|
ASSERT( pOutputBuffer != NULL);
|
|
|
|
UlAcquireResourceShared( &pProcess->pAppPool->pResource->Resource, TRUE );
|
|
|
|
//
|
|
// Make sure we're not cleaning up the process.
|
|
//
|
|
|
|
if (!pProcess->InCleanup)
|
|
{
|
|
//
|
|
// Obtain the request based on the request ID. This can be from the
|
|
// NewRequestQueue of the AppPool if the ID is NULL, or directly
|
|
// from the matching opaque ID entry.
|
|
//
|
|
|
|
if (HTTP_IS_NULL_ID(&RequestId))
|
|
{
|
|
pRequest = UlpDequeueNewRequest( pProcess );
|
|
}
|
|
else
|
|
{
|
|
pRequest = UlGetRequestFromId( RequestId );
|
|
|
|
if (NULL != pRequest)
|
|
{
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST(pRequest) );
|
|
|
|
//
|
|
// Weed out the request in bad state.
|
|
//
|
|
|
|
if (pRequest->AppPool.QueueState != QueueCopiedState ||
|
|
pRequest->AppPool.pProcess != pProcess)
|
|
{
|
|
UL_DEREFERENCE_INTERNAL_REQUEST( pRequest );
|
|
pRequest = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Let go the lock since we have taken a short-lived reference of
|
|
// the request in the success case.
|
|
//
|
|
|
|
UlReleaseResource( &pProcess->pAppPool->pResource->Resource );
|
|
|
|
//
|
|
// Return immediately if no request is found and let the slow path
|
|
// handle this.
|
|
//
|
|
|
|
if (NULL == pRequest)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Copy it to the output buffer.
|
|
//
|
|
|
|
Status = UlpFastCopyHttpRequest(
|
|
pRequest,
|
|
pOutputBuffer,
|
|
OutputBufferLength,
|
|
pBytesRead
|
|
);
|
|
|
|
//
|
|
// Let go our reference.
|
|
//
|
|
|
|
UL_DEREFERENCE_INTERNAL_REQUEST( pRequest );
|
|
|
|
return Status;
|
|
} // UlpFastReceiveHttpRequest
|
|
|
|
|
|
NTSTATUS
|
|
UlpFastCopyHttpRequest(
|
|
IN PUL_INTERNAL_REQUEST pRequest,
|
|
IN PVOID pOutputBuffer,
|
|
IN ULONG OutputBufferLength,
|
|
OUT PULONG pBytesRead
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG BytesNeeded;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST(pRequest) );
|
|
ASSERT( pOutputBuffer != NULL);
|
|
|
|
//
|
|
// Make sure this is big enough to handle the request, and
|
|
// if so copy it in.
|
|
//
|
|
|
|
Status = UlpComputeRequestBytesNeeded( pRequest, &BytesNeeded );
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Make sure we've got enough space to handle the whole request.
|
|
//
|
|
|
|
if (BytesNeeded <= OutputBufferLength)
|
|
{
|
|
//
|
|
// This request will fit in this buffer, so copy it.
|
|
//
|
|
|
|
__try
|
|
{
|
|
ProbeForWrite(
|
|
pOutputBuffer,
|
|
OutputBufferLength,
|
|
TYPE_ALIGNMENT(HTTP_REQUEST)
|
|
);
|
|
|
|
Status = UlpCopyRequestToBuffer(
|
|
pRequest,
|
|
(PUCHAR) pOutputBuffer,
|
|
pOutputBuffer,
|
|
OutputBufferLength,
|
|
NULL,
|
|
0
|
|
);
|
|
}
|
|
__except( UL_EXCEPTION_FILTER() )
|
|
{
|
|
Status = UL_CONVERT_EXCEPTION_CODE( GetExceptionCode() );
|
|
}
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
*pBytesRead = BytesNeeded;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Let the slow path handle this.
|
|
//
|
|
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
} // UlpFastCopyHttpRequest
|
|
|