Leaked source code of windows server 2003
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.
 
 
 
 
 
 

6017 lines
172 KiB

/*++
Copyright (c) 1998-2002 Microsoft Corporation
Module Name:
ulparse.c
Abstract:
Contains the kernel-mode server-side HTTP parsing code.
Author:
Henry Sanders (henrysa) 27-Apr-1998
Revision History:
Rajesh Sundaram (rajeshsu) 15-Feb-2002 Moved from parse.c
--*/
#include "precomp.h"
#include "ulparsep.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text( PAGE, UlpFindWSToken )
#pragma alloc_text( PAGE, UlpLookupVerb )
#pragma alloc_text( PAGE, UlpParseFullUrl )
#pragma alloc_text( PAGE, UlLookupHeader )
#pragma alloc_text( PAGE, UlParseHeaderWithHint )
#pragma alloc_text( PAGE, UlParseHeader )
#pragma alloc_text( PAGE, UlParseHeaders )
#pragma alloc_text( PAGE, UlParseHttp )
#pragma alloc_text( PAGE, UlpFormatPort )
#pragma alloc_text( PAGE, UlpCookUrl )
#pragma alloc_text( PAGE, UlGenerateRoutingToken )
#pragma alloc_text( PAGE, UlGenerateFixedHeaders )
#pragma alloc_text( PAGE, UlpGenerateDateHeaderString )
#pragma alloc_text( PAGE, UlGenerateDateHeader )
#pragma alloc_text( INIT, UlInitializeDateCache )
#pragma alloc_text( PAGE, UlTerminateDateCache )
#pragma alloc_text( PAGE, UlComputeFixedHeaderSize )
#pragma alloc_text( PAGE, UlComputeMaxVariableHeaderSize )
#pragma alloc_text( PAGE, UlGenerateVariableHeaders )
#pragma alloc_text( PAGE, UlAppendHeaderValue )
#pragma alloc_text( PAGE, UlSingleHeaderHandler )
#pragma alloc_text( PAGE, UlMultipleHeaderHandler )
#pragma alloc_text( PAGE, UlAcceptHeaderHandler )
#pragma alloc_text( PAGE, UlHostHeaderHandler )
#pragma alloc_text( PAGE, UlCheckCacheControlHeaders )
#pragma alloc_text( PAGE, UlIsAcceptHeaderOk )
#pragma alloc_text( PAGE, UlGetTypeAndSubType )
#if DBG
#pragma alloc_text( PAGE, UlVerbToString )
#pragma alloc_text( PAGE, UlParseStateToString )
#endif
#endif // ALLOC_PRAGMA
#if 0 // Non-Pageable Functions
NOT PAGEABLE -- UlIsContentEncodingOk
#endif // Non-Pageable Functions
//
// Global initialization flag.
//
BOOLEAN g_DateCacheInitialized = FALSE;
//
// Keep track of UrlLength statistics
//
#define URL_LENGTH_STATS 1
#ifdef URL_LENGTH_STATS
struct {
ULONGLONG SumUrlLengths;
ULONG NumUrls;
ULONG NumReallocs;
} g_UrlLengthStats = {0, 0, 0};
#define URL_LENGTH_STATS_UPDATE(UrlLength) \
UlInterlockedAdd64((PLONGLONG) &g_UrlLengthStats.SumUrlLengths, UrlLength);\
InterlockedIncrement((PLONG) &g_UrlLengthStats.NumUrls)
#define URL_LENGTH_STATS_REALLOC() \
InterlockedIncrement((PLONG) &g_UrlLengthStats.NumReallocs)
#else // !URL_LENGTH_STATS
#define URL_LENGTH_STATS_UPDATE(UrlLength) ((void) 0)
#define URL_LENGTH_STATS_REALLOC() ((void) 0)
#endif // !URL_LENGTH_STATS
// Hack for the special case of an AbsUri without a '/' for the abspath
const UCHAR g_SlashPath[3] = "/ ";
//
// The fast verb translation table. Ordered by frequency.
//
const DECLSPEC_ALIGN(UL_CACHE_LINE) FAST_VERB_ENTRY
FastVerbTable[] =
{
CREATE_FAST_VERB_ENTRY(GET),
CREATE_FAST_VERB_ENTRY(HEAD),
CREATE_FAST_VERB_ENTRY(POST),
CREATE_FAST_VERB_ENTRY(PUT),
CREATE_FAST_VERB_ENTRY(DELETE),
CREATE_FAST_VERB_ENTRY(TRACE),
CREATE_FAST_VERB_ENTRY(TRACK),
CREATE_FAST_VERB_ENTRY(OPTIONS),
CREATE_FAST_VERB_ENTRY(CONNECT),
CREATE_FAST_VERB_ENTRY(MOVE),
CREATE_FAST_VERB_ENTRY(COPY),
CREATE_FAST_VERB_ENTRY(MKCOL),
CREATE_FAST_VERB_ENTRY(LOCK),
CREATE_FAST_VERB_ENTRY(UNLOCK),
CREATE_FAST_VERB_ENTRY(SEARCH)
};
//
// The long verb translation table. All known verbs more than 7 characters
// long belong in this table.
//
const LONG_VERB_ENTRY LongVerbTable[] =
{
CREATE_LONG_VERB_ENTRY(PROPFIND),
CREATE_LONG_VERB_ENTRY(PROPPATCH)
};
#define NUMBER_FAST_VERB_ENTRIES DIMENSION(FastVerbTable)
#define NUMBER_LONG_VERB_ENTRIES DIMENSION(LongVerbTable)
//
// The enum->verb translation table for error logging.
//
LONG_VERB_ENTRY NewVerbTable[HttpVerbMaximum] =
{
CREATE_LONG_VERB_ENTRY(Unparsed),
CREATE_LONG_VERB_ENTRY(Unknown),
CREATE_LONG_VERB_ENTRY(Invalid),
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)
};
/*++
Routine Description:
A utility routine to find a token. We take an input pointer, skip any
preceding LWS, then scan the token until we find either LWS or a CRLF
pair.
Arguments:
pBuffer - Buffer to search for token.
BufferLength - Length of data pointed to by pBuffer.
ppTokenStart - Start of token or NULL
pTokenLength - Where to return the length of the token.
Return Values:
STATUS_SUCCESS - Valid token, described by *ppTokenStart and *pTokenLength
STATUS_MORE_PROCESSING_REQUIRED - No terminating WS_TOKEN found
=> retry later with more data
STATUS_INVALID_DEVICE_REQUEST - Invalid token characters.
--*/
NTSTATUS
UlpFindWSToken(
IN PUCHAR pBuffer,
IN ULONG BufferLength,
OUT PUCHAR* ppTokenStart,
OUT PULONG pTokenLength
)
{
PUCHAR pTokenStart;
#if DBG
ULONG OriginalBufferLength = BufferLength;
#endif
PAGED_CODE();
ASSERT(NULL != pBuffer);
ASSERT(NULL != ppTokenStart);
ASSERT(NULL != pTokenLength);
*ppTokenStart = NULL;
//
// First, skip any preceding LWS (SP | HT).
//
while (BufferLength > 0 && IS_HTTP_LWS(*pBuffer))
{
pBuffer++;
BufferLength--;
}
// If we stopped because we ran out of buffer, fail soft.
if (BufferLength == 0)
{
return STATUS_MORE_PROCESSING_REQUIRED;
}
pTokenStart = pBuffer;
// Now skip over the token, until we see (SP | HT | CR | LF)
do
{
// token = 1*<any CHAR except CTLs or separators>
// If a non-token, non-whitespace character is found, fail hard.
if (!IS_HTTP_TOKEN(*pBuffer))
{
UlTraceError(PARSER, (
"http!UlpFindWSToken(): non-token char %02x\n",
*pBuffer
));
return STATUS_INVALID_DEVICE_REQUEST;
}
pBuffer++;
BufferLength--;
} while ( ( BufferLength != 0 ) && ( !IS_HTTP_WS_TOKEN(*pBuffer) ));
// See why we stopped.
if (BufferLength == 0)
{
// Ran out of buffer before end of token. Fail soft.
return STATUS_MORE_PROCESSING_REQUIRED;
}
// Success. Set the token length and return the start of the token.
*ppTokenStart = pTokenStart;
*pTokenLength = DIFF(pBuffer - pTokenStart);
ASSERT(0 < *pTokenLength && *pTokenLength < OriginalBufferLength);
return STATUS_SUCCESS;
} // UlpFindWSToken
/*++
Routine Description:
The slower way to look up a verb. We find the verb in the request and then
look for it in the LongVerbTable. If it's not found, we'll return
UnknownVerb. If it can't be parsed we return UnparsedVerb. Otherwise
we return the verb type.
Arguments:
pRequest - HTTP request.
pHttpRequest - Pointer to the incoming HTTP request data.
HttpRequestLength - Length of data pointed to by pHttpRequest.
pBytesTaken - The total length consumed, including the length of
the verb plus preceding & 1 trailing whitespace.
Return Value:
STATUS_SUCCESS or STATUS_INVALID_DEVICE_REQUEST
--*/
NTSTATUS
UlpLookupVerb(
IN OUT PUL_INTERNAL_REQUEST pRequest,
IN PUCHAR pHttpRequest,
IN ULONG HttpRequestLength,
OUT PULONG pBytesTaken
)
{
NTSTATUS Status;
ULONG TokenLength;
PUCHAR pToken;
PUCHAR pTempRequest;
ULONG TempLength;
ULONG i;
//
// Sanity check.
//
PAGED_CODE();
// Since we may have gotten here due to a extraneous CRLF pair, skip
// any of those now. Need to use a temporary variable since
// the original input pointer and length are used below.
pTempRequest = pHttpRequest;
TempLength = HttpRequestLength;
while ( TempLength != 0 &&
((*pTempRequest == CR) || (*pTempRequest == LF)) )
{
pTempRequest++;
TempLength--;
}
// First find the verb.
Status = UlpFindWSToken(pTempRequest, TempLength, &pToken, &TokenLength);
if (!NT_SUCCESS(Status))
{
if (STATUS_MORE_PROCESSING_REQUIRED == Status)
{
// Didn't find it, let's get more buffer
pRequest->Verb = HttpVerbUnparsed;
*pBytesTaken = 0;
return STATUS_SUCCESS;
}
ASSERT(STATUS_INVALID_DEVICE_REQUEST == Status);
pRequest->Verb = HttpVerbInvalid;
UlTraceError(PARSER, (
"http!UlpLookupVerb(pRequest = %p): "
"invalid token in verb\n",
pRequest
));
UlSetErrorCode(pRequest, UlErrorVerb, NULL);
return Status;
}
ASSERT(STATUS_SUCCESS == Status);
ASSERT(NULL != pToken);
ASSERT(0 < TokenLength && TokenLength < TempLength);
ASSERT(IS_HTTP_WS_TOKEN(pToken[TokenLength]));
// Is the verb terminated by CR or LF (instead of SP or HT),
// or is it ridiculously long? Reject, if so.
if (!IS_HTTP_LWS(pToken[TokenLength]) || TokenLength > MAX_VERB_LENGTH)
{
pRequest->Verb = HttpVerbInvalid;
if (!IS_HTTP_LWS(pToken[TokenLength]))
{
UlTraceError(PARSER, (
"http!UlpLookupVerb(pRequest = %p) "
"ERROR: no LWS after verb, %02x\n",
pRequest, pToken[TokenLength]
));
}
else
{
UlTraceError(PARSER, (
"http!UlpLookupVerb(pRequest = %p) "
"ERROR: Verb too long\n",
pRequest, TokenLength
));
}
UlSetErrorCode(pRequest, UlErrorVerb, NULL);
return STATUS_INVALID_DEVICE_REQUEST;
}
// Otherwise, we found one, so update bytes taken and look up up in
// the tables.
*pBytesTaken = DIFF(pToken - pHttpRequest) + TokenLength + 1;
//
// If we ate some leading whitespace, or if the HttpRequestLength is less
// than sizeof(ULONGLONG), we must look through the "fast" verb table
// again, but do it the "slow" way. Note: verbs are case-sensitive.
//
for (i = 0; i < NUMBER_FAST_VERB_ENTRIES; i++)
{
ASSERT(FastVerbTable[i].RawVerbLength - STRLEN_LIT(" ")
< sizeof(ULONGLONG));
if ((FastVerbTable[i].RawVerbLength == (TokenLength + STRLEN_LIT(" ")))
&& RtlEqualMemory(pToken, FastVerbTable[i].RawVerb.Char,
TokenLength))
{
// It matched. Save the translated verb from the
// table, and bail out.
//
pRequest->Verb = FastVerbTable[i].TranslatedVerb;
return STATUS_SUCCESS;
}
}
//
// Now look through the "long" verb table
//
for (i = 0; i < NUMBER_LONG_VERB_ENTRIES; i++)
{
ASSERT(LongVerbTable[i].RawVerbLength >= sizeof(ULONGLONG));
if (LongVerbTable[i].RawVerbLength == TokenLength &&
RtlEqualMemory(pToken, LongVerbTable[i].RawVerb, TokenLength))
{
// Found it.
//
pRequest->Verb = LongVerbTable[i].TranslatedVerb;
return STATUS_SUCCESS;
}
}
//
// If we got here, we searched both tables and didn't find it.
// It's a raw (unknown) verb
//
pRequest->Verb = HttpVerbUnknown;
pRequest->pRawVerb = pToken;
pRequest->RawVerbLength = (UCHAR) TokenLength;
ASSERT(pRequest->RawVerbLength == TokenLength);
UlTrace(PARSER, (
"http!UlpLookupVerb(pRequest = %p) "
"Unknown verb (%lu) '%.*s'\n",
pRequest, TokenLength, TokenLength, pToken
));
//
// include room for the terminator
//
pRequest->TotalRequestSize += (TokenLength + 1) * sizeof(CHAR);
ASSERT( !(pRequest->RawVerbLength==3
&& RtlEqualMemory(pRequest->pRawVerb,"GET",3)));
return STATUS_SUCCESS;
} // UlpLookupVerb
/*++
Routine Description:
A utility routine to parse an absolute URL in a URL string. When this
is called, we already have loaded the entire url into RawUrl.pUrl and
know that it starts with "http".
This function's job is to set RawUrl.pHost and RawUrl.pAbsPath.
Arguments:
pRequest - Pointer to the HTTP_REQUEST
Return Value:
NTSTATUS
Author:
Henry Sanders () 1998
Paul McDaniel (paulmcd) 6-Mar-1999
--*/
NTSTATUS
UlpParseFullUrl(
IN PUL_INTERNAL_REQUEST pRequest
)
{
PUCHAR pURL;
ULONG UrlLength;
PUCHAR pUrlStart;
//
// Sanity check.
//
PAGED_CODE();
pURL = pRequest->RawUrl.pUrl;
UrlLength = pRequest->RawUrl.Length;
ASSERT(NULL != pURL);
ASSERT(0 < UrlLength);
// First four characters must be "http" (case-insensitive),
// as guaranteed by the caller.
ASSERT(UrlLength >= HTTP_PREFIX_SIZE &&
(*(UNALIGNED64 ULONG *) pURL & HTTP_PREFIX_MASK) == HTTP_PREFIX);
//
// When we're called, we know that the start of the URL must point at
// an absolute scheme prefix. Adjust for that now.
//
pUrlStart = pURL + HTTP_PREFIX_SIZE;
UrlLength -= HTTP_PREFIX_SIZE;
//
// Now check the second half of the absolute URL prefix. We use the larger
// of the two possible prefix lengths here to do the check, because even if
// it's the smaller of the two, we'll need the extra bytes after the prefix
// anyway for the host name.
//
if (UrlLength < HTTP_PREFIX2_SIZE)
{
C_ASSERT(HTTP_PREFIX2_SIZE >= HTTP_PREFIX1_SIZE);
UlTraceError(PARSER, (
"http!UlpParseFullUrl(pRequest = %p) "
"ERROR: no room for URL scheme name\n",
pRequest
));
UlSetErrorCode(pRequest, UlErrorUrl, NULL);
return STATUS_INVALID_DEVICE_REQUEST;
}
// Are the next three characters == "://", i.e, starts with "http://" ?
if ( (*(UNALIGNED64 ULONG *)pUrlStart & HTTP_PREFIX1_MASK) == HTTP_PREFIX1)
{
// Valid absolute URL.
pUrlStart += HTTP_PREFIX1_SIZE;
UrlLength -= HTTP_PREFIX1_SIZE;
ASSERT(0 == _strnicmp((const char*) pRequest->RawUrl.pUrl,
"http://",
STRLEN_LIT("http://")));
if (pRequest->Secure)
{
UlTraceError(PARSER, (
"http!UlpParseFullUrl(pRequest = %p) "
"ERROR: URL scheme name does not match endpoint "
"security: \"http://\" seen on secure endpoint\n",
pRequest
));
UlSetErrorCode(pRequest, UlErrorUrl, NULL);
return STATUS_INVALID_DEVICE_REQUEST;
}
}
// Or are the next four characters == "s://", i.e, starts with "https://" ?
else if ( (*(UNALIGNED64 ULONG *)pUrlStart & HTTP_PREFIX2_MASK)
== HTTP_PREFIX2)
{
// Valid absolute URL.
pUrlStart += HTTP_PREFIX2_SIZE;
UrlLength -= HTTP_PREFIX2_SIZE;
ASSERT(0 == _strnicmp((const char*) pRequest->RawUrl.pUrl,
"https://",
STRLEN_LIT("https://")));
if (!pRequest->Secure)
{
UlTraceError(PARSER, (
"http!UlpParseFullUrl(pRequest = %p) "
"ERROR: URL scheme name does not match endpoint "
"security: \"https://\" seen on insecure endpoint\n",
pRequest
));
UlSetErrorCode(pRequest, UlErrorUrl, NULL);
return STATUS_INVALID_DEVICE_REQUEST;
}
}
else
{
UlTraceError(PARSER, (
"http!UlpParseFullUrl(pRequest = %p) "
"ERROR: invalid URL scheme name\n",
pRequest
));
UlSetErrorCode(pRequest, UlErrorUrl, NULL);
return STATUS_INVALID_DEVICE_REQUEST;
}
//
// OK, we've got a valid absolute URL, and we've skipped over
// the prefix part of it. Save a pointer to the host, and
// search the host string until we find the trailing slash,
// which signifies the end of the host/start of the absolute
// path.
//
pRequest->RawUrl.pHost = pUrlStart;
//
// scan the host looking for the terminator
//
while (UrlLength > 0 && pUrlStart[0] != '/')
{
pUrlStart++;
UrlLength--;
}
if (UrlLength == 0)
{
// Special case: we've received something like
// GET http://www.example.com HTTP/1.1
// (perhaps as a result of a redirect to "http://www.example.com",
// see bug #527947). We will create a special path of "/".
//
pUrlStart = (PUCHAR) &g_SlashPath[0];
}
//
// pUrlStart points to the start of the absolute path portion.
//
pRequest->RawUrl.pAbsPath = pUrlStart;
return STATUS_SUCCESS;
} // UlpParseFullUrl
/*++
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:
pRequest - Pointer to the current request
pHttpRequest - Pointer to the current raw request data.
HttpRequestLength - Bytes left in the request data.
pHeaderMap - Pointer to start of an array of header map entries
(may be NULL).
HeaderMapCount - Number of entries in array pointed to by pHeaderMap.
bIgnore - We don't want to write to the buffer.
(used for parsing trailers)
pBytesTaken - Bytes consumed by this routine from pHttpRequest,
including CRLF.
Return Value:
STATUS_SUCCESS or an error.
--*/
NTSTATUS
UlLookupHeader(
IN PUL_INTERNAL_REQUEST pRequest,
IN PUCHAR pHttpRequest,
IN ULONG HttpRequestLength,
IN PHEADER_MAP_ENTRY pHeaderMap,
IN ULONG HeaderMapCount,
IN BOOLEAN bIgnore,
OUT ULONG * pBytesTaken
)
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG CurrentOffset;
USHORT HeaderNameLength;
USHORT HeaderNameAndTrailingWSLength;
ULONG i;
ULONG BytesTaken;
USHORT HeaderValueLength;
USHORT TrailingWhiteSpaceCount;
UCHAR CurrentChar;
PUL_HTTP_UNKNOWN_HEADER pUnknownHeader;
PLIST_ENTRY pListStart;
PLIST_ENTRY pCurrentListEntry;
ULONG OldHeaderLength;
PUCHAR pHeaderValue;
BOOLEAN ExternalAllocated;
//
// Sanity check.
//
PAGED_CODE();
//
// 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.
//
TrailingWhiteSpaceCount = 0;
for (CurrentOffset = 0; CurrentOffset < HttpRequestLength; CurrentOffset++)
{
CurrentChar = *(pHttpRequest + CurrentOffset);
if (CurrentChar == ':')
{
// We've found the end of the header.
break;
}
else
{
if (CurrentOffset <= ANSI_STRING_MAX_CHAR_LEN)
{
if (IS_HTTP_TOKEN(CurrentChar))
{
if (TrailingWhiteSpaceCount == 0)
{
// We haven't come across any LWS yet.
continue;
}
// else:
// We are in the midst of skipping trailing LWS,
// and don't expect anything but ':' or more LWS.
// Fall through to error handler.
//
}
else
{
//
// Allow for trailing white-space chars
//
if (IS_HTTP_LWS(CurrentChar))
{
TrailingWhiteSpaceCount++;
continue;
}
// else:
// Invalid header; fall through to error handler
//
}
}
// Uh-oh, this isn't a valid header. What do we do now?
UlTraceError(PARSER, (
"UlLookupHeader(pRequest = %p) "
"CurrentChar = 0x%02x '%c' Offset=%lu\n"
" ERROR: invalid header char\n",
pRequest,
CurrentChar,
isprint(CurrentChar) ? CurrentChar : '.',
CurrentOffset
));
UlSetErrorCode(pRequest, UlErrorHeader, NULL);
Status = STATUS_INVALID_DEVICE_REQUEST;
goto end;
}
}
// 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.
ASSERT( ':' == *(pHttpRequest + CurrentOffset) );
CurrentOffset++; // Update to point beyond terminator.
HeaderNameAndTrailingWSLength = (USHORT) CurrentOffset;
HeaderNameLength = HeaderNameAndTrailingWSLength - TrailingWhiteSpaceCount;
}
else
{
// Didn't find the :, need more.
//
*pBytesTaken = 0;
goto end;
}
// 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->pServerHandler != NULL);
if (HeaderNameLength == pHeaderMap->HeaderLength &&
//
// Ignore the last character when doing the strnicmp - the
// last character in pHeaderMap->Header.HeaderChar is a ':'
// and the last character in pHttpRequest is either a space
// or a ':'.
//
// We want to treat header-name<b>: as header-name:, so it's
// OK to ignore the last character.
//
// Also note that we do this ONLY if the length's match.
//
_strnicmp(
(const char *)(pHttpRequest),
(const char *)(pHeaderMap->Header.HeaderChar),
(HeaderNameLength-1)
) == 0 &&
pHeaderMap->pServerHandler != NULL)
{
ASSERT(
(*(pHttpRequest + HeaderNameLength - 1) == ':')
||
(*(pHttpRequest + HeaderNameLength - 1) == ' ')
);
if (HttpRequestLength - HeaderNameLength
> ANSI_STRING_MAX_CHAR_LEN)
{
UlTraceError(PARSER, (
"UlLookupHeader(pRequest = %p) "
"Header too long: %lu\n",
pRequest,
HttpRequestLength - HeaderNameLength
));
UlSetErrorCode(pRequest, UlErrorHeader, NULL);
Status = STATUS_INVALID_DEVICE_REQUEST;
goto end;
}
// This header matches. Call the handling function for it.
Status = (*(pHeaderMap->pServerHandler))(
pRequest,
pHttpRequest + HeaderNameAndTrailingWSLength,
(USHORT) (HttpRequestLength - HeaderNameAndTrailingWSLength),
pHeaderMap->HeaderID,
&BytesTaken
);
if (NT_SUCCESS(Status) == FALSE)
goto end;
// If the handler consumed a non-zero number of bytes, it
// worked, so return that number plus whatever is consumed
// already.
//
// BUGBUG - it might be possible for a header handler to
// encounter an error, for example being unable to
// allocate memory, or a bad syntax in some header. We
// need a more sophisticated method to detect this than
// just checking bytes taken.
//
if (BytesTaken != 0)
{
*pBytesTaken = HeaderNameAndTrailingWSLength + BytesTaken;
goto end;
}
// Otherwise he didn't take anything, so return 0.
// we need more buffer
//
*pBytesTaken = 0;
goto end;
}
pHeaderMap++;
}
}
// The value's length must fit in a USHORT
if (HttpRequestLength - HeaderNameLength > ANSI_STRING_MAX_CHAR_LEN)
{
UlTraceError(PARSER, (
"UlLookupHeader(pRequest = %p) "
"Header too long: %lu\n",
pRequest,
HttpRequestLength - HeaderNameLength
));
UlSetErrorCode(pRequest, UlErrorHeader, NULL);
Status = STATUS_INVALID_DEVICE_REQUEST;
goto end;
}
// 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
//
Status = FindRequestHeaderEnd(
pRequest,
pHttpRequest + HeaderNameAndTrailingWSLength,
(USHORT) (HttpRequestLength - HeaderNameAndTrailingWSLength),
&BytesTaken
);
if (!NT_SUCCESS(Status))
goto end;
if (BytesTaken == 0)
{
*pBytesTaken = 0;
goto end;
}
ASSERT(BytesTaken - CRLF_SIZE <= ANSI_STRING_MAX_CHAR_LEN);
//
// Strip off the trailing CRLF from the header value length
//
HeaderValueLength = (USHORT) (BytesTaken - CRLF_SIZE);
pHeaderValue = pHttpRequest + HeaderNameAndTrailingWSLength;
//
// skip any preceding LWS.
//
while ( HeaderValueLength > 0 && IS_HTTP_LWS(*pHeaderValue) )
{
pHeaderValue++;
HeaderValueLength--;
}
if(!bIgnore)
{
//
// Have an unknown header. Search our list of unknown headers,
// and if we've already seen one instance of this header add this
// on. Otherwise allocate an unknown header structure and set it
// to point at this header.
//
// RFC 2616, Section 4.2 "Message Headers" says:
// "Multiple message-header fields with the same field-name MAY be
// present in a message if and only if the entire field-value for
// that header field is defined as a comma-separated list
// [i.e., #(values)]. It MUST be possible to combine the multiple
// header fields into one "field-name: field-value" pair, without
// changing the semantics of the message, by appending each subsequent
// field-value to the first, each separated by a comma."
//
// Therefore we search the list of unknown headers and add the new
// field-value to the end of the existing comma delimited list of
// field-values
//
pListStart = &pRequest->UnknownHeaderList;
for (pCurrentListEntry = pRequest->UnknownHeaderList.Flink;
pCurrentListEntry != pListStart;
pCurrentListEntry = pCurrentListEntry->Flink
)
{
pUnknownHeader = CONTAINING_RECORD(
pCurrentListEntry,
UL_HTTP_UNKNOWN_HEADER,
List
);
//
// somehow HeaderNameLength includes the ':' character,
// which is not the case of pUnknownHeader->HeaderNameLength.
//
// so we need to adjust for this here
//
if ((HeaderNameLength-1) == pUnknownHeader->HeaderNameLength &&
_strnicmp(
(const char *)(pHttpRequest),
(const char *)(pUnknownHeader->pHeaderName),
(HeaderNameLength-1)
) == 0)
{
// This header matches.
OldHeaderLength = pUnknownHeader->HeaderValue.HeaderLength;
Status = UlAppendHeaderValue(
pRequest,
&pUnknownHeader->HeaderValue,
pHeaderValue,
HeaderValueLength
);
if (NT_SUCCESS(Status) == FALSE)
goto end;
//
// Successfully appended it. Update the total request
// length for the length added. no need to add 1 for
// the terminator, just add our new char count.
//
pRequest->TotalRequestSize +=
(pUnknownHeader->HeaderValue.HeaderLength
- OldHeaderLength) * sizeof(CHAR);
//
// don't subtract for the ':' character, as that character
// was "taken"
//
*pBytesTaken = HeaderNameAndTrailingWSLength + BytesTaken;
goto end;
} // if (headermatch)
} // for (walk list)
//
// Didn't find a match. Allocate a new unknown header structure, set
// it up and add it to the list.
//
if (pRequest->NextUnknownHeaderIndex < DEFAULT_MAX_UNKNOWN_HEADERS)
{
ExternalAllocated = FALSE;
pUnknownHeader = &pRequest->UnknownHeaders[pRequest->NextUnknownHeaderIndex];
pRequest->NextUnknownHeaderIndex++;
}
else
{
ExternalAllocated = TRUE;
pUnknownHeader = UL_ALLOCATE_STRUCT(
NonPagedPool,
UL_HTTP_UNKNOWN_HEADER,
UL_HTTP_UNKNOWN_HEADER_POOL_TAG
);
//
// Assume the memory allocation will succeed so just blindly set the
// flag below to TRUE which forces us to take a slow path in
// UlpFreeHttpRequest.
//
pRequest->HeadersAppended = TRUE;
}
if (pUnknownHeader == NULL)
{
Status = STATUS_NO_MEMORY;
goto end;
}
//
// subtract the : from the header name length
//
pUnknownHeader->HeaderNameLength = HeaderNameLength - 1;
pUnknownHeader->pHeaderName = pHttpRequest;
//
// header value
//
pUnknownHeader->HeaderValue.HeaderLength = HeaderValueLength;
pUnknownHeader->HeaderValue.pHeader = pHeaderValue;
//
// null terminate our copy, the terminating CRLF gives
// us space for this
//
pHeaderValue[HeaderValueLength] = ANSI_NULL;
//
// flags
//
pUnknownHeader->HeaderValue.OurBuffer = 0;
pUnknownHeader->HeaderValue.ExternalAllocated = ExternalAllocated;
InsertTailList(&pRequest->UnknownHeaderList, &pUnknownHeader->List);
pRequest->UnknownHeaderCount++;
if(pRequest->UnknownHeaderCount == 0)
{
// Overflow!
Status = STATUS_INVALID_DEVICE_REQUEST;
goto end;
}
//
// subtract 1 for the ':' and add space for the 2 terminators
//
pRequest->TotalRequestSize +=
((HeaderNameLength - 1 + 1) + HeaderValueLength + 1) * sizeof(CHAR);
}
*pBytesTaken = HeaderNameAndTrailingWSLength + BytesTaken;
end:
return Status;
} // UlLookupHeader
/*++
Routine Description:
The routine to parse an individual header based on a hint. We take in
a pointer to the header and the bytes remaining in the request,
and try to find the header based on the hint passed.
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.
pHeaderHintMap - Hint to the Map that may match the current request
pBytesTaken - Bytes consumed by this routine from pHttpRequest,
including CRLF.
Return Value:
STATUS_SUCCESS or an error.
--*/
__inline
NTSTATUS
UlParseHeaderWithHint(
IN PUL_INTERNAL_REQUEST pRequest,
IN PUCHAR pHttpRequest,
IN ULONG HttpRequestLength,
IN PHEADER_MAP_ENTRY pHeaderHintMap,
OUT ULONG * pBytesTaken
)
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG BytesTaken;
ULONGLONG Temp;
ULONG j;
PAGED_CODE();
ASSERT(pHeaderHintMap != NULL);
if (HttpRequestLength >= pHeaderHintMap->MinBytesNeeded)
{
for (j = 0; j < pHeaderHintMap->ArrayCount; j++)
{
Temp = *(UNALIGNED64 ULONGLONG *)(pHttpRequest +
(j * sizeof(ULONGLONG)));
if ((Temp & pHeaderHintMap->HeaderMask[j]) !=
pHeaderHintMap->Header.HeaderLong[j] )
{
break;
}
}
// See why we exited out.
if (j == pHeaderHintMap->ArrayCount &&
pHeaderHintMap->pServerHandler != NULL)
{
if (HttpRequestLength - pHeaderHintMap->HeaderLength
> ANSI_STRING_MAX_CHAR_LEN)
{
UlTraceError(PARSER, (
"UlParseHeaderWithHint(pRequest = %p) "
"Header too long: %lu\n",
pRequest,
HttpRequestLength - pHeaderHintMap->HeaderLength
));
UlSetErrorCode(pRequest, UlErrorHeader, NULL);
Status = STATUS_INVALID_DEVICE_REQUEST;
goto end;
}
// Exited because we found a match. Call the
// handler for this header to take cake of this.
Status = (*(pHeaderHintMap->pServerHandler))(
pRequest,
pHttpRequest + pHeaderHintMap->HeaderLength,
(USHORT) (HttpRequestLength - pHeaderHintMap->HeaderLength),
pHeaderHintMap->HeaderID,
&BytesTaken
);
if (NT_SUCCESS(Status) == FALSE)
goto end;
// If the handler consumed a non-zero number of
// bytes, it worked, so return that number plus
// the header length.
if (BytesTaken != 0)
{
*pBytesTaken = pHeaderHintMap->HeaderLength + BytesTaken;
goto end;
}
// Otherwise need more buffer
*pBytesTaken = 0;
}
else
{
// No match
*pBytesTaken = (ULONG) -1;
}
}
else
{
// No match
*pBytesTaken = (ULONG) -1;
}
end:
return Status;
} // UlParseHeaderWithHint
/*++
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.
pBytesTaken - Bytes consumed by this routine from pHttpRequest,
including CRLF.
Return Value:
STATUS_SUCCESS or an error.
--*/
NTSTATUS
UlParseHeader(
IN PUL_INTERNAL_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;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(HttpRequestLength >= CRLF_SIZE);
c = *pHttpRequest;
// message-headers start with field-name [= token]
//
if (IS_HTTP_TOKEN(c) == FALSE)
{
UlTraceError(PARSER, (
"UlParseHeader (pRequest = %p) c = 0x%02x '%c'"
"ERROR: invalid header char \n",
pRequest,
c, isprint(c) ? c : '.'
));
UlSetErrorCode(pRequest, UlErrorHeader, NULL);
return STATUS_INVALID_DEVICE_REQUEST;
}
// Does the header start with an alpha?
//
if (!IS_HTTP_ALPHA(c))
{
pCurrentHeaderMap = NULL;
HeaderMapCount = 0;
}
else
{
// Uppercase the character, and find the appropriate set of header map
// entries.
//
c = UPCASE_CHAR(c);
ASSERT('A' <= c && c <= 'Z');
c -= 'A';
pCurrentHeaderMap = g_RequestHeaderIndexTable[c].pHeaderMap;
HeaderMapCount = g_RequestHeaderIndexTable[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->pServerHandler != 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)
{
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->pServerHandler != NULL)
{
if (HttpRequestLength - pCurrentHeaderMap->HeaderLength
> ANSI_STRING_MAX_CHAR_LEN)
{
UlTraceError(PARSER, (
"UlParseHeader(pRequest = %p) "
"Header too long: %lu\n",
pRequest,
HttpRequestLength
- pCurrentHeaderMap->HeaderLength
));
UlSetErrorCode(pRequest, UlErrorHeader, NULL);
Status = STATUS_INVALID_DEVICE_REQUEST;
goto end;
}
// Exited because we found a match. Call the
// handler for this header to take cake of this.
Status = (*(pCurrentHeaderMap->pServerHandler))(
pRequest,
pHttpRequest + pCurrentHeaderMap->HeaderLength,
(USHORT) (HttpRequestLength
- pCurrentHeaderMap->HeaderLength),
pCurrentHeaderMap->HeaderID,
&BytesTaken
);
if (NT_SUCCESS(Status) == FALSE)
goto end;
// If the handler consumed a non-zero number of
// bytes, it worked, so return that number plus
// the header length.
if (BytesTaken != 0)
{
*pBytesTaken = pCurrentHeaderMap->HeaderLength +
BytesTaken;
goto end;
}
// Otherwise need more buffer
//
*pBytesTaken = 0;
goto end;
}
// If we get here, we exited out early because a match
// failed, so keep going.
}
// 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.
pCurrentHeaderMap = g_RequestHeaderIndexTable[c].pHeaderMap;
}
// 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 = UlLookupHeader(
pRequest,
pHttpRequest,
HttpRequestLength,
pCurrentHeaderMap,
HeaderMapCount,
FALSE,
&BytesTaken
);
if (NT_SUCCESS(Status) == FALSE)
goto end;
// Lookup header returns the total bytes taken, including the header name
//
*pBytesTaken = BytesTaken;
end:
return Status;
} // UlParseHeader
/*++
Routine Description:
Parse all the remaining header data, breaking it into one or more
headers. Terminates when an empty line is seen (end of headers),
when buffer is exhausted (=> more data needed and UlParseHeaders()
will be called again), or upon error.
Arguments:
pRequest - Pointer to the current request.
pBuffer - where to start parsing
BufferLength - Length of pBuffer, in bytes
pBytesTaken - how much of pBuffer was consumed
Return Value:
STATUS_SUCCESS - Found the terminating CRLF that ends the headers
STATUS_MORE_PROCESSING_REQUIRED - haven't found the terminating CRLF,
so need more header data
various errors
--*/
NTSTATUS
UlParseHeaders(
IN OUT PUL_INTERNAL_REQUEST pRequest,
IN PUCHAR pBuffer,
IN ULONG BufferLength,
OUT PULONG pBytesTaken
)
{
NTSTATUS Status;
ULONG BytesTaken = 0;
LONG NextHintIndex = 0;
LONG HintIndex = 0;
PHEADER_MAP_ENTRY pHeaderHintMap;
UCHAR UpcaseChar;
PAGED_CODE();
*pBytesTaken = 0;
//
// loop over all headers
//
while (BufferLength >= CRLF_SIZE)
{
//
// If this is an empty header, we're done with this stage
//
if (*(UNALIGNED64 USHORT *)pBuffer == CRLF ||
*(UNALIGNED64 USHORT *)pBuffer == LFLF)
{
//
// consume it
//
pBuffer += CRLF_SIZE;
*pBytesTaken += CRLF_SIZE;
BufferLength -= CRLF_SIZE;
Status = STATUS_SUCCESS;
goto end;
}
// Otherwise call our header parse routine to deal with this.
//
// Try to find a header hint based on the first char and certain order
//
// We start from where we find a succeful hint + 1
//
pHeaderHintMap = NULL;
if (IS_HTTP_ALPHA(*pBuffer))
{
UpcaseChar = UPCASE_CHAR(*pBuffer);
for (
HintIndex = NextHintIndex;
HintIndex < NUMBER_HEADER_HINT_INDICES;
++HintIndex
)
{
if (g_RequestHeaderHintIndexTable[HintIndex].c == UpcaseChar)
{
pHeaderHintMap
= g_RequestHeaderHintIndexTable[HintIndex].pHeaderMap;
break;
}
}
}
if (NULL != pHeaderHintMap)
{
Status = UlParseHeaderWithHint(
pRequest,
pBuffer,
BufferLength,
pHeaderHintMap,
&BytesTaken
);
if (-1 == BytesTaken)
{
// hint failed
Status = UlParseHeader(
pRequest,
pBuffer,
BufferLength,
&BytesTaken
);
} else
{
NextHintIndex = HintIndex + 1;
}
}
else
{
Status = UlParseHeader(
pRequest,
pBuffer,
BufferLength,
&BytesTaken
);
}
if (NT_SUCCESS(Status) == FALSE)
goto end;
//
// Check if the parsed headers are longer than maximum allowed.
//
if ( (*pBytesTaken+BytesTaken) > g_UlMaxFieldLength )
{
UlTraceError(PARSER, (
"UlParseHeaders(pRequest = %p) "
"ERROR: Header field is too big\n",
pRequest
));
UlSetErrorCode(pRequest, UlErrorFieldLength, NULL);
Status = STATUS_SECTION_TOO_BIG;
goto end;
}
//
// If no bytes were consumed, the header must be incomplete, so
// bail out until we get more data on this connection.
//
if (BytesTaken == 0)
{
Status = STATUS_MORE_PROCESSING_REQUIRED;
goto end;
}
//
// Otherwise we parsed a header, so update and continue.
//
pBuffer += BytesTaken;
*pBytesTaken += BytesTaken;
BufferLength -= BytesTaken;
}
//
// we only get here if we didn't see the CRLF headers terminator
//
// we need more data
//
Status = STATUS_MORE_PROCESSING_REQUIRED;
end:
//
// Terminate the header index table when we are done with parsing headers.
//
pRequest->HeaderIndex[pRequest->HeaderCount] = HttpHeaderRequestMaximum;
return Status;
} // UlParseHeaders
/*++
Routine Description:
This is the core HTTP protocol request engine. It takes a stream of bytes
and parses them as an HTTP request.
Arguments:
pRequest - Pointer to the current request.
pHttpRequest - Pointer to the incoming raw HTTP request data.
HttpRequestLength - Length of data pointed to by pHttpRequest.
pBytesTaken - How much of pHttpRequest was consumed
Return Value:
Status of parse attempt.
--*/
NTSTATUS
UlParseHttp(
IN PUL_INTERNAL_REQUEST pRequest,
IN PUCHAR pHttpRequest,
IN ULONG HttpRequestLength,
OUT ULONG * pBytesTaken
)
{
ULONG OriginalBufferLength;
ULONG TokenLength;
ULONG CurrentBytesTaken = 0;
ULONG TotalBytesTaken;
ULONG i;
NTSTATUS ReturnStatus;
PUCHAR pStart;
USHORT Eol;
//
// Sanity check.
//
PAGED_CODE();
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pRequest ) );
ReturnStatus = STATUS_SUCCESS;
TotalBytesTaken = 0;
//
// remember the original buffer length
//
OriginalBufferLength = HttpRequestLength;
//
// Put this label here to allow for a manual re-pump of the
// parser. This is currently used for HTTP/0.9 requests.
//
parse_it:
//
// what state are we in ?
//
switch (pRequest->ParseState)
{
case ParseVerbState:
UlTraceVerbose(PARSER, (
"UlParseHttp(pRequest = %p): Entering ParseVerbState\n",
pRequest
));
// Look through the fast verb table for the verb. We can only do
// this if the input data is big enough.
if (HttpRequestLength >= sizeof(ULONGLONG))
{
ULONGLONG RawInputVerb;
RawInputVerb = *(UNALIGNED64 ULONGLONG *) pHttpRequest;
// Loop through the fast verb table, looking for the verb.
for (i = 0; i < NUMBER_FAST_VERB_ENTRIES; i++)
{
// Mask out the raw input verb and compare against this
// entry. Note: verbs are case-sensitive.
ASSERT(FastVerbTable[i].RawVerbLength - STRLEN_LIT(" ")
< sizeof(ULONGLONG));
if ((RawInputVerb & FastVerbTable[i].RawVerbMask) ==
FastVerbTable[i].RawVerb.LongLong)
{
// It matched. Save the translated verb from the
// table, update the request pointer and length,
// switch states, and get out.
pRequest->Verb = FastVerbTable[i].TranslatedVerb;
CurrentBytesTaken = FastVerbTable[i].RawVerbLength;
pRequest->ParseState = ParseUrlState;
break;
}
}
}
if (pRequest->ParseState != ParseUrlState)
{
// Didn't switch states yet, because we haven't found the
// verb yet. This could be because a) the incoming request
// was too small to allow us to use our fast lookup (which
// might be OK in an HTTP/0.9 request), or b) the incoming
// verb is a PROPFIND or such that is too big to fit into
// our fast find table, or c) this is an unknown verb, or
// d) there was leading white-space before the verb.
// In any of these cases call our slower verb parser to try
// again.
ReturnStatus = UlpLookupVerb(
pRequest,
pHttpRequest,
HttpRequestLength,
&CurrentBytesTaken
);
if (NT_SUCCESS(ReturnStatus) == FALSE)
goto end;
if (CurrentBytesTaken == 0)
{
ReturnStatus = STATUS_MORE_PROCESSING_REQUIRED;
goto end;
}
//
// we finished parsing the custom verb
//
pRequest->ParseState = ParseUrlState;
}
//
// now fall through to ParseUrlState
//
ASSERT(CurrentBytesTaken <= HttpRequestLength);
pHttpRequest += CurrentBytesTaken;
HttpRequestLength -= CurrentBytesTaken;
TotalBytesTaken += CurrentBytesTaken;
case ParseUrlState:
ASSERT(ParseUrlState == pRequest->ParseState);
UlTraceVerbose(PARSER, (
"UlParseHttp(pRequest = %p): Verb='%s', "
"Entering ParseUrlState\n",
pRequest,
UlVerbToString(pRequest->Verb)
));
//
// We're parsing the URL. pHttpRequest points to the incoming URL,
// HttpRequestLength is the length of this request that is left.
//
//
// Find the WS terminating the URL.
//
ReturnStatus = HttpFindUrlToken(
&g_UrlC14nConfig,
pHttpRequest,
HttpRequestLength,
&pRequest->RawUrl.pUrl,
&TokenLength,
&pRequest->RawUrlClean
);
if (NT_SUCCESS(ReturnStatus) == FALSE)
{
UlTraceError(PARSER, (
"UlParseHttp(pRequest = %p) ERROR %s: "
"could not parse URL\n",
pRequest,
HttpStatusToString(ReturnStatus)
));
UlSetErrorCode(pRequest, UlErrorUrl, NULL);
goto end;
}
if (pRequest->RawUrl.pUrl == NULL)
{
ReturnStatus = STATUS_MORE_PROCESSING_REQUIRED;
goto end;
}
ASSERT(TokenLength > 0);
ASSERT(pHttpRequest <= pRequest->RawUrl.pUrl
&& pRequest->RawUrl.pUrl < pHttpRequest + HttpRequestLength);
if (TokenLength > g_UlMaxFieldLength)
{
//
// The URL is longer than maximum allowed size
//
UlTraceError(PARSER, (
"UlParseHttp(pRequest = %p) ERROR: URL is too big\n",
pRequest
));
UlSetErrorCode(pRequest, UlErrorUrlLength, NULL);
ReturnStatus = STATUS_SECTION_TOO_BIG;
goto end;
}
//
// Bytes taken includes leading WS in front of URL
//
CurrentBytesTaken = DIFF(pRequest->RawUrl.pUrl - pHttpRequest)
+ TokenLength;
ASSERT(CurrentBytesTaken <= HttpRequestLength);
//
// set url length
//
pRequest->RawUrl.Length = TokenLength;
//
// Now, let's see if this is an absolute URL. Check to see
// if the first four characters are "http" (case-insensitive).
//
if (pRequest->RawUrl.Length >= HTTP_PREFIX_SIZE &&
(*(UNALIGNED64 ULONG *)pRequest->RawUrl.pUrl & HTTP_PREFIX_MASK)
== HTTP_PREFIX)
{
//
// It is. let's parse it and find the host.
//
ReturnStatus = UlpParseFullUrl(pRequest);
if (NT_SUCCESS(ReturnStatus) == FALSE)
goto end;
}
else
{
pRequest->RawUrl.pHost = NULL;
pRequest->RawUrl.pAbsPath = pRequest->RawUrl.pUrl;
}
//
// count the space it needs in the user's buffer, including terminator.
//
pRequest->TotalRequestSize +=
(pRequest->RawUrl.Length + 1) * sizeof(CHAR);
//
// adjust our book keeping vars
//
pHttpRequest += CurrentBytesTaken;
HttpRequestLength -= CurrentBytesTaken;
TotalBytesTaken += CurrentBytesTaken;
//
// fall through to parsing the version.
//
pRequest->ParseState = ParseVersionState;
case ParseVersionState:
ASSERT(ParseVersionState == pRequest->ParseState);
UlTraceVerbose(PARSER, (
"UlParseHttp(pRequest = %p): Entering ParseVersionState\n",
pRequest
));
//
// skip lws
//
pStart = pHttpRequest;
while (HttpRequestLength > 0 && IS_HTTP_LWS(*pHttpRequest))
{
pHttpRequest++;
HttpRequestLength--;
}
//
// is this an HTTP/0.9 request (no version) ?
//
if (HttpRequestLength >= CRLF_SIZE)
{
Eol = *(UNALIGNED64 USHORT *)(pHttpRequest);
if (Eol == CRLF || Eol == LFLF)
{
// This IS a 0.9 request. No need to go any further,
// since by definition there are no more headers.
// Just update things and get out.
TotalBytesTaken += DIFF(pHttpRequest - pStart) + CRLF_SIZE;
HTTP_SET_VERSION(pRequest->Version, 0, 9);
UlTraceVerbose(PARSER, (
"UlParseHttp(pRequest = %p): HTTP/0.9 request\n",
pRequest
));
//
// set the state to CookState so that we parse the url
//
pRequest->ParseState = ParseCookState;
//
// manually restart the parse switch, we changed the
// parse state
//
goto parse_it;
}
}
//
// do we have enough buffer to strcmp the version?
//
if (HttpRequestLength < (MIN_VERSION_SIZE + CRLF_SIZE))
{
ReturnStatus = STATUS_MORE_PROCESSING_REQUIRED;
goto end;
}
Eol = *(UNALIGNED64 USHORT *)(pHttpRequest + MIN_VERSION_SIZE);
//
// let's compare it
//
if ((*(UNALIGNED64 ULONGLONG *)pHttpRequest == HTTP_11_VERSION) &&
(Eol == CRLF || Eol == LFLF))
{
HTTP_SET_VERSION(pRequest->Version, 1, 1);
HttpRequestLength -= MIN_VERSION_SIZE;
pHttpRequest += MIN_VERSION_SIZE;
}
else if ((*(UNALIGNED64 ULONGLONG *)pHttpRequest == HTTP_10_VERSION) &&
(Eol == CRLF || Eol == LFLF))
{
HTTP_SET_VERSION(pRequest->Version, 1, 0);
HttpRequestLength -= MIN_VERSION_SIZE;
pHttpRequest += MIN_VERSION_SIZE;
}
else
{
ULONG VersionBytes = UlpParseHttpVersion(
pHttpRequest,
HttpRequestLength,
&pRequest->Version );
if (0 != VersionBytes)
{
pHttpRequest += VersionBytes;
HttpRequestLength -= VersionBytes;
}
else
{
// Could not parse version.
UlTraceError(PARSER, (
"UlParseHttp(pRequest = %p) "
"ERROR: could not parse HTTP version\n",
pRequest
));
UlSetErrorCode(pRequest, UlError, NULL);
ReturnStatus = STATUS_INVALID_DEVICE_REQUEST;
goto end;
}
}
//
// skip trailing lws
//
while (HttpRequestLength > 0 && IS_HTTP_LWS(*pHttpRequest))
{
pHttpRequest++;
HttpRequestLength--;
}
//
// Make sure we're terminated on this line.
//
if (HttpRequestLength < CRLF_SIZE)
{
ReturnStatus = STATUS_MORE_PROCESSING_REQUIRED;
goto end;
}
Eol = *(UNALIGNED64 USHORT *)(pHttpRequest);
if (Eol != CRLF && Eol != LFLF)
{
// Bad line termination after successfully grabbing version.
UlTraceError(PARSER, (
"UlParseHttp(pRequest = %p) "
"ERROR: HTTP version not terminated correctly\n",
pRequest
));
UlSetErrorCode(pRequest, UlError, NULL);
ReturnStatus = STATUS_INVALID_DEVICE_REQUEST;
goto end;
}
pHttpRequest += CRLF_SIZE;
HttpRequestLength -= CRLF_SIZE;
TotalBytesTaken += DIFF(pHttpRequest - pStart);
UlTraceVerbose(PARSER, (
"UlParseHttp(pRequest = %p): HTTP/%hu.%hu request\n",
pRequest,
pRequest->Version.MajorVersion,
pRequest->Version.MinorVersion
));
//
// Fall through to parsing the headers
//
pRequest->ParseState = ParseHeadersState;
case ParseHeadersState:
ASSERT(ParseHeadersState == pRequest->ParseState);
UlTraceVerbose(PARSER, (
"UlParseHttp(pRequest = %p): Entering ParseHeadersState\n",
pRequest
));
ReturnStatus = UlParseHeaders(
pRequest,
pHttpRequest,
HttpRequestLength,
&CurrentBytesTaken
);
pHttpRequest += CurrentBytesTaken;
HttpRequestLength -= CurrentBytesTaken;
TotalBytesTaken += CurrentBytesTaken;
if (NT_SUCCESS(ReturnStatus) == FALSE)
goto end;
//
// fall through, this is the only way to get here, we never return
// pending in this state
//
pRequest->ParseState = ParseCookState;
case ParseCookState:
ASSERT(ParseCookState == pRequest->ParseState);
UlTraceVerbose(PARSER, (
"UlParseHttp(pRequest = %p): Entering ParseCookState\n",
pRequest
));
//
// time for post processing. cook it up!
//
{
//
// First cook up the url, unicode it + such.
//
ReturnStatus = UlpCookUrl(pRequest);
if (NT_SUCCESS(ReturnStatus) == FALSE)
goto end;
//
// mark if we are chunk encoded (only possible for 1.1)
//
if ((HTTP_GREATER_EQUAL_VERSION(pRequest->Version, 1, 1)) &&
(pRequest->HeaderValid[HttpHeaderTransferEncoding]))
{
ASSERT(pRequest->Headers[HttpHeaderTransferEncoding].pHeader != NULL);
//
// CODEWORK, there can be more than 1 encoding
//
if (_stricmp(
(const char *)(
pRequest->Headers[HttpHeaderTransferEncoding].pHeader
),
"chunked"
) == 0)
{
pRequest->Chunked = 1;
}
else
{
UlTraceError(PARSER, (
"UlParseHttp(pRequest = %p) "
"ERROR: unknown Transfer-Encoding!\n",
pRequest
));
UlSetErrorCode(pRequest, UlErrorNotImplemented, NULL);
ReturnStatus = STATUS_INVALID_DEVICE_REQUEST;
goto end;
}
}
//
// Now let's decode the content length header
//
if (pRequest->HeaderValid[HttpHeaderContentLength])
{
ASSERT(pRequest->Headers[HttpHeaderContentLength].pHeader != NULL);
ReturnStatus =
UlAnsiToULongLong(
pRequest->Headers[HttpHeaderContentLength].pHeader,
pRequest->Headers[HttpHeaderContentLength].HeaderLength,
10,
&pRequest->ContentLength
);
if (NT_SUCCESS(ReturnStatus) == FALSE)
{
UlTraceError(PARSER, (
"UlParseHttp(pRequest = %p) "
"ERROR: couldn't decode Content-Length\n",
pRequest
));
if (ReturnStatus == STATUS_SECTION_TOO_BIG)
{
UlSetErrorCode(pRequest, UlErrorEntityTooLarge, NULL);
}
else
{
UlSetErrorCode(pRequest, UlErrorNum, NULL);
}
goto end;
}
if (pRequest->Chunked == 0)
{
//
// prime the first (and only) chunk size
//
pRequest->ChunkBytesToParse = pRequest->ContentLength;
pRequest->ChunkBytesToRead = pRequest->ContentLength;
}
}
}
pRequest->ParseState = ParseEntityBodyState;
//
// fall through
//
case ParseEntityBodyState:
UlTraceVerbose(PARSER, (
"UlParseHttp(pRequest = %p): Entering ParseEntityBodyState\n",
pRequest
));
ASSERT(ParseEntityBodyState == pRequest->ParseState);
//
// the only parsing we do here is chunk length calculation,
// and that is not necessary if we have no more bytes to parse
//
if (pRequest->ChunkBytesToParse == 0)
{
//
// no more bytes left to parse, let's see if there are any
// more in the request
//
if (pRequest->Chunked == 1)
{
//
// the request is chunk encoded
//
//
// attempt to read the size of the next chunk
//
ReturnStatus = ParseChunkLength(
pRequest->ParsedFirstChunk,
pHttpRequest,
HttpRequestLength,
&CurrentBytesTaken,
&(pRequest->ChunkBytesToParse)
);
UlTraceVerbose(PARSER, (
"http!UlParseHttp(pRequest = %p): %s. "
"Chunk length: %lu bytes taken, "
"0x%I64x (%I64u) bytes to parse.\n",
pRequest,
HttpStatusToString(ReturnStatus),
CurrentBytesTaken,
pRequest->ChunkBytesToParse,
pRequest->ChunkBytesToParse
));
if (NT_SUCCESS(ReturnStatus) == FALSE)
{
if (ReturnStatus == STATUS_MORE_PROCESSING_REQUIRED)
{
goto end;
}
UlTraceError(PARSER, (
"http!UlParseHttp (pRequest = %p) "
"ERROR: didn't grok chunk length\n",
pRequest
));
if (ReturnStatus == STATUS_SECTION_TOO_BIG)
{
UlSetErrorCode(pRequest, UlErrorEntityTooLarge, NULL);
}
else
{
UlSetErrorCode(pRequest, UlErrorNum, NULL);
}
goto end;
}
//
// Otherwise we parsed it, so update and continue.
//
pHttpRequest += CurrentBytesTaken;
TotalBytesTaken += CurrentBytesTaken;
HttpRequestLength -= CurrentBytesTaken;
//
// was this the first chunk?
//
if (pRequest->ParsedFirstChunk == 0)
{
//
// Prime the reader, let it read the first chunk
// even though we haven't quite parsed it yet....
//
UlTraceVerbose(PARSER, (
"UlParseHttp (pRequest=%p) first-chunk seen\n",
pRequest
));
pRequest->ChunkBytesToRead = pRequest->ChunkBytesToParse;
pRequest->ParsedFirstChunk = 1;
}
//
// is this the last chunk (denoted with a 0 byte chunk)?
//
if (pRequest->ChunkBytesToParse == 0)
{
//
// time to parse the trailer
//
UlTraceVerbose(PARSER, (
"UlParseHttp (pRequest=%p) last-chunk seen\n",
pRequest
));
pRequest->ParseState = ParseTrailerState;
}
}
else // if (pRequest->Chunked == 1)
{
//
// not chunk-encoded , all done
//
UlTraceVerbose(PARSER, (
"UlParseHttp (pRequest=%p) State: EntityBody->Done\n",
pRequest
));
pRequest->ParseState = ParseDoneState;
}
} // if (pRequest->ChunkBytesToParse == 0)
else
{
UlTraceVerbose(PARSER, (
"UlParseHttp (pRequest=%p) State: EntityBody, "
"ChunkBytesToParse=0x%I64x (%I64u).\n",
pRequest,
pRequest->ChunkBytesToParse, pRequest->ChunkBytesToParse
));
}
//
// looks all good
//
if (pRequest->ParseState != ParseTrailerState)
{
break;
}
//
// fall through
//
case ParseTrailerState:
ASSERT(ParseTrailerState == pRequest->ParseState);
UlTraceVerbose(PARSER, (
"UlParseHttp(pRequest = %p): Entering ParseTrailerState\n",
pRequest
));
//
// parse any existing trailer
//
// ParseHeaders will bail immediately if CRLF is
// next in the buffer (no trailer)
//
while(HttpRequestLength >= CRLF_SIZE)
{
if (*(UNALIGNED64 USHORT *)pHttpRequest == CRLF ||
*(UNALIGNED64 USHORT *)pHttpRequest == LFLF)
{
pHttpRequest += CRLF_SIZE;
HttpRequestLength -= CRLF_SIZE;
TotalBytesTaken += CRLF_SIZE;
//
// All done.
//
UlTrace(PARSER, (
"UlParseHttp (pRequest=%p) State: Trailer->Done\n",
pRequest
));
pRequest->ParseState = ParseDoneState;
ReturnStatus = STATUS_SUCCESS;
goto end;
}
else
{
// Treat this as an unknown header, but don't write it.
ReturnStatus = UlLookupHeader(
pRequest,
pHttpRequest,
HttpRequestLength,
NULL,
0,
TRUE,
&CurrentBytesTaken
);
if (NT_SUCCESS(ReturnStatus) == FALSE)
goto end;
//
// If no bytes were consumed, the header must be incomplete, so
// bail out until we get more data on this connection.
//
if (CurrentBytesTaken == 0)
{
ReturnStatus = STATUS_MORE_PROCESSING_REQUIRED;
goto end;
}
pHttpRequest += CurrentBytesTaken;
HttpRequestLength -= CurrentBytesTaken;
TotalBytesTaken += CurrentBytesTaken;
}
}
ReturnStatus = STATUS_MORE_PROCESSING_REQUIRED;
break;
default:
//
// this should never happen!
//
ASSERT(! "Unhandled ParseState");
break;
} // switch (pRequest->ParseState)
end:
*pBytesTaken = TotalBytesTaken;
if (ReturnStatus == STATUS_MORE_PROCESSING_REQUIRED &&
TotalBytesTaken == OriginalBufferLength)
{
//
// convert this to success, we consumed the entire buffer
//
ReturnStatus = STATUS_SUCCESS;
}
UlTrace(PARSER, (
"UlParseHttp returning %s, "
"(%p)->ParseState = %d (%s), TotalBytesTaken = %lu\n",
HttpStatusToString(ReturnStatus),
pRequest,
pRequest->ParseState,
UlParseStateToString(pRequest->ParseState),
TotalBytesTaken
));
return ReturnStatus;
} // UlParseHttp
/*++
Routine Description:
Prints a TCP port number into a string buffer
Arguments:
pString - Output buffer
Port - Port number, in host order
Return Value:
Number of WCHARs
--*/
ULONG
UlpFormatPort(
OUT PWSTR pString,
IN ULONG Port
)
{
PWSTR p1;
PWSTR p2;
WCHAR ch;
ULONG digit;
ULONG length;
//
// Sanity check.
//
PAGED_CODE();
//
// Fast-path common ports. While we're at it, special case port 0,
// which is definitely not common, but handling it specially makes
// the general conversion code a bit simpler.
//
switch (Port)
{
case 0:
*pString++ = L'0';
*pString = UNICODE_NULL;
return 1;
case 80:
*pString++ = L'8';
*pString++ = L'0';
*pString = UNICODE_NULL;
return 2;
case 443:
*pString++ = L'4';
*pString++ = L'4';
*pString++ = L'3';
*pString = UNICODE_NULL;
return 3;
default:
break;
}
//
// Pull the least significant digits off the port value and store them
// into the pString. Note that this will store the digits in reverse
// order.
//
p1 = p2 = pString;
while (Port != 0)
{
digit = Port % 10;
Port = Port / 10;
*p1++ = L'0' + (WCHAR)digit;
}
length = DIFF(p1 - pString);
//
// Reverse the digits in the pString.
//
*p1-- = UNICODE_NULL;
while (p1 > p2)
{
ch = *p1;
*p1 = *p2;
*p2 = ch;
p2++;
p1--;
}
return length;
} // UlpFormatPort
/*
An origin server that does differentiate resources based on the host
requested (sometimes referred to as virtual hosts or vanity host
names) MUST use the following rules for determining the requested
resource on an HTTP/1.1 request:
1. If Request-URI is an absoluteURI, the host is part of the
Request-URI. Any Host header field value in the request MUST be
ignored.
2. If the Request-URI is not an absoluteURI, and the request includes
a Host header field, the host is determined by the Host header
field value.
3. If the host as determined by rule 1 or 2 is not a valid host on
the server, the response MUST be a 400 (Bad Request) error message.
Recipients of an HTTP/1.0 request that lacks a Host header field MAY
attempt to use heuristics (e.g., examination of the URI path for
something unique to a particular host) in order to determine what
exact resource is being requested.
*/
NTSTATUS
UlpCookUrl(
PUL_INTERNAL_REQUEST pRequest
)
{
NTSTATUS Status;
PUCHAR pHost;
ULONG HostLength;
PUCHAR pAbsPath;
ULONG AbsPathLength;
ULONG UrlLength;
ULONG LengthCopied;
PWSTR pUrl = NULL;
PWSTR pCurrent;
UCHAR IpAddressString[MAX_IP_ADDR_AND_PORT_STRING_LEN + 1];
USHORT IpPortNum;
HOSTNAME_TYPE HostnameType;
URL_ENCODING_TYPE HostnameEncodingType;
SHORT HostnameAddressType;
SHORT TransportAddressType
= pRequest->pHttpConn->pConnection->AddressType;
ULONG Index;
ULONG PortLength;
//
// Sanity check.
//
PAGED_CODE();
//
// We must have already parsed the entire headers + such
//
if (pRequest->ParseState != ParseCookState)
return STATUS_INVALID_DEVICE_STATE;
UlTraceVerbose(PARSER, ("http!UlpCookUrl(pRequest = %p)\n", pRequest));
//
// Better have an absolute url. We require a literal '/' at the beginning
// of pAbsPath; we will not accept %2F or UTF-8 encoding. See bug 467445.
//
if (pRequest->RawUrl.pAbsPath[0] != '/')
{
UCHAR FirstChar = pRequest->RawUrl.pAbsPath[0];
UCHAR SecondChar = pRequest->RawUrl.pAbsPath[1];
//
// allow * for Verb = OPTIONS
//
if (FirstChar == '*' &&
IS_HTTP_LWS(SecondChar) &&
pRequest->Verb == HttpVerbOPTIONS)
{
// ok
}
else
{
UlTraceError(PARSER, (
"http!UlpCookUrl(pRequest = %p): "
"Invalid lead chars for URL, verb='%s', "
"'%c' 0x%02x\n '%c' 0x%02x\n",
pRequest,
UlVerbToString(pRequest->Verb),
IS_HTTP_PRINT(FirstChar) ? FirstChar : '?',
FirstChar,
IS_HTTP_PRINT(SecondChar) ? SecondChar : '?',
SecondChar
));
UlSetErrorCode(pRequest, UlErrorUrl, NULL);
Status = STATUS_INVALID_DEVICE_REQUEST;
goto end;
}
}
//
// get the IP port from the transport
//
if (TransportAddressType == TDI_ADDRESS_TYPE_IP)
{
IpPortNum =
pRequest->pHttpConn->pConnection->LocalAddrIn.sin_port;
}
else if (TransportAddressType == TDI_ADDRESS_TYPE_IP6)
{
IpPortNum =
pRequest->pHttpConn->pConnection->LocalAddrIn6.sin6_port;
}
else
{
ASSERT(! "Unexpected TdiAddressType");
IpPortNum = 0;
}
// Convert port from network order to host order
IpPortNum = SWAP_SHORT(IpPortNum);
//
// collect the host + abspath sections
//
if (pRequest->RawUrl.pHost != NULL)
{
// We have an absUri in the Request-line
PUCHAR pAbsUri;
pAbsUri = pRequest->RawUrl.pUrl;
pHost = pRequest->RawUrl.pHost;
pAbsPath = pRequest->RawUrl.pAbsPath;
HostnameType = Hostname_AbsUri;
ASSERT(pRequest->RawUrl.Length >= HTTP_PREFIX_SIZE &&
(*(UNALIGNED64 ULONG *) pAbsUri & HTTP_PREFIX_MASK)
== HTTP_PREFIX);
ASSERT('/' == *pAbsPath);
//
// Even though we have a hostname in the Request-line, we still
// MUST have a Host header for HTTP/1.1 requests. The Host header
// was syntax checked, if present, but will otherwise be ignored.
//
if (!pRequest->HeaderValid[HttpHeaderHost]
&& HTTP_GREATER_EQUAL_VERSION(pRequest->Version, 1, 1))
{
UlTraceError(PARSER, (
"http!UlpCookUrl(pRequest = %p) "
"ERROR: 1.1 (or greater) request w/o host header\n",
pRequest
));
UlSetErrorCode(pRequest, UlErrorHost, NULL);
Status = STATUS_INVALID_DEVICE_REQUEST;
goto end;
}
// Hack for the special case of an AbsUri without a '/' for the abspath
if (&g_SlashPath[0] == pAbsPath)
{
// We just have the scheme and the hostname, no real AbsPath
HostLength = pRequest->RawUrl.Length - DIFF(pHost - pAbsUri);
AbsPathLength = STRLEN_LIT("/");
}
else
{
HostLength = DIFF(pAbsPath - pHost);
AbsPathLength = pRequest->RawUrl.Length - DIFF(pAbsPath - pAbsUri);
}
ASSERT(HostLength > 0);
ASSERT(AbsPathLength > 0);
}
else
{
// We do not have a hostname in the Request-line
pHost = NULL;
HostLength = 0;
pAbsPath = pRequest->RawUrl.pAbsPath;
AbsPathLength = pRequest->RawUrl.Length;
//
// do we have a Host header?
//
if (pRequest->HeaderValid[HttpHeaderHost] &&
(pRequest->Headers[HttpHeaderHost].HeaderLength > 0) )
{
ASSERT(pRequest->Headers[HttpHeaderHost].pHeader != NULL);
pHost = pRequest->Headers[HttpHeaderHost].pHeader;
HostLength = pRequest->Headers[HttpHeaderHost].HeaderLength;
HostnameType = Hostname_HostHeader;
}
else
{
//
// If this was a 1.1 client, it's an invalid request
// if it does not have a Host header, so fail it.
// RFC 2616, 14.23 "Host" says the Host header can be empty,
// but it MUST be present.
//
if (!pRequest->HeaderValid[HttpHeaderHost]
&& HTTP_GREATER_EQUAL_VERSION(pRequest->Version, 1, 1))
{
UlTraceError(PARSER, (
"http!UlpCookUrl(pRequest = %p) "
"ERROR: 1.1 (or greater) request w/o host header\n",
pRequest
));
UlSetErrorCode(pRequest, UlErrorHost, NULL);
Status = STATUS_INVALID_DEVICE_REQUEST;
goto end;
}
//
// format the transport address into a string
//
pHost = IpAddressString;
// CODEWORK: we probably ought to be writing the scope ID
// for IPv6 literals
HostLength = HostAddressAndPortToString(
pHost,
pRequest->pHttpConn->pConnection->LocalAddress,
TransportAddressType);
ASSERT(HostLength < sizeof(IpAddressString));
HostnameType = Hostname_Transport;
}
}
//
// is there a port # already there ?
//
for (Index = HostLength; Index-- > 0; )
{
if (pHost[Index] == ':')
{
//
// Remove the port length from HostLength since we always
// generate the port number ourselves from the transport's port
//
HostLength = Index;
break;
}
else if (pHost[Index] == ']')
{
// ']' => end of a literal IPv6 address. Not going to find
// a valid port before this, so abort the rest of the loop
break;
}
}
//
// Validate that the hostname is syntactically correct
//
Status = HttpValidateHostname(
&g_UrlC14nConfig,
pHost,
HostLength,
HostnameType,
&HostnameAddressType
);
if (!NT_SUCCESS(Status))
{
UlSetErrorCode(pRequest, UlErrorHost, NULL);
goto end;
}
//
// If the hostname is a literal IPv4 or IPv6 address,
// it must match the transport that the request was received on.
//
if (0 != HostnameAddressType)
{
BOOLEAN Valid = (BOOLEAN) (TransportAddressType == HostnameAddressType);
// CODEWORK: should we check that the transport IP address
// matches the hostname address?
if (!Valid)
{
UlTraceError(PARSER, (
"http!UlpCookUrl(pRequest = %p): "
"Host is IPv%c, transport is IPv%c\n",
pRequest,
(TDI_ADDRESS_TYPE_IP == HostnameAddressType)
? '4'
: (TDI_ADDRESS_TYPE_IP6 == HostnameAddressType)
? '6' : '?',
(TDI_ADDRESS_TYPE_IP == TransportAddressType)
? '4'
: (TDI_ADDRESS_TYPE_IP6 == TransportAddressType)
? '6' : '?'
));
UlSetErrorCode(pRequest, UlErrorHost, NULL);
Status = STATUS_INVALID_DEVICE_REQUEST;
goto end;
}
}
//
// Pessimistically calculate the largest buffer needed to hold the
// cooked URL. If DBCS, %-encoded, or UTF-8 characters are present
// in the raw URL, the cooked URL won't need this many WCHARs.
//
UrlLength = ((HTTP_PREFIX_SIZE+HTTP_PREFIX2_SIZE)
+ HostLength
+ STRLEN_LIT(":")
+ MAX_PORT_LENGTH
+ AbsPathLength
+ 1) // terminating NUL
* sizeof(WCHAR);
//
// Too big? Too bad!
//
if (UrlLength > UNICODE_STRING_MAX_BYTE_LEN)
{
UlTraceError(PARSER, (
"http!UlpCookUrl(pRequest = %p): "
"Url is too long, %lu\n",
pRequest, UrlLength
));
Status = STATUS_DATA_OVERRUN;
goto end;
}
//
// allocate a new buffer to hold this guy
//
URL_LENGTH_STATS_UPDATE(UrlLength);
if (UrlLength > g_UlMaxInternalUrlLength)
{
pUrl = UL_ALLOCATE_ARRAY(
NonPagedPool,
WCHAR,
UrlLength / sizeof(WCHAR),
URL_POOL_TAG
);
URL_LENGTH_STATS_REALLOC();
}
else
{
pUrl = pRequest->pUrlBuffer;
}
if (pUrl == NULL)
{
Status = STATUS_NO_MEMORY;
goto end;
}
pRequest->CookedUrl.pUrl = pCurrent = pUrl;
HTTP_FILL_BUFFER(pCurrent, UrlLength);
//
// compute the scheme
//
if (pRequest->Secure)
{
RtlCopyMemory(pCurrent, L"https://", sizeof(L"https://"));
pCurrent += WCSLEN_LIT(L"https://");
pRequest->CookedUrl.Length = WCSLEN_LIT(L"https://") * sizeof(WCHAR);
}
else
{
RtlCopyMemory(pCurrent, L"http://", sizeof(L"http://"));
pCurrent += WCSLEN_LIT(L"http://");
pRequest->CookedUrl.Length = WCSLEN_LIT(L"http://") * sizeof(WCHAR);
}
//
// assemble the rest of the url
//
Status = HttpCopyHost(
&g_UrlC14nConfig,
pCurrent,
pHost,
HostLength,
&LengthCopied,
&HostnameEncodingType
);
if (NT_SUCCESS(Status) == FALSE)
{
UlSetErrorCode(pRequest, UlErrorHost, NULL);
goto end;
}
if (pRequest->CookedUrl.Length + LengthCopied > UNICODE_STRING_MAX_BYTE_LEN)
{
Status = STATUS_DATA_OVERRUN;
goto end;
}
pRequest->CookedUrl.pHost = pCurrent;
pRequest->CookedUrl.Length += LengthCopied;
pCurrent += LengthCopied / sizeof(WCHAR);
//
// port
//
*pCurrent = L':';
ASSERT(0 != IpPortNum);
PortLength = UlpFormatPort( pCurrent+1, IpPortNum ) + 1;
ASSERT(PortLength <= (MAX_PORT_LENGTH+1));
pCurrent += PortLength;
// UlpFormatPort returns WCHAR not byte count
pRequest->CookedUrl.Length += PortLength * sizeof(WCHAR);
if (pRequest->CookedUrl.Length > UNICODE_STRING_MAX_BYTE_LEN)
{
Status = STATUS_DATA_OVERRUN;
goto end;
}
//
// abs_path
//
if (pRequest->RawUrlClean)
{
Status = HttpCopyUrl(
&g_UrlC14nConfig,
pCurrent,
pAbsPath,
AbsPathLength,
&LengthCopied,
&pRequest->CookedUrl.UrlEncoding
);
}
else
{
Status = HttpCleanAndCopyUrl(
&g_UrlC14nConfig,
UrlPart_AbsPath,
pCurrent,
pAbsPath,
AbsPathLength,
&LengthCopied,
&pRequest->CookedUrl.pQueryString,
&pRequest->CookedUrl.UrlEncoding
);
}
if (NT_SUCCESS(Status) == FALSE)
{
if (STATUS_OBJECT_PATH_INVALID == Status)
{
UlTraceError(PARSER, (
"http!UlpCookUrl(pRequest = %p) Invalid URL\n",
pRequest
));
UlSetErrorCode(pRequest, UlErrorForbiddenUrl, NULL);
}
goto end;
}
if (pRequest->CookedUrl.Length + LengthCopied > UNICODE_STRING_MAX_BYTE_LEN)
{
Status = STATUS_DATA_OVERRUN;
goto end;
}
pRequest->CookedUrl.pAbsPath = pCurrent;
pRequest->CookedUrl.Length += LengthCopied;
ASSERT(pRequest->CookedUrl.Length <= UrlLength);
//
// Update pRequest, include space for the terminator
//
pRequest->TotalRequestSize += pRequest->CookedUrl.Length + sizeof(WCHAR);
//
// Let's create the hash for the whole CookedUrl
//
pRequest->CookedUrl.Hash = HashStringNoCaseW(pRequest->CookedUrl.pUrl, 0);
//
// Scramble it
//
pRequest->CookedUrl.Hash = HashRandomizeBits(pRequest->CookedUrl.Hash);
ASSERT(pRequest->CookedUrl.pHost != NULL);
ASSERT(pRequest->CookedUrl.pAbsPath != NULL);
//
// Setup the routing token Pointers to point to the default
// token buffer (allocated inline with request).
//
ASSERT(pRequest->CookedUrl.pRoutingToken == NULL);
ASSERT(pRequest->CookedUrl.RoutingTokenBufferSize == 0);
ASSERT(pRequest->pDefaultRoutingTokenBuffer);
pRequest->CookedUrl.pRoutingToken = pRequest->pDefaultRoutingTokenBuffer;
pRequest->CookedUrl.RoutingTokenBufferSize = DEFAULT_MAX_ROUTING_TOKEN_LENGTH;
pRequest->CookedUrl.RoutingTokenType = RoutingTokenNotExists;
Status = STATUS_SUCCESS;
end:
if (! NT_SUCCESS(Status))
{
if (pUrl != NULL)
{
if (pUrl != pRequest->pUrlBuffer)
{
UL_FREE_POOL(pUrl, URL_POOL_TAG);
}
RtlZeroMemory(&pRequest->CookedUrl, sizeof(pRequest->CookedUrl));
}
//
// has a specific error code been set?
//
UlTraceError(PARSER, (
"http!UlpCookUrl(pRequest = %p) "
"ERROR: unhappy. %s\n",
pRequest,
HttpStatusToString(Status)
));
if (pRequest->ErrorCode == UlErrorNone)
{
UlSetErrorCode(pRequest, UlErrorUrl, NULL);
}
}
return Status;
} // UlpCookUrl
/*++
Routine Description:
A utility routine to generate the routing token. As well as
the corresponding token hash. The hash is used if we decide
to cache this url.
This function is used for the IP Bound site lookup in the
cgroup tree.
When token is generated TokenLength is in bytes, and does
not include the terminating NULL.
Arguments:
pRequest - The request's cooked url holds the routing token
IpBased - If True, rather than copying the Host from
cooked url, the ip address used in place.
This is for Ip-only site lookup.
Returns:
STATUS_SUCCESS - If the requested token is already there.
- If the token is successfully generated.
STATUS_NO_MEMORY- If the memory allocation failed for a large
possible token size.
--*/
NTSTATUS
UlGenerateRoutingToken(
IN OUT PUL_INTERNAL_REQUEST pRequest,
IN BOOLEAN IpBased
)
{
USHORT TokenLength = 0;
PUL_HTTP_CONNECTION pHttpConn = NULL;
PWCHAR pToken = NULL;
PAGED_CODE();
//
// The Routing Token ends with a terminating null.
// And once it is generated, it will look like as;
//
// "http(s)://host:port:Ip" or "http(s)://Ip:port:Ip"
// --------- --- ------- ---
// X Y X Y
//
// When IpBased is set to FALSE, X comes from cookedUrl
// which's the host sent by the client. (case1)
// When IpBased is set to TRUE, X comes from the Ip address
// of the connection on which the request is received. (case2)
//
// Y always comes from the connection. For Host+Ip bound sites
// cgroup needs the token in case1. For IP only bound sites
// cgroup needs the token in case2.
//
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
pHttpConn = pRequest->pHttpConn;
ASSERT(IS_VALID_CONNECTION(pHttpConn->pConnection));
ASSERT(pRequest->pDefaultRoutingTokenBuffer);
ASSERT(pRequest->CookedUrl.pRoutingToken);
ASSERT(pRequest->CookedUrl.RoutingTokenBufferSize);
ASSERT(IS_VALID_ROUTING_TOKEN(pRequest->CookedUrl.RoutingTokenType));
//
// Short cut if the requested token is already there.
//
if (IpBased &&
pRequest->CookedUrl.RoutingTokenType == RoutingTokenIP)
{
return STATUS_SUCCESS;
}
else if (IpBased == FALSE &&
pRequest->CookedUrl.RoutingTokenType == RoutingTokenHostPlusIP)
{
return STATUS_SUCCESS;
}
//
// We should not be trying to generate a new token on the same
// buffer, if the previous token is already matched in the cgroup.
// or in the uri cache.
//
ASSERT(pRequest->ConfigInfo.SiteUrlType == HttpUrlSite_None);
if (IpBased)
{
PWSTR pUrl = pRequest->CookedUrl.pUrl;
PWSTR pTemp;
ASSERT(pUrl);
pToken = (PWCHAR) pRequest->CookedUrl.pRoutingToken;
//
// Default buffer should always be big enough to hold the max
// possible IP Based routing token.
//
ASSERT(MAX_IP_BASED_ROUTING_TOKEN_LENGTH
<= pRequest->CookedUrl.RoutingTokenBufferSize);
//
// Build the HTTP prefix first.
//
if (pUrl[HTTP_PREFIX_COLON_INDEX] == L':')
{
RtlCopyMemory(
pToken,
HTTP_IP_PREFIX,
HTTP_IP_PREFIX_LENGTH
);
TokenLength = HTTP_IP_PREFIX_LENGTH;
}
else
{
ASSERT(pUrl[HTTPS_PREFIX_COLON_INDEX] == L':');
RtlCopyMemory(
pToken,
HTTPS_IP_PREFIX,
HTTPS_IP_PREFIX_LENGTH
);
TokenLength = HTTPS_IP_PREFIX_LENGTH;
}
pTemp = pToken + (TokenLength / sizeof(WCHAR));
//
// Now Add the "Ip : Port : Ip"
//
ASSERT(IS_VALID_CONNECTION(pHttpConn->pConnection));
TokenLength = TokenLength +
HostAddressAndPortToRoutingTokenW(
pTemp,
pHttpConn->pConnection->LocalAddress,
pHttpConn->pConnection->AddressType
);
ASSERT(TokenLength <= MAX_IP_BASED_ROUTING_TOKEN_LENGTH);
pRequest->CookedUrl.RoutingTokenType = RoutingTokenIP;
}
else // Host + Ip based token (IpBased == FALSE)
{
USHORT MaxRoutingTokenSize;
USHORT CookedHostLength =
DIFF_USHORT(pRequest->CookedUrl.pAbsPath - pRequest->CookedUrl.pUrl);
ASSERT(CookedHostLength);
//
// Check if the default buffer is big enough to hold the token.
//
MaxRoutingTokenSize = (
CookedHostLength + // For http(s)://host:port
1 + // For ':' after port
1 + // For terminating Null
MAX_IP_ADDR_PLUS_BRACKETS_STRING_LEN // For IP Address at the end
) * sizeof(WCHAR)
;
if (MaxRoutingTokenSize > pRequest->CookedUrl.RoutingTokenBufferSize)
{
PWSTR pRoutingToken = UL_ALLOCATE_ARRAY(
NonPagedPool,
WCHAR,
MaxRoutingTokenSize / sizeof(WCHAR),
URL_POOL_TAG
);
if (pRoutingToken == NULL)
{
return STATUS_NO_MEMORY;
}
//
// There shouldn't be a previous large buffer, but let's check
// it out anyway.
//
if (pRequest->CookedUrl.pRoutingToken !=
pRequest->pDefaultRoutingTokenBuffer)
{
ASSERT(!"This should never happen !");
UL_FREE_POOL(pRequest->CookedUrl.pRoutingToken, URL_POOL_TAG);
}
pRequest->CookedUrl.pRoutingToken = pRoutingToken;
pRequest->CookedUrl.RoutingTokenBufferSize = MaxRoutingTokenSize;
}
//
// Copy over everything from the beginning of the cooked url
// to the AbsPath of the cooked url.
//
pToken = (PWCHAR) pRequest->CookedUrl.pRoutingToken;
RtlCopyMemory(pToken,
pRequest->CookedUrl.pUrl,
CookedHostLength * sizeof(WCHAR)
);
pToken += CookedHostLength;
ASSERT((pRequest->CookedUrl.pUrl)[CookedHostLength] == L'/');
*pToken++ = L':';
TokenLength = (CookedHostLength + 1) * sizeof(WCHAR);
//
// Now copy over the IP Address to the end.
//
TokenLength = TokenLength +
HostAddressToStringW(
pToken,
pHttpConn->pConnection->LocalAddress,
pHttpConn->pConnection->AddressType
);
pRequest->CookedUrl.RoutingTokenType = RoutingTokenHostPlusIP;
}
//
// Make sure that we did not overflow the allocated buffer.
// TokenLength does not include the terminating null.
//
ASSERT((TokenLength + sizeof(WCHAR))
<= pRequest->CookedUrl.RoutingTokenBufferSize);
//
// Set the tokenlength to show the actual amount we are using.
// Also recalculate the token hash INCLUDING the AbsPath from the
// original cooked url.
//
pRequest->CookedUrl.RoutingTokenLength = TokenLength;
//
// Create the hash for the whole RoutingToken plus AbsPath.
//
pRequest->CookedUrl.RoutingHash =
HashStringsNoCaseW(
pRequest->CookedUrl.pRoutingToken,
pRequest->CookedUrl.pAbsPath,
0
);
pRequest->CookedUrl.RoutingHash =
HashRandomizeBits(pRequest->CookedUrl.RoutingHash);
UlTrace(CONFIG_GROUP_FNC,
("Http!UlGenerateRoutingToken: "
"pRoutingToken:(%S) pAbsPath: (%S) Hash %lx\n",
pRequest->CookedUrl.pRoutingToken,
pRequest->CookedUrl.pAbsPath,
pRequest->CookedUrl.RoutingHash
));
//
// Ready for a cgroup lookup !
//
return STATUS_SUCCESS;
} // UlGenerateRoutingToken
/***************************************************************************++
Routine Description:
Generates the fixed part of the header. Fixed headers include the
status 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:
Version - the http version for the status line
pUserResponse - the user specified response
BufferLength - length of pBuffer
AccessMode - UserMode (probe) or KernelMode (no probe)
pBuffer - generate the headers here
pBytesCopied - gets the number of bytes generated
--***************************************************************************/
NTSTATUS
UlGenerateFixedHeaders(
IN HTTP_VERSION Version,
IN PHTTP_RESPONSE pUserResponse,
IN USHORT HttpStatusCode,
IN ULONG BufferLength,
IN KPROCESSOR_MODE AccessMode,
OUT PUCHAR pBuffer,
OUT PULONG pBytesCopied
)
{
PUCHAR pStartBuffer;
PUCHAR pEndBuffer;
ULONG BytesToCopy;
ULONG i;
PHTTP_KNOWN_HEADER pKnownHeaders;
PHTTP_UNKNOWN_HEADER pUnknownHeaders;
NTSTATUS Status = STATUS_SUCCESS;
USHORT ReasonLength;
PCSTR pReason;
USHORT RawValueLength;
PCSTR pRawValue;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pUserResponse != NULL);
ASSERT(pBuffer != NULL && BufferLength > 0);
ASSERT(pBytesCopied != NULL);
//
// The pUserResponse is (probably) user-mode data and cannot be trusted.
// Hence the try/except.
//
__try
{
pStartBuffer = pBuffer;
pEndBuffer = pBuffer + BufferLength;
// Check for arithmetic overflow
if (pEndBuffer <= pStartBuffer)
return STATUS_INVALID_PARAMETER;
ReasonLength = pUserResponse->ReasonLength;
pReason = pUserResponse->pReason;
//
// Build the response headers.
//
if (HTTP_NOT_EQUAL_VERSION(Version, 0, 9))
{
BytesToCopy =
STRLEN_LIT("HTTP/1.1 ") +
4 +
ReasonLength +
sizeof(USHORT);
if (DIFF(pEndBuffer - pBuffer) < BytesToCopy)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Always send back 1.1 in the response.
//
RtlCopyMemory(pBuffer, "HTTP/1.1 ", STRLEN_LIT("HTTP/1.1 "));
pBuffer += STRLEN_LIT("HTTP/1.1 ");
//
// Build ASCII representation of 3-digit status code
// in reverse order: units, tens, hundreds.
// Section 6.1.1 of RFC 2616 says that Status-Code is 3DIGIT;
// reject anything that can't be represented in three digits.
//
if (HttpStatusCode > UL_MAX_HTTP_STATUS_CODE)
return STATUS_INVALID_PARAMETER;
pBuffer[2] = (UCHAR) ('0' + (HttpStatusCode % 10));
HttpStatusCode /= 10;
pBuffer[1] = (UCHAR) ('0' + (HttpStatusCode % 10));
HttpStatusCode /= 10;
pBuffer[0] = (UCHAR) ('0' + (HttpStatusCode % 10));
pBuffer[3] = SP;
pBuffer += 4;
//
// Copy the optional reason phrase.
//
if (0 != ReasonLength)
{
UlProbeAnsiString(pReason, ReasonLength, AccessMode);
for (i = 0; i < ReasonLength; ++i)
{
// Reason-Phrase must be printable ASCII characters or LWS
if (IS_HTTP_PRINT(pReason[i]))
{
*pBuffer++ = pReason[i];
}
else
{
return STATUS_INVALID_PARAMETER;
}
}
}
//
// Terminate with the CRLF.
//
((UNALIGNED64 USHORT *)pBuffer)[0] = CRLF;
pBuffer += sizeof(USHORT);
//
// Loop through the known headers.
//
pKnownHeaders = pUserResponse->Headers.KnownHeaders;
for (i = 0; i < HttpHeaderResponseMaximum; ++i)
{
//
// Skip some headers we'll generate.
//
if ((i == HttpHeaderDate) ||
(i == HttpHeaderConnection) ||
(i == HttpHeaderServer))
{
continue;
}
RawValueLength = pKnownHeaders[i].RawValueLength;
// We have no provision for sending back a known header
// with an empty value, but the RFC specifies non-empty
// values for each of the known headers.
if (RawValueLength > 0)
{
PHEADER_MAP_ENTRY pEntry
= &(g_ResponseHeaderMapTable[g_ResponseHeaderMap[i]]);
pRawValue = pKnownHeaders[i].pRawValue;
UlProbeAnsiString(
pRawValue,
RawValueLength,
AccessMode
);
BytesToCopy =
pEntry->HeaderLength +
1 + // space
RawValueLength +
sizeof(USHORT); // CRLF
if (DIFF(pEndBuffer - pBuffer) < BytesToCopy)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlCopyMemory(
pBuffer,
pEntry->MixedCaseHeader,
pEntry->HeaderLength
);
pBuffer += pEntry->HeaderLength;
*pBuffer++ = SP;
RtlCopyMemory(
pBuffer,
pRawValue,
RawValueLength
);
pBuffer += RawValueLength;
((UNALIGNED64 USHORT *)pBuffer)[0] = CRLF;
pBuffer += sizeof(USHORT);
}
}
//
// Handle the Server: header
//
if ( UL_DISABLE_SERVER_HEADER_ALL != g_UlDisableServerHeader )
{
if ( g_UlDisableServerHeader == UL_DISABLE_SERVER_HEADER_DRIVER &&
(pUserResponse->Flags & HTTP_RESPONSE_FLAG_DRIVER) )
{
// skip generating server header on driver-created responses
}
else
{
BOOLEAN Suppress = FALSE;
pRawValue = pKnownHeaders[HttpHeaderServer].pRawValue;
RawValueLength = pKnownHeaders[HttpHeaderServer
].RawValueLength;
// check to see if app wishes to suppress Server: header
if ( (0 == RawValueLength) && pRawValue )
{
// Probe pRawValue and see if it's a single null char
UlProbeAnsiString(
pRawValue,
sizeof(UCHAR),
AccessMode
);
if ( '\0' == *pRawValue )
{
Suppress = TRUE;
}
}
// If we're not supressing it, generate it!
if ( !Suppress )
{
PHEADER_MAP_ENTRY pEntry;
pEntry = &(g_ResponseHeaderMapTable[g_ResponseHeaderMap[
HttpHeaderServer]]);
BytesToCopy =
pEntry->HeaderLength +
1 +
DEFAULT_SERVER_HDR_LENGTH +
sizeof(USHORT);
if (DIFF(pEndBuffer - pBuffer) < BytesToCopy)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlCopyMemory(
pBuffer,
pEntry->MixedCaseHeader,
pEntry->HeaderLength
);
pBuffer += pEntry->HeaderLength;
*pBuffer++ = SP;
//
// Prepend user's Server header data + SP
//
if ( RawValueLength )
{
BytesToCopy = RawValueLength + // User's Data
1 + // SP
DEFAULT_SERVER_HDR_LENGTH +
sizeof(USHORT); // CRLF
if (DIFF(pEndBuffer - pBuffer) < BytesToCopy)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
UlProbeAnsiString(
pRawValue,
RawValueLength,
AccessMode
);
RtlCopyMemory(
pBuffer,
pRawValue,
RawValueLength
);
pBuffer += RawValueLength;
*pBuffer++ = SP;
}
//
// Append default Server header vaule
//
RtlCopyMemory(
pBuffer,
DEFAULT_SERVER_HDR,
DEFAULT_SERVER_HDR_LENGTH
);
pBuffer += DEFAULT_SERVER_HDR_LENGTH;
// Terminate the header
((UNALIGNED64 USHORT *)pBuffer)[0] = CRLF;
pBuffer += sizeof(USHORT);
}
}
}
//
// And now the unknown headers (this might throw an exception).
//
pUnknownHeaders = pUserResponse->Headers.pUnknownHeaders;
if (pUnknownHeaders != NULL)
{
USHORT UnknownHeaderCount
= pUserResponse->Headers.UnknownHeaderCount;
if (UnknownHeaderCount >= UL_MAX_CHUNKS)
{
return STATUS_INVALID_PARAMETER;
}
UlProbeForRead(
pUnknownHeaders,
sizeof(HTTP_UNKNOWN_HEADER) * UnknownHeaderCount,
TYPE_ALIGNMENT(PVOID),
AccessMode
);
for (i = 0 ; i < UnknownHeaderCount; ++i)
{
USHORT NameLength = pUnknownHeaders[i].NameLength;
PCSTR pName;
RawValueLength = pUnknownHeaders[i].RawValueLength;
if (NameLength > 0)
{
BytesToCopy =
NameLength +
STRLEN_LIT(": ") +
RawValueLength +
sizeof(USHORT); // CRLF
if (DIFF(pEndBuffer - pBuffer) < BytesToCopy)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
pName = pUnknownHeaders[i].pName;
UlProbeAnsiString(
pName,
NameLength,
AccessMode
);
RtlCopyMemory(
pBuffer,
pName,
NameLength
);
pBuffer += NameLength;
*pBuffer++ = ':';
*pBuffer++ = SP;
//
// Empty values are legitimate
//
if (0 != RawValueLength)
{
pRawValue = pUnknownHeaders[i].pRawValue;
UlProbeAnsiString(
pRawValue,
RawValueLength,
AccessMode
);
RtlCopyMemory(
pBuffer,
pRawValue,
RawValueLength
);
pBuffer += RawValueLength;
}
((UNALIGNED64 USHORT *)pBuffer)[0] = CRLF;
pBuffer += sizeof(USHORT);
} // if (NameLength > 0)
}
} // if (pUnknownHeaders != NULL)
*pBytesCopied = DIFF(pBuffer - pStartBuffer);
} // if (Version > UlHttpVersion09)
else
{
*pBytesCopied = 0;
}
//
// Ensure we didn't use too much.
//
ASSERT(DIFF(pBuffer - pStartBuffer) <= BufferLength);
}
__except( UL_EXCEPTION_FILTER() )
{
Status = UL_CONVERT_EXCEPTION_CODE(GetExceptionCode());
}
return Status;
} // UlGenerateFixedHeaders
PCSTR Weekdays[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
PCSTR Months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
/***************************************************************************++
Routine Description:
Generates a date header string from a LARGE_INTEGER.
Arguments:
pBuffer: Buffer to store generated date string
systemTime: A 64-bit Time value to be converted
--***************************************************************************/
ULONG
UlpGenerateDateHeaderString(
OUT PUCHAR pBuffer,
IN LARGE_INTEGER systemTime
)
{
TIME_FIELDS timeFields;
int length;
PAGED_CODE();
RtlTimeToTimeFields( &systemTime, &timeFields );
length = _snprintf(
(char *) pBuffer,
DATE_HDR_LENGTH,
"%s, %02hd %s %04hd %02hd:%02hd:%02hd GMT",
Weekdays[timeFields.Weekday],
timeFields.Day,
Months[timeFields.Month - 1],
timeFields.Year,
timeFields.Hour,
timeFields.Minute,
timeFields.Second
);
return (ULONG)length;
} // UlpGenerateDateHeaderString
/***************************************************************************++
Routine Description:
Generates a date header and updates cached value if required.
Arguments:
pBuffer: Buffer to store generated date header.
pSystemTime: caller allocated buffer to receive the SystemTime equivalent
of the generated string time.
--***************************************************************************/
ULONG
UlGenerateDateHeader(
OUT PUCHAR pBuffer,
OUT PLARGE_INTEGER pSystemTime
)
{
LARGE_INTEGER CacheTime;
LONGLONG timediff;
PAGED_CODE();
//
// Get the current time.
//
KeQuerySystemTime( pSystemTime );
CacheTime.QuadPart = g_UlSystemTime.QuadPart;
//
// Check the difference between the current time, and
// the cached time. Note that timediff is signed.
//
timediff = pSystemTime->QuadPart - CacheTime.QuadPart;
if (timediff < ONE_SECOND)
{
//
// The entry hasn't gone stale yet. We can copy.
// Force a barrier around reading the string into memory.
//
UL_READMOSTLY_READ_BARRIER();
RtlCopyMemory(pBuffer, g_UlDateString, g_UlDateStringLength+1);
UL_READMOSTLY_READ_BARRIER();
//
// Inspect the global time value again in case it changed.
//
if (CacheTime.QuadPart == g_UlSystemTime.QuadPart) {
//
// Global value hasn't changed. We are all set.
//
pSystemTime->QuadPart = CacheTime.QuadPart;
return g_UlDateStringLength;
}
}
//
// The cached value is stale, or is/was being changed. We need to update
// or re-read it. Note that we could also spin trying to re-read and
// acquire the lock.
//
UlAcquirePushLockExclusive(&g_pUlNonpagedData->DateHeaderPushLock);
//
// Has someone else updated the time while we were blocked?
//
CacheTime.QuadPart = g_UlSystemTime.QuadPart;
timediff = pSystemTime->QuadPart - CacheTime.QuadPart;
if (timediff >= ONE_SECOND)
{
g_UlSystemTime.QuadPart = 0;
KeQuerySystemTime( pSystemTime );
UL_READMOSTLY_WRITE_BARRIER();
g_UlDateStringLength = UlpGenerateDateHeaderString(
g_UlDateString,
*pSystemTime
);
UL_READMOSTLY_WRITE_BARRIER();
g_UlSystemTime.QuadPart = pSystemTime->QuadPart;
}
else
{
// Capture the system time used to generate the buffer
pSystemTime->QuadPart = g_UlSystemTime.QuadPart;
}
//
// The time has been updated. Copy the new string into
// the caller's buffer.
//
RtlCopyMemory(
pBuffer,
g_UlDateString,
g_UlDateStringLength + 1
);
UlReleasePushLockExclusive(&g_pUlNonpagedData->DateHeaderPushLock);
return g_UlDateStringLength;
} // UlGenerateDateHeader
/***************************************************************************++
Routine Description:
Initializes the date cache.
--***************************************************************************/
NTSTATUS
UlInitializeDateCache( VOID )
{
LARGE_INTEGER now;
KeQuerySystemTime(&now);
g_UlDateStringLength = UlpGenerateDateHeaderString(g_UlDateString, now);
UlInitializePushLock(
&g_pUlNonpagedData->DateHeaderPushLock,
"DateHeaderPushLock",
0,
UL_DATE_HEADER_PUSHLOCK_TAG
);
g_DateCacheInitialized = TRUE;
return STATUS_SUCCESS;
} // UlInitializeDateCache
/***************************************************************************++
Routine Description:
Terminates the date header cache.
--***************************************************************************/
VOID
UlTerminateDateCache( VOID )
{
if (g_DateCacheInitialized)
{
UlDeletePushLock(&g_pUlNonpagedData->DateHeaderPushLock);
}
}
/***************************************************************************++
Routine Description:
Figures out how big the fixed headers are. Fixed headers include the
status 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:
Version - HTTP Version of the request: 0.9, 1.0., 1.1
pUserResponse - the response containing the headers
pHeaderLength - result: the number of bytes in the fixed headers.
Return Value:
NTSTATUS - STATUS_SUCCESS or an error code (possibly from an exception)
--***************************************************************************/
NTSTATUS
UlComputeFixedHeaderSize(
IN HTTP_VERSION Version,
IN PHTTP_RESPONSE pUserResponse,
IN KPROCESSOR_MODE AccessMode,
OUT PULONG pHeaderLength
)
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG HeaderLength = 0;
ULONG i;
PHTTP_UNKNOWN_HEADER pUnknownHeaders;
USHORT UnknownHeaderCount;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pHeaderLength != NULL);
if ((pUserResponse == NULL) || (HTTP_EQUAL_VERSION(Version, 0, 9)))
{
*pHeaderLength = 0;
return STATUS_SUCCESS;
}
//
// The pUserResponse is user-mode data and cannot be trusted.
// Hence the try/except.
//
__try
{
HeaderLength
+= (VERSION_SIZE + // HTTP-Version
1 + // SP
3 + // Status-Code
1 + // SP
pUserResponse->ReasonLength / sizeof(CHAR) +// Reason-Phrase
CRLF_SIZE // CRLF
);
//
// Loop through the known headers.
//
for (i = 0; i < HttpHeaderResponseMaximum; ++i)
{
USHORT RawValueLength
= pUserResponse->Headers.KnownHeaders[i].RawValueLength;
// skip some headers we'll generate
if ((i == HttpHeaderDate) ||
(i == HttpHeaderConnection) ||
(i == HttpHeaderServer)) {
continue;
}
if (RawValueLength > 0)
{
HeaderLength
+= (g_ResponseHeaderMapTable[
g_ResponseHeaderMap[i]
].HeaderLength + // Header-Name
1 + // SP
RawValueLength / sizeof(CHAR) + // Header-Value
CRLF_SIZE // CRLF
);
}
}
//
// Handle the Server header
//
if ( UL_DISABLE_SERVER_HEADER_ALL != g_UlDisableServerHeader )
{
if ( g_UlDisableServerHeader == UL_DISABLE_SERVER_HEADER_DRIVER &&
(pUserResponse->Flags & HTTP_RESPONSE_FLAG_DRIVER) )
{
// skip generating server header on driver-created responses
}
else
{
BOOLEAN Suppress = FALSE;
USHORT RawValueLength =
pUserResponse->Headers.KnownHeaders
[HttpHeaderServer].RawValueLength;
PCSTR pRawValue =
pUserResponse->Headers.KnownHeaders
[HttpHeaderServer].pRawValue;
// check to see if app wishes to suppress Server: header
if ( (0 == RawValueLength) && pRawValue )
{
// Probe pRawValue and see if it's a single null char
UlProbeAnsiString(
pRawValue,
sizeof(UCHAR),
AccessMode
);
if ( '\0' == *pRawValue )
{
Suppress = TRUE;
}
}
// If user specifies a server header, append it to
// the default Server header
if ( !Suppress )
{
HeaderLength += (g_ResponseHeaderMapTable[
g_ResponseHeaderMap[HttpHeaderServer]
].HeaderLength + // Header-Name
1 + // SP
DEFAULT_SERVER_HDR_LENGTH + // Header-Value
CRLF_SIZE // CRLF
);
if (RawValueLength)
{
HeaderLength += (1 + // SP
RawValueLength // User's Data to append
);
}
}
}
}
//
// And the unknown headers (this might throw an exception).
//
pUnknownHeaders = pUserResponse->Headers.pUnknownHeaders;
if (pUnknownHeaders != NULL)
{
UnknownHeaderCount = pUserResponse->Headers.UnknownHeaderCount;
if (UnknownHeaderCount >= UL_MAX_CHUNKS)
{
ExRaiseStatus( STATUS_INVALID_PARAMETER );
}
UlProbeForRead(
pUnknownHeaders,
sizeof(HTTP_UNKNOWN_HEADER) * UnknownHeaderCount,
sizeof(PVOID),
AccessMode
);
for (i = 0 ; i < UnknownHeaderCount; ++i)
{
USHORT Length = pUnknownHeaders[i].NameLength;
if (Length > 0)
{
HeaderLength += (Length / sizeof(CHAR) + // Header-Name
1 + // ':'
1 + // SP
pUnknownHeaders[i].RawValueLength /
sizeof(CHAR) + // Header-Value
CRLF_SIZE // CRLF
);
}
}
}
}
__except( UL_EXCEPTION_FILTER() )
{
Status = UL_CONVERT_EXCEPTION_CODE(GetExceptionCode());
HeaderLength = 0;
}
*pHeaderLength = HeaderLength;
return Status;
} // UlComputeFixedHeaderSize
/***************************************************************************++
Routine Description:
Figures out how big the maximum default variable headers are. Variable
headers include Date, Content and Connection.
The final CRLF separating headers from body is considered part of
the variable headers.
Arguments:
Return Values:
The maximum number of bytes in the variable headers.
--***************************************************************************/
ULONG
UlComputeMaxVariableHeaderSize( VOID )
{
ULONG Length = 0;
PHEADER_MAP_ENTRY pEntry;
PAGED_CODE();
//
// Date: header
//
pEntry = &(g_ResponseHeaderMapTable[g_ResponseHeaderMap[HttpHeaderDate]]);
Length += pEntry->HeaderLength; // header name
Length += 1; // SP
Length += DATE_HDR_LENGTH; // header value
Length += CRLF_SIZE; // CRLF
//
// Connection: header
//
pEntry = &(g_ResponseHeaderMapTable[g_ResponseHeaderMap[HttpHeaderConnection]]);
Length += pEntry->HeaderLength;
Length += 1;
Length += MAX(CONN_CLOSE_HDR_LENGTH, CONN_KEEPALIVE_HDR_LENGTH);
Length += CRLF_SIZE;
//
// Content-Length: header
//
pEntry = &(g_ResponseHeaderMapTable[g_ResponseHeaderMap[HttpHeaderContentLength]]);
Length += pEntry->HeaderLength;
Length += 1;
Length += MAX_ULONGLONG_STR;
Length += CRLF_SIZE;
//
// final CRLF
//
Length += CRLF_SIZE;
return Length;
} // UlComputeMaxVariableHeaderSize
/***************************************************************************++
Routine Description:
Generates the variable part of the header, namely, Date:, Connection:,
Content-Length:, and final CRLF.
Relies on the caller having correctly allocated enough memory to hold
these variable headers, which should have been done in
UlComputeMaxVariableHeaderSize().
Arguments:
ConnHeader - Supplies the type of Connection: header to generate.
pContentLengthString - Supplies a header value for an optional
Content-Length header. If this is the empty string "", then no
Content-Length header is generated.
ContentLengthStringLength - Supplies the length of the above string.
pBuffer - Supplies the target buffer for the generated headers.
pBytesCopied - Receives the number of header bytes generated.
pDateTime - Receives the system time equivalent of the Date: header
--***************************************************************************/
VOID
UlGenerateVariableHeaders(
IN UL_CONN_HDR ConnHeader,
IN BOOLEAN GenerateDate,
IN PUCHAR pContentLengthString,
IN ULONG ContentLengthStringLength,
OUT PUCHAR pBuffer,
OUT PULONG pBytesCopied,
OUT PLARGE_INTEGER pDateTime
)
{
PHEADER_MAP_ENTRY pEntry;
PUCHAR pStartBuffer;
PUCHAR pCloseHeaderValue;
ULONG CloseHeaderValueLength;
ULONG BytesCopied;
PAGED_CODE();
ASSERT( pContentLengthString != NULL );
ASSERT( pBuffer );
ASSERT( pBytesCopied );
ASSERT( pDateTime );
pStartBuffer = pBuffer;
//
// generate Date: header
//
if (GenerateDate)
{
pEntry = &(g_ResponseHeaderMapTable[g_ResponseHeaderMap[HttpHeaderDate]]);
RtlCopyMemory(
pBuffer,
pEntry->MixedCaseHeader,
pEntry->HeaderLength
);
pBuffer += pEntry->HeaderLength;
pBuffer[0] = SP;
pBuffer += 1;
BytesCopied = UlGenerateDateHeader( pBuffer, pDateTime );
pBuffer += BytesCopied;
((UNALIGNED64 USHORT *)pBuffer)[0] = CRLF;
pBuffer += sizeof(USHORT);
}
else
{
pDateTime->QuadPart = (LONGLONG) 0L;
}
//
// generate Connection: header
//
switch (ConnHeader)
{
case ConnHdrNone:
pCloseHeaderValue = NULL;
CloseHeaderValueLength = 0;
break;
case ConnHdrClose:
pCloseHeaderValue = (PUCHAR) CONN_CLOSE_HDR;
CloseHeaderValueLength = CONN_CLOSE_HDR_LENGTH;
break;
case ConnHdrKeepAlive:
pCloseHeaderValue = (PUCHAR) CONN_KEEPALIVE_HDR;
CloseHeaderValueLength = CONN_KEEPALIVE_HDR_LENGTH;
break;
default:
ASSERT(ConnHeader < ConnHdrMax);
pCloseHeaderValue = NULL;
CloseHeaderValueLength = 0;
break;
}
if (pCloseHeaderValue != NULL)
{
pEntry = &(g_ResponseHeaderMapTable[g_ResponseHeaderMap[HttpHeaderConnection]]);
RtlCopyMemory(
pBuffer,
pEntry->MixedCaseHeader,
pEntry->HeaderLength
);
pBuffer += pEntry->HeaderLength;
pBuffer[0] = SP;
pBuffer += 1;
RtlCopyMemory(
pBuffer,
pCloseHeaderValue,
CloseHeaderValueLength
);
pBuffer += CloseHeaderValueLength;
((UNALIGNED64 USHORT *)pBuffer)[0] = CRLF;
pBuffer += sizeof(USHORT);
}
if (pContentLengthString[0] != '\0')
{
ASSERT( ContentLengthStringLength > 0 );
pEntry = &(g_ResponseHeaderMapTable[g_ResponseHeaderMap[HttpHeaderContentLength]]);
RtlCopyMemory(
pBuffer,
pEntry->MixedCaseHeader,
pEntry->HeaderLength
);
pBuffer += pEntry->HeaderLength;
pBuffer[0] = SP;
pBuffer += 1;
RtlCopyMemory(
pBuffer,
pContentLengthString,
ContentLengthStringLength
);
pBuffer += ContentLengthStringLength;
((UNALIGNED64 USHORT *)pBuffer)[0] = CRLF;
pBuffer += sizeof(USHORT);
}
else
{
ASSERT( ContentLengthStringLength == 0 );
}
//
// generate final CRLF
//
((UNALIGNED64 USHORT *)pBuffer)[0] = CRLF;
pBuffer += sizeof(USHORT);
//
// make sure we didn't use too much
//
BytesCopied = DIFF(pBuffer - pStartBuffer);
*pBytesCopied = BytesCopied;
} // UlGenerateVariableHeaders
/*++
Routine Description:
Append a header value to an existing HTTP_HEADER entry, allocating
a buffer and copying the existing buffer.
Arguments:
pHttpHeader - Pointer to HTTP_HEADER structure to append to.
pHeader - Pointer header to be appended.
HeaderLength - Length of data pointed to by pHeader.
Return Value:
TRUE if we succeed, FALSE otherwise.
--*/
NTSTATUS
UlAppendHeaderValue(
PUL_INTERNAL_REQUEST pRequest,
PUL_HTTP_HEADER pHttpHeader,
PUCHAR pHeader,
USHORT HeaderLength
)
{
PUCHAR pNewHeader, pOldHeader;
USHORT OldHeaderLength;
PAGED_CODE();
OldHeaderLength = pHttpHeader->HeaderLength;
pNewHeader = UL_ALLOCATE_ARRAY(
NonPagedPool,
UCHAR,
OldHeaderLength + HeaderLength
+ STRLEN_LIT(", ") + sizeof(CHAR),
HEADER_VALUE_POOL_TAG
);
if (pNewHeader == NULL)
{
// Had a failure.
return STATUS_NO_MEMORY;
}
//
// Copy the old data into the new header.
//
RtlCopyMemory(pNewHeader, pHttpHeader->pHeader, OldHeaderLength);
// And copy in the new data as well, seperated by a comma.
//
*(pNewHeader + OldHeaderLength) = ',';
*(pNewHeader + OldHeaderLength + 1) = ' ';
OldHeaderLength += STRLEN_LIT(", ");
RtlCopyMemory( pNewHeader + OldHeaderLength, pHeader, HeaderLength);
// Now replace the existing header.
//
pOldHeader = pHttpHeader->pHeader;
pHttpHeader->HeaderLength = OldHeaderLength + HeaderLength;
pHttpHeader->pHeader = pNewHeader;
// If the old header was our buffer, free it too.
//
if (pHttpHeader->OurBuffer)
{
UL_FREE_POOL( pOldHeader, HEADER_VALUE_POOL_TAG );
}
pHttpHeader->OurBuffer = 1;
//
// null terminate it
//
pHttpHeader->pHeader[pHttpHeader->HeaderLength] = ANSI_NULL;
pRequest->HeadersAppended = TRUE;
return STATUS_SUCCESS;
}
/*++
Routine Description:
The default routine for handling headers. Used when we don't want to
do anything with the header but find out if we have the whole thing
and save a pointer to it if we do. This does not allow multiple header
values to exist for this header. Use UlMultipleHeaderHandler for
handling that by appending the values together (CSV).
Arguments:
pHttpConn - HTTP connection on which this header was received.
pHeader - Pointer to the header value.
HeaderLength - Length of data pointed to by pHeader.
HeaderID - ID of the header.
Return Value:
The length of the header value, or 0 if it's not terminated.
--*/
NTSTATUS
UlSingleHeaderHandler(
IN PUL_INTERNAL_REQUEST pRequest,
IN PUCHAR pHeader,
IN USHORT HeaderLength,
IN HTTP_HEADER_ID HeaderID,
OUT PULONG pBytesTaken
)
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG BytesTaken;
USHORT HeaderValueLength;
PAGED_CODE();
// Find the end of the header value
//
Status = FindRequestHeaderEnd(pRequest, pHeader, HeaderLength, &BytesTaken);
if (!NT_SUCCESS(Status))
goto end;
if (BytesTaken > 0)
{
ASSERT(BytesTaken <= ANSI_STRING_MAX_CHAR_LEN);
// Strip off the trailing CRLF from the header value length
HeaderValueLength = (USHORT) (BytesTaken - CRLF_SIZE);
// skip any preceding LWS.
while (HeaderValueLength > 0 && IS_HTTP_LWS(*pHeader))
{
pHeader++;
HeaderValueLength--;
}
// remove any trailing LWS.
while (HeaderValueLength > 0 && IS_HTTP_LWS(pHeader[HeaderValueLength-1]))
{
HeaderValueLength--;
}
// do we have an existing header?
//
if (pRequest->HeaderValid[HeaderID] == FALSE)
{
// No existing header, just save this pointer for now.
//
pRequest->HeaderIndex[pRequest->HeaderCount] = (UCHAR)HeaderID;
pRequest->HeaderCount++;
pRequest->HeaderValid[HeaderID] = TRUE;
pRequest->Headers[HeaderID].HeaderLength = HeaderValueLength;
pRequest->Headers[HeaderID].pHeader = pHeader;
pRequest->Headers[HeaderID].OurBuffer = 0;
//
// null terminate it. we have space as all headers end with CRLF.
// we are over-writing the CR
//
pHeader[HeaderValueLength] = ANSI_NULL;
//
// make space for a terminator
//
pRequest->TotalRequestSize += (HeaderValueLength + 1) * sizeof(CHAR);
}
else
{
//
// uh oh. Have an existing header; ignore duplicate only if this
// one exactly matches the first one. See RAID: 466626
//
UlTrace(PARSER, (
"http!UlSingleHeaderHandler(pRequest = %p, pHeader = %p)\n"
" WARNING: duplicate headers found.\n",
pRequest,
pHeader
));
//
// null terminate it. we have space as all headers end with CRLF.
// we are over-writing the CR
//
pHeader[HeaderValueLength] = ANSI_NULL;
//
// If they aren't the same length or don't EXACTLY compare, fail
// the request.
//
if ( (pRequest->Headers[HeaderID].HeaderLength != HeaderValueLength)
|| (HeaderValueLength != RtlCompareMemory(
pRequest->Headers[HeaderID].pHeader,
pHeader,
HeaderValueLength)) )
{
UlTraceError(PARSER, (
"http!UlSingleHeaderHandler(pRequest = %p, pHeader = %p)\n"
" ERROR: mismatching duplicate headers found.\n",
pRequest,
pHeader
));
UlSetErrorCode(pRequest, UlErrorHeader, NULL);
Status = STATUS_INVALID_DEVICE_REQUEST;
goto end;
}
}
}
// Success!
//
*pBytesTaken = BytesTaken;
end:
return Status;
} // UlSingleHeaderHandler
/*++
Routine Description:
The default routine for handling headers. Used when we don't want to
do anything with the header but find out if we have the whole thing
and save a pointer to it if we do. This function handles multiple
headers with the same name, and appends the values together separated
by commas.
Arguments:
pHttpConn - HTTP connection on which this header was received.
pHeader - Pointer to the header value.
HeaderLength - Length of data pointed to by pHeader.
HeaderID - ID of the header.
Return Value:
The length of the header value, or 0 if it's not terminated.
--*/
NTSTATUS
UlMultipleHeaderHandler(
IN PUL_INTERNAL_REQUEST pRequest,
IN PUCHAR pHeader,
IN USHORT HeaderLength,
IN HTTP_HEADER_ID HeaderID,
OUT PULONG pBytesTaken
)
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG BytesTaken;
USHORT HeaderValueLength;
PAGED_CODE();
// Find the end of the header value
//
Status = FindRequestHeaderEnd(pRequest, pHeader, HeaderLength, &BytesTaken);
if (!NT_SUCCESS(Status))
goto end;
if (BytesTaken > 0)
{
ASSERT(BytesTaken <= ANSI_STRING_MAX_CHAR_LEN);
// Strip off the trailing CRLF from the header value length
//
HeaderValueLength = (USHORT) (BytesTaken - CRLF_SIZE);
//
// skip any preceding LWS.
//
while (HeaderValueLength > 0 && IS_HTTP_LWS(*pHeader))
{
pHeader++;
HeaderValueLength--;
}
//
// remove any trailing LWS.
//
while (HeaderValueLength > 0 && IS_HTTP_LWS(pHeader[HeaderValueLength-1]))
{
HeaderValueLength--;
}
// do we have an existing header?
//
if (pRequest->HeaderValid[HeaderID] == FALSE)
{
// No existing header, just save this pointer for now.
//
pRequest->HeaderIndex[pRequest->HeaderCount] = (UCHAR)HeaderID;
pRequest->HeaderCount++;
pRequest->HeaderValid[HeaderID] = TRUE;
pRequest->Headers[HeaderID].HeaderLength = HeaderValueLength;
pRequest->Headers[HeaderID].pHeader = pHeader;
pRequest->Headers[HeaderID].OurBuffer = 0;
//
// null terminate it. we have space as all headers end with CRLF.
// we are over-writing the CR
//
pHeader[HeaderValueLength] = ANSI_NULL;
//
// make space for a terminator
//
pRequest->TotalRequestSize += (HeaderValueLength + 1) * sizeof(CHAR);
}
else
{
USHORT OldHeaderLength;
// Have an existing header, append this one.
OldHeaderLength = pRequest->Headers[HeaderID].HeaderLength;
Status = UlAppendHeaderValue(
pRequest,
&pRequest->Headers[HeaderID],
pHeader,
HeaderValueLength
);
if (NT_SUCCESS(Status) == FALSE)
goto end;
//
// Update total request length for the amount we just added.
// space for the terminator is already in there
//
pRequest->TotalRequestSize +=
(pRequest->Headers[HeaderID].HeaderLength - OldHeaderLength) *
sizeof(CHAR);
}
}
// Success!
//
*pBytesTaken = BytesTaken;
end:
return Status;
} // UlMultipleHeaderHandler
/*++
Routine Description:
The routine for handling Accept headers.
Arguments:
pHttpConn - HTTP connection on which this header was received.
pHeader - Pointer to the header value.
HeaderLength - Length of data pointed to by pHeader.
HeaderID - ID of the header.
Return Value:
The length of the header value, or 0 if it's not terminated.
Wildcard bit is set in the request if found
--*/
NTSTATUS
UlAcceptHeaderHandler(
IN PUL_INTERNAL_REQUEST pRequest,
IN PUCHAR pHeader,
IN USHORT HeaderLength,
IN HTTP_HEADER_ID HeaderID,
OUT PULONG pBytesTaken
)
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG BytesTaken;
USHORT HeaderValueLength;
PAGED_CODE();
// Find the end of the header value
//
Status = FindRequestHeaderEnd(pRequest, pHeader, HeaderLength, &BytesTaken);
if (!NT_SUCCESS(Status))
goto end;
if (BytesTaken > 0)
{
ASSERT(BytesTaken <= ANSI_STRING_MAX_CHAR_LEN);
// Strip off the trailing CRLF from the header value length
//
HeaderValueLength = (USHORT) (BytesTaken - CRLF_SIZE);
//
// skip any preceding LWS.
//
while (HeaderValueLength > 0 && IS_HTTP_LWS(*pHeader))
{
pHeader++;
HeaderValueLength--;
}
//
// remove any trailing LWS.
//
while (HeaderValueLength > 0 && IS_HTTP_LWS(pHeader[HeaderValueLength-1]))
{
HeaderValueLength--;
}
// do we have an existing header?
//
if (pRequest->HeaderValid[HeaderID] == FALSE)
{
// No existing header, just save this pointer for now.
//
pRequest->HeaderIndex[pRequest->HeaderCount] = (UCHAR)HeaderID;
pRequest->HeaderCount++;
pRequest->HeaderValid[HeaderID] = TRUE;
pRequest->Headers[HeaderID].HeaderLength = HeaderValueLength;
pRequest->Headers[HeaderID].pHeader = pHeader;
pRequest->Headers[HeaderID].OurBuffer = 0;
//
// null terminate it. we have space as all headers end with CRLF.
// we are over-writing the CR
//
pHeader[HeaderValueLength] = ANSI_NULL;
//
// make space for a terminator
//
pRequest->TotalRequestSize += (HeaderValueLength + 1) * sizeof(CHAR);
if (HeaderValueLength > WILDCARD_SIZE)
{
//
// for the fast path, we'll check only */* at the end
//
if (
(*(UNALIGNED64 ULONG *) (&pHeader[HeaderValueLength - WILDCARD_SIZE]) == WILDCARD_SPACE) ||
(*(UNALIGNED64 ULONG *) (&pHeader[HeaderValueLength - WILDCARD_SIZE]) == WILDCARD_COMMA)
)
{
pRequest->AcceptWildcard = TRUE;
}
}
}
else
{
ULONG OldHeaderLength;
// Have an existing header, append this one.
OldHeaderLength = pRequest->Headers[HeaderID].HeaderLength;
Status = UlAppendHeaderValue(
pRequest,
&pRequest->Headers[HeaderID],
pHeader,
HeaderValueLength
);
if (NT_SUCCESS(Status) == FALSE)
goto end;
//
// Update total request length for the amount we just added.
// space for the terminator is already in there
//
pRequest->TotalRequestSize +=
(pRequest->Headers[HeaderID].HeaderLength - OldHeaderLength) *
sizeof(CHAR);
}
}
// Success!
//
*pBytesTaken = BytesTaken;
end:
return Status;
} // UlAcceptHeaderHandler
/*++
Routine Description:
The default routine for handling Host headers. Used when we don't want to
do anything with the Host header but find out if we have the whole thing
and save a pointer to it if we do. this does not allow multiple Host header
values to exist for this header. use UlMultipleHeaderHandler for
handling that by appending the values together (CSV) .
Arguments:
pHttpConn - HTTP connection on which this header was received.
pHeader - Pointer to the header value.
HeaderLength - Length of data pointed to by pHeader.
HeaderID - ID of the header.
Return Value:
The length of the header value, or 0 if it's not terminated.
--*/
NTSTATUS
UlHostHeaderHandler(
IN PUL_INTERNAL_REQUEST pRequest,
IN PUCHAR pHeader,
IN USHORT HeaderLength,
IN HTTP_HEADER_ID HeaderID,
OUT PULONG pBytesTaken
)
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG BytesTaken;
USHORT HeaderValueLength;
SHORT AddressType;
PAGED_CODE();
// Find the end of the header value
Status = FindRequestHeaderEnd(pRequest, pHeader, HeaderLength, &BytesTaken);
if (!NT_SUCCESS(Status))
goto end;
// Non-zero => Found the CRLF that terminates the header
if (BytesTaken > 0)
{
ASSERT(BytesTaken <= ANSI_STRING_MAX_CHAR_LEN);
// Strip off the trailing CRLF from the header value length
HeaderValueLength = (USHORT) (BytesTaken - CRLF_SIZE);
// skip any preceding LWS.
while (HeaderValueLength > 0 && IS_HTTP_LWS(*pHeader))
{
pHeader++;
HeaderValueLength--;
}
// remove any trailing LWS.
while (HeaderValueLength > 0 && IS_HTTP_LWS(pHeader[HeaderValueLength-1]))
{
HeaderValueLength--;
}
// do we have an existing header?
if (pRequest->HeaderValid[HeaderID] == FALSE)
{
// No existing header, just save this pointer for now.
pRequest->HeaderIndex[pRequest->HeaderCount] = (UCHAR)HeaderID;
pRequest->HeaderCount++;
pRequest->HeaderValid[HeaderID] = TRUE;
pRequest->Headers[HeaderID].HeaderLength = HeaderValueLength;
pRequest->Headers[HeaderID].pHeader = pHeader;
pRequest->Headers[HeaderID].OurBuffer = 0;
//
// null terminate it. we have space as all headers end with CRLF.
// we are over-writing the CR
//
pHeader[HeaderValueLength] = ANSI_NULL;
//
// make space for a terminator
//
pRequest->TotalRequestSize += (HeaderValueLength + 1) * sizeof(CHAR);
//
// Now validate that the Host header has a well-formed value
//
Status = HttpValidateHostname(
&g_UrlC14nConfig,
pHeader,
HeaderValueLength,
Hostname_HostHeader,
&AddressType
);
if (!NT_SUCCESS(Status))
{
UlSetErrorCode(pRequest, UlErrorHost, NULL);
goto end;
}
}
else
{
//
// uh oh. Have an existing Host header, fail the request.
//
UlTraceError(PARSER, (
"ul!UlHostHeaderHandler(pRequest = %p, pHeader = %p)\n"
" ERROR: multiple headers not allowed.\n",
pRequest,
pHeader
));
UlSetErrorCode(pRequest, UlErrorHeader, NULL);
Status = STATUS_INVALID_DEVICE_REQUEST;
goto end;
}
}
// Success!
//
*pBytesTaken = BytesTaken;
end:
return Status;
} // UlHostHeaderHandler
/***************************************************************************++
Routine Description:
Checks the request to see if it has any of the following headers:
If-Modified-Since:
If-Match:
If-None-Match:
If so, we see if we can skip the sending of the full item. If we can skip,
we send back the apropriate response of either 304 (not modified) or
set the parser state to send back a 412 (precondition not met).
Arguments:
pRequest - The request to check
pUriCacheEntry - The cache entry being requested
Returns:
0 Send cannot be skipped; continue with sending the cache entry.
304 Send can be skipped. 304 response sent. NOTE: pRequest may be
invalid on return.
400 Send can be skipped. Caller must set ParseErrorState w/ErrorCode
set to UlError
412 Send can be skipped. pRequest->ParseState set to ParseErrorState with
pRequest->ErrorCode set to UlErrorPreconditionFailed (412)
--***************************************************************************/
ULONG
UlCheckCacheControlHeaders(
IN PUL_INTERNAL_REQUEST pRequest,
IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
IN BOOLEAN ResumeParsing
)
{
ULONG RetStatus = 0; // Assume can't skip.
BOOLEAN fIfNoneMatchPassed = TRUE;
BOOLEAN fSkipIfModifiedSince = FALSE;
LARGE_INTEGER liModifiedSince;
LARGE_INTEGER liUnmodifiedSince;
LARGE_INTEGER liNow;
ULONG BytesSent = 0;
FIND_ETAG_STATUS EtagStatus;
ASSERT( UL_IS_VALID_INTERNAL_REQUEST(pRequest) );
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
//
// 1. Check If-Match
//
if ( pRequest->HeaderValid[HttpHeaderIfMatch] )
{
EtagStatus = FindInETagList( pUriCacheEntry->pETag,
pRequest->Headers[HttpHeaderIfMatch].pHeader,
FALSE);
switch( EtagStatus )
{
case ETAG_NOT_FOUND:
// Match failed.
goto PreconditionFailed;
case ETAG_PARSE_ERROR:
goto ParseError;
default:
break;
}
}
//
// 2. Check If-None-Match
//
if ( pRequest->HeaderValid[HttpHeaderIfNoneMatch] )
{
EtagStatus = FindInETagList( pUriCacheEntry->pETag,
pRequest->Headers[HttpHeaderIfNoneMatch].pHeader,
TRUE);
switch( EtagStatus )
{
case ETAG_FOUND:
// ETag found on list.
fIfNoneMatchPassed = FALSE;
break;
case ETAG_NOT_FOUND:
//
// Header present and ETag not found on list. This modifies
// the semantic of the If-Modified-Since header; Namely,
// If-None-Match takes precidence over If-Modified-Since.
//
fSkipIfModifiedSince = TRUE;
break;
case ETAG_PARSE_ERROR:
goto ParseError;
}
}
//
// 3. Check If-Modified-Since
//
if ( !fSkipIfModifiedSince &&
pRequest->HeaderValid[HttpHeaderIfModifiedSince] )
{
if ( StringTimeToSystemTime(
(PCSTR) pRequest->Headers[HttpHeaderIfModifiedSince].pHeader,
pRequest->Headers[HttpHeaderIfModifiedSince].HeaderLength,
&liModifiedSince) )
{
//
// If the cache entry was created before the
// time specified in the If-Modified-Since header, we
// can return a 304 (Not Modified) status.
//
if ( pUriCacheEntry->CreationTime.QuadPart <= liModifiedSince.QuadPart )
{
//
// Check if the time specified in the request is
// greater than the current time (i.e., Invalid). If it is,
// ignore the If-Modified-Since header.
//
KeQuerySystemTime(&liNow);
if ( liModifiedSince.QuadPart < liNow.QuadPart )
{
// Valid time.
goto NotModified;
}
}
}
else
{
//
// if converting the If-Modified-Since header failed, we
// need to report the parse failure.
//
goto ParseError;
}
//
// If-Modified-Since overrides If-None-Match.
//
fIfNoneMatchPassed = TRUE;
}
if ( !fIfNoneMatchPassed )
{
//
// We could either skip the If-Modified-Since header, or it
// was not present, AND we did not pass the If-None-Match
// predicate. Since this is a "GET" or "HEAD" request (because
// that's all we cache, we should return 304. If this were
// any other verb, we should return 412.
//
ASSERT( (HttpVerbGET == pRequest->Verb) || (HttpVerbHEAD == pRequest->Verb) );
goto NotModified;
}
//
// 4. Check If-Unmodified-Since
//
if ( pRequest->HeaderValid[HttpHeaderIfUnmodifiedSince] )
{
if ( StringTimeToSystemTime(
(PCSTR) pRequest->Headers[HttpHeaderIfUnmodifiedSince].pHeader,
pRequest->Headers[HttpHeaderIfUnmodifiedSince].HeaderLength,
&liUnmodifiedSince) )
{
//
// If the cache entry was created after the time
// specified in the If-Unmodified-Since header, we
// MUST return a 412 (Precondition Failed) status.
//
if ( pUriCacheEntry->CreationTime.QuadPart > liUnmodifiedSince.QuadPart )
{
goto PreconditionFailed;
}
}
else
{
//
// if converting the If-Unmodified-Since header failed, we
// need to report the parse failure.
//
goto ParseError;
}
}
Cleanup:
return RetStatus;
NotModified:
RetStatus = 304;
//
// Send 304 (Not Modified) response
//
BytesSent = UlSendSimpleStatusEx(
pRequest,
UlStatusNotModified,
pUriCacheEntry,
ResumeParsing
);
//
// Update the server to client bytes sent.
// The logging & perf counters will use it.
//
pRequest->BytesSent += BytesSent;
goto Cleanup;
PreconditionFailed:
RetStatus = 412;
goto Cleanup;
ParseError:
// Parse Error encountered.
RetStatus = 400;
goto Cleanup;
} // UlCheckCacheControlHeaders
/***************************************************************************++
Routine Description:
Checks the cached response against the "Accept:" header in the request
to see if it can satisfy the requested Content-Type(s).
(Yes, I know this is really gross...I encourage anyone to find a better
way to parse this! --EricSten)
Arguments:
pRequest - The request to check.
pUriCacheEntry - The cache entry that might possibly match.
Returns:
TRUE At least one of the possible formats matched the Content-Type
of the cached entry.
FALSE None of the requested types matched the Content-Type of the
cached entry.
--***************************************************************************/
BOOLEAN
UlIsAcceptHeaderOk(
IN PUL_INTERNAL_REQUEST pRequest,
IN PUL_URI_CACHE_ENTRY pUriCacheEntry
)
{
BOOLEAN bRet = TRUE;
ULONG Len;
PUCHAR pHdr;
PUCHAR pSubType;
PUCHAR pTmp;
PUL_CONTENT_TYPE pContentType;
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
ASSERT(IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry));
if ( pRequest->HeaderValid[HttpHeaderAccept] &&
(pRequest->Headers[HttpHeaderAccept].HeaderLength > 0) )
{
Len = pRequest->Headers[HttpHeaderAccept].HeaderLength;
pHdr = pRequest->Headers[HttpHeaderAccept].pHeader;
pContentType = &pUriCacheEntry->ContentType;
//
// First, do "fast-path" check; see if "*/*" is anywhere in the header.
//
pTmp = (PUCHAR) strstr( (const char*) pHdr, "*/*" );
//
// If we found "*/*" and its either at the beginning of the line,
// the end of the line, or surrounded by either ' ' or ',', then
// it's really a wildcard.
//
if ((pTmp != NULL) &&
((pTmp == pHdr) ||
IS_HTTP_LWS(pTmp[-1]) ||
(pTmp[-1] == ',')) &&
((pTmp[3] == '\0') ||
IS_HTTP_LWS(pTmp[3]) ||
(pTmp[3] == ',')))
{
goto end;
}
//
// Wildcard not found; continue with slow path
//
while (Len)
{
if (pContentType->TypeLen > Len)
{
// Bad! No more string left...Bail out.
bRet = FALSE;
goto end;
}
if ( (pContentType->TypeLen == RtlCompareMemory(
pHdr,
pContentType->Type,
pContentType->TypeLen
)) &&
( '/' == pHdr[pContentType->TypeLen] ) )
{
//
// Found matching type; check subtype
//
pSubType = &pHdr[pContentType->TypeLen + 1];
if ( '*' == *pSubType )
{
// Subtype wildcard match!
goto end;
}
else
{
if ( pContentType->SubTypeLen >
(Len - ( pContentType->TypeLen + 1 )) )
{
// Bad! No more string left...Bail out.
bRet = FALSE;
goto end;
}
if ( pContentType->SubTypeLen == RtlCompareMemory(
pSubType,
pContentType->SubType,
pContentType->SubTypeLen
) &&
!IS_HTTP_TOKEN(pSubType[pContentType->SubTypeLen]) )
{
// Subtype exact match!
goto end;
}
}
}
//
// Didn't match this one; advance to next Content-Type in the Accept field
//
pTmp = (PUCHAR) strchr( (const char *) pHdr, ',' );
if (pTmp)
{
// Found a comma; step over it and any whitespace.
ASSERT ( Len > DIFF(pTmp - pHdr));
Len -= (DIFF(pTmp - pHdr) +1);
pHdr = (pTmp+1);
while( Len && IS_HTTP_LWS(*pHdr) )
{
pHdr++;
Len--;
}
} else
{
// No more content-types; bail.
bRet = FALSE;
goto end;
}
} // walk list of things
//
// Walked all Accept items and didn't find a match.
//
bRet = FALSE;
}
end:
UlTrace(PARSER,
("UlIsAcceptHeaderOk: returning %s\n",
bRet ? "TRUE" : "FALSE" ));
return bRet;
} // UlIsAcceptHeaderOk
/***************************************************************************++
Routine Description:
parses a content-type into its type and subtype components.
Arguments:
pStr String containing valid content type
StrLen Length of string (in bytes)
pContentType pointer to user provided UL_CONTENT_TYPE structure
--***************************************************************************/
VOID
UlGetTypeAndSubType(
IN PCSTR pStr,
IN ULONG StrLen,
IN PUL_CONTENT_TYPE pContentType
)
{
PCHAR pSlash;
ASSERT(pStr && StrLen);
ASSERT(pContentType);
pSlash = strnchr(pStr, '/', StrLen);
if ( NULL == pSlash ||
pStr == pSlash ||
(pSlash == (pStr + (StrLen-1))) )
{
//
// BAD!
// 1. content types should always have a slash!
// 2. content type can't have a null type
// 3. content type can't have a null sub-type
//
return;
}
//
// Minimal content type is "a/b"
//
ASSERT( StrLen >= 3 );
pContentType->TypeLen = (ULONG) MIN( (pSlash - pStr), MAX_TYPE_LENGTH );
RtlCopyMemory(
pContentType->Type,
pStr,
pContentType->TypeLen
);
ASSERT( StrLen > (pContentType->TypeLen + 1) );
pContentType->SubTypeLen = MIN( (StrLen - (pContentType->TypeLen + 1)), MAX_SUBTYPE_LENGTH );
RtlCopyMemory(
pContentType->SubType,
pSlash+1,
pContentType->SubTypeLen
);
} // UlGetTypeAndSubType
/*--
Routine Description:
This function converts the enum verb type to string to the
provided buffer. Used normally by the error logging.
Conversion happens as follows;
For HttpVerbUnknown, pRequest->pRawVerb is copied over
to the output buffer up to MAX_VERB_LENGTH characters.
For others NewVerbTable is used.
Arguments:
psz - Pointer to output buffer
pHttpRequest - Pointer to the incoming HTTP request.
chTerminator - Terminator will be written at the end.
Return Value:
Pointer to the end of the copied space.
--*/
PCHAR
UlCopyHttpVerb(
IN OUT PCHAR psz,
IN PUL_INTERNAL_REQUEST pRequest,
IN CHAR chTerminator
)
{
//
// Sanity check.
//
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
ASSERT(pRequest->Verb < HttpVerbMaximum);
if (pRequest->Verb == HttpVerbUnknown)
{
ULONG RawVerbLength =
MIN(MAX_VERB_LENGTH, pRequest->RawVerbLength);
ASSERT(pRequest->pRawVerb && pRequest->RawVerbLength);
RtlCopyMemory(
psz,
pRequest->pRawVerb,
RawVerbLength
);
psz += RawVerbLength;
}
else
{
//
// Using the raw verb in the request should be fine.
//
RtlCopyMemory(
psz,
NewVerbTable[pRequest->Verb].RawVerb,
NewVerbTable[pRequest->Verb].RawVerbLength
);
psz += NewVerbTable[pRequest->Verb].RawVerbLength;
}
*psz = chTerminator;
// Move past the terminator character unless it's a nul
if (chTerminator != '\0')
psz++;
return psz;
}
#if DBG
PCSTR
UlVerbToString(
HTTP_VERB Verb
)
{
PAGED_CODE();
switch (Verb)
{
case HttpVerbUnparsed:
return "Unparsed";
case HttpVerbUnknown:
return "Unknown";
case HttpVerbInvalid:
return "Invalid";
case HttpVerbOPTIONS:
return "OPTIONS";
case HttpVerbGET:
return "GET";
case HttpVerbHEAD:
return "HEAD";
case HttpVerbPOST:
return "POST";
case HttpVerbPUT:
return "PUT";
case HttpVerbDELETE:
return "DELETE";
case HttpVerbTRACE:
return "TRACE";
case HttpVerbCONNECT:
return "CONNECT";
case HttpVerbTRACK:
return "TRACK";
case HttpVerbMOVE:
return "MOVE";
case HttpVerbCOPY:
return "COPY";
case HttpVerbPROPFIND:
return "PROPFIND";
case HttpVerbPROPPATCH:
return "PROPPATCH";
case HttpVerbMKCOL:
return "MKCOL";
case HttpVerbLOCK:
return "LOCK";
case HttpVerbUNLOCK:
return "UNLOCK";
case HttpVerbSEARCH:
return "SEARCH";
default:
ASSERT(! "Unrecognized HTTP_VERB");
return "???";
}
} // UlVerbToString
PCSTR
UlParseStateToString(
PARSE_STATE ParseState
)
{
PAGED_CODE();
switch (ParseState)
{
case ParseVerbState:
return "Verb";
case ParseUrlState:
return "Url";
case ParseVersionState:
return "Version";
case ParseHeadersState:
return "Headers";
case ParseCookState:
return "Cook";
case ParseEntityBodyState:
return "EntityBody";
case ParseTrailerState:
return "Trailer";
case ParseDoneState:
return "Done";
case ParseErrorState:
return "Error";
default:
ASSERT(! "Unknown PARSE_STATE");
return "?Unknown?";
};
} // UlParseStateToString
#endif // DBG