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.
6333 lines
166 KiB
6333 lines
166 KiB
/*++
|
|
|
|
Copyright (c) 1995 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
headers.cxx
|
|
|
|
Abstract:
|
|
|
|
Methods for HTTP_HEADERS (..\inc\http.hxx) classes
|
|
|
|
Contents:
|
|
|
|
HEADER_STRING::CreateHash
|
|
HTTP_HEADERS::HeaderMatch
|
|
HTTP_HEADERS::AllocateHeaders
|
|
HTTP_HEADERS::FreeHeaders
|
|
HTTP_HEADERS::CopyHeaders
|
|
HTTP_HEADERS::FindFreeSlot
|
|
HTTP_HEADERS::AddHeader
|
|
HTTP_HEADERS::ReplaceHeader
|
|
HTTP_HEADERS::FindHeader
|
|
HTTP_HEADERS::QueryRawHeaders
|
|
HTTP_HEADERS::AddRequest
|
|
HTTP_HEADERS::ModifyRequest
|
|
HTTP_HEADERS::SetRequestVersion
|
|
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"
|
|
|
|
//
|
|
// private manifests
|
|
//
|
|
|
|
#define DATE_AND_TIME_STRING_BUFFER_LENGTH 128
|
|
|
|
#ifdef COMPRESSED_HEADERS
|
|
|
|
typedef struct tagHEADER_MAP {
|
|
|
|
LPSTR lpszLongHeader;
|
|
DWORD dwLenLongHeader;
|
|
LPSTR lpszShortHeader;
|
|
DWORD dwLenShortHeader;
|
|
|
|
}
|
|
HEADER_MAP;
|
|
|
|
|
|
// Sorted Compression map
|
|
|
|
HEADER_MAP rgsHeaderMap[] = {
|
|
{"", 0, "", 0 },
|
|
{"Accept", sizeof("Accept")-1, "#A", sizeof("#A")-1},
|
|
{"Accept-Charset", sizeof("Accept-Charset")-1, "#B", sizeof("#B")-1},
|
|
{"Accept-Encoding", sizeof("Accept-Encoding")-1, "#C", sizeof("#C")-1},
|
|
{"Accept-Language", sizeof("Accept-Language")-1, "#D", sizeof("#D")-1},
|
|
{"Accept-Ranges", sizeof("Accept-Ranges")-1, "#E", sizeof("#E")-1},
|
|
{"Age", sizeof("Age")-1, "#F", sizeof("#F")-1},
|
|
{"Allow", sizeof("Allow")-1, "#G", sizeof("#G")-1},
|
|
{"Authorization", sizeof("Authorization")-1, "#H", sizeof("#H")-1},
|
|
{"Cache-Control", sizeof("Cache-Control")-1, "#I", sizeof("#I")-1},
|
|
{"Connection", sizeof("Connection")-1, "#J", sizeof("#J")-1},
|
|
{"Content-Base", sizeof("Content-Base")-1, "#K", sizeof("#K")-1},
|
|
{"Content-Encoding", sizeof("Content-Encoding")-1, "#L", sizeof("#L")-1},
|
|
{"Content-Language", sizeof("Content-Language")-1, "#M", sizeof("#M")-1},
|
|
{"Content-Length", sizeof("Content-Length")-1, "#N", sizeof("#N")-1},
|
|
{"Content-Location", sizeof("Content-Location")-1, "#O", sizeof("#O")-1},
|
|
{"Content-MD5", sizeof("Content-MD5")-1, "#P", sizeof("#P")-1},
|
|
{"Content-Range", sizeof("Content-Range")-1, "#Q", sizeof("#Q")-1},
|
|
{"Content-Type", sizeof("Content-Type")-1, "#R", sizeof("#R")-1},
|
|
|
|
{"Cookie", sizeof("Cookie")-1, "#5", sizeof("#5")-1},
|
|
|
|
{"Date", sizeof("Date")-1, "#S", sizeof("#S")-1},
|
|
{"ETag", sizeof("ETag")-1, "#T", sizeof("#T")-1},
|
|
{"Expires", sizeof("Expires")-1, "#U", sizeof("#U")-1},
|
|
{"From", sizeof("From")-1, "#V", sizeof("#V")-1},
|
|
{"Host", sizeof("Host")-1, "#W", sizeof("#W")-1},
|
|
{"If-Modified-Since", sizeof("If-Modified-Since")-1, "#X", sizeof("#X")-1},
|
|
{"If-Match", sizeof("If-Match")-1, "#Y", sizeof("#Y")-1},
|
|
{"If-None-Match", sizeof("If-None-Match")-1, "#Z", sizeof("#Z")-1},
|
|
{"If-Range", sizeof("If-Range")-1, "#a", sizeof("#a")-1},
|
|
{"If-Unmodified-Since",sizeof("If-Unmodified-Since")-1, "#b", sizeof("#b")-1},
|
|
{"Last-Modified", sizeof("Last-Modified")-1, "#c", sizeof("#c")-1},
|
|
{"Location", sizeof("Location")-1, "#d", sizeof("#d")-1},
|
|
{"Max-Forwards", sizeof("Max-Forwards")-1, "#e", sizeof("#e")-1},
|
|
{"Pragma", sizeof("Pragma")-1, "#f", sizeof("#f")-1},
|
|
{"Proxy-Authenticate", sizeof("Proxy-Authenticate")-1, "#g", sizeof("#g")-1},
|
|
{"Proxy-Authorization",sizeof("Proxy-Authorization")-1, "#h", sizeof("#h")-1},
|
|
{"Public", sizeof("Public")-1, "#I", sizeof("#I")-1},
|
|
{"Range", sizeof("Range")-1, "#j", sizeof("#j")-1},
|
|
{"Referer", sizeof("Referer")-1, "#k", sizeof("#k")-1},
|
|
{"Retry-After", sizeof("Retry-After")-1, "#l", sizeof("#l")-1},
|
|
{"Server", sizeof("Server")-1, "#m", sizeof("#m")-1},
|
|
{"Transfer-Encoding", sizeof("Transfer-Encoding")-1, "#n", sizeof("#n")-1},
|
|
|
|
{"UA-color", sizeof("UA-color")-1, "#1", sizeof("#1")-1},
|
|
{"UA-cpu", sizeof("UA-cpu")-1, "#2", sizeof("#2")-1},
|
|
{"UA-OS", sizeof("UA-OS")-1, "#3", sizeof("#3")-1},
|
|
{"UA-pixels", sizeof("UA-pixels")-1, "#4", sizeof("#4")-1},
|
|
|
|
{"Upgrade", sizeof("Upgrade")-1, "#o", sizeof("#o")-1},
|
|
{"User-Agent", sizeof("User-Agent")-1, "#p", sizeof("#p")-1},
|
|
{"Vary", sizeof("Vary")-1, "#q", sizeof("#q")-1},
|
|
{"Via", sizeof("Via")-1, "#r", sizeof("#r")-1},
|
|
{"Warning", sizeof("Warning")-1, "#s", sizeof("#s")-1},
|
|
{"WWW-Authenticate", sizeof("WWW-Authenticate")-1, "#t", sizeof("#t")-1}
|
|
};
|
|
|
|
|
|
#endif //COMPRESSED_HEADERS
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Private functions
|
|
//
|
|
|
|
PRIVATE
|
|
BOOL
|
|
FMatchList(
|
|
LPSTR *lplpList,
|
|
DWORD cListLen,
|
|
HEADER_STRING *lpHeader,
|
|
LPSTR lpBase
|
|
);
|
|
|
|
|
|
|
|
|
|
//
|
|
// external functions
|
|
//
|
|
|
|
extern
|
|
BOOL
|
|
HttpDateToSystemTime(
|
|
IN LPSTR lpszHttpDate,
|
|
OUT LPSYSTEMTIME lpSystemTime
|
|
);
|
|
|
|
#ifdef COMPRESSED_HEADERS
|
|
DWORD
|
|
LookupHeadermap(
|
|
LPSTR lpszHeader
|
|
);
|
|
|
|
extern BOOL vfCompressedHeaders;
|
|
#endif //COMPRESSED_HEADERS
|
|
|
|
|
|
DWORD
|
|
FASTCALL
|
|
CalculateHashNoCase(
|
|
IN LPSTR lpszString,
|
|
IN DWORD dwStringLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Calculate a case-insensitive hash number given a string. Assumes input is
|
|
7-bit ASCII
|
|
|
|
Arguments:
|
|
|
|
lpszString - string to hash
|
|
|
|
dwStringLength - length of lpszString, or -1 if we need to calculate
|
|
|
|
Return Value:
|
|
|
|
DWORD - a generated hash value
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD dwHash = HEADER_HASH_SEED;
|
|
|
|
while (dwStringLength != 0) {
|
|
CHAR ch = *lpszString;
|
|
|
|
if ((ch >= 'A') && (ch <= 'Z')) {
|
|
ch = MAKE_LOWER(ch);
|
|
}
|
|
dwHash += (DWORD)(dwHash << 5) + ch; /*+ *pszName++;*/
|
|
|
|
++lpszString;
|
|
--dwStringLength;
|
|
}
|
|
return dwHash;
|
|
}
|
|
|
|
//
|
|
// methods
|
|
//
|
|
|
|
VOID
|
|
inline
|
|
HEADER_STRING::CreateHash(
|
|
LPSTR lpszBase
|
|
)
|
|
{
|
|
DWORD i = 0;
|
|
LPSTR string = StringAddress(lpszBase);
|
|
|
|
while ((i < (DWORD)StringLength())
|
|
&& !((string[i] == ':')
|
|
|| (string[i] == ' ')
|
|
|| (string[i] == '\r')
|
|
|| (string[i] == '\n'))) {
|
|
++i;
|
|
}
|
|
m_Hash = CalculateHashNoCase(string, i);
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
HTTP_HEADERS::AllocateHeaders(
|
|
IN DWORD dwNumberOfHeaders
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Allocates or grows the array of header pointers (HEADER_STRING objects)
|
|
|
|
Arguments:
|
|
|
|
dwNumberOfHeaders - number of additional header slots to create
|
|
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_NOT_ENOUGH_MEMORY
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
Dword,
|
|
"AllocateHeaders",
|
|
"%d",
|
|
dwNumberOfHeaders
|
|
));
|
|
|
|
PERF_ENTER(AllocateHeaders);
|
|
|
|
//
|
|
// we really need to be able to realloc an array of HEADER_STRING objects
|
|
// (see below)
|
|
//
|
|
|
|
DWORD error;
|
|
DWORD slots = _TotalSlots;
|
|
|
|
|
|
if ( (_TotalSlots + dwNumberOfHeaders) > (INVALID_HEADER_INDEX-1))
|
|
{
|
|
INET_ASSERT(FALSE);
|
|
_NextOpenSlot = 0;
|
|
_TotalSlots = 0;
|
|
_FreeSlots = 0;
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto quit;
|
|
}
|
|
|
|
|
|
_lpHeaders = (HEADER_STRING *)ResizeBuffer((HLOCAL)_lpHeaders,
|
|
(_TotalSlots + dwNumberOfHeaders)
|
|
* sizeof(HEADER_STRING),
|
|
FALSE // not moveable
|
|
);
|
|
if (_lpHeaders != NULL) {
|
|
_NextOpenSlot = _TotalSlots;
|
|
_TotalSlots += dwNumberOfHeaders;
|
|
_FreeSlots += dwNumberOfHeaders;
|
|
|
|
//
|
|
// this is slightly ugly, but it seems there's no easy C++ way to
|
|
// do this - we need to be able to realloc() an array of objects
|
|
// created by new(), but so far, it can't be done
|
|
//
|
|
|
|
for (; slots < _TotalSlots; ++slots) {
|
|
_lpHeaders[slots].Clear();
|
|
}
|
|
error = ERROR_SUCCESS;
|
|
} else {
|
|
|
|
INET_ASSERT(FALSE);
|
|
_NextOpenSlot = 0;
|
|
_TotalSlots = 0;
|
|
_FreeSlots = 0;
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
quit:
|
|
|
|
INET_ASSERT(_FreeSlots <= _TotalSlots);
|
|
|
|
PERF_LEAVE(AllocateHeaders);
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
VOID
|
|
HTTP_HEADERS::FreeHeaders(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Free the headers strings and the headers array
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
None,
|
|
"FreeHeaders",
|
|
NULL
|
|
));
|
|
|
|
LockHeaders();
|
|
|
|
//
|
|
// free up each individual entry (free string buffers)
|
|
//
|
|
|
|
for (DWORD i = 0; i < _TotalSlots; ++i) {
|
|
_lpHeaders[i] = (LPSTR)NULL;
|
|
}
|
|
|
|
//
|
|
// followed by the array itself
|
|
//
|
|
|
|
if (_lpHeaders) {
|
|
_lpHeaders = (HEADER_STRING *)FREE_MEMORY((HLOCAL)_lpHeaders);
|
|
|
|
INET_ASSERT(_lpHeaders == NULL);
|
|
}
|
|
|
|
_TotalSlots = 0;
|
|
_FreeSlots = 0;
|
|
_HeadersLength = 0;
|
|
_lpszVerb = NULL;
|
|
_dwVerbLength = 0;
|
|
_lpszObjectName = NULL;
|
|
_dwObjectNameLength = 0;
|
|
_lpszVersion = NULL;
|
|
_dwVersionLength = 0;
|
|
|
|
UnlockHeaders();
|
|
|
|
DEBUG_LEAVE(0);
|
|
}
|
|
|
|
|
|
VOID
|
|
HTTP_HEADERS::CopyHeaders(
|
|
IN OUT LPSTR * lpBuffer,
|
|
IN LPSTR lpszObjectName,
|
|
IN DWORD dwObjectNameLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Copy the headers to the caller's buffer. Each header is terminated by CR-LF.
|
|
This method is called to convert the request headers list to a buffer that
|
|
we can send to the server
|
|
|
|
N.B. This function MUST be called with the headers already locked
|
|
|
|
Arguments:
|
|
|
|
lpBuffer - pointer to pointer to buffer where headers are
|
|
written. We update the pointer
|
|
|
|
lpszObjectName - optional object name
|
|
|
|
dwObjectNameLength - optional object name length
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
None,
|
|
"CopyHeaders",
|
|
"%#x, %#x [%q], %d",
|
|
lpBuffer,
|
|
lpszObjectName,
|
|
lpszObjectName,
|
|
dwObjectNameLength
|
|
));
|
|
|
|
LockHeaders();
|
|
|
|
DWORD i = 0;
|
|
|
|
if (lpszObjectName != NULL) {
|
|
memcpy(*lpBuffer, _lpszVerb, _dwVerbLength);
|
|
*lpBuffer += _dwVerbLength;
|
|
*(*lpBuffer)++ = ' ';
|
|
memcpy(*lpBuffer, lpszObjectName, dwObjectNameLength);
|
|
*lpBuffer += dwObjectNameLength;
|
|
*(*lpBuffer)++ = ' ';
|
|
memcpy(*lpBuffer, _lpszVersion, _dwVersionLength);
|
|
*lpBuffer += _dwVersionLength;
|
|
*(*lpBuffer)++ = '\r';
|
|
*(*lpBuffer)++ = '\n';
|
|
i = 1;
|
|
}
|
|
for (; i < _TotalSlots; ++i) {
|
|
if (_lpHeaders[i].HaveString()) {
|
|
_lpHeaders[i].CopyTo(*lpBuffer);
|
|
*lpBuffer += _lpHeaders[i].StringLength();
|
|
*(*lpBuffer)++ = '\r';
|
|
*(*lpBuffer)++ = '\n';
|
|
}
|
|
}
|
|
|
|
UnlockHeaders();
|
|
|
|
DEBUG_LEAVE(0);
|
|
}
|
|
|
|
#ifdef COMPRESSED_HEADERS
|
|
|
|
VOID
|
|
HTTP_HEADERS::CopyCompressedHeaders(
|
|
IN OUT LPSTR * lpBuffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Copy the headers to the caller's buffer. Each header is terminated by CR-LF.
|
|
This method is called to convert the request headers list to a buffer that
|
|
we can send to the server
|
|
|
|
N.B. This function MUST be called with the headers already locked
|
|
|
|
Arguments:
|
|
|
|
lpBuffer - pointer to pointer to buffer where headers are written. We
|
|
update the pointer
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
None,
|
|
"CopyCompressedHeaders",
|
|
"%#x",
|
|
lpBuffer
|
|
));
|
|
LPSTR lpszHeaderString;
|
|
DWORD j;
|
|
LockHeaders();
|
|
|
|
for (DWORD i = 0; i < _TotalSlots; ++i) {
|
|
if (_lpHeaders[i].HaveString()) {
|
|
|
|
lpszHeaderString = _lpHeaders[i].StringAddress(NULL);
|
|
|
|
j = LookupHeadermap(lpszHeaderString);
|
|
|
|
if (j) {
|
|
// copy the corresponding short header and bump the pointer
|
|
// ahead
|
|
|
|
|
|
DEBUG_PRINT(
|
|
HTTP,
|
|
INFO,
|
|
("Compressed:%s using %s\n",
|
|
lpszHeaderString,
|
|
rgsHeaderMap[j].lpszShortHeader
|
|
)
|
|
);
|
|
|
|
strcpy(*lpBuffer, rgsHeaderMap[j].lpszShortHeader);
|
|
|
|
*lpBuffer += rgsHeaderMap[j].dwLenShortHeader;
|
|
|
|
// then copy the value of the header
|
|
// and bump the destination further by the right amount
|
|
strncpy(*lpBuffer,
|
|
lpszHeaderString + rgsHeaderMap[j].dwLenLongHeader,
|
|
_lpHeaders[i].StringLength() - rgsHeaderMap[j].dwLenLongHeader
|
|
);
|
|
|
|
*lpBuffer += (_lpHeaders[i].StringLength() - rgsHeaderMap[j].dwLenLongHeader);
|
|
|
|
}
|
|
else {
|
|
|
|
// No match found, must be a header we don't know about
|
|
DEBUG_PRINT(
|
|
HTTP,
|
|
INFO,
|
|
("Couldn't compress header for %s\n",
|
|
lpszHeaderString
|
|
)
|
|
);
|
|
|
|
_lpHeaders[i].CopyTo(*lpBuffer);
|
|
*lpBuffer += _lpHeaders[i].StringLength();
|
|
|
|
}
|
|
|
|
*(*lpBuffer)++ = '\r';
|
|
*(*lpBuffer)++ = '\n';
|
|
|
|
}
|
|
}
|
|
|
|
UnlockHeaders();
|
|
|
|
DEBUG_LEAVE(0);
|
|
}
|
|
#endif //COMPRESSED_HEADERS
|
|
|
|
|
|
HEADER_STRING *
|
|
FASTCALL
|
|
HTTP_HEADERS::FindFreeSlot(
|
|
DWORD* piSlot
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Finds the next free slot in the headers list, or adds some new slots
|
|
|
|
N.B. This function MUST be called with the headers already locked
|
|
|
|
Arguments:
|
|
|
|
piSlot: returns index of slot found
|
|
|
|
Return Value:
|
|
|
|
HEADER_STRING * - pointer to next free slot
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
Pointer,
|
|
"FindFreeSlot",
|
|
NULL
|
|
));
|
|
|
|
PERF_ENTER(FindFreeSlot);
|
|
|
|
DWORD i;
|
|
DWORD error;
|
|
HEADER_STRING * header = NULL;
|
|
|
|
//
|
|
// if there are no free slots, allocate some more
|
|
//
|
|
|
|
if (_FreeSlots == 0) {
|
|
i = _TotalSlots;
|
|
error = AllocateHeaders(HEADERS_INCREMENT);
|
|
} else {
|
|
i = 0;
|
|
error = ERROR_SUCCESS;
|
|
if (!_lpHeaders[_NextOpenSlot].HaveString())
|
|
{
|
|
--_FreeSlots;
|
|
header = &_lpHeaders[_NextOpenSlot];
|
|
*piSlot = _NextOpenSlot;
|
|
_NextOpenSlot = (_NextOpenSlot == (_TotalSlots-1)) ? (_TotalSlots-1) : _NextOpenSlot++;
|
|
goto quit;
|
|
}
|
|
}
|
|
if (error == ERROR_SUCCESS) {
|
|
for (; i < _TotalSlots; ++i) {
|
|
if (!_lpHeaders[i].HaveString()) {
|
|
--_FreeSlots;
|
|
header = &_lpHeaders[i];
|
|
*piSlot = i;
|
|
_NextOpenSlot = (i == (_TotalSlots-1)) ? (_TotalSlots-1) : (i+1);
|
|
break;
|
|
}
|
|
}
|
|
if (header == NULL) {
|
|
|
|
//
|
|
// we would have just allocated extra slots if we didn't have
|
|
// any, so we shouldn't be here
|
|
//
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
quit:
|
|
_Error = error;
|
|
|
|
PERF_LEAVE(FindFreeSlot);
|
|
|
|
DEBUG_LEAVE(header);
|
|
|
|
return header;
|
|
}
|
|
|
|
|
|
VOID
|
|
HTTP_HEADERS::ShrinkHeader(
|
|
IN LPBYTE pbBase,
|
|
IN DWORD iSlot,
|
|
IN DWORD dwOldQueryIndex,
|
|
IN DWORD dwNewQueryIndex,
|
|
IN DWORD cbNewSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Low level function that does a surgical replace of one header with another.
|
|
This code updates internal structures such as bKnownHeaders and the stored
|
|
hash value for the new Header.
|
|
|
|
N.B. This function MUST be called with the headers already locked
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
HEADER_STRING* pHeader = _lpHeaders + iSlot;
|
|
|
|
INET_ASSERT(_bKnownHeaders[dwOldQueryIndex] == (BYTE) iSlot ||
|
|
dwNewQueryIndex == dwOldQueryIndex );
|
|
|
|
//
|
|
// Swap in the new header. Update Length, Hash, and its cached position
|
|
// in the known header array.
|
|
//
|
|
|
|
_bKnownHeaders[dwOldQueryIndex] = INVALID_HEADER_INDEX;
|
|
_bKnownHeaders[dwNewQueryIndex] = (BYTE) iSlot;
|
|
|
|
pHeader->SetLength (cbNewSize);
|
|
pHeader->SetHash (GlobalKnownHeaders[dwNewQueryIndex].HashVal);
|
|
}
|
|
|
|
DWORD
|
|
inline
|
|
HTTP_HEADERS::SlowFind(
|
|
IN LPSTR lpBase,
|
|
IN LPSTR lpszHeaderName,
|
|
IN DWORD dwHeaderNameLength,
|
|
IN DWORD dwIndex,
|
|
IN DWORD dwHash,
|
|
OUT DWORD *lpdwQueryIndex,
|
|
OUT BYTE **lplpbPrevIndex
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Finds the next occurance of lpszHeaderName in the header array, uses
|
|
a cached table of well known headers to accerlate the search if the
|
|
string is a known header.
|
|
|
|
N.B. This function MUST be called with the headers already locked
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
DWORD - index to Slot in array, or INVALID_HEADER_SLOT if not found
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
//
|
|
// Now see if this is a known header passed in as a string,
|
|
// If it is, we save ourselves the loop, and just map it right in to a known header
|
|
//
|
|
|
|
DWORD dwKnownQueryIndex = GlobalHeaderHashs[(dwHash % MAX_HEADER_HASH_SIZE)];
|
|
|
|
*lpdwQueryIndex = INVALID_HEADER_SLOT;
|
|
|
|
if ( dwKnownQueryIndex != 0 )
|
|
{
|
|
dwKnownQueryIndex--;
|
|
|
|
if ( ((int)dwHeaderNameLength >= GlobalKnownHeaders[dwKnownQueryIndex].Length) &&
|
|
strnicmp(lpszHeaderName,
|
|
GlobalKnownHeaders[dwKnownQueryIndex].Text,
|
|
GlobalKnownHeaders[dwKnownQueryIndex].Length) == 0)
|
|
{
|
|
*lpdwQueryIndex = dwKnownQueryIndex;
|
|
|
|
INET_ASSERT((int)(dwHeaderNameLength) == GlobalKnownHeaders[dwKnownQueryIndex].Length);
|
|
|
|
if ( lplpbPrevIndex )
|
|
{
|
|
return FastNukeFind(
|
|
dwKnownQueryIndex,
|
|
dwIndex,
|
|
lplpbPrevIndex
|
|
);
|
|
}
|
|
else
|
|
{
|
|
return FastFind(
|
|
dwKnownQueryIndex,
|
|
dwIndex
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Otherwise we painfully enumerate the whole array of headers
|
|
//
|
|
|
|
for (DWORD i = 0; i < _TotalSlots; ++i)
|
|
{
|
|
HEADER_STRING * pString;
|
|
|
|
pString = &_lpHeaders[i];
|
|
|
|
if (!pString->HaveString()) {
|
|
continue;
|
|
}
|
|
|
|
if (pString->HashStrnicmp(lpBase,
|
|
lpszHeaderName,
|
|
dwHeaderNameLength,
|
|
dwHash) == 0)
|
|
{
|
|
|
|
//
|
|
// if we haven't reached the required index yet, continue
|
|
//
|
|
|
|
if (dwIndex != 0) {
|
|
--dwIndex;
|
|
continue;
|
|
}
|
|
|
|
return i; // found index/slot
|
|
}
|
|
}
|
|
|
|
return INVALID_HEADER_SLOT; // not found
|
|
}
|
|
|
|
|
|
DWORD
|
|
inline
|
|
HTTP_HEADERS::FastFind(
|
|
IN DWORD dwQueryIndex,
|
|
IN DWORD dwIndex
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Finds the next occurance of a known header string in the lpHeaders array.
|
|
Since this is a known string, an index is used to refer to it.
|
|
A cached table of well known headers is used to accerlate the search.
|
|
|
|
N.B. This function MUST be called with the headers already locked
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
DWORD - index to Slot in array, or INVALID_HEADER_SLOT if not found
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD dwSlot;
|
|
|
|
dwSlot = _bKnownHeaders[dwQueryIndex];
|
|
|
|
while ( (dwIndex > 0) && (dwSlot < INVALID_HEADER_INDEX) )
|
|
{
|
|
HEADER_STRING * pString;
|
|
|
|
pString = &_lpHeaders[dwSlot];
|
|
dwSlot = pString->GetNextKnownIndex();
|
|
|
|
dwIndex--;
|
|
}
|
|
|
|
if ( dwSlot >= INVALID_HEADER_INDEX)
|
|
{
|
|
return INVALID_HEADER_SLOT;
|
|
}
|
|
|
|
return dwSlot; // found it.
|
|
}
|
|
|
|
|
|
DWORD
|
|
inline
|
|
HTTP_HEADERS::FastNukeFind(
|
|
IN DWORD dwQueryIndex,
|
|
IN DWORD dwIndex,
|
|
OUT BYTE **lplpbPrevIndex
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Finds the next occurance of a known header string in the lpHeaders array.
|
|
Since this is a known string, an index is used to refer to it.
|
|
A cached table of well known headers is used to accerlate the search.
|
|
Also provides a ptr to ptr to the slot which directs us to the one found.
|
|
This is needed for deletion purposes.
|
|
|
|
N.B. This function MUST be called with the headers already locked
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
DWORD - index to Slot in array, or INVALID_HEADER_SLOT if not found
|
|
|
|
--*/
|
|
|
|
{
|
|
BYTE *lpbSlot;
|
|
|
|
*lplpbPrevIndex = lpbSlot = &_bKnownHeaders[dwQueryIndex];
|
|
dwIndex++;
|
|
|
|
while ( (dwIndex > 0) && (*lpbSlot < INVALID_HEADER_INDEX) )
|
|
{
|
|
HEADER_STRING * pString;
|
|
|
|
pString = &_lpHeaders[*lpbSlot];
|
|
*lplpbPrevIndex = lpbSlot;
|
|
lpbSlot = pString->GetNextKnownIndexPtr();
|
|
|
|
dwIndex--;
|
|
}
|
|
|
|
if ( **lplpbPrevIndex >= INVALID_HEADER_INDEX ||
|
|
dwIndex > 0 )
|
|
{
|
|
return INVALID_HEADER_SLOT;
|
|
}
|
|
|
|
return ((DWORD) **lplpbPrevIndex); // found it.
|
|
}
|
|
|
|
VOID
|
|
HTTP_HEADERS::RemoveAllByIndex(
|
|
IN DWORD dwQueryIndex
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Removes all Known Headers found in the header array.
|
|
|
|
N.B. This function MUST be called with the headers already locked
|
|
|
|
Arguments:
|
|
|
|
dwQueryIndex - index to known header string to remove from array.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
BYTE bSlot;
|
|
BYTE bPrevSlot;
|
|
|
|
bSlot = bPrevSlot = _bKnownHeaders[dwQueryIndex];
|
|
|
|
while (bSlot < INVALID_HEADER_INDEX)
|
|
{
|
|
HEADER_STRING * pString;
|
|
|
|
bPrevSlot = bSlot;
|
|
pString = &_lpHeaders[bSlot];
|
|
bSlot = (BYTE) pString->GetNextKnownIndex();
|
|
|
|
RemoveHeader(bPrevSlot, dwQueryIndex, &_bKnownHeaders[dwQueryIndex]);
|
|
}
|
|
|
|
_bKnownHeaders[dwQueryIndex] = INVALID_HEADER_INDEX;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
BOOL
|
|
inline
|
|
HTTP_HEADERS::HeaderMatch(
|
|
IN DWORD dwHash,
|
|
IN LPSTR lpszHeaderName,
|
|
IN DWORD dwHeaderNameLength,
|
|
OUT DWORD *lpdwQueryIndex
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Looks up a Known HTTP header string using its Hash value and
|
|
string contained the name of the header.
|
|
|
|
Arguments:
|
|
|
|
dwHash - Hash value of header name string
|
|
|
|
lpszHeaderName - name of header we are matching
|
|
|
|
dwHeaderNameLength - length of header name string
|
|
|
|
lpdwQueryIndex - If found, this is the HTTP_QUERY_* based index to the header.
|
|
|
|
Return Value:
|
|
|
|
BOOL
|
|
Success - The string and hash matched againsted a known header
|
|
|
|
Failure - There is no known header for that hash & string pair.
|
|
|
|
--*/
|
|
|
|
{
|
|
*lpdwQueryIndex = GlobalHeaderHashs[(dwHash % MAX_HEADER_HASH_SIZE)];
|
|
|
|
if ( *lpdwQueryIndex != 0 )
|
|
{
|
|
(*lpdwQueryIndex)--;
|
|
|
|
if ( ((int)dwHeaderNameLength == GlobalKnownHeaders[*lpdwQueryIndex].Length) &&
|
|
strnicmp(lpszHeaderName,
|
|
GlobalKnownHeaders[*lpdwQueryIndex].Text,
|
|
GlobalKnownHeaders[*lpdwQueryIndex].Length) == 0)
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BYTE
|
|
inline
|
|
HTTP_HEADERS::FastAdd(
|
|
IN DWORD dwQueryIndex,
|
|
IN DWORD dwSlot
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Rapidly adds a known string to the header array, this function
|
|
is used to matain coherency of the _bKnownHeaders which
|
|
contained indexed offsets into the header array for known headers.
|
|
|
|
Note that this function is used instead of latter listed below
|
|
in order to maintain proper order in headers received.
|
|
|
|
N.B. This function MUST be called with the headers already locked
|
|
|
|
Arguments:
|
|
|
|
dwQueryIndex - index to known header string to remove from array.
|
|
|
|
dwSlot - Slot in which this header is being added.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
BYTE *lpbSlot;
|
|
|
|
lpbSlot = &_bKnownHeaders[dwQueryIndex];
|
|
|
|
while ( (*lpbSlot < INVALID_HEADER_INDEX) )
|
|
{
|
|
HEADER_STRING * pString;
|
|
|
|
pString = &_lpHeaders[*lpbSlot];
|
|
lpbSlot = pString->GetNextKnownIndexPtr();
|
|
}
|
|
|
|
INET_ASSERT(*lpbSlot == INVALID_HEADER_INDEX);
|
|
|
|
*lpbSlot = (BYTE) dwSlot;
|
|
return INVALID_HEADER_INDEX;
|
|
}
|
|
|
|
|
|
//BYTE
|
|
//inline
|
|
//HTTP_HEADERS::FastAdd(
|
|
// IN DWORD dwQueryIndex,
|
|
// IN DWORD dwSlot
|
|
// )
|
|
//{
|
|
// BYTE bOldSlot;
|
|
//
|
|
// bOldSlot = _bKnownHeaders[dwQueryIndex];
|
|
// _bKnownHeaders[dwQueryIndex] = (BYTE) dwSlot;
|
|
//
|
|
// return bOldSlot;
|
|
//}
|
|
|
|
|
|
|
|
|
|
DWORD
|
|
HTTP_HEADERS::AddHeader(
|
|
IN LPSTR lpszHeaderName,
|
|
IN DWORD dwHeaderNameLength,
|
|
IN LPSTR lpszHeaderValue,
|
|
IN DWORD dwHeaderValueLength,
|
|
IN DWORD dwIndex,
|
|
IN DWORD dwFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Adds a single header to the array of headers, given the header name and
|
|
value. Called via HttpOpenRequest()
|
|
|
|
Arguments:
|
|
|
|
lpszHeaderName - pointer to name of header to add, e.g. "Accept:"
|
|
|
|
dwHeaderNameLength - length of the header name
|
|
|
|
lpszHeaderValue - pointer to value of header to add, e.g. "text/html"
|
|
|
|
dwHeaderValueLength - length of the header value
|
|
|
|
dwIndex - if coalescing headers, index of header to update
|
|
|
|
dwFlags - flags controlling function:
|
|
|
|
COALESCE_HEADER_WITH_COMMA
|
|
COALESCE_HEADER_WITH_SEMICOLON
|
|
- headers of the same name can be combined
|
|
|
|
CLEAN_HEADER
|
|
- header is supplied by user, so we must ensure
|
|
it has correct format
|
|
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_NOT_ENOUGH_MEMORY
|
|
Ran out of memory allocating string
|
|
|
|
ERROR_INVALID_PARAMETER
|
|
The header value was bad
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
Dword,
|
|
"AddHeader",
|
|
"%.*q, %d, %.*q, %d, %d, %#x",
|
|
min(dwHeaderNameLength + 1, 80),
|
|
lpszHeaderName,
|
|
dwHeaderNameLength,
|
|
min(dwHeaderValueLength + 1, 80),
|
|
lpszHeaderValue,
|
|
dwHeaderValueLength,
|
|
dwIndex,
|
|
dwFlags
|
|
));
|
|
|
|
PERF_ENTER(AddHeader);
|
|
|
|
LockHeaders();
|
|
|
|
INET_ASSERT(lpszHeaderName != NULL);
|
|
INET_ASSERT(*lpszHeaderName != '\0');
|
|
INET_ASSERT(dwHeaderNameLength != 0);
|
|
INET_ASSERT(lpszHeaderValue != NULL);
|
|
INET_ASSERT(*lpszHeaderValue != '\0');
|
|
INET_ASSERT(dwHeaderValueLength != 0);
|
|
INET_ASSERT(_FreeSlots <= _TotalSlots);
|
|
|
|
//
|
|
// we may have been handed a header with a trailing colon. We don't care
|
|
// for such nasty imagery
|
|
//
|
|
|
|
if (lpszHeaderName[dwHeaderNameLength - 1] == ':') {
|
|
--dwHeaderNameLength;
|
|
}
|
|
|
|
DWORD error = ERROR_HTTP_HEADER_NOT_FOUND;
|
|
DWORD dwQueryIndex;
|
|
DWORD dwHash = CalculateHashNoCase(lpszHeaderName, dwHeaderNameLength);
|
|
|
|
DWORD i = 0;
|
|
|
|
//
|
|
// if we are coalescing headers then find a header with the same name
|
|
//
|
|
|
|
if ((dwFlags & COALESCE_HEADER_WITH_COMMA) ||
|
|
(dwFlags & COALESCE_HEADER_WITH_SEMICOLON) )
|
|
{
|
|
DWORD dwSlot;
|
|
|
|
dwSlot = SlowFind(
|
|
NULL,
|
|
lpszHeaderName,
|
|
dwHeaderNameLength,
|
|
dwIndex,
|
|
dwHash,
|
|
&dwQueryIndex,
|
|
NULL
|
|
);
|
|
|
|
if (dwSlot != ((DWORD) -1))
|
|
{
|
|
|
|
HEADER_STRING * pString;
|
|
|
|
pString = &_lpHeaders[dwSlot];
|
|
|
|
//
|
|
// found what we are looking for. Coalesce it
|
|
//
|
|
|
|
pString->ResizeString((sizeof("; ")-1) + dwHeaderValueLength); // save us from multiple reallocs
|
|
|
|
pString->Strncat(
|
|
(dwFlags & COALESCE_HEADER_WITH_SEMICOLON) ?
|
|
"; " :
|
|
", ",
|
|
2);
|
|
|
|
pString->Strncat(lpszHeaderValue, dwHeaderValueLength);
|
|
_HeadersLength += 2 + dwHeaderValueLength;
|
|
error = ERROR_SUCCESS;
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
//
|
|
// Check to verify that the header we're adding is a known header,
|
|
// If its a known header we use dwQueryIndex to update the known header array
|
|
// otherwise, IF ITS NOT, we make sure to set dwQueryIndex to INVALID_...
|
|
//
|
|
|
|
if (! HeaderMatch(dwHash, lpszHeaderName, dwHeaderNameLength, &dwQueryIndex) )
|
|
{
|
|
dwQueryIndex = INVALID_HEADER_SLOT;
|
|
}
|
|
|
|
/*
|
|
// Perhaps this more efficent ???
|
|
dwQueryIndex = GlobalHeaderHashs[(dwHash % MAX_HEADER_HASH_SIZE)];
|
|
|
|
if ( dwQueryIndex != 0 )
|
|
{
|
|
dwQueryIndex--;
|
|
|
|
if ( ((int)dwHeaderNameLength < GlobalKnownHeaders[dwQueryIndex].Length) ||
|
|
strnicmp(lpszHeaderName,
|
|
GlobalKnownHeaders[dwQueryIndex].Text,
|
|
GlobalKnownHeaders[dwQueryIndex].Length) != 0)
|
|
{
|
|
dwQueryIndex = INVALID_HEADER_SLOT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dwQueryIndex = INVALID_HEADER_SLOT;
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
//
|
|
// if we didn't find the header value or we are not coalescing then add the
|
|
// header
|
|
//
|
|
|
|
if (error == ERROR_HTTP_HEADER_NOT_FOUND)
|
|
{
|
|
//
|
|
// find the next slot for this header
|
|
//
|
|
|
|
HEADER_STRING * freeHeader;
|
|
DWORD iSlot;
|
|
|
|
freeHeader = FindFreeSlot(&iSlot);
|
|
if (freeHeader == NULL) {
|
|
error = GetError();
|
|
|
|
INET_ASSERT(error != ERROR_SUCCESS);
|
|
|
|
goto quit;
|
|
}
|
|
|
|
|
|
freeHeader->CreateStringBuffer((LPVOID)lpszHeaderName,
|
|
dwHeaderNameLength,
|
|
dwHeaderNameLength
|
|
+ sizeof(": ") - 1
|
|
+ dwHeaderValueLength
|
|
+ 1 // for extra NULL terminator
|
|
);
|
|
if (freeHeader->IsError()) {
|
|
error = ::GetLastError();
|
|
|
|
INET_ASSERT(error != ERROR_SUCCESS);
|
|
|
|
goto quit;
|
|
}
|
|
freeHeader->Strncat((LPVOID)": ", sizeof(": ") - 1);
|
|
freeHeader->Strncat((LPVOID)lpszHeaderValue, dwHeaderValueLength);
|
|
_HeadersLength += dwHeaderNameLength
|
|
+ (sizeof(": ") - 1)
|
|
+ dwHeaderValueLength
|
|
+ (sizeof("\r\n") - 1)
|
|
;
|
|
freeHeader->SetHash(dwHash);
|
|
|
|
if ( dwQueryIndex != INVALID_HEADER_SLOT )
|
|
{
|
|
freeHeader->SetNextKnownIndex(FastAdd(dwQueryIndex, iSlot));
|
|
}
|
|
|
|
error = ERROR_SUCCESS;
|
|
}
|
|
|
|
quit:
|
|
|
|
UnlockHeaders();
|
|
|
|
PERF_LEAVE(AddHeader);
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
HTTP_HEADERS::AddHeader(
|
|
IN DWORD dwQueryIndex,
|
|
IN LPSTR lpszHeaderValue,
|
|
IN DWORD dwHeaderValueLength,
|
|
IN DWORD dwIndex,
|
|
IN DWORD dwFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Adds a single header to the array of headers, given the header name and
|
|
value. Called via HttpOpenRequest()
|
|
|
|
Arguments:
|
|
|
|
dwQueryIndex - a index into a array of known HTTP headers, see wininet.h HTTP_QUERY_* codes
|
|
|
|
lpszHeaderValue - pointer to value of header to add, e.g. "text/html"
|
|
|
|
dwHeaderValueLength - length of the header value
|
|
|
|
dwIndex - if coalescing headers, index of header to update
|
|
|
|
dwFlags - flags controlling function:
|
|
|
|
COALESCE_HEADER_WITH_COMMA
|
|
COALESCE_HEADER_WITH_SEMICOLON
|
|
- headers of the same name can be combined
|
|
|
|
CLEAN_HEADER
|
|
- header is supplied by user, so we must ensure
|
|
it has correct format
|
|
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_NOT_ENOUGH_MEMORY
|
|
Ran out of memory allocating string
|
|
|
|
ERROR_INVALID_PARAMETER
|
|
The header value was bad
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
Dword,
|
|
"AddHeader",
|
|
"%q, %u, %.*q, %d, %d, %#x",
|
|
GlobalKnownHeaders[dwQueryIndex].Text,
|
|
dwQueryIndex,
|
|
min(dwHeaderValueLength + 1, 80),
|
|
lpszHeaderValue,
|
|
dwHeaderValueLength,
|
|
dwIndex,
|
|
dwFlags
|
|
));
|
|
|
|
PERF_ENTER(AddHeader);
|
|
|
|
INET_ASSERT(dwQueryIndex <= HTTP_QUERY_MAX);
|
|
INET_ASSERT(lpszHeaderValue != NULL);
|
|
INET_ASSERT(*lpszHeaderValue != '\0');
|
|
INET_ASSERT(dwHeaderValueLength != 0);
|
|
INET_ASSERT(_FreeSlots <= _TotalSlots);
|
|
|
|
DWORD error = ERROR_HTTP_HEADER_NOT_FOUND;
|
|
DWORD i = 0;
|
|
LPSTR lpszHeaderName;
|
|
DWORD dwHeaderNameLength;
|
|
DWORD dwHash;
|
|
|
|
dwHash = GlobalKnownHeaders[dwQueryIndex].HashVal;
|
|
lpszHeaderName = GlobalKnownHeaders[dwQueryIndex].Text;
|
|
dwHeaderNameLength = GlobalKnownHeaders[dwQueryIndex].Length;
|
|
|
|
//
|
|
// if we are coalescing headers then find a header with the same name
|
|
//
|
|
|
|
if ((dwFlags & COALESCE_HEADER_WITH_COMMA) ||
|
|
(dwFlags & COALESCE_HEADER_WITH_SEMICOLON) )
|
|
{
|
|
DWORD dwSlot;
|
|
|
|
dwSlot = FastFind(
|
|
dwQueryIndex,
|
|
dwIndex
|
|
);
|
|
|
|
if (dwSlot != INVALID_HEADER_SLOT)
|
|
{
|
|
|
|
HEADER_STRING * pString;
|
|
|
|
pString = &_lpHeaders[dwSlot];
|
|
|
|
//
|
|
// found what we are looking for. Coalesce it
|
|
//
|
|
|
|
pString->ResizeString((sizeof("; ")-1) + dwHeaderValueLength); // save us from multiple reallocs
|
|
|
|
pString->Strncat(
|
|
(dwFlags & COALESCE_HEADER_WITH_SEMICOLON) ?
|
|
"; " :
|
|
", ",
|
|
2);
|
|
|
|
pString->Strncat(lpszHeaderValue, dwHeaderValueLength);
|
|
_HeadersLength += 2 + dwHeaderValueLength;
|
|
error = ERROR_SUCCESS;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// if we didn't find the header value or we are not coalescing then add the
|
|
// header
|
|
//
|
|
|
|
if (error == ERROR_HTTP_HEADER_NOT_FOUND)
|
|
{
|
|
//
|
|
// find the next slot for this header
|
|
//
|
|
|
|
HEADER_STRING * freeHeader;
|
|
DWORD iSlot;
|
|
|
|
freeHeader = FindFreeSlot(&iSlot);
|
|
if (freeHeader == NULL) {
|
|
error = GetError();
|
|
|
|
INET_ASSERT(error != ERROR_SUCCESS);
|
|
|
|
goto quit;
|
|
}
|
|
|
|
|
|
freeHeader->CreateStringBuffer((LPVOID)lpszHeaderName,
|
|
dwHeaderNameLength,
|
|
dwHeaderNameLength
|
|
+ sizeof(": ") - 1
|
|
+ dwHeaderValueLength
|
|
+ 1 // for extra NULL terminator
|
|
);
|
|
if (freeHeader->IsError()) {
|
|
error = ::GetLastError();
|
|
|
|
INET_ASSERT(error != ERROR_SUCCESS);
|
|
|
|
goto quit;
|
|
}
|
|
freeHeader->Strncat((LPVOID)": ", sizeof(": ") - 1);
|
|
freeHeader->Strncat((LPVOID)lpszHeaderValue, dwHeaderValueLength);
|
|
_HeadersLength += dwHeaderNameLength
|
|
+ (sizeof(": ") - 1)
|
|
+ dwHeaderValueLength
|
|
+ (sizeof("\r\n") - 1)
|
|
;
|
|
freeHeader->SetHash(dwHash);
|
|
freeHeader->SetNextKnownIndex(FastAdd(dwQueryIndex, iSlot));
|
|
|
|
error = ERROR_SUCCESS;
|
|
}
|
|
|
|
quit:
|
|
|
|
PERF_LEAVE(AddHeader);
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
HTTP_HEADERS::ReplaceHeader(
|
|
IN LPSTR lpszHeaderName,
|
|
IN DWORD dwHeaderNameLength,
|
|
IN LPSTR lpszHeaderValue,
|
|
IN DWORD dwHeaderValueLength,
|
|
IN DWORD dwIndex,
|
|
IN DWORD dwFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Replaces a HTTP (request) header. The header can be replaced with a NULL
|
|
value, meaning that the header is removed
|
|
|
|
Arguments:
|
|
|
|
lpszHeaderName - pointer to the header name
|
|
|
|
dwHeaderNameLength - length of the header name
|
|
|
|
lpszHeaderValue - pointer to the header value
|
|
|
|
dwHeaderValueLength - length of the header value
|
|
|
|
dwIndex - index of header to replace
|
|
|
|
dwFlags - flags controlling function. Allowed flags are:
|
|
|
|
COALESCE_HEADER_WITH_COMMA
|
|
COALESCE_HEADER_WITH_SEMICOLON
|
|
- headers of the same name can be combined
|
|
|
|
ADD_HEADER
|
|
- if the header-name is not found and there is
|
|
a valid header-value, then the header is added
|
|
|
|
ADD_HEADER_IF_NEW
|
|
- if the header-name exists then we return an
|
|
error, else we add the header-value
|
|
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_HTTP_HEADER_NOT_FOUND
|
|
The requested header wasn't found
|
|
|
|
ERROR_HTTP_HEADER_ALREADY_EXISTS
|
|
The header already exists, and was not added or replaced
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
Dword,
|
|
"ReplaceHeader",
|
|
"%.*q, %d, %.*q, %d, %d, %#x",
|
|
min(dwHeaderNameLength + 1, 80),
|
|
lpszHeaderName,
|
|
dwHeaderNameLength,
|
|
min(dwHeaderValueLength + 1, 80),
|
|
lpszHeaderValue,
|
|
dwHeaderValueLength,
|
|
dwIndex,
|
|
dwFlags
|
|
));
|
|
|
|
PERF_ENTER(ReplaceHeader);
|
|
|
|
INET_ASSERT(lpszHeaderName != NULL);
|
|
INET_ASSERT(dwHeaderNameLength != 0);
|
|
INET_ASSERT(lpszHeaderName[dwHeaderNameLength - 1] != ':');
|
|
|
|
DWORD error = ERROR_HTTP_HEADER_NOT_FOUND;
|
|
DWORD dwHash = CalculateHashNoCase(lpszHeaderName, dwHeaderNameLength);
|
|
DWORD dwSlot;
|
|
DWORD dwQueryIndex;
|
|
BYTE *pbPrevByte;
|
|
|
|
LockHeaders();
|
|
|
|
dwSlot = SlowFind(
|
|
NULL,
|
|
lpszHeaderName,
|
|
dwHeaderNameLength,
|
|
dwIndex,
|
|
dwHash,
|
|
&dwQueryIndex,
|
|
&pbPrevByte
|
|
);
|
|
|
|
if ( dwSlot != ((DWORD) -1))
|
|
{
|
|
//
|
|
// if ADD_HEADER_IF_NEW is set, then we already have the header
|
|
//
|
|
|
|
if (dwFlags & ADD_HEADER_IF_NEW) {
|
|
error = ERROR_HTTP_HEADER_ALREADY_EXISTS;
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// for both replace and remove operations, we are going to remove
|
|
// the current header
|
|
//
|
|
|
|
RemoveHeader(dwSlot, dwQueryIndex, pbPrevByte);
|
|
|
|
//
|
|
// if replacing then add the new header value
|
|
//
|
|
|
|
if (dwHeaderValueLength != 0)
|
|
{
|
|
if ( dwQueryIndex != ((DWORD) -1) )
|
|
{
|
|
error = AddHeader(dwQueryIndex,
|
|
lpszHeaderValue,
|
|
dwHeaderValueLength,
|
|
0,
|
|
dwFlags
|
|
);
|
|
}
|
|
else
|
|
{
|
|
error = AddHeader(lpszHeaderName,
|
|
dwHeaderNameLength,
|
|
lpszHeaderValue,
|
|
dwHeaderValueLength,
|
|
0,
|
|
dwFlags
|
|
);
|
|
}
|
|
|
|
|
|
} else {
|
|
error = ERROR_SUCCESS;
|
|
}
|
|
}
|
|
|
|
//
|
|
// if we didn't find the header but ADD_HEADER is set then we simply add it
|
|
// but only if the value length is not zero
|
|
//
|
|
|
|
if ((error == ERROR_HTTP_HEADER_NOT_FOUND)
|
|
&& (dwHeaderValueLength != 0)
|
|
&& (dwFlags & (ADD_HEADER | ADD_HEADER_IF_NEW)))
|
|
{
|
|
if ( dwQueryIndex != ((DWORD) -1) )
|
|
{
|
|
error = AddHeader(dwQueryIndex,
|
|
lpszHeaderValue,
|
|
dwHeaderValueLength,
|
|
0,
|
|
dwFlags
|
|
);
|
|
}
|
|
else
|
|
{
|
|
error = AddHeader(lpszHeaderName,
|
|
dwHeaderNameLength,
|
|
lpszHeaderValue,
|
|
dwHeaderValueLength,
|
|
0,
|
|
dwFlags
|
|
);
|
|
|
|
}
|
|
}
|
|
|
|
quit:
|
|
|
|
UnlockHeaders();
|
|
|
|
PERF_LEAVE(ReplaceHeader);
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
HTTP_HEADERS::ReplaceHeader(
|
|
IN DWORD dwQueryIndex,
|
|
IN LPSTR lpszHeaderValue,
|
|
IN DWORD dwHeaderValueLength,
|
|
IN DWORD dwIndex,
|
|
IN DWORD dwFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Replaces a HTTP (request) header. The header can be replaced with a NULL
|
|
value, meaning that the header is removed
|
|
|
|
Arguments:
|
|
|
|
lpszHeaderValue - pointer to the header value
|
|
|
|
dwQueryIndex - a index into a array of known HTTP headers, see wininet.h HTTP_QUERY_* codes
|
|
|
|
dwHeaderValueLength - length of the header value
|
|
|
|
dwIndex - index of header to replace
|
|
|
|
dwFlags - flags controlling function. Allowed flags are:
|
|
|
|
COALESCE_HEADER_WITH_COMMA
|
|
COALESCE_HEADER_WITH_SEMICOLON
|
|
- headers of the same name can be combined
|
|
|
|
ADD_HEADER
|
|
- if the header-name is not found and there is
|
|
a valid header-value, then the header is added
|
|
|
|
ADD_HEADER_IF_NEW
|
|
- if the header-name exists then we return an
|
|
error, else we add the header-value
|
|
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_HTTP_HEADER_NOT_FOUND
|
|
The requested header wasn't found
|
|
|
|
ERROR_HTTP_HEADER_ALREADY_EXISTS
|
|
The header already exists, and was not added or replaced
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
Dword,
|
|
"ReplaceHeader",
|
|
"%q, %u, %.*q, %d, %d, %#x",
|
|
GlobalKnownHeaders[dwQueryIndex].Text,
|
|
dwQueryIndex,
|
|
min(dwHeaderValueLength + 1, 80),
|
|
lpszHeaderValue,
|
|
dwHeaderValueLength,
|
|
dwIndex,
|
|
dwFlags
|
|
));
|
|
|
|
PERF_ENTER(ReplaceHeader);
|
|
|
|
DWORD error = ERROR_HTTP_HEADER_NOT_FOUND;
|
|
DWORD dwSlot;
|
|
BYTE *pbPrevByte;
|
|
|
|
LockHeaders();
|
|
|
|
dwSlot = FastNukeFind(
|
|
dwQueryIndex,
|
|
dwIndex,
|
|
&pbPrevByte
|
|
);
|
|
|
|
if ( dwSlot != INVALID_HEADER_SLOT)
|
|
{
|
|
//
|
|
// if ADD_HEADER_IF_NEW is set, then we already have the header
|
|
//
|
|
|
|
if (dwFlags & ADD_HEADER_IF_NEW) {
|
|
error = ERROR_HTTP_HEADER_ALREADY_EXISTS;
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// for both replace and remove operations, we are going to remove
|
|
// the current header
|
|
//
|
|
|
|
RemoveHeader(dwSlot, dwQueryIndex, pbPrevByte);
|
|
|
|
//
|
|
// if replacing then add the new header value
|
|
//
|
|
|
|
if (dwHeaderValueLength != 0)
|
|
{
|
|
error = AddHeader(dwQueryIndex,
|
|
lpszHeaderValue,
|
|
dwHeaderValueLength,
|
|
0,
|
|
dwFlags
|
|
);
|
|
} else {
|
|
error = ERROR_SUCCESS;
|
|
}
|
|
}
|
|
|
|
//
|
|
// if we didn't find the header but ADD_HEADER is set then we simply add it
|
|
// but only if the value length is not zero
|
|
//
|
|
|
|
if ((error == ERROR_HTTP_HEADER_NOT_FOUND)
|
|
&& (dwHeaderValueLength != 0)
|
|
&& (dwFlags & (ADD_HEADER | ADD_HEADER_IF_NEW)))
|
|
{
|
|
error = AddHeader(dwQueryIndex,
|
|
lpszHeaderValue,
|
|
dwHeaderValueLength,
|
|
0,
|
|
dwFlags
|
|
);
|
|
}
|
|
|
|
quit:
|
|
|
|
UnlockHeaders();
|
|
|
|
PERF_LEAVE(ReplaceHeader);
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
DWORD
|
|
HTTP_HEADERS::FindHeader(
|
|
IN LPSTR lpBase,
|
|
IN LPSTR lpszHeaderName,
|
|
IN DWORD dwHeaderNameLength,
|
|
IN DWORD dwModifiers,
|
|
OUT LPVOID lpBuffer,
|
|
IN OUT LPDWORD lpdwBufferLength,
|
|
IN OUT LPDWORD lpdwIndex
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Finds a request or response header
|
|
|
|
Arguments:
|
|
|
|
lpBase - base for offset HEADER_STRINGs
|
|
|
|
lpszHeaderName - pointer to header name
|
|
|
|
dwHeaderNameLength - length of header name
|
|
|
|
dwModifiers - flags which modify returned value
|
|
|
|
lpBuffer - pointer to buffer for results
|
|
|
|
lpdwBufferLength - IN: length of lpBuffer
|
|
OUT: length of results, or required length of lpBuffer
|
|
|
|
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
|
|
*lpdwBufferLength contains the amount required
|
|
|
|
ERROR_HTTP_HEADER_NOT_FOUND
|
|
The specified header (or index of header) was not found
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
Dword,
|
|
"HTTP_HEADERS::FindHeader",
|
|
"%#x [%.*q], %d, %#x, %#x [%#x], %#x, %#x [%d]",
|
|
lpszHeaderName,
|
|
min(dwHeaderNameLength + 1, 80),
|
|
lpszHeaderName,
|
|
dwHeaderNameLength,
|
|
lpBuffer,
|
|
lpdwBufferLength,
|
|
*lpdwBufferLength,
|
|
dwModifiers,
|
|
lpdwIndex,
|
|
*lpdwIndex
|
|
));
|
|
|
|
|
|
PERF_ENTER(FindHeader);
|
|
|
|
|
|
|
|
INET_ASSERT(lpdwIndex != NULL);
|
|
|
|
DWORD error = ERROR_HTTP_HEADER_NOT_FOUND;
|
|
DWORD dwSlot;
|
|
HEADER_STRING * pString;
|
|
DWORD dwQueryIndex;
|
|
DWORD dwHash = CalculateHashNoCase(lpszHeaderName, dwHeaderNameLength);
|
|
|
|
LockHeaders();
|
|
|
|
dwSlot = SlowFind(
|
|
lpBase,
|
|
lpszHeaderName,
|
|
dwHeaderNameLength,
|
|
*lpdwIndex,
|
|
dwHash,
|
|
&dwQueryIndex,
|
|
NULL
|
|
);
|
|
|
|
if ( dwSlot != ((DWORD) -1) )
|
|
{
|
|
pString = &_lpHeaders[dwSlot];
|
|
|
|
//
|
|
// found the header - get to the value
|
|
//
|
|
|
|
DWORD stringLen;
|
|
LPSTR value;
|
|
|
|
stringLen = pString->StringLength();
|
|
|
|
INET_ASSERT(stringLen > dwHeaderNameLength);
|
|
|
|
//
|
|
// get a pointer to the value string
|
|
//
|
|
|
|
value = pString->StringAddress(lpBase) + dwHeaderNameLength;
|
|
stringLen -= dwHeaderNameLength;
|
|
|
|
//
|
|
// the input string could be a substring of a different header
|
|
//
|
|
|
|
//INET_ASSERT(*value != ':');
|
|
|
|
//
|
|
// find the first non-space character in the value.
|
|
//
|
|
// N.B.: Servers can return empty headers, so we may end up with a
|
|
// zero length string
|
|
//
|
|
|
|
do {
|
|
++value;
|
|
--stringLen;
|
|
} while ((stringLen > 0) && (*value == ' '));
|
|
|
|
//
|
|
// get the data in the format requested by the app
|
|
//
|
|
|
|
LPVOID lpData;
|
|
DWORD dwDataSize;
|
|
DWORD dwRequiredSize;
|
|
SYSTEMTIME systemTime;
|
|
DWORD number;
|
|
|
|
//
|
|
// error is no longer ERROR_HTTP_HEADER_NOT_FOUND, but it might not
|
|
// really be success either...
|
|
//
|
|
|
|
error = ERROR_SUCCESS;
|
|
|
|
if (dwModifiers & HTTP_QUERY_FLAG_SYSTEMTIME) {
|
|
|
|
char buf[DATE_AND_TIME_STRING_BUFFER_LENGTH];
|
|
|
|
if (stringLen < sizeof(buf)) {
|
|
|
|
//
|
|
// value probably does not point at a zero-terminated string
|
|
// which HttpDateToSystemTime() expects, so we make a copy
|
|
// and terminate it
|
|
//
|
|
|
|
memcpy((LPVOID)buf, (LPVOID)value, stringLen);
|
|
buf[stringLen] = '\0';
|
|
if (HttpDateToSystemTime(buf, &systemTime)) {
|
|
lpData = (LPVOID)&systemTime;
|
|
dwRequiredSize = dwDataSize = sizeof(systemTime);
|
|
} else {
|
|
|
|
//
|
|
// couldn't convert date/time. Presume header must be bogus
|
|
//
|
|
|
|
error = ERROR_HTTP_INVALID_QUERY_REQUEST;
|
|
|
|
DEBUG_PRINT(HTTP,
|
|
ERROR,
|
|
("cannot convert %.40q to SYSTEMTIME\n",
|
|
value
|
|
));
|
|
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// we would break the date/time buffer!
|
|
//
|
|
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
}
|
|
} else if (dwModifiers & HTTP_QUERY_FLAG_NUMBER) {
|
|
if (isdigit(*value)) {
|
|
number = 0;
|
|
for (int i = 0;
|
|
(stringLen > 0) && isdigit(value[i]);
|
|
++i, --stringLen) {
|
|
|
|
number = number * 10 + (DWORD)(value[i] - '0');
|
|
}
|
|
lpData = (LPVOID)&number;
|
|
dwRequiredSize = dwDataSize = sizeof(number);
|
|
} else {
|
|
|
|
//
|
|
// not a numeric field. Request must be bogus for this header
|
|
//
|
|
|
|
error = ERROR_HTTP_INVALID_QUERY_REQUEST;
|
|
|
|
DEBUG_PRINT(HTTP,
|
|
ERROR,
|
|
("cannot convert %.20q to NUMBER\n",
|
|
value
|
|
));
|
|
|
|
}
|
|
} else {
|
|
lpData = (LPVOID)value;
|
|
dwDataSize = stringLen;
|
|
dwRequiredSize = dwDataSize + 1;
|
|
}
|
|
|
|
//
|
|
// if error == ERROR_SUCCESS then we can attempt to copy the data
|
|
//
|
|
|
|
if (error == ERROR_SUCCESS) {
|
|
if (*lpdwBufferLength < dwRequiredSize) {
|
|
*lpdwBufferLength = dwRequiredSize;
|
|
error = ERROR_INSUFFICIENT_BUFFER;
|
|
} else {
|
|
memcpy(lpBuffer, lpData, dwDataSize);
|
|
*lpdwBufferLength = dwDataSize;
|
|
|
|
//
|
|
// if dwRequiredSize > dwDataSize, then this is a variable-
|
|
// length item (i.e. a STRING!) so we add a terminating '\0'
|
|
//
|
|
|
|
if (dwRequiredSize > dwDataSize) {
|
|
|
|
INET_ASSERT(dwRequiredSize - dwDataSize == 1);
|
|
|
|
((LPSTR)lpBuffer)[dwDataSize] = '\0';
|
|
}
|
|
|
|
//
|
|
// successfully retrieved the requested header - bump the
|
|
// index
|
|
//
|
|
|
|
++*lpdwIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
UnlockHeaders();
|
|
|
|
PERF_LEAVE(FindHeader);
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
HTTP_HEADERS::FindHeader(
|
|
IN LPSTR lpBase,
|
|
IN DWORD dwQueryIndex,
|
|
IN DWORD dwModifiers,
|
|
OUT LPVOID lpBuffer,
|
|
IN OUT LPDWORD lpdwBufferLength,
|
|
IN OUT LPDWORD lpdwIndex
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Finds a request or response header, based on index to the header name we are searching for.
|
|
|
|
Arguments:
|
|
|
|
lpBase - base for offset HEADER_STRINGs
|
|
|
|
dwQueryIndex - a index into a array of known HTTP headers, see wininet.h HTTP_QUERY_* codes
|
|
|
|
dwModifiers - flags which modify returned value
|
|
|
|
lpBuffer - pointer to buffer for results
|
|
|
|
lpdwBufferLength - IN: length of lpBuffer
|
|
OUT: length of results, or required length of lpBuffer
|
|
|
|
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
|
|
*lpdwBufferLength contains the amount required
|
|
|
|
ERROR_HTTP_HEADER_NOT_FOUND
|
|
The specified header (or index of header) was not found
|
|
|
|
--*/
|
|
{
|
|
|
|
DWORD error;
|
|
LPSTR lpData;
|
|
DWORD dwDataSize = 0;
|
|
DWORD dwRequiredSize;
|
|
SYSTEMTIME systemTime;
|
|
DWORD number;
|
|
|
|
error = FastFindHeader(
|
|
lpBase,
|
|
dwQueryIndex,
|
|
(LPVOID *)&lpData,
|
|
&dwDataSize,
|
|
*lpdwIndex
|
|
);
|
|
|
|
if ( error != ERROR_SUCCESS )
|
|
{
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// get the data in the format requested by the app
|
|
//
|
|
|
|
if (dwModifiers & HTTP_QUERY_FLAG_SYSTEMTIME)
|
|
{
|
|
char buf[DATE_AND_TIME_STRING_BUFFER_LENGTH];
|
|
|
|
if (dwDataSize < sizeof(buf))
|
|
{
|
|
|
|
//
|
|
// value probably does not point at a zero-terminated string
|
|
// which HttpDateToSystemTime() expects, so we make a copy
|
|
// and terminate it
|
|
//
|
|
|
|
memcpy((LPVOID)buf, (LPVOID)lpData, dwDataSize);
|
|
buf[dwDataSize] = '\0';
|
|
if (HttpDateToSystemTime(buf, &systemTime)) {
|
|
lpData = (LPSTR)&systemTime;
|
|
dwRequiredSize = dwDataSize = sizeof(systemTime);
|
|
} else {
|
|
|
|
//
|
|
// couldn't convert date/time. Presume header must be bogus
|
|
//
|
|
|
|
error = ERROR_HTTP_INVALID_QUERY_REQUEST;
|
|
|
|
DEBUG_PRINT(HTTP,
|
|
ERROR,
|
|
("cannot convert %.40q to SYSTEMTIME\n",
|
|
lpData
|
|
));
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
//
|
|
// we would break the date/time buffer!
|
|
//
|
|
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
else if (dwModifiers & HTTP_QUERY_FLAG_NUMBER)
|
|
{
|
|
if (isdigit(*lpData)) {
|
|
number = 0;
|
|
for (int i = 0;
|
|
(dwDataSize > 0) && isdigit(lpData[i]);
|
|
++i, --dwDataSize) {
|
|
|
|
number = number * 10 + (DWORD)(lpData[i] - '0');
|
|
}
|
|
lpData = (LPSTR)&number;
|
|
dwRequiredSize = dwDataSize = sizeof(number);
|
|
} else {
|
|
|
|
//
|
|
// not a numeric field. Request must be bogus for this header
|
|
//
|
|
|
|
error = ERROR_HTTP_INVALID_QUERY_REQUEST;
|
|
|
|
DEBUG_PRINT(HTTP,
|
|
ERROR,
|
|
("cannot convert %.20q to NUMBER\n",
|
|
lpData
|
|
));
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dwRequiredSize = dwDataSize + 1;
|
|
}
|
|
|
|
//
|
|
// if error == ERROR_SUCCESS then we can attempt to copy the data
|
|
//
|
|
|
|
if (error == ERROR_SUCCESS)
|
|
{
|
|
if (*lpdwBufferLength < dwRequiredSize)
|
|
{
|
|
*lpdwBufferLength = dwRequiredSize;
|
|
error = ERROR_INSUFFICIENT_BUFFER;
|
|
}
|
|
else
|
|
{
|
|
memcpy(lpBuffer, lpData, dwDataSize);
|
|
*lpdwBufferLength = dwDataSize;
|
|
|
|
//
|
|
// if dwRequiredSize > dwDataSize, then this is a variable-
|
|
// length item (i.e. a STRING!) so we add a terminating '\0'
|
|
//
|
|
|
|
if (dwRequiredSize > dwDataSize)
|
|
{
|
|
INET_ASSERT(dwRequiredSize - dwDataSize == 1);
|
|
|
|
((LPSTR)lpBuffer)[dwDataSize] = '\0';
|
|
}
|
|
|
|
//
|
|
// successfully retrieved the requested header - bump the
|
|
// index
|
|
//
|
|
|
|
++*lpdwIndex;
|
|
}
|
|
}
|
|
quit:
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
HTTP_HEADERS::FastFindHeader(
|
|
IN LPSTR lpBase,
|
|
IN DWORD dwQueryIndex,
|
|
OUT LPVOID *lplpBuffer,
|
|
IN OUT LPDWORD lpdwBufferLength,
|
|
IN DWORD dwIndex
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Finds a request or response header slightly quicker than its higher level
|
|
cousin, FindHeader. Unlike FindHeader this function simply returns
|
|
a pointer and length, and does not copy header data.
|
|
|
|
|
|
lpBase - base address of strings
|
|
|
|
dwQueryIndex - a index into a array known HTTP headers, see wininet.h HTTP_QUERY_* codes
|
|
|
|
lplpBuffer - pointer to pointer of the actual header to be returned in.
|
|
|
|
lpdwBufferLength - OUT: if successful, length of output buffer, minus 1
|
|
for any trailing EOS, or if the buffer is not
|
|
large enough, the size required
|
|
|
|
dwIndex - a index of which header we're asking for, as there can be multiple headers
|
|
under the same name.
|
|
|
|
Arguments:
|
|
|
|
lpBase - base for offset HEADER_STRINGs
|
|
|
|
lpszHeaderName - pointer to header name
|
|
|
|
dwHeaderNameLength - length of header name
|
|
|
|
dwModifiers - flags which modify returned value
|
|
|
|
lpBuffer - pointer to buffer for results
|
|
|
|
lpdwBufferLength - IN: length of lpBuffer
|
|
OUT: length of results, or required length of lpBuffer
|
|
|
|
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
|
|
*lpdwBufferLength contains the amount required
|
|
|
|
ERROR_HTTP_HEADER_NOT_FOUND
|
|
The specified header (or index of header) was not found
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
Dword,
|
|
"HTTP_HEADERS::FastFindHeader",
|
|
"%q, %#x, %#x [%#x], %u",
|
|
GlobalKnownHeaders[dwQueryIndex].Text,
|
|
lplpBuffer,
|
|
lpdwBufferLength,
|
|
*lpdwBufferLength,
|
|
dwIndex
|
|
));
|
|
|
|
PERF_ENTER(FindHeader);
|
|
|
|
DWORD error = ERROR_HTTP_HEADER_NOT_FOUND;
|
|
|
|
HEADER_STRING * curHeader;
|
|
DWORD dwSlot;
|
|
|
|
dwSlot = FastFind(dwQueryIndex, dwIndex);
|
|
|
|
if ( dwSlot != INVALID_HEADER_SLOT)
|
|
{
|
|
//
|
|
// found the header - get to the value
|
|
//
|
|
|
|
DWORD stringLen;
|
|
LPSTR value;
|
|
|
|
curHeader = GetSlot(dwSlot);
|
|
|
|
//
|
|
// get a pointer to the value string
|
|
//
|
|
|
|
value = curHeader->StringAddress(lpBase) + (GlobalKnownHeaders[dwQueryIndex].Length+1);
|
|
stringLen = curHeader->StringLength() - (GlobalKnownHeaders[dwQueryIndex].Length+1);
|
|
|
|
//
|
|
// find the first non-space character in the value.
|
|
//
|
|
// N.B.: Servers can return empty headers, so we may end up with a
|
|
// zero length string
|
|
//
|
|
|
|
while ((stringLen > 0) && (*value == ' '))
|
|
{
|
|
++value;
|
|
--stringLen;
|
|
}
|
|
|
|
//
|
|
// get the data in the format requested by the app
|
|
//
|
|
|
|
//
|
|
// error is no longer ERROR_HTTP_HEADER_NOT_FOUND, but it might not
|
|
// really be success either...
|
|
//
|
|
|
|
error = ERROR_SUCCESS;
|
|
|
|
*lplpBuffer = (LPVOID)value;
|
|
*lpdwBufferLength = stringLen;
|
|
}
|
|
|
|
PERF_LEAVE(FindHeader);
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
HTTP_HEADERS::QueryRawHeaders(
|
|
IN LPSTR lpBase,
|
|
IN BOOL bCrLfTerminated,
|
|
IN LPVOID lpBuffer,
|
|
IN OUT LPDWORD lpdwBufferLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns all the request or response headers in a single buffer. The headers
|
|
can be returned as ASCIIZ strings, or CR-LF terminated strings
|
|
|
|
Arguments:
|
|
|
|
lpBase - base address of strings
|
|
|
|
bCrLfTerminated - TRUE if each string is terminated with CR-LF
|
|
|
|
lpBuffer - pointer to buffer to write headers
|
|
|
|
lpdwBufferLength - IN: length of lpBuffer
|
|
OUT: if successful, length of output buffer, minus 1
|
|
for any trailing EOS, or if the buffer is not
|
|
large enough, the size required
|
|
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_INSUFFICIENT_BUFFER
|
|
|
|
--*/
|
|
|
|
{
|
|
PERF_ENTER(QueryRawHeaders);
|
|
|
|
DWORD requiredLength = 0;
|
|
LPSTR lpszBuffer = (LPSTR)lpBuffer;
|
|
|
|
LockHeaders();
|
|
|
|
for (DWORD i = 0; i < _TotalSlots; ++i) {
|
|
if (_lpHeaders[i].HaveString()) {
|
|
|
|
DWORD length;
|
|
|
|
length = _lpHeaders[i].StringLength();
|
|
|
|
requiredLength += length + (bCrLfTerminated ? 2 : 1);
|
|
if (*lpdwBufferLength > requiredLength) {
|
|
_lpHeaders[i].CopyTo(lpBase, lpszBuffer);
|
|
lpszBuffer += length;
|
|
if (bCrLfTerminated) {
|
|
*lpszBuffer++ = '\r';
|
|
*lpszBuffer++ = '\n';
|
|
} else {
|
|
*lpszBuffer++ = '\0';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bCrLfTerminated)
|
|
{
|
|
requiredLength += 2;
|
|
if (*lpdwBufferLength > requiredLength)
|
|
{
|
|
*lpszBuffer++ = '\r';
|
|
*lpszBuffer++ = '\n';
|
|
}
|
|
}
|
|
|
|
UnlockHeaders();
|
|
|
|
++requiredLength;
|
|
|
|
DWORD error;
|
|
|
|
if (*lpdwBufferLength < requiredLength) {
|
|
error = ERROR_INSUFFICIENT_BUFFER;
|
|
} else {
|
|
*lpszBuffer = '\0';
|
|
--requiredLength; // remove 1 for trailing '\0'
|
|
error = ERROR_SUCCESS;
|
|
}
|
|
*lpdwBufferLength = requiredLength;
|
|
|
|
PERF_LEAVE(QueryRawHeaders);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
DWORD
|
|
HTTP_HEADERS::QueryFilteredRawHeaders(
|
|
IN LPSTR lpBase,
|
|
IN LPSTR *lplpFilterList,
|
|
IN DWORD cListElements,
|
|
IN BOOL fExclude,
|
|
IN BOOL fSkipVerb,
|
|
IN BOOL bCrLfTerminated,
|
|
IN LPVOID lpBuffer,
|
|
IN OUT LPDWORD lpdwBufferLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns all the request or response headers in a single buffer. The headers
|
|
can be returned as ASCIIZ strings, or CR-LF terminated strings
|
|
|
|
Arguments:
|
|
|
|
lpBase - base address of strings
|
|
|
|
bCrLfTerminated - TRUE if each string is terminated with CR-LF
|
|
|
|
lpBuffer - pointer to buffer to write headers
|
|
|
|
lpdwBufferLength - IN: length of lpBuffer
|
|
OUT: if successful, length of output buffer, minus 1
|
|
for any trailing EOS, or if the buffer is not
|
|
large enough, the size required
|
|
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_INSUFFICIENT_BUFFER
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD error = ERROR_NOT_SUPPORTED;
|
|
|
|
DWORD requiredLength = 0;
|
|
LPSTR lpszBuffer = (LPSTR)lpBuffer;
|
|
BOOL fCopy;
|
|
|
|
DWORD i = fSkipVerb ? 1 : 0;
|
|
for (; i < _TotalSlots; ++i) {
|
|
if (_lpHeaders[i].HaveString()) {
|
|
fCopy = TRUE;
|
|
if (lplpFilterList
|
|
&& FMatchList(lplpFilterList, cListElements, _lpHeaders+i, lpBase)) {
|
|
fCopy = fExclude?FALSE:TRUE;
|
|
}
|
|
if (fCopy) {
|
|
DWORD length;
|
|
|
|
length = _lpHeaders[i].StringLength();
|
|
requiredLength += length + (bCrLfTerminated ? 2 : 1);
|
|
if (*lpdwBufferLength > requiredLength) {
|
|
_lpHeaders[i].CopyTo(lpBase, lpszBuffer);
|
|
lpszBuffer += length;
|
|
if (bCrLfTerminated) {
|
|
*lpszBuffer++ = '\r';
|
|
*lpszBuffer++ = '\n';
|
|
} else {
|
|
*lpszBuffer++ = '\0';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bCrLfTerminated)
|
|
{
|
|
requiredLength += 2;
|
|
if (*lpdwBufferLength > requiredLength)
|
|
{
|
|
*lpszBuffer++ = '\r';
|
|
*lpszBuffer++ = '\n';
|
|
}
|
|
}
|
|
|
|
++requiredLength;
|
|
|
|
|
|
if (*lpdwBufferLength < requiredLength) {
|
|
error = ERROR_INSUFFICIENT_BUFFER;
|
|
} else {
|
|
*lpszBuffer = '\0';
|
|
--requiredLength; // remove 1 for trailing '\0'
|
|
error = ERROR_SUCCESS;
|
|
}
|
|
*lpdwBufferLength = requiredLength;
|
|
return error;
|
|
}
|
|
|
|
|
|
DWORD
|
|
HTTP_HEADERS::AddRequest(
|
|
IN LPSTR lpszVerb,
|
|
IN LPSTR lpszObject,
|
|
IN LPSTR lpszVersion
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Builds the request line from its constituent parts. The request line is the
|
|
first (0th) header in the request headers
|
|
|
|
Assumes: 1. This is the one-and-only call to this method
|
|
2. lpszObject must already be escaped if necessary
|
|
|
|
Arguments:
|
|
|
|
lpszVerb - pointer to HTTP verb, e.g. "GET"
|
|
|
|
lpszObject - pointer to HTTP object name, e.g. "/users/albert/~emc2.htm".
|
|
|
|
lpszVersion - pointer to HTTP version string, e.g. "HTTP/1.0"
|
|
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_NOT_ENOUGH_MEMORY
|
|
|
|
--*/
|
|
|
|
{
|
|
PERF_ENTER(AddRequest);
|
|
|
|
//
|
|
// there must not be a header when this method is called
|
|
//
|
|
|
|
INET_ASSERT(_HeadersLength == 0);
|
|
|
|
DWORD error = ERROR_SUCCESS;
|
|
int verbLen = lstrlen(lpszVerb);
|
|
int objectLen = lstrlen(lpszObject);
|
|
int versionLen = lstrlen(lpszVersion);
|
|
int len = verbLen // "GET"
|
|
+ 1 // ' '
|
|
+ objectLen // "/users/albert/~emc2.htm"
|
|
+ 1 // ' '
|
|
+ versionLen // "HTTP/1.0"
|
|
+ 1 // '\0'
|
|
;
|
|
|
|
//
|
|
// we are about to start updating the headers for the current
|
|
// HTTP_REQUEST_HANDLE_OBJECT. Serialize access
|
|
//
|
|
|
|
HEADER_STRING * pRequest = GetFirstHeader();
|
|
HEADER_STRING & request = *pRequest;
|
|
|
|
if (pRequest == NULL) {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto quit;
|
|
}
|
|
|
|
INET_ASSERT(!request.HaveString());
|
|
|
|
_lpszVerb = NULL;
|
|
_dwVerbLength = 0;
|
|
_lpszObjectName = NULL;
|
|
_dwObjectNameLength = 0;
|
|
_lpszVersion = NULL;
|
|
_dwVersionLength = 0;
|
|
|
|
request.CreateStringBuffer((LPVOID)lpszVerb, verbLen, len);
|
|
if (request.IsError()) {
|
|
error = GetLastError();
|
|
|
|
INET_ASSERT(error != ERROR_SUCCESS);
|
|
|
|
} else {
|
|
request += ' ';
|
|
request.Strncat((LPVOID)lpszObject, objectLen);
|
|
request += ' ';
|
|
request.Strncat((LPVOID)lpszVersion, versionLen);
|
|
|
|
_HeadersLength = len - 1 + (sizeof("\r\n") - 1);
|
|
|
|
//
|
|
// we have used the first free slot in the headers array
|
|
//
|
|
|
|
--_FreeSlots;
|
|
|
|
//
|
|
// update the component variables in case of a ModifyRequest()
|
|
//
|
|
|
|
_lpszVerb = request.StringAddress();
|
|
_dwVerbLength = verbLen;
|
|
_lpszObjectName = _lpszVerb + verbLen + 1;
|
|
_dwObjectNameLength = objectLen;
|
|
_lpszVersion = _lpszObjectName + objectLen + 1;
|
|
_dwVersionLength = versionLen;
|
|
SetRequestVersion();
|
|
error = request.IsError() ? ::GetLastError() : ERROR_SUCCESS;
|
|
}
|
|
|
|
quit:
|
|
|
|
PERF_LEAVE(AddRequest);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
DWORD
|
|
HTTP_HEADERS::ModifyRequest(
|
|
IN HTTP_METHOD_TYPE tMethod,
|
|
IN LPSTR lpszObjectName,
|
|
IN DWORD dwObjectNameLength,
|
|
IN LPSTR lpszVersion OPTIONAL,
|
|
IN DWORD dwVersionLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Updates the request line. Used in redirection
|
|
|
|
Arguments:
|
|
|
|
tMethod - type of new method
|
|
|
|
lpszObjectName - pointer to new object name
|
|
|
|
dwObjectNameLength - length of new object name
|
|
|
|
lpszVersion - optional pointer to version string
|
|
|
|
dwVersionLength - length of lpszVersion string if present
|
|
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_NOT_ENOUGH_MEMORY
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
Dword,
|
|
"ModifyRequest",
|
|
"%s, %q, %d, %q, %d",
|
|
MapHttpMethodType(tMethod),
|
|
lpszObjectName,
|
|
dwObjectNameLength,
|
|
lpszVersion,
|
|
dwVersionLength
|
|
));
|
|
|
|
PERF_ENTER(ModifyRequest);
|
|
|
|
INET_ASSERT(lpszObjectName != NULL);
|
|
INET_ASSERT(dwObjectNameLength != 0);
|
|
|
|
//
|
|
// there must already be a header when this method is called
|
|
//
|
|
|
|
INET_ASSERT(_HeadersLength != 0);
|
|
|
|
//
|
|
// we are about to start updating the headers for the current
|
|
// HTTP_REQUEST_HANDLE_OBJECT. Serialize access
|
|
//
|
|
|
|
//
|
|
// BUGBUG [arthurbi] using two HEADER_STRINGs here causes an extra
|
|
// ReAlloc when use the Copy operator between the two.
|
|
//
|
|
|
|
HEADER_STRING * pRequest = GetFirstHeader();
|
|
HEADER_STRING & request = *pRequest;
|
|
HEADER_STRING newRequest;
|
|
LPCSTR lpcszVerb;
|
|
DWORD verbLength;
|
|
DWORD error = ERROR_SUCCESS;
|
|
DWORD length;
|
|
|
|
//
|
|
// there must already be a request line
|
|
//
|
|
|
|
if (pRequest == NULL) {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto quit;
|
|
}
|
|
|
|
INET_ASSERT(request.HaveString());
|
|
|
|
//
|
|
// get the verb/method to use.
|
|
//
|
|
|
|
if (tMethod == HTTP_METHOD_TYPE_UNKNOWN) {
|
|
|
|
//
|
|
// the method is unknown, read the old one out of the string
|
|
// and save off, basically we're reusing the previous one.
|
|
//
|
|
|
|
lpcszVerb = request.StringAddress();
|
|
|
|
for (DWORD i = 0; i < request.StringLength(); i++) {
|
|
if (lpcszVerb[i] == ' ') {
|
|
break;
|
|
}
|
|
}
|
|
|
|
INET_ASSERT((i > 0) && (i < request.StringLength()));
|
|
|
|
verbLength = i;
|
|
} else {
|
|
|
|
//
|
|
// its one of the normal kind, just map it.
|
|
//
|
|
|
|
verbLength = MapHttpMethodType(tMethod, &lpcszVerb);
|
|
}
|
|
if (lpszVersion == NULL) {
|
|
lpszVersion = _lpszVersion;
|
|
dwVersionLength = _dwVersionLength;
|
|
}
|
|
|
|
_lpszVerb = NULL;
|
|
_dwVerbLength = 0;
|
|
_lpszObjectName = NULL;
|
|
_dwObjectNameLength = 0;
|
|
_lpszVersion = NULL;
|
|
_dwVersionLength = 0;
|
|
|
|
//
|
|
// calculate the new length from the component lengths we originally set
|
|
// in AddRequest(), and the new object name
|
|
//
|
|
|
|
length = verbLength + 1 + dwObjectNameLength + 1 + dwVersionLength + 1;
|
|
|
|
//
|
|
// create a new request line
|
|
//
|
|
|
|
newRequest.CreateStringBuffer((LPVOID)lpcszVerb, verbLength, length);
|
|
if (newRequest.IsError()) {
|
|
error = GetLastError();
|
|
} else {
|
|
newRequest += ' ';
|
|
newRequest.Strncat((LPVOID)lpszObjectName, dwObjectNameLength);
|
|
newRequest += ' ';
|
|
newRequest.Strncat((LPVOID)lpszVersion, dwVersionLength);
|
|
|
|
//
|
|
// remove the current request line length from the header buffer
|
|
// aggregate
|
|
//
|
|
|
|
_HeadersLength -= request.StringLength();
|
|
|
|
//
|
|
// make the current request line the new one
|
|
//
|
|
|
|
request = newRequest.StringAddress();
|
|
|
|
//
|
|
// and update the address and length variables (version length is the
|
|
// only thing that stays the same)
|
|
//
|
|
|
|
if (!request.IsError()) {
|
|
_lpszVerb = request.StringAddress();
|
|
_dwVerbLength = verbLength;
|
|
_lpszObjectName = _lpszVerb + verbLength + 1;
|
|
_dwObjectNameLength = dwObjectNameLength;
|
|
_lpszVersion = _lpszObjectName + dwObjectNameLength + 1;
|
|
_dwVersionLength = dwVersionLength;
|
|
SetRequestVersion();
|
|
|
|
//
|
|
// and the new request line length to the aggregate header length
|
|
//
|
|
|
|
_HeadersLength += request.StringLength();
|
|
} else {
|
|
error = GetLastError();
|
|
}
|
|
}
|
|
|
|
quit:
|
|
|
|
PERF_LEAVE(ModifyRequest);
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
VOID
|
|
HTTP_HEADERS::SetRequestVersion(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Set _RequestVersionMajor and _RequestVersionMinor based on the HTTP
|
|
version string
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
None,
|
|
"HTTP_HEADERS::SetRequestVersion",
|
|
NULL
|
|
));
|
|
|
|
INET_ASSERT(_lpszVersion != NULL);
|
|
|
|
_RequestVersionMajor = 0;
|
|
_RequestVersionMinor = 0;
|
|
if (strncmp(_lpszVersion, "HTTP/", sizeof("HTTP/") - 1) == 0) {
|
|
|
|
LPSTR pNum = _lpszVersion + sizeof("HTTP/") - 1;
|
|
|
|
ExtractInt(&pNum, 0, (LPINT)&_RequestVersionMajor);
|
|
while (!isdigit(*pNum) && (*pNum != '\0')) {
|
|
++pNum;
|
|
}
|
|
ExtractInt(&pNum, 0, (LPINT)&_RequestVersionMinor);
|
|
|
|
DEBUG_PRINT(HTTP,
|
|
INFO,
|
|
("request version = %d.%d\n",
|
|
_RequestVersionMajor,
|
|
_RequestVersionMinor
|
|
));
|
|
|
|
} else {
|
|
|
|
DEBUG_PRINT(HTTP,
|
|
WARNING,
|
|
("\"HTTP/\" not found in %q\n",
|
|
_lpszVersion
|
|
));
|
|
|
|
}
|
|
|
|
DEBUG_LEAVE(0);
|
|
}
|
|
|
|
|
|
DWORD
|
|
HTTP_HEADERS::QueryRequestVersion(
|
|
IN LPVOID lpBuffer,
|
|
IN OUT LPDWORD lpdwBufferLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Get HtttpVersion used in request.
|
|
|
|
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
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
None,
|
|
"HTTP_HEADERS::QueryRequestVersion",
|
|
NULL
|
|
));
|
|
|
|
DWORD error = ERROR_HTTP_HEADER_NOT_FOUND;
|
|
|
|
LockHeaders();
|
|
|
|
if (_lpszVersion != NULL)
|
|
{
|
|
DWORD dwLen = lstrlen(_lpszVersion);
|
|
|
|
if (*lpdwBufferLength > dwLen) {
|
|
memcpy(lpBuffer, _lpszVersion, dwLen);
|
|
((LPSTR)lpBuffer)[dwLen] = '\0';
|
|
*lpdwBufferLength = dwLen;
|
|
error = ERROR_SUCCESS;
|
|
} else {
|
|
*lpdwBufferLength = dwLen + 1;
|
|
error = ERROR_INSUFFICIENT_BUFFER;
|
|
}
|
|
}
|
|
|
|
UnlockHeaders();
|
|
|
|
DEBUG_LEAVE(0);
|
|
return error;
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
*lpbCombinedData = FALSE;
|
|
|
|
_RequestHeaders.LockHeaders();
|
|
|
|
DWORD headersLength;
|
|
DWORD requestLength;
|
|
DWORD optionalLength;
|
|
HEADER_STRING * pRequest = _RequestHeaders.GetFirstHeader();
|
|
HEADER_STRING & request = *pRequest;
|
|
LPSTR requestBuffer = NULL;
|
|
/*
|
|
WCHAR wszUrl[1024];
|
|
LPWSTR pwszUrl = NULL;
|
|
BYTE utf8Url[2048];
|
|
LPBYTE pbUrl = NULL;
|
|
*/
|
|
LPSTR pszObject = _RequestHeaders.ObjectName();
|
|
DWORD dwObjectLength = _RequestHeaders.ObjectNameLength();
|
|
|
|
if (pRequest == NULL) {
|
|
goto quit;
|
|
}
|
|
|
|
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
|
|
//
|
|
|
|
//#ifdef COMPRESSED_HEADERS
|
|
// if (vfCompressedHeaders) {
|
|
// DEBUG_PRINT(HTTP,
|
|
// INFO,
|
|
// ("Compressing Headers")
|
|
// );
|
|
// _RequestHeaders.CopyCompressedHeaders(&buffer);
|
|
//
|
|
// }
|
|
// else
|
|
//#endif //COMPRESSED_HEADERS
|
|
{
|
|
_RequestHeaders.CopyHeaders(&buffer, pszObject, dwObjectLength);
|
|
}
|
|
|
|
//
|
|
// terminate the request
|
|
//
|
|
|
|
*buffer++ = '\r';
|
|
*buffer++ = '\n';
|
|
|
|
//#ifdef COMPRESSED_HEADERS
|
|
// if (vfCompressedHeaders) {
|
|
//
|
|
// *lpdwRequestLength = ((DWORD)buffer - (DWORD)requestBuffer);
|
|
//
|
|
// DEBUG_PRINT(HTTP,
|
|
// INFO,
|
|
// ("Compressed Headers: Old Length=%d, New Length = %d, Saved=%d\n",
|
|
// requestLength,
|
|
// *lpdwRequestLength,
|
|
// requestLength - *lpdwRequestLength
|
|
// )
|
|
// );
|
|
// } else {
|
|
//#endif //COMPRESSED_HEADERS
|
|
|
|
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;
|
|
|
|
//#ifdef COMPRESSED_HEADERS
|
|
// }
|
|
//#endif
|
|
}
|
|
|
|
quit:
|
|
|
|
_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);
|
|
}
|
|
*/
|
|
|
|
PERF_LEAVE(CreateRequestBuffer);
|
|
|
|
DEBUG_LEAVE(requestBuffer);
|
|
|
|
return requestBuffer;
|
|
}
|
|
|
|
|
|
DWORD
|
|
HTTP_REQUEST_HANDLE_OBJECT::QueryRequestHeader(
|
|
IN LPSTR 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_INTERNET_INCORRECT_FORMAT
|
|
Can't convert the data to the requested format
|
|
|
|
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_INTERNET_INCORRECT_FORMAT
|
|
Can't convert the data to the requested format
|
|
|
|
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;
|
|
|
|
//
|
|
// 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);
|
|
}
|
|
|
|
//
|
|
// 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
|
|
//
|
|
|
|
_ResponseHeaders.LockHeaders();
|
|
|
|
//
|
|
// 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()) {
|
|
|
|
#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)
|
|
//
|
|
|
|
//
|
|
// Due to possible DoS attacks outlined in RAID item 510295 we are to
|
|
//be more stringent as to what sort of 'noise' is allowed before the start
|
|
//of an HTTP response. We now allow the noise to consist of at most
|
|
//8 characters of whitespace or '\0'. If the content-length is slightly
|
|
//off because of a sloppy server, this should skip whatever terminators
|
|
//the server expected us to use.
|
|
//
|
|
|
|
const DWORD c_dwSmallestAcceptableStatusLength = ARRAY_ELEMENTS("HTTP/1.1 100\r\n") - 1;
|
|
const DWORD c_dwMaxNoiseAllowed = 8;
|
|
const DWORD c_dwMaxPreHTTPLength = ARRAY_ELEMENTS("Secure-HTTP/")-1;
|
|
|
|
LPSTR lpszBuf;
|
|
DWORD bytesLeft;
|
|
|
|
lpszBuf = lpszBuffer;
|
|
bytesLeft = dwBytesReceived;
|
|
|
|
//
|
|
// Check that we've read enough bytes for a status line to be ready.
|
|
//
|
|
if ((dwBytesReceived < c_dwSmallestAcceptableStatusLength) && !IsEof())
|
|
{
|
|
goto done;
|
|
}
|
|
|
|
//
|
|
// Allow up to c_dwMaxNoiseAllowed bytes worth of noise
|
|
//
|
|
int noiseBytesLeft = min(bytesLeft, c_dwMaxNoiseAllowed);
|
|
int noiseBytesScanned = 0;
|
|
while ((noiseBytesLeft > 0)
|
|
&& (isspace((unsigned char)*lpszBuf)
|
|
|| *lpszBuffer == '\0'))
|
|
{
|
|
++lpszBuf;
|
|
--bytesLeft;
|
|
--noiseBytesLeft;
|
|
++noiseBytesScanned;
|
|
}
|
|
|
|
//
|
|
// 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);
|
|
_ResponseScanned += noiseBytesScanned;
|
|
|
|
//
|
|
// we have start of this response
|
|
//
|
|
|
|
lpszBuffer = lpszBuf;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!IsUpLevel())
|
|
{
|
|
//
|
|
// if we didn't find the start of a valid HTTP response and we have
|
|
// not filled the response buffer sufficiently then allow
|
|
// re-entry to retry.
|
|
//
|
|
// if we didn't find the start of a valid HTTP response and we
|
|
//have filled the buffer sufficiently to expect the response,
|
|
//report the response as invalid.
|
|
//
|
|
|
|
if ((bytesLeft < c_dwMaxPreHTTPLength ) && !IsEof())
|
|
{
|
|
goto done;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// WinHTTP only accepts IsUpLevel() type responses.
|
|
//
|
|
|
|
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 quit;
|
|
}
|
|
|
|
if (!statusLine->HaveString())
|
|
{
|
|
BOOL fNeedMoreBuffer;
|
|
int majorVersion = 0;
|
|
int minorVersion = 0;
|
|
BOOL fSupportsHttp1_1;
|
|
|
|
_StatusCode = 0;
|
|
|
|
//
|
|
// Parse the status line. It has already been checked up to the first '/'
|
|
//
|
|
|
|
error = _ResponseHeaders.ParseStatusLine(
|
|
(LPSTR)_ResponseBuffer,
|
|
_BytesReceived,
|
|
IsEof(),
|
|
&_ResponseScanned,
|
|
&fNeedMoreBuffer,
|
|
&_StatusCode,
|
|
(LPDWORD)&majorVersion,
|
|
(LPDWORD)&minorVersion
|
|
);
|
|
|
|
if (error != ERROR_SUCCESS)
|
|
{
|
|
goto quit;
|
|
}
|
|
|
|
if (fNeedMoreBuffer)
|
|
{
|
|
error = ERROR_SUCCESS;
|
|
goto quit;
|
|
}
|
|
|
|
|
|
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
|
|
&& minorVersion >= 1)
|
|
{
|
|
fSupportsHttp1_1 = TRUE;
|
|
}
|
|
|
|
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(GlobalMaxConnectionsPerServer);
|
|
} else {
|
|
pServerInfo->SetHttp1_0();
|
|
|
|
//
|
|
// up the connection limit from HTTP 1.1 (default 2) to
|
|
// HTTP 1.0 (default 4)
|
|
//
|
|
|
|
pServerInfo->SetNewLimit(GlobalMaxConnectionsPer1_0Server);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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 quit;
|
|
}
|
|
|
|
|
|
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()) {
|
|
CheckWellKnownHeaders();
|
|
if (ERROR_SUCCESS != error)
|
|
{
|
|
goto quit;
|
|
}
|
|
*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();
|
|
}
|
|
}
|
|
}
|
|
|
|
error = ERROR_SUCCESS;
|
|
|
|
quit:
|
|
|
|
//
|
|
// 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();
|
|
|
|
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_INTERNET_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_INTERNET_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
|
|
VOID
|
|
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:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
None,
|
|
"HTTP_REQUEST_HANDLE_OBJECT::CheckWellKnownHeaders",
|
|
NULL
|
|
));
|
|
|
|
//
|
|
// check for "Content-Length:" and "Content-Range"
|
|
//
|
|
|
|
if ( IsResponseHeaderPresent(HTTP_QUERY_CONTENT_RANGE) )
|
|
{
|
|
_iSlotContentRange = _ResponseHeaders._bKnownHeaders[HTTP_QUERY_CONTENT_RANGE];
|
|
}
|
|
|
|
if ( IsResponseHeaderPresent(HTTP_QUERY_CONTENT_LENGTH) )
|
|
{
|
|
HEADER_STRING * curHeader;
|
|
DWORD dwHeaderLength;
|
|
LPSTR lpszHeader;
|
|
|
|
_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)
|
|
//
|
|
|
|
// darrenmi - commenting out because we're hitting it in stress.
|
|
// headers coming from the cache have an extra space in them in
|
|
// some circumstances. Investigating seperately.
|
|
// 2/25/00
|
|
|
|
// 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 "Refresh"
|
|
//
|
|
|
|
if ( IsResponseHeaderPresent(HTTP_QUERY_REFRESH)
|
|
&& !IsRefresh() )
|
|
{
|
|
|
|
DEBUG_PRINT(HTTP,
|
|
INFO,
|
|
("have \"Refresh:\" header\n"
|
|
));
|
|
|
|
SetRefresh(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))
|
|
{
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
DEBUG_LEAVE(0);
|
|
}
|
|
|
|
|
|
//
|
|
// 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
|
|
|
|
//
|
|
//DWORD
|
|
//CreateEscapedUrlPath(
|
|
// IN LPSTR lpszUrlPath,
|
|
// OUT LPSTR * lplpszEncodedUrlPath
|
|
// )
|
|
//
|
|
///*++
|
|
//
|
|
//Routine Description:
|
|
//
|
|
// Given an URL-path, encodes it into a new buffer
|
|
//
|
|
//Arguments:
|
|
//
|
|
// lpszUrlPath - URL-path to encode
|
|
//
|
|
// lplpszEncodedUrlPath - pointer to returned allocated buffer containing
|
|
// escaped URL-path
|
|
//
|
|
//Return Value:
|
|
//
|
|
// DWORD
|
|
// Success - ERROR_SUCCESS
|
|
//
|
|
// Failure -
|
|
//
|
|
//--*/
|
|
//
|
|
//{
|
|
// LPSTR lpszEncodedUrlPath = NULL;
|
|
// DWORD urlPathLength;
|
|
// DWORD encodedUrlPathLength;
|
|
// DWORD error;
|
|
//
|
|
// //
|
|
// // we need to encode the URL-path into a separate buffer (it may grow)
|
|
// //
|
|
//
|
|
// urlPathLength = strlen(lpszUrlPath);
|
|
// encodedUrlPathLength = INTERNET_MAX_PATH_LENGTH;
|
|
//
|
|
// do {
|
|
//
|
|
// //
|
|
// // we allow ourselves to fail due to insufficient buffer (at least once)
|
|
// //
|
|
//
|
|
// lpszEncodedUrlPath = (LPSTR)ResizeBuffer(lpszEncodedUrlPath,
|
|
// encodedUrlPathLength,
|
|
// FALSE
|
|
// );
|
|
// if (lpszEncodedUrlPath != NULL) {
|
|
//
|
|
// DWORD previousLength = encodedUrlPathLength;
|
|
//
|
|
// error = EncodeUrlPath(NO_ENCODE_PATH_SEP,
|
|
//
|
|
// //
|
|
// // BUGBUG - assuming HTTP
|
|
// //
|
|
//
|
|
// SCHEME_HTTP,
|
|
// lpszUrlPath,
|
|
// urlPathLength,
|
|
// lpszEncodedUrlPath,
|
|
// &encodedUrlPathLength
|
|
// );
|
|
//
|
|
// if ((error == ERROR_INSUFFICIENT_BUFFER)
|
|
// && (previousLength >= encodedUrlPathLength)) {
|
|
//
|
|
// //
|
|
// // this should never happen, but we will avoid a loop if it does
|
|
// //
|
|
//
|
|
// INET_ASSERT(FALSE);
|
|
//
|
|
// error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
// }
|
|
// } else {
|
|
//
|
|
// //
|
|
// // failed to (re)alloc
|
|
// //
|
|
//
|
|
// error = ERROR_NOT_ENOUGH_MEMORY;
|
|
// }
|
|
// } while (error == ERROR_INSUFFICIENT_BUFFER);
|
|
//
|
|
// *lplpszEncodedUrlPath = lpszEncodedUrlPath;
|
|
//
|
|
// return error;
|
|
//}
|
|
|
|
PRIVATE
|
|
BOOL
|
|
FMatchList(
|
|
LPSTR *lplpList,
|
|
DWORD cListLen,
|
|
HEADER_STRING *lpHeader,
|
|
LPSTR lpBase
|
|
)
|
|
{
|
|
DWORD i;
|
|
for (i=0; i < cListLen; ++i) {
|
|
if (!lpHeader->Strnicmp(lpBase, lplpList[i], strlen(lplpList[i]))) {
|
|
return (TRUE);
|
|
}
|
|
}
|
|
return(FALSE);
|
|
}
|
|
|
|
#ifdef COMPRESSED_HEADERS
|
|
DWORD
|
|
LookupHeadermap(
|
|
LPSTR lpszHeader
|
|
)
|
|
{
|
|
DWORD top, mid, bottom, ret = 0;
|
|
int cmp;
|
|
LPSTR lpszColon;
|
|
|
|
lpszColon = strchr(lpszHeader, ':');
|
|
|
|
if (!lpszColon || (lpszColon == lpszHeader)) {
|
|
return(ret);
|
|
}
|
|
|
|
// yuk
|
|
*lpszColon = 0;
|
|
|
|
top = 1;
|
|
|
|
bottom = sizeof(rgsHeaderMap)/sizeof(HEADER_MAP);
|
|
|
|
INET_ASSERT(bottom >= top);
|
|
|
|
do {
|
|
|
|
mid = (bottom+top)/2;
|
|
if (!(cmp = stricmp( rgsHeaderMap[mid].lpszLongHeader,
|
|
lpszHeader
|
|
))) {
|
|
// we found a matching header,
|
|
ret = mid;
|
|
break;
|
|
}
|
|
|
|
if (cmp > 0) {
|
|
// the mid header is larger than the passed in header
|
|
// so we must check at the upper end of the sorted array of headers
|
|
bottom = mid-1;
|
|
}
|
|
else {
|
|
// the mid header is smaller than the passed in header
|
|
// so we must check at the lower end of the sorted array of headers
|
|
top = mid+1;
|
|
}
|
|
} while (bottom >= top);
|
|
|
|
*lpszColon = ':';
|
|
return (ret);
|
|
}
|
|
#endif //COMPRESSED_HEADERS
|
|
|
|
|
|
//
|
|
// HTTP_HEADER_PARSER implementation
|
|
//
|
|
|
|
HTTP_HEADER_PARSER::HTTP_HEADER_PARSER(
|
|
IN LPSTR szHeaders,
|
|
IN DWORD cbHeaders
|
|
) : HTTP_HEADERS()
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Constructor for the HTTP_HEADER_PARSER object. Calls ParseHeaders to
|
|
build a parsed version of the header string passed in.
|
|
|
|
Arguments:
|
|
|
|
szHeaders - pointer to the headers to parse
|
|
|
|
cbHeaders - length of the headers
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD dwBytesScaned = 0;
|
|
BOOL fFoundCompleteLine;
|
|
BOOL fFoundEndOfHeaders;
|
|
DWORD error;
|
|
|
|
error = ParseHeaders(
|
|
szHeaders,
|
|
cbHeaders,
|
|
TRUE, // Eof
|
|
&dwBytesScaned,
|
|
&fFoundCompleteLine,
|
|
&fFoundEndOfHeaders
|
|
);
|
|
|
|
INET_ASSERT(error == ERROR_SUCCESS);
|
|
INET_ASSERT(fFoundCompleteLine);
|
|
INET_ASSERT(fFoundEndOfHeaders);
|
|
}
|
|
|
|
|
|
/* // some test cases which can be used to test ParseStatusLine()
|
|
char bad1[] = "HTTP1.1 200 Description yeah yeah\r\n";
|
|
char bad2[] = "HTTP/1234.1 200 Description yeah yeah\r\n";
|
|
char bad3[] = "HTTP/1.1234 200 Description yeah yeah\r\n";
|
|
char bad4[] = "HTTP/1.1 1234 Description yeah yeah\r\n";
|
|
char bad5[] = "HTTP/ 1.1 200 Description yeah yeah\r\n";
|
|
char bad6[] = "HTTP/1.1 200 Description yeah yeah\r\n";
|
|
char bad7[] = "HTTP/1.1 200Description yeah yeah\r\n";
|
|
char bad8[3000] = "HTTP/1.1 200 Description yeah yeah";
|
|
char bad9[] = "HTTP/1 1.1 200 Description yeah yeah\r\n";
|
|
char good1[] = "HTTP/ 123.123 200 Description yeah yeah\r\n";
|
|
*/
|
|
|
|
|
|
DWORD
|
|
HTTP_HEADER_PARSER::ParseStatusLine(
|
|
IN LPSTR lpHeaderBase,
|
|
IN DWORD dwBufferLength,
|
|
IN BOOL fEof,
|
|
IN OUT DWORD *lpdwBufferLengthScanned,
|
|
OUT BOOL *lpfNeedMoreBuffer,
|
|
OUT DWORD *lpdwStatusCode,
|
|
OUT DWORD *lpdwMajorVersion,
|
|
OUT DWORD *lpdwMinorVersion
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Parses the Status line of an HTTP server response. Takes care of adding the status
|
|
line to HTTP header array.
|
|
|
|
From HTTP v1.1. spec:
|
|
{
|
|
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
|
|
HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT
|
|
Status-Code = 1*DIGIT
|
|
Reason-Phrase = *<TEXT, excluding CR LF>
|
|
(1*DIGIT means at least one digit, maybe more)
|
|
}
|
|
|
|
WinHTTP strictly enforces the status line spec. The only exception is that up to 3
|
|
spaces are allowed before the Status-Code and major version number.
|
|
|
|
To prevent malicious servers from hogging the channel, the integers are limited to
|
|
3 digits and the Reason-Phrase is limited to GlobalMaxSizeStatusLineResultText characters.
|
|
|
|
"HTTP" could be other things like "S-HTTP", this is checked by UpdateFromHeaders()
|
|
before ParseStatusLine() is called. The existence of the first '/' is verified before
|
|
ParseStatusLine is called.
|
|
|
|
Arguments:
|
|
|
|
lpszHeader - pointer to the header to check
|
|
|
|
dwHeaderLength - length of the header
|
|
|
|
Return Value:
|
|
|
|
BOOL - TRUE if line was successively parsed and processed, FALSE otherwise
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
#define BEFORE_VERSION_NUMBERS 0
|
|
#define MAJOR_VERSION_NUMBER 1
|
|
#define MINOR_VERSION_NUMBER 2
|
|
#define STATUS_CODE_NUMBER 3
|
|
#define AFTER_STATUS_CODE 4
|
|
#define MAX_STATUS_INTS 4
|
|
|
|
LPSTR lpszEnd = lpHeaderBase + dwBufferLength;
|
|
LPSTR response = lpHeaderBase + *lpdwBufferLengthScanned;
|
|
DWORD dwBytesScanned = 0;
|
|
DWORD dwStatusLineLength = 0;
|
|
LPSTR lpszStatusLine = NULL;
|
|
int ver_state = BEFORE_VERSION_NUMBERS;
|
|
BOOL afStatusIntsFound[MAX_STATUS_INTS] = {0};
|
|
DWORD adwStatusInts[MAX_STATUS_INTS] = {0};
|
|
DWORD dwStatusPieceLength = 0;
|
|
BOOL error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
|
|
lpszStatusLine = response;
|
|
|
|
//
|
|
// While walking the Status Line looking for terminating \r\n,
|
|
// we extract the Major.Minor Versions and Status Code in that order.
|
|
// text and spaces will lie between/before/after the three numbers
|
|
// but the idea is to remeber which number we're calculating based on a numeric state
|
|
// If all goes well the loop will churn out an array with the 3 numbers plugged in as DWORDs
|
|
//
|
|
|
|
while ((response < lpszEnd) && (*response != '\r') && (*response != '\n'))
|
|
{
|
|
switch (ver_state)
|
|
{
|
|
case BEFORE_VERSION_NUMBERS:
|
|
//
|
|
// We've already matched the status line with something
|
|
//of the form "****/" in UpdateFromHeaders(), we can ignore everything
|
|
//through the first '/'.
|
|
//
|
|
if (*response == '/')
|
|
{
|
|
INET_ASSERT(ver_state == BEFORE_VERSION_NUMBERS);
|
|
ver_state++; // = MAJOR_VERSION_NUMBER
|
|
dwStatusPieceLength = 0; // next piece is either spaces or an int
|
|
}
|
|
|
|
break;
|
|
|
|
case MAJOR_VERSION_NUMBER:
|
|
|
|
if (*response == '.' && ver_state == MAJOR_VERSION_NUMBER)
|
|
{
|
|
ver_state++; // = MINOR_VERSION_NUMBER
|
|
dwStatusPieceLength = 0; // next piece is an int
|
|
break;
|
|
}
|
|
// fall through
|
|
|
|
case MINOR_VERSION_NUMBER:
|
|
|
|
if (*response == ' ' && ver_state == MINOR_VERSION_NUMBER)
|
|
{
|
|
ver_state++; // = STATUS_CODE_NUMBER
|
|
dwStatusPieceLength = 0; // next piece is either spaces or an int.
|
|
break;
|
|
}
|
|
// fall through
|
|
|
|
case STATUS_CODE_NUMBER:
|
|
|
|
if (isdigit(*response))
|
|
{
|
|
if (!afStatusIntsFound[ver_state])
|
|
{
|
|
// transitioning from counting spaces
|
|
//to counting integers
|
|
dwStatusPieceLength = 0;
|
|
}
|
|
|
|
// Allow up to 3 digits per integer.
|
|
if (++dwStatusPieceLength > 3)
|
|
goto doneInvalidStatusLine;
|
|
|
|
int val = *response - '0';
|
|
afStatusIntsFound[ver_state] = TRUE;
|
|
adwStatusInts[ver_state] = adwStatusInts[ver_state] * 10 + val;
|
|
}
|
|
else if ( adwStatusInts[STATUS_CODE_NUMBER] > 0 )
|
|
{
|
|
INET_ASSERT(ver_state == STATUS_CODE_NUMBER);
|
|
if (*response != ' ')
|
|
goto doneInvalidStatusLine;
|
|
ver_state++; // = AFTER_STATUS_CODE
|
|
dwStatusPieceLength = 0; // next piece is the status line
|
|
break;
|
|
}
|
|
else if (*response == ' ' && !afStatusIntsFound[ver_state])
|
|
{
|
|
//
|
|
// Before processing MAJOR_VERSION_NUMBER or STATUS_CODE_NUMBER,
|
|
//allow up to 3 spaces.
|
|
//
|
|
// Multiple spaces are being allowed here because it is
|
|
//legacy behavior and may therefore be necessary, and being non-strict
|
|
//about it doesn't put anything at risk.
|
|
//
|
|
if (++dwStatusPieceLength > 3)
|
|
goto doneInvalidStatusLine;
|
|
}
|
|
else
|
|
{
|
|
// We fail if anything outside the spec is found, except
|
|
//for allowing multiple spaces before the status code.
|
|
goto doneInvalidStatusLine;
|
|
}
|
|
|
|
break;
|
|
|
|
case AFTER_STATUS_CODE:
|
|
//
|
|
// This will advance to the next CR or LF..
|
|
//
|
|
// We limit Reason-Phrase length to protect against malicious socket hogging
|
|
//
|
|
|
|
if (++dwStatusPieceLength > GlobalMaxSizeStatusLineResultText)
|
|
{
|
|
goto doneInvalidStatusLine;
|
|
}
|
|
break;
|
|
|
|
}
|
|
|
|
++response;
|
|
++dwBytesScanned;
|
|
}
|
|
|
|
//
|
|
// Verify there is enough reponse left to check for a CRLF..
|
|
//
|
|
if (response == lpszEnd
|
|
|| response + 1 == lpszEnd)
|
|
{
|
|
//
|
|
//
|
|
// If we're at the end of the connection then the server sent us an
|
|
//incorrectly formatted response, Invalid status line.
|
|
//
|
|
// If more data may come, indicate to retry it.
|
|
//
|
|
|
|
DEBUG_PRINT(HTTP,
|
|
INFO,
|
|
("found end of short response in status line\n"
|
|
));
|
|
|
|
if (fEof)
|
|
goto doneInvalidStatusLine;
|
|
else
|
|
goto doneNeedMoreData;
|
|
}
|
|
|
|
dwStatusLineLength = dwBytesScanned;
|
|
|
|
//
|
|
// And to finish, verify the CRLF...
|
|
//
|
|
|
|
//CR
|
|
// some servers may not send the CR so give them a pass for compatibility
|
|
if ( *response == '\r')
|
|
{
|
|
++response; // we know its safe to step since we checked if (respone == lpszEnd) above.
|
|
++dwBytesScanned;
|
|
}
|
|
|
|
//LF
|
|
if ( *response != '\n')
|
|
goto doneInvalidStatusLine;
|
|
++response; // we know its safe to step again since we checked if (respone+1 == lpszEnd) above.
|
|
++dwBytesScanned;
|
|
|
|
//
|
|
// Some validation checking
|
|
//
|
|
// All three status ints must have been found.
|
|
// I found some code that assumes that if the Status Code == 0, then
|
|
//the status line hasn't been parsed yet. To be sure this assumption
|
|
//remains true, explicitly reject status lines with a 0 status code.
|
|
//
|
|
if (afStatusIntsFound[MAJOR_VERSION_NUMBER] != TRUE
|
|
|| afStatusIntsFound[MINOR_VERSION_NUMBER] != TRUE
|
|
|| afStatusIntsFound[STATUS_CODE_NUMBER] != TRUE
|
|
|| adwStatusInts[STATUS_CODE_NUMBER] == 0)
|
|
{
|
|
goto doneInvalidStatusLine;
|
|
}
|
|
|
|
//
|
|
// Now we have our parsed header to add to the array
|
|
//
|
|
|
|
HEADER_STRING * freeHeader;
|
|
DWORD iSlot;
|
|
|
|
freeHeader = FindFreeSlot(&iSlot);
|
|
if (freeHeader == NULL) {
|
|
goto doneFailError;
|
|
} else {
|
|
INET_ASSERT(iSlot == 0); // status line should always be first
|
|
freeHeader->CreateOffsetString((DWORD)(lpszStatusLine - lpHeaderBase), dwStatusLineLength);
|
|
freeHeader->SetHash(0); // status line has no hash value.
|
|
}
|
|
|
|
//
|
|
// Success.. fill in the output params appropriately.
|
|
//
|
|
|
|
*lpfNeedMoreBuffer = FALSE;
|
|
*lpdwStatusCode = adwStatusInts[STATUS_CODE_NUMBER];
|
|
*lpdwMajorVersion = adwStatusInts[MAJOR_VERSION_NUMBER];
|
|
*lpdwMinorVersion = adwStatusInts[MINOR_VERSION_NUMBER];
|
|
|
|
*lpdwBufferLengthScanned += dwBytesScanned;
|
|
|
|
error = ERROR_SUCCESS;
|
|
goto exitFinalReturn;
|
|
|
|
doneNeedMoreData:
|
|
error = ERROR_SUCCESS;
|
|
*lpfNeedMoreBuffer = TRUE;
|
|
goto exitFinalReturn;
|
|
|
|
doneInvalidStatusLine:
|
|
error = ERROR_HTTP_INVALID_SERVER_RESPONSE;
|
|
*lpfNeedMoreBuffer = FALSE;
|
|
goto exitFinalReturn;
|
|
|
|
doneFailError:
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
*lpfNeedMoreBuffer = FALSE;
|
|
goto exitFinalReturn;
|
|
|
|
exitFinalReturn:
|
|
return error;
|
|
}
|
|
|
|
DWORD
|
|
HTTP_HEADER_PARSER::ParseHeaders(
|
|
IN LPSTR lpHeaderBase,
|
|
IN DWORD dwBufferLength,
|
|
IN BOOL fEof,
|
|
IN OUT DWORD *lpdwBufferLengthScanned,
|
|
OUT LPBOOL pfFoundCompleteLine,
|
|
OUT LPBOOL pfFoundEndOfHeaders
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Loads headers into HTTP_HEADERS member for subsequent parsing.
|
|
|
|
Parses string based headers and adds their parts to an internally stored
|
|
array of HTTP_HEADERS.
|
|
|
|
Input is assumed to be well formed Header Name/Value pairs, each deliminated
|
|
by ':' and '\r\n'.
|
|
|
|
Arguments:
|
|
|
|
lpszHeader - pointer to the header to check
|
|
|
|
dwHeaderLength - length of the header
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
|
|
LPSTR lpszEnd = lpHeaderBase + dwBufferLength;
|
|
LPSTR response = lpHeaderBase + *lpdwBufferLengthScanned;
|
|
DWORD dwBytesScanned = 0;
|
|
BOOL success = FALSE;
|
|
DWORD error = ERROR_SUCCESS;
|
|
|
|
*pfFoundEndOfHeaders = FALSE;
|
|
|
|
//
|
|
// Each iteration of the following loop
|
|
// walks an HTTP header line of the form:
|
|
// HeaderName: HeaderValue\r\n
|
|
//
|
|
|
|
do
|
|
{
|
|
DWORD dwHash = HEADER_HASH_SEED;
|
|
LPSTR lpszHeaderName;
|
|
DWORD dwHeaderNameLength = 0;
|
|
DWORD dwHeaderLineLength = 0;
|
|
DWORD dwPreviousAmountOfBytesScanned = dwBytesScanned;
|
|
|
|
//
|
|
// Remove leading whitespace from header
|
|
//
|
|
|
|
while ( (response < lpszEnd) && ((*response == ' ') || (*response == '\t')) )
|
|
{
|
|
++response;
|
|
++dwBytesScanned;
|
|
}
|
|
|
|
//
|
|
// Scan for HeaderName:
|
|
//
|
|
|
|
lpszHeaderName = response;
|
|
dwPreviousAmountOfBytesScanned = dwBytesScanned;
|
|
|
|
while ((response < lpszEnd) && (*response != ':') && (*response != '\r') && (*response != '\n'))
|
|
{
|
|
//
|
|
// This code incapsulates CalculateHashNoCase as an optimization,
|
|
// we attempt to calculate the Hash value as we parse the header.
|
|
//
|
|
|
|
CHAR ch = *response;
|
|
|
|
if ((ch >= 'A') && (ch <= 'Z')) {
|
|
ch = MAKE_LOWER(ch);
|
|
}
|
|
dwHash += (DWORD)(dwHash << 5) + ch;
|
|
|
|
++response;
|
|
++dwBytesScanned;
|
|
}
|
|
|
|
dwHeaderNameLength = (DWORD) (response - lpszHeaderName);
|
|
|
|
//
|
|
// catch bogus responses: if we find what looks like one of a (very)
|
|
// small set of HTML tags, then assume the previous header was the
|
|
// last
|
|
//
|
|
|
|
if ((dwHeaderNameLength >= sizeof("<HTML>") - 1)
|
|
&& (*lpszHeaderName == '<')
|
|
&& (!strnicmp(lpszHeaderName, "<HTML>", sizeof("<HTML>") - 1)
|
|
|| !strnicmp(lpszHeaderName, "<HEAD>", sizeof("<HEAD>") - 1))) {
|
|
*pfFoundEndOfHeaders = TRUE;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Keep scanning till end of the line.
|
|
//
|
|
|
|
while ((response < lpszEnd) && (*response != '\r') && (*response != '\n'))
|
|
{
|
|
++response;
|
|
++dwBytesScanned;
|
|
}
|
|
|
|
dwHeaderLineLength = (DWORD) (response - lpszHeaderName); // note: this headerLINElength
|
|
|
|
if (response == lpszEnd) {
|
|
|
|
//
|
|
// response now points one past the end of the buffer. We may be looking
|
|
// over the edge...
|
|
//
|
|
// if we're at the end of the connection then the server sent us an
|
|
// incorrectly formatted response. Probably an error.
|
|
//
|
|
// Otherwise its a partial response. We need more
|
|
//
|
|
|
|
|
|
DEBUG_PRINT(HTTP,
|
|
INFO,
|
|
("found end of short response\n"
|
|
));
|
|
|
|
success = fEof ? TRUE : FALSE;
|
|
|
|
//
|
|
// if we really hit the end of the response then update the amount of
|
|
// headers scanned
|
|
//
|
|
|
|
if (!success) {
|
|
dwBytesScanned = dwPreviousAmountOfBytesScanned;
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
//
|
|
// we reached a CR or LF. This is the end of this current header. Find
|
|
// the start of the next one
|
|
//
|
|
|
|
//
|
|
// first, strip off any trailing spaces from the current header. We do
|
|
// this by simply reducing the string length. We only look for space
|
|
// and tab characters. Only do this if we have a non-zero length header
|
|
//
|
|
|
|
if (dwHeaderLineLength != 0) {
|
|
for (int i = -1; response[i] == ' ' || response[i] == '\t'; --i) {
|
|
--dwHeaderLineLength;
|
|
}
|
|
}
|
|
|
|
INET_ASSERT((int)dwHeaderLineLength >= 0);
|
|
|
|
//
|
|
// some servers respond with "\r\r\n". Lame
|
|
// A new twist: "\r \r\n". Lamer
|
|
//
|
|
|
|
while ((response < lpszEnd)
|
|
&& ((*response == '\r') || (*response == ' '))) {
|
|
++response;
|
|
++dwBytesScanned;
|
|
}
|
|
if (response == lpszEnd) {
|
|
|
|
//
|
|
// hit end of buffer without finding LF
|
|
//
|
|
|
|
success = FALSE;
|
|
|
|
DEBUG_PRINT(HTTP,
|
|
WARNING,
|
|
("hit end of buffer without finding LF\n"
|
|
));
|
|
|
|
//
|
|
// get more data, reparse this line
|
|
//
|
|
|
|
dwBytesScanned = dwPreviousAmountOfBytesScanned;
|
|
break;
|
|
} else if (*response == '\n') {
|
|
++response;
|
|
++dwBytesScanned;
|
|
|
|
//
|
|
// if we found the empty line then we are done
|
|
//
|
|
|
|
if (dwHeaderLineLength == 0) {
|
|
*pfFoundEndOfHeaders = TRUE;
|
|
break;
|
|
}
|
|
|
|
success = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
// If a header name has no trailing colon ignore it
|
|
|
|
if (lpszHeaderName[dwHeaderNameLength] != ':')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Now we have our parsed header to add to the array
|
|
//
|
|
|
|
HEADER_STRING * freeHeader;
|
|
DWORD iSlot;
|
|
|
|
freeHeader = FindFreeSlot(&iSlot);
|
|
if (freeHeader == NULL) {
|
|
error = GetError();
|
|
|
|
INET_ASSERT(error != ERROR_SUCCESS);
|
|
goto quit;
|
|
|
|
} else {
|
|
freeHeader->CreateOffsetString((DWORD) (lpszHeaderName - lpHeaderBase), dwHeaderLineLength);
|
|
freeHeader->SetHash(dwHash);
|
|
}
|
|
|
|
|
|
//CHAR szTemp[256];
|
|
//
|
|
//memcpy(szTemp, lpszHeaderName, dwHeaderLineLength);
|
|
//lpszHeaderName[dwHeaderLineLength] = '\0';
|
|
|
|
//DEBUG_PRINT(HTTP,
|
|
// INFO,
|
|
// ("ParseHeaders: adding=%q\n", lpszHeaderName
|
|
// ));
|
|
|
|
|
|
//
|
|
// Now see if this is a known header we are adding, if so then we note that fact
|
|
//
|
|
|
|
DWORD dwKnownQueryIndex;
|
|
|
|
if (HeaderMatch(dwHash, lpszHeaderName, dwHeaderNameLength, &dwKnownQueryIndex) )
|
|
{
|
|
freeHeader->SetNextKnownIndex(FastAdd(dwKnownQueryIndex, iSlot));
|
|
}
|
|
} while (TRUE);
|
|
|
|
quit:
|
|
|
|
*lpdwBufferLengthScanned += dwBytesScanned;
|
|
*pfFoundCompleteLine = success;
|
|
|
|
return error;
|
|
}
|
|
|
|
#if 0
|
|
//
|
|
// Slower version of the function above used for performance work!!! Keep around
|
|
// until we're sure it will never be used.
|
|
//
|
|
|
|
DWORD
|
|
HTTP_HEADER_PARSER::ParseHeaders(
|
|
IN LPSTR lpHeaderBase,
|
|
IN DWORD dwBufferLength,
|
|
IN BOOL fEof,
|
|
IN OUT DWORD *lpdwBufferLengthScanned,
|
|
OUT LPBOOL pfFoundCompleteLine,
|
|
OUT LPBOOL pfFoundEndOfHeaders
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Parses string based headers and adds their parts to an internally stored
|
|
array of HTTP_HEADERS.
|
|
|
|
|
|
Input is assumed to be well formed Header Name/Value pairs, each deliminated
|
|
by ':' and '\r\n'.
|
|
|
|
Arguments:
|
|
|
|
lpszHeader - pointer to the header to check
|
|
|
|
dwHeaderLength - length of the header
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
|
|
#define HTTP_HEADER_PARSE_LEADING_SPACE 0
|
|
#define HTTP_HEADER_PARSE_NAME 1
|
|
#define HTTP_HEADER_PARSE_VALUE 2
|
|
#define HTTP_HEADER_PARSE_CR 3
|
|
#define HTTP_HEADER_PARSE_LF 4
|
|
|
|
|
|
LPSTR lpszEnd = lpHeaderBase + dwBufferLength;
|
|
LPSTR response = lpHeaderBase + *lpdwBufferLengthScanned;
|
|
DWORD dwBytesScanned = 0;
|
|
BOOL success = FALSE;
|
|
DWORD error = ERROR_SUCCESS;
|
|
DWORD state = HTTP_HEADER_PARSE_LEADING_SPACE;
|
|
|
|
*pfFoundEndOfHeaders = FALSE;
|
|
|
|
//
|
|
// Each iteration of the following loop
|
|
// walks an HTTP header line of the form:
|
|
// HeaderName: HeaderValue\r\n
|
|
//
|
|
|
|
DWORD dwHash = HEADER_HASH_SEED;
|
|
LPSTR lpszHeaderName;
|
|
DWORD dwHeaderNameLength = 0;
|
|
DWORD dwHeaderLineLength = 0;
|
|
DWORD dwPreviousAmountOfBytesScanned = dwBytesScanned;
|
|
DWORD dwWhiteSpace = 0;
|
|
|
|
|
|
while ( (response < lpszEnd) )
|
|
{
|
|
switch (state)
|
|
{
|
|
|
|
case HTTP_HEADER_PARSE_LEADING_SPACE:
|
|
|
|
//
|
|
// Remove leading whitespace from header
|
|
//
|
|
|
|
if ( *response == ' ' ||
|
|
*response == '\t' )
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Scan for HeaderName:
|
|
//
|
|
|
|
state = HTTP_HEADER_PARSE_NAME;
|
|
lpszHeaderName = response;
|
|
dwPreviousAmountOfBytesScanned = dwBytesScanned;
|
|
|
|
// fall through
|
|
|
|
case HTTP_HEADER_PARSE_NAME:
|
|
|
|
switch (*response)
|
|
{
|
|
case ':':
|
|
//
|
|
// Now parse the Header Value
|
|
//
|
|
|
|
state = HTTP_HEADER_PARSE_VALUE;
|
|
dwHeaderNameLength = (DWORD) (response - lpszHeaderName);
|
|
break;
|
|
|
|
|
|
case '\r':
|
|
state = HTTP_HEADER_PARSE_CR;
|
|
// note: this is headerLINElength
|
|
dwHeaderLineLength = (DWORD) (response - lpszHeaderName);
|
|
break;
|
|
|
|
case '\n':
|
|
|
|
state = HTTP_HEADER_PARSE_LF;
|
|
// note: this is headerLINElength
|
|
dwHeaderLineLength = (DWORD) (response - lpszHeaderName);
|
|
goto Got_LF;
|
|
//break;
|
|
|
|
default:
|
|
{
|
|
CHAR ch = *response;
|
|
|
|
if ((ch >= 'A') && (ch <= 'Z')) {
|
|
ch = MAKE_LOWER(ch);
|
|
}
|
|
dwHash += (DWORD)(dwHash << 5) + ch;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case HTTP_HEADER_PARSE_VALUE:
|
|
|
|
switch ( *response )
|
|
{
|
|
case '\r':
|
|
state = HTTP_HEADER_PARSE_CR;
|
|
// note: this is headerLINElength
|
|
dwHeaderLineLength = (DWORD) (response - lpszHeaderName) - dwWhiteSpace;
|
|
break;
|
|
|
|
case '\n':
|
|
|
|
state = HTTP_HEADER_PARSE_LF;
|
|
// note: this is headerLINElength
|
|
dwHeaderLineLength = (DWORD) (response - lpszHeaderName) - dwWhiteSpace;
|
|
goto Got_LF;
|
|
|
|
case ' ':
|
|
case '\t':
|
|
// count whitespace at end of the line
|
|
dwWhiteSpace++;
|
|
break;
|
|
|
|
default:
|
|
dwWhiteSpace = 0;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case HTTP_HEADER_PARSE_CR:
|
|
|
|
if (*response == ' ' ||
|
|
*response == '\r' )
|
|
{
|
|
break;
|
|
}
|
|
|
|
state = HTTP_HEADER_PARSE_LF;
|
|
// fall through
|
|
|
|
case HTTP_HEADER_PARSE_LF:
|
|
|
|
Got_LF:
|
|
|
|
//
|
|
// if we found the empty line then we are done
|
|
//
|
|
|
|
success = TRUE;
|
|
|
|
if (dwHeaderLineLength == 0) {
|
|
++dwBytesScanned;
|
|
++response;
|
|
*pfFoundEndOfHeaders = TRUE;
|
|
goto quit;
|
|
}
|
|
|
|
|
|
{
|
|
//
|
|
// Now we have our parsed header to add to the array
|
|
//
|
|
|
|
HEADER_STRING * freeHeader;
|
|
DWORD iSlot;
|
|
|
|
freeHeader = FindFreeSlot(&iSlot);
|
|
if (freeHeader == NULL) {
|
|
error = GetError();
|
|
|
|
INET_ASSERT(error != ERROR_SUCCESS);
|
|
goto quit;
|
|
|
|
} else {
|
|
freeHeader->CreateOffsetString((lpszHeaderName - lpHeaderBase), dwHeaderLineLength);
|
|
freeHeader->SetHash(dwHash);
|
|
}
|
|
|
|
CHAR szTemp[256];
|
|
|
|
memcpy(szTemp, lpszHeaderName, dwHeaderLineLength);
|
|
szTemp[dwHeaderLineLength] = '\0';
|
|
|
|
DEBUG_PRINT(HTTP,
|
|
INFO,
|
|
("ParseHeaders: adding=%q\n", lpszHeaderName
|
|
));
|
|
|
|
|
|
//
|
|
// Now see if this is a known header we are adding, if so then we note that fact
|
|
//
|
|
|
|
DWORD dwKnownQueryIndex;
|
|
|
|
if (HeaderMatch(dwHash, lpszHeaderName, dwHeaderNameLength, &dwKnownQueryIndex) )
|
|
{
|
|
freeHeader->SetNextKnownIndex(FastAdd(dwKnownQueryIndex, iSlot));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Move on to our next header.
|
|
//
|
|
|
|
dwHash = HEADER_HASH_SEED;
|
|
dwHeaderNameLength = 0;
|
|
dwHeaderLineLength = 0;
|
|
dwPreviousAmountOfBytesScanned = dwBytesScanned;
|
|
dwWhiteSpace = 0;
|
|
|
|
state = HTTP_HEADER_PARSE_LEADING_SPACE;
|
|
|
|
break;
|
|
|
|
} // switch (state)
|
|
|
|
++response;
|
|
++dwBytesScanned;
|
|
|
|
} // while (response < lpszEnd)
|
|
|
|
|
|
//
|
|
// response now points one past the end of the buffer. We may be looking
|
|
// over the edge...
|
|
//
|
|
// if we're at the end of the connection then the server sent us an
|
|
// incorrectly formatted response. Probably an error.
|
|
//
|
|
// Otherwise its a partial response. We need more
|
|
//
|
|
|
|
|
|
DEBUG_PRINT(HTTP,
|
|
INFO,
|
|
("found end of short response\n"
|
|
));
|
|
|
|
success = fEof ? TRUE : FALSE;
|
|
|
|
//
|
|
// if we really hit the end of the response then update the amount of
|
|
// headers scanned
|
|
//
|
|
|
|
if (!success) {
|
|
dwBytesScanned = dwPreviousAmountOfBytesScanned;
|
|
}
|
|
|
|
quit:
|
|
|
|
*lpdwBufferLengthScanned += dwBytesScanned;
|
|
*pfFoundCompleteLine = success;
|
|
|
|
return error;
|
|
}
|
|
|
|
#endif
|