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.
3307 lines
108 KiB
3307 lines
108 KiB
/*++
|
|
|
|
Copyright (c) 2000-2002 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
ucrcv.c
|
|
|
|
Abstract:
|
|
|
|
Contains the code that parses incoming HTTP responses. We have
|
|
to handle the following cases.
|
|
|
|
- Pipelined responses (an indication has more than one response).
|
|
- Batched TDI receives (response split across TDI indications).
|
|
- App does not have sufficent buffer space to hold the parsed
|
|
response.
|
|
- Differnet types of encoding.
|
|
- Efficient parsing.
|
|
|
|
We try to minimize the number of buffer copies. In the best case,
|
|
we parse directly into the app's buffer. The best case is achieved
|
|
when the app has passed output buffer in the HttpSendRequest() call.
|
|
|
|
|
|
Also contains all of the per-header handling code for received headers.
|
|
|
|
Author:
|
|
|
|
Rajesh Sundaram (rajeshsu), 25th Aug 2000
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
#include "ucparse.h"
|
|
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
|
|
//
|
|
// None of these routines are PAGEABLE since we are parsing at DPC.
|
|
//
|
|
|
|
#pragma alloc_text( PAGEUC, UcpGetResponseBuffer)
|
|
#pragma alloc_text( PAGEUC, UcpMergeIndications)
|
|
#pragma alloc_text( PAGEUC, UcpParseHttpResponse)
|
|
#pragma alloc_text( PAGEUC, UcHandleResponse)
|
|
#pragma alloc_text( PAGEUC, UcpHandleConnectVerbFailure)
|
|
#pragma alloc_text( PAGEUC, UcpHandleParsedRequest)
|
|
#pragma alloc_text( PAGEUC, UcpCarveDataChunk)
|
|
#pragma alloc_text( PAGEUC, UcpCopyEntityToDataChunk )
|
|
#pragma alloc_text( PAGEUC, UcpReferenceForReceive )
|
|
#pragma alloc_text( PAGEUC, UcpDereferenceForReceive )
|
|
#pragma alloc_text( PAGEUC, UcpCopyHttpResponseHeaders )
|
|
#pragma alloc_text( PAGEUC, UcpExpandResponseBuffer )
|
|
#pragma alloc_text( PAGEUC, UcpCompleteReceiveResponseIrp )
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//
|
|
// Private Functions.
|
|
//
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine carves a HTTP_DATA_CHUNK in the HTTP_RESPONSE structure and
|
|
adjusts all the pointers in the UC_HTTP_REQUEST structure.
|
|
|
|
|
|
Arguments:
|
|
|
|
pResponse - The HTTP_RESPONSE
|
|
pRequest - The internal HTTP request.
|
|
pIndication - pointer to buffer
|
|
BytesIndicated - buffer length to be written.
|
|
AlignLength - The align'd length for pointer manipulations.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - Successfully copied.
|
|
STATUS_INTEGER_OVERFLOW - Entity chunk overflow
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
_inline
|
|
UcpCarveDataChunk(
|
|
IN PHTTP_RESPONSE pResponse,
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
IN PUCHAR pIndication,
|
|
IN ULONG BytesIndicated,
|
|
IN ULONG AlignLength
|
|
)
|
|
{
|
|
USHORT j;
|
|
PUCHAR pBuffer;
|
|
|
|
ASSERT(AlignLength == ALIGN_UP(BytesIndicated, PVOID));
|
|
|
|
j = pResponse->EntityChunkCount;
|
|
|
|
if((pResponse->EntityChunkCount + 1) < j)
|
|
{
|
|
return STATUS_INTEGER_OVERFLOW;
|
|
}
|
|
|
|
pResponse->EntityChunkCount++;
|
|
|
|
pBuffer = pRequest->CurrentBuffer.pOutBufferTail - AlignLength;
|
|
|
|
pResponse->pEntityChunks[j].FromMemory.BufferLength = BytesIndicated;
|
|
|
|
pResponse->pEntityChunks[j].FromMemory.pBuffer = pBuffer;
|
|
|
|
RtlCopyMemory(
|
|
pBuffer,
|
|
pIndication,
|
|
BytesIndicated
|
|
);
|
|
|
|
pRequest->CurrentBuffer.pOutBufferHead += sizeof(HTTP_DATA_CHUNK);
|
|
|
|
pRequest->CurrentBuffer.pOutBufferTail -= AlignLength;
|
|
|
|
pRequest->CurrentBuffer.BytesAvailable -= (sizeof(HTTP_DATA_CHUNK) +
|
|
AlignLength);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine carves a HTTP_DATA_CHUNK in the HTTP_RESPONSE structure and
|
|
adjusts all the pointers in the UC_HTTP_REQUEST structure.
|
|
|
|
Arguments:
|
|
|
|
pRequest - The internal HTTP request.
|
|
BytesToTake - Bytes we want to consume.
|
|
BytesIndicated - Total no of bytes indicated by TDI.
|
|
pIndication - pointer to buffer
|
|
pBytesTaken - Bytes we consumed.
|
|
|
|
Return Value:
|
|
|
|
UcDataChunkCopyAll : We copied of BytesToTake into a HTTP_DATA_CHUNK
|
|
UcDataChunkCopyPartial: We copied some of BytesToTake into HTTP_DATA_CHUNK
|
|
|
|
--***************************************************************************/
|
|
UC_DATACHUNK_RETURN
|
|
UcpCopyEntityToDataChunk(
|
|
IN PHTTP_RESPONSE pResponse,
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
IN ULONG BytesToTake,
|
|
IN ULONG BytesIndicated,
|
|
IN PUCHAR pIndication,
|
|
OUT PULONG pBytesTaken
|
|
)
|
|
{
|
|
ULONG AlignLength;
|
|
|
|
*pBytesTaken = 0;
|
|
|
|
if(BytesToTake == 0)
|
|
{
|
|
// What's the point in creating a 0 length chunk?
|
|
//
|
|
return UcDataChunkCopyAll;
|
|
}
|
|
|
|
if(BytesToTake > BytesIndicated)
|
|
{
|
|
// We don't want to exceed the amount that is indicated by TDI.
|
|
//
|
|
BytesToTake = BytesIndicated;
|
|
}
|
|
|
|
AlignLength = ALIGN_UP(BytesToTake, PVOID);
|
|
|
|
if(pRequest->CurrentBuffer.BytesAvailable >=
|
|
AlignLength + sizeof(HTTP_DATA_CHUNK))
|
|
{
|
|
// There is enough out buffer space to consume the indicated data
|
|
//
|
|
if(UcpCarveDataChunk(
|
|
pResponse,
|
|
pRequest,
|
|
pIndication,
|
|
BytesToTake,
|
|
AlignLength
|
|
) == STATUS_SUCCESS)
|
|
{
|
|
*pBytesTaken += BytesToTake;
|
|
|
|
return UcDataChunkCopyAll;
|
|
}
|
|
}
|
|
else if(pRequest->CurrentBuffer.BytesAvailable > sizeof(HTTP_DATA_CHUNK))
|
|
{
|
|
ULONG Size = pRequest->CurrentBuffer.BytesAvailable -
|
|
sizeof(HTTP_DATA_CHUNK);
|
|
|
|
AlignLength = ALIGN_DOWN(Size, PVOID);
|
|
|
|
if(0 != AlignLength)
|
|
{
|
|
if(UcpCarveDataChunk(pResponse,
|
|
pRequest,
|
|
pIndication,
|
|
AlignLength,
|
|
AlignLength
|
|
) == STATUS_SUCCESS)
|
|
{
|
|
*pBytesTaken += AlignLength;
|
|
}
|
|
}
|
|
}
|
|
|
|
return UcDataChunkCopyPartial;
|
|
}
|
|
|
|
|
|
/**************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called when we have a parsed response buffer that can be
|
|
copied to a Receive Response IRP.
|
|
|
|
Arguments:
|
|
|
|
pRequest - The Request
|
|
OldIrql - The IRQL at which the connection spin lock was acquired.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--**************************************************************************/
|
|
VOID
|
|
UcpCompleteReceiveResponseIrp(
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
IN KIRQL OldIrql
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PIRP pIrp;
|
|
PIO_STACK_LOCATION pIrpSp;
|
|
ULONG OutBufferLen;
|
|
ULONG BytesTaken;
|
|
PUC_HTTP_RECEIVE_RESPONSE pHttpResponse;
|
|
LIST_ENTRY TmpIrpList;
|
|
PLIST_ENTRY pListEntry;
|
|
PUC_RESPONSE_BUFFER pTmpBuffer;
|
|
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
ASSERT(UC_IS_VALID_HTTP_REQUEST(pRequest));
|
|
ASSERT(UlDbgSpinLockOwned(&pRequest->pConnection->SpinLock));
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
pIrp = NULL;
|
|
pIrpSp = NULL;
|
|
pHttpResponse = NULL;
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
|
|
//
|
|
// Initially no App's IRP to complete.
|
|
//
|
|
|
|
InitializeListHead(&TmpIrpList);
|
|
|
|
//
|
|
// If there is a Receive Response IRP waiting, try to complete it now.
|
|
//
|
|
|
|
Retry:
|
|
if (!IsListEmpty(&pRequest->ReceiveResponseIrpList))
|
|
{
|
|
pListEntry = RemoveHeadList(&pRequest->ReceiveResponseIrpList);
|
|
|
|
pHttpResponse = CONTAINING_RECORD(pListEntry,
|
|
UC_HTTP_RECEIVE_RESPONSE,
|
|
Linkage);
|
|
|
|
if (UcRemoveRcvRespCancelRoutine(pHttpResponse))
|
|
{
|
|
//
|
|
// This IRP has already got cancelled, let's move on
|
|
//
|
|
InitializeListHead(&pHttpResponse->Linkage);
|
|
goto Retry;
|
|
}
|
|
|
|
pIrp = pHttpResponse->pIrp;
|
|
pIrpSp = IoGetCurrentIrpStackLocation( pIrp );
|
|
OutBufferLen = pIrpSp->Parameters.DeviceIoControl.OutputBufferLength;
|
|
|
|
//
|
|
// Find parsed response buffers to copy to this IRP's buffer
|
|
//
|
|
|
|
Status = UcFindBuffersForReceiveResponseIrp(
|
|
pRequest,
|
|
OutBufferLen,
|
|
TRUE,
|
|
&pHttpResponse->ResponseBufferList,
|
|
&BytesTaken);
|
|
|
|
switch(Status)
|
|
{
|
|
case STATUS_INVALID_PARAMETER:
|
|
case STATUS_PENDING:
|
|
//
|
|
// There must be at least one buffer available for copying
|
|
//
|
|
ASSERT(FALSE);
|
|
break;
|
|
|
|
case STATUS_BUFFER_TOO_SMALL:
|
|
//
|
|
// This IRP is too small to hold the parsed response.
|
|
//
|
|
pIrp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
|
|
|
|
//
|
|
// Note that since this is async completion, the IO mgr
|
|
// will make the Information field available to the app.
|
|
//
|
|
pIrp->IoStatus.Information = BytesTaken;
|
|
|
|
InsertTailList(&TmpIrpList, &pHttpResponse->Linkage);
|
|
|
|
goto Retry;
|
|
|
|
case STATUS_SUCCESS:
|
|
//
|
|
// We got buffers to copy...
|
|
//
|
|
break;
|
|
|
|
default:
|
|
ASSERT(FALSE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Do the dirty work outside the spinlock.
|
|
//
|
|
|
|
UlReleaseSpinLock(&pRequest->pConnection->SpinLock, OldIrql);
|
|
|
|
if (Status == STATUS_SUCCESS)
|
|
{
|
|
//
|
|
// We found an IPR and parsed response buffers to copy to the IRP.
|
|
// Copy the parsed response buffers and complete the IRP
|
|
//
|
|
BOOLEAN bDone;
|
|
|
|
//
|
|
// Copy parsed response buffers to the IRP
|
|
//
|
|
|
|
Status = UcCopyResponseToIrp(pIrp,
|
|
&pHttpResponse->ResponseBufferList,
|
|
&bDone,
|
|
&BytesTaken);
|
|
|
|
//
|
|
// The request must not be done right now!
|
|
//
|
|
ASSERT(bDone == FALSE);
|
|
|
|
pIrp->IoStatus.Status = Status;
|
|
pIrp->IoStatus.Information = BytesTaken;
|
|
|
|
//
|
|
// Queue the IRP for completion
|
|
//
|
|
InsertTailList(&TmpIrpList, &pHttpResponse->Linkage);
|
|
|
|
//
|
|
// Free parsed response buffers
|
|
//
|
|
while (!IsListEmpty(&pHttpResponse->ResponseBufferList))
|
|
{
|
|
pListEntry = RemoveHeadList(&pHttpResponse->ResponseBufferList);
|
|
|
|
pTmpBuffer = CONTAINING_RECORD(pListEntry,
|
|
UC_RESPONSE_BUFFER,
|
|
Linkage);
|
|
|
|
ASSERT(IS_VALID_UC_RESPONSE_BUFFER(pTmpBuffer));
|
|
|
|
UL_FREE_POOL_WITH_QUOTA(pTmpBuffer,
|
|
UC_RESPONSE_APP_BUFFER_POOL_TAG,
|
|
NonPagedPool,
|
|
pTmpBuffer->BytesAllocated,
|
|
pRequest->pServerInfo->pProcess);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Complete Receive Response IRP's
|
|
//
|
|
while(!IsListEmpty(&TmpIrpList))
|
|
{
|
|
pListEntry = RemoveHeadList(&TmpIrpList);
|
|
|
|
pHttpResponse = CONTAINING_RECORD(pListEntry,
|
|
UC_HTTP_RECEIVE_RESPONSE,
|
|
Linkage);
|
|
|
|
UlCompleteRequest(pHttpResponse->pIrp, IO_NETWORK_INCREMENT);
|
|
|
|
UL_FREE_POOL_WITH_QUOTA(pHttpResponse,
|
|
UC_HTTP_RECEIVE_RESPONSE_POOL_TAG,
|
|
NonPagedPool,
|
|
sizeof(UC_HTTP_RECEIVE_RESPONSE),
|
|
pRequest->pServerInfo->pProcess);
|
|
|
|
UC_DEREFERENCE_REQUEST(pRequest);
|
|
}
|
|
}
|
|
|
|
|
|
/**************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine copies one response buffer to a new (must be bigger)
|
|
response buffer.
|
|
|
|
We make sure that all response headers are stored in a single response
|
|
buffer. When a buffer is found to be too small to contain all the
|
|
headers, a new bigger buffer is allocated. This routine is then called
|
|
to copy old buffer into the new buffer. Bufer layout:
|
|
|
|
HTTP_RESPONSE Reason Unknown Header Known Header Unknown Header
|
|
| String array Values Name/Values
|
|
| | | | |
|
|
V V V V V
|
|
+---------------------------------------------------------------------+
|
|
| | | |\\\\\\\| | |
|
|
+---------------------------------------------------------------------+
|
|
^ ^
|
|
Head Tail
|
|
|
|
Arguments:
|
|
|
|
pNewResponse - New response buffer
|
|
pOldResponse - Old response buffer
|
|
ppBufferHead - Pointer to pointer to top of the free buffer space
|
|
ppBufferTail - Pointer to pointer to bottom of the free buffer space
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--**************************************************************************/
|
|
VOID
|
|
UcpCopyHttpResponseHeaders(
|
|
PHTTP_RESPONSE pNewResponse,
|
|
PHTTP_RESPONSE pOldResponse,
|
|
PUCHAR *ppBufferHead,
|
|
PUCHAR *ppBufferTail
|
|
)
|
|
{
|
|
USHORT i;
|
|
PUCHAR pBufferHead, pBufferTail;
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
ASSERT(pNewResponse);
|
|
ASSERT(pOldResponse);
|
|
ASSERT(ppBufferHead && *ppBufferHead);
|
|
ASSERT(ppBufferTail && *ppBufferTail);
|
|
|
|
//
|
|
// Initialize locals
|
|
//
|
|
pBufferHead = *ppBufferHead;
|
|
pBufferTail = *ppBufferTail;
|
|
|
|
//
|
|
// First, copy the HTTP_RESPONSE structure.
|
|
//
|
|
RtlCopyMemory(pNewResponse, pOldResponse, sizeof(HTTP_RESPONSE));
|
|
|
|
//
|
|
// Then, copy the Reason string, if any
|
|
//
|
|
if (pNewResponse->ReasonLength)
|
|
{
|
|
pNewResponse->pReason = (PCSTR)pBufferHead;
|
|
|
|
RtlCopyMemory((PUCHAR)pNewResponse->pReason,
|
|
(PUCHAR)pOldResponse->pReason,
|
|
pNewResponse->ReasonLength);
|
|
|
|
pBufferHead += pNewResponse->ReasonLength;
|
|
}
|
|
|
|
//
|
|
// Copy unknown headers, if any
|
|
//
|
|
|
|
pBufferHead = ALIGN_UP_POINTER(pBufferHead, PVOID);
|
|
|
|
pNewResponse->Headers.pUnknownHeaders = (PHTTP_UNKNOWN_HEADER)pBufferHead;
|
|
|
|
if (pNewResponse->Headers.UnknownHeaderCount)
|
|
{
|
|
pBufferHead = (PUCHAR)((PHTTP_UNKNOWN_HEADER)pBufferHead +
|
|
pNewResponse->Headers.UnknownHeaderCount);
|
|
|
|
for (i = 0; i < pNewResponse->Headers.UnknownHeaderCount; i++)
|
|
{
|
|
ASSERT(pOldResponse->Headers.pUnknownHeaders[i].pName);
|
|
ASSERT(pOldResponse->Headers.pUnknownHeaders[i].NameLength);
|
|
ASSERT(pOldResponse->Headers.pUnknownHeaders[i].pRawValue);
|
|
ASSERT(pOldResponse->Headers.pUnknownHeaders[i].RawValueLength);
|
|
|
|
//
|
|
// Copy HTTP_UNKNOWN_HEADER structure
|
|
//
|
|
RtlCopyMemory(&pNewResponse->Headers.pUnknownHeaders[i],
|
|
&pOldResponse->Headers.pUnknownHeaders[i],
|
|
sizeof(HTTP_UNKNOWN_HEADER));
|
|
|
|
//
|
|
// Make space for unknown header name
|
|
//
|
|
pBufferTail -= pNewResponse->Headers.pUnknownHeaders[i].NameLength;
|
|
|
|
pNewResponse->Headers.pUnknownHeaders[i].pName =(PCSTR)pBufferTail;
|
|
|
|
//
|
|
// Copy unknown header name
|
|
//
|
|
RtlCopyMemory(
|
|
(PUCHAR)pNewResponse->Headers.pUnknownHeaders[i].pName,
|
|
(PUCHAR)pOldResponse->Headers.pUnknownHeaders[i].pName,
|
|
pNewResponse->Headers.pUnknownHeaders[i].NameLength);
|
|
|
|
//
|
|
// Make space for unknown header value
|
|
//
|
|
pBufferTail -=
|
|
pNewResponse->Headers.pUnknownHeaders[i].RawValueLength;
|
|
|
|
pNewResponse->Headers.pUnknownHeaders[i].pRawValue =
|
|
(PCSTR)pBufferTail;
|
|
|
|
//
|
|
// Copy unknow header value
|
|
//
|
|
RtlCopyMemory(
|
|
(PUCHAR)pNewResponse->Headers.pUnknownHeaders[i].pRawValue,
|
|
(PUCHAR)pOldResponse->Headers.pUnknownHeaders[i].pRawValue,
|
|
pNewResponse->Headers.pUnknownHeaders[i].RawValueLength);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy known headers.
|
|
//
|
|
|
|
for (i = 0; i < HttpHeaderResponseMaximum; i++)
|
|
{
|
|
if (pNewResponse->Headers.KnownHeaders[i].RawValueLength)
|
|
{
|
|
//
|
|
// Make space for known header value
|
|
//
|
|
pBufferTail -=pNewResponse->Headers.KnownHeaders[i].RawValueLength;
|
|
|
|
pNewResponse->Headers.KnownHeaders[i].pRawValue =
|
|
(PCSTR)pBufferTail;
|
|
|
|
//
|
|
// Copy known header value
|
|
//
|
|
RtlCopyMemory(
|
|
(PUCHAR)pNewResponse->Headers.KnownHeaders[i].pRawValue,
|
|
(PUCHAR)pOldResponse->Headers.KnownHeaders[i].pRawValue,
|
|
pNewResponse->Headers.KnownHeaders[i].RawValueLength);
|
|
}
|
|
}
|
|
|
|
//
|
|
// There should not be any entities
|
|
//
|
|
ASSERT(pNewResponse->EntityChunkCount == 0);
|
|
ASSERT(pNewResponse->pEntityChunks == NULL);
|
|
|
|
//
|
|
// Return head and tail pointers
|
|
//
|
|
|
|
*ppBufferHead = pBufferHead;
|
|
*ppBufferTail = pBufferTail;
|
|
}
|
|
|
|
|
|
/**************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine allocates a new UC_RESPONSE_BUFFER and copies the current
|
|
UC_RESPONSE_BUFFER to this new buffer. This routine is called when
|
|
we run out of buffer space while parsing response headers. Since all
|
|
the headers must be present into a single buffer, a new buffer is
|
|
allocated.
|
|
|
|
Arguments:
|
|
|
|
pRequest - The Request
|
|
BytesIndicated - The number of bytes indicated by TDI.
|
|
pResponseBufferFlags - Flags the new buffer must have.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--**************************************************************************/
|
|
NTSTATUS
|
|
UcpExpandResponseBuffer(
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
IN ULONG BytesIndicated,
|
|
IN ULONG ResponseBufferFlags
|
|
)
|
|
{
|
|
PUC_RESPONSE_BUFFER pInternalBuffer;
|
|
ULONG BytesToAllocate;
|
|
ULONGLONG TmpLength;
|
|
PUCHAR pBufferHead, pBufferTail;
|
|
PUCHAR pTmpHead, pTmpTail;
|
|
PUC_CLIENT_CONNECTION pConnection;
|
|
KIRQL OldIrql;
|
|
PIRP pIrp;
|
|
|
|
|
|
//
|
|
// Client connection
|
|
//
|
|
pConnection = pRequest->pConnection;
|
|
ASSERT(UC_IS_VALID_CLIENT_CONNECTION(pConnection));
|
|
|
|
//
|
|
// Allocate a new 2 times bigger buffer that can contain all
|
|
// response headers. (hopefully!)
|
|
//
|
|
|
|
//
|
|
// pRequest->CurrentBuffer.BytesAllocated contains the buffer length
|
|
// in both case: case 1, we're using App's buffer OR
|
|
// case 2, we're using Driver allocated buffer.
|
|
//
|
|
TmpLength = 2 * (pRequest->CurrentBuffer.BytesAllocated + BytesIndicated)
|
|
+ sizeof(UC_RESPONSE_BUFFER);
|
|
|
|
//
|
|
// Align up. Is there ALIGN_UP for ULONGLONG?
|
|
//
|
|
TmpLength = (TmpLength+sizeof(PVOID)-1) & (~((ULONGLONG)sizeof(PVOID)-1));
|
|
|
|
BytesToAllocate = (ULONG)TmpLength;
|
|
|
|
//
|
|
// Check for Arithmetic Overflow.
|
|
//
|
|
|
|
if (TmpLength == BytesToAllocate)
|
|
{
|
|
//
|
|
// No arithmetic overflow. Try allocating memory.
|
|
//
|
|
|
|
pInternalBuffer = (PUC_RESPONSE_BUFFER)
|
|
UL_ALLOCATE_POOL_WITH_QUOTA(
|
|
NonPagedPool,
|
|
BytesToAllocate,
|
|
UC_RESPONSE_APP_BUFFER_POOL_TAG,
|
|
pRequest->pServerInfo->pProcess);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// There was an Overflow in the above computation of TmpLength.
|
|
// We can't handle more than 4GB of headers.
|
|
//
|
|
pInternalBuffer = NULL;
|
|
}
|
|
|
|
if (pInternalBuffer == NULL)
|
|
{
|
|
//
|
|
// Either there was an arithmetic overflow or memory allocation
|
|
// failed. In both cases, return error.
|
|
//
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// Initialize pInternalBuffer
|
|
//
|
|
|
|
RtlZeroMemory(pInternalBuffer, sizeof(UC_RESPONSE_BUFFER));
|
|
pInternalBuffer->Signature = UC_RESPONSE_BUFFER_SIGNATURE;
|
|
pInternalBuffer->BytesAllocated = BytesToAllocate;
|
|
|
|
//
|
|
// Header buffer is not mergeable with previous buffers.
|
|
//
|
|
pInternalBuffer->Flags = ResponseBufferFlags;
|
|
pInternalBuffer->Flags |= UC_RESPONSE_BUFFER_FLAG_NOT_MERGEABLE;
|
|
|
|
//
|
|
// Copy old HTTP_RESPONSE to new buffer's HTTP_RESPONSE.
|
|
//
|
|
|
|
pTmpHead = pBufferHead = (PUCHAR)(pInternalBuffer + 1);
|
|
pTmpTail = pBufferTail = (PUCHAR)(pInternalBuffer) + BytesToAllocate;
|
|
|
|
//
|
|
// If we are using App's buffer, the InternalResponse must be used
|
|
// while copying. Otherwise we use the original buffer's Response
|
|
// strucuture. In any case, the CurrentBuffer.pResponse is already
|
|
// init'ed to point to "the right" place.
|
|
//
|
|
ASSERT(pRequest->CurrentBuffer.pResponse != NULL);
|
|
|
|
UcpCopyHttpResponseHeaders(&pInternalBuffer->HttpResponse,
|
|
pRequest->CurrentBuffer.pResponse,
|
|
&pBufferHead,
|
|
&pBufferTail);
|
|
|
|
ASSERT(pTmpHead <= pBufferHead);
|
|
ASSERT(pTmpTail >= pBufferTail);
|
|
ASSERT(pBufferHead <= pBufferTail);
|
|
|
|
//
|
|
// Set up the current buffer structure...
|
|
//
|
|
|
|
//
|
|
// BytesAllocated is sizeof(HTTP_RESPONSE) + Data buffer length
|
|
// It is used to determine how much space is needed to copy
|
|
// this parsed response buffer.
|
|
//
|
|
pRequest->CurrentBuffer.BytesAllocated = BytesToAllocate -
|
|
(sizeof(UC_RESPONSE_BUFFER) - sizeof(HTTP_RESPONSE));
|
|
|
|
//
|
|
// Update bytes allocated
|
|
//
|
|
pRequest->CurrentBuffer.BytesAvailable =
|
|
pRequest->CurrentBuffer.BytesAllocated - sizeof(HTTP_RESPONSE)
|
|
- (ULONG)(pBufferHead - pTmpHead)
|
|
- (ULONG)(pTmpTail - pBufferTail);
|
|
|
|
pRequest->CurrentBuffer.pResponse = &pInternalBuffer->HttpResponse;
|
|
|
|
pRequest->CurrentBuffer.pOutBufferHead = pBufferHead;
|
|
pRequest->CurrentBuffer.pOutBufferTail = pBufferTail;
|
|
|
|
if (pRequest->CurrentBuffer.pCurrentBuffer)
|
|
{
|
|
//
|
|
// Old buffer was a driver allocated buffer. Free it now.
|
|
//
|
|
|
|
PLIST_ENTRY pEntry;
|
|
PUC_RESPONSE_BUFFER pOldResponseBuffer;
|
|
|
|
//
|
|
// Remove old buffer and insert new one.
|
|
//
|
|
|
|
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
|
|
|
|
pOldResponseBuffer = pRequest->CurrentBuffer.pCurrentBuffer;
|
|
|
|
pEntry = RemoveHeadList(&pRequest->pBufferList);
|
|
|
|
ASSERT(pEntry == &pRequest->CurrentBuffer.pCurrentBuffer->Linkage);
|
|
|
|
InsertHeadList(&pRequest->pBufferList, &pInternalBuffer->Linkage);
|
|
|
|
pRequest->CurrentBuffer.pCurrentBuffer = pInternalBuffer;
|
|
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
|
|
UL_FREE_POOL_WITH_QUOTA(pOldResponseBuffer,
|
|
UC_RESPONSE_APP_BUFFER_POOL_TAG,
|
|
NonPagedPool,
|
|
pOldResponseBuffer->BytesAllocated,
|
|
pConnection->pServerInfo->pProcess);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// App's buffer...complete App's IRP
|
|
//
|
|
|
|
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
|
|
|
|
//
|
|
// Set the current buffer pointer.
|
|
//
|
|
|
|
pRequest->CurrentBuffer.pCurrentBuffer = pInternalBuffer;
|
|
|
|
//
|
|
// Insert the buffer in the list.
|
|
//
|
|
|
|
ASSERT(IsListEmpty(&pRequest->pBufferList));
|
|
|
|
InsertHeadList(&pRequest->pBufferList, &pInternalBuffer->Linkage);
|
|
|
|
//
|
|
// Reference the request for the buffer just added.
|
|
//
|
|
|
|
UC_REFERENCE_REQUEST(pRequest);
|
|
|
|
if(pRequest->CurrentBuffer.pResponse &&
|
|
!((pRequest->ResponseStatusCode == 401 ||
|
|
pRequest->ResponseStatusCode == 407) &&
|
|
pRequest->Renegotiate == TRUE &&
|
|
pRequest->DontFreeMdls == TRUE &&
|
|
pRequest->RequestStatus == STATUS_SUCCESS
|
|
)
|
|
&& pRequest->RequestState == UcRequestStateSendCompletePartialData
|
|
)
|
|
{
|
|
//
|
|
// Complete App's IRP with error
|
|
//
|
|
|
|
pIrp = UcPrepareRequestIrp(pRequest, STATUS_BUFFER_TOO_SMALL);
|
|
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
|
|
if(pIrp)
|
|
{
|
|
pIrp->IoStatus.Information = BytesToAllocate;
|
|
UlCompleteRequest(pIrp, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used by the HTTP response parser to get buffer space to
|
|
hold the parsed response.
|
|
|
|
|
|
Arguments:
|
|
|
|
pRequest - The internal HTTP request.
|
|
BytesIndicated - The number of bytes indicated by TDI. We will buffer for
|
|
at least this amount
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UcpGetResponseBuffer(
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
IN ULONG BytesIndicated,
|
|
IN ULONG ResponseBufferFlags
|
|
)
|
|
{
|
|
PUC_RESPONSE_BUFFER pInternalBuffer;
|
|
PUC_CLIENT_CONNECTION pConnection = pRequest->pConnection;
|
|
ULONG BytesToAllocate;
|
|
ULONG AlignLength;
|
|
KIRQL OldIrql;
|
|
PIRP pIrp;
|
|
|
|
|
|
//
|
|
// All the headers must go into one buffer. If the buffer ran out
|
|
// of space when parsing headers, a buffer is allocated and old contents
|
|
// are copied to the new buffer. If the old buffer came from App, the
|
|
// App's request is failed with STATUS_BUFFER_TOO_SMALL.
|
|
//
|
|
|
|
switch(pRequest->ParseState)
|
|
{
|
|
case UcParseStatusLineVersion:
|
|
case UcParseStatusLineStatusCode:
|
|
//
|
|
// If we are allocating buffer in this state, then the buffers should
|
|
// not be mergeable.
|
|
//
|
|
ResponseBufferFlags |= UC_RESPONSE_BUFFER_FLAG_NOT_MERGEABLE;
|
|
break;
|
|
|
|
case UcParseStatusLineReasonPhrase:
|
|
case UcParseHeaders:
|
|
//
|
|
// If the app did not pass any buffers and this the first time
|
|
// buffers are allocated for this response, goto the normal path.
|
|
//
|
|
if (pRequest->CurrentBuffer.BytesAllocated == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
return UcpExpandResponseBuffer(pRequest,
|
|
BytesIndicated,
|
|
ResponseBufferFlags);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// If we are in this routine, we have run out of buffer space. This
|
|
// allows us to complete the app's IRP
|
|
//
|
|
|
|
if(!pRequest->CurrentBuffer.pCurrentBuffer)
|
|
{
|
|
|
|
if(pRequest->CurrentBuffer.pResponse &&
|
|
!((pRequest->ResponseStatusCode == 401 ||
|
|
pRequest->ResponseStatusCode == 407) &&
|
|
pRequest->Renegotiate == TRUE &&
|
|
pRequest->DontFreeMdls == TRUE &&
|
|
pRequest->RequestStatus == STATUS_SUCCESS
|
|
)
|
|
)
|
|
{
|
|
pRequest->CurrentBuffer.pResponse->Flags |=
|
|
HTTP_RESPONSE_FLAG_MORE_DATA;
|
|
|
|
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
|
|
|
|
pRequest->RequestIRPBytesWritten =
|
|
pRequest->CurrentBuffer.BytesAllocated -
|
|
pRequest->CurrentBuffer.BytesAvailable;
|
|
|
|
if(pRequest->RequestState ==
|
|
UcRequestStateSendCompletePartialData)
|
|
{
|
|
//
|
|
// We have been called in the send complete and we own
|
|
// the IRP completion.
|
|
//
|
|
pIrp = UcPrepareRequestIrp(pRequest, STATUS_SUCCESS);
|
|
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
|
|
if(pIrp)
|
|
{
|
|
UlCompleteRequest(pIrp, IO_NETWORK_INCREMENT);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// App did not post any buffers, this IRP should have been
|
|
// completed by the SendComplete handler.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UlAcquireSpinLock(&pRequest->pConnection->SpinLock, &OldIrql);
|
|
|
|
pRequest->CurrentBuffer.pCurrentBuffer->Flags |=
|
|
UC_RESPONSE_BUFFER_FLAG_READY;
|
|
|
|
pRequest->CurrentBuffer.pCurrentBuffer->BytesWritten =
|
|
pRequest->CurrentBuffer.BytesAllocated -
|
|
pRequest->CurrentBuffer.BytesAvailable;
|
|
|
|
pRequest->CurrentBuffer.pResponse->Flags |=
|
|
HTTP_RESPONSE_FLAG_MORE_DATA;
|
|
|
|
//
|
|
// Complete App's receive response Irp, if any.
|
|
//
|
|
UcpCompleteReceiveResponseIrp(pRequest, OldIrql);
|
|
}
|
|
|
|
//
|
|
// UC_BUGBUG (PERF): want to use fixed size pools ?
|
|
//
|
|
|
|
//
|
|
// TDI has indicated BytesIndicated bytes of data. We have to allocate
|
|
// a buffer space for holding all of this data. Now, some of these might
|
|
// be unknown headers or entity bodies. Since we parse unknown headers
|
|
// and entity bodies as variable length arrays, we need some array space.
|
|
// Since we have not parsed these unknown headers or entity bodies as yet,
|
|
// we have to make a guess on the count of these and hope that we allocate
|
|
// sufficient space. If our guess does not work, we'll land up calling
|
|
// this routine again.
|
|
//
|
|
|
|
AlignLength = ALIGN_UP(BytesIndicated, PVOID);
|
|
BytesToAllocate = AlignLength +
|
|
sizeof(UC_RESPONSE_BUFFER) +
|
|
UC_RESPONSE_EXTRA_BUFFER;
|
|
|
|
pInternalBuffer = (PUC_RESPONSE_BUFFER)
|
|
UL_ALLOCATE_POOL_WITH_QUOTA(
|
|
NonPagedPool,
|
|
BytesToAllocate,
|
|
UC_RESPONSE_APP_BUFFER_POOL_TAG,
|
|
pRequest->pServerInfo->pProcess
|
|
);
|
|
|
|
if(!pInternalBuffer)
|
|
{
|
|
UlTrace(PARSER,
|
|
("[UcpGetResponseBuffer]: Could not allocate memory for "
|
|
"buffering \n"));
|
|
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlZeroMemory(pInternalBuffer, BytesToAllocate);
|
|
|
|
//
|
|
// Set up the internal request structure so that we have nice pointers.
|
|
//
|
|
|
|
pInternalBuffer->Signature = UC_RESPONSE_BUFFER_SIGNATURE;
|
|
|
|
pInternalBuffer->BytesAllocated = BytesToAllocate;
|
|
|
|
pInternalBuffer->Flags |= ResponseBufferFlags;
|
|
|
|
pRequest->CurrentBuffer.BytesAllocated = AlignLength +
|
|
UC_RESPONSE_EXTRA_BUFFER +
|
|
sizeof(HTTP_RESPONSE);
|
|
|
|
pRequest->CurrentBuffer.BytesAvailable =
|
|
pRequest->CurrentBuffer.BytesAllocated - sizeof(HTTP_RESPONSE);
|
|
|
|
pRequest->CurrentBuffer.pResponse = &pInternalBuffer->HttpResponse;
|
|
|
|
pRequest->CurrentBuffer.pOutBufferHead =
|
|
(PUCHAR) ((PUCHAR)pInternalBuffer + sizeof(UC_RESPONSE_BUFFER));
|
|
|
|
pRequest->CurrentBuffer.pOutBufferTail =
|
|
pRequest->CurrentBuffer.pOutBufferHead +
|
|
AlignLength +
|
|
UC_RESPONSE_EXTRA_BUFFER;
|
|
|
|
pRequest->CurrentBuffer.pCurrentBuffer = pInternalBuffer;
|
|
|
|
//
|
|
// depending on where we are, setup the response pointers.
|
|
//
|
|
|
|
switch(pRequest->ParseState)
|
|
{
|
|
case UcParseEntityBody:
|
|
case UcParseEntityBodyMultipartFinal:
|
|
|
|
pRequest->CurrentBuffer.pResponse->pEntityChunks =
|
|
(PHTTP_DATA_CHUNK)pRequest->CurrentBuffer.pOutBufferHead;
|
|
break;
|
|
|
|
case UcParseHeaders:
|
|
case UcParseEntityBodyMultipartInit:
|
|
case UcParseEntityBodyMultipartHeaders:
|
|
pRequest->CurrentBuffer.pResponse->Headers.pUnknownHeaders =
|
|
(PHTTP_UNKNOWN_HEADER) pRequest->CurrentBuffer.pOutBufferHead;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
|
|
|
|
InsertTailList(
|
|
&pRequest->pBufferList,
|
|
&pInternalBuffer->Linkage
|
|
);
|
|
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
|
|
|
|
//
|
|
// Take a ref on the request for this buffer
|
|
//
|
|
UC_REFERENCE_REQUEST(pRequest);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used by the HTTP response parser to merge a TDI indication
|
|
with a previously buffered one.
|
|
|
|
There could be cases when a TDI indication does not carry the entire
|
|
data required to parse the HTTP response. For e.g. if we were parsing
|
|
headers and the TDI indication ended with "Accept-", we would not know
|
|
which header to parse the indication to.
|
|
|
|
In such cases, we buffer the un-parsed portion of the response (in this
|
|
case it would be "Accept-"), and merge the subsequent indication from TDI
|
|
with the buffered indication, and process it as one chunk.
|
|
|
|
Now, in order to minimize the buffering overhead, we don't merge all of the
|
|
new indication with the old one. We just assume that the data can be parsed
|
|
using the next 256 bytes, and copy that amount into the prior indication.
|
|
If this was not sufficient, we'll just copy more data.
|
|
|
|
We'll then treat these as two (or one) seperate indications.
|
|
|
|
Arguments:
|
|
|
|
pConnection - A pointer to UC_CLIENT_CONNECTION
|
|
pIndication - A pointer to the (new) TDI indication.
|
|
BytesIndicated - The number of bytes in the new indication.
|
|
Indication - An output array that carries the seperated indications
|
|
IndicationLength - An output array that stores the length of the
|
|
indications.
|
|
IndicationCount - The # of seperated indications.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UcpMergeIndications(
|
|
IN PUC_CLIENT_CONNECTION pConnection,
|
|
IN PUCHAR pIndication,
|
|
IN ULONG BytesIndicated,
|
|
OUT PUCHAR Indication[2],
|
|
OUT ULONG IndicationLength[2],
|
|
OUT PULONG IndicationCount
|
|
)
|
|
{
|
|
PUCHAR pOriginalIndication;
|
|
ULONG BytesAvailable;
|
|
|
|
|
|
//
|
|
// It's okay to chain this off the connection - this is not a per-request
|
|
// thing.
|
|
//
|
|
|
|
if(pConnection->MergeIndication.pBuffer)
|
|
{
|
|
//
|
|
// Yes - there was a portion of a prior indication that we did not
|
|
// consume. We'll just assume that the next
|
|
//
|
|
|
|
pOriginalIndication = pConnection->MergeIndication.pBuffer +
|
|
pConnection->MergeIndication.BytesWritten;
|
|
|
|
BytesAvailable = pConnection->MergeIndication.BytesAllocated -
|
|
pConnection->MergeIndication.BytesWritten;
|
|
|
|
if(BytesIndicated <= BytesAvailable)
|
|
{
|
|
// the second indication completly fits into our merge buffer.
|
|
// we'll just merge all of this and treat as one indication.
|
|
|
|
RtlCopyMemory(pOriginalIndication,
|
|
pIndication,
|
|
BytesIndicated);
|
|
|
|
pConnection->MergeIndication.BytesWritten += BytesIndicated;
|
|
|
|
*IndicationCount = 1;
|
|
Indication[0] = pConnection->MergeIndication.pBuffer;
|
|
IndicationLength[0] = pConnection->MergeIndication.BytesWritten;
|
|
}
|
|
else
|
|
{
|
|
// Fill up the Merge buffer completly.
|
|
|
|
RtlCopyMemory(pOriginalIndication,
|
|
pIndication,
|
|
BytesAvailable);
|
|
|
|
pConnection->MergeIndication.BytesWritten += BytesAvailable;
|
|
|
|
pIndication += BytesAvailable;
|
|
BytesIndicated -= BytesAvailable;
|
|
|
|
//
|
|
// We need to process 2 buffers.
|
|
//
|
|
|
|
*IndicationCount = 2;
|
|
|
|
Indication[0] = pConnection->MergeIndication.pBuffer;
|
|
IndicationLength[0] = pConnection->MergeIndication.BytesWritten;
|
|
|
|
Indication[1] = pIndication;
|
|
IndicationLength[1] = BytesIndicated;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// No original buffer, let's just process the new indication.
|
|
//
|
|
|
|
*IndicationCount = 1;
|
|
Indication[0] = pIndication;
|
|
IndicationLength[0] = BytesIndicated;
|
|
|
|
}
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This is the core HTTP response protocol parsing engine. It takes a stream of
|
|
bytes and parses them as a HTTP response.
|
|
|
|
|
|
Arguments:
|
|
|
|
pRequest - The Internal HTTP request structure.
|
|
pIndicatedBuffer - The current Indication
|
|
BytesIndicated - Size of the current Indication.
|
|
BytesTaken - An output parameter that indicates the # of bytes that
|
|
got consumed by the parser. Note that even when this
|
|
routine returns an error code, it could have consumed
|
|
some amount of data.
|
|
|
|
For e.g. if we got something like
|
|
"HTTP/1.1 200 OK .... CRLF Accept-", we'll consume all
|
|
data till the "Accept-".
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
STATUS_MORE_PROCESSING_REQUIRED : The Indication was not sufficient to
|
|
parse the response. This happens when
|
|
the indication ends inbetween a header
|
|
boundary. In such cases, the caller has
|
|
to buffer the data and call this routine
|
|
when it gets more data.
|
|
|
|
STATUS_INSUFFICIENT_RESOURCES : The parser ran out of output buffer.
|
|
When this happens, the parser has to get
|
|
more buffer and resume the parsing.
|
|
|
|
STATUS_SUCCESS : This request got parsed successfully.
|
|
|
|
STATUS_INVALID_NETWORK_RESPONSE : Illegal HTTP response.
|
|
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UcpParseHttpResponse(
|
|
PUC_HTTP_REQUEST pRequest,
|
|
PUCHAR pIndicatedBuffer,
|
|
ULONG BytesIndicated,
|
|
PULONG BytesTaken
|
|
)
|
|
{
|
|
PUCHAR pStartReason, pStart;
|
|
PHTTP_RESPONSE pResponse;
|
|
ULONG i;
|
|
NTSTATUS Status;
|
|
ULONG ResponseRangeLength, HeaderBytesTaken;
|
|
PCHAR pEnd;
|
|
ULONG AlignLength;
|
|
BOOLEAN bFoundLWS, bEnd;
|
|
BOOLEAN bIgnoreParsing;
|
|
PUCHAR pFoldingBuffer = NULL;
|
|
ULONG EntityBytesTaken;
|
|
UC_DATACHUNK_RETURN DataChunkStatus;
|
|
USHORT OldMinorVersion;
|
|
|
|
|
|
HeaderBytesTaken = 0;
|
|
|
|
*BytesTaken = 0;
|
|
pResponse = pRequest->CurrentBuffer.pResponse;
|
|
|
|
bIgnoreParsing = (BOOLEAN)(pRequest->RequestFlags.ProxySslConnect != 0);
|
|
|
|
if(!pResponse)
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
while(BytesIndicated > 0)
|
|
{
|
|
switch(pRequest->ParseState)
|
|
{
|
|
case UcParseStatusLineVersion:
|
|
|
|
//
|
|
// Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
|
|
//
|
|
|
|
pStart = pIndicatedBuffer;
|
|
|
|
//
|
|
// Skip LWS
|
|
//
|
|
|
|
while(BytesIndicated && IS_HTTP_LWS(*pIndicatedBuffer))
|
|
{
|
|
pIndicatedBuffer ++;
|
|
BytesIndicated --;
|
|
}
|
|
|
|
//
|
|
// Do we have enough buffer to strcmp the version ?
|
|
//
|
|
|
|
if(BytesIndicated < MIN_VERSION_SIZE)
|
|
{
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
ASSERT(VERSION_OTHER_SIZE <= MIN_VERSION_SIZE);
|
|
|
|
|
|
if(strncmp((const char *)pIndicatedBuffer,
|
|
HTTP_VERSION_OTHER,
|
|
VERSION_OTHER_SIZE) == 0)
|
|
{
|
|
pIndicatedBuffer = pIndicatedBuffer + VERSION_OTHER_SIZE;
|
|
BytesIndicated -= VERSION_OTHER_SIZE;
|
|
|
|
//
|
|
// Parse the Major Version number. We'll only accept a major
|
|
// version of 1.
|
|
//
|
|
pResponse->Version.MajorVersion = 0;
|
|
|
|
while(BytesIndicated > 0 && IS_HTTP_DIGIT(*pIndicatedBuffer))
|
|
{
|
|
pResponse->Version.MajorVersion =
|
|
(pResponse->Version.MajorVersion * 10) +
|
|
*pIndicatedBuffer - '0';
|
|
pIndicatedBuffer ++;
|
|
BytesIndicated --;
|
|
|
|
//
|
|
// Check for overflow.
|
|
//
|
|
if(pResponse->Version.MajorVersion > 1)
|
|
{
|
|
UlTrace(PARSER,
|
|
("[UcpParseHttpResponse]:Invalid HTTP "
|
|
"version \n"));
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
}
|
|
|
|
if(0 == BytesIndicated)
|
|
{
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
if(*pIndicatedBuffer != '.' ||
|
|
pResponse->Version.MajorVersion != 1)
|
|
{
|
|
//
|
|
// INVALID HTTP version!!
|
|
//
|
|
|
|
UlTrace(PARSER,
|
|
("[UcpParseHttpResponse]:Invalid HTTP version \n"));
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
|
|
//
|
|
// Ignore the '.'
|
|
//
|
|
pIndicatedBuffer ++;
|
|
BytesIndicated --;
|
|
|
|
//
|
|
// Parse the Minor Version number. We'll accept anything for
|
|
// the minor version number as long as it's a USHORT (to be
|
|
// protocol compliant)
|
|
//
|
|
|
|
pResponse->Version.MinorVersion = 0;
|
|
OldMinorVersion = 0;
|
|
|
|
while(BytesIndicated > 0 && IS_HTTP_DIGIT(*pIndicatedBuffer))
|
|
{
|
|
OldMinorVersion = pResponse->Version.MinorVersion;
|
|
|
|
pResponse->Version.MinorVersion =
|
|
(pResponse->Version.MinorVersion * 10) +
|
|
*pIndicatedBuffer - '0';
|
|
|
|
pIndicatedBuffer ++;
|
|
BytesIndicated --;
|
|
|
|
//
|
|
// Check for overflow.
|
|
//
|
|
if(pResponse->Version.MinorVersion < OldMinorVersion)
|
|
{
|
|
UlTrace(PARSER,
|
|
("[UcpParseHttpResponse]:Invalid HTTP "
|
|
"version \n"));
|
|
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
}
|
|
|
|
if(0 == BytesIndicated)
|
|
{
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
|
|
ASSERT(pResponse->Version.MajorVersion == 1 );
|
|
|
|
if(
|
|
pResponse->Version.MinorVersion == 0
|
|
)
|
|
{
|
|
// By default, we have to close the connection for 1.0
|
|
// requests.
|
|
|
|
pRequest->ResponseVersion11 = FALSE;
|
|
pRequest->ResponseConnectionClose = TRUE;
|
|
}
|
|
else
|
|
{
|
|
PUC_CLIENT_CONNECTION pConnection;
|
|
|
|
pRequest->ResponseVersion11 = TRUE;
|
|
pRequest->ResponseConnectionClose = FALSE;
|
|
|
|
//
|
|
// Also update the version number on the COMMON
|
|
// SERVINFO, so that future requests to this server use
|
|
// the proper version.
|
|
//
|
|
pConnection = pRequest->pConnection;
|
|
pConnection->pServerInfo->pNextHopInfo->Version11 = TRUE;
|
|
}
|
|
|
|
pRequest->ParseState = UcParseStatusLineStatusCode;
|
|
|
|
*BytesTaken += DIFF(pIndicatedBuffer - pStart);
|
|
}
|
|
else
|
|
{
|
|
UlTrace(PARSER,
|
|
("[UcpParseHttpResponse]: Invalid HTTP version \n"));
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
|
|
//
|
|
// FALLTHROUGH
|
|
//
|
|
|
|
case UcParseStatusLineStatusCode:
|
|
|
|
ASSERT(pRequest->ParseState == UcParseStatusLineStatusCode);
|
|
|
|
pStart = pIndicatedBuffer;
|
|
|
|
//
|
|
// skip LWS
|
|
//
|
|
|
|
bFoundLWS = FALSE;
|
|
while(BytesIndicated && IS_HTTP_LWS(*pIndicatedBuffer))
|
|
{
|
|
bFoundLWS = TRUE;
|
|
pIndicatedBuffer ++;
|
|
BytesIndicated --;
|
|
}
|
|
|
|
|
|
//
|
|
// NOTE: Order of the following two if conditions is important
|
|
// We don't want to fail this response if we got here when
|
|
// BytesIndicated was 0 in the first place.
|
|
//
|
|
|
|
if(BytesIndicated < STATUS_CODE_LENGTH)
|
|
{
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
if(!bFoundLWS)
|
|
{
|
|
UlTrace(PARSER,
|
|
("[UcpParseHttpResponse]: No LWS between reason & "
|
|
"status code \n"));
|
|
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
|
|
pResponse->StatusCode = 0;
|
|
|
|
for(i = 0; i < STATUS_CODE_LENGTH; i++)
|
|
{
|
|
//
|
|
// The status code has to be a 3 digit string
|
|
//
|
|
|
|
if(!IS_HTTP_DIGIT(*pIndicatedBuffer))
|
|
{
|
|
UlTrace(PARSER,
|
|
("[UcpParseHttpResponse]: Invalid status code \n"));
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
|
|
pResponse->StatusCode = (pResponse->StatusCode * 10) +
|
|
*pIndicatedBuffer - '0';
|
|
pIndicatedBuffer ++;
|
|
BytesIndicated --;
|
|
}
|
|
|
|
pRequest->ResponseStatusCode = pResponse->StatusCode;
|
|
|
|
pRequest->ParseState = UcParseStatusLineReasonPhrase;
|
|
|
|
if(pRequest->ResponseStatusCode >= 100 &&
|
|
pRequest->ResponseStatusCode <= 199 &&
|
|
pRequest->pServerInfo->IgnoreContinues)
|
|
{
|
|
bIgnoreParsing = TRUE;
|
|
}
|
|
|
|
*BytesTaken += DIFF(pIndicatedBuffer - pStart);
|
|
|
|
//
|
|
// FALLTHROUGH
|
|
//
|
|
|
|
case UcParseStatusLineReasonPhrase:
|
|
|
|
ASSERT(pRequest->ParseState == UcParseStatusLineReasonPhrase);
|
|
|
|
pStart = pIndicatedBuffer;
|
|
|
|
//
|
|
// Make sure we have bytes to look for a LWS. Make sure that it is
|
|
// indeed a LWS.
|
|
//
|
|
|
|
bFoundLWS = FALSE;
|
|
|
|
while(BytesIndicated && IS_HTTP_LWS(*pIndicatedBuffer))
|
|
{
|
|
bFoundLWS = TRUE;
|
|
pIndicatedBuffer ++;
|
|
BytesIndicated --;
|
|
}
|
|
|
|
//
|
|
// NOTE: Order of the following two if conditions is important
|
|
// We don't want to fail this response if we got here when
|
|
// BytesIndicated was 0 in the first place.
|
|
//
|
|
|
|
if(BytesIndicated == 0)
|
|
{
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
if(!bFoundLWS)
|
|
{
|
|
UlTrace(PARSER,
|
|
("[UcpParseHttpResponse]: No LWS between reason & "
|
|
"status code \n"));
|
|
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
|
|
//
|
|
// Look for a CRLF
|
|
//
|
|
|
|
pStartReason = pIndicatedBuffer;
|
|
|
|
while(BytesIndicated >= CRLF_SIZE)
|
|
{
|
|
if (*(UNALIGNED64 USHORT *)pIndicatedBuffer == CRLF ||
|
|
*(UNALIGNED64 USHORT *)pIndicatedBuffer == LFLF)
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Advance to the next character.
|
|
//
|
|
pIndicatedBuffer ++;
|
|
BytesIndicated --;
|
|
}
|
|
|
|
if(BytesIndicated < CRLF_SIZE)
|
|
{
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
//
|
|
// We've reached end of reason string, consume the CRLF.
|
|
//
|
|
pIndicatedBuffer += CRLF_SIZE;
|
|
BytesIndicated -= CRLF_SIZE;
|
|
|
|
if(!bIgnoreParsing)
|
|
{
|
|
//
|
|
// Set the reason string pointer and copy out the reason
|
|
// string.
|
|
//
|
|
|
|
pResponse->pReason=
|
|
(PSTR)pRequest->CurrentBuffer.pOutBufferHead;
|
|
|
|
pResponse->ReasonLength
|
|
= DIFF_USHORT(pIndicatedBuffer - pStartReason) - CRLF_SIZE;
|
|
|
|
AlignLength = ALIGN_UP(pResponse->ReasonLength, PVOID);
|
|
|
|
if(pRequest->CurrentBuffer.BytesAvailable >= AlignLength)
|
|
{
|
|
RtlCopyMemory((PSTR) pResponse->pReason,
|
|
pStartReason,
|
|
pResponse->ReasonLength);
|
|
|
|
pRequest->CurrentBuffer.pOutBufferHead += AlignLength;
|
|
pRequest->CurrentBuffer.BytesAvailable -= AlignLength;
|
|
|
|
}
|
|
else
|
|
{
|
|
pResponse->pReason = NULL;
|
|
pResponse->ReasonLength = 0;
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
// The head pointer is now going to be used for the unknown header
|
|
// array. Let's set it.
|
|
//
|
|
|
|
pResponse->Headers.pUnknownHeaders = (PHTTP_UNKNOWN_HEADER)
|
|
pRequest->CurrentBuffer.pOutBufferHead;
|
|
|
|
*BytesTaken += DIFF(pIndicatedBuffer - pStart);
|
|
pRequest->ParseState = UcParseHeaders;
|
|
|
|
//
|
|
// FALLTHROUGH
|
|
//
|
|
|
|
ASSERT(pRequest->ParseState == UcParseHeaders);
|
|
|
|
case UcParseHeaders:
|
|
case UcParseTrailers:
|
|
case UcParseEntityBodyMultipartHeaders:
|
|
|
|
pStart = pIndicatedBuffer;
|
|
|
|
while(BytesIndicated >= CRLF_SIZE)
|
|
{
|
|
//
|
|
// If this is an empty header, we are done.
|
|
//
|
|
|
|
if (*(UNALIGNED64 USHORT *)pIndicatedBuffer == CRLF ||
|
|
*(UNALIGNED64 USHORT *)pIndicatedBuffer == LFLF)
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Parse the headers.
|
|
//
|
|
|
|
if(bIgnoreParsing)
|
|
{
|
|
ULONG HeaderNameLength;
|
|
|
|
Status = UcFindHeaderNameEnd(
|
|
pIndicatedBuffer,
|
|
BytesIndicated,
|
|
&HeaderNameLength
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
ASSERT(BytesIndicated - HeaderNameLength
|
|
< ANSI_STRING_MAX_CHAR_LEN);
|
|
|
|
Status = UcFindHeaderValueEnd(
|
|
pIndicatedBuffer + HeaderNameLength,
|
|
(USHORT) (BytesIndicated - HeaderNameLength),
|
|
&pFoldingBuffer,
|
|
&HeaderBytesTaken
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
};
|
|
|
|
ASSERT(HeaderBytesTaken != 0);
|
|
|
|
// No need to check for NT_STATUS here. We are ignoring
|
|
// headers anyway.
|
|
|
|
if(pFoldingBuffer)
|
|
{
|
|
UL_FREE_POOL(
|
|
pFoldingBuffer,
|
|
UC_HEADER_FOLDING_POOL_TAG
|
|
);
|
|
}
|
|
|
|
HeaderBytesTaken += HeaderNameLength;
|
|
}
|
|
else
|
|
{
|
|
Status = UcParseHeader(pRequest,
|
|
pIndicatedBuffer,
|
|
BytesIndicated,
|
|
&HeaderBytesTaken);
|
|
|
|
}
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(HeaderBytesTaken != 0);
|
|
pIndicatedBuffer += HeaderBytesTaken;
|
|
BytesIndicated -= HeaderBytesTaken;
|
|
*BytesTaken += HeaderBytesTaken;
|
|
}
|
|
}
|
|
|
|
if(BytesIndicated >= CRLF_SIZE)
|
|
{
|
|
//
|
|
// We have reached the end of the headers.
|
|
//
|
|
|
|
pIndicatedBuffer += CRLF_SIZE;
|
|
BytesIndicated -= CRLF_SIZE;
|
|
*BytesTaken += CRLF_SIZE;
|
|
|
|
//
|
|
// If we were parsing headers, we have to parse entity bodies.
|
|
// if we were parsing trailers, we are done!
|
|
//
|
|
|
|
if(pRequest->ParseState == UcParseHeaders)
|
|
{
|
|
//
|
|
// See if it is valid for this response to include a
|
|
// message body. All 1xx, 204 and 304 responses should not
|
|
// have a message body.
|
|
|
|
//
|
|
// For 1XX responses, we need to re-set the ParseState to
|
|
// Init, so that we parse the subsequent response also.
|
|
//
|
|
|
|
if((pResponse->StatusCode >= 100 &&
|
|
pResponse->StatusCode <= 199))
|
|
{
|
|
pRequest->ParseState = UcParseStatusLineVersion;
|
|
|
|
if(!bIgnoreParsing)
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if((pResponse->StatusCode == 204) ||
|
|
(pResponse->StatusCode == 304) ||
|
|
(pRequest->RequestFlags.NoResponseEntityBodies == TRUE)
|
|
)
|
|
{
|
|
//
|
|
// These responses cannot have a entity body. If a
|
|
// rogue server has passed an entity body, it will
|
|
// just be treated as the part of a subsequent
|
|
// indication
|
|
//
|
|
|
|
pRequest->ParseState = UcParseDone;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Set up the pointers for the entity body.
|
|
//
|
|
|
|
pResponse->EntityChunkCount = 0;
|
|
pResponse->pEntityChunks = (PHTTP_DATA_CHUNK)
|
|
pRequest->CurrentBuffer.pOutBufferHead;
|
|
|
|
if(pResponse->StatusCode == 206 &&
|
|
pRequest->ResponseMultipartByteranges)
|
|
{
|
|
if(pRequest->ResponseEncodingChunked)
|
|
{
|
|
// UC_BUGBUG (WORKITEM)
|
|
// Ouch. We can't handle this right now.
|
|
// we have to first unchunk the entitities,
|
|
// and then decode the multipart response.
|
|
// for now, we'll fail this request.
|
|
|
|
UlTrace(PARSER,
|
|
("[UcpParseHttpResponse]: "
|
|
"Multipart-chunked\n"));
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
|
|
// multipart byterange
|
|
pRequest->ParseState = UcParseEntityBodyMultipartInit;
|
|
}
|
|
else
|
|
{
|
|
pRequest->ParseState = UcParseEntityBody;
|
|
}
|
|
}
|
|
else if(pRequest->ParseState == UcParseTrailers)
|
|
{
|
|
pRequest->ParseState = UcParseDone;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
pResponse->EntityChunkCount = 0;
|
|
pResponse->pEntityChunks = (PHTTP_DATA_CHUNK)
|
|
pRequest->CurrentBuffer.pOutBufferHead;
|
|
|
|
pRequest->ParseState = UcParseEntityBodyMultipartFinal;
|
|
pRequest->MultipartRangeRemaining = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
break;
|
|
|
|
case UcParseEntityBodyMultipartInit:
|
|
|
|
pStart = pIndicatedBuffer;
|
|
|
|
if(BytesIndicated >= pRequest->MultipartStringSeparatorLength)
|
|
{
|
|
pEnd = UxStrStr(
|
|
(const char *) pIndicatedBuffer,
|
|
(const char *) pRequest->pMultipartStringSeparator,
|
|
BytesIndicated
|
|
);
|
|
|
|
if(!pEnd)
|
|
{
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
pIndicatedBuffer =
|
|
(PUCHAR) pEnd + pRequest->MultipartStringSeparatorLength;
|
|
|
|
BytesIndicated -= DIFF(pIndicatedBuffer - pStart);
|
|
|
|
if(BytesIndicated >= 2 &&
|
|
*pIndicatedBuffer == '-' && *(pIndicatedBuffer+1) == '-')
|
|
{
|
|
BytesIndicated -= 2;
|
|
pIndicatedBuffer += 2;
|
|
bEnd = TRUE;
|
|
}
|
|
else
|
|
{
|
|
bEnd = FALSE;
|
|
}
|
|
|
|
//
|
|
// skip any LWS.
|
|
//
|
|
|
|
while(BytesIndicated && IS_HTTP_LWS(*pIndicatedBuffer))
|
|
{
|
|
pIndicatedBuffer ++;
|
|
BytesIndicated --;
|
|
}
|
|
|
|
if(BytesIndicated >= 2)
|
|
{
|
|
if(*(UNALIGNED64 USHORT *)pIndicatedBuffer == CRLF ||
|
|
*(UNALIGNED64 USHORT *)pIndicatedBuffer == LFLF)
|
|
{
|
|
BytesIndicated -= CRLF_SIZE;
|
|
pIndicatedBuffer += CRLF_SIZE;
|
|
|
|
if(!bEnd)
|
|
{
|
|
// we are ready to parse the range. Since we always
|
|
// indicate Multipart in a new HTTP_RESPONSE
|
|
// structure, get more buffer.
|
|
|
|
Status = UcpGetResponseBuffer(
|
|
pRequest,
|
|
BytesIndicated,
|
|
UC_RESPONSE_BUFFER_FLAG_NOT_MERGEABLE);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
pResponse = pRequest->CurrentBuffer.pResponse;
|
|
|
|
pRequest->ParseState =
|
|
UcParseEntityBodyMultipartHeaders;
|
|
}
|
|
else
|
|
{
|
|
if(BytesIndicated == 2)
|
|
{
|
|
if(*(UNALIGNED64 USHORT *)pIndicatedBuffer
|
|
== CRLF)
|
|
{
|
|
BytesIndicated -= CRLF_SIZE;
|
|
pIndicatedBuffer += CRLF_SIZE;
|
|
|
|
pRequest->ParseState = UcParseDone;
|
|
}
|
|
else
|
|
{
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
*BytesTaken += DIFF(pIndicatedBuffer - pStart);
|
|
|
|
break;
|
|
|
|
case UcParseEntityBodyMultipartFinal:
|
|
|
|
//
|
|
// We have parsed out the Content-Type & Content-Range of the
|
|
// Multipart encoding, we now proceed to carve out HTTP_DATA_CHUNKs
|
|
// for the data. We will keep searching the data till we hit a
|
|
// seperator.
|
|
//
|
|
|
|
if(pRequest->MultipartRangeRemaining != 0)
|
|
{
|
|
//
|
|
// We didn't consume part of an earlier range.
|
|
//
|
|
DataChunkStatus = UcpCopyEntityToDataChunk(
|
|
pResponse,
|
|
pRequest,
|
|
pRequest->MultipartRangeRemaining, // BytesToTake
|
|
BytesIndicated, // BytesIndicated
|
|
pIndicatedBuffer,
|
|
&EntityBytesTaken
|
|
);
|
|
|
|
*BytesTaken += EntityBytesTaken;
|
|
pRequest->MultipartRangeRemaining -= EntityBytesTaken;
|
|
|
|
if(UcDataChunkCopyAll == DataChunkStatus)
|
|
{
|
|
if(0 == pRequest->MultipartRangeRemaining)
|
|
{
|
|
// Done with this range, we consumed all of it.
|
|
|
|
pRequest->ParseState = UcParseEntityBodyMultipartInit;
|
|
pIndicatedBuffer += EntityBytesTaken;
|
|
BytesIndicated -= EntityBytesTaken;
|
|
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// We consumed all, but there's more data remaining.
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We consumed partial, need more buffer
|
|
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
else if(BytesIndicated >= pRequest->MultipartStringSeparatorLength)
|
|
{
|
|
pEnd = UxStrStr(
|
|
(const char *) pIndicatedBuffer,
|
|
(const char *) pRequest->pMultipartStringSeparator,
|
|
BytesIndicated
|
|
);
|
|
|
|
if(!pEnd)
|
|
{
|
|
// If we haven't reached the end, we can't blindly carve
|
|
// out a data chunk with whatever we have! This is because
|
|
// we could have a case where the StringSeperator is
|
|
// spread across indications & we wouldn't want to treat
|
|
// the part of the string separator as entity!
|
|
|
|
|
|
// So, we'll carve out a data chunk for
|
|
// BytesIndicated - MultipartStringSeparatorLength. we are
|
|
// guaranteed that this is entity body.
|
|
|
|
|
|
ResponseRangeLength =
|
|
BytesIndicated - pRequest->MultipartStringSeparatorLength;
|
|
|
|
DataChunkStatus = UcpCopyEntityToDataChunk(
|
|
pResponse,
|
|
pRequest,
|
|
ResponseRangeLength, // BytesToTake
|
|
BytesIndicated, // BytesIndicated
|
|
pIndicatedBuffer,
|
|
&EntityBytesTaken
|
|
);
|
|
|
|
*BytesTaken += EntityBytesTaken;
|
|
|
|
if(UcDataChunkCopyAll == DataChunkStatus)
|
|
{
|
|
// Since we haven't yet seen the terminator, we have
|
|
// to return STATUS_MORE_PROCESSING_REQUIRED.
|
|
//
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
else
|
|
{
|
|
// consumed partial, more buffer required
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ResponseRangeLength = DIFF((PUCHAR)pEnd - pIndicatedBuffer);
|
|
|
|
ASSERT(ResponseRangeLength < BytesIndicated);
|
|
|
|
DataChunkStatus = UcpCopyEntityToDataChunk(
|
|
pResponse,
|
|
pRequest,
|
|
ResponseRangeLength, // BytesToTake
|
|
BytesIndicated, // BytesIndicated
|
|
pIndicatedBuffer,
|
|
&EntityBytesTaken
|
|
);
|
|
|
|
|
|
*BytesTaken += EntityBytesTaken;
|
|
|
|
if(UcDataChunkCopyAll == DataChunkStatus)
|
|
{
|
|
//
|
|
// Done with this range, we consumed all of it.
|
|
//
|
|
|
|
ASSERT(EntityBytesTaken == ResponseRangeLength);
|
|
|
|
pRequest->ParseState = UcParseEntityBodyMultipartInit;
|
|
|
|
// Update these fields, because we have to continue
|
|
// parsing.
|
|
|
|
pIndicatedBuffer += ResponseRangeLength;
|
|
BytesIndicated -= ResponseRangeLength;
|
|
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// this field has to be a ULONG, because we are
|
|
// gated by BytesIndicated. It doesn't have to be a
|
|
// ULONGLONG.
|
|
|
|
pRequest->MultipartRangeRemaining =
|
|
ResponseRangeLength - EntityBytesTaken;
|
|
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
break;
|
|
|
|
case UcParseEntityBody:
|
|
|
|
if(pRequest->ResponseEncodingChunked)
|
|
{
|
|
//
|
|
// If there's a chunk that we've not fully consumed.
|
|
// The chunk-length of the current chunk is stored in
|
|
// ResponseContentLength.
|
|
//
|
|
|
|
if(pRequest->ResponseContentLength)
|
|
{
|
|
DataChunkStatus =
|
|
UcpCopyEntityToDataChunk(
|
|
pResponse,
|
|
pRequest,
|
|
(ULONG)pRequest->ResponseContentLength,
|
|
BytesIndicated,
|
|
pIndicatedBuffer,
|
|
&EntityBytesTaken
|
|
);
|
|
|
|
pRequest->ResponseContentLength -= EntityBytesTaken;
|
|
|
|
*BytesTaken += EntityBytesTaken;
|
|
|
|
if(UcDataChunkCopyAll == DataChunkStatus)
|
|
{
|
|
if(0 == pRequest->ResponseContentLength)
|
|
{
|
|
// We are done. Move onto the next chunk.
|
|
//
|
|
pIndicatedBuffer += EntityBytesTaken;
|
|
BytesIndicated -= EntityBytesTaken;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(BytesIndicated == EntityBytesTaken);
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ULONG ChunkBytesTaken;
|
|
|
|
Status = ParseChunkLength(
|
|
pRequest->ParsedFirstChunk,
|
|
pIndicatedBuffer,
|
|
BytesIndicated,
|
|
&ChunkBytesTaken,
|
|
&pRequest->ResponseContentLength
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
*BytesTaken += ChunkBytesTaken;
|
|
|
|
pIndicatedBuffer += ChunkBytesTaken;
|
|
BytesIndicated -= ChunkBytesTaken;
|
|
|
|
pRequest->ParsedFirstChunk = 1;
|
|
|
|
if(0 == pRequest->ResponseContentLength)
|
|
{
|
|
//
|
|
// We are done - Let's handle the trailers.
|
|
//
|
|
pRequest->ParseState = UcParseTrailers;
|
|
bIgnoreParsing = TRUE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
else if(pRequest->ResponseContentLengthSpecified)
|
|
{
|
|
if(pRequest->ResponseContentLength == 0)
|
|
{
|
|
pRequest->ParseState = UcParseDone;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
DataChunkStatus = UcpCopyEntityToDataChunk(
|
|
pResponse,
|
|
pRequest,
|
|
(ULONG) pRequest->ResponseContentLength,
|
|
BytesIndicated,
|
|
pIndicatedBuffer,
|
|
&EntityBytesTaken
|
|
);
|
|
|
|
*BytesTaken += EntityBytesTaken;
|
|
pRequest->ResponseContentLength -= EntityBytesTaken;
|
|
|
|
if(UcDataChunkCopyAll == DataChunkStatus)
|
|
{
|
|
if(0 == pRequest->ResponseContentLength)
|
|
{
|
|
//
|
|
// We've consumed everything.
|
|
//
|
|
pRequest->ParseState = UcParseDone;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Consume all of it. We will assume that this will end when
|
|
// we get called in the disconnect handler.
|
|
//
|
|
|
|
DataChunkStatus = UcpCopyEntityToDataChunk(
|
|
pResponse,
|
|
pRequest,
|
|
BytesIndicated,
|
|
BytesIndicated,
|
|
pIndicatedBuffer,
|
|
&EntityBytesTaken
|
|
);
|
|
|
|
*BytesTaken += EntityBytesTaken;
|
|
|
|
if(UcDataChunkCopyAll == DataChunkStatus)
|
|
{
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
else
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case UcParseDone:
|
|
return STATUS_SUCCESS;
|
|
break;
|
|
|
|
default:
|
|
|
|
ASSERT(FALSE);
|
|
UlTrace(PARSER,
|
|
("[UcpParseHttpResponse]: Invalid state \n"));
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
|
|
} // case
|
|
}
|
|
|
|
if(pRequest->ParseState != UcParseDone)
|
|
{
|
|
//
|
|
// Ideally, we want to do this check inside UcParseEntityBody -
|
|
// however, if BytesIndicated == 0 adn we are done, we might not
|
|
// get a chance to even go there.
|
|
//
|
|
|
|
if(pRequest->ParseState == UcParseEntityBody &&
|
|
pRequest->ResponseContentLengthSpecified &&
|
|
pRequest->ResponseContentLength == 0)
|
|
{
|
|
pRequest->ParseState = UcParseDone;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
else
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This is the main routine that gets called from the TDI receive indication
|
|
handler. It merges the indications, allocates buffers (if required) and
|
|
kicks of the Parser.
|
|
|
|
Arguments:
|
|
|
|
pConnectionContext - The UC_CLIENT_CONNECTION structure.
|
|
pIndicatedBuffer - The current Indication
|
|
BytesIndicated - Size of the current Indication.
|
|
UnreceivedLength - Bytes the transport has, but arent in buffer.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
STATUS_SUCCESS : Parsed or buffered the indication.
|
|
|
|
STATUS_INSUFFICIENT_RESOURCES : Out of memory. This will result in a
|
|
connection tear down.
|
|
|
|
STATUS_INVALID_NETWORK_RESPONSE : Illegal HTTP response, mismatched
|
|
response.
|
|
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UcHandleResponse(IN PVOID pListeningContext,
|
|
IN PVOID pConnectionContext,
|
|
IN PVOID pIndicatedBuffer,
|
|
IN ULONG BytesIndicated,
|
|
IN ULONG UnreceivedLength,
|
|
OUT PULONG pBytesTaken)
|
|
{
|
|
ULONG BytesTaken;
|
|
ULONG i;
|
|
PUC_CLIENT_CONNECTION pConnection;
|
|
PUCHAR pIndication, Indication[2], pOldIndication;
|
|
ULONG IndicationLength[2];
|
|
ULONG IndicationCount;
|
|
PUC_HTTP_REQUEST pRequest;
|
|
NTSTATUS Status;
|
|
PLIST_ENTRY pList;
|
|
KIRQL OldIrql;
|
|
ULONG OriginalBytesIndicated;
|
|
ULONG OldIndicationBytesAllocated;
|
|
BOOLEAN DoneWithLoop = FALSE;
|
|
|
|
UNREFERENCED_PARAMETER(pListeningContext);
|
|
UNREFERENCED_PARAMETER(UnreceivedLength);
|
|
|
|
OriginalBytesIndicated = BytesIndicated;
|
|
OldIndicationBytesAllocated = 0;
|
|
*pBytesTaken = 0;
|
|
|
|
pConnection = (PUC_CLIENT_CONNECTION) pConnectionContext;
|
|
|
|
ASSERT( UC_IS_VALID_CLIENT_CONNECTION(pConnection) );
|
|
|
|
//
|
|
// Right now, no body calls the client receive function with
|
|
// Unreceived length. In TDI receive handler, we always drain
|
|
// out the data before calling UcHandleResponse.
|
|
//
|
|
|
|
ASSERT(UnreceivedLength == 0);
|
|
|
|
//
|
|
// We might have got insufficient indication from a prior indication.
|
|
// Let's see if we need to merge this.
|
|
//
|
|
|
|
UcpMergeIndications(pConnection,
|
|
(PUCHAR)pIndicatedBuffer,
|
|
BytesIndicated,
|
|
Indication,
|
|
IndicationLength,
|
|
&IndicationCount);
|
|
|
|
ASSERT( (IndicationCount == 1 || IndicationCount == 2) );
|
|
|
|
//
|
|
// Begin parsing.
|
|
//
|
|
|
|
for(i=0; !DoneWithLoop && i<IndicationCount; i++)
|
|
{
|
|
BytesIndicated = IndicationLength[i];
|
|
pIndication = Indication[i];
|
|
|
|
while(BytesIndicated)
|
|
{
|
|
//
|
|
// We first need to pick the first request that got submitted to
|
|
// TDI. We need this to match responses to requests. There will be
|
|
// an entry in this list even if the app did not submit an output
|
|
// buffer with the request.
|
|
//
|
|
|
|
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
|
|
|
|
//
|
|
// If the connection is being cleaned up, we should not even
|
|
// be here. Ideally, this will never happen, because TDI will
|
|
// not call our cleanup handler till all outstanding receives
|
|
// are complete.
|
|
//
|
|
// However, when we go over SSL, we buffer the data & complete
|
|
// TDI's receive thread.
|
|
//
|
|
|
|
if(pConnection->ConnectionState ==
|
|
UcConnectStateConnectCleanup ||
|
|
pConnection->ConnectionState ==
|
|
UcConnectStateConnectCleanupBegin)
|
|
{
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
|
|
UlTrace(PARSER,
|
|
("[UcHandleResponse]: Connection cleaned \n"));
|
|
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
|
|
if(IsListEmpty(&pConnection->SentRequestList))
|
|
{
|
|
//
|
|
// This can happen if the server sends a screwed up response.
|
|
// we'll never be able to match responses with requests. We are
|
|
// forced to tear down the connection.
|
|
//
|
|
|
|
//
|
|
// We won't do any clean-up here - This status code will cause
|
|
// the connection to get torn down and we'll localize all the
|
|
// conneciton cleanup code.
|
|
//
|
|
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
|
|
UlTrace(PARSER,
|
|
("[UcHandleResponse]: Malformed HTTP packet \n"));
|
|
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
|
|
pList = (&pConnection->SentRequestList)->Flink;
|
|
|
|
pRequest = CONTAINING_RECORD(pList, UC_HTTP_REQUEST, Linkage);
|
|
|
|
if(pRequest->ParseState == UcParseDone)
|
|
{
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
|
|
switch(pRequest->RequestState)
|
|
{
|
|
case UcRequestStateSent:
|
|
|
|
// Received first byte of data but haven't got called
|
|
// in send complete handler.
|
|
|
|
pRequest->RequestState =
|
|
UcRequestStateNoSendCompletePartialData;
|
|
break;
|
|
|
|
case UcRequestStateNoSendCompletePartialData:
|
|
|
|
// Received more data but still haven't got called
|
|
// in send complete handler. We'll remain in the same
|
|
// state.
|
|
|
|
break;
|
|
|
|
case UcRequestStateSendCompleteNoData:
|
|
|
|
// We have been called in Send complete & are receiving
|
|
// the first byte of data.
|
|
|
|
pRequest->RequestState =
|
|
UcRequestStateSendCompletePartialData;
|
|
break;
|
|
|
|
case UcRequestStateSendCompletePartialData:
|
|
// We have been called in Send complete & are receiving
|
|
// data.
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
|
|
if(UcpReferenceForReceive(pRequest) == FALSE)
|
|
{
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
|
|
UlTrace(PARSER,
|
|
("[UcHandleResponse]: Receive cancelled \n"));
|
|
|
|
return STATUS_CANCELLED;
|
|
}
|
|
|
|
pConnection->Flags |= CLIENT_CONN_FLAG_RECV_BUSY;
|
|
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
|
|
//
|
|
// Given a request & response buffer, we will loop till we parse
|
|
// out this completly.
|
|
//
|
|
|
|
while(TRUE)
|
|
{
|
|
Status = UcpParseHttpResponse(pRequest,
|
|
pIndication,
|
|
BytesIndicated,
|
|
&BytesTaken);
|
|
|
|
//
|
|
// Even if there is an error, we could have consumed some
|
|
// amount of data.
|
|
//
|
|
|
|
BytesIndicated -= BytesTaken;
|
|
pIndication += BytesTaken;
|
|
|
|
ASSERT((LONG)BytesIndicated >= 0);
|
|
|
|
if(Status == STATUS_SUCCESS)
|
|
{
|
|
//
|
|
// This one worked! Complete the IRP that got pended.
|
|
//
|
|
|
|
UcpHandleParsedRequest(pRequest,
|
|
&pIndication,
|
|
&BytesIndicated,
|
|
BytesTaken
|
|
);
|
|
|
|
UcpDereferenceForReceive(pRequest);
|
|
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if(Status == STATUS_MORE_PROCESSING_REQUIRED)
|
|
{
|
|
UcpDereferenceForReceive(pRequest);
|
|
|
|
if(BytesIndicated == 0)
|
|
{
|
|
// This just means that this indication was parsed
|
|
// completly, but we did not reach the end of the
|
|
// response. Let's move on to the next indication.
|
|
|
|
// This will take us out of the While(TRUE) loop
|
|
// as well as while(BytesIndicated) loop.
|
|
|
|
break;
|
|
}
|
|
else if(DoneWithLoop || i == IndicationCount - 1)
|
|
{
|
|
// We ran out of buffer space for the last
|
|
// indication. This could either be a TDI
|
|
// indication or a "merged" indication
|
|
//
|
|
// If its a TDI indication we have to copy since
|
|
// we don't own the TDI buffers. If its our
|
|
// indication, we still have to copy since some
|
|
// portion of it might have got consumed by the
|
|
// parser & we care only about the remaining.
|
|
|
|
pOldIndication =
|
|
pConnection->MergeIndication.pBuffer;
|
|
|
|
OldIndicationBytesAllocated =
|
|
pConnection->MergeIndication.BytesAllocated;
|
|
|
|
pConnection->MergeIndication.pBuffer =
|
|
(PUCHAR) UL_ALLOCATE_POOL_WITH_QUOTA(
|
|
NonPagedPool,
|
|
BytesIndicated +
|
|
UC_INSUFFICIENT_INDICATION_EXTRA_BUFFER,
|
|
UC_RESPONSE_TDI_BUFFER_POOL_TAG,
|
|
pConnection->pServerInfo->pProcess
|
|
);
|
|
|
|
if(!pConnection->MergeIndication.pBuffer)
|
|
{
|
|
if(pOldIndication)
|
|
{
|
|
UL_FREE_POOL_WITH_QUOTA(
|
|
pOldIndication,
|
|
UC_RESPONSE_TDI_BUFFER_POOL_TAG,
|
|
NonPagedPool,
|
|
OldIndicationBytesAllocated,
|
|
pConnection->pServerInfo->pProcess
|
|
);
|
|
}
|
|
|
|
UlAcquireSpinLock(&pConnection->SpinLock,
|
|
&OldIrql);
|
|
|
|
UcClearConnectionBusyFlag(
|
|
pConnection,
|
|
CLIENT_CONN_FLAG_RECV_BUSY,
|
|
OldIrql,
|
|
FALSE
|
|
);
|
|
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
pConnection->MergeIndication.BytesAllocated =
|
|
BytesIndicated +
|
|
UC_INSUFFICIENT_INDICATION_EXTRA_BUFFER;
|
|
|
|
pConnection->MergeIndication.BytesWritten =
|
|
BytesIndicated;
|
|
|
|
RtlCopyMemory(
|
|
pConnection->MergeIndication.pBuffer,
|
|
pIndication,
|
|
BytesIndicated
|
|
);
|
|
|
|
if(pOldIndication)
|
|
{
|
|
UL_FREE_POOL_WITH_QUOTA(
|
|
pOldIndication,
|
|
UC_RESPONSE_TDI_BUFFER_POOL_TAG,
|
|
NonPagedPool,
|
|
OldIndicationBytesAllocated,
|
|
pConnection->pServerInfo->pProcess
|
|
);
|
|
}
|
|
|
|
//
|
|
// Let's pretend as if we have read all of the data.
|
|
//
|
|
|
|
*pBytesTaken = OriginalBytesIndicated;
|
|
|
|
UlAcquireSpinLock(&pConnection->SpinLock,
|
|
&OldIrql);
|
|
|
|
UcClearConnectionBusyFlag(
|
|
pConnection,
|
|
CLIENT_CONN_FLAG_RECV_BUSY,
|
|
OldIrql,
|
|
FALSE
|
|
);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Our merge did not go well. We have to copy more
|
|
// data from the TDI indication into this
|
|
// and continue. To keep things simple, we'll just
|
|
// copy everything.
|
|
//
|
|
|
|
ASSERT(i==0);
|
|
ASSERT(pConnection->MergeIndication.pBuffer);
|
|
|
|
if(pConnection->MergeIndication.BytesAllocated <
|
|
(BytesIndicated + IndicationLength[i+1] +
|
|
UC_INSUFFICIENT_INDICATION_EXTRA_BUFFER)
|
|
)
|
|
{
|
|
pOldIndication =
|
|
pConnection->MergeIndication.pBuffer;
|
|
|
|
OldIndicationBytesAllocated =
|
|
pConnection->MergeIndication.BytesAllocated;
|
|
|
|
pConnection->MergeIndication.pBuffer =
|
|
(PUCHAR) UL_ALLOCATE_POOL_WITH_QUOTA(
|
|
NonPagedPool,
|
|
BytesIndicated+ IndicationLength[i+1]+
|
|
UC_INSUFFICIENT_INDICATION_EXTRA_BUFFER,
|
|
UC_RESPONSE_TDI_BUFFER_POOL_TAG,
|
|
pConnection->pServerInfo->pProcess
|
|
);
|
|
|
|
if(!pConnection->MergeIndication.pBuffer)
|
|
{
|
|
UL_FREE_POOL_WITH_QUOTA(
|
|
pOldIndication,
|
|
UC_RESPONSE_TDI_BUFFER_POOL_TAG,
|
|
NonPagedPool,
|
|
OldIndicationBytesAllocated,
|
|
pConnection->pServerInfo->pProcess
|
|
);
|
|
|
|
UlAcquireSpinLock(&pConnection->SpinLock,
|
|
&OldIrql);
|
|
|
|
UcClearConnectionBusyFlag(
|
|
pConnection,
|
|
CLIENT_CONN_FLAG_RECV_BUSY,
|
|
OldIrql,
|
|
FALSE
|
|
);
|
|
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
pConnection->MergeIndication.BytesAllocated =
|
|
BytesIndicated + IndicationLength[i+1] +
|
|
UC_INSUFFICIENT_INDICATION_EXTRA_BUFFER;
|
|
|
|
}
|
|
else
|
|
{
|
|
pOldIndication = 0;
|
|
}
|
|
|
|
RtlCopyMemory(
|
|
pConnection->MergeIndication.pBuffer,
|
|
pIndication,
|
|
BytesIndicated
|
|
);
|
|
|
|
RtlCopyMemory(
|
|
pConnection->MergeIndication.pBuffer +
|
|
BytesIndicated,
|
|
Indication[i+1],
|
|
IndicationLength[i+1]
|
|
);
|
|
|
|
if(pOldIndication)
|
|
{
|
|
UL_FREE_POOL_WITH_QUOTA(
|
|
pOldIndication,
|
|
UC_RESPONSE_TDI_BUFFER_POOL_TAG,
|
|
NonPagedPool,
|
|
OldIndicationBytesAllocated,
|
|
pConnection->pServerInfo->pProcess
|
|
);
|
|
}
|
|
|
|
pConnection->MergeIndication.BytesWritten =
|
|
BytesIndicated + IndicationLength[i+1];
|
|
|
|
//
|
|
// Fix up all the variables so that we run through
|
|
// the loop only once with the new buffer.
|
|
//
|
|
|
|
pIndication =
|
|
pConnection->MergeIndication.pBuffer;
|
|
|
|
BytesIndicated =
|
|
pConnection->MergeIndication.BytesWritten;
|
|
|
|
DoneWithLoop = TRUE;
|
|
|
|
//
|
|
// This will take us out of the while(TRUE) loop.
|
|
// so we will resume at while(BytesIndicated) loop
|
|
// pick the next request & party on.
|
|
//
|
|
|
|
break;
|
|
}
|
|
}
|
|
else if(Status == STATUS_INSUFFICIENT_RESOURCES)
|
|
{
|
|
//
|
|
// We ran out of output buffer space. Let's get more
|
|
// buffer to hold the response.
|
|
//
|
|
|
|
Status = UcpGetResponseBuffer(pRequest,
|
|
BytesIndicated,
|
|
0);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
UcpDereferenceForReceive(pRequest);
|
|
|
|
UlAcquireSpinLock(&pConnection->SpinLock,
|
|
&OldIrql);
|
|
|
|
UcClearConnectionBusyFlag(
|
|
pConnection,
|
|
CLIENT_CONN_FLAG_RECV_BUSY,
|
|
OldIrql,
|
|
FALSE
|
|
);
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// since we have got more buffer, continue parsing the
|
|
// response.
|
|
//
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Some other error - Bail right away.
|
|
//
|
|
|
|
pRequest->ParseState = UcParseError;
|
|
|
|
UcpDereferenceForReceive(pRequest);
|
|
|
|
// The common parser returns
|
|
// STATUS_INVALID_DEVICE_REQUEST if it finds an
|
|
// illegal response. A better error code would be
|
|
// STATUS_INVALID_NETWORK_RESPONSE. Since we don't
|
|
// want to change the common parser, we'll just eat
|
|
// this error code.
|
|
|
|
if(STATUS_INVALID_DEVICE_REQUEST == Status)
|
|
{
|
|
Status = STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
|
|
UlAcquireSpinLock(&pConnection->SpinLock,
|
|
&OldIrql);
|
|
|
|
UcClearConnectionBusyFlag(
|
|
pConnection,
|
|
CLIENT_CONN_FLAG_RECV_BUSY,
|
|
OldIrql,
|
|
FALSE
|
|
);
|
|
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We can never get here.
|
|
//
|
|
|
|
// ASSERT(FALSE);
|
|
|
|
} // while(TRUE)
|
|
|
|
UlAcquireSpinLock(&pConnection->SpinLock,
|
|
&OldIrql);
|
|
|
|
UcClearConnectionBusyFlag(
|
|
pConnection,
|
|
CLIENT_CONN_FLAG_RECV_BUSY,
|
|
OldIrql,
|
|
FALSE
|
|
);
|
|
|
|
} // while(BytesIndicated)
|
|
|
|
} // for(i=0; i<IndicationCount; i++)
|
|
|
|
//
|
|
// If we have reached here, we have successfully parsed out the
|
|
// buffers
|
|
|
|
if(pConnection->MergeIndication.pBuffer)
|
|
{
|
|
UL_FREE_POOL_WITH_QUOTA(
|
|
pConnection->MergeIndication.pBuffer,
|
|
UC_RESPONSE_TDI_BUFFER_POOL_TAG,
|
|
NonPagedPool,
|
|
pConnection->MergeIndication.BytesAllocated,
|
|
pConnection->pServerInfo->pProcess
|
|
);
|
|
|
|
pConnection->MergeIndication.pBuffer = 0;
|
|
}
|
|
|
|
*pBytesTaken = OriginalBytesIndicated;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine handles failures to the CONNECT verb. In such cases, we have
|
|
to pick up the head request from the pending list that initiated the
|
|
connect & fail it.
|
|
|
|
Arguments:
|
|
|
|
pConnection - pointer to the connection
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UcpHandleConnectVerbFailure(
|
|
IN PUC_CLIENT_CONNECTION pConnection,
|
|
OUT PUCHAR *pIndication,
|
|
OUT PULONG BytesIndicated,
|
|
IN ULONG BytesTaken
|
|
)
|
|
{
|
|
PLIST_ENTRY pEntry;
|
|
KIRQL OldIrql;
|
|
PUC_HTTP_REQUEST pRequest;
|
|
|
|
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
|
|
|
|
//
|
|
// Remove the dummy request form the sent list.
|
|
//
|
|
pEntry = RemoveHeadList(&pConnection->SentRequestList);
|
|
|
|
//
|
|
// Initialize it so that we won't remove it again.
|
|
//
|
|
InitializeListHead(pEntry);
|
|
|
|
if(pConnection->ConnectionState == UcConnectStateProxySslConnect)
|
|
{
|
|
pConnection->ConnectionState = UcConnectStateConnectComplete;
|
|
}
|
|
|
|
//
|
|
// Remove the head request from the pending list &
|
|
// insert in the sent request list.
|
|
//
|
|
ASSERT(IsListEmpty(&pConnection->SentRequestList));
|
|
|
|
if(!IsListEmpty(&pConnection->PendingRequestList))
|
|
{
|
|
|
|
pEntry = RemoveHeadList(&pConnection->PendingRequestList);
|
|
|
|
pRequest = CONTAINING_RECORD(pEntry,
|
|
UC_HTTP_REQUEST,
|
|
Linkage);
|
|
|
|
InsertHeadList(&pConnection->SentRequestList, pEntry);
|
|
|
|
|
|
//
|
|
// Fake a send completion.
|
|
//
|
|
|
|
ASSERT(pRequest->RequestState == UcRequestStateCaptured);
|
|
|
|
pRequest->RequestState = UcRequestStateSent;
|
|
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
|
|
UcRestartMdlSend(pRequest,
|
|
STATUS_SUCCESS,
|
|
0
|
|
);
|
|
|
|
*pIndication -= BytesTaken;
|
|
*BytesIndicated += BytesTaken;
|
|
|
|
}
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine does post parsing book keeping for a request. We update the
|
|
auth cache, proxy auth cache, re-issue NTLM requests, etc, etc.
|
|
|
|
Arguments:
|
|
|
|
pRequest - The fully parsed request
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UcpHandleParsedRequest(
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
OUT PUCHAR *pIndication,
|
|
OUT PULONG BytesIndicated,
|
|
IN ULONG BytesTaken
|
|
)
|
|
{
|
|
PUC_CLIENT_CONNECTION pConnection;
|
|
KIRQL OldIrql;
|
|
|
|
pConnection = pRequest->pConnection;
|
|
|
|
//
|
|
// See if we have to issue a close. There are two cases - If the server
|
|
// has sent a Connection: close header OR if we are doing error detection
|
|
// for POSTs.
|
|
//
|
|
|
|
if(
|
|
(pRequest->ResponseConnectionClose &&
|
|
!pRequest->RequestConnectionClose) ||
|
|
(!(pRequest->ResponseStatusCode >= 200 &&
|
|
pRequest->ResponseStatusCode <=299) &&
|
|
!pRequest->RequestFlags.NoRequestEntityBodies &&
|
|
!pRequest->Renegotiate)
|
|
)
|
|
{
|
|
// Error Detection for POSTS:
|
|
//
|
|
// The scenario here is that the client sends a request with
|
|
// entity body, has not sent all of the entity in one call & is
|
|
// hitting a URI that triggers a error response (e.g. 401).
|
|
|
|
// As per section 8.2.2, we have to terminate the entity send when
|
|
// we see the error response. Now, we can do this in two ways. If the
|
|
// request is sent using content-length encoding, we are forced to
|
|
// close the connection. If the request is sent with chunked encoding,
|
|
// we can send a 0 length chunk.
|
|
//
|
|
// However, we will ALWAYS close the connection. There are two reasons
|
|
// behind this design rationale
|
|
// a. Simplifies the code.
|
|
// b. Allows us to expose a consistent API semantic. Subsequent
|
|
// HttpSendRequestEntityBody will fail with
|
|
// STATUS_CONNECTION_DISCONNECTED. When this happens, the
|
|
// app CAN see the response by posting a response buffer.
|
|
|
|
UC_CLOSE_CONNECTION(pConnection, FALSE, STATUS_CONNECTION_DISCONNECTED);
|
|
}
|
|
|
|
//
|
|
// Now, we have to complete the IRP.
|
|
//
|
|
|
|
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
|
|
|
|
//
|
|
// If we have allocated buffers, we need to adjust BytesWritten
|
|
//
|
|
|
|
if(pRequest->CurrentBuffer.pCurrentBuffer)
|
|
{
|
|
pRequest->CurrentBuffer.pCurrentBuffer->Flags |=
|
|
UC_RESPONSE_BUFFER_FLAG_READY;
|
|
|
|
pRequest->CurrentBuffer.pCurrentBuffer->BytesWritten =
|
|
pRequest->CurrentBuffer.BytesAllocated -
|
|
pRequest->CurrentBuffer.BytesAvailable;
|
|
}
|
|
|
|
switch(pRequest->RequestState)
|
|
{
|
|
case UcRequestStateNoSendCompletePartialData:
|
|
|
|
// Request got parsed completly before we got called in
|
|
// the send complete.
|
|
|
|
pRequest->RequestState = UcRequestStateNoSendCompleteFullData;
|
|
|
|
//
|
|
// If we are pipelining sends, we don't want the next response
|
|
// to be re-parsed into this already parsed request.
|
|
//
|
|
|
|
if(!pRequest->Renegotiate)
|
|
{
|
|
|
|
RemoveEntryList(&pRequest->Linkage);
|
|
|
|
InitializeListHead(&pRequest->Linkage);
|
|
}
|
|
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
break;
|
|
|
|
case UcRequestStateSendCompletePartialData:
|
|
|
|
if(pRequest->RequestFlags.Cancelled == FALSE)
|
|
{
|
|
pRequest->RequestState = UcRequestStateResponseParsed;
|
|
|
|
UcCompleteParsedRequest(
|
|
pRequest,
|
|
pRequest->RequestStatus,
|
|
TRUE,
|
|
OldIrql);
|
|
}
|
|
else
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
|
|
break;
|
|
|
|
default:
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
break;
|
|
}
|
|
|
|
if(pRequest->RequestFlags.ProxySslConnect &&
|
|
pRequest->Renegotiate == FALSE)
|
|
{
|
|
if(pRequest->ResponseStatusCode != 200)
|
|
{
|
|
// Some real error, we need to show this to the app.
|
|
|
|
UcpHandleConnectVerbFailure(
|
|
pConnection,
|
|
pIndication,
|
|
BytesIndicated,
|
|
BytesTaken
|
|
);
|
|
}
|
|
else
|
|
{
|
|
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
|
|
|
|
if(pConnection->ConnectionState == UcConnectStateProxySslConnect)
|
|
{
|
|
pConnection->ConnectionState =
|
|
UcConnectStateProxySslConnectComplete;
|
|
}
|
|
|
|
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine references a request for the receive parser.
|
|
|
|
Arguments:
|
|
|
|
pRequest - The request
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--***************************************************************************/
|
|
BOOLEAN
|
|
UcpReferenceForReceive(
|
|
IN PUC_HTTP_REQUEST pRequest
|
|
)
|
|
{
|
|
LONG OldReceiveBusy;
|
|
|
|
|
|
//
|
|
// Flag this request so that it doesn't get cancelled beneath us
|
|
//
|
|
|
|
OldReceiveBusy = InterlockedExchange(
|
|
&pRequest->ReceiveBusy,
|
|
UC_REQUEST_RECEIVE_BUSY
|
|
);
|
|
|
|
if(OldReceiveBusy == UC_REQUEST_RECEIVE_CANCELLED)
|
|
{
|
|
// This request already got cancelled
|
|
return FALSE;
|
|
}
|
|
|
|
ASSERT(OldReceiveBusy == UC_REQUEST_RECEIVE_READY);
|
|
|
|
UC_REFERENCE_REQUEST(pRequest);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine de-references a request for the receive parser. Resumes
|
|
any cleanup if required.
|
|
|
|
Arguments:
|
|
|
|
pRequest - The request
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UcpDereferenceForReceive(
|
|
IN PUC_HTTP_REQUEST pRequest
|
|
)
|
|
{
|
|
LONG OldReceiveBusy;
|
|
|
|
OldReceiveBusy = InterlockedExchange(
|
|
&pRequest->ReceiveBusy,
|
|
UC_REQUEST_RECEIVE_READY
|
|
);
|
|
|
|
if(OldReceiveBusy == UC_REQUEST_RECEIVE_CANCELLED)
|
|
{
|
|
//
|
|
// The reqeust got cancelled when the receive thread was running,
|
|
// we now have to resume it.
|
|
//
|
|
|
|
IoAcquireCancelSpinLock(&pRequest->RequestIRP->CancelIrql);
|
|
|
|
UC_WRITE_TRACE_LOG(
|
|
g_pUcTraceLog,
|
|
UC_ACTION_REQUEST_CLEAN_RESUMED,
|
|
pRequest->pConnection,
|
|
pRequest,
|
|
pRequest->RequestIRP,
|
|
UlongToPtr((ULONG)STATUS_CANCELLED)
|
|
);
|
|
|
|
UcCancelSentRequest(
|
|
NULL,
|
|
pRequest->RequestIRP
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(OldReceiveBusy == UC_REQUEST_RECEIVE_BUSY);
|
|
}
|
|
|
|
UC_DEREFERENCE_REQUEST(pRequest);
|
|
}
|