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.
 
 
 
 
 
 

2176 lines
58 KiB

/*++
Copyright (c) 1995 Microsoft Corporation
Module Name:
headers.cxx
Abstract:
Contents:
HTTP_REQUEST_HANDLE_OBJECT::CreateRequestBuffer
HTTP_REQUEST_HANDLE_OBJECT::QueryRequestHeader
HTTP_REQUEST_HANDLE_OBJECT::AddInternalResponseHeader
HTTP_REQUEST_HANDLE_OBJECT::UpdateResponseHeaders
HTTP_REQUEST_HANDLE_OBJECT::CreateResponseHeaders
HTTP_REQUEST_HANDLE_OBJECT::QueryResponseVersion
HTTP_REQUEST_HANDLE_OBJECT::QueryStatusCode
HTTP_REQUEST_HANDLE_OBJECT::QueryStatusText
HTTP_REQUEST_HANDLE_OBJECT::QueryRawResponseHeaders
HTTP_REQUEST_HANDLE_OBJECT::RemoveAllRequestHeadersByName
HTTP_REQUEST_HANDLE_OBJECT::CheckWellKnownHeaders
MapHttpMethodType
CreateEscapedUrlPath
(CalculateHashNoCase)
Author:
Richard L Firth (rfirth) 20-Dec-1995
Revision History:
20-Dec-1995 rfirth
Created
--*/
#include <wininetp.h>
#include <perfdiag.hxx>
#include "httpp.h"
VOID
HTTP_REQUEST_HANDLE_OBJECT::ReplaceStatusHeader(
IN LPCSTR lpszStatus
)
/*
Description:
Replace the status line in a header (eg. "200 OK") with the specified text.
Arguments:
lpszStatus - Status text (eg. "200 OK")
Return Value:
None
*/
{
LockHeaders();
//INET_ASSERT (!_CacheWriteInProgress);
LPSTR pszHeader = _ResponseHeaders.GetHeaderPointer(_ResponseBuffer, 0);
INET_ASSERT(pszHeader);
LPSTR pszStatus = StrChr (pszHeader, ' ');
SKIPWS(pszStatus);
INET_ASSERT (!memcmp(pszStatus, "206", 3));
memcpy(pszStatus, lpszStatus, lstrlen(lpszStatus)+1);
_ResponseHeaders.ShrinkHeader(_ResponseBuffer, 0,
HTTP_QUERY_STATUS_TEXT, HTTP_QUERY_STATUS_TEXT,
(DWORD) (pszStatus - pszHeader) + lstrlen(lpszStatus));
UnlockHeaders();
}
LPSTR
HTTP_REQUEST_HANDLE_OBJECT::CreateRequestBuffer(
OUT LPDWORD lpdwRequestLength,
IN LPVOID lpOptional,
IN DWORD dwOptionalLength,
IN BOOL bExtraCrLf,
IN DWORD dwMaxPacketLength,
OUT LPBOOL lpbCombinedData
)
/*++
Routine Description:
Creates a request buffer from the HTTP request and headers
Arguments:
lpdwRequestLength - pointer to returned buffer length
lpOptional - pointer to optional data
dwOptionalLength - length of optional data
bExtraCrLf - TRUE if we need to add additional CR-LF to buffer
dwMaxPacketLength - maximum length of buffer
lpbCombinedData - output TRUE if data successfully combined into one
Return Value:
LPSTR
Success - pointer to allocated buffer
Failure - NULL
--*/
{
DEBUG_ENTER((DBG_HTTP,
Pointer,
"HTTP_REQUEST_HANDLE_OBJECT::CreateRequestBuffer",
"%#x, %#x, %d, %B, %d, %#x",
lpdwRequestLength,
lpOptional,
dwOptionalLength,
bExtraCrLf,
dwMaxPacketLength,
lpbCombinedData
));
PERF_ENTER(CreateRequestBuffer);
LPSTR requestBuffer = NULL;
*lpbCombinedData = FALSE;
if (!_RequestHeaders.LockHeaders())
{
goto quit;
}
DWORD headersLength;
DWORD requestLength;
DWORD optionalLength;
HEADER_STRING * pRequest = _RequestHeaders.GetFirstHeader();
HEADER_STRING & request = *pRequest;
/*
WCHAR wszUrl[1024];
LPWSTR pwszUrl = NULL;
BYTE utf8Url[2048];
LPBYTE pbUrl = NULL;
*/
LPSTR pszObject = _RequestHeaders.ObjectName();
DWORD dwObjectLength = _RequestHeaders.ObjectNameLength();
if (pRequest == NULL) {
goto Cleanup;
}
INET_ASSERT(request.HaveString());
headersLength = _RequestHeaders.HeadersLength();
requestLength = headersLength + (sizeof("\r\n") - 1);
/*------------------------------------------------------------------
GlobalEnableUtf8Encoding = FALSE;
if (GlobalEnableUtf8Encoding
&& StringContainsHighAnsi(pszObject, dwObjectLength)) {
pwszUrl = wszUrl;
DWORD arrayElements = ARRAY_ELEMENTS(wszUrl);
if (dwObjectLength > ARRAY_ELEMENTS(wszUrl)) {
arrayElements = dwObjectLength;
pwszUrl = (LPWSTR)ALLOCATE_FIXED_MEMORY(arrayElements * sizeof(*pwszUrl));
if (pwszUrl == NULL) {
goto utf8_cleanup;
}
}
PFNINETMULTIBYTETOUNICODE pfnMBToUnicode;
pfnMBToUnicode = GetInetMultiByteToUnicode( );
if (pfnMBToUnicode == NULL) {
goto utf8_cleanup;
}
HRESULT hr;
DWORD dwMode;
INT nMBChars;
INT nWChars;
nMBChars = dwObjectLength;
nWChars = arrayElements;
dwMode = 0;
hr = pfnMBToUnicode(&dwMode,
GetCodePage(),
pszObject,
&nMBChars,
pwszUrl,
&nWChars
);
if (hr != S_OK || nWChars == 0) {
goto utf8_cleanup;
}
DWORD nBytes;
nBytes = CountUnicodeToUtf8(pwszUrl, (DWORD)nWChars, TRUE);
pbUrl = utf8Url;
if (nBytes > ARRAY_ELEMENTS(utf8Url)) {
pbUrl = (LPBYTE)ALLOCATE_FIXED_MEMORY(nBytes);
if (pbUrl == NULL) {
goto utf8_cleanup;
}
}
DWORD error;
error = ConvertUnicodeToUtf8(pwszUrl,
(DWORD)nWChars,
pbUrl,
nBytes,
TRUE
);
INET_ASSERT(error == ERROR_SUCCESS);
if (error != ERROR_SUCCESS) {
goto utf8_cleanup;
}
requestLength = requestLength - dwObjectLength + nBytes;
headersLength = headersLength - dwObjectLength + nBytes;
pszObject = (LPSTR)pbUrl;
dwObjectLength = nBytes;
goto after_utf8;
utf8_cleanup:
if ((pwszUrl != wszUrl) && (pwszUrl != NULL)) {
FREE_MEMORY(pwszUrl);
}
pwszUrl = NULL;
if ((pbUrl != utf8Url) && (pbUrl != NULL)) {
FREE_MEMORY(pbUrl);
}
pbUrl = NULL;
pszObject = NULL;
dwObjectLength = 0;
}
after_utf8:
------------------------------------------------------------------*/
optionalLength = (DWORD)(dwOptionalLength + (bExtraCrLf ? (sizeof("\r\n") - 1) : 0));
if (requestLength + optionalLength <= dwMaxPacketLength) {
requestLength += optionalLength;
} else {
optionalLength = 0;
bExtraCrLf = FALSE;
}
requestBuffer = (LPSTR)ResizeBuffer(NULL, requestLength, FALSE);
if (requestBuffer != NULL) {
if (optionalLength != 0) {
*lpbCombinedData = TRUE;
}
} else if (optionalLength != 0) {
requestLength = headersLength + (sizeof("\r\n") - 1);
optionalLength = 0;
bExtraCrLf = FALSE;
requestBuffer = (LPSTR)ResizeBuffer(NULL, requestLength, FALSE);
}
if (requestBuffer != NULL) {
LPSTR buffer = requestBuffer;
//
// copy the headers. Remember: header 0 is the request
//
if (ERROR_SUCCESS != _RequestHeaders.CopyHeaders(&buffer, pszObject, dwObjectLength))
{
ResizeBuffer(requestBuffer, 0, FALSE);
requestBuffer = buffer = NULL;
goto Cleanup;
}
//
// terminate the request
//
*buffer++ = '\r';
*buffer++ = '\n';
if (optionalLength != 0) {
if (dwOptionalLength != 0) {
memcpy(buffer, lpOptional, dwOptionalLength);
buffer += dwOptionalLength;
}
if (bExtraCrLf) {
*buffer++ = '\r';
*buffer++ = '\n';
}
}
INET_ASSERT((SIZE_T)(buffer-requestBuffer) == requestLength);
*lpdwRequestLength = requestLength;
}
Cleanup:
_RequestHeaders.UnlockHeaders();
DEBUG_PRINT(HTTP,
INFO,
("request length = %d, combined = %B\n",
*lpdwRequestLength,
*lpbCombinedData
));
/*
if ((pbUrl != NULL) && (pbUrl != utf8Url)) {
FREE_MEMORY(pbUrl);
}
if ((pwszUrl != NULL) && (pwszUrl != wszUrl)) {
FREE_MEMORY(pwszUrl);
}
*/
quit:
PERF_LEAVE(CreateRequestBuffer);
DEBUG_LEAVE(requestBuffer);
return requestBuffer;
}
DWORD
HTTP_REQUEST_HANDLE_OBJECT::QueryRequestHeader(
IN LPCSTR lpszHeaderName,
IN DWORD dwHeaderNameLength,
IN LPVOID lpBuffer,
IN OUT LPDWORD lpdwBufferLength,
IN DWORD dwModifiers,
IN OUT LPDWORD lpdwIndex
)
/*++
Routine Description:
Searches for an arbitrary request header and if found, returns its value
Arguments:
lpszHeaderName - pointer to the name of the header to find
dwHeaderNameLength - length of the header
lpBuffer - pointer to buffer for results
lpdwBufferLength - IN: length of lpBuffer
OUT: length of the returned header value, or required
length of lpBuffer
dwModifiers - how to return the data: as number, as SYSTEMTIME
structure, etc.
lpdwIndex - IN: 0-based index of header to find
OUT: next header index if success returned
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure - ERROR_INSUFFICIENT_BUFFER
lpBuffer not large enough for results
ERROR_HTTP_HEADER_NOT_FOUND
Couldn't find the requested header
--*/
{
DEBUG_ENTER((DBG_HTTP,
Dword,
"QueryRequestHeader",
"%#x [%.*q], %d, %#x, %#x [%#x], %#x, %#x [%d]",
lpszHeaderName,
min(dwHeaderNameLength + 1, 80),
lpszHeaderName,
dwHeaderNameLength,
lpBuffer,
lpdwBufferLength,
*lpdwBufferLength,
dwModifiers,
lpdwIndex,
*lpdwIndex
));
PERF_ENTER(QueryRequestHeader);
DWORD error;
error = _RequestHeaders.FindHeader(NULL,
lpszHeaderName,
dwHeaderNameLength,
dwModifiers,
lpBuffer,
lpdwBufferLength,
lpdwIndex
);
PERF_LEAVE(QueryRequestHeader);
DEBUG_LEAVE(error);
return error;
}
DWORD
HTTP_REQUEST_HANDLE_OBJECT::QueryRequestHeader(
IN DWORD dwQueryIndex,
IN LPVOID lpBuffer,
IN OUT LPDWORD lpdwBufferLength,
IN DWORD dwModifiers,
IN OUT LPDWORD lpdwIndex
)
/*++
Routine Description:
Searches for an arbitrary request header and if found, returns its value
Arguments:
lpszHeaderName - pointer to the name of the header to find
dwHeaderNameLength - length of the header
lpBuffer - pointer to buffer for results
lpdwBufferLength - IN: length of lpBuffer
OUT: length of the returned header value, or required
length of lpBuffer
dwModifiers - how to return the data: as number, as SYSTEMTIME
structure, etc.
lpdwIndex - IN: 0-based index of header to find
OUT: next header index if success returned
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure - ERROR_INSUFFICIENT_BUFFER
lpBuffer not large enough for results
ERROR_HTTP_HEADER_NOT_FOUND
Couldn't find the requested header
--*/
{
DEBUG_ENTER((DBG_HTTP,
Dword,
"QueryRequestHeader",
"%u, %#x [%#x], %#x, %#x [%d]",
dwQueryIndex,
lpBuffer,
lpdwBufferLength,
*lpdwBufferLength,
dwModifiers,
lpdwIndex,
*lpdwIndex
));
PERF_ENTER(QueryRequestHeader);
DWORD error;
error = _RequestHeaders.FindHeader(NULL,
dwQueryIndex,
dwModifiers,
lpBuffer,
lpdwBufferLength,
lpdwIndex
);
PERF_LEAVE(QueryRequestHeader);
DEBUG_LEAVE(error);
return error;
}
DWORD
HTTP_REQUEST_HANDLE_OBJECT::AddInternalResponseHeader(
IN DWORD dwHeaderIndex,
IN LPSTR lpszHeader,
IN DWORD dwHeaderLength
)
/*++
Routine Description:
Adds a created response header to the response header array. Unlike normal
response headers, this will be a pointer to an actual string, not an offset
into the response buffer.
Even if the address of the response buffer changes, created response headers
will remain fixed
N.B. The header MUST NOT have a CR-LF terminator
N.B.-2 This function must be called under the header lock.
Arguments:
dwHeaderIndex - index into header value we are actually creating
lpszHeader - pointer to created (internal) header to add
dwHeaderLength - length of response header, or -1 if ASCIIZ
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure - ERROR_NOT_ENOUGH_MEMORY
--*/
{
DEBUG_ENTER((DBG_HTTP,
Dword,
"AddInternalResponseHeader",
"%u [%q], %q, %d",
dwHeaderIndex,
GlobalKnownHeaders[dwHeaderIndex].Text,
lpszHeader,
dwHeaderLength
));
DWORD error;
if (dwHeaderLength == (DWORD)-1) {
dwHeaderLength = lstrlen(lpszHeader);
}
INET_ASSERT((lpszHeader[dwHeaderLength - 1] != '\r')
&& (lpszHeader[dwHeaderLength - 1] != '\n'));
//
// find the next slot for this header
//
HEADER_STRING * freeHeader;
//
// if we already have all the headers (the 'empty' header is the last one
// in the array) then change the last header to be the one we are adding
// and add a new empty header, else just add this one
//
DWORD iSlot;
freeHeader = _ResponseHeaders.FindFreeSlot(&iSlot);
if (freeHeader == NULL) {
error = _ResponseHeaders.GetError();
INET_ASSERT(error != ERROR_SUCCESS);
} else {
HEADER_STRING * lastHeader;
lastHeader = _ResponseHeaders.GetEmptyHeader();
if (lastHeader != NULL) {
//
// make copy of last header - its an offset string
//
*freeHeader = *lastHeader;
//
// use what was last header as free header
//
freeHeader = lastHeader;
}
freeHeader->MakeCopy(lpszHeader, dwHeaderLength);
freeHeader->SetNextKnownIndex(_ResponseHeaders.FastAdd(dwHeaderIndex, iSlot));
error = ERROR_SUCCESS;
}
DEBUG_LEAVE(error);
return error;
}
DWORD
HTTP_REQUEST_HANDLE_OBJECT::UpdateResponseHeaders(
IN OUT LPBOOL lpbEof
)
/*++
Routine Description:
Given the next chunk of the response, updates the response headers. The
buffer pointer, buffer length and number of bytes received values are all
maintained in this object (_ResponseBuffer, _ResponseBufferLength and
_BytesReceived, resp.)
Arguments:
lpbEof - IN: TRUE if we have reached the end of the response
OUT: TRUE if we have reached the end of the response or the end
of the headers
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure - ERROR_NOT_ENOUGH_MEMORY
--*/
{
DEBUG_ENTER((DBG_HTTP,
Dword,
"HTTP_REQUEST_HANDLE_OBJECT::UpdateResponseHeaders",
"%#x [%.*q], %d, %d, %#x [%B]",
_ResponseBuffer + _ResponseScanned,
min(_ResponseBufferLength + 1, 80),
_ResponseBuffer + _ResponseScanned,
_ResponseBufferLength,
_BytesReceived,
lpbEof,
*lpbEof
));
PERF_ENTER(UpdateResponseHeaders);
LPSTR lpszBuffer = (LPSTR)_ResponseBuffer + _ResponseScanned;
DWORD dwBytesReceived = _BytesReceived - _ResponseScanned;
DWORD error = ERROR_SUCCESS;
BOOL success = TRUE;
HEADER_STRING * statusLine;
//
// lock down the response headers for the duration of this request. The only
// way another thread is going to wait on this lock is if the reference on
// the HTTP request object goes to zero, which *shouldn't* happen
//
if (!_ResponseHeaders.LockHeaders())
{
error = ERROR_NOT_ENOUGH_MEMORY;
goto quit;
}
//
// if input EOF is set then the caller is telling us that the end of the
// response has been reached at transport level (the server closed the
// connectiion)
//
if (*lpbEof) {
SetEof(TRUE);
}
//
// if we don't yet know whether we have a HTTP/1.0 (or greater) or HTTP/0.9
// response yet, then try to find out.
//
// Only responses greater than HTTP/0.9 start with the "HTTP/#.#" string
//
if (!IsDownLevel() && !IsUpLevel()) {
if ((dwBytesReceived < sizeof("Secure-HTTP/")) && !*lpbEof) {
goto done;
}
#define MAKE_VERSION_ENTRY(string) string, sizeof(string) - 1
static struct {
LPSTR Version;
DWORD Length;
} KnownVersionsStrings[] = {
MAKE_VERSION_ENTRY("HTTP/"),
MAKE_VERSION_ENTRY("S-HTTP/"),
MAKE_VERSION_ENTRY("SHTTP/"),
MAKE_VERSION_ENTRY("Secure-HTTP/"),
//
// allow for servers generating slightly off-the-wall responses
//
MAKE_VERSION_ENTRY("HTTP ")
};
#define NUM_HTTP_VERSIONS ARRAY_ELEMENTS(KnownVersionsStrings)
//
// We know this is the start of a HTTP response, but there may be some
// noise at the start from bad HTML authoring, or bad content-length on
// the previous response on a keep-alive connection. We will try to sync
// up to the HTTP header (we will only look for this - I have never seen
// any of the others, and I doubt its worth the increased complexity and
// processing time)
//
LPSTR lpszBuf;
DWORD bytesLeft;
BOOL bFoundStart;
lpszBuf = lpszBuffer;
bytesLeft = dwBytesReceived;
bFoundStart = FALSE;
do {
while ((bytesLeft > 0) && (*lpszBuf != 'H') && (*lpszBuf != 'h')) {
++lpszBuf;
--bytesLeft;
++_ResponseScanned;
}
if (bytesLeft == 0) {
break;
}
//
// scan for the known version strings
//
for (int i = 0; i < NUM_HTTP_VERSIONS; ++i) {
LPSTR version = KnownVersionsStrings[i].Version;
DWORD length = KnownVersionsStrings[i].Length;
if ((bytesLeft >= length)
//
// try the most common case as a direct comparison. memcmp()
// should expand to cmpsd && cmpsb on x86 (most common platform
// and one on which we are most interested in improving perf)
//
&& (((i == 0)
&& (memcmp(lpszBuf, "HTTP/", sizeof("HTTP/") - 1) == 0))
//&& (lpszBuf[0] == 'H')
//&& (lpszBuf[1] == 'T')
//&& (lpszBuf[2] == 'T')
//&& (lpszBuf[3] == 'P')
//&& (lpszBuf[4] == '/'))
//
// "Clients should be tolerant in parsing the Status-Line"
// quote from HTTP/1.1 spec, therefore we perform a
// case-insensitive string comparison here
//
|| (_strnicmp(lpszBuf, version, length) == 0))) {
//
// it starts with one of the recognized protocol version strings.
// We assume its not a down-level server, although it could be,
// sending back a plain text document that has e.g. "HTTP/1.0..."
// at its start
//
// According to the HTTP "spec", though, it is mentioned that 0.9
// servers typically only return HTML, hence we shouldn't see
// even a 0.9 response start with non-HTML data
//
SetUpLevel(TRUE);
//
// we have start of this response
//
lpszBuffer = lpszBuf;
bFoundStart = TRUE;
break;
}
}
//
// if we didn't find the start of the HTTP response then search again
//
if (!bFoundStart) {
++lpszBuf;
--bytesLeft;
++_ResponseScanned;
}
} while (!bFoundStart && (bytesLeft > 0));
//
// if we didn't find a recognizable HTTP 1.x response then we assume its
// a down-level response
//
if (!IsUpLevel()) {
//
// if we didn't find the start of a valid HTTP response and we have
// not filled the response buffer or hit the end of the connection
// then quit so we can get the next packet
//
if ((_BytesReceived < _ResponseBufferLength) && !IsEof()) {
DEBUG_PRINT(HTTP,
WARNING,
("Didn't find start of response. Try again\n"
));
//dprintf("*** didn't find start of response. Try again\n");
goto done;
}
//
// this may be a real down-level server, or it may be the response
// from an FTP or gopher server via a proxy, in which case there
// will be no headers. We will add some default headers to make
// life easier for higher level software
//
AddInternalResponseHeader(HTTP_QUERY_STATUS_TEXT, // use non-standard index, since we never query this normally
"HTTP/1.0 200 OK",
sizeof("HTTP/1.0 200 OK") - 1
);
_StatusCode = HTTP_STATUS_OK;
//SetDownLevel(TRUE);
//
// we're now ready for the app to start reading data out
//
SetData(TRUE);
//
// down-level server: we're done
//
DEBUG_PRINT(HTTP,
INFO,
("Server is down-level\n"
));
goto done;
}
}
//
// this stuff's only for uplevel responses, sorry
//
INET_ASSERT(IsUpLevel());
//
// Note: at this point we can't store pointers into the response buffer
// because it might move during a subsequent reallocation. We have to
// maintain offsets into the buffer and convert to pointers when we come to
// read the data out of the buffer (when the response is complete, or at
// least we've finished receiving headers)
//
//
// if we haven't checked the response yet, then the first thing to
// get is the status line
//
statusLine = GetStatusLine();
if (statusLine == NULL) {
error = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
if (!statusLine->HaveString())
{
int majorVersion = 0;
int minorVersion = 0;
BOOL fSupportsHttp1_1;
_StatusCode = 0;
//
// find the status line
//
success = _ResponseHeaders.ParseStatusLine(
(LPSTR)_ResponseBuffer,
_BytesReceived,
IsEof(),
&_ResponseScanned,
&_StatusCode,
(LPDWORD)&majorVersion,
(LPDWORD)&minorVersion
);
if ( !success )
{
error = ERROR_SUCCESS;
goto Cleanup;
}
DEBUG_PRINT(HTTP,
INFO,
("Version = %d.%d\n",
majorVersion,
minorVersion
));
DEBUG_PRINT(HTTP,
INFO,
("_StatusCode = %d\n",
_StatusCode
));
fSupportsHttp1_1 = FALSE;
if ( majorVersion > 1 )
{
//
// for higher version servers, the 1.1 spec dictates
// that we return the highest version the client
// supports, and in our case that is 1.1.
//
fSupportsHttp1_1 = TRUE;
}
else if ( majorVersion == 1 )
{
if ( minorVersion >= 1 )
{
fSupportsHttp1_1 = TRUE;
}
} else if ((majorVersion < 0) || (minorVersion < 0)) {
error = ERROR_HTTP_INVALID_SERVER_RESPONSE;
goto Cleanup;
}
SetResponseHttp1_1(fSupportsHttp1_1);
//
// record the server HTTP version in the server info object
//
CServerInfo * pServerInfo = GetServerInfo();
if (pServerInfo != NULL) {
if (fSupportsHttp1_1) {
pServerInfo->SetHttp1_1();
//
// Set the max connections per HTTP 1.1 server.
//
pServerInfo->SetNewLimit(GetMaxConnectionsPerServer(WINHTTP_OPTION_MAX_CONNS_PER_SERVER));
} else {
pServerInfo->SetHttp1_0();
//
// Set the max connections per HTTP 1.0 server.
//
pServerInfo->SetNewLimit(GetMaxConnectionsPerServer(WINHTTP_OPTION_MAX_CONNS_PER_1_0_SERVER));
}
}
if (_StatusCode == 0) {
//
// BUGBUG [arthurbi] malformed header, should we really just accept it?
// what if we get indeterminate garbage?
//
INET_ASSERT(FALSE);
AddInternalResponseHeader(HTTP_QUERY_STATUS_TEXT, // use non-standard index, since we never query this normally
"HTTP/1.0 200 OK",
sizeof("HTTP/1.0 200 OK") - 1
);
_StatusCode = HTTP_STATUS_OK;
error = ERROR_SUCCESS;
goto Cleanup;
}
}
//
// continue scanning headers here until we have tested all the current
// buffer, or we have found the start of the data
//
BOOL fFoundEndOfHeaders;
error = _ResponseHeaders.ParseHeaders(
(LPSTR)_ResponseBuffer,
_BytesReceived,
IsEof(),
&_ResponseScanned,
&success,
&fFoundEndOfHeaders
);
if ( error != ERROR_SUCCESS )
{
goto Cleanup;
}
if ( fFoundEndOfHeaders )
{
//
// we found the end of the headers
//
SetEof(TRUE);
//
// and the start of the data
//
SetData(TRUE);
_DataOffset = _ResponseScanned;
DEBUG_PRINT(HTTP,
INFO,
("found end of headers. _DataOffset = %d\n",
_DataOffset
));
}
done:
//
// if we have reached the end of the headers then we communicate this fact
// to the caller
//
if (IsData() || IsEof()) {
error = CheckWellKnownHeaders();
if (ERROR_SUCCESS != error)
{
goto Cleanup;
}
*lpbEof = TRUE;
/*
Set connection persistency based on these rules:
persistent = (1.0Request && Con: K-A && 1.0Response && Con: K-A)
|| (1.1Request && Con: K-A && 1.0Response && Con: K-A)
|| (1.0Request && Con: K-A && 1.1Response && Con: K-A)
|| (1.1Request && !Con: Close && 1.1Response && !Con: Close)
therefore,
persistent = 1.1Request && 1.1Response
? (!Con: Close in request || response)
: Con: K-A in request && response
*/
if (IsRequestHttp1_1() && IsResponseHttp1_1()) {
BOOL bHaveConnCloseRequest;
bHaveConnCloseRequest = FindConnCloseRequestHeader(
IsRequestUsingProxy()
? HTTP_QUERY_PROXY_CONNECTION
: HTTP_QUERY_CONNECTION
);
if (!(IsConnCloseResponse() || bHaveConnCloseRequest)) {
DEBUG_PRINT(HTTP,
INFO,
("HTTP/1.1 persistent connection\n"
));
SetKeepAlive(TRUE);
SetPersistentConnection(IsRequestUsingProxy()
&& !IsTalkingToSecureServerViaProxy()
);
} else {
DEBUG_PRINT(HTTP,
INFO,
("HTTP/1.1 non-persistent connection: close on: request: %B; response: %B\n",
bHaveConnCloseRequest,
IsConnCloseResponse()
));
SetKeepAlive(FALSE);
SetNoLongerKeepAlive();
ClearPersistentConnection();
}
}
}
Cleanup:
//
// we are finished updating the response headers (no other thread should be
// waiting for this if the reference count and object state is correct)
//
_ResponseHeaders.UnlockHeaders();
quit:
PERF_LEAVE(UpdateResponseHeaders);
DEBUG_LEAVE(error);
return error;
}
DWORD
HTTP_REQUEST_HANDLE_OBJECT::CreateResponseHeaders(
IN OUT LPSTR* ppszBuffer,
IN DWORD dwBufferLength
)
/*++
Routine Description:
Create the response headers given a buffer containing concatenated headers.
Called when we are creating this object from the cache
Arguments:
lpszBuffer - pointer to buffer containing headers
dwBufferLength - length of lpszBuffer
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure - ERROR_NOT_ENOUGH_MEMORY
Couldn't create headers
--*/
{
DEBUG_ENTER((DBG_HTTP,
Dword,
"HTTP_REQUEST_HANDLE_OBJECT::CreateResponseHeaders",
"%.32q, %d",
ppszBuffer,
dwBufferLength
));
//
// there SHOULD NOT already be a response buffer if we're adding an
// external buffer
//
INET_ASSERT(_ResponseBuffer == NULL);
DWORD error;
BOOL eof = FALSE;
_ResponseBuffer = (LPBYTE) *ppszBuffer;
_ResponseBufferLength = dwBufferLength;
_BytesReceived = dwBufferLength;
error = UpdateResponseHeaders(&eof);
if (error != ERROR_SUCCESS) {
//
// if we failed, we will clean up our variables including clearing
// out the response buffer address and length, but leave freeing
// the buffer to the caller
//
_ResponseBuffer = NULL;
_ResponseBufferLength = 0;
ResetResponseVariables();
} else {
//
// Success - the object owns the buffer so the caller should not free.
//
*ppszBuffer = NULL;
}
DEBUG_LEAVE(error);
return error;
}
DWORD
HTTP_REQUEST_HANDLE_OBJECT::QueryResponseVersion(
IN LPVOID lpBuffer,
IN OUT LPDWORD lpdwBufferLength
)
/*++
Routine Description:
Returns the HTTP version string from the status line
Arguments:
lpBuffer - pointer to buffer to copy version string into
lpdwBufferLength - IN: size of lpBuffer
OUT: size of version string excluding terminating '\0'
if successful, else required buffer length
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure - ERROR_INSUFFICIENT_BUFFER
--*/
{
PERF_ENTER(QueryResponseVersion);
DWORD error;
HEADER_STRING * statusLine = GetStatusLine();
if ((statusLine == NULL) || statusLine->IsError()) {
error = ERROR_WINHTTP_INTERNAL_ERROR;
goto quit;
}
LPSTR string;
DWORD length;
//
// get a pointer into the response buffer where the status line starts
// and its length
//
string = statusLine->StringAddress((LPSTR)_ResponseBuffer);
length = (DWORD)statusLine->StringLength();
//
// the version string is the first token on the line, delimited by spaces
//
DWORD index;
for (index = 0; index < length; ++index) {
//
// we'll also check for CR and LF, although just space should be
// sufficient
//
if ((string[index] == ' ')
|| (string[index] == '\r')
|| (string[index] == '\n')) {
break;
}
}
if (*lpdwBufferLength > index) {
memcpy(lpBuffer, (LPVOID)string, index);
((LPSTR)lpBuffer)[index] = '\0';
*lpdwBufferLength = index;
error = ERROR_SUCCESS;
} else {
*lpdwBufferLength = index + 1;
error = ERROR_INSUFFICIENT_BUFFER;
}
quit:
PERF_LEAVE(QueryResponseVersion);
return error;
}
DWORD
HTTP_REQUEST_HANDLE_OBJECT::QueryStatusCode(
IN LPVOID lpBuffer,
IN OUT LPDWORD lpdwBufferLength,
IN DWORD dwModifiers
)
/*++
Routine Description:
Returns the status code as a string or a number
Arguments:
lpBuffer - pointer to buffer where results written
lpdwBufferLength - IN: length of buffer
OUT: size of returned information, or required size'
of buffer
dwModifiers - flags which modify returned value
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure - ERROR_INSUFFICIENT_BUFFER
--*/
{
PERF_ENTER(QueryStatusCode);
DWORD error;
DWORD requiredSize;
if (dwModifiers & HTTP_QUERY_FLAG_NUMBER) {
requiredSize = sizeof(_StatusCode);
if (*lpdwBufferLength >= requiredSize) {
*(LPDWORD)lpBuffer = _StatusCode;
error = ERROR_SUCCESS;
} else {
error = ERROR_INSUFFICIENT_BUFFER;
}
} else {
//
// the number should always be only 3 characters long, but we'll be
// flexible (just in case)
//
char numBuf[sizeof("4294967296")];
requiredSize = wsprintf(numBuf, "%u", _StatusCode) + 1;
#ifdef DEBUG
// Debug check to make sure everything is good because the above
// used to be ultoa.
char debugBuf[sizeof("4294967296")];
ultoa(_StatusCode, debugBuf, 10);
if (strcmp(debugBuf,numBuf))
{
INET_ASSERT(FALSE);
}
INET_ASSERT(requiredSize == lstrlen(numBuf) + 1);
#endif
if (*lpdwBufferLength >= requiredSize) {
memcpy(lpBuffer, (LPVOID)numBuf, requiredSize);
*lpdwBufferLength = requiredSize - 1;
error = ERROR_SUCCESS;
} else {
*lpdwBufferLength = requiredSize;
error = ERROR_INSUFFICIENT_BUFFER;
}
}
PERF_LEAVE(QueryStatusCode);
return error;
}
DWORD
HTTP_REQUEST_HANDLE_OBJECT::QueryStatusText(
IN LPVOID lpBuffer,
IN OUT LPDWORD lpdwBufferLength
)
/*++
Routine Description:
Returns the status text - if any - returned by the server in the status line
Arguments:
lpBuffer - pointer to buffer where status text is written
lpdwBufferLength - IN: size of lpBuffer
OUT: length of the status text string minus 1 for the
'\0', or the required buffer length if we return
ERROR_INSUFFICIENT_BUFFER
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure - ERROR_INSUFFICIENT_BUFFER
--*/
{
PERF_ENTER(QueryStatusText);
DWORD error;
HEADER_STRING * statusLine = GetStatusLine();
if ((statusLine == NULL) || statusLine->IsError()) {
error = ERROR_WINHTTP_INTERNAL_ERROR;
goto quit;
}
LPSTR str;
DWORD len;
//
// find the third token on the status line. The status line has the form
//
// "HTTP/1.0 302 Try again\r\n"
//
// ^ ^ ^
// | | |
// | | +- status text
// | +- status code
// +- version
//
str = statusLine->StringAddress((LPSTR)_ResponseBuffer);
len = statusLine->StringLength();
DWORD i;
i = 0;
int j;
for (j = 0; j < 2; ++j) {
while ((i < len) && (str[i] != ' ')) {
++i;
}
while ((i < len) && (str[i] == ' ')) {
++i;
}
}
len -= i;
if (*lpdwBufferLength > len) {
memcpy(lpBuffer, (LPVOID)&str[i], len);
((LPSTR)lpBuffer)[len] = '\0';
*lpdwBufferLength = len;
error = ERROR_SUCCESS;
} else {
*lpdwBufferLength = len + 1;
error = ERROR_INSUFFICIENT_BUFFER;
}
quit:
PERF_LEAVE(QueryStatusText);
return error;
}
DWORD
HTTP_REQUEST_HANDLE_OBJECT::QueryRawResponseHeaders(
IN BOOL bCrLfTerminated,
OUT LPVOID lpBuffer,
IN OUT LPDWORD lpdwBufferLength
)
/*++
Routine Description:
Gets the raw response headers
Arguments:
bCrLfTerminated - TRUE if we want RAW_HEADERS_CRLF else RAW_HEADERS
lpBuffer - pointer to buffer where headers returned
lpdwBufferLength - IN: length of lpBuffer
OUT: returned length of lpBuffer
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure -
--*/
{
DEBUG_ENTER((DBG_HTTP,
Dword,
"QueryRawHeaders",
"%B, %#x, %#x [%d]",
bCrLfTerminated,
lpBuffer,
lpdwBufferLength,
*lpdwBufferLength
));
PERF_ENTER(QueryRawHeaders);
DWORD error = _ResponseHeaders.QueryRawHeaders(
(LPSTR)_ResponseBuffer,
bCrLfTerminated,
lpBuffer,
lpdwBufferLength
);
IF_DEBUG_CODE() {
if (error == ERROR_INSUFFICIENT_BUFFER) {
DEBUG_PRINT(HTTP,
INFO,
("*lpdwBufferLength = %d\n",
*lpdwBufferLength
));
}
}
PERF_LEAVE(QueryRawHeaders);
DEBUG_LEAVE(error);
return error;
}
VOID
HTTP_REQUEST_HANDLE_OBJECT::RemoveAllRequestHeadersByName(
IN DWORD dwQueryIndex
)
/*++
Routine Description:
Removes all headers of a particular type from the request object
Arguments:
lpszHeaderName - name of header to remove
Return Value:
None.
--*/
{
DEBUG_ENTER((DBG_HTTP,
None,
"RemoveAllRequestHeadersByName",
"%q, %u",
GlobalKnownHeaders[dwQueryIndex].Text,
dwQueryIndex
));
PERF_ENTER(RemoveAllRequestHeadersByName);
_RequestHeaders.RemoveAllByIndex(dwQueryIndex);
PERF_LEAVE(RemoveAllRequestHeadersByName);
DEBUG_LEAVE(0);
}
//
// private methods
//
PRIVATE
DWORD
HTTP_REQUEST_HANDLE_OBJECT::CheckWellKnownHeaders(
VOID
)
/*++
Routine Description:
Tests for a couple of well-known headers that are important to us as well as
the app:
"Connection: Keep-Alive"
"Proxy-Connection: Keep-Alive"
"Connection: Close"
"Proxy-Connection: Close"
"Transfer-Encoding: chunked"
"Content-Length: ####"
"Content-Range: bytes ####-####/####"
The header DOES NOT contain CR-LF. That is, dwHeaderLength will not include
any counts for line termination
We need to know if the server honoured a request for a keep-alive connection
so that we don't try to receive until we hit the end of the connection. The
server will keep it open.
We need to know the content length if we are talking over a persistent (keep
alive) connection.
If either header is found, we set the corresponding flag in the HTTP_HEADERS
object, and in the case of "Content-Length:" we parse out the length.
Arguments:
None.
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure - ERROR_NOT_ENOUGH_MEMORY
--*/
{
DEBUG_ENTER((DBG_HTTP,
None,
"HTTP_REQUEST_HANDLE_OBJECT::CheckWellKnownHeaders",
NULL
));
DWORD dwError = ERROR_SUCCESS;
//
// check for "Content-Length:"
//
if ( IsResponseHeaderPresent(HTTP_QUERY_CONTENT_LENGTH) )
{
HEADER_STRING * curHeader;
DWORD dwHeaderLength;
LPSTR lpszHeader;
DWORD iSlotContentLength = _ResponseHeaders._bKnownHeaders[HTTP_QUERY_CONTENT_LENGTH];
curHeader = _ResponseHeaders.GetSlot(iSlotContentLength);
lpszHeader = curHeader->StringAddress((LPSTR)_ResponseBuffer);
dwHeaderLength = curHeader->StringLength();
dwHeaderLength -= GlobalKnownHeaders[HTTP_QUERY_CONTENT_LENGTH].Length+1;
lpszHeader += GlobalKnownHeaders[HTTP_QUERY_CONTENT_LENGTH].Length+1;
while (dwHeaderLength && (*lpszHeader == ' ')) {
--dwHeaderLength;
++lpszHeader;
}
while (dwHeaderLength && isdigit(*lpszHeader)) {
_ContentLength = _ContentLength * 10 + (*lpszHeader - '0');
--dwHeaderLength;
++lpszHeader;
}
//
// once we have _ContentLength, we don't modify it (unless
// we fix it up when using a 206 partial response to resume
// a partial download.) The header value should be returned
// by HttpQueryInfo(). Instead, we keep account of the
// amount of keep-alive data left to copy in _BytesRemaining
//
_BytesRemaining = _ContentLength;
//
// although we said we may be one past the end of the header, in
// reality, if we received a buffer with "Content-Length:" then we
// expect it to be terminated by CR-LF (or CR-CR-LF or just LF,
// depending on the wackiness quotient of the server)
//
// MSXML3 bug 56001: commenting-out this assert; it's informational
// only and ignorable.
// INET_ASSERT((*lpszHeader == '\r') || (*lpszHeader == '\n'));
SetHaveContentLength(TRUE);
DEBUG_PRINT(HTTP,
INFO,
("_ContentLength = %d\n",
_ContentLength
));
_BytesInSocket = (_ContentLength != 0)
? (_ContentLength - (_BytesReceived - _DataOffset))
: 0;
//
// we could have multiple responses in the same buffer. If
// the amount received is greater than the content length
// then we have all the data; there are no bytes left in
// the socket for the current response
//
if ((int)_BytesInSocket < 0) {
_BytesInSocket = 0;
}
DEBUG_PRINT(HTTP,
INFO,
("bytes left in socket = %d\n",
_BytesInSocket
));
}
if ( IsResponseHeaderPresent(HTTP_QUERY_CONNECTION) ||
IsResponseHeaderPresent(HTTP_QUERY_PROXY_CONNECTION) )
{
//
// check for "Connection: Keep-Alive" or "Proxy-Connection: Keep-Alive".
// This test protects us against the unlikely
// event of a server returning to us a keep-alive response header (because
// that would cause problems for the proxy)
//
if (IsWantKeepAlive() && (!IsKeepAlive() || IsResponseHttp1_1()))
{
HEADER_STRING * curHeader;
DWORD dwHeaderLength, headerNameLength;
LPSTR lpszHeader;
DWORD iSlot;
char ch;
if (IsRequestUsingProxy() &&
IsResponseHeaderPresent(HTTP_QUERY_PROXY_CONNECTION))
{
iSlot = _ResponseHeaders._bKnownHeaders[HTTP_QUERY_PROXY_CONNECTION];
headerNameLength = GlobalKnownHeaders[HTTP_QUERY_PROXY_CONNECTION].Length+1;
}
else if (IsResponseHeaderPresent(HTTP_QUERY_CONNECTION))
{
iSlot = _ResponseHeaders._bKnownHeaders[HTTP_QUERY_CONNECTION];
headerNameLength = GlobalKnownHeaders[HTTP_QUERY_CONNECTION].Length+1;
}
else
{
iSlot = _ResponseHeaders._bKnownHeaders[HTTP_QUERY_PROXY_CONNECTION];
headerNameLength = GlobalKnownHeaders[HTTP_QUERY_PROXY_CONNECTION].Length+1;
INET_ASSERT(FALSE);
}
curHeader = _ResponseHeaders.GetSlot(iSlot);
lpszHeader = curHeader->StringAddress((LPSTR)_ResponseBuffer);
dwHeaderLength = curHeader->StringLength();
dwHeaderLength -= headerNameLength;
lpszHeader += headerNameLength;
while (dwHeaderLength && (*lpszHeader == ' ')) {
++lpszHeader;
--dwHeaderLength;
}
//
// both headers use "Keep-Alive" as header-value ONLY for HTTP 1.0 servers
//
if (((int)dwHeaderLength >= KEEP_ALIVE_LEN)
&& !strnicmp(lpszHeader, KEEP_ALIVE_SZ, KEEP_ALIVE_LEN)) {
DEBUG_PRINT(HTTP,
INFO,
("Connection: Keep-Alive\n"
));
//
// BUGBUG - we are setting k-a when coming from cache!
//
SetKeepAlive(TRUE);
SetPersistentConnection(headerNameLength == HTTP_PROXY_CONNECTION_LEN);
}
//
// also check for "Close" as header-value ONLY for HTTP 1.1 servers
//
else if ((*lpszHeader == 'C' || *lpszHeader == 'c')
&& ((int)dwHeaderLength >= CLOSE_LEN)
&& IsResponseHttp1_1()
&& !strnicmp(lpszHeader, CLOSE_SZ, CLOSE_LEN)) {
DEBUG_PRINT(HTTP,
INFO,
("Connection: Close (HTTP/1.1)\n"
));
SetConnCloseResponse(TRUE);
}
}
}
//
// check for "Transfer-Encoding:"
//
if (IsResponseHeaderPresent(HTTP_QUERY_TRANSFER_ENCODING) &&
IsResponseHttp1_1())
{
//
// If Http 1.1, check for Chunked Transfer
//
HEADER_STRING * curHeader;
DWORD dwHeaderLength;
LPSTR lpszHeader;
DWORD iSlot;
iSlot = _ResponseHeaders._bKnownHeaders[HTTP_QUERY_TRANSFER_ENCODING];
curHeader = _ResponseHeaders.GetSlot(iSlot);
lpszHeader = curHeader->StringAddress((LPSTR)_ResponseBuffer);
dwHeaderLength = curHeader->StringLength();
dwHeaderLength -= GlobalKnownHeaders[HTTP_QUERY_TRANSFER_ENCODING].Length+1;
lpszHeader += GlobalKnownHeaders[HTTP_QUERY_TRANSFER_ENCODING].Length+1;
while (dwHeaderLength && (*lpszHeader == ' ')) {
++lpszHeader;
--dwHeaderLength;
}
//
// look for "chunked" entry that confirms that we're doing chunked transfer encoding
//
if (((int)dwHeaderLength >= CHUNKED_LEN)
&& !strnicmp(lpszHeader, CHUNKED_SZ, CHUNKED_LEN))
{
INTERNET_HANDLE_OBJECT* pRoot = GetRootHandle(this);
DWORD_PTR dwChunkFilterCtx = 0;
// Now that we know this is a chunked response, allocate
// a decoder context for parsing the data later. If anything
// fails here, the request needs to fail.
if (S_OK != pRoot->_ChunkFilter.RegisterContext(&dwChunkFilterCtx))
{
dwError = ERROR_NOT_ENOUGH_MEMORY;
goto quit;
}
else if (!_ResponseFilterList.Insert(&pRoot->_ChunkFilter, dwChunkFilterCtx))
{
pRoot->_ChunkFilter.UnregisterContext(dwChunkFilterCtx);
dwError = ERROR_NOT_ENOUGH_MEMORY;
goto quit;
}
SetHaveChunkEncoding(TRUE);
DEBUG_PRINT(HTTP,
INFO,
("server is sending Chunked Transfer Encoding\n"
));
//
// if both "transfer-encoding: chunked" and "content-length:"
// were received then the chunking takes precedence
//
INET_ASSERT(!(IsChunkEncoding() && IsContentLength()));
if (IsContentLength()) {
SetHaveContentLength(FALSE);
}
}
}
SetBadNSServer(FALSE);
if (IsResponseHttp1_1())
{
//
// For IIS 4.0 Servers, and all other normal servers, if we make
// a HEAD request, we should ignore the Content-Length.
//
// IIS 3.0 servers send an illegal body, and this is a bug in the server.
// since they're not HTTP 1.1 we should be ok here.
//
if ( (GetMethodType() == HTTP_METHOD_TYPE_HEAD) &&
(_ContentLength > 0) &&
IsWantKeepAlive()
)
{
//
// set length to 0
//
_ContentLength = 0;
}
if ( IsRequestHttp1_1() )
{
//
// check for NS servers that don't return correct HTTP/1.1 responses
//
LPSTR buffer;
DWORD buflen;
DWORD status = FastQueryResponseHeader(HTTP_QUERY_SERVER,
(LPVOID*)&buffer,
&buflen,
0
);
#define NSEP "Netscape-Enterprise/3"
#define NSEPLEN (sizeof(NSEP) - 1)
#define NSFT "Netscape-FastTrack/3"
#define NSFTLEN (sizeof(NSFT) - 1)
#define NSCS "Netscape-Commerce/3"
#define NSCSLEN (sizeof(NSCS) - 1)
if (status == ERROR_SUCCESS) {
BOOL fIsBadServer = ((buflen > NSEPLEN) && !strnicmp(buffer, NSEP, NSEPLEN))
|| ((buflen > NSFTLEN) && !strnicmp(buffer, NSFT, NSFTLEN))
|| ((buflen > NSCSLEN) && !strnicmp(buffer, NSCS, NSCSLEN));
if ( fIsBadServer )
{
CServerInfo * pServerInfo = GetServerInfo();
SetBadNSServer(fIsBadServer);
if (pServerInfo != NULL)
{
//
// Note this Bad Server info in the server info obj,
// as we they fail to do keep-alive with SSL properly
//
pServerInfo->SetBadNSServer();
}
DEBUG_PRINT(HTTP,
INFO,
("IsBadNSServer() == %B\n",
IsBadNSServer()
));
}
}
}
//
// BUGBUG - content-type: multipart/byteranges means we
// also have data
//
DWORD statusCode = GetStatusCode();
if (!IsBadNSServer()
&& !IsContentLength()
&& !IsChunkEncoding()
&& (((statusCode >= HTTP_STATUS_CONTINUE) // 100
&& (statusCode < HTTP_STATUS_OK)) // 200
|| (statusCode == HTTP_STATUS_NO_CONTENT) // 204
|| (statusCode == HTTP_STATUS_MOVED) // 301
|| (statusCode == HTTP_STATUS_REDIRECT) // 302
|| (statusCode == HTTP_STATUS_REDIRECT_METHOD) // 303
|| (statusCode == HTTP_STATUS_NOT_MODIFIED) // 304
|| (statusCode == HTTP_STATUS_REDIRECT_KEEP_VERB)) // 307
|| (GetMethodType() == HTTP_METHOD_TYPE_HEAD)) {
DEBUG_PRINT(HTTP,
INFO,
("header-only HTTP/1.1 response\n"
));
SetData(FALSE);
}
}
quit:
DEBUG_LEAVE(dwError);
return dwError;
}
//
// this array has the same order as the HTTP_METHOD_TYPE enum
//
#define MAKE_REQUEST_METHOD_TYPE(Type) \
sizeof(# Type) - 1, # Type, HTTP_METHOD_TYPE_ ## Type
//
// darrenmi - need a new macro because *_M-POST isn't a valid enum member.
// we need a seperate enum type and string value.
//
// map HTTP_METHOD_TYPE_MPOST <=> "M-POST"
//
#define MAKE_REQUEST_METHOD_TYPE2(EnumType,Type) \
sizeof(# Type) - 1, # Type, HTTP_METHOD_TYPE_ ## EnumType
static const struct _REQUEST_METHOD {
int Length;
LPSTR Name;
HTTP_METHOD_TYPE MethodType;
} MethodNames[] = {
MAKE_REQUEST_METHOD_TYPE(GET),
MAKE_REQUEST_METHOD_TYPE(HEAD),
MAKE_REQUEST_METHOD_TYPE(POST),
MAKE_REQUEST_METHOD_TYPE(PUT),
MAKE_REQUEST_METHOD_TYPE(PROPFIND),
MAKE_REQUEST_METHOD_TYPE(PROPPATCH),
MAKE_REQUEST_METHOD_TYPE(LOCK),
MAKE_REQUEST_METHOD_TYPE(UNLOCK),
MAKE_REQUEST_METHOD_TYPE(COPY),
MAKE_REQUEST_METHOD_TYPE(MOVE),
MAKE_REQUEST_METHOD_TYPE(MKCOL),
MAKE_REQUEST_METHOD_TYPE(CONNECT),
MAKE_REQUEST_METHOD_TYPE(DELETE),
MAKE_REQUEST_METHOD_TYPE(LINK),
MAKE_REQUEST_METHOD_TYPE(UNLINK),
MAKE_REQUEST_METHOD_TYPE(BMOVE),
MAKE_REQUEST_METHOD_TYPE(BCOPY),
MAKE_REQUEST_METHOD_TYPE(BPROPFIND),
MAKE_REQUEST_METHOD_TYPE(BPROPPATCH),
MAKE_REQUEST_METHOD_TYPE(BDELETE),
MAKE_REQUEST_METHOD_TYPE(SUBSCRIBE),
MAKE_REQUEST_METHOD_TYPE(UNSUBSCRIBE),
MAKE_REQUEST_METHOD_TYPE(NOTIFY),
MAKE_REQUEST_METHOD_TYPE(POLL),
MAKE_REQUEST_METHOD_TYPE(CHECKIN),
MAKE_REQUEST_METHOD_TYPE(CHECKOUT),
MAKE_REQUEST_METHOD_TYPE(INVOKE),
MAKE_REQUEST_METHOD_TYPE(SEARCH),
MAKE_REQUEST_METHOD_TYPE(PIN),
MAKE_REQUEST_METHOD_TYPE2(MPOST,M-POST)
};
HTTP_METHOD_TYPE
MapHttpRequestMethod(
IN LPCSTR lpszVerb
)
/*++
Routine Description:
Maps request method string to type. Method names *are* case-sensitive
Arguments:
lpszVerb - method (verb) string
Return Value:
HTTP_METHOD_TYPE
--*/
{
int verbLen = strlen(lpszVerb);
for (int i = 0; i < ARRAY_ELEMENTS(MethodNames); ++i) {
if ((MethodNames[i].Length == verbLen)
&& (memcmp(lpszVerb, MethodNames[i].Name, verbLen) == 0)) {
return MethodNames[i].MethodType;
}
}
//
// we now hande HTTP_METHOD_TYPE_UNKNOWN
//
return HTTP_METHOD_TYPE_UNKNOWN;
}
DWORD
MapHttpMethodType(
IN HTTP_METHOD_TYPE tMethod,
OUT LPCSTR * lplpcszName
)
/*++
Routine Description:
Map a method type to the corresponding name and length
Arguments:
tMethod - to map
lplpcszName - pointer to pointer to returned name
Return Value:
DWORD
Success - length of method name
Failure - (DWORD)-1
--*/
{
DWORD length;
if ((tMethod >= HTTP_METHOD_TYPE_FIRST) && (tMethod <= HTTP_METHOD_TYPE_LAST)) {
*lplpcszName = MethodNames[tMethod].Name;
length = MethodNames[tMethod].Length;
} else {
length = (DWORD)-1;
}
return length;
}
#if INET_DEBUG
LPSTR
MapHttpMethodType(
IN HTTP_METHOD_TYPE tMethod
)
{
return (tMethod == HTTP_METHOD_TYPE_UNKNOWN)
? "Unknown"
: MethodNames[tMethod].Name;
}
#endif