1479 lines
33 KiB
C++
1479 lines
33 KiB
C++
/*++
|
|
|
|
Copyright (c) 2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
http.cxx
|
|
|
|
Abstract:
|
|
|
|
HTTP client functions
|
|
|
|
Revision History:
|
|
|
|
08/08/2000 davidx
|
|
Created it.
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
#include <stdio.h>
|
|
#include "httptime.h"
|
|
|
|
//
|
|
// Default HTTP string constants
|
|
//
|
|
const WCHAR WininetImpl::HttpDefaultVerbStr[] = L"GET";
|
|
const WCHAR WininetImpl::HttpDefaultVersionStr[] = HTTP_VERSION;
|
|
const WCHAR WininetImpl::HttpDefaultObjectStr[] = L"/";
|
|
static const WCHAR HttpSchemeStr[] = L"http://";
|
|
|
|
|
|
InternetObject*
|
|
InternetObject::Create(
|
|
const WCHAR* userAgent,
|
|
const WCHAR* proxyServer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Instantiate a new internet object
|
|
|
|
Arguments:
|
|
|
|
userAgent - Specifies the user agent
|
|
proxyServer - Specifies the proxy server name
|
|
|
|
Return Value:
|
|
|
|
Pointer to the newly created internet object
|
|
NULL if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
static const WCHAR defaultUserAgent[] = L"XBox HTTP Client Library";
|
|
InternetObject* inetobj;
|
|
WSADATA wsadata;
|
|
INT err;
|
|
|
|
// Instantiate the object
|
|
inetobj = new InternetObject();
|
|
if (!inetobj) goto failed;
|
|
|
|
// Startup winsock
|
|
err = WSAStartup(WINSOCK_VERSION, &wsadata);
|
|
if (err != NO_ERROR) {
|
|
SetLastError(err);
|
|
goto failed;
|
|
}
|
|
inetobj->wsastartup = TRUE;
|
|
|
|
// Save user-agent string
|
|
if (!userAgent) userAgent = defaultUserAgent;
|
|
inetobj->userAgent = strdupWtoA(userAgent);
|
|
if (!inetobj->userAgent) goto failed;
|
|
|
|
// Save proxy server name and resolve its IP address
|
|
if (proxyServer) {
|
|
inetobj->proxyServer = strdupWtoA(proxyServer);
|
|
if (!inetobj->proxyServer) goto failed;
|
|
|
|
inetobj->proxyServerAddr = ResolveHostAddr(inetobj->proxyServer);
|
|
if (inetobj->proxyServerAddr.s_addr == 0) goto failed;
|
|
}
|
|
|
|
return inetobj;
|
|
|
|
failed:
|
|
WARNING_("InternetOpen failed: %d", GetLastError());
|
|
delete inetobj;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
ConnectObject*
|
|
ConnectObject::Create(
|
|
InternetObject* inetobj,
|
|
const WCHAR* serverName,
|
|
INTERNET_PORT serverPort,
|
|
const WCHAR* username,
|
|
const WCHAR* password,
|
|
DWORD_PTR appContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Instantiate an HTTP connection object
|
|
|
|
Arguments:
|
|
|
|
inetobj - Points to an InternetObject
|
|
serverName - Server host name
|
|
username - Username
|
|
password - Password
|
|
appContext - App-specific context value
|
|
|
|
Return Value:
|
|
|
|
Pointer to the newly created connection object
|
|
NULL if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
ConnectObject* connobj;
|
|
|
|
connobj = new ConnectObject();
|
|
if (!connobj) goto failed;
|
|
|
|
// Keep a reference to the internet object
|
|
inetobj->AddRef();
|
|
connobj->inetobj = inetobj;
|
|
connobj->appContext = appContext;
|
|
connobj->serverPort = serverPort;
|
|
|
|
// Save server name and resolve its IP address
|
|
connobj->serverName = strdupWtoA(serverName);
|
|
if (!connobj->serverName) goto failed;
|
|
|
|
if (inetobj->GetProxyServer()) {
|
|
connobj->serverAddr = inetobj->GetProxyServerAddr();
|
|
} else {
|
|
connobj->serverAddr = ResolveHostAddr(connobj->serverName);
|
|
if (connobj->serverAddr.s_addr == 0) goto failed;
|
|
}
|
|
|
|
// Save username and password information
|
|
if (username) {
|
|
connobj->username = strdupWtoA(username);
|
|
if (!connobj->username) goto failed;
|
|
}
|
|
|
|
if (password) {
|
|
connobj->password = strdupWtoA(password);
|
|
if (!connobj->password) goto failed;
|
|
}
|
|
|
|
// Successful return
|
|
return connobj;
|
|
|
|
failed:
|
|
WARNING_("InternetConnect failed: %d", GetLastError());
|
|
delete connobj;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
RequestObject*
|
|
RequestObject::Create(
|
|
ConnectObject* connobj,
|
|
const WCHAR* verb,
|
|
const WCHAR* objectName,
|
|
const WCHAR* httpVer,
|
|
const WCHAR* referer OPTIONAL,
|
|
const WCHAR* acceptTypes[] OPTIONAL,
|
|
DWORD_PTR appContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Instantiate a new HTTP request object
|
|
|
|
Arguments:
|
|
|
|
connobj - Points to the HTTP connection object
|
|
verb - Verb for the HTTP request
|
|
objectName - Name of the object in question
|
|
httpVer - HTTP version string
|
|
referer - Referer URL
|
|
acceptTypes - Media types accepted by the client
|
|
appContext - App-specific context value
|
|
|
|
Return Value:
|
|
|
|
Pointer to the newly created HTTP request object
|
|
NULL if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
RequestObject* reqobj;
|
|
|
|
reqobj = new RequestObject();
|
|
if (!reqobj) return NULL;
|
|
|
|
// Keep a reference to the parent connection object
|
|
connobj->AddRef();
|
|
reqobj->connobj = connobj;
|
|
reqobj->appContext = appContext;
|
|
|
|
//
|
|
// Assemble default HTTP request headers
|
|
//
|
|
InternetObject* inetobj = connobj->GetInternetObject();
|
|
HdrBuf* hdrbuf = &reqobj->reqhdrs;
|
|
INT ok;
|
|
|
|
// Request-line
|
|
ok = hdrbuf->AppendUnicodeString(verb, SPACE);
|
|
if (inetobj->GetProxyServer()) {
|
|
ok &= hdrbuf->AppendUnicodeString(HttpSchemeStr, 0);
|
|
ok &= hdrbuf->AppendAsciiString(connobj->GetServerName(), 0);
|
|
}
|
|
ok &= hdrbuf->AppendUnicodeString(objectName, SPACE);
|
|
ok &= hdrbuf->AppendUnicodeString(httpVer, LF);
|
|
|
|
// User-agent
|
|
ok &= hdrbuf->AppendAsciiString("User-Agent", COLON);
|
|
ok &= hdrbuf->AppendAsciiString(inetobj->GetUserAgent(), LF);
|
|
|
|
// Accept
|
|
// note: this is actually an HTTP/1.1 header field
|
|
if (acceptTypes && *acceptTypes) {
|
|
const WCHAR* lasttype = *acceptTypes++;
|
|
ok &= hdrbuf->AppendAsciiString("Accept", COLON);
|
|
while (*acceptTypes) {
|
|
ok &= hdrbuf->AppendUnicodeString(lasttype, COMMA);
|
|
lasttype = *acceptTypes++;
|
|
}
|
|
ok &= hdrbuf->AppendUnicodeString(lasttype, LF);
|
|
}
|
|
|
|
// Referer
|
|
if (referer) {
|
|
ok &= hdrbuf->AppendAsciiString("Referer", COLON);
|
|
ok &= hdrbuf->AppendUnicodeString(referer, LF);
|
|
}
|
|
|
|
if (!ok) {
|
|
WARNING_("Failed to assembly default HTTP request headers");
|
|
delete reqobj;
|
|
return NULL;
|
|
}
|
|
return reqobj;
|
|
}
|
|
|
|
|
|
BOOL
|
|
RequestObject::Connect()
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Establish a TCP connection to the HTTP server
|
|
|
|
Arguments:
|
|
|
|
NONE
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
struct sockaddr_in serveraddr;
|
|
|
|
// Do nothing if already connected
|
|
if (IsConnected()) return TRUE;
|
|
|
|
// Create the socket and connect to the HTTP server
|
|
sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (sock == INVALID_SOCKET)
|
|
return FALSE;
|
|
|
|
connobj->GetServerAddr(&serveraddr);
|
|
if (_connect(sock, &serveraddr) != NO_ERROR) {
|
|
Disconnect();
|
|
return FALSE;
|
|
}
|
|
|
|
// NOTE: we could set receive timeout option here...
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static BOOL
|
|
DoSendData(
|
|
SOCKET s,
|
|
WSABUF* bufs,
|
|
UINT bufcnt
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send data out of a TCP connection
|
|
|
|
Arguments:
|
|
|
|
s - Socket handle
|
|
bufs - Data buffers
|
|
bufcnt - Number of data buffers
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
INT err;
|
|
DWORD sent;
|
|
|
|
while (bufcnt) {
|
|
err = WSASend(s, bufs, bufcnt, &sent, 0, NULL, NULL);
|
|
if (err != NO_ERROR) return FALSE;
|
|
|
|
// Only partial amount of data was sent:
|
|
// we need to update the send buffers and
|
|
// then call WSASend again
|
|
while (sent) {
|
|
DWORD n = min(sent, bufs->len);
|
|
|
|
if ((bufs->len -= n) == 0) {
|
|
bufs++, bufcnt--;
|
|
} else
|
|
bufs->buf += n;
|
|
sent -= n;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static HANDLE
|
|
DoOpenFile(
|
|
const WCHAR* filename,
|
|
UINT* fileSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Open a file for reading and get the file size
|
|
|
|
Arguments:
|
|
|
|
filename - Specifies the filename
|
|
fileSize - Return the file size
|
|
|
|
Return Value:
|
|
|
|
Handle to the open file
|
|
INVALID_HANDLE_VALUE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
// BUGBUG
|
|
// Since CreateFileW API is gone, we need to strip down
|
|
// the Unicode string to ANSI string. Eventually we should
|
|
// change the HttpSendRequest API to have it pass in
|
|
// ANSI string.
|
|
|
|
XDBGWRN("HTTP",
|
|
"HttpSendRequest: lpOptional parameter is treated as Unicode filename - %ws",
|
|
filename);
|
|
|
|
CHAR *p, buf[MAX_PATH];
|
|
ASSERT(wcslen(filename) < MAX_PATH);
|
|
|
|
p = buf;
|
|
while ((*p++ = (CHAR) *filename++) != 0)
|
|
;
|
|
|
|
HANDLE file;
|
|
|
|
//
|
|
// Open the file for reading
|
|
//
|
|
file = CreateFileA(
|
|
buf,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN,
|
|
NULL);
|
|
|
|
if (file != INVALID_HANDLE_VALUE) {
|
|
//
|
|
// Get file size
|
|
//
|
|
*fileSize = GetFileSize(file, NULL);
|
|
if (*fileSize == 0xffffffff) {
|
|
CloseHandle(file);
|
|
file = INVALID_HANDLE_VALUE;
|
|
}
|
|
}
|
|
|
|
return file;
|
|
}
|
|
|
|
|
|
static BOOL
|
|
DoSendFile(
|
|
SOCKET s,
|
|
HANDLE file,
|
|
UINT filesize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send the content of a file out of a TCP connection
|
|
|
|
Arguments:
|
|
|
|
s - Specifies the socket handle
|
|
file - Specifies the open file handle
|
|
filesize - Total file size
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
#define SENDFILE_BLKSIZE 4096
|
|
|
|
{
|
|
CHAR* buf = NULL;
|
|
WSABUF wsabuf;
|
|
UINT bufsize;
|
|
|
|
bufsize = min(filesize, SENDFILE_BLKSIZE);
|
|
buf = (CHAR*) MAlloc(bufsize);
|
|
if (!buf) return FALSE;
|
|
|
|
while (filesize) {
|
|
DWORD count = min(filesize, bufsize);
|
|
DWORD bytesRead;
|
|
|
|
// Read the next chunk of data from the file
|
|
if (!ReadFile(file, buf, count, &bytesRead, NULL) ||
|
|
count != bytesRead)
|
|
break;
|
|
|
|
// Send it out
|
|
wsabuf.buf = buf;
|
|
wsabuf.len = count;
|
|
if (!DoSendData(s, &wsabuf, 1)) break;
|
|
|
|
filesize -= count;
|
|
}
|
|
|
|
Free(buf);
|
|
return (filesize == 0);
|
|
}
|
|
|
|
|
|
BOOL
|
|
RequestObject::SendRequest(
|
|
const WCHAR* headers,
|
|
UINT headerLength,
|
|
const VOID* optionalData,
|
|
UINT optionalLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send the request to the HTTP server
|
|
|
|
Arguments:
|
|
|
|
headers - Points to extra HTTP request headers
|
|
headerLength - Extra header length
|
|
optionalData - Points to optional data sent along the HTTP request
|
|
optionalLength - Optional data length
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
// Establish a TCP connection to the server
|
|
if (!Connect()) return FALSE;
|
|
|
|
// Free any existing response information
|
|
ResetRespInfo();
|
|
|
|
HdrBuf hdrend;
|
|
CHAR* extrahdrs = NULL;
|
|
INT ok = 1;
|
|
HANDLE file = INVALID_HANDLE_VALUE;
|
|
|
|
//
|
|
// Artificial loop for error handling
|
|
//
|
|
do {
|
|
CHAR lenstr[16];
|
|
|
|
// Content-Length header field
|
|
if (optionalLength == 0xffffffff) {
|
|
file = DoOpenFile((const WCHAR*) optionalData, &optionalLength);
|
|
if (file == INVALID_HANDLE_VALUE) {
|
|
WARNING_("Couldn't open file: %ws", optionalData);
|
|
ok = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ok &= hdrend.AppendAsciiString("Content-Length", COLON);
|
|
sprintf(lenstr, "%u", optionalLength);
|
|
ok &= hdrend.AppendAsciiString(lenstr, LF);
|
|
|
|
// Empty line
|
|
ok &= hdrend.AppendAsciiString("", LF);
|
|
if (!ok) break;
|
|
|
|
// Assemble send buffers
|
|
WSABUF bufs[4];
|
|
UINT bufcnt = 0;
|
|
|
|
bufs[bufcnt].len = reqhdrs.size;
|
|
bufs[bufcnt++].buf = (CHAR*) reqhdrs.data;
|
|
|
|
if (headerLength == 0xffffffff) {
|
|
headerLength = wcslen(headers);
|
|
}
|
|
|
|
if (headerLength) {
|
|
extrahdrs = strdupWtoA(headers, headerLength);
|
|
if (!extrahdrs) break;
|
|
|
|
bufs[bufcnt].len = headerLength;
|
|
bufs[bufcnt++].buf = extrahdrs;
|
|
}
|
|
|
|
bufs[bufcnt].len = hdrend.size;
|
|
bufs[bufcnt++].buf = (CHAR*) hdrend.data;
|
|
|
|
if (optionalLength && file == INVALID_HANDLE_VALUE) {
|
|
bufs[bufcnt].len = optionalLength;
|
|
bufs[bufcnt++].buf = (CHAR*) optionalData;
|
|
}
|
|
|
|
// Send request data
|
|
// note: we need to use a wrapper function here
|
|
// and cann't call WSASend directly because
|
|
// WSASend can return success only after
|
|
// sending partial amount of data.
|
|
ok = DoSendData(sock, bufs, bufcnt) &&
|
|
(file == INVALID_HANDLE_VALUE ||
|
|
DoSendFile(sock, file, optionalLength));
|
|
} while (FALSE);
|
|
|
|
if (file == INVALID_HANDLE_VALUE) {
|
|
CloseHandle(file);
|
|
}
|
|
Free(extrahdrs);
|
|
|
|
if (!ok) {
|
|
Disconnect();
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
|
|
BOOL
|
|
RequestObject::QueryDataAvailable(
|
|
DWORD* bytesAvailable
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Query the amount of HTTP response data available
|
|
|
|
Arguments:
|
|
|
|
bytesAvailable - Returns the number of bytes available
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Make sure we're connected and
|
|
// we have already read the HTTP response headers
|
|
//
|
|
if (!ReadRespHdrs(FALSE)) return FALSE;
|
|
|
|
// We didn't finish reading the response headers
|
|
if (!IsRespHdrsOk()) {
|
|
*bytesAvailable = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
// Check if there is any data to be read
|
|
ULONG avail;
|
|
INT err = ioctlsocket(sock, FIONREAD, &avail);
|
|
|
|
if (err != NO_ERROR) return FALSE;
|
|
*bytesAvailable = peeklen + avail;
|
|
if (*bytesAvailable)
|
|
return TRUE;
|
|
|
|
// If there is no data, check if the connection is closed
|
|
fd_set fds;
|
|
FD_ZERO(&fds);
|
|
FD_SET(sock, &fds);
|
|
struct timeval timeout = { 0, 0 };
|
|
|
|
err = select(1, &fds, NULL, NULL, &timeout);
|
|
switch (err) {
|
|
case 0:
|
|
break;
|
|
|
|
case SOCKET_ERROR:
|
|
return FALSE;
|
|
|
|
default:
|
|
ASSERT(err == 1);
|
|
err = ioctlsocket(sock, FIONREAD, &avail);
|
|
if (err != NO_ERROR) return FALSE;
|
|
|
|
// Connection has been closed
|
|
if (avail == 0) {
|
|
SetLastError(ERROR_HANDLE_EOF);
|
|
return FALSE;
|
|
}
|
|
|
|
*bytesAvailable = avail;
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
RequestObject::ReadData(
|
|
CHAR* buffer,
|
|
UINT bufferSize,
|
|
DWORD* bytesRead
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read HTTP response data
|
|
|
|
Arguments:
|
|
|
|
buffer - Output data buffer
|
|
bufferSize - Output buffer size
|
|
bytesRead - Returns the number of bytes actually read
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
*bytesRead = 0;
|
|
|
|
//
|
|
// Make sure we're connected and
|
|
// we have already read the HTTP response headers
|
|
//
|
|
if (!ReadRespHdrs(TRUE)) return FALSE;
|
|
|
|
//
|
|
// If we have read too much data while parsing
|
|
// the response headers, return it here.
|
|
//
|
|
UINT n;
|
|
if (peeklen) {
|
|
n = min(bufferSize, peeklen);
|
|
CopyMem(buffer, peekdata, n);
|
|
buffer += n;
|
|
peekdata += n;
|
|
peeklen -= n;
|
|
|
|
*bytesRead += n;
|
|
readcnt += n;
|
|
bufferSize -= n;
|
|
}
|
|
|
|
while (bufferSize) {
|
|
n = bufferSize;
|
|
if (contentlen != 0xffffffff) {
|
|
//
|
|
// If Content-Length header field is not present,
|
|
// we'll keep on reading until the server closes
|
|
// the connection.
|
|
//
|
|
// Otherwise, we only read the specified amount of
|
|
// data (and ignore any extra data at the end).
|
|
//
|
|
if (readcnt >= contentlen) break;
|
|
if (n > contentlen - readcnt)
|
|
n = contentlen - readcnt;
|
|
}
|
|
|
|
INT count = recv(sock, buffer, n, 0);
|
|
if (count < 0) return FALSE;
|
|
|
|
// Stop if connection is closed
|
|
if (count == 0) break;
|
|
|
|
n = count;
|
|
*bytesRead += n;
|
|
readcnt += n;
|
|
buffer += n;
|
|
bufferSize -= n;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
RequestObject::ReadRespHdrs(
|
|
BOOL wait
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read HTTP response headers
|
|
|
|
Arguments:
|
|
|
|
wait - Whether to wait for the header data
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Check if we're currently connected
|
|
//
|
|
if (!IsConnected()) {
|
|
SetLastError(ERROR_NOT_CONNECTED);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check if we have already read the response headers
|
|
//
|
|
if (IsRespHdrsOk()) return TRUE;
|
|
|
|
do {
|
|
//
|
|
// Reallocate memory buffer if necessary
|
|
//
|
|
if (resphdrs.SpaceLeft() < 512) {
|
|
BYTE* olddata = resphdrs.data;
|
|
if (!resphdrs.ReserveSpace(512)) goto failed;
|
|
|
|
UINT offset = resphdrs.data - olddata;
|
|
peekdata += offset;
|
|
for (UINT i=0; i < resphdrLinecnt; i++)
|
|
resphdrLines[i] += offset;
|
|
}
|
|
|
|
UINT n = resphdrs.SpaceLeft();
|
|
INT result;
|
|
if (!wait) {
|
|
ULONG avail;
|
|
result = ioctlsocket(sock, FIONREAD, &avail);
|
|
if (result == SOCKET_ERROR) goto failed;
|
|
if (avail == 0) break;
|
|
if (n > avail) n = avail;
|
|
}
|
|
|
|
result = recv(sock, (CHAR*) resphdrs.data + resphdrs.size, n, 0);
|
|
if (result == SOCKET_ERROR) goto failed;
|
|
|
|
resphdrs.size += result;
|
|
ParseRespHdrs(result == 0);
|
|
if (result == 0) {
|
|
//
|
|
// Server has closed the connection
|
|
//
|
|
if (peeklen != 0) goto failed;
|
|
resphdrsOk = TRUE;
|
|
}
|
|
} while (!IsRespHdrsOk());
|
|
return TRUE;
|
|
|
|
failed:
|
|
Disconnect();
|
|
WARNING_("Failed to read HTTP response headers");
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
VOID
|
|
RequestObject::ParseRespHdrs(
|
|
BOOL eof
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Parse HTTP response header fields
|
|
|
|
Arguments:
|
|
|
|
eof - Whether the server connection has been closed
|
|
|
|
Return Value:
|
|
|
|
NONE
|
|
|
|
--*/
|
|
|
|
#define IsLWS(c) ((c) == SPACE || (c) == TAB)
|
|
|
|
{
|
|
BYTE* start = peekdata;
|
|
BYTE* end = resphdrs.data + (resphdrs.size-1);
|
|
|
|
while (TRUE) {
|
|
//
|
|
// Find the end of the next line
|
|
//
|
|
BYTE* p = start;
|
|
BYTE* q;
|
|
|
|
findeol:
|
|
while (p < end) {
|
|
if (p[0] == CR || p[1] == LF) break;
|
|
p++;
|
|
}
|
|
if (p >= end) break;
|
|
|
|
if (p == start) {
|
|
//
|
|
// Empty line - end of header section
|
|
//
|
|
start += 2;
|
|
resphdrsOk = TRUE;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Handle line continuations
|
|
//
|
|
q = p + 2;
|
|
if (q > end) {
|
|
if (!eof) break;
|
|
} else if (IsLWS(*q)) {
|
|
do {
|
|
q++;
|
|
} while (q <= end && IsLWS(*q));
|
|
if (q > end) break;
|
|
|
|
UINT movecnt = (end-q) + 1;
|
|
*p++ = SPACE;
|
|
MoveMem(p, q, movecnt);
|
|
|
|
end -= (q - p);
|
|
goto findeol;
|
|
}
|
|
|
|
//
|
|
// Strip trailing whitespaces
|
|
//
|
|
do {
|
|
*p-- = 0;
|
|
} while (p >= start && IsLWS(*p));
|
|
|
|
if (resphdrLinecnt < MAXRESPHDRS) {
|
|
resphdrLines[resphdrLinecnt++] = start;
|
|
} else {
|
|
WARNING_("Too many response header fields");
|
|
}
|
|
|
|
start = q;
|
|
|
|
//
|
|
// Look for Content-Length: field
|
|
//
|
|
CHAR* str;
|
|
if (MatchHeaderField(start, "Content-Length", &str)) {
|
|
NTSTATUS status;
|
|
ULONG val;
|
|
status = RtlCharToInteger(str, 10, &val);
|
|
if (NT_SUCCESS(status)) contentlen = val;
|
|
}
|
|
}
|
|
|
|
peekdata = start;
|
|
peeklen = (end - start) + 1;
|
|
}
|
|
|
|
|
|
BOOL
|
|
RequestObject::MatchHeaderField(
|
|
BYTE* data,
|
|
const CHAR* fieldname,
|
|
CHAR** fieldval
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Match a specified header field
|
|
|
|
Arguments:
|
|
|
|
data - Points to the header field data
|
|
fieldname - Specifies the name of the interested field
|
|
fieldval - Return a pointer to the field value string
|
|
|
|
Return Value:
|
|
|
|
TRUE if the name of the field matches the specified name
|
|
FALSE otherwise
|
|
|
|
--*/
|
|
|
|
{
|
|
INT len = strlen(fieldname);
|
|
|
|
if (_strnicmp((CHAR*) data, fieldname, len) != 0 || data[len] != COLON)
|
|
return FALSE;
|
|
|
|
data += (len+1);
|
|
while (*data && IsLWS(*data)) data++;
|
|
*fieldval = (CHAR*) data;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
RequestObject::QueryRespInfo(
|
|
DWORD infoLevel,
|
|
WCHAR* buffer,
|
|
DWORD* buflen,
|
|
DWORD* hdrindex
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Retrieves header information associated with an HTTP request
|
|
|
|
Arguments:
|
|
|
|
infoLevel - Specifies what attribute to retrieve
|
|
buffer - Output data buffer
|
|
buflen - Output data buffer size
|
|
On entry, it contains the buffer size in number of WCHARs
|
|
On return, it contains either the actual output data in number
|
|
of WCHARs (not including the null terminator) or the actual
|
|
number of bytes needed if the output buffer is too small
|
|
hdrindex - 0-based index specifying which occurrence if of interest
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
#define HTTP_QUERY_FLAG_ALL \
|
|
(HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_FLAG_SYSTEMTIME)
|
|
|
|
#define ReturnInsufficientBufferError(_bytesNeeded) { \
|
|
*buflen = (_bytesNeeded); \
|
|
SetLastError(ERROR_INSUFFICIENT_BUFFER); \
|
|
return FALSE; \
|
|
}
|
|
|
|
{
|
|
static const struct {
|
|
DWORD fieldindex;
|
|
const CHAR* fieldname;
|
|
} mapping[] = {
|
|
{ HTTP_QUERY_CONTENT_LENGTH, "Content-Length" },
|
|
{ HTTP_QUERY_CONTENT_TYPE, "Content-Type" },
|
|
{ HTTP_QUERY_DATE, "Date" },
|
|
{ HTTP_QUERY_EXPIRES, "Expires" },
|
|
{ HTTP_QUERY_LAST_MODIFIED, "Last-Modified" },
|
|
{ HTTP_QUERY_ACCEPT, "Accept" },
|
|
};
|
|
|
|
DWORD modifier = infoLevel & 0xffff0000;
|
|
DWORD fieldIndex = infoLevel & 0xffff;
|
|
CHAR tempbuf[64];
|
|
const CHAR* fieldname;
|
|
CHAR* fieldval = NULL;
|
|
UINT i, count;
|
|
DWORD occurrence = hdrindex ? *hdrindex : 0;
|
|
|
|
//
|
|
// Make sure we're connected and
|
|
// we have already read the HTTP response headers
|
|
//
|
|
if (!ReadRespHdrs(TRUE)) return FALSE;
|
|
|
|
if (modifier & ~HTTP_QUERY_FLAG_ALL)
|
|
goto unsupported;
|
|
|
|
switch (fieldIndex) {
|
|
case HTTP_QUERY_VERSION:
|
|
case HTTP_QUERY_STATUS_CODE:
|
|
case HTTP_QUERY_STATUS_TEXT:
|
|
//
|
|
// Extract information from the status line
|
|
//
|
|
if (occurrence) goto unsupported;
|
|
fieldval = ParseStatusLine(tempbuf, sizeof(tempbuf), fieldIndex);
|
|
break;
|
|
|
|
case HTTP_QUERY_RAW_HEADERS:
|
|
case HTTP_QUERY_RAW_HEADERS_CRLF:
|
|
//
|
|
// Return all the header fields
|
|
//
|
|
if (modifier || occurrence) goto unsupported;
|
|
return ReturnAllRespHdrs(buffer, buflen, fieldIndex);
|
|
|
|
default:
|
|
if (fieldIndex == HTTP_QUERY_CUSTOM) {
|
|
//
|
|
// Arbitrary field name
|
|
//
|
|
count = wcslen(buffer);
|
|
if (count >= sizeof(tempbuf)) goto unsupported;
|
|
strcpyWtoA(tempbuf, buffer);
|
|
fieldname = tempbuf;
|
|
} else {
|
|
//
|
|
// Map field index to field name
|
|
//
|
|
count = ARRAYCOUNT(mapping);
|
|
for (i=0; i < count; i++) {
|
|
if (fieldIndex == mapping[i].fieldindex) break;
|
|
}
|
|
|
|
if (i == count) goto unsupported;
|
|
fieldname = mapping[i].fieldname;
|
|
}
|
|
|
|
//
|
|
// Find the field with the specified name
|
|
//
|
|
for (i=count=0; i < resphdrLinecnt; i++) {
|
|
if (MatchHeaderField(resphdrLines[i], fieldname, &fieldval)) {
|
|
if (count == occurrence) break;
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if (i == resphdrLinecnt)
|
|
fieldval = NULL;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Check if the specified field is present
|
|
//
|
|
if (fieldval == NULL) {
|
|
SetLastError(ERROR_NO_DATA);
|
|
return FALSE;
|
|
}
|
|
|
|
if (modifier & HTTP_QUERY_FLAG_NUMBER) {
|
|
//
|
|
// Return the field value as an integer
|
|
// NOTE: This is really confusing at the API level:
|
|
// should app pass in buffer length in # of WCHARs
|
|
// or should it be in # of bytes?
|
|
if (*buflen < sizeof(DWORD)) {
|
|
ReturnInsufficientBufferError(sizeof(DWORD));
|
|
}
|
|
|
|
NTSTATUS status;
|
|
ULONG val;
|
|
|
|
status = RtlCharToInteger(fieldval, 10, &val);
|
|
if (!NT_SUCCESS(status)) {
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return FALSE;
|
|
}
|
|
|
|
*((DWORD*) buffer) = val;
|
|
} else if (modifier & HTTP_QUERY_FLAG_SYSTEMTIME) {
|
|
//
|
|
// Parse the HTTP date/time string
|
|
//
|
|
if (*buflen < sizeof(SYSTEMTIME)) {
|
|
ReturnInsufficientBufferError(sizeof(SYSTEMTIME));
|
|
}
|
|
|
|
if (!HttpDateTime::Parse(fieldval, (SYSTEMTIME*) buffer))
|
|
return FALSE;
|
|
} else {
|
|
//
|
|
// Return the field value as Unicode string.
|
|
// Check if the caller's buffer is large enough
|
|
//
|
|
count = strlen(fieldval);
|
|
if (*buflen <= count) {
|
|
ReturnInsufficientBufferError((count + 1) * sizeof(WCHAR));
|
|
}
|
|
|
|
*buflen = count;
|
|
strcpyAtoW(buffer, fieldval);
|
|
}
|
|
|
|
if (hdrindex) *hdrindex = ++occurrence;
|
|
return TRUE;
|
|
|
|
unsupported:
|
|
WARNING_("HttpQueryInfo: unsupported parameter", infoLevel);
|
|
SetLastError(ERROR_NOT_SUPPORTED);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
CHAR*
|
|
RequestObject::ParseStatusLine(
|
|
CHAR* buf,
|
|
DWORD buflen,
|
|
DWORD field
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Extract the specified field from the HTTP response status line
|
|
|
|
Arguments:
|
|
|
|
buf - Points to the output buffer
|
|
buflen - Output buffer size
|
|
These two parameters are only used for the version
|
|
and status code fields.
|
|
field - Specifies which field the caller is interested in
|
|
|
|
Return Value:
|
|
|
|
Pointer to the string value for the specified field
|
|
|
|
--*/
|
|
|
|
{
|
|
if (resphdrLinecnt == 0) return NULL;
|
|
|
|
field = (field == HTTP_QUERY_VERSION) ? 0 :
|
|
(field == HTTP_QUERY_STATUS_CODE) ? 1 : 2;
|
|
|
|
CHAR* q = (CHAR*) resphdrLines[0];
|
|
CHAR* p = NULL;
|
|
DWORD i, n;
|
|
|
|
for (i=n=0; i <= field; i++) {
|
|
p = q;
|
|
if (i < 2) {
|
|
while (*q && !IsLWS(*q)) q++;
|
|
n = q - p;
|
|
while (*q && IsLWS(*q)) q++;
|
|
} else {
|
|
n = strlen(q);
|
|
q += n;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The request field is not present
|
|
//
|
|
if (n == 0) return NULL;
|
|
|
|
//
|
|
// For the status text field, return
|
|
// a pointer to our internal data buffer
|
|
//
|
|
if (field == 2) return p;
|
|
|
|
//
|
|
// Copy version and status code value into the output buffer
|
|
//
|
|
if (buflen <= n) return NULL;
|
|
CopyMem(buf, p, n);
|
|
buf[n] = 0;
|
|
return buf;
|
|
}
|
|
|
|
|
|
BOOL
|
|
RequestObject::ReturnAllRespHdrs(
|
|
WCHAR* buf,
|
|
DWORD* buflen,
|
|
DWORD field
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Return all the HTTP response headers in one chunk
|
|
|
|
Arguments:
|
|
|
|
buf - Points to the output buffer
|
|
buflen - See comments for QueryRespInfo
|
|
field - Specifies whether the header fields should be separated
|
|
by the null character or the CRLF
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
if (resphdrLinecnt == 0) {
|
|
SetLastError(ERROR_NO_DATA);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Figure out the size of output buffer we need
|
|
//
|
|
|
|
BOOL crlf = (field == HTTP_QUERY_RAW_HEADERS_CRLF);
|
|
UINT i, count;
|
|
|
|
for (i=count=0; i < resphdrLinecnt; i++)
|
|
count += strlen((CHAR*) resphdrLines[i]);
|
|
|
|
count += resphdrLinecnt * (crlf ? 2 : 1);
|
|
if (*buflen <= count) {
|
|
ReturnInsufficientBufferError((count + 1) * sizeof(WCHAR));
|
|
}
|
|
|
|
//
|
|
// Copy all the header fields to the output buffer
|
|
//
|
|
|
|
*buflen = count;
|
|
for (i=0; i < resphdrLinecnt; i++) {
|
|
CHAR* str = (CHAR*) resphdrLines[i];
|
|
strcpyAtoW(buf, str);
|
|
count = strlen(str);
|
|
buf += count;
|
|
if (crlf) {
|
|
*buf++ = CR;
|
|
*buf++ = LF;
|
|
} else
|
|
*buf++ = 0;
|
|
}
|
|
|
|
*buf = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
WininetImpl::HttpParseUrl(
|
|
const WCHAR* url,
|
|
WCHAR** serverName,
|
|
INTERNET_PORT* serverPort,
|
|
WCHAR** objectName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Crack an HTTP URL to extract various parts:
|
|
serverName
|
|
serverPort
|
|
objectName
|
|
|
|
Arguments:
|
|
|
|
url - Specifies the input URL string
|
|
serverName - Returns a pointer to the server name string
|
|
serverPort - Returns the server port number
|
|
objectName - Returns a pointer to the object name string
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
ASSERT(url != NULL);
|
|
*serverName = NULL;
|
|
*objectName = NULL;
|
|
*serverPort = INTERNET_DEFAULT_HTTP_PORT;
|
|
|
|
//
|
|
// Artificial loop for error handling
|
|
//
|
|
while (TRUE) {
|
|
//
|
|
// We only support http:// scheme
|
|
//
|
|
UINT len = wcslen(HttpSchemeStr);
|
|
if (_wcsnicmp(url, HttpSchemeStr, len) != 0) break;
|
|
|
|
//
|
|
// Parse the server name
|
|
//
|
|
const WCHAR* server = url + len;
|
|
const WCHAR* cp = server;
|
|
|
|
while (*cp && *cp != COLON && *cp != SLASH) cp++;
|
|
if ((len = cp - server) == 0) break;
|
|
|
|
WCHAR* p = (WCHAR*) MAlloc((len+1) * sizeof(WCHAR));
|
|
if (!p) break;
|
|
|
|
CopyMem(p, server, len*sizeof(WCHAR));
|
|
p[len] = 0;
|
|
*serverName = p;
|
|
|
|
//
|
|
// Parse the server port number
|
|
//
|
|
if (*cp == COLON) {
|
|
const WCHAR* port = ++cp;
|
|
while (*cp && *cp != SLASH) cp++;
|
|
if ((len = cp - port) == 0) break;
|
|
|
|
UNICODE_STRING ustr;
|
|
NTSTATUS status;
|
|
ULONG val;
|
|
|
|
ustr.Buffer = (WCHAR*) port;
|
|
ustr.Length = (USHORT) (len * sizeof(WCHAR));
|
|
ustr.MaximumLength = (USHORT) (ustr.Length + sizeof(WCHAR));
|
|
status = RtlUnicodeStringToInteger(&ustr, 0, &val);
|
|
|
|
if (!NT_SUCCESS(status) || val > 0xffff) break;
|
|
*serverPort = (INTERNET_PORT) val;
|
|
}
|
|
|
|
//
|
|
// Parse the object name
|
|
//
|
|
if (*cp == SLASH) {
|
|
len = (wcslen(cp) + 1) * sizeof(WCHAR);
|
|
p = (WCHAR*) MAlloc(len);
|
|
if (!p) break;
|
|
|
|
*objectName = p;
|
|
CopyMem(p, cp, len);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
WARNING_("HttpParseUrl failed: %ws", url);
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
Free(*serverName);
|
|
*serverName = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//
|
|
// Semi-klugy way to determine if a hostname
|
|
// string is in dotted-decimal form
|
|
//
|
|
inline BOOL IsHostAddrString(const CHAR* hostname) {
|
|
const CHAR* p = hostname;
|
|
while (*p) {
|
|
if ((*p < '0' || *p > '9') && *p != '.')
|
|
return FALSE;
|
|
p++;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
struct in_addr
|
|
WininetImpl::ResolveHostAddr(
|
|
const CHAR* hostname
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Resolve a host name to its IP address
|
|
|
|
Arguments:
|
|
|
|
hostname - Points to the hostname string
|
|
|
|
Return Value:
|
|
|
|
IP address of the specified host
|
|
0 if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
struct in_addr hostaddr;
|
|
|
|
hostaddr.s_addr = 0;
|
|
if (IsHostAddrString(hostname)) {
|
|
//
|
|
// The hostname is in dotted-decimal form:
|
|
// just convert it to IP address directly
|
|
//
|
|
LONG addr = inet_addr(hostname);
|
|
if (addr != INADDR_NONE)
|
|
hostaddr.s_addr = addr;
|
|
} else {
|
|
//
|
|
// Use DNS to map hostname to IP address
|
|
//
|
|
struct hostent* hostent;
|
|
LONG* paddr;
|
|
|
|
hostent = gethostbyname(hostname);
|
|
if (hostent) {
|
|
ASSERT(hostent->h_addr_list);
|
|
paddr = (LONG*) hostent->h_addr_list[0];
|
|
if (paddr) hostaddr.s_addr = *paddr;
|
|
}
|
|
}
|
|
|
|
return hostaddr;
|
|
}
|
|
|