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.
3603 lines
109 KiB
3603 lines
109 KiB
/*++
|
|
|
|
Copyright (c) 1998-2002 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
ucparse.c
|
|
|
|
Abstract:
|
|
|
|
Contains all of the kernel mode HTTP parsing code for the client
|
|
|
|
Author:
|
|
|
|
Rajesh Sundaram (rajeshsu) 10-Oct-2000 Implemented client parser
|
|
|
|
Revision History:
|
|
|
|
Rajesh Sundaram (rajeshsu) 15-Feb-2002 Moved from parse.c
|
|
|
|
--*/
|
|
|
|
|
|
#include "precomp.h"
|
|
|
|
#include "ucparse.h"
|
|
#include "ucrcv.h"
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
|
|
#pragma alloc_text( PAGEUC, UcComputeRequestHeaderSize)
|
|
#pragma alloc_text( PAGEUC, UcGenerateRequestHeaders)
|
|
#pragma alloc_text( PAGEUC, UcGenerateContentLength)
|
|
#pragma alloc_text( PAGEUC, UcComputeConnectVerbHeaderSize)
|
|
#pragma alloc_text( PAGEUC, UcGenerateConnectVerbHeader)
|
|
#pragma alloc_text( PAGEUC, UcCheckDisconnectInfo)
|
|
#pragma alloc_text( PAGEUC, UcCanonicalizeURI)
|
|
#pragma alloc_text( PAGEUC, UcParseWWWAuthenticateHeader)
|
|
#pragma alloc_text( PAGEUC, UcpFindAttribValuePair)
|
|
#pragma alloc_text( PAGEUC, UcpParseAuthParams)
|
|
#pragma alloc_text( PAGEUC, UcpParseAuthBlob)
|
|
|
|
#pragma alloc_text( PAGEUC, UcFindHeaderNameEnd)
|
|
#pragma alloc_text( PAGEUC, UcpLookupHeader)
|
|
#pragma alloc_text( PAGEUC, UcParseHeader)
|
|
#pragma alloc_text( PAGEUC, UcSingleHeaderHandler)
|
|
#pragma alloc_text( PAGEUC, UcMultipleHeaderHandler)
|
|
#pragma alloc_text( PAGEUC, UcAuthenticateHeaderHandler)
|
|
#pragma alloc_text( PAGEUC, UcContentLengthHeaderHandler)
|
|
#pragma alloc_text( PAGEUC, UcTransferEncodingHeaderHandler)
|
|
#pragma alloc_text( PAGEUC, UcConnectionHeaderHandler)
|
|
#pragma alloc_text( PAGEUC, UcContentTypeHeaderHandler)
|
|
|
|
#endif
|
|
|
|
|
|
//
|
|
// The enum->verb translation table
|
|
//
|
|
LONG_VERB_ENTRY EnumVerbTable[HttpVerbMaximum] =
|
|
{
|
|
CREATE_LONG_VERB_ENTRY(GET), // Unparsed defaults to GET
|
|
CREATE_LONG_VERB_ENTRY(GET), // Unknown defaults to GET
|
|
CREATE_LONG_VERB_ENTRY(GET), // Invalid defaults to GET
|
|
CREATE_LONG_VERB_ENTRY(OPTIONS),
|
|
CREATE_LONG_VERB_ENTRY(GET),
|
|
CREATE_LONG_VERB_ENTRY(HEAD),
|
|
CREATE_LONG_VERB_ENTRY(POST),
|
|
CREATE_LONG_VERB_ENTRY(PUT),
|
|
CREATE_LONG_VERB_ENTRY(DELETE),
|
|
CREATE_LONG_VERB_ENTRY(TRACE),
|
|
CREATE_LONG_VERB_ENTRY(CONNECT),
|
|
CREATE_LONG_VERB_ENTRY(TRACK),
|
|
CREATE_LONG_VERB_ENTRY(MOVE),
|
|
CREATE_LONG_VERB_ENTRY(COPY),
|
|
CREATE_LONG_VERB_ENTRY(PROPFIND),
|
|
CREATE_LONG_VERB_ENTRY(PROPPATCH),
|
|
CREATE_LONG_VERB_ENTRY(MKCOL),
|
|
CREATE_LONG_VERB_ENTRY(LOCK),
|
|
CREATE_LONG_VERB_ENTRY(UNLOCK),
|
|
CREATE_LONG_VERB_ENTRY(SEARCH)
|
|
};
|
|
|
|
|
|
//
|
|
// A macro to process the header value.
|
|
// It stips leading LWS and trailing LWS and CRLF from a header value.
|
|
//
|
|
|
|
#define UC_PROCESS_HEADER_VALUE(pHeaderValue, HeaderValueLength) \
|
|
{ \
|
|
while((HeaderValueLength) > 0 && IS_HTTP_LWS(*(pHeaderValue))) \
|
|
{ \
|
|
(pHeaderValue)++; \
|
|
(HeaderValueLength)--; \
|
|
} \
|
|
while((HeaderValueLength) > 0 && \
|
|
IS_HTTP_WS_TOKEN((pHeaderValue)[(HeaderValueLength)-1])) \
|
|
{ \
|
|
(HeaderValueLength)--; \
|
|
} \
|
|
}
|
|
|
|
//
|
|
// Private macros.
|
|
//
|
|
|
|
//
|
|
// COPY_DATA_TO_BUFFER
|
|
// copies source buffer to destination buffer after making sure that
|
|
// the destination buffer can hold the data.
|
|
//
|
|
|
|
#define COPY_DATA_TO_BUFFER(pDest, DestLen, pSrc, SrcLen) \
|
|
do { \
|
|
if ((SrcLen) > (DestLen)) \
|
|
{ \
|
|
ASSERT(FALSE); \
|
|
return STATUS_BUFFER_TOO_SMALL; \
|
|
} \
|
|
RtlCopyMemory((pDest), (pSrc), (SrcLen)); \
|
|
(pDest) += (SrcLen); \
|
|
(DestLen) -= (SrcLen); \
|
|
} while (0)
|
|
|
|
//
|
|
// ADVANCE_POINTER
|
|
// Advances a pointer to buffer after making sure that the new pointer
|
|
// does not point beyond the buffer.
|
|
//
|
|
|
|
#define ADVANCE_POINTER(pDest, DestLen, SrcLen) \
|
|
do { \
|
|
if ((SrcLen) > (DestLen)) \
|
|
{ \
|
|
ASSERT(FALSE); \
|
|
return STATUS_BUFFER_TOO_SMALL; \
|
|
} \
|
|
(pDest) += (SrcLen); \
|
|
(DestLen) -= (SrcLen); \
|
|
} while (0)
|
|
|
|
//
|
|
// COPY_UCHAR_TO_BUFFER
|
|
//
|
|
|
|
#define COPY_UCHAR_TO_BUFFER(pDest, DestLen, UChar) \
|
|
do { \
|
|
if (sizeof(UCHAR) > (DestLen)) \
|
|
{ \
|
|
ASSERT(FALSE); \
|
|
return STATUS_BUFFER_TOO_SMALL; \
|
|
} \
|
|
*(pDest)++ = (UChar); \
|
|
(DestLen) -= sizeof(UCHAR); \
|
|
} while (0)
|
|
|
|
//
|
|
// COPY_SP_TO_BUFFER
|
|
//
|
|
|
|
#define COPY_SP_TO_BUFFER(pDest, DestLen) \
|
|
COPY_UCHAR_TO_BUFFER(pDest, DestLen, SP)
|
|
|
|
//
|
|
// COPY_CRLF_TO_BUFFER
|
|
//
|
|
|
|
#define COPY_CRLF_TO_BUFFER(pDest, DestLen) \
|
|
do { \
|
|
if (CRLF_SIZE > (DestLen)) \
|
|
{ \
|
|
ASSERT(FALSE); \
|
|
return STATUS_BUFFER_TOO_SMALL; \
|
|
} \
|
|
*((UNALIGNED64 USHORT *)(pDest)) = CRLF; \
|
|
(pDest) += CRLF_SIZE; \
|
|
(DestLen) -= CRLF_SIZE; \
|
|
} while (0)
|
|
|
|
//
|
|
// COPY_HEADER_NAME_SP_TO_BUFFER
|
|
//
|
|
|
|
#define COPY_HEADER_NAME_SP_TO_BUFFER(pBuffer, BufferLen, i) \
|
|
do { \
|
|
PHEADER_MAP_ENTRY _pEntry; \
|
|
_pEntry = &(g_RequestHeaderMapTable[g_RequestHeaderMap[i]]); \
|
|
\
|
|
if (_pEntry->HeaderLength + sizeof(UCHAR) > (BufferLen)) \
|
|
{ \
|
|
ASSERT(FALSE); \
|
|
return STATUS_BUFFER_TOO_SMALL; \
|
|
} \
|
|
\
|
|
(BufferLen) -= (_pEntry->HeaderLength + sizeof(UCHAR)); \
|
|
\
|
|
RtlCopyMemory((pBuffer), \
|
|
_pEntry->MixedCaseHeader, \
|
|
_pEntry->HeaderLength); \
|
|
\
|
|
(pBuffer) += _pEntry->HeaderLength; \
|
|
*(pBuffer)++ = SP; \
|
|
\
|
|
} while (0)
|
|
|
|
|
|
//
|
|
// Request Generator functions.
|
|
//
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Figures out how big the fixed headers are. Fixed headers include the
|
|
request line, and any headers that don't have to be generated for
|
|
every request (such as Date and Connection).
|
|
|
|
The final CRLF separating headers from body is considered part of
|
|
the variable headers.
|
|
|
|
Arguments:
|
|
|
|
pServInfo - The server information.
|
|
pHttpRequest - The request structure.
|
|
bChunked - a boolean that tells if the encoding is chunked.
|
|
bContentLengthHeader - a boolean that tells if the Content-Length header
|
|
is to be generated.
|
|
UriLength - An OUT parameter that will contain the URI length.
|
|
|
|
Return Values:
|
|
|
|
The number of bytes in the fixed headers.
|
|
|
|
--***************************************************************************/
|
|
ULONG
|
|
UcComputeRequestHeaderSize(
|
|
IN PUC_PROCESS_SERVER_INFORMATION pServInfo,
|
|
IN PHTTP_REQUEST pHttpRequest,
|
|
IN BOOLEAN bChunked,
|
|
IN BOOLEAN bContentLengthHeader,
|
|
IN PUC_HTTP_AUTH pAuth,
|
|
IN PUC_HTTP_AUTH pProxyAuth,
|
|
IN PBOOLEAN bPreAuth,
|
|
IN PBOOLEAN bProxyPreAuth
|
|
)
|
|
{
|
|
ULONG MethodLength, HeaderLength;
|
|
ULONG i;
|
|
PHTTP_KNOWN_HEADER pKnownHeaders;
|
|
PHTTP_UNKNOWN_HEADER pUnknownHeaders;
|
|
PHEADER_MAP_ENTRY pEntry;
|
|
|
|
pKnownHeaders = pHttpRequest->Headers.KnownHeaders;
|
|
pUnknownHeaders = pHttpRequest->Headers.pUnknownHeaders;
|
|
|
|
ASSERT(*bPreAuth == FALSE);
|
|
ASSERT(*bProxyPreAuth == FALSE);
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
HeaderLength = 0;
|
|
|
|
if(pHttpRequest->UnknownVerbLength)
|
|
{
|
|
//
|
|
// The app has passed an unknown verb.
|
|
//
|
|
|
|
HeaderLength = pHttpRequest->UnknownVerbLength;
|
|
}
|
|
else
|
|
{
|
|
// Enums are signed, so we have to do the < 0 check!
|
|
|
|
if(pHttpRequest->Verb < 0 ||
|
|
pHttpRequest->Verb >= HttpVerbMaximum ||
|
|
pHttpRequest->Verb == HttpVerbUnparsed ||
|
|
pHttpRequest->Verb == HttpVerbUnknown ||
|
|
pHttpRequest->Verb == HttpVerbInvalid
|
|
)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
HeaderLength = EnumVerbTable[pHttpRequest->Verb].RawVerbLength;
|
|
}
|
|
}
|
|
|
|
MethodLength = HeaderLength;
|
|
|
|
// SP
|
|
HeaderLength ++;
|
|
|
|
//
|
|
// If we are going through a proxy, we need to compute space for the
|
|
// scheme & the server name.
|
|
//
|
|
if(pServInfo->bProxy)
|
|
{
|
|
HeaderLength += pServInfo->pServerInfo->AnsiServerNameLength;
|
|
|
|
if(pServInfo->bSecure)
|
|
{
|
|
HeaderLength += HTTPS_PREFIX_ANSI_LENGTH;
|
|
}
|
|
else
|
|
{
|
|
HeaderLength += HTTP_PREFIX_ANSI_LENGTH;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Formulate the version. We'll support only 1.0 or 1.1 requests,
|
|
// so we allocate space only for HTTP/1.1
|
|
//
|
|
|
|
HeaderLength += VERSION_SIZE;
|
|
|
|
HeaderLength += CRLF_SIZE; // CRLF
|
|
|
|
//
|
|
// Loop through the known headers.
|
|
//
|
|
|
|
for (i = 0; i < HttpHeaderRequestMaximum; ++i)
|
|
{
|
|
ULONG RawValueLength = pKnownHeaders[i].RawValueLength;
|
|
|
|
//
|
|
// skip some headers that we generate.
|
|
//
|
|
|
|
if (RawValueLength > 0 &&
|
|
!g_RequestHeaderMapTable[g_RequestHeaderMap[i]].AutoGenerate)
|
|
{
|
|
HeaderLength += g_RequestHeaderMapTable[
|
|
g_RequestHeaderMap[i]
|
|
].HeaderLength + // Header-Name
|
|
1 + // SP
|
|
RawValueLength + // Header-Value
|
|
CRLF_SIZE; // CRLF
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Include the default headers we may need to generate on behalf of the
|
|
// application. We do this only if the application has not specified any
|
|
// headers by itself.
|
|
//
|
|
|
|
pEntry = &(g_RequestHeaderMapTable[g_RequestHeaderMap[
|
|
HttpHeaderContentLength]]);
|
|
|
|
if(bContentLengthHeader)
|
|
{
|
|
HeaderLength += (pEntry->HeaderLength + 1 + CRLF_SIZE);
|
|
HeaderLength += MAX_ULONGLONG_STR;
|
|
}
|
|
else
|
|
{
|
|
if(!bChunked)
|
|
{
|
|
//
|
|
// We are going to compute the content length at some point
|
|
// in the future. Might as well allocate a content length
|
|
// for this right away. We do this to avoid allocating a
|
|
// new buffer + MDL when we know the actual length.
|
|
//
|
|
|
|
HeaderLength += (pEntry->HeaderLength + 1 + CRLF_SIZE);
|
|
HeaderLength += MAX_ULONGLONG_STR;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we are using chunked encoding, we need to update the
|
|
// TransferEncoding field. If the app has already passed a
|
|
// value, we need to append "chunked" to the end of the Transfer
|
|
// encoding.
|
|
//
|
|
|
|
if(bChunked)
|
|
{
|
|
pEntry = &(g_RequestHeaderMapTable[g_RequestHeaderMap[
|
|
HttpHeaderTransferEncoding]]);
|
|
|
|
HeaderLength += (pEntry->HeaderLength + 1 + CRLF_SIZE);
|
|
|
|
HeaderLength += CHUNKED_HDR_LENGTH;
|
|
}
|
|
|
|
//
|
|
// Add a host header - We'll override the app's header, even if they
|
|
// have passed one. This is done by the "AutoGenerate" flag
|
|
//
|
|
|
|
HeaderLength += g_RequestHeaderMapTable[
|
|
g_RequestHeaderMap[HttpHeaderHost]
|
|
].HeaderLength + // Header-Name
|
|
1 + // SP
|
|
pServInfo->pServerInfo->AnsiServerNameLength + // Value
|
|
CRLF_SIZE; // CRLF
|
|
|
|
//
|
|
// And the unknown headers (this might throw an exception).
|
|
//
|
|
|
|
if (pUnknownHeaders != NULL)
|
|
{
|
|
for (i = 0 ; i < pHttpRequest->Headers.UnknownHeaderCount; ++i)
|
|
{
|
|
if (pUnknownHeaders[i].NameLength > 0)
|
|
{
|
|
HeaderLength +=
|
|
pUnknownHeaders[i].NameLength + // Header-Name
|
|
1 + // ':'
|
|
1 + // SP
|
|
pUnknownHeaders[i].RawValueLength + // Header-Value
|
|
CRLF_SIZE; // CRLF
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if(pHttpRequest->Headers.KnownHeaders
|
|
[HttpHeaderAuthorization].RawValueLength == 0)
|
|
{
|
|
if(pAuth)
|
|
{
|
|
// User has passed auth credentials. We'll just use this.
|
|
|
|
HeaderLength += pAuth->RequestAuthHeaderMaxLength;
|
|
}
|
|
else if((pServInfo->PreAuthEnable &&
|
|
pServInfo->GreatestAuthHeaderMaxLength))
|
|
{
|
|
HeaderLength += pServInfo->GreatestAuthHeaderMaxLength;
|
|
*bPreAuth = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// User has passed their creds, let's just use that. The space
|
|
// for this was accounted when we computed the known header size.
|
|
//
|
|
}
|
|
|
|
//
|
|
// Do the same thing for Proxy Auth.
|
|
//
|
|
|
|
if(pHttpRequest->Headers.KnownHeaders
|
|
[HttpHeaderProxyAuthorization].RawValueLength == 0)
|
|
{
|
|
if(pProxyAuth)
|
|
{
|
|
HeaderLength += pProxyAuth->RequestAuthHeaderMaxLength;
|
|
}
|
|
else if(pServInfo->ProxyPreAuthEnable && pServInfo->pProxyAuthInfo)
|
|
{
|
|
HeaderLength +=
|
|
pServInfo->pProxyAuthInfo->RequestAuthHeaderMaxLength;
|
|
*bProxyPreAuth = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// User has passed their creds, let's just use that. The space
|
|
// for this was accounted when we computed the known header size.
|
|
//
|
|
}
|
|
|
|
// Header terminator.
|
|
HeaderLength += CRLF_SIZE; // CRLF
|
|
|
|
return HeaderLength;
|
|
|
|
} // UcComputeRequestHeaderSize
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Generates the header for HTTP requests.
|
|
|
|
Arguments:
|
|
|
|
pRequest - The HTTP request structure passed by the app
|
|
pKeRequest - Our internal representation of the structure.
|
|
pAuth - The Auth credentials as passed by the app.
|
|
pProxyAuth - The Proxy auth credentials as passed by the app.
|
|
bChunked - To indicate if we are using chunked encoding.
|
|
ContentLength - Content Length
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UcGenerateRequestHeaders(
|
|
IN PHTTP_REQUEST pRequest,
|
|
IN PUC_HTTP_REQUEST pKeRequest,
|
|
IN BOOLEAN bChunked,
|
|
IN ULONGLONG ContentLength
|
|
)
|
|
{
|
|
PUCHAR pStartHeaders;
|
|
ULONG BytesCopied;
|
|
ULONG i;
|
|
PHTTP_UNKNOWN_HEADER pUnknownHeaders;
|
|
ULONG RemainingLen = pKeRequest->MaxHeaderLength;
|
|
PUCHAR pBuffer = pKeRequest->pHeaders;
|
|
PSTR pMethod;
|
|
ULONG MethodLength;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
BOOLEAN bProxySslRequest = FALSE;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pRequest != NULL);
|
|
ASSERT(pBuffer != NULL && RemainingLen > 0);
|
|
|
|
//
|
|
// Remember the start of the headers buffer.
|
|
//
|
|
|
|
pStartHeaders = pBuffer;
|
|
|
|
//
|
|
// Generate the request line.
|
|
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
|
//
|
|
|
|
pMethod = (PSTR) pBuffer;
|
|
|
|
if(pRequest->UnknownVerbLength)
|
|
{
|
|
//
|
|
// The app has passed an unknown verb.
|
|
//
|
|
|
|
COPY_DATA_TO_BUFFER(pBuffer,
|
|
RemainingLen,
|
|
pRequest->pUnknownVerb,
|
|
pRequest->UnknownVerbLength);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(0 <= pRequest->Verb && pRequest->Verb < HttpVerbMaximum);
|
|
|
|
COPY_DATA_TO_BUFFER(pBuffer,
|
|
RemainingLen,
|
|
EnumVerbTable[pRequest->Verb].RawVerb,
|
|
EnumVerbTable[pRequest->Verb].RawVerbLength);
|
|
}
|
|
|
|
MethodLength = (ULONG)((PUCHAR)pBuffer - (PUCHAR)pMethod);
|
|
|
|
//
|
|
// Add a SP.
|
|
//
|
|
|
|
COPY_SP_TO_BUFFER(pBuffer, RemainingLen);
|
|
|
|
//
|
|
// Copy the request URI.
|
|
//
|
|
|
|
if(pKeRequest->pServerInfo->bProxy)
|
|
{
|
|
//
|
|
// Normally, when a proxy is present, an absoluteURI is generated
|
|
// in the request. In case of SSL, the proxy mainly acts as a tunnel.
|
|
// We, therefore, don't generate an absoluteURI but generate
|
|
// abs_path instead.
|
|
//
|
|
|
|
if(pKeRequest->pServerInfo->bSecure)
|
|
{
|
|
bProxySslRequest = TRUE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Add "http://" prefix.
|
|
//
|
|
|
|
COPY_DATA_TO_BUFFER(pBuffer,
|
|
RemainingLen,
|
|
HTTP_PREFIX_ANSI,
|
|
HTTP_PREFIX_ANSI_LENGTH);
|
|
|
|
//
|
|
// Now, copy the server name.
|
|
//
|
|
|
|
COPY_DATA_TO_BUFFER(
|
|
pBuffer,
|
|
RemainingLen,
|
|
pKeRequest->pServerInfo->pServerInfo->pAnsiServerName,
|
|
pKeRequest->pServerInfo->pServerInfo->AnsiServerNameLength
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy the Uri.
|
|
//
|
|
|
|
COPY_DATA_TO_BUFFER(pBuffer,
|
|
RemainingLen,
|
|
pKeRequest->pUri,
|
|
pKeRequest->UriLength);
|
|
|
|
//
|
|
// Add a SP.
|
|
//
|
|
|
|
COPY_SP_TO_BUFFER(pBuffer, RemainingLen);
|
|
|
|
//
|
|
// Add the protocol.
|
|
//
|
|
|
|
if(pRequest->Version.MajorVersion == 1)
|
|
{
|
|
if(pRequest->Version.MinorVersion == 1)
|
|
{
|
|
//
|
|
// Copy "HTTP/1.1" string.
|
|
//
|
|
|
|
COPY_DATA_TO_BUFFER(pBuffer,
|
|
RemainingLen,
|
|
HTTP_VERSION_11,
|
|
VERSION_SIZE);
|
|
}
|
|
else if(pRequest->Version.MinorVersion == 0)
|
|
{
|
|
//
|
|
// Copy "HTTP/1.0" string.
|
|
//
|
|
|
|
COPY_DATA_TO_BUFFER(pBuffer,
|
|
RemainingLen,
|
|
HTTP_VERSION_10,
|
|
VERSION_SIZE);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We don't support minor versions > 1.
|
|
//
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We don't support major version != 1.
|
|
//
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Terminate the request-line with a CRLF.
|
|
//
|
|
|
|
COPY_CRLF_TO_BUFFER(pBuffer, RemainingLen);
|
|
|
|
//
|
|
// Determine if we have to close the TCP connection after sending
|
|
// this request.
|
|
//
|
|
|
|
pKeRequest->RequestConnectionClose =
|
|
UcCheckDisconnectInfo(&pRequest->Version,
|
|
pRequest->Headers.KnownHeaders);
|
|
|
|
//
|
|
// Loop through the known headers.
|
|
//
|
|
|
|
for (i = 0; i < HttpHeaderRequestMaximum; ++i)
|
|
{
|
|
//
|
|
// skip some headers we'll generate
|
|
//
|
|
|
|
if (pRequest->Headers.KnownHeaders[i].RawValueLength > 0)
|
|
{
|
|
PHEADER_MAP_ENTRY pEntry;
|
|
|
|
pEntry = &(g_RequestHeaderMapTable[g_RequestHeaderMap[i]]);
|
|
|
|
if(pEntry->AutoGenerate == FALSE)
|
|
{
|
|
//
|
|
// Copy known header name followed by a ':' and a SP.
|
|
//
|
|
|
|
COPY_HEADER_NAME_SP_TO_BUFFER(pBuffer, RemainingLen, i);
|
|
|
|
//
|
|
// Copy known header value.
|
|
//
|
|
|
|
COPY_DATA_TO_BUFFER(
|
|
pBuffer,
|
|
RemainingLen,
|
|
pRequest->Headers.KnownHeaders[i].pRawValue,
|
|
pRequest->Headers.KnownHeaders[i].RawValueLength
|
|
);
|
|
|
|
//
|
|
// Terminate the header by CRLF.
|
|
//
|
|
|
|
COPY_CRLF_TO_BUFFER(pBuffer, RemainingLen);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add a host header - We'll override the app's header, even if they
|
|
// have passed one. This is done by the "AutoGenerate" flag.
|
|
//
|
|
|
|
// Copy "Host: " string.
|
|
COPY_HEADER_NAME_SP_TO_BUFFER(pBuffer, RemainingLen, HttpHeaderHost);
|
|
|
|
// Copy server name.
|
|
COPY_DATA_TO_BUFFER(
|
|
pBuffer,
|
|
RemainingLen,
|
|
pKeRequest->pServerInfo->pServerInfo->pAnsiServerName,
|
|
pKeRequest->pServerInfo->pServerInfo->AnsiServerNameLength
|
|
);
|
|
|
|
// Terminate the host header with a CRLF.
|
|
COPY_CRLF_TO_BUFFER(pBuffer, RemainingLen);
|
|
|
|
//
|
|
// Generate the content length header.
|
|
//
|
|
|
|
if(ContentLength)
|
|
{
|
|
Status = UcGenerateContentLength(ContentLength,
|
|
pBuffer,
|
|
RemainingLen,
|
|
&BytesCopied);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
ASSERT(BytesCopied <= RemainingLen);
|
|
|
|
ADVANCE_POINTER(pBuffer, RemainingLen, BytesCopied);
|
|
}
|
|
|
|
if(bChunked)
|
|
{
|
|
//
|
|
// If we are using chunked encoding, we have to add the
|
|
// "Transfer-Encoding: chunked" header.
|
|
//
|
|
|
|
// Copy header name - "Transfer-Encoding: ".
|
|
COPY_HEADER_NAME_SP_TO_BUFFER(pBuffer,
|
|
RemainingLen,
|
|
HttpHeaderTransferEncoding);
|
|
|
|
// Copy header value - "chunked".
|
|
COPY_DATA_TO_BUFFER(pBuffer,
|
|
RemainingLen,
|
|
CHUNKED_HDR,
|
|
CHUNKED_HDR_LENGTH);
|
|
|
|
// Terminate header with CRLF.
|
|
COPY_CRLF_TO_BUFFER(pBuffer, RemainingLen);
|
|
}
|
|
|
|
//
|
|
// And now the unknown headers
|
|
//
|
|
|
|
pUnknownHeaders = pRequest->Headers.pUnknownHeaders;
|
|
if (pUnknownHeaders != NULL)
|
|
{
|
|
for (i = 0 ; i < pRequest->Headers.UnknownHeaderCount; ++i)
|
|
{
|
|
if (pUnknownHeaders[i].NameLength > 0)
|
|
{
|
|
// First, copy the header name.
|
|
COPY_DATA_TO_BUFFER(pBuffer,
|
|
RemainingLen,
|
|
pUnknownHeaders[i].pName,
|
|
pUnknownHeaders[i].NameLength);
|
|
|
|
// Copy ':' after header name.
|
|
COPY_UCHAR_TO_BUFFER(pBuffer, RemainingLen, ':');
|
|
|
|
// Add a space.
|
|
COPY_SP_TO_BUFFER(pBuffer, RemainingLen);
|
|
|
|
// Now, copy the header value.
|
|
COPY_DATA_TO_BUFFER(pBuffer,
|
|
RemainingLen,
|
|
pUnknownHeaders[i].pRawValue,
|
|
pUnknownHeaders[i].RawValueLength);
|
|
|
|
// Terminate the header with a CRLF.
|
|
COPY_CRLF_TO_BUFFER(pBuffer, RemainingLen);
|
|
|
|
} // if (pUnknownHeaders[i].NameLength > 0)
|
|
}
|
|
} // if (pUnknownHeaders != NULL)
|
|
|
|
//
|
|
// Generate the Authorization headers. This should be done at the very last
|
|
// since we might have to update the Authorization header & re-issue the
|
|
// request (for NTLM/kerberos). The size of the new Authorization header
|
|
// will not be the same as the old one.
|
|
//
|
|
// If the Authorization header is at the end, we can easily re-generate it.
|
|
// and append it with the existing headers.
|
|
//
|
|
// The one exception to this rule is content-length - If the app is
|
|
// indicating data in chunks and has not specified a content-length, it will
|
|
// get generated at the very end. But this is no big deal. We can easily
|
|
// re-generate the content-length hdr.
|
|
//
|
|
|
|
if(pRequest->Headers.KnownHeaders[HttpHeaderAuthorization].RawValueLength
|
|
== 0)
|
|
{
|
|
if(pKeRequest->pAuthInfo)
|
|
{
|
|
//
|
|
// User has supplied credentials, we have to use it.
|
|
//
|
|
|
|
Status =
|
|
UcGenerateAuthHeaderFromCredentials(
|
|
pKeRequest->pServerInfo,
|
|
pKeRequest->pAuthInfo,
|
|
HttpHeaderAuthorization,
|
|
pMethod,
|
|
MethodLength,
|
|
pKeRequest->pUri,
|
|
pKeRequest->UriLength,
|
|
pBuffer,
|
|
RemainingLen,
|
|
&BytesCopied,
|
|
&pKeRequest->DontFreeMdls
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return(Status);
|
|
}
|
|
|
|
ASSERT(BytesCopied <= RemainingLen);
|
|
ADVANCE_POINTER(pBuffer, RemainingLen, BytesCopied);
|
|
}
|
|
else if (pKeRequest->RequestFlags.UsePreAuth)
|
|
{
|
|
//
|
|
// See if PreAuth is enabled. We cannot check for the
|
|
// pServerInfo->PreAuth flag here. We check for this
|
|
// in the UcpComputeAuthHeaderSize function. If we check
|
|
// for this here, we cannot be sure that this flag was
|
|
// set when we called UcpComputeAuthHeaderSize
|
|
//
|
|
|
|
UcFindURIEntry(pKeRequest->pServerInfo,
|
|
pKeRequest->pUri,
|
|
pKeRequest,
|
|
pMethod,
|
|
MethodLength,
|
|
pBuffer,
|
|
RemainingLen,
|
|
&BytesCopied);
|
|
|
|
ASSERT(BytesCopied <= RemainingLen);
|
|
ADVANCE_POINTER(pBuffer, RemainingLen, BytesCopied);
|
|
}
|
|
}
|
|
|
|
if (pRequest->Headers.KnownHeaders[HttpHeaderProxyAuthorization].
|
|
RawValueLength == 0)
|
|
{
|
|
|
|
if (pKeRequest->pProxyAuthInfo)
|
|
{
|
|
if(!bProxySslRequest)
|
|
{
|
|
Status =
|
|
UcGenerateAuthHeaderFromCredentials(
|
|
pKeRequest->pServerInfo,
|
|
pKeRequest->pProxyAuthInfo,
|
|
HttpHeaderProxyAuthorization,
|
|
pMethod,
|
|
MethodLength,
|
|
pKeRequest->pUri,
|
|
pKeRequest->UriLength,
|
|
pBuffer,
|
|
RemainingLen,
|
|
&BytesCopied,
|
|
&pKeRequest->DontFreeMdls
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return(Status);
|
|
}
|
|
|
|
ASSERT(BytesCopied <= RemainingLen);
|
|
ADVANCE_POINTER(pBuffer, RemainingLen, BytesCopied);
|
|
}
|
|
}
|
|
else if (pKeRequest->RequestFlags.UseProxyPreAuth && !bProxySslRequest)
|
|
{
|
|
Status = UcGenerateProxyAuthHeaderFromCache(pKeRequest,
|
|
pMethod,
|
|
MethodLength,
|
|
pBuffer,
|
|
RemainingLen,
|
|
&BytesCopied);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
ASSERT(BytesCopied <= RemainingLen);
|
|
ADVANCE_POINTER(pBuffer, RemainingLen, BytesCopied);
|
|
}
|
|
}
|
|
|
|
pKeRequest->HeaderLength = DIFF(pBuffer - pStartHeaders);
|
|
|
|
//
|
|
// Ensure we didn't use too much.
|
|
//
|
|
|
|
ASSERT(pBuffer <= pStartHeaders + pKeRequest->MaxHeaderLength);
|
|
|
|
return Status;
|
|
|
|
} // UcGenerateRequestHeaders
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Generates the content length header.
|
|
|
|
Arguments:
|
|
|
|
ContentLength - Supplies the content length.
|
|
pBuffer - Supplies pointer to the output buffer.
|
|
BufferLen - Supplies length of the output buffer.
|
|
BytesWritten - Returns the number of bytes consumed from the buffer.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UcGenerateContentLength(
|
|
IN ULONGLONG ContentLength,
|
|
IN PUCHAR pBuffer,
|
|
IN ULONG BufferLen,
|
|
OUT PULONG pBytesWritten
|
|
)
|
|
{
|
|
PUCHAR pBufferTemp;
|
|
ULONG BufferLenTemp;
|
|
|
|
// Initialize locals.
|
|
pBufferTemp = pBuffer;
|
|
BufferLenTemp = BufferLen;
|
|
|
|
*pBytesWritten = 0;
|
|
|
|
//
|
|
// Copy "Content-Length:" header name and a space.
|
|
//
|
|
|
|
COPY_HEADER_NAME_SP_TO_BUFFER(pBuffer,
|
|
BufferLen,
|
|
HttpHeaderContentLength);
|
|
|
|
//
|
|
// Check if there is enough space to copy ULONGLONG content length
|
|
// in string format and a CRLF.
|
|
//
|
|
|
|
if (MAX_ULONGLONG_STR + CRLF_SIZE > BufferLen)
|
|
{
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
pBuffer = (PUCHAR) UlStrPrintUlonglong((PCHAR) pBuffer,
|
|
ContentLength,
|
|
'\0');
|
|
//
|
|
//
|
|
//
|
|
BufferLen -= MAX_ULONGLONG_STR;
|
|
|
|
COPY_CRLF_TO_BUFFER(pBuffer, BufferLen);
|
|
|
|
*pBytesWritten = (ULONG)(pBuffer - pBufferTemp);
|
|
ASSERT(*pBytesWritten <= BufferLenTemp);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Figures out the header size for the CONNECT verb.
|
|
|
|
|
|
Arguments:
|
|
|
|
pServInfo - The server information.
|
|
pProxyAuthInfo - Proxy auth info.
|
|
|
|
Return Values:
|
|
|
|
The number of bytes in the fixed headers.
|
|
|
|
--***************************************************************************/
|
|
ULONG
|
|
UcComputeConnectVerbHeaderSize(
|
|
IN PUC_PROCESS_SERVER_INFORMATION pServInfo,
|
|
IN PUC_HTTP_AUTH pProxyAuthInfo
|
|
)
|
|
{
|
|
|
|
#define PROXY_CONNECTION_KEEPALIVE "Proxy-Connection: Keep-Alive"
|
|
#define PROXY_CONNECTION_KEEPALIVE_SIZE (sizeof(PROXY_CONNECTION_KEEPALIVE)-1)
|
|
|
|
ULONG HeaderLength;
|
|
|
|
//
|
|
// IE adds the following headers as well.
|
|
// UserAgent:
|
|
// Content-Length : 0
|
|
// Pragma: no-cache
|
|
//
|
|
|
|
HeaderLength =
|
|
EnumVerbTable[HttpVerbCONNECT].RawVerbLength + // Method
|
|
1 + // SP
|
|
pServInfo->pServerInfo->AnsiServerNameLength + // URI
|
|
4 + // port
|
|
1 + // SP
|
|
VERSION_SIZE + // Version
|
|
CRLF_SIZE;
|
|
|
|
//
|
|
// Add a host header
|
|
//
|
|
|
|
HeaderLength +=
|
|
g_RequestHeaderMapTable[
|
|
g_RequestHeaderMap[HttpHeaderHost]
|
|
].HeaderLength +
|
|
1 + // SP
|
|
pServInfo->pServerInfo->AnsiServerNameLength + // Value
|
|
CRLF_SIZE; // CRLF
|
|
|
|
//
|
|
// Add a Proxy keepalive.
|
|
//
|
|
HeaderLength += PROXY_CONNECTION_KEEPALIVE_SIZE + CRLF_SIZE;
|
|
|
|
|
|
//
|
|
// If we are doing proxy auth, add a proxy auth header. Since we have
|
|
// already generated this header when we built the request, we can
|
|
// just clone it from there.
|
|
//
|
|
|
|
if (pProxyAuthInfo)
|
|
{
|
|
HeaderLength += pProxyAuthInfo->RequestAuthHeaderMaxLength;
|
|
}
|
|
else if(pServInfo->ProxyPreAuthEnable && pServInfo->pProxyAuthInfo)
|
|
{
|
|
HeaderLength += pServInfo->pProxyAuthInfo->RequestAuthHeaderMaxLength;
|
|
}
|
|
|
|
//
|
|
// Terminate
|
|
//
|
|
HeaderLength += CRLF_SIZE;
|
|
|
|
return HeaderLength;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Generates the header size for the CONNECT verb.
|
|
|
|
|
|
Arguments:
|
|
|
|
pServInfo - The server information.
|
|
pProxyAuthInfo - Proxy auth info.
|
|
|
|
Return Values:
|
|
|
|
Status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UcGenerateConnectVerbHeader(
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
IN PUC_HTTP_REQUEST pHeadRequest,
|
|
IN PUC_HTTP_AUTH pProxyAuthInfo
|
|
)
|
|
{
|
|
PUCHAR pBuffer, pStartHeaders;
|
|
PUCHAR pUri;
|
|
USHORT UriLength;
|
|
ULONG BytesWritten;
|
|
ULONG RemainingLength;
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Remember the start of header and max total header length.
|
|
//
|
|
|
|
pStartHeaders = pBuffer = pRequest->pHeaders;
|
|
RemainingLength = pRequest->MaxHeaderLength;
|
|
|
|
//
|
|
// Copy "CONNECT" verb.
|
|
//
|
|
|
|
COPY_DATA_TO_BUFFER(pBuffer,
|
|
RemainingLength,
|
|
EnumVerbTable[HttpVerbCONNECT].RawVerb,
|
|
EnumVerbTable[HttpVerbCONNECT].RawVerbLength);
|
|
|
|
// Copy a SP.
|
|
COPY_SP_TO_BUFFER(pBuffer, RemainingLength);
|
|
|
|
//
|
|
// Now, the URI. The URI here is the name of the origin server
|
|
// followed by the port number.
|
|
//
|
|
|
|
pUri = pBuffer;
|
|
|
|
COPY_DATA_TO_BUFFER(
|
|
pBuffer,
|
|
RemainingLength,
|
|
pRequest->pServerInfo->pServerInfo->pAnsiServerName,
|
|
pRequest->pServerInfo->pServerInfo->AnsiServerNameLength
|
|
);
|
|
|
|
//
|
|
// If server name does not have a port number, include the default
|
|
// port number. Note that, the only way the port number could be
|
|
// different is when it is present in the server name.
|
|
//
|
|
|
|
if (!pRequest->pServerInfo->pServerInfo->bPortNumber)
|
|
{
|
|
//
|
|
// Copy the default port number.
|
|
//
|
|
|
|
COPY_DATA_TO_BUFFER(pBuffer,
|
|
RemainingLength,
|
|
":443",
|
|
STRLEN_LIT(":443"));
|
|
}
|
|
|
|
UriLength = DIFF_USHORT(pBuffer - pUri);
|
|
|
|
//
|
|
// Remember URI and Uri Length in Request structure for future use.
|
|
//
|
|
|
|
pRequest->UriLength = UriLength;
|
|
pRequest->pUri = (PSTR) pUri;
|
|
|
|
// Add a space
|
|
COPY_SP_TO_BUFFER(pBuffer, RemainingLength);
|
|
|
|
//
|
|
// Add the protocol.
|
|
//
|
|
|
|
COPY_DATA_TO_BUFFER(pBuffer,
|
|
RemainingLength,
|
|
HTTP_VERSION_11,
|
|
VERSION_SIZE);
|
|
|
|
//
|
|
// Terminate the request-line with a CRLF.
|
|
//
|
|
|
|
COPY_CRLF_TO_BUFFER(pBuffer, RemainingLength);
|
|
|
|
//
|
|
// Generate the host header
|
|
//
|
|
|
|
COPY_HEADER_NAME_SP_TO_BUFFER(pBuffer, RemainingLength, HttpHeaderHost);
|
|
|
|
COPY_DATA_TO_BUFFER(
|
|
pBuffer,
|
|
RemainingLength,
|
|
pRequest->pServerInfo->pServerInfo->pAnsiServerName,
|
|
pRequest->pServerInfo->pServerInfo->AnsiServerNameLength
|
|
);
|
|
|
|
COPY_CRLF_TO_BUFFER(pBuffer, RemainingLength);
|
|
|
|
//
|
|
// Proxy Keepalive
|
|
//
|
|
|
|
COPY_DATA_TO_BUFFER(pBuffer,
|
|
RemainingLength,
|
|
PROXY_CONNECTION_KEEPALIVE,
|
|
PROXY_CONNECTION_KEEPALIVE_SIZE);
|
|
|
|
COPY_CRLF_TO_BUFFER(pBuffer, RemainingLength);
|
|
|
|
//
|
|
// Proxy Auth
|
|
//
|
|
|
|
if (pProxyAuthInfo)
|
|
{
|
|
Status = UcGenerateAuthHeaderFromCredentials(
|
|
pRequest->pServerInfo,
|
|
pProxyAuthInfo,
|
|
HttpHeaderProxyAuthorization,
|
|
(PSTR)EnumVerbTable[HttpVerbCONNECT].RawVerb,
|
|
EnumVerbTable[HttpVerbCONNECT].RawVerbLength,
|
|
(PSTR) pUri,
|
|
UriLength,
|
|
pBuffer,
|
|
RemainingLength,
|
|
&BytesWritten,
|
|
&pRequest->DontFreeMdls
|
|
);
|
|
|
|
//
|
|
// What do we do if this fails ? It's probably OK to just send the
|
|
// request which will result in another 401. There is no clean way
|
|
// of propogating this to the app.
|
|
//
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
ASSERT(BytesWritten <= RemainingLength);
|
|
ADVANCE_POINTER(pBuffer, RemainingLength, BytesWritten);
|
|
}
|
|
}
|
|
else if(pRequest->pServerInfo->ProxyPreAuthEnable &&
|
|
pRequest->pServerInfo->pProxyAuthInfo)
|
|
{
|
|
Status = UcGenerateProxyAuthHeaderFromCache(
|
|
pHeadRequest,
|
|
(PSTR) EnumVerbTable[HttpVerbCONNECT].RawVerb,
|
|
EnumVerbTable[HttpVerbCONNECT].RawVerbLength,
|
|
pBuffer,
|
|
RemainingLength,
|
|
&BytesWritten
|
|
);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
ASSERT(BytesWritten < RemainingLength);
|
|
ADVANCE_POINTER(pBuffer, RemainingLength, BytesWritten);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Header end.
|
|
//
|
|
|
|
COPY_CRLF_TO_BUFFER(pBuffer, RemainingLength);
|
|
|
|
pRequest->HeaderLength = DIFF(pBuffer - pStartHeaders);
|
|
ASSERT(pRequest->HeaderLength <= pRequest->MaxHeaderLength);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Convert '%xy' to byte value (works with Unicode strings)
|
|
//
|
|
|
|
NTSTATUS
|
|
UnescapeW(
|
|
IN PCWSTR pWChar,
|
|
OUT PWCHAR pOutWChar
|
|
)
|
|
|
|
{
|
|
WCHAR Result, Digit;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
if (pWChar[0] != '%' || pWChar[1] >= 0x80 || pWChar[2] >= 0x80 ||
|
|
!IS_HTTP_HEX(pWChar[1]) || !IS_HTTP_HEX(pWChar[2]))
|
|
{
|
|
UlTraceError(PARSER, (
|
|
"ul!Unescape( %C%C%C ) not HTTP_HEX format\n",
|
|
pWChar[0],
|
|
pWChar[1],
|
|
pWChar[2]
|
|
));
|
|
|
|
return STATUS_OBJECT_PATH_SYNTAX_BAD;
|
|
}
|
|
|
|
//
|
|
// HexToChar() inlined. Note: '0' < 'A' < 'a'
|
|
//
|
|
|
|
// uppercase #1
|
|
//
|
|
if ('a' <= pWChar[1])
|
|
{
|
|
ASSERT('a' <= pWChar[1] && pWChar[1] <= 'f');
|
|
Digit = pWChar[1] - 'a' + 0xA;
|
|
}
|
|
else if ('A' <= pWChar[1])
|
|
{
|
|
ASSERT('A' <= pWChar[1] && pWChar[1] <= 'F');
|
|
Digit = pWChar[1] - 'A' + 0xA;
|
|
}
|
|
else
|
|
{
|
|
ASSERT('0' <= pWChar[1] && pWChar[1] <= '9');
|
|
Digit = pWChar[1] - '0';
|
|
}
|
|
|
|
ASSERT(Digit < 0x10);
|
|
|
|
Result = Digit << 4;
|
|
|
|
// uppercase #2
|
|
//
|
|
if ('a' <= pWChar[2])
|
|
{
|
|
ASSERT('a' <= pWChar[2] && pWChar[2] <= 'f');
|
|
Digit = pWChar[2] - 'a' + 0xA;
|
|
}
|
|
else if ('A' <= pWChar[2])
|
|
{
|
|
ASSERT('A' <= pWChar[2] && pWChar[2] <= 'F');
|
|
Digit = pWChar[2] - 'A' + 0xA;
|
|
}
|
|
else
|
|
{
|
|
ASSERT('0' <= pWChar[2] && pWChar[2] <= '9');
|
|
Digit = pWChar[2] - '0';
|
|
}
|
|
|
|
ASSERT(Digit < 0x10);
|
|
|
|
Result |= Digit;
|
|
|
|
*pOutWChar = Result;
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // UnescapeW
|
|
|
|
__inline
|
|
BOOLEAN
|
|
UcCheckDisconnectInfo(
|
|
IN PHTTP_VERSION pVersion,
|
|
IN PHTTP_KNOWN_HEADER pKnownHeaders
|
|
)
|
|
{
|
|
BOOLEAN Disconnect;
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
|
|
if (
|
|
//
|
|
// or version 1.0 with no Connection: Keep-Alive
|
|
// CODEWORK: and no Keep-Alive header
|
|
//
|
|
|
|
(HTTP_EQUAL_VERSION(*pVersion, 1, 0) &&
|
|
(pKnownHeaders[HttpHeaderConnection].RawValueLength == 0 ||
|
|
!(pKnownHeaders[HttpHeaderConnection].RawValueLength == 10 &&
|
|
(_stricmp(
|
|
(const char*) pKnownHeaders[HttpHeaderConnection].pRawValue,
|
|
"keep-alive"
|
|
) == 0)))) ||
|
|
|
|
//
|
|
// or version 1.1 with a Connection: close
|
|
// CODEWORK: move to parser or just make better in general..
|
|
//
|
|
|
|
(HTTP_EQUAL_VERSION(*pVersion, 1, 1) &&
|
|
pKnownHeaders[HttpHeaderConnection].RawValueLength == 5 &&
|
|
_stricmp((const char*)
|
|
pKnownHeaders[HttpHeaderConnection].pRawValue, "close") == 0)
|
|
)
|
|
{
|
|
Disconnect = TRUE;
|
|
}
|
|
else
|
|
{
|
|
Disconnect = FALSE;
|
|
}
|
|
|
|
return Disconnect;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Canonicalizes abs path in a URI. The tasks performed are:
|
|
- Remove extra '/' e.g. /a///b => /a/b
|
|
- Process '.' and '..' e.g. /./a/../b => /b
|
|
- Copy Query string as it is
|
|
- Copy Fragment as it is
|
|
- Encode the output in UTF8
|
|
- Optionally hex encode bytes >= 0x80
|
|
|
|
Arguments:
|
|
IN pInUri Input URI in Unicode
|
|
IN InUriLen Length of input URI (in CHAR)
|
|
IN pOutUri Pointer to the output buffer
|
|
IN OUT pOutUriLen Length of the output buffer
|
|
the actual number of bytes written is returned back
|
|
IN bEncode TRUE if chars >= 0x80 should be escaped
|
|
|
|
Return Values:
|
|
|
|
Status.
|
|
|
|
--***************************************************************************/
|
|
|
|
UCHAR NextStateTable[TOTAL_STATES][CHAR_TOTAL_TYPES+2] = INIT_TRANSITION_TABLE;
|
|
UCHAR (*ActionTable)[TOTAL_STATES][CHAR_TOTAL_TYPES+2] = &NextStateTable;
|
|
|
|
#define NEXT_STATE(state, type) ((NextStateTable[state][type])&0xf)
|
|
#define ACTION(state, type) ((((*ActionTable)[state][type])>>4)&0xf)
|
|
|
|
//
|
|
// Macro to read next char from the input URI
|
|
// The macro handles correctly a '.' char even if it is escaped as %2E (or %2e)
|
|
// HttpChars is used to quickly lookup '/', '.', '#' or '?' chars
|
|
//
|
|
#define GET_NEXT_CHAR(pInUri, CharsLeft, CurrChar, CurrCharType) \
|
|
do \
|
|
{ \
|
|
CurrCharType = CHAR_END_OF_STRING; \
|
|
if (CharsLeft == 0) \
|
|
break; \
|
|
\
|
|
CurrChar = *pInUri++; \
|
|
CharsLeft--; \
|
|
\
|
|
CurrCharType = CHAR_EXTENDED_CHAR; \
|
|
if (CurrChar >= 0x80) \
|
|
break; \
|
|
\
|
|
if (CurrChar == '%') \
|
|
{ \
|
|
WCHAR UnescapedChar; \
|
|
\
|
|
if (CharsLeft < 2) \
|
|
goto error; \
|
|
\
|
|
pInUri--; \
|
|
CharsLeft++; \
|
|
\
|
|
if (!NT_SUCCESS(UnescapeW(pInUri, &UnescapedChar))) \
|
|
goto error; \
|
|
\
|
|
pInUri++; \
|
|
CharsLeft--; \
|
|
\
|
|
if (UnescapedChar == '.') \
|
|
{ \
|
|
CurrChar = L'.'; \
|
|
pInUri += 2; \
|
|
CharsLeft -= 2; \
|
|
} \
|
|
} \
|
|
\
|
|
CurrCharType = (HttpChars[CurrChar]>>HTTP_CHAR_SHIFT); \
|
|
if (CurrCharType == 0) \
|
|
CurrCharType = (IS_URL_TOKEN(CurrChar))? \
|
|
CHAR_PATH_CHAR : CHAR_INVALID_CHAR; \
|
|
\
|
|
} while (0)
|
|
|
|
|
|
//
|
|
// Output a BYTE to the output buffer.
|
|
// PERF NOTE: Replace the comparision of OutBufLeft by an ASSERT.
|
|
//
|
|
#define EMIT_A_BYTE(b) \
|
|
do \
|
|
{ \
|
|
ASSERT(b < 0x80); \
|
|
\
|
|
if (OutBufLeft == 0) \
|
|
goto overflow; \
|
|
\
|
|
*pOutput++ = (b); \
|
|
OutBufLeft--; \
|
|
} while (0)
|
|
|
|
|
|
//
|
|
// Output a CHAR to the output buffer. Encodes a UNICODE char in UTF8.
|
|
// The UTF8 char is escaped if bEncode is specified.
|
|
//
|
|
#define EMIT_A_CHAR(c) \
|
|
do { \
|
|
ULONG adj; \
|
|
\
|
|
if (OutBufLeft == 0) \
|
|
goto overflow; \
|
|
\
|
|
if ((c) < 0x80) \
|
|
{ \
|
|
*pOutput++ = (UCHAR)(c); \
|
|
OutBufLeft--; \
|
|
break; \
|
|
} \
|
|
\
|
|
if (!NT_SUCCESS(HttpUnicodeToUTF8Encode(&c, 1, pOutput, OutBufLeft, &adj, bEncode))) \
|
|
goto overflow; \
|
|
\
|
|
pOutput += adj; \
|
|
OutBufLeft -= adj; \
|
|
\
|
|
} while (0)
|
|
|
|
//
|
|
// Main routine.
|
|
//
|
|
NTSTATUS
|
|
UcCanonicalizeURI(
|
|
IN LPCWSTR pInUri, // Input URI in Unicode
|
|
IN USHORT InUriLen, // Length of input URI (in wchar)
|
|
IN OUT PUCHAR pOutUri, // buffer where the output goes
|
|
IN OUT PUSHORT pOutUriLen, // length of the output buffer
|
|
IN BOOLEAN bEncode // TRUE if char >= 0x80 should be escaped
|
|
)
|
|
{
|
|
ULONG state, nstate, action;
|
|
|
|
WCHAR CurrChar = L'\0';
|
|
ULONG CurrCharType;
|
|
|
|
PUCHAR pOutput = pOutUri;
|
|
ULONG OutBufLeft = *pOutUriLen;
|
|
|
|
// Sanity check
|
|
ASSERT(pInUri && InUriLen != 0);
|
|
ASSERT(pOutUri && *pOutUriLen != 0);
|
|
|
|
nstate = state = 0;
|
|
|
|
do
|
|
{
|
|
GET_NEXT_CHAR(pInUri, InUriLen, CurrChar, CurrCharType);
|
|
|
|
nstate = NEXT_STATE(state, CurrCharType);
|
|
|
|
action = ACTION(state, CurrCharType);
|
|
|
|
UlTraceVerbose(PARSER, ("UcCanonicalizeURI: CurrChar = 0x%02x, "
|
|
"CurrCharType = %ld, state %ld, nstate = %ld\n",
|
|
(ULONG)CurrChar, CurrCharType, state, nstate));
|
|
|
|
switch (action)
|
|
{
|
|
case ACT_EMIT_DOT_DOT_CHAR:
|
|
EMIT_A_BYTE('.');
|
|
// fall through
|
|
case ACT_EMIT_DOT_CHAR:
|
|
EMIT_A_BYTE('.');
|
|
// fall through
|
|
case ACT_EMIT_CHAR:
|
|
EMIT_A_CHAR(CurrChar);
|
|
break;
|
|
|
|
case ACT_NONE:
|
|
break;
|
|
|
|
case ACT_BACKUP:
|
|
case ACT_BACKUP_EMIT_CHAR:
|
|
ASSERT(pOutput > pOutUri && pOutput[-1] == '/' && *pOutUri == '/');
|
|
//
|
|
// Can we backup? (e.g. if the URI is "/../", we can't)
|
|
//
|
|
if (pOutput > pOutUri + 1)
|
|
{
|
|
// Yes we can backup to a pervious '/'
|
|
pOutput -= 2;
|
|
while (*pOutput != '/')
|
|
pOutput--, OutBufLeft++;
|
|
pOutput++;
|
|
|
|
OutBufLeft += 1;
|
|
|
|
ASSERT(pOutput > pOutUri);
|
|
}
|
|
|
|
if (action == ACT_BACKUP_EMIT_CHAR)
|
|
EMIT_A_CHAR(CurrChar);
|
|
break;
|
|
|
|
case ACT_ERROR:
|
|
// URI is invalid
|
|
goto error;
|
|
break;
|
|
|
|
case ACT_PANIC:
|
|
// Internal error...we shouldn't be here!
|
|
default:
|
|
UlTraceError(PARSER, ("UcCanonicalizeURI: internal error\n"));
|
|
ASSERT(FALSE);
|
|
break;
|
|
}
|
|
|
|
state = nstate;
|
|
|
|
} while (state != 6);
|
|
|
|
ASSERT(pOutput >= pOutUri && pOutput <= pOutUri + *pOutUriLen);
|
|
UlTrace(PARSER, ("UcCanonicalizeURI: Return length = %d\n",
|
|
pOutput - pOutUri));
|
|
|
|
// return the actual number of bytes written to the output buffer
|
|
*pOutUriLen = (USHORT)(pOutput - pOutUri);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
error:
|
|
// Invalid URI
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
overflow:
|
|
// Output buffer is not big enough
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
|
|
//
|
|
// Response Parser functions.
|
|
//
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Find the header name terminator of a header:value pair.
|
|
|
|
Arguments:
|
|
|
|
pHttpRequest - Pointer to the current request.
|
|
HttpRequestLength - Bytes left in the request.
|
|
HeaderNameLength - Pointer to return header name.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS : Worked.
|
|
STATUS_INVALID_DEVICE_REQUEST : Invalid header.
|
|
STATUS_MORE_PROCESSING_REQUIRED : More data required.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UcFindHeaderNameEnd(
|
|
IN PUCHAR pHttpRequest,
|
|
IN ULONG HttpRequestLength,
|
|
OUT PULONG HeaderNameLength
|
|
)
|
|
{
|
|
ULONG CurrentOffset;
|
|
NTSTATUS Status;
|
|
UCHAR CurrentChar;
|
|
|
|
for (CurrentOffset = 0; CurrentOffset < HttpRequestLength; CurrentOffset++)
|
|
{
|
|
CurrentChar = *(pHttpRequest + CurrentOffset);
|
|
|
|
if (CurrentChar == ':')
|
|
{
|
|
// We've found the end of the header.
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (!IS_HTTP_TOKEN(CurrentChar))
|
|
{
|
|
// Uh-oh, this isn't a valid header. What do we do now?
|
|
//
|
|
|
|
UlTraceError(PARSER,
|
|
("[UcFindHeaderNameEnd]: Bogus header \n"));
|
|
|
|
return STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Find out why we got out. If the current offset is less than the
|
|
// header length, we got out because we found the :.
|
|
|
|
if (CurrentOffset < HttpRequestLength)
|
|
{
|
|
// Found the terminator. Point Beyond the terminator.
|
|
*HeaderNameLength = CurrentOffset + 1;
|
|
|
|
if(*HeaderNameLength > ANSI_STRING_MAX_CHAR_LEN)
|
|
{
|
|
UlTraceError(PARSER,
|
|
("[UcFindHeaderNameEnd]: Very long header name \n"));
|
|
*HeaderNameLength = 0;
|
|
Status = STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Didn't find the :, need more.
|
|
//
|
|
*HeaderNameLength = 0;
|
|
Status = STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Find the end of header value.
|
|
|
|
Arguments:
|
|
|
|
pHeaderValue - Pointer to the header value
|
|
RemainingBufferLength - Bytes remaining
|
|
ppFoldingHeader - Will be Non NULL if we allocate from pool to do
|
|
header folding. Caller has to free this buffer.
|
|
pBytesTaken - Bytes Consumed.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS : Worked.
|
|
STATUS_INVALID_DEVICE_REQUEST : Invalid header.
|
|
STATUS_MORE_PROCESSING_REQUIRED : More data required.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UcFindHeaderValueEnd(
|
|
IN PUCHAR pHeaderValue,
|
|
IN ULONG RemainingBufferLength,
|
|
IN PUCHAR *ppFoldingHeader,
|
|
IN PULONG pBytesTaken
|
|
)
|
|
{
|
|
ULONG BytesTaken = 0;
|
|
PUCHAR pFoldingBuffer;
|
|
NTSTATUS Status;
|
|
|
|
ASSERT(NULL == *ppFoldingHeader);
|
|
|
|
//
|
|
// Find the end of the header value
|
|
//
|
|
Status = FindHeaderEndReadOnly(
|
|
pHeaderValue,
|
|
RemainingBufferLength,
|
|
&BytesTaken
|
|
);
|
|
|
|
if(STATUS_MORE_PROCESSING_REQUIRED == Status)
|
|
{
|
|
//
|
|
// The headers need to be folded. Since we can't modify TCP's data
|
|
// we'll allocate a buffer for this. We don't really care to optimize
|
|
// for this case, because header folding is pretty rare, so we won't
|
|
// bother with lookaside lists, etc.
|
|
//
|
|
|
|
pFoldingBuffer = UL_ALLOCATE_POOL(
|
|
NonPagedPool,
|
|
RemainingBufferLength,
|
|
UC_HEADER_FOLDING_POOL_TAG
|
|
);
|
|
|
|
if(!pFoldingBuffer)
|
|
{
|
|
// Can't use STATUS_INSUFFICIENT_RESOURCES because it means
|
|
// something else.
|
|
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
RtlCopyMemory(pFoldingBuffer,
|
|
pHeaderValue,
|
|
RemainingBufferLength
|
|
);
|
|
|
|
Status = FindHeaderEnd(
|
|
pFoldingBuffer,
|
|
RemainingBufferLength,
|
|
&BytesTaken
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
ASSERT(BytesTaken == 0);
|
|
|
|
UL_FREE_POOL(pFoldingBuffer, UC_HEADER_FOLDING_POOL_TAG);
|
|
|
|
return Status;
|
|
}
|
|
else
|
|
{
|
|
if(BytesTaken == 0)
|
|
{
|
|
UL_FREE_POOL(pFoldingBuffer, UC_HEADER_FOLDING_POOL_TAG);
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
else if(BytesTaken > ANSI_STRING_MAX_CHAR_LEN)
|
|
{
|
|
UL_FREE_POOL(pFoldingBuffer, UC_HEADER_FOLDING_POOL_TAG);
|
|
|
|
UlTraceError(PARSER,
|
|
("[UcFindHeaderValueEnd]: Very long header value \n"));
|
|
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
}
|
|
|
|
*ppFoldingHeader = pFoldingBuffer;
|
|
}
|
|
else if(NT_SUCCESS(Status))
|
|
{
|
|
if(BytesTaken == 0)
|
|
{
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
else if(BytesTaken > ANSI_STRING_MAX_CHAR_LEN)
|
|
{
|
|
UlTraceError(PARSER,
|
|
("[UcFindHeaderValueEnd]: Very long header value \n"));
|
|
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
}
|
|
|
|
*pBytesTaken = BytesTaken;
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Look up a header that we don't have in our fast lookup table. This
|
|
could be because it's a header we don't understand, or because we
|
|
couldn't use the fast lookup table due to insufficient buffer length.
|
|
The latter reason is uncommon, but we'll check the input table anyway
|
|
if we're given one. If we find a header match in our mapping table,
|
|
we'll call the header handler. Otherwise we'll try to allocate an
|
|
unknown header element, fill it in and chain it on the http connection.
|
|
|
|
Arguments:
|
|
|
|
pHttpConn - Pointer to the current connection on which the
|
|
request arrived.
|
|
pHttpRequest - Pointer to the current request.
|
|
HttpRequestLength - Bytes left in the request.
|
|
pHeaderMap - Pointer to start of an array of header map entries
|
|
(may be NULL).
|
|
HeaderMapCount - Number of entries in array pointed to by pHeaderMap.
|
|
|
|
Return Value:
|
|
|
|
Number of bytes in the header (including CRLF), or 0 if we couldn't
|
|
parse the header.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UcpLookupHeader(
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
IN PUCHAR pHttpRequest,
|
|
IN ULONG HttpRequestLength,
|
|
IN PHEADER_MAP_ENTRY pHeaderMap,
|
|
IN ULONG HeaderMapCount,
|
|
OUT ULONG * pBytesTaken
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG HeaderNameLength;
|
|
ULONG i;
|
|
ULONG BytesTaken;
|
|
ULONG HeaderValueLength, RemainingBufferLength;
|
|
PUCHAR pBufferHead;
|
|
PUCHAR pBufferTail;
|
|
PHTTP_UNKNOWN_HEADER pUnknownHeader;
|
|
PUCHAR pHeaderValue;
|
|
PUCHAR pFoldingBuffer = NULL;
|
|
ULONG AlignNameLength, AlignValueLength;
|
|
|
|
|
|
// First, let's find the terminating : of the header name, if there is one.
|
|
// This will also give us the length of the header, which we can then
|
|
// use to search the header map table if we have one.
|
|
//
|
|
|
|
Status = UcFindHeaderNameEnd(
|
|
pHttpRequest,
|
|
HttpRequestLength,
|
|
&HeaderNameLength
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
// See if we have a header map array we need to search.
|
|
//
|
|
if (pHeaderMap != NULL)
|
|
{
|
|
// We do have an array to search.
|
|
for (i = 0; i < HeaderMapCount; i++)
|
|
{
|
|
ASSERT(pHeaderMap->pClientHandler != NULL);
|
|
|
|
if (HeaderNameLength == pHeaderMap->HeaderLength &&
|
|
_strnicmp(
|
|
(const char *)(pHttpRequest),
|
|
(const char *)(pHeaderMap->Header.HeaderChar),
|
|
HeaderNameLength
|
|
) == 0 &&
|
|
pHeaderMap->pClientHandler != NULL)
|
|
{
|
|
|
|
// First, find the header end.
|
|
|
|
pHeaderValue = pHttpRequest + HeaderNameLength;
|
|
RemainingBufferLength = (HttpRequestLength - HeaderNameLength);
|
|
|
|
Status = UcFindHeaderValueEnd(
|
|
pHeaderValue,
|
|
RemainingBufferLength,
|
|
&pFoldingBuffer,
|
|
&BytesTaken
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
ASSERT(BytesTaken >= CRLF_SIZE);
|
|
|
|
HeaderValueLength = BytesTaken - CRLF_SIZE;
|
|
|
|
if(pFoldingBuffer != NULL)
|
|
{
|
|
pHeaderValue = pFoldingBuffer;
|
|
}
|
|
|
|
UC_PROCESS_HEADER_VALUE(pHeaderValue, HeaderValueLength);
|
|
|
|
// This header matches. Call the handling function for it.
|
|
Status = (*(pHeaderMap->pClientHandler))(
|
|
pRequest,
|
|
pHeaderValue,
|
|
HeaderValueLength,
|
|
pHeaderMap->HeaderID
|
|
);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
pHeaderMap++;
|
|
}
|
|
}
|
|
|
|
// OK, at this point either we had no header map array or none of them
|
|
// matched. We have an unknown header. Just make sure this header is
|
|
// terminated and save a pointer to it.
|
|
//
|
|
// Find the end of the header value
|
|
//
|
|
pHeaderValue = pHttpRequest + HeaderNameLength;
|
|
HeaderValueLength = (HttpRequestLength - HeaderNameLength);
|
|
|
|
Status = UcFindHeaderValueEnd(
|
|
pHeaderValue,
|
|
HeaderValueLength,
|
|
&pFoldingBuffer,
|
|
&BytesTaken
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
ASSERT(BytesTaken >= CRLF_SIZE);
|
|
|
|
HeaderValueLength = BytesTaken - CRLF_SIZE;
|
|
|
|
if(pFoldingBuffer != NULL)
|
|
{
|
|
pHeaderValue = pFoldingBuffer;
|
|
}
|
|
|
|
UC_PROCESS_HEADER_VALUE(pHeaderValue, HeaderValueLength);
|
|
|
|
//
|
|
// We Have an unknown header. We don't have to search our list of
|
|
// unknown headers to see if this unknown header already exists.
|
|
// even if it does exist, we cannot concatenate these two header
|
|
// values - Since the header is unknown, the syntax for the header-value
|
|
// is also unknown and merging might mess around with the field terminator
|
|
//
|
|
|
|
//
|
|
// Carve out a UNKNOWN_HEADER structure using pBufferHead
|
|
//
|
|
|
|
pBufferHead = pRequest->CurrentBuffer.pOutBufferHead;
|
|
pBufferTail = pRequest->CurrentBuffer.pOutBufferTail;
|
|
|
|
AlignNameLength = ALIGN_UP(HeaderNameLength, PVOID);
|
|
AlignValueLength = ALIGN_UP(HeaderValueLength, PVOID);
|
|
|
|
if(pRequest->CurrentBuffer.BytesAvailable >=
|
|
sizeof(HTTP_UNKNOWN_HEADER) + AlignNameLength + AlignValueLength)
|
|
{
|
|
pUnknownHeader = (PHTTP_UNKNOWN_HEADER)pBufferHead;
|
|
|
|
//
|
|
// The Header name has a ':'.
|
|
//
|
|
|
|
pUnknownHeader->NameLength = (USHORT) (HeaderNameLength - 1);
|
|
|
|
pBufferTail -= AlignNameLength;
|
|
|
|
pUnknownHeader->pName = (PCSTR) pBufferTail;
|
|
|
|
RtlCopyMemory(
|
|
pBufferTail,
|
|
pHttpRequest,
|
|
(USHORT) (HeaderNameLength - 1)
|
|
);
|
|
|
|
//
|
|
// header value
|
|
//
|
|
|
|
pUnknownHeader->RawValueLength = (USHORT) HeaderValueLength;
|
|
|
|
pBufferTail -= AlignValueLength;
|
|
|
|
pUnknownHeader->pRawValue = (PCSTR) pBufferTail;
|
|
|
|
RtlCopyMemory(
|
|
pBufferTail,
|
|
pHeaderValue,
|
|
(USHORT) HeaderValueLength
|
|
);
|
|
|
|
pRequest->CurrentBuffer.pResponse->Headers.UnknownHeaderCount ++;
|
|
|
|
pRequest->CurrentBuffer.pOutBufferHead =
|
|
pBufferHead + sizeof(HTTP_UNKNOWN_HEADER);
|
|
|
|
pRequest->CurrentBuffer.pOutBufferTail = pBufferTail;
|
|
|
|
pRequest->CurrentBuffer.BytesAvailable -=
|
|
(sizeof(HTTP_UNKNOWN_HEADER) + AlignNameLength + AlignValueLength);
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
end:
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
*pBytesTaken = HeaderNameLength + BytesTaken;
|
|
}
|
|
|
|
if(pFoldingBuffer)
|
|
{
|
|
UL_FREE_POOL(pFoldingBuffer, UC_HEADER_FOLDING_POOL_TAG);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UcpLookupHeader
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
The routine to parse an individual header. We take in a pointer to the
|
|
header and the bytes remaining in the request, and try to find
|
|
the header in our lookup table. We try first the fast way, and then
|
|
try again the slow way in case there wasn't quite enough data the first
|
|
time.
|
|
|
|
On input, HttpRequestLength is at least CRLF_SIZE.
|
|
|
|
Arguments:
|
|
|
|
pRequest - Pointer to the current connection on which the
|
|
request arrived.
|
|
pHttpRequest - Pointer to the current request.
|
|
HttpRequestLength - Bytes left in the request.
|
|
|
|
Return Value:
|
|
|
|
Number of bytes in the header (including CRLF), or 0 if we couldn't
|
|
parse the header.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UcParseHeader(
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
IN PUCHAR pHttpRequest,
|
|
IN ULONG HttpRequestLength,
|
|
OUT ULONG * pBytesTaken
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG i;
|
|
ULONG j;
|
|
ULONG BytesTaken;
|
|
ULONGLONG Temp;
|
|
UCHAR c;
|
|
PHEADER_MAP_ENTRY pCurrentHeaderMap;
|
|
ULONG HeaderMapCount;
|
|
BOOLEAN SmallHeader = FALSE;
|
|
PHTTP_RESPONSE_HEADERS pResponseHeaders;
|
|
PUCHAR *pOutBufferHead;
|
|
PUCHAR *pOutBufferTail;
|
|
PULONG BytesAvailable;
|
|
PUCHAR pHeaderValue;
|
|
ULONG HeaderValueLength;
|
|
ULONG RemainingBufferLength;
|
|
PUCHAR pFoldingBuffer = NULL;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
ASSERT(HttpRequestLength >= CRLF_SIZE);
|
|
|
|
pResponseHeaders = &pRequest->CurrentBuffer.pResponse->Headers;
|
|
pOutBufferHead = &pRequest->CurrentBuffer.pOutBufferHead;
|
|
pOutBufferTail = &pRequest->CurrentBuffer.pOutBufferTail;
|
|
BytesAvailable = &pRequest->CurrentBuffer.BytesAvailable;
|
|
|
|
c = *pHttpRequest;
|
|
|
|
// message-headers start with field-name [= token]
|
|
//
|
|
if (IS_HTTP_TOKEN(c) == FALSE)
|
|
{
|
|
UlTraceError(PARSER, (
|
|
"ul!UcParseHeader c = 0x%x ERROR: invalid header char\n",
|
|
c
|
|
));
|
|
|
|
return STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
|
|
// Does the header start with an alpha?
|
|
//
|
|
if (IS_HTTP_ALPHA(c))
|
|
{
|
|
// Uppercase the character, and find the appropriate set of header map
|
|
// entries.
|
|
//
|
|
c = UPCASE_CHAR(c);
|
|
|
|
c -= 'A';
|
|
|
|
pCurrentHeaderMap = g_ResponseHeaderIndexTable[c].pHeaderMap;
|
|
HeaderMapCount = g_ResponseHeaderIndexTable[c].Count;
|
|
|
|
// Loop through all the header map entries that might match
|
|
// this header, and check them. The count will be 0 if there
|
|
// are no entries that might match and we'll skip the loop.
|
|
|
|
for (i = 0; i < HeaderMapCount; i++)
|
|
{
|
|
|
|
ASSERT(pCurrentHeaderMap->pClientHandler != NULL);
|
|
|
|
// If we have enough bytes to do the fast check, do it.
|
|
// Otherwise skip this. We may skip a valid match, but if
|
|
// so we'll catch it later.
|
|
|
|
if (HttpRequestLength >= pCurrentHeaderMap->MinBytesNeeded)
|
|
{
|
|
ASSERT(HttpRequestLength >= ((pCurrentHeaderMap->ArrayCount-1) *
|
|
sizeof(ULONGLONG)));
|
|
|
|
for (j = 0; j < pCurrentHeaderMap->ArrayCount; j++)
|
|
{
|
|
Temp = *(UNALIGNED64 ULONGLONG *)(pHttpRequest +
|
|
(j * sizeof(ULONGLONG)));
|
|
|
|
if ((Temp & pCurrentHeaderMap->HeaderMask[j]) !=
|
|
pCurrentHeaderMap->Header.HeaderLong[j] )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// See why we exited out.
|
|
if (j == pCurrentHeaderMap->ArrayCount &&
|
|
pCurrentHeaderMap->pClientHandler != NULL)
|
|
{
|
|
pHeaderValue = pHttpRequest +
|
|
pCurrentHeaderMap->HeaderLength;
|
|
|
|
RemainingBufferLength = (HttpRequestLength -
|
|
pCurrentHeaderMap->HeaderLength);
|
|
|
|
Status = UcFindHeaderValueEnd(
|
|
pHeaderValue,
|
|
RemainingBufferLength,
|
|
&pFoldingBuffer,
|
|
&BytesTaken
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
ASSERT(BytesTaken >= CRLF_SIZE);
|
|
HeaderValueLength = BytesTaken - CRLF_SIZE;
|
|
|
|
if(pFoldingBuffer != NULL)
|
|
{
|
|
pHeaderValue = pFoldingBuffer;
|
|
}
|
|
|
|
UC_PROCESS_HEADER_VALUE(pHeaderValue, HeaderValueLength);
|
|
|
|
// Exited because we found a match. Call the
|
|
// handler for this header to take cake of this.
|
|
|
|
Status = (*(pCurrentHeaderMap->pClientHandler))(
|
|
pRequest,
|
|
pHeaderValue,
|
|
HeaderValueLength,
|
|
pCurrentHeaderMap->HeaderID
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
ASSERT(BytesTaken != 0);
|
|
|
|
*pBytesTaken = pCurrentHeaderMap->HeaderLength + BytesTaken;
|
|
goto end;
|
|
|
|
}
|
|
|
|
// If we get here, we exited out early because a match
|
|
// failed, so keep going.
|
|
}
|
|
else if (SmallHeader == FALSE)
|
|
{
|
|
//
|
|
// Remember that we didn't check a header map entry
|
|
// because the bytes in the buffer was not LONGLONG
|
|
// aligned
|
|
//
|
|
SmallHeader = TRUE;
|
|
}
|
|
|
|
// Either didn't match or didn't have enough bytes for the
|
|
// check. In either case, check the next header map entry.
|
|
|
|
pCurrentHeaderMap++;
|
|
}
|
|
|
|
// Got all the way through the appropriate header map entries
|
|
// without a match. This could be because we're dealing with a
|
|
// header we don't know about or because it's a header we
|
|
// care about that was too small to do the fast check. The
|
|
// latter case should be very rare, but we still need to
|
|
// handle it.
|
|
|
|
// Update the current header map pointer to point back to the
|
|
// first of the possibles. If there were no possibles,
|
|
// the pointer will be NULL and the HeaderMapCount 0, so it'll
|
|
// stay NULL. Otherwise the subtraction will back it up the
|
|
// appropriate amount.
|
|
|
|
if (SmallHeader)
|
|
{
|
|
pCurrentHeaderMap -= HeaderMapCount;
|
|
}
|
|
else
|
|
{
|
|
pCurrentHeaderMap = NULL;
|
|
HeaderMapCount = 0;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
pCurrentHeaderMap = NULL;
|
|
HeaderMapCount = 0;
|
|
}
|
|
|
|
// At this point either the header starts with a non-alphabetic
|
|
// character or we don't have a set of header map entries for it.
|
|
|
|
Status = UcpLookupHeader(
|
|
pRequest,
|
|
pHttpRequest,
|
|
HttpRequestLength,
|
|
pCurrentHeaderMap,
|
|
HeaderMapCount,
|
|
&BytesTaken
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
// Lookup header returns the total bytes taken, including the header name
|
|
//
|
|
*pBytesTaken = BytesTaken;
|
|
|
|
end:
|
|
if(pFoldingBuffer != NULL)
|
|
{
|
|
UL_FREE_POOL(pFoldingBuffer, UC_HEADER_FOLDING_POOL_TAG);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UcParseHeader
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Parses WWW-Authenticate header value. The value can contain multiple
|
|
challenges. Each challenge can have zero or more comma-separated
|
|
auth-parameters.
|
|
|
|
The routine returns pointers (pointing into the original header value)
|
|
to each auth scheme found in the header.
|
|
|
|
Arguments:
|
|
|
|
IN pAuthHeader - WWW-Authenticate header value (value only)
|
|
IN AuthHeaderLength - Length of the header value
|
|
OUT AuthSchemes - Contains pointers to various auth schemes
|
|
present in the header value.
|
|
e.g. AuthSchemes[HttpAuthTypeBasic] will
|
|
be initialized to point to Basic scheme
|
|
portion of the AuthHeader.
|
|
|
|
Return Values:
|
|
|
|
Status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UcParseWWWAuthenticateHeader(
|
|
IN PCSTR pAuthHeader,
|
|
IN ULONG AuthHeaderLength,
|
|
OUT PHTTP_AUTH_PARSED_PARAMS pAuthParsedParams
|
|
)
|
|
{
|
|
ULONG i;
|
|
NTSTATUS Status;
|
|
PCSTR ptr = pAuthHeader;
|
|
|
|
// Sanity check
|
|
ASSERT(pAuthHeader && AuthHeaderLength);
|
|
ASSERT(pAuthParsedParams);
|
|
|
|
do
|
|
{
|
|
// skip white space
|
|
while (AuthHeaderLength && IS_HTTP_LWS(*ptr))
|
|
AuthHeaderLength--, ptr++;
|
|
|
|
// See if any header left to parse
|
|
if (AuthHeaderLength == 0)
|
|
break;
|
|
|
|
// See if any scheme name matches
|
|
for (i = 1; i < HttpAuthTypesCount; i++)
|
|
{
|
|
// Quick test for lengths and delimiters
|
|
if ((AuthHeaderLength == HttpAuthScheme[i].NameLength) ||
|
|
((AuthHeaderLength > HttpAuthScheme[i].NameLength) &&
|
|
(IS_HTTP_LWS(ptr[HttpAuthScheme[i].NameLength]) ||
|
|
ptr[HttpAuthScheme[i].NameLength] == ',')))
|
|
{
|
|
// See if the scheme name matches
|
|
if (_strnicmp(
|
|
ptr,
|
|
HttpAuthScheme[i].Name,
|
|
HttpAuthScheme[i].NameLength) == 0)
|
|
{
|
|
// An auth scheme should not appear more than once!
|
|
if (pAuthParsedParams[i].bPresent)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
// Parse its parameters, if any
|
|
Status = HttpAuthScheme[i].ParamParser(
|
|
&HttpAuthScheme[i],
|
|
&pAuthParsedParams[i],
|
|
&ptr,
|
|
&AuthHeaderLength
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
return Status;
|
|
|
|
// No need to loop thro' other schemes
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Error if we don't identify a scheme
|
|
if (i >= HttpAuthTypesCount)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
} while (1);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Searches for an attribute=value pair.
|
|
|
|
The routine returns
|
|
-1 : if parse error
|
|
0 : if no attribute value pair is found
|
|
1 : if only "attribute" is found (not followed by an '=')
|
|
3 : if a valid attribute=value pair is found
|
|
|
|
WWW-Authenticate header pointer and length are updated to next non-space
|
|
character. If the return value is 3, the pointer and length are updated
|
|
past the attribute-value pair.
|
|
|
|
Arguments:
|
|
|
|
IN OUT ppHeader - Pointer to WWW-Authenticate header value
|
|
IN OUT pHeaderLength - Pointer to length of WWW-Auth header value
|
|
OUT Attrib - Pointer to attribute
|
|
OUT AttribLen - Length of the attribute string
|
|
OUT Value - Pointer to value
|
|
OUT ValueLen - Length of the value string
|
|
|
|
Return Values:
|
|
|
|
Status.
|
|
|
|
--***************************************************************************/
|
|
LONG
|
|
UcpFindAttribValuePair(
|
|
PCSTR *ppHeader,
|
|
ULONG *pHeaderLength,
|
|
PCSTR *Attrib,
|
|
ULONG *AttribLen,
|
|
PCSTR *Value,
|
|
ULONG *ValueLen
|
|
)
|
|
{
|
|
LONG retval = 3;
|
|
PCSTR pHeader = *ppHeader;
|
|
ULONG HeaderLength = *pHeaderLength;
|
|
|
|
// Initialize return values
|
|
*Attrib = NULL;
|
|
*AttribLen = 0;
|
|
*Value = NULL;
|
|
*ValueLen = 0;
|
|
|
|
// Skip space
|
|
while (HeaderLength && IS_HTTP_LWS(*pHeader))
|
|
HeaderLength--, pHeader++;
|
|
|
|
// Update header pointer and length
|
|
*ppHeader = pHeader;
|
|
*pHeaderLength = HeaderLength;
|
|
|
|
// Remember the start of an attribute
|
|
*Attrib = pHeader;
|
|
|
|
// Skip the attribute name
|
|
while (HeaderLength && IS_HTTP_TOKEN(*pHeader))
|
|
HeaderLength--, pHeader++;
|
|
|
|
// Length of the attribute
|
|
*AttribLen = (ULONG)(pHeader - *Attrib);
|
|
|
|
// If we did not see any attribute name
|
|
if (pHeader == *Attrib)
|
|
{
|
|
// Nope.
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
// an attribute must be terminated by an '='
|
|
if (HeaderLength == 0 || *pHeader != '=')
|
|
{
|
|
// Saw only an attribute
|
|
retval = 1;
|
|
goto done;
|
|
}
|
|
|
|
// Skip '='
|
|
HeaderLength--, pHeader++;
|
|
|
|
// Quoted string
|
|
if (HeaderLength && *pHeader == '"')
|
|
{
|
|
// Skip '"'
|
|
HeaderLength--, pHeader++;
|
|
|
|
// Remember the start of value ('"' not included)
|
|
*Value = pHeader;
|
|
|
|
// Find the matching '"'
|
|
while (HeaderLength && *pHeader != '"')
|
|
{
|
|
if (*pHeader == '\\')
|
|
{
|
|
// Skip '\\' char
|
|
HeaderLength--, pHeader++;
|
|
|
|
if (HeaderLength == 0)
|
|
{
|
|
// Error! There must be at least one char following '\\'.
|
|
retval = -1;
|
|
goto done;
|
|
}
|
|
// Else skip the char that appeared after '\\'.
|
|
}
|
|
|
|
HeaderLength--, pHeader++;
|
|
}
|
|
|
|
// Calculate length of the value string
|
|
*ValueLen = (ULONG)(pHeader - *Value);
|
|
|
|
// Error if we did not find a matching '"'
|
|
if (HeaderLength == 0)
|
|
retval = -1;
|
|
else
|
|
// Skip '"'
|
|
HeaderLength--, pHeader++;
|
|
}
|
|
// Token
|
|
else
|
|
{
|
|
// Remember start of the value string
|
|
*Value = pHeader;
|
|
|
|
// Find the end of value string
|
|
while (HeaderLength && IS_HTTP_TOKEN(*pHeader))
|
|
HeaderLength--, pHeader++;
|
|
|
|
// Calculate the length of the value string
|
|
*ValueLen = (ULONG)(pHeader - *Value);
|
|
}
|
|
|
|
// Update header pointer and length
|
|
*pHeaderLength = HeaderLength;
|
|
*ppHeader = pHeader;
|
|
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Parses WWW-Authenticate header for an authentication scheme which
|
|
has parameters in the form of attribute value pairs. (e.g. Digest)
|
|
|
|
The routine returns pointer to the parameter values and their lengths.
|
|
WWW-Authenticate header pointer and length are updated.
|
|
|
|
Arguments:
|
|
|
|
IN pAuthScheme - Pointer to the auth scheme being parsed
|
|
OUT pAuthParamValues - Output parameter value pointers and lengths
|
|
IN OUT ppHeader - Pointer to WWW-Authenticate header value
|
|
IN OUT pHeaderLength - Pointer to length of WWW-Auth header value
|
|
|
|
Return Values:
|
|
|
|
Status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UcpParseAuthParams(
|
|
PHTTP_AUTH_SCHEME pAuthScheme,
|
|
PHTTP_AUTH_PARSED_PARAMS pAuthParsedParams,
|
|
PCSTR *ppHeader,
|
|
ULONG *pHeaderLength
|
|
)
|
|
{
|
|
ULONG i;
|
|
LONG retval;
|
|
PCSTR attrib, value;
|
|
ULONG attribLen, valueLen;
|
|
|
|
ULONG ParamCount = 0;
|
|
PCSTR pHeader = *ppHeader;
|
|
ULONG HeaderLength = *pHeaderLength;
|
|
PHTTP_AUTH_PARAM_VALUE pAuthParamValues;
|
|
|
|
// Sanity check
|
|
ASSERT(pAuthParsedParams);
|
|
ASSERT(pHeader && HeaderLength);
|
|
ASSERT(pAuthScheme);
|
|
ASSERT(pAuthScheme->NumberParams);
|
|
ASSERT(pAuthScheme->NameLength <= HeaderLength);
|
|
ASSERT(_strnicmp(pAuthScheme->Name, pHeader, pAuthScheme->NameLength) == 0);
|
|
|
|
pAuthParsedParams->pScheme = pHeader;
|
|
pAuthParamValues = pAuthParsedParams->Params;
|
|
|
|
// Zero out return value
|
|
if (pAuthParamValues)
|
|
RtlZeroMemory(
|
|
pAuthParamValues,
|
|
sizeof(*pAuthParamValues) * pAuthScheme->NumberParams
|
|
);
|
|
|
|
// Skip the scheme name
|
|
pHeader += pAuthScheme->NameLength;
|
|
HeaderLength -= pAuthScheme->NameLength;
|
|
|
|
do {
|
|
|
|
// Find an attribute value pair
|
|
retval = UcpFindAttribValuePair(
|
|
&pHeader,
|
|
&HeaderLength,
|
|
&attrib,
|
|
&attribLen,
|
|
&value,
|
|
&valueLen
|
|
);
|
|
|
|
// Parse error!
|
|
if (retval < 0)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
switch (retval)
|
|
{
|
|
case 0: // No attribute value pair found
|
|
case 1: // Only attribute found (not followed by '=')
|
|
goto done;
|
|
|
|
case 3: // valid attribute value pair found
|
|
|
|
// A valid parameter was found.
|
|
ParamCount++;
|
|
|
|
// See if the caller is interested in parameter values
|
|
if (pAuthParamValues)
|
|
{
|
|
// See if the auth scheme supports the attribute
|
|
for (i = 0; i < pAuthScheme->NumberParams; i++)
|
|
{
|
|
if (attribLen == pAuthScheme->ParamAttribs[i].Length &&
|
|
_strnicmp(attrib, pAuthScheme->ParamAttribs[i].Name,
|
|
attribLen) == 0)
|
|
{
|
|
if (pAuthParamValues[i].Value ||
|
|
pAuthParamValues[i].Length)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
pAuthParsedParams->NumberKnownParams++;
|
|
|
|
// Return the parameter value
|
|
pAuthParamValues[i].Value = value;
|
|
pAuthParamValues[i].Length = valueLen;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i >= pAuthScheme->NumberParams)
|
|
pAuthParsedParams->NumberUnknownParams++;
|
|
}
|
|
|
|
// Skip blank spaces
|
|
while (HeaderLength && IS_HTTP_LWS(*pHeader))
|
|
HeaderLength--, pHeader++;
|
|
|
|
// A ',' must be present.
|
|
if (HeaderLength)
|
|
{
|
|
if (*pHeader != ',')
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
HeaderLength--, pHeader++;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
// Should not be here!
|
|
ASSERT(FALSE);
|
|
break;
|
|
}
|
|
|
|
} while (HeaderLength);
|
|
|
|
done:
|
|
|
|
// We must have parsed at least one parameter.
|
|
if (ParamCount == 0)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
// Update WWW-Authenticate header pointer and length
|
|
pAuthParsedParams->bPresent = TRUE;
|
|
pAuthParsedParams->Length = (ULONG)(pHeader - pAuthParsedParams->pScheme);
|
|
*ppHeader = pHeader;
|
|
*pHeaderLength = HeaderLength;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Parses WWW-Authenticate header for an authentication scheme which
|
|
has only one parameter NOT in the form of attribute value pair.
|
|
(e.g. auth scheme NTLM).
|
|
|
|
The routine returns pointer to the parameter value and its length.
|
|
WWW-Authenticate header pointer and length are updated.
|
|
|
|
Arguments:
|
|
|
|
IN pAuthScheme - Pointer to the auth scheme being parsed
|
|
OUT pAuthParams - Output parameter pointer and its length
|
|
IN OUT ppHeader - Pointer to WWW-Authenticate header value
|
|
IN OUT pHeaderLength - Pointer to the length of WWW-Auth header value
|
|
|
|
Return Values:
|
|
|
|
Status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UcpParseAuthBlob(
|
|
PHTTP_AUTH_SCHEME pAuthScheme,
|
|
PHTTP_AUTH_PARSED_PARAMS pAuthParsedParams,
|
|
PCSTR *ppHeader,
|
|
ULONG *pHeaderLength
|
|
)
|
|
{
|
|
PCSTR pHeader = *ppHeader;
|
|
ULONG HeaderLength = *pHeaderLength;
|
|
PCSTR value; // Pointer to the parameter value
|
|
PHTTP_AUTH_PARAM_VALUE pAuthParams;
|
|
|
|
// Sanity check
|
|
ASSERT(pAuthParsedParams);
|
|
ASSERT(pHeader);
|
|
ASSERT(pAuthScheme);
|
|
ASSERT(pAuthScheme->NumberParams == 0);
|
|
ASSERT(pAuthScheme->NameLength <= HeaderLength);
|
|
ASSERT(_strnicmp(pAuthScheme->Name, pHeader, pAuthScheme->NameLength) == 0);
|
|
|
|
pAuthParsedParams->pScheme = pHeader;
|
|
pAuthParams = pAuthParsedParams->Params;
|
|
|
|
// Zero out the return values
|
|
if (pAuthParams)
|
|
RtlZeroMemory(pAuthParams, sizeof(*pAuthParams));
|
|
|
|
// Skip the scheme name
|
|
pHeader += pAuthScheme->NameLength;
|
|
HeaderLength -= pAuthScheme->NameLength;
|
|
|
|
// Skip white spaces
|
|
while (HeaderLength && IS_HTTP_LWS(*pHeader))
|
|
HeaderLength--, pHeader++;
|
|
|
|
// Begining of paramter value
|
|
value = pHeader;
|
|
|
|
// Search for the end
|
|
while (HeaderLength && *pHeader != ',')
|
|
HeaderLength--, pHeader++;
|
|
|
|
// Return the parameter value, if any, if asked
|
|
if (pHeader != value && pAuthParams)
|
|
{
|
|
pAuthParsedParams->NumberUnknownParams++;
|
|
pAuthParams->Value = value;
|
|
pAuthParams->Length = (ULONG)(pHeader - value);
|
|
}
|
|
|
|
// Skip the trailing ','
|
|
if (HeaderLength > 0 && *pHeader == ',')
|
|
HeaderLength--, pHeader++;
|
|
|
|
pAuthParsedParams->bPresent = TRUE;
|
|
// Update header pointer and length
|
|
pAuthParsedParams->Length = (ULONG)(pHeader - pAuthParsedParams->pScheme);
|
|
*ppHeader = pHeader;
|
|
*pHeaderLength = HeaderLength;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/****************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
The default routine for handling single headers.
|
|
|
|
Arguments:
|
|
|
|
pRequest - pointer to internal request.
|
|
pHeader - Pointer to the header value.
|
|
HeaderLength - Length of data pointed to by pHeader.
|
|
HeaderID - ID of the header.
|
|
pBytesTaken - BytesTaken
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if success, else failure.
|
|
|
|
|
|
--****************************************************************************/
|
|
NTSTATUS
|
|
UcSingleHeaderHandler(
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
IN PUCHAR pHeader,
|
|
IN ULONG HeaderValueLength,
|
|
IN HTTP_HEADER_ID HeaderID
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG AlignLength;
|
|
PUCHAR pBufferHead;
|
|
PUCHAR pBufferTail;
|
|
PHTTP_KNOWN_HEADER pKnownHeaders;
|
|
|
|
pKnownHeaders = pRequest->CurrentBuffer.pResponse->Headers.KnownHeaders;
|
|
pBufferHead = pRequest->CurrentBuffer.pOutBufferHead;
|
|
pBufferTail = pRequest->CurrentBuffer.pOutBufferTail;
|
|
|
|
// do we have an existing header?
|
|
//
|
|
if (pKnownHeaders[HeaderID].RawValueLength == 0)
|
|
{
|
|
// No existing header, just save this pointer for now.
|
|
//
|
|
|
|
AlignLength = ALIGN_UP(HeaderValueLength, PVOID);
|
|
|
|
if(pRequest->CurrentBuffer.BytesAvailable >= AlignLength)
|
|
{
|
|
PUCHAR pBuffer = pBufferTail-AlignLength;
|
|
|
|
ASSERT(pBuffer >= pBufferHead);
|
|
|
|
//
|
|
// copy and NULL terminate.
|
|
//
|
|
|
|
RtlCopyMemory(pBuffer, pHeader, HeaderValueLength);
|
|
|
|
pKnownHeaders[HeaderID].RawValueLength =
|
|
(USHORT)HeaderValueLength;
|
|
|
|
pKnownHeaders[HeaderID].pRawValue = (PCSTR) pBuffer;
|
|
|
|
pRequest->CurrentBuffer.pOutBufferTail = pBuffer;
|
|
|
|
pRequest->CurrentBuffer.BytesAvailable -= AlignLength;
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// uh oh. Have an existing header, fail the request.
|
|
//
|
|
|
|
UlTraceError(PARSER, (
|
|
"[UcSingleHeaderHandler]: (pHeader = %p)\n"
|
|
" ERROR: multiple headers not allowed.\n",
|
|
pHeader
|
|
));
|
|
|
|
Status = STATUS_INVALID_NETWORK_RESPONSE;
|
|
goto end;
|
|
|
|
}
|
|
|
|
end:
|
|
return Status;
|
|
|
|
} // UcSingleHeaderHandler
|
|
|
|
|
|
/****************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
The default routine for handling multiple headers. This function handles
|
|
multiple headers with the same name, and appends the values together
|
|
separated by commas.
|
|
|
|
Arguments:
|
|
|
|
pRequest - pointer to internal request.
|
|
pHeader - Pointer to the header value.
|
|
HeaderLength - Length of data pointed to by pHeader.
|
|
HeaderID - ID of the header.
|
|
pBytesTaken - BytesTaken
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if success, else failure.
|
|
|
|
|
|
--****************************************************************************/
|
|
NTSTATUS
|
|
UcMultipleHeaderHandler(
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
IN PUCHAR pHeader,
|
|
IN ULONG HeaderValueLength,
|
|
IN HTTP_HEADER_ID HeaderID
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG AlignLength;
|
|
PUCHAR pBufferHead;
|
|
PUCHAR pBufferTail;
|
|
PHTTP_KNOWN_HEADER pKnownHeaders;
|
|
|
|
|
|
pKnownHeaders = pRequest->CurrentBuffer.pResponse->Headers.KnownHeaders;
|
|
pBufferHead = pRequest->CurrentBuffer.pOutBufferHead;
|
|
pBufferTail = pRequest->CurrentBuffer.pOutBufferTail;
|
|
|
|
// do we have an existing header?
|
|
//
|
|
if (pKnownHeaders[HeaderID].RawValueLength == 0)
|
|
{
|
|
AlignLength = ALIGN_UP(HeaderValueLength, PVOID);
|
|
|
|
if(pRequest->CurrentBuffer.BytesAvailable >= AlignLength)
|
|
{
|
|
PUCHAR pBuffer = pBufferTail-AlignLength;
|
|
|
|
ASSERT(pBuffer >= pBufferHead);
|
|
|
|
//
|
|
// copy & null terminate it.
|
|
//
|
|
RtlCopyMemory(pBuffer, pHeader, HeaderValueLength);
|
|
|
|
pKnownHeaders[HeaderID].RawValueLength =
|
|
(USHORT)HeaderValueLength;
|
|
|
|
pKnownHeaders[HeaderID].pRawValue = (PCSTR) pBuffer;
|
|
|
|
pRequest->CurrentBuffer.pOutBufferTail = pBuffer;
|
|
|
|
pRequest->CurrentBuffer.BytesAvailable -= AlignLength;
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ULONG OldHeaderLength;
|
|
ULONG CombinedHeaderLength;
|
|
PUCHAR pBuffer;
|
|
|
|
// Have an existing header, append this one.
|
|
|
|
OldHeaderLength = pKnownHeaders[HeaderID].RawValueLength;
|
|
CombinedHeaderLength = OldHeaderLength + HeaderValueLength + 1;
|
|
|
|
AlignLength = ALIGN_UP(CombinedHeaderLength, PVOID);
|
|
|
|
//
|
|
// UC_BUGBUG:
|
|
//
|
|
if(pRequest->CurrentBuffer.BytesAvailable >= AlignLength)
|
|
{
|
|
|
|
pBuffer = pBufferTail-AlignLength;
|
|
pRequest->CurrentBuffer.pOutBufferTail = pBuffer;
|
|
|
|
ASSERT(pBuffer >= pBufferHead);
|
|
|
|
|
|
// Copy the old header.
|
|
RtlCopyMemory(pBuffer,
|
|
pKnownHeaders[HeaderID].pRawValue,
|
|
pKnownHeaders[HeaderID].RawValueLength);
|
|
|
|
//
|
|
// Save pointers to the new values.
|
|
//
|
|
pKnownHeaders[HeaderID].pRawValue = (PCSTR) pBuffer;
|
|
|
|
|
|
// advance the buffer.
|
|
|
|
pBuffer += pKnownHeaders[HeaderID].RawValueLength;
|
|
|
|
// Add a ','
|
|
|
|
*pBuffer = ',';
|
|
pBuffer ++;
|
|
|
|
//
|
|
// append the new header
|
|
//
|
|
|
|
RtlCopyMemory(pBuffer, pHeader, HeaderValueLength);
|
|
|
|
//
|
|
// Account for the new header + a ','
|
|
//
|
|
pKnownHeaders[HeaderID].RawValueLength +=
|
|
((USHORT)HeaderValueLength + 1);
|
|
|
|
pRequest->CurrentBuffer.BytesAvailable -= AlignLength;
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
}
|
|
|
|
end:
|
|
return Status;
|
|
|
|
} // UcMultipleHeaderHandler
|
|
|
|
|
|
/****************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
The default routine for handling ProxyAuthenticte & WwwAuthenticate
|
|
|
|
Arguments:
|
|
|
|
pRequest - pointer to internal request.
|
|
pHeader - Pointer to the header value.
|
|
HeaderLength - Length of data pointed to by pHeader.
|
|
HeaderID - ID of the header.
|
|
pBytesTaken - BytesTaken
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if success, else failure.
|
|
|
|
|
|
--****************************************************************************/
|
|
|
|
NTSTATUS
|
|
UcAuthenticateHeaderHandler(
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
IN PUCHAR pHeader,
|
|
IN ULONG HeaderLength,
|
|
IN HTTP_HEADER_ID HeaderID
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PCSTR pBuffer;
|
|
ULONG BufLen;
|
|
PHTTP_KNOWN_HEADER pKnownHeaders;
|
|
|
|
pKnownHeaders = pRequest->CurrentBuffer.pResponse->Headers.KnownHeaders;
|
|
|
|
ASSERT(HeaderID == HttpHeaderProxyAuthenticate ||
|
|
HeaderID == HttpHeaderWwwAuthenticate);
|
|
|
|
Status = UcMultipleHeaderHandler(pRequest,
|
|
pHeader,
|
|
HeaderLength,
|
|
HeaderID
|
|
);
|
|
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// First detect the auth scheme.
|
|
//
|
|
|
|
pBuffer = pKnownHeaders[HeaderID].pRawValue;
|
|
BufLen = pKnownHeaders[HeaderID].RawValueLength;
|
|
|
|
if(HeaderID == HttpHeaderWwwAuthenticate)
|
|
{
|
|
if((Status = UcParseAuthChallenge(
|
|
pRequest->pAuthInfo,
|
|
pBuffer,
|
|
BufLen,
|
|
pRequest,
|
|
&pRequest->CurrentBuffer.pResponse->Flags
|
|
)) != STATUS_SUCCESS)
|
|
{
|
|
return Status;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if((Status = UcParseAuthChallenge(
|
|
pRequest->pProxyAuthInfo,
|
|
pBuffer,
|
|
BufLen,
|
|
pRequest,
|
|
&pRequest->CurrentBuffer.pResponse->Flags
|
|
)) != STATUS_SUCCESS)
|
|
{
|
|
return Status;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/****************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
The default routine for handling Content-Length header
|
|
|
|
Arguments:
|
|
|
|
pRequest - pointer to internal request.
|
|
pHeader - Pointer to the header value.
|
|
HeaderLength - Length of data pointed to by pHeader.
|
|
HeaderID - ID of the header.
|
|
pBytesTaken - BytesTaken
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if success, else failure.
|
|
|
|
|
|
--****************************************************************************/
|
|
NTSTATUS
|
|
UcContentLengthHeaderHandler(
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
IN PUCHAR pHeader,
|
|
IN ULONG HeaderLength,
|
|
IN HTTP_HEADER_ID HeaderID
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUCHAR pBuffer;
|
|
USHORT Length;
|
|
PHTTP_KNOWN_HEADER pKnownHeaders;
|
|
|
|
pKnownHeaders = pRequest->CurrentBuffer.pResponse->Headers.KnownHeaders;
|
|
|
|
ASSERT(HeaderID == HttpHeaderContentLength);
|
|
|
|
Status = UcSingleHeaderHandler(pRequest,
|
|
pHeader,
|
|
HeaderLength,
|
|
HeaderID
|
|
);
|
|
|
|
if(Status == STATUS_SUCCESS)
|
|
{
|
|
//
|
|
// Convert to ULONG
|
|
//
|
|
|
|
pRequest->ResponseContentLengthSpecified = TRUE;
|
|
pRequest->ResponseContentLength = 0;
|
|
pBuffer = (PUCHAR) pKnownHeaders[HttpHeaderContentLength].pRawValue;
|
|
Length = pKnownHeaders[HttpHeaderContentLength].RawValueLength;
|
|
|
|
Status = UlAnsiToULongLong(
|
|
pBuffer,
|
|
Length,
|
|
10,
|
|
&pRequest->ResponseContentLength
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
// Eat the error code that is returned by UlAnsiToULongLong
|
|
|
|
Status = STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/****************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
The default routine for handling transfer encoding header
|
|
|
|
Arguments:
|
|
|
|
pRequest - pointer to internal request.
|
|
pHeader - Pointer to the header value.
|
|
HeaderLength - Length of data pointed to by pHeader.
|
|
HeaderID - ID of the header.
|
|
pBytesTaken - BytesTaken
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if success, else failure.
|
|
|
|
|
|
--****************************************************************************/
|
|
NTSTATUS
|
|
UcTransferEncodingHeaderHandler(
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
IN PUCHAR pHeader,
|
|
IN ULONG HeaderLength,
|
|
IN HTTP_HEADER_ID HeaderID
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PHTTP_KNOWN_HEADER pKnownHeaders;
|
|
|
|
pKnownHeaders = pRequest->CurrentBuffer.pResponse->Headers.KnownHeaders;
|
|
|
|
ASSERT(HeaderID == HttpHeaderTransferEncoding);
|
|
|
|
Status = UcMultipleHeaderHandler(pRequest,
|
|
pHeader,
|
|
HeaderLength,
|
|
HeaderID
|
|
);
|
|
|
|
if(Status == STATUS_SUCCESS)
|
|
{
|
|
//
|
|
// Since this is a multiple header, we have to do a strstr.
|
|
// We can't do strstr, since input string is not NULL terminated.
|
|
// so, we use our internal function
|
|
//
|
|
|
|
if(UxStriStr(
|
|
pKnownHeaders[HttpHeaderTransferEncoding].pRawValue,
|
|
"identity",
|
|
pKnownHeaders[HttpHeaderTransferEncoding].RawValueLength
|
|
))
|
|
{
|
|
pRequest->ResponseEncodingChunked = FALSE;
|
|
}
|
|
else
|
|
{
|
|
pRequest->ResponseEncodingChunked = TRUE;
|
|
pRequest->ResponseContentLength = 0;
|
|
pRequest->ParsedFirstChunk = 0;
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/****************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
The default routine for handling Connection close header
|
|
|
|
Arguments:
|
|
|
|
pRequest - pointer to internal request.
|
|
pHeader - Pointer to the header value.
|
|
HeaderLength - Length of data pointed to by pHeader.
|
|
HeaderID - ID of the header.
|
|
pBytesTaken - BytesTaken
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if success, else failure.
|
|
|
|
|
|
--****************************************************************************/
|
|
NTSTATUS
|
|
UcConnectionHeaderHandler(
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
IN PUCHAR pHeader,
|
|
IN ULONG HeaderLength,
|
|
IN HTTP_HEADER_ID HeaderID
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PHTTP_KNOWN_HEADER pKnownHeaders;
|
|
|
|
pKnownHeaders = pRequest->CurrentBuffer.pResponse->Headers.KnownHeaders;
|
|
|
|
ASSERT(HeaderID == HttpHeaderConnection);
|
|
|
|
Status = UcMultipleHeaderHandler(pRequest,
|
|
pHeader,
|
|
HeaderLength,
|
|
HeaderID
|
|
);
|
|
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
if(pRequest->ResponseVersion11)
|
|
{
|
|
ASSERT(pRequest->ResponseConnectionClose == FALSE);
|
|
|
|
// If it's a 1.1 response, we have to look for the
|
|
// Connection:Close header
|
|
|
|
if(UxStriStr(
|
|
pKnownHeaders[HttpHeaderConnection].pRawValue,
|
|
"close",
|
|
pKnownHeaders[HttpHeaderConnection].RawValueLength))
|
|
{
|
|
pRequest->ResponseConnectionClose = TRUE;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// If it's a 1.0 server, by default we close the connection.
|
|
// unless we see a Keepalive
|
|
|
|
ASSERT(pRequest->ResponseConnectionClose == TRUE);
|
|
|
|
if(UxStriStr(
|
|
pKnownHeaders[HttpHeaderConnection].pRawValue,
|
|
"keep-alive",
|
|
pKnownHeaders[HttpHeaderConnection].RawValueLength))
|
|
{
|
|
pRequest->ResponseConnectionClose = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/****************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
The default routine for handling Content-Type header (used for byte range)
|
|
|
|
Arguments:
|
|
|
|
pRequest - pointer to internal request.
|
|
pHeader - Pointer to the header value.
|
|
HeaderLength - Length of data pointed to by pHeader.
|
|
HeaderID - ID of the header.
|
|
pBytesTaken - BytesTaken
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if success, else failure.
|
|
|
|
|
|
--****************************************************************************/
|
|
NTSTATUS
|
|
UcContentTypeHeaderHandler(
|
|
IN PUC_HTTP_REQUEST pRequest,
|
|
IN PUCHAR pHeader,
|
|
IN ULONG HeaderLength,
|
|
IN HTTP_HEADER_ID HeaderID
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PHTTP_KNOWN_HEADER pKnownHeaders;
|
|
BOOLEAN bEndQuote;
|
|
|
|
pKnownHeaders = pRequest->CurrentBuffer.pResponse->Headers.KnownHeaders;
|
|
|
|
Status = UcSingleHeaderHandler(
|
|
pRequest,
|
|
pHeader,
|
|
HeaderLength,
|
|
HeaderID
|
|
);
|
|
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
if(pKnownHeaders[HttpHeaderContentType].RawValueLength <
|
|
(STRLEN_LIT("multipart/byteranges")))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
if(_strnicmp((const char *)
|
|
(pKnownHeaders[HttpHeaderContentType].pRawValue),
|
|
"multipart/byteranges",
|
|
STRLEN_LIT("multipart/byteranges")) == 0)
|
|
{
|
|
PCSTR s;
|
|
USHORT l;
|
|
|
|
// Now, we need to store the string separator in the internal
|
|
// request structure, so that it can be used for parsing out
|
|
// individual ranges.
|
|
//
|
|
// The content-type header is encoded as follows:
|
|
// multipart/byteranges; boundary=THIS_STRING_SEPERATES.
|
|
|
|
// Can't use UcFindKeyValuePair as the string separator might have
|
|
// a space (quoted string).
|
|
//
|
|
s = pKnownHeaders[HttpHeaderContentType].pRawValue;
|
|
l = pKnownHeaders[HttpHeaderContentType].RawValueLength;
|
|
|
|
bEndQuote = FALSE;
|
|
|
|
// Walk up to the '='
|
|
|
|
while(l)
|
|
{
|
|
if(*s == '=')
|
|
{
|
|
s++; l--;
|
|
|
|
// Ignore the quote after the string
|
|
|
|
if(l && *s == '"')
|
|
{
|
|
bEndQuote = TRUE;
|
|
s++; l--;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
s++; l--;
|
|
}
|
|
|
|
if(l == 0)
|
|
{
|
|
// We have reached the end with no boundary separator!
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
|
|
pRequest->MultipartStringSeparatorLength = 2 + l;
|
|
|
|
if(pRequest->MultipartStringSeparatorLength+1 <
|
|
MULTIPART_SEPARATOR_SIZE)
|
|
{
|
|
pRequest->pMultipartStringSeparator =
|
|
pRequest->MultipartStringSeparatorBuffer;
|
|
}
|
|
else
|
|
{
|
|
// The string separator is too big, allocate a buffer
|
|
pRequest->pMultipartStringSeparator = (PSTR)
|
|
UL_ALLOCATE_POOL_WITH_QUOTA(
|
|
NonPagedPool,
|
|
pRequest->MultipartStringSeparatorLength+1,
|
|
UC_MULTIPART_STRING_BUFFER_POOL_TAG,
|
|
pRequest->pServerInfo->pProcess
|
|
);
|
|
|
|
if(!pRequest->pMultipartStringSeparator)
|
|
return STATUS_INVALID_NETWORK_RESPONSE;
|
|
}
|
|
|
|
pRequest->pMultipartStringSeparator[0] = '-';
|
|
pRequest->pMultipartStringSeparator[1] = '-';
|
|
|
|
RtlCopyMemory(pRequest->pMultipartStringSeparator+2,
|
|
s,
|
|
pRequest->MultipartStringSeparatorLength-2
|
|
);
|
|
|
|
|
|
// If there was an end quote, then the trailing quote
|
|
// should be ignored.
|
|
|
|
if(bEndQuote)
|
|
{
|
|
pRequest->pMultipartStringSeparator[
|
|
pRequest->MultipartStringSeparatorLength-1] = 0;
|
|
pRequest->MultipartStringSeparatorLength --;
|
|
}
|
|
|
|
pRequest->pMultipartStringSeparator[
|
|
pRequest->MultipartStringSeparatorLength] = ANSI_NULL;
|
|
|
|
pRequest->ResponseMultipartByteranges = TRUE;
|
|
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|