mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 13:47:58 +03:00
1740 lines
45 KiB
C
1740 lines
45 KiB
C
/*
|
|
GameSpy GHTTP SDK
|
|
Dan "Mr. Pants" Schoenblum
|
|
dan@gamespy.com
|
|
|
|
Copyright 1999-2007 GameSpy Industries, Inc
|
|
|
|
devsupport@gamespy.com
|
|
*/
|
|
|
|
#include "ghttpProcess.h"
|
|
#include "ghttpCallbacks.h"
|
|
#include "ghttpPost.h"
|
|
#include "ghttpMain.h"
|
|
#include "ghttpCommon.h"
|
|
|
|
// Parse the URL into:
|
|
// server address (and IP)
|
|
// server port
|
|
// request path.
|
|
/////////////////////////////
|
|
static GHTTPBool ghiParseURL
|
|
(
|
|
GHIConnection * connection
|
|
)
|
|
{
|
|
char * URL;
|
|
int nIndex;
|
|
char tempChar;
|
|
char * str;
|
|
|
|
assert(connection);
|
|
if(!connection)
|
|
return GHTTPFalse;
|
|
|
|
// 2002.Apr.18.JED - Make sure we have an URL
|
|
/////////////////////////////////////////////
|
|
assert(connection->URL);
|
|
if(!connection->URL)
|
|
return GHTTPFalse;
|
|
|
|
URL = connection->URL;
|
|
|
|
// Check for "http://".
|
|
//////////////////////
|
|
if(strncmp(URL, "http://", 7) == 0)
|
|
{
|
|
connection->protocol = GHIHttp;
|
|
URL += 7;
|
|
}
|
|
else if (strncmp(URL, "https://", 8) == 0)
|
|
{
|
|
connection->protocol = GHIHttps;
|
|
URL += 8;
|
|
}
|
|
else
|
|
{
|
|
return GHTTPFalse;
|
|
}
|
|
|
|
// Read the address.
|
|
////////////////////
|
|
nIndex = (int)strcspn(URL, ":/");
|
|
tempChar = URL[nIndex];
|
|
URL[nIndex] = '\0';
|
|
connection->serverAddress = goastrdup(URL);
|
|
if(!connection->serverAddress)
|
|
return GHTTPFalse;
|
|
URL[nIndex] = tempChar;
|
|
URL += nIndex;
|
|
|
|
// Read the port.
|
|
/////////////////
|
|
if(*URL == ':')
|
|
{
|
|
URL++;
|
|
connection->serverPort = (unsigned short)atoi(URL);
|
|
if(!connection->serverPort)
|
|
return GHTTPFalse;
|
|
do
|
|
{
|
|
URL++;
|
|
}while(*URL && (*URL != '/'));
|
|
}
|
|
else
|
|
{
|
|
if (connection->protocol == GHIHttps)
|
|
connection->serverPort = GHI_DEFAULT_SECURE_PORT;
|
|
else
|
|
connection->serverPort = GHI_DEFAULT_PORT;
|
|
}
|
|
|
|
// Read the path.
|
|
/////////////////
|
|
if(!*URL)
|
|
URL = "/";
|
|
connection->requestPath = goastrdup(URL);
|
|
while((str = strchr(connection->requestPath, ' ')) != NULL)
|
|
*str = '+';
|
|
if(!connection->requestPath)
|
|
return GHTTPFalse;
|
|
|
|
return GHTTPTrue;
|
|
}
|
|
|
|
/****************
|
|
** SOCKET INIT **
|
|
****************/
|
|
void ghiDoSocketInit
|
|
(
|
|
GHIConnection * connection
|
|
)
|
|
{
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Socket Initialization\n");
|
|
|
|
// Progress.
|
|
////////////
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
|
|
// Init sockets.
|
|
////////////////
|
|
SocketStartUp();
|
|
|
|
// Parse the URL.
|
|
/////////////////
|
|
if(!ghiParseURL(connection))
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPParseURLFailed;
|
|
return;
|
|
}
|
|
|
|
// Check if an encryption type was set.
|
|
///////////////////////////////////////
|
|
if((connection->protocol == GHIHttps) && (connection->encryptor.mEngine == GHTTPEncryptionEngine_None))
|
|
{
|
|
// default to gamespy engine
|
|
//ghttpSetRequestEncryptionEngine(connection->request, GHTTPEncryptionEngine_GameSpy);
|
|
|
|
// 02OCT07 BED: Design changed so that only one engine can be active at a time
|
|
// Use the active engine rather than GameSpy
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_WarmError,
|
|
"Encryption engine not set for HTTPS. Using default engine\r\n");
|
|
ghttpSetRequestEncryptionEngine(connection->request, GHTTPEncryptionEngine_Default);
|
|
}
|
|
else if ((connection->protocol != GHIHttps) && (connection->encryptor.mEngine != GHTTPEncryptionEngine_None))
|
|
{
|
|
// URL is not secured
|
|
ghttpSetRequestEncryptionEngine(connection->request, GHTTPEncryptionEngine_None);
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_WarmError,
|
|
"Encryption engine set for unsecured URL. Removing encryption.\r\n");
|
|
}
|
|
|
|
// Init the encryption engine.
|
|
//////////////////////////////
|
|
if ((connection->protocol == GHIHttps) && connection->encryptor.mInitialized == GHTTPFalse)
|
|
{
|
|
GHIEncryptionResult aResult;
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Debug, "Initializing SSL engine\n");
|
|
aResult = (connection->encryptor.mInitFunc)(connection, &connection->encryptor);
|
|
if (aResult == GHIEncryptionResult_Error)
|
|
{
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_WarmError, "Failed to initialize SSL engine\n");
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPEncryptionError;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Progress.
|
|
////////////
|
|
connection->state = GHTTPHostLookup;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
}
|
|
|
|
/****************
|
|
** HOST LOOKUP **
|
|
****************/
|
|
void ghiDoHostLookup
|
|
(
|
|
GHIConnection * connection
|
|
)
|
|
{
|
|
HOSTENT * host = NULL;
|
|
const char * server = NULL;
|
|
|
|
#if !defined(GSI_NO_THREADS)
|
|
// Check to see if asynch lookup is taking place
|
|
////////////////////////////////////////////////
|
|
if (connection->handle)
|
|
{
|
|
GSI_UNUSED(host);
|
|
GSI_UNUSED(server);
|
|
|
|
// Lookup incomplete - set to lookupPending state
|
|
/////////////////////////////////////////////////
|
|
connection->state = GHTTPLookupPending;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Host Lookup\n");
|
|
|
|
|
|
// Check for using a proxy.
|
|
///////////////////////////
|
|
if (connection->proxyOverrideServer) // request specific proxy
|
|
server = connection->proxyOverrideServer;
|
|
else if(ghiProxyAddress)
|
|
server = ghiProxyAddress;
|
|
else
|
|
server = connection->serverAddress;
|
|
|
|
// Try resolving the address as an IP a.b.c.d number.
|
|
/////////////////////////////////////////////////////
|
|
connection->serverIP = inet_addr(server);
|
|
if(connection->serverIP == INADDR_NONE)
|
|
{
|
|
// Try resolving with DNS - asynchronously if possible
|
|
//////////////////////////
|
|
|
|
#if defined(GSI_NO_THREADS)
|
|
//blocking version - no threads
|
|
host = gethostbyname(server);
|
|
|
|
if(host == NULL)
|
|
{
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_HotError,
|
|
"Host Lookup failed\n");
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPHostLookupFailed;
|
|
return;
|
|
}
|
|
|
|
// Get the IP.
|
|
//////////////
|
|
connection->serverIP = *(unsigned int *)host->h_addr_list[0];
|
|
#else
|
|
|
|
//threaded version
|
|
if (gsiStartResolvingHostname(server, &(connection->handle)) == -1)
|
|
{
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_HotError,
|
|
"Thread Creation Failed\n");
|
|
|
|
//make sure to set it back to NULL
|
|
connection->handle = NULL;
|
|
|
|
//exit with Host Lookup Failed error message
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPHostLookupFailed;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//thread created properly - continue into lookupPending state
|
|
GSI_UNUSED(host);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Progress.
|
|
////////////
|
|
|
|
//check to see if lookup is complete
|
|
if (connection->serverIP == INADDR_NONE)
|
|
{
|
|
//lookup incomplete - set to lookupPending state
|
|
connection->state = GHTTPLookupPending;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
}
|
|
else
|
|
{
|
|
//lookup complete - proceed with connection stage
|
|
connection->state = GHTTPConnecting;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
}
|
|
}
|
|
|
|
/******************
|
|
** LOOKUP PENDING**
|
|
******************/
|
|
void ghiDoLookupPending
|
|
(
|
|
GHIConnection * connection
|
|
)
|
|
{
|
|
#if !defined(GSI_NO_THREADS)
|
|
//check if lookup is complete
|
|
connection->serverIP = gsiGetResolvedIP(connection->handle);
|
|
|
|
//make sure there were no problems with the IP
|
|
if (connection->serverIP == GSI_ERROR_RESOLVING_HOSTNAME)
|
|
{
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_HotError,
|
|
"Error resolving hostname\n");
|
|
|
|
//set to NULL
|
|
connection->handle = NULL;
|
|
|
|
//notify that the lookup failed
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPHostLookupFailed;
|
|
return;
|
|
}
|
|
|
|
if (connection->serverIP == GSI_STILL_RESOLVING_HOSTNAME)
|
|
{
|
|
//lookup incomplete - keep calling this function
|
|
connection->state = GHTTPLookupPending;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
}
|
|
else
|
|
{
|
|
//set to NULL
|
|
connection->handle = NULL;
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment,
|
|
"DNS lookup complete\n");
|
|
//looks like we got ourselves a server! proceed with connection phase
|
|
connection->state = GHTTPConnecting;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/***************
|
|
** CONNECTING **
|
|
***************/
|
|
void ghiDoConnecting
|
|
(
|
|
GHIConnection * connection
|
|
)
|
|
{
|
|
int rcode;
|
|
SOCKADDR_IN address;
|
|
int writeFlag;
|
|
int exceptFlag;
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Connecting\n");
|
|
|
|
// If we don't have a socket yet, set it up.
|
|
////////////////////////////////////////////
|
|
if(connection->socket == INVALID_SOCKET)
|
|
{
|
|
// Create the socket.
|
|
/////////////////////
|
|
connection->socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if(connection->socket == INVALID_SOCKET)
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPSocketFailed;
|
|
connection->socketError = GOAGetLastError(connection->socket);
|
|
return;
|
|
}
|
|
|
|
// Set the socket to non-blocking.
|
|
//////////////////////////////////
|
|
if(!SetSockBlocking(connection->socket, 0))
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPSocketFailed;
|
|
connection->socketError = GOAGetLastError(connection->socket);
|
|
return;
|
|
}
|
|
|
|
// If throttling, use a small receive buffer.
|
|
/////////////////////////////////////////////
|
|
if(connection->throttle)
|
|
SetReceiveBufferSize(connection->socket, ghiThrottleBufferSize);
|
|
|
|
// Setup the server address.
|
|
////////////////////////////
|
|
memset(&address, 0, sizeof(SOCKADDR_IN));
|
|
address.sin_family = AF_INET;
|
|
if (connection->proxyOverrideServer)
|
|
address.sin_port = htons(connection->proxyOverridePort);
|
|
else if(ghiProxyAddress)
|
|
address.sin_port = htons(ghiProxyPort);
|
|
else
|
|
address.sin_port = htons(connection->serverPort);
|
|
address.sin_addr.s_addr = connection->serverIP;
|
|
|
|
// Start the connect.
|
|
/////////////////////
|
|
rcode = connect(connection->socket, (SOCKADDR *)&address, sizeof(address));
|
|
if(gsiSocketIsError(rcode))
|
|
{
|
|
int socketError = GOAGetLastError(connection->socket);
|
|
if((socketError != WSAEWOULDBLOCK) && (socketError != WSAEINPROGRESS) && (socketError != WSAETIMEDOUT))
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPConnectFailed;
|
|
connection->socketError = socketError;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if the connect has completed.
|
|
//////////////////////////////////////
|
|
rcode = GSISocketSelect(connection->socket, NULL, &writeFlag, &exceptFlag);
|
|
if((gsiSocketIsError(rcode)) || ((rcode == 1) && exceptFlag))
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPConnectFailed;
|
|
if(gsiSocketIsError(rcode))
|
|
connection->socketError = GOAGetLastError(connection->socket);
|
|
else
|
|
connection->socketError = 0;
|
|
return;
|
|
}
|
|
|
|
// Check if we're connected.
|
|
////////////////////////////
|
|
if((rcode == 1) && writeFlag)
|
|
{
|
|
// Progress.
|
|
////////////
|
|
if (connection->encryptor.mEngine == GHTTPEncryptionEngine_None)
|
|
connection->state = GHTTPSendingRequest;
|
|
else
|
|
connection->state = GHTTPSecuringSession;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
}
|
|
}
|
|
|
|
/******************
|
|
** SSL HANDSHAKE **
|
|
*******************/
|
|
void ghiDoSecuringSession
|
|
(
|
|
GHIConnection * connection
|
|
)
|
|
{
|
|
// Client sends hello
|
|
// Server sends hello, [certificate], [certificate request], [server key exchange]
|
|
// Client sends client <key exchange>, <finished>, [certificate], [certificate verify]
|
|
// Server sends finished
|
|
|
|
// skip the ghiDoSecuringSession step...
|
|
// - when not using encryption or
|
|
// - if the connection is already secure
|
|
|
|
GHIRecvResult result;
|
|
|
|
// This buffer must be large enough to receive any handshake messages.
|
|
char buffer[1025];
|
|
int bufferLen;
|
|
|
|
// Start the handshake process
|
|
if (connection->encryptor.mSessionStarted == GHTTPFalse)
|
|
{
|
|
GHIEncryptionResult aResult;
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Securing Session\n");
|
|
|
|
GS_ASSERT(connection->encryptor.mStartFunc != NULL);
|
|
if (connection->encryptor.mStartFunc != NULL)
|
|
{
|
|
aResult = (connection->encryptor.mStartFunc)(connection, &connection->encryptor);
|
|
if (aResult == GHIEncryptionResult_Error)
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPEncryptionError;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check for session established
|
|
if (connection->encryptor.mSessionEstablished)
|
|
{
|
|
connection->state = GHTTPSendingRequest;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// if the SSL lib controls the handshake, just keep calling
|
|
// start until the session has been established
|
|
GS_ASSERT(connection->encryptor.mSessionEstablished == GHTTPFalse);
|
|
if (connection->encryptor.mLibSendsHandshakeMessages)
|
|
{
|
|
GS_ASSERT(connection->encryptor.mStartFunc != NULL);
|
|
if (connection->encryptor.mStartFunc != NULL)
|
|
{
|
|
GHIEncryptionResult aResult = (connection->encryptor.mStartFunc)(connection, &connection->encryptor);
|
|
if (aResult == GHIEncryptionResult_Error)
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPEncryptionError;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check for session established
|
|
if (connection->encryptor.mSessionEstablished)
|
|
{
|
|
connection->state = GHTTPSendingRequest;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Continue to send and receive handshake messages until the session has been secured
|
|
// Send any session messages
|
|
if (connection->sendBuffer.pos < connection->sendBuffer.len)
|
|
{
|
|
if (!ghiSendBufferedData(connection))
|
|
return; // Todo: handle error?
|
|
|
|
// Check for data still buffered.
|
|
/////////////////////////////////
|
|
if(connection->sendBuffer.pos < connection->sendBuffer.len)
|
|
return;
|
|
|
|
ghiResetBuffer(&connection->sendBuffer);
|
|
}
|
|
|
|
// Get data
|
|
bufferLen = sizeof(buffer);
|
|
result = ghiDoReceive(connection, buffer, &bufferLen);
|
|
|
|
// Handle error or conn closed.
|
|
///////////////////////////////
|
|
if((result == GHIError) || (result == GHIConnClosed))
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPEncryptionError;
|
|
return;
|
|
}
|
|
|
|
// check for received data
|
|
if(result == GHIRecvData)
|
|
{
|
|
// Append new encrypted data to anything we've held over
|
|
// We have to do this because we can't decrypt partial SSL messages
|
|
if (!ghiAppendDataToBuffer(&connection->decodeBuffer, buffer, bufferLen))
|
|
return;
|
|
|
|
// Decrypt as much as we can
|
|
if (!ghiDecryptReceivedData(connection))
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPEncryptionError;
|
|
return;
|
|
}
|
|
|
|
// Check for session established (handshake complete)
|
|
if (connection->encryptor.mSessionEstablished)
|
|
{
|
|
connection->state = GHTTPSendingRequest;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/********************
|
|
** SENDING REQUEST **
|
|
********************/
|
|
void ghiDoSendingRequest
|
|
(
|
|
GHIConnection * connection
|
|
)
|
|
{
|
|
char * requestType;
|
|
int oldPos;
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Sending Request\n");
|
|
|
|
// If we haven't filled the send buffer yet, do that first.
|
|
///////////////////////////////////////////////////////////
|
|
if(!connection->sendBuffer.len)
|
|
{
|
|
// Using a pointer so we can pipe output to a different destination
|
|
// (e.g. for efficiency and testing purposes we may want to encrypt in larger blocks)
|
|
GHIBuffer* writeBuffer = NULL;
|
|
if (connection->encryptor.mEngine == GHTTPEncryptionEngine_None ||
|
|
connection->encryptor.mEncryptOnBuffer == GHTTPFalse)
|
|
{
|
|
// write directly to send buffer
|
|
writeBuffer = &connection->sendBuffer;
|
|
}
|
|
else
|
|
{
|
|
// write to temp buffer so it can be encrypted before sending
|
|
writeBuffer = &connection->encodeBuffer;
|
|
}
|
|
|
|
// Fill in the request line.
|
|
////////////////////////////
|
|
if(connection->post && !connection->postingState.completed)
|
|
requestType = "POST ";
|
|
else if(connection->type == GHIHEAD)
|
|
requestType = "HEAD ";
|
|
else
|
|
requestType = "GET ";
|
|
ghiAppendDataToBuffer(writeBuffer, requestType, 0);
|
|
if (connection->proxyOverrideServer || ghiProxyAddress)
|
|
ghiAppendDataToBuffer(writeBuffer, connection->URL, 0);
|
|
else
|
|
ghiAppendDataToBuffer(writeBuffer, connection->requestPath, 0);
|
|
ghiAppendDataToBuffer(writeBuffer, " HTTP/1.1" CRLF, 0);
|
|
|
|
// Add the host header.
|
|
///////////////////////
|
|
if(connection->serverPort == GHI_DEFAULT_PORT)
|
|
{
|
|
ghiAppendHeaderToBuffer(writeBuffer, "Host", connection->serverAddress);
|
|
}
|
|
else
|
|
{
|
|
ghiAppendDataToBuffer(writeBuffer, "Host: ", 0);
|
|
ghiAppendDataToBuffer(writeBuffer, connection->serverAddress, 0);
|
|
ghiAppendCharToBuffer(writeBuffer, ':');
|
|
ghiAppendIntToBuffer(writeBuffer, connection->serverPort);
|
|
ghiAppendDataToBuffer(writeBuffer, CRLF, 2);
|
|
}
|
|
|
|
// Add the user-agent header.
|
|
/////////////////////////////
|
|
if (connection->sendHeaders == NULL || strstr(connection->sendHeaders, "User-Agent")==NULL)
|
|
ghiAppendHeaderToBuffer(writeBuffer, "User-Agent", "GameSpyHTTP/1.0");
|
|
|
|
// Check for persistant connections.
|
|
//////////////////////////////////////
|
|
if (connection->persistConnection)
|
|
ghiAppendHeaderToBuffer(writeBuffer, "Connection", "Keep-Alive");
|
|
else
|
|
ghiAppendHeaderToBuffer(writeBuffer, "Connection", "close");
|
|
|
|
// Post needs extra headers.
|
|
////////////////////////////
|
|
if(connection->post && !connection->postingState.completed)
|
|
{
|
|
char buf[16];
|
|
|
|
// Add the content-length header.
|
|
/////////////////////////////////
|
|
sprintf(buf, "%d", connection->postingState.totalBytes);
|
|
ghiAppendHeaderToBuffer(writeBuffer, "Content-Length", buf);
|
|
|
|
// Add the content-type header.
|
|
///////////////////////////////
|
|
ghiAppendHeaderToBuffer(writeBuffer, "Content-Type", ghiPostGetContentType(connection));
|
|
}
|
|
|
|
// Not supported by all servers
|
|
//ghiAppendHeaderToBuffer(writeBuffer, "Expect", "100-continue");
|
|
|
|
// Add user-headers.
|
|
////////////////////
|
|
if(connection->sendHeaders)
|
|
ghiAppendDataToBuffer(writeBuffer, connection->sendHeaders, 0);
|
|
|
|
// Add the blank line to finish it off.
|
|
///////////////////////////////////////
|
|
ghiAppendDataToBuffer(writeBuffer, CRLF, 2);
|
|
|
|
// Encrypt it, if necessary. This copy is unfortunate since matrixSsl can't encrypt in place
|
|
if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None &&
|
|
connection->encryptor.mEncryptOnBuffer == GHTTPTrue)
|
|
{
|
|
GS_ASSERT(writeBuffer == &connection->encodeBuffer);
|
|
if (!ghiEncryptDataToBuffer(&connection->sendBuffer, writeBuffer->data, writeBuffer->len))
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPEncryptionError;
|
|
return;
|
|
}
|
|
ghiResetBuffer(writeBuffer);
|
|
}
|
|
}
|
|
|
|
// Store the old position.
|
|
//////////////////////////
|
|
oldPos = connection->sendBuffer.pos;
|
|
|
|
// Send what we can.
|
|
////////////////////
|
|
if(!ghiSendBufferedData(connection))
|
|
return;
|
|
|
|
// Log anything we sent.
|
|
////////////////////////
|
|
#ifdef HTTP_LOG
|
|
if(connection->sendBuffer.pos != oldPos)
|
|
ghiLogRequest(connection->sendBuffer.data + oldPos, connection->sendBuffer.pos - oldPos);
|
|
#endif
|
|
|
|
// Check for data still buffered.
|
|
/////////////////////////////////
|
|
if(connection->sendBuffer.pos < connection->sendBuffer.len)
|
|
return;
|
|
|
|
// Clear the send buffer.
|
|
/////////////////////////
|
|
ghiResetBuffer(&connection->sendBuffer);
|
|
|
|
// Finished sending.
|
|
////////////////////
|
|
if(connection->post && !connection->postingState.completed)
|
|
connection->state = GHTTPPosting;
|
|
else
|
|
connection->state = GHTTPWaiting;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
|
|
GSI_UNUSED(oldPos);
|
|
}
|
|
|
|
/************
|
|
** POSTING **
|
|
************/
|
|
void ghiDoPosting
|
|
(
|
|
GHIConnection * connection
|
|
)
|
|
{
|
|
GHIPostingResult result;
|
|
int oldBytesPosted;
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Posting\n");
|
|
|
|
// Store the old bytes posted.
|
|
//////////////////////////////
|
|
oldBytesPosted = connection->postingState.bytesPosted;
|
|
|
|
// Do some posting.
|
|
///////////////////
|
|
result = ghiPostDoPosting(connection);
|
|
|
|
// Check for an error.
|
|
//////////////////////
|
|
if(result == GHIPostingError)
|
|
{
|
|
int rcode = 0;
|
|
int readFlag = 0;
|
|
|
|
// Make sure we already set the error stuff.
|
|
////////////////////////////////////////////
|
|
assert(connection->completed && connection->result);
|
|
|
|
// Cleanup the posting state.
|
|
/////////////////////////////
|
|
ghiPostCleanupState(connection);
|
|
|
|
// Is there a server response?
|
|
rcode = GSISocketSelect(connection->socket, &readFlag, NULL, NULL);
|
|
if((rcode == 1) && readFlag)
|
|
{
|
|
// Ready to receive.
|
|
////////////////////
|
|
connection->state = GHTTPReceivingStatus;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// When sending DIME wait for initial
|
|
// continue before uploading
|
|
/////////////////////////////////////////
|
|
if (result == GHIPostingWaitForContinue)
|
|
{
|
|
// Disable by skipping the wait
|
|
connection->postingState.waitPostContinue = GHTTPFalse;
|
|
return;
|
|
|
|
//connection->state = GHTTPWaiting;
|
|
//return;
|
|
}
|
|
|
|
// Call the callback if we sent anything.
|
|
/////////////////////////////////////////
|
|
if(oldBytesPosted != connection->postingState.bytesPosted)
|
|
ghiCallPostCallback(connection);
|
|
|
|
// Check for done.
|
|
//////////////////
|
|
if(result == GHIPostingDone)
|
|
{
|
|
// Cleanup the posting state.
|
|
/////////////////////////////
|
|
ghiPostCleanupState(connection);
|
|
connection->postingState.completed = GHTTPTrue;
|
|
|
|
// Set the new connection state.
|
|
////////////////////////////////
|
|
connection->state = GHTTPWaiting;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
/************
|
|
** WAITING **
|
|
************/
|
|
void ghiDoWaiting
|
|
(
|
|
GHIConnection * connection
|
|
)
|
|
{
|
|
int readFlag;
|
|
int exceptFlag;
|
|
int rcode;
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Waiting\n");
|
|
|
|
// We're waiting to receive something.
|
|
//////////////////////////////////////
|
|
rcode = GSISocketSelect(connection->socket, &readFlag, NULL, &exceptFlag);
|
|
if((gsiSocketIsError(rcode)) || ((rcode == 1) && exceptFlag))
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPSocketFailed;
|
|
if(gsiSocketIsError(rcode))
|
|
connection->socketError = GOAGetLastError(connection->socket);
|
|
else
|
|
connection->socketError = 0;
|
|
return;
|
|
}
|
|
|
|
// Check for waiting data.
|
|
//////////////////////////
|
|
if((rcode == 1) && readFlag)
|
|
{
|
|
// Ready to receive.
|
|
////////////////////
|
|
connection->state = GHTTPReceivingStatus;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
}
|
|
}
|
|
|
|
// Parse the status line.
|
|
/////////////////////////
|
|
static GHTTPBool ghiParseStatus
|
|
(
|
|
GHIConnection * connection
|
|
)
|
|
{
|
|
int majorVersion;
|
|
int minorVersion;
|
|
int statusCode;
|
|
int statusStringIndex;
|
|
int rcode;
|
|
char c;
|
|
|
|
GS_ASSERT(connection);
|
|
GS_ASSERT(connection->recvBuffer.len > 0);
|
|
|
|
#if defined(_X360)
|
|
//Xbox 360 needs "%n" to be manually enabled
|
|
{
|
|
int oldPrintCountValue = _set_printf_count_output(1);
|
|
#endif
|
|
|
|
// Parse the string.
|
|
////////////////////
|
|
rcode = sscanf(connection->recvBuffer.data, "HTTP/%d.%d %d%n",
|
|
&majorVersion,
|
|
&minorVersion,
|
|
&statusCode,
|
|
&statusStringIndex);
|
|
|
|
#if defined(_X360)
|
|
_set_printf_count_output(oldPrintCountValue);
|
|
}
|
|
#endif
|
|
|
|
// Check what we got.
|
|
/////////////////////
|
|
if((rcode != 3) || // Not all fields read.
|
|
//!*statusString || // No status string. PANTS|9.16.02 - apparently some servers don't return a status string
|
|
(majorVersion < 1) || // Major version is less than 1.
|
|
(statusCode < 100) || // 1xx is lowest status code.
|
|
(statusCode >= 600)) // 5xx is highest status code.
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPBadResponse;
|
|
return GHTTPFalse;
|
|
}
|
|
|
|
// Figure out where the status string starts.
|
|
/////////////////////////////////////////////
|
|
while((c = connection->recvBuffer.data[statusStringIndex]) != '\0' && isspace(c))
|
|
statusStringIndex++;
|
|
|
|
// Set connection members.
|
|
//////////////////////////
|
|
connection->statusMajorVersion = majorVersion;
|
|
connection->statusMinorVersion = minorVersion;
|
|
connection->statusCode = statusCode;
|
|
connection->statusStringIndex = statusStringIndex;
|
|
|
|
return GHTTPTrue;
|
|
}
|
|
|
|
/*********************
|
|
** RECEIVING STATUS **
|
|
*********************/
|
|
void ghiDoReceivingStatus
|
|
(
|
|
GHIConnection * connection
|
|
)
|
|
{
|
|
char buffer[1024];
|
|
int bufferLen;
|
|
GHIRecvResult result;
|
|
char * endOfStatus;
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Receiving Status\n");
|
|
|
|
// Get data.
|
|
////////////
|
|
bufferLen = sizeof(buffer);
|
|
result = ghiDoReceive(connection, buffer, &bufferLen);
|
|
|
|
// Handle error or no data.
|
|
///////////////////////////
|
|
if(result == GHIError)
|
|
return;
|
|
if(result == GHINoData)
|
|
return;
|
|
|
|
// Only append data if we got data.
|
|
///////////////////////////////////
|
|
if(result == GHIRecvData)
|
|
{
|
|
// Check for encryption.
|
|
////////////////////////
|
|
if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None &&
|
|
connection->encryptor.mEncryptOnBuffer == GHTTPTrue)
|
|
{
|
|
// Append new encrypted data to anything we've held over
|
|
// We have to do this because we can't decrypt partial SSL messages
|
|
if (!ghiAppendDataToBuffer(&connection->decodeBuffer, buffer, bufferLen))
|
|
return;
|
|
|
|
// Decrypt as much as we can
|
|
if (!ghiDecryptReceivedData(connection))
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPEncryptionError;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Add the data directly to the buffer.
|
|
///////////////////////////////////////
|
|
if(!ghiAppendDataToBuffer(&connection->recvBuffer, buffer, bufferLen))
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check if the status is finished.
|
|
/////////////////////////////////////
|
|
endOfStatus = strstr(connection->recvBuffer.data, CRLF);
|
|
if(endOfStatus)
|
|
{
|
|
int statusLength;
|
|
|
|
// Cap the status.
|
|
//////////////////
|
|
*endOfStatus = '\0';
|
|
|
|
// Get the status length.
|
|
/////////////////////////
|
|
statusLength = (endOfStatus - connection->recvBuffer.data);
|
|
|
|
// Log it.
|
|
//////////
|
|
ghiLogResponse(connection->recvBuffer.data, statusLength);
|
|
ghiLogResponse("\n", 1);
|
|
|
|
// Parse the status line.
|
|
/////////////////////////
|
|
if(!ghiParseStatus(connection))
|
|
return;
|
|
|
|
// Store the position of the start of the headers.
|
|
//////////////////////////////////////////////////
|
|
connection->headerStringIndex = (statusLength + 2);
|
|
|
|
|
|
if (connection->statusCode == 100 && connection->postingState.waitPostContinue)
|
|
{
|
|
// DIME uploads must wait for initial continue before posting
|
|
connection->postingState.waitPostContinue = GHTTPFalse;
|
|
ghiResetBuffer(&connection->recvBuffer); // clear the continue
|
|
connection->state = GHTTPPosting;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_Comment,
|
|
"Got HTTP continue\r\n");
|
|
}
|
|
else
|
|
{
|
|
// We're receiving headers now.
|
|
///////////////////////////////
|
|
connection->state = GHTTPReceivingHeaders;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
}
|
|
}
|
|
else if(result == GHIConnClosed)
|
|
{
|
|
// Connection closed.
|
|
/////////////////////
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPBadResponse;
|
|
connection->socketError = GOAGetLastError(connection->socket);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Delivers incoming file data to the appropriate place,
|
|
// then calls the progress callback.
|
|
// For GetFile, adds to buffer.
|
|
// For SaveFile, writes to disk.
|
|
// For StreamFile, does nothing.
|
|
// Returns false on error.
|
|
////////////////////////////////////////////////////////
|
|
static GHTTPBool ghiDeliverIncomingFileData
|
|
(
|
|
GHIConnection * connection,
|
|
char * data,
|
|
int len
|
|
)
|
|
{
|
|
char * buffer = NULL;
|
|
int bufferLen = 0;
|
|
|
|
// Add this to the total.
|
|
/////////////////////////
|
|
connection->fileBytesReceived += len;
|
|
|
|
// Do we have the whole thing?
|
|
//////////////////////////////
|
|
if(connection->fileBytesReceived == connection->totalSize || connection->connectionClosed)
|
|
connection->completed = GHTTPTrue;
|
|
|
|
// Handle based on type.
|
|
////////////////////////
|
|
if(connection->type == GHIGET)
|
|
{
|
|
// Put this in the buffer.
|
|
//////////////////////////
|
|
if(!ghiAppendDataToBuffer(&connection->getFileBuffer, data, len))
|
|
return GHTTPFalse;
|
|
|
|
// Set the callback parameters.
|
|
///////////////////////////////
|
|
buffer = connection->getFileBuffer.data;
|
|
bufferLen = connection->getFileBuffer.len;
|
|
}
|
|
else if(connection->type == GHISAVE)
|
|
{
|
|
int bytesWritten = 0;
|
|
#ifndef NOFILE
|
|
bytesWritten = fwrite(data, 1, len, connection->saveFile);
|
|
#endif
|
|
if(bytesWritten != len)
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPFileWriteFailed;
|
|
return GHTTPFalse;
|
|
}
|
|
|
|
// Set the callback parameters.
|
|
///////////////////////////////
|
|
buffer = data;
|
|
bufferLen = len;
|
|
}
|
|
else if(connection->type == GHISTREAM)
|
|
{
|
|
// Set the callback parameters.
|
|
///////////////////////////////
|
|
buffer = data;
|
|
bufferLen = len;
|
|
}
|
|
|
|
// Call the callback.
|
|
/////////////////////
|
|
ghiCallProgressCallback(connection, buffer, bufferLen);
|
|
|
|
return GHTTPTrue;
|
|
}
|
|
|
|
// Gets the size of a chunk from a chunk header.
|
|
// Returns -1 on error.
|
|
////////////////////////////////////////////////
|
|
static int ghiParseChunkSize
|
|
(
|
|
GHIConnection * connection
|
|
)
|
|
{
|
|
char * header;
|
|
int len;
|
|
int num;
|
|
int rcode;
|
|
|
|
header = connection->chunkHeader;
|
|
len = connection->chunkHeaderLen;
|
|
|
|
assert(len);
|
|
GSI_UNUSED(len);
|
|
|
|
rcode = sscanf(header, "%x", &num);
|
|
if(rcode != 1)
|
|
return -1;
|
|
|
|
return num;
|
|
}
|
|
|
|
// Appends the data to the chunk header buffer.
|
|
///////////////////////////////////////////////
|
|
static void ghiAppendToChunkHeaderBuffer
|
|
(
|
|
GHIConnection * connection,
|
|
char * data,
|
|
int len
|
|
)
|
|
{
|
|
assert(connection);
|
|
assert(data);
|
|
assert(len >= 0);
|
|
|
|
// This can happen at the end of a header.
|
|
//////////////////////////////////////////
|
|
if(len == 0)
|
|
return;
|
|
|
|
// Is there room in the buffer? If not, just
|
|
// skip, we most likely already have the chunk size.
|
|
////////////////////////////////////////////////////
|
|
if(connection->chunkHeaderLen < CHUNK_HEADER_SIZE)
|
|
{
|
|
int numBytes;
|
|
|
|
// How many bytes are we copying?
|
|
/////////////////////////////////
|
|
numBytes = min(CHUNK_HEADER_SIZE - connection->chunkHeaderLen, len);
|
|
|
|
// Move the (possibly partial) header into the buffer.
|
|
//////////////////////////////////////////////////////
|
|
memcpy(connection->chunkHeader + connection->chunkHeaderLen, data, (unsigned int)numBytes);
|
|
|
|
// Cap off the buffer.
|
|
//////////////////////
|
|
connection->chunkHeaderLen += numBytes;
|
|
connection->chunkHeader[connection->chunkHeaderLen] = '\0';
|
|
}
|
|
}
|
|
|
|
// Does any neccessary processing to incoming file data
|
|
// before it gets delivered. This includes un-chunking.
|
|
// Returns false on error.
|
|
////////////////////////////////////////////////////////
|
|
static GHTTPBool ghiProcessIncomingFileData
|
|
(
|
|
GHIConnection * connection,
|
|
char * data,
|
|
int len
|
|
)
|
|
{
|
|
assert(connection);
|
|
assert(data);
|
|
assert(len > 0);
|
|
|
|
// Is this a chunked transfer?
|
|
//////////////////////////////
|
|
if(connection->chunkedTransfer)
|
|
{
|
|
// Loop while there's stuff to process.
|
|
///////////////////////////////////////
|
|
while(len > 0)
|
|
{
|
|
// Reading a header?
|
|
////////////////////
|
|
if(connection->chunkReadingState == CRHeader)
|
|
{
|
|
char * endOfHeader;
|
|
|
|
// Have we hit the LF (as in the CRLF ending the header)?
|
|
/////////////////////////////////////////////////////////
|
|
endOfHeader = strchr(data, 0xA);
|
|
if(endOfHeader)
|
|
{
|
|
// Append what we have to the buffer.
|
|
/////////////////////////////////////
|
|
ghiAppendToChunkHeaderBuffer(connection, data, endOfHeader - data);
|
|
|
|
// Adjust data and len.
|
|
///////////////////////
|
|
endOfHeader++;
|
|
len -= (endOfHeader - data);
|
|
data = endOfHeader;
|
|
|
|
// Read the chunk size.
|
|
///////////////////////
|
|
connection->chunkBytesLeft = ghiParseChunkSize(connection);
|
|
if(connection->chunkBytesLeft == -1)
|
|
{
|
|
// There was an error reading the chunk size.
|
|
/////////////////////////////////////////////
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPBadResponse;
|
|
return GHTTPFalse;
|
|
}
|
|
|
|
// Set the chunk reading state.
|
|
///////////////////////////////
|
|
if(connection->chunkBytesLeft == 0)
|
|
{
|
|
connection->chunkReadingState = CRFooter;
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_RawDump,
|
|
"Reading footer\n");
|
|
}
|
|
else
|
|
{
|
|
connection->chunkReadingState = CRChunk;
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_RawDump,
|
|
"Reading %d byte chunk\n", connection->chunkBytesLeft);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Move it all into the buffer.
|
|
///////////////////////////////
|
|
ghiAppendToChunkHeaderBuffer(connection, data, len);
|
|
|
|
// Nothing else we can do now.
|
|
//////////////////////////////
|
|
return GHTTPTrue;
|
|
}
|
|
}
|
|
// Reading a chunk?
|
|
///////////////////
|
|
else if(connection->chunkReadingState == CRChunk)
|
|
{
|
|
int numBytes;
|
|
|
|
// How many bytes of data are we dealing with?
|
|
//////////////////////////////////////////////
|
|
numBytes = min(connection->chunkBytesLeft, len);
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_RawDump,
|
|
"Read %d bytes of chunk\n", numBytes);
|
|
|
|
// Deliver the bytes.
|
|
/////////////////////
|
|
if(!ghiDeliverIncomingFileData(connection, data, numBytes))
|
|
return GHTTPFalse;
|
|
|
|
// Adjust data and len.
|
|
///////////////////////
|
|
data += numBytes;
|
|
len -= numBytes;
|
|
|
|
// Figure out how many bytes left in chunk.
|
|
///////////////////////////////////////////
|
|
connection->chunkBytesLeft -= numBytes;
|
|
|
|
// Did we finish the chunk?
|
|
///////////////////////////
|
|
if(connection->chunkBytesLeft == 0)
|
|
connection->chunkReadingState = CRCRLF;
|
|
}
|
|
// Reading a chunk footer (CRLF)?
|
|
/////////////////////////////////
|
|
else if(connection->chunkReadingState == CRCRLF)
|
|
{
|
|
char * endOfFooter;
|
|
|
|
// Did we get an LF?
|
|
////////////////////
|
|
endOfFooter = strchr(data, 0xA);
|
|
|
|
// The footer hasn't ended yet.
|
|
///////////////////////////////
|
|
if(!endOfFooter)
|
|
return GHTTPTrue;
|
|
|
|
// Adjust data and len.
|
|
///////////////////////
|
|
endOfFooter++;
|
|
len -= (endOfFooter - data);
|
|
data = endOfFooter;
|
|
|
|
// Set up for reading the next header.
|
|
//////////////////////////////////////
|
|
connection->chunkHeader[0] = '\0';
|
|
connection->chunkHeaderLen = 0;
|
|
connection->chunkBytesLeft = 0;
|
|
connection->chunkReadingState = CRHeader;
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_RawDump,
|
|
"Read chunk footer\n");
|
|
}
|
|
// Reading the footer?
|
|
//////////////////////
|
|
else if(connection->chunkReadingState == CRFooter)
|
|
{
|
|
// We're done.
|
|
//////////////
|
|
connection->completed = GHTTPTrue;
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_RawDump,
|
|
"Finished reading chunks\n");
|
|
|
|
return GHTTPTrue;
|
|
}
|
|
// Bad state!
|
|
/////////////
|
|
else
|
|
{
|
|
assert(0);
|
|
return GHTTPFalse;
|
|
}
|
|
}
|
|
|
|
return GHTTPTrue;
|
|
}
|
|
|
|
// Regular transfer, just deliver it.
|
|
/////////////////////////////////////
|
|
return ghiDeliverIncomingFileData(connection, data, len);
|
|
}
|
|
|
|
/**********************
|
|
** RECEIVING HEADERS **
|
|
**********************/
|
|
void ghiDoReceivingHeaders
|
|
(
|
|
GHIConnection * connection
|
|
)
|
|
{
|
|
char buffer[4096];
|
|
int bufferLen;
|
|
GHIRecvResult result;
|
|
GHTTPBool hasHeaders = GHTTPTrue;
|
|
char * headers;
|
|
char * endOfHeaders = NULL;
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Receiving Headers\n");
|
|
|
|
// Get data.
|
|
////////////
|
|
bufferLen = sizeof(buffer);
|
|
result = ghiDoReceive(connection, buffer, &bufferLen);
|
|
|
|
// Handle error, no data, conn closed.
|
|
//////////////////////////////////////
|
|
if(result == GHIError)
|
|
return;
|
|
if(result == GHINoData)
|
|
return;
|
|
|
|
// Only append data if we got data.
|
|
///////////////////////////////////
|
|
if(result == GHIRecvData)
|
|
{
|
|
// Check for encryption.
|
|
////////////////////////
|
|
if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None &&
|
|
connection->encryptor.mEncryptOnBuffer == GHTTPTrue)
|
|
{
|
|
// Append new encrypted data to anything we've held over
|
|
// We have to do this because we can't decrypt partial SSL messages
|
|
if (!ghiAppendDataToBuffer(&connection->decodeBuffer, buffer, bufferLen))
|
|
return;
|
|
|
|
// Decrypt as much as we can
|
|
if (!ghiDecryptReceivedData(connection))
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPEncryptionError;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Add the data directly to the buffer.
|
|
///////////////////////////////////////
|
|
if(!ghiAppendDataToBuffer(&connection->recvBuffer, buffer, bufferLen))
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Cache a pointer to the front of the headers.
|
|
///////////////////////////////////////////////
|
|
headers = (connection->recvBuffer.data + connection->headerStringIndex);
|
|
|
|
// Check if the headers are finished.
|
|
/////////////////////////////////////
|
|
if( ((connection->statusCode / 100) == 1) &&
|
|
(strncmp(headers, "\r\n", 2) == 0 || strncmp(headers, "\xA\xA", 2) == 0)
|
|
)
|
|
{
|
|
// If a continue doesn't have a header (immediate CRLF) move on to next status
|
|
endOfHeaders = headers;
|
|
hasHeaders = GHTTPFalse;
|
|
}
|
|
else
|
|
{
|
|
endOfHeaders = strstr(headers, CRLF CRLF);
|
|
}
|
|
if(!endOfHeaders)
|
|
{
|
|
endOfHeaders = strstr(headers, "\xA\xA"); // some servers seem to use LFs only?! Seen in 302 redirect. (28may01/bgw)
|
|
}
|
|
if(endOfHeaders)
|
|
{
|
|
char * fileStart;
|
|
int fileLength;
|
|
//int headersLength;
|
|
char * contentLength;
|
|
|
|
#ifdef HTTP_LOG
|
|
int headersLength;
|
|
#endif
|
|
|
|
// Clear off the empty line.
|
|
////////////////////////////
|
|
if (GHTTPTrue == hasHeaders)
|
|
endOfHeaders += 2;
|
|
*endOfHeaders = '\0';
|
|
|
|
// Figure out where the file starts, and how many bytes.
|
|
////////////////////////////////////////////////////////
|
|
#ifdef HTTP_LOG
|
|
headersLength = (endOfHeaders - headers);
|
|
#endif
|
|
|
|
fileStart = (endOfHeaders + 2);
|
|
fileLength = (connection->recvBuffer.len - (fileStart - connection->recvBuffer.data));
|
|
|
|
// Set the headers buffer's new length.
|
|
///////////////////////////////////////
|
|
connection->recvBuffer.len = (endOfHeaders - connection->recvBuffer.data + 1);
|
|
connection->recvBuffer.pos = connection->recvBuffer.len;
|
|
|
|
// Log it.
|
|
//////////
|
|
#ifdef HTTP_LOG
|
|
ghiLogResponse(headers, headersLength);
|
|
ghiLogResponse("\n", 1);
|
|
#endif
|
|
|
|
// Check for continue.
|
|
//////////////////////
|
|
if((connection->statusCode / 100) == 1)
|
|
{
|
|
if(fileLength)
|
|
{
|
|
// Move any data to the front of the buffer.
|
|
////////////////////////////////////////////
|
|
memmove(connection->recvBuffer.data, fileStart, (unsigned int)fileLength + 1);
|
|
connection->recvBuffer.len = fileLength;
|
|
}
|
|
else
|
|
{
|
|
// Reset the buffer.
|
|
/////////////////////////
|
|
ghiResetBuffer(&connection->recvBuffer);
|
|
}
|
|
|
|
// Some posts must wait for continue before uploading
|
|
// Check if we should return to posting
|
|
if (connection->postingState.waitPostContinue)
|
|
{
|
|
connection->postingState.waitPostContinue = GHTTPFalse;
|
|
connection->state = GHTTPPosting;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
}
|
|
|
|
// We're back to receiving status.
|
|
//////////////////////////////////
|
|
connection->state = GHTTPReceivingStatus;
|
|
ghiCallProgressCallback(connection, NULL, 0);
|
|
|
|
return;
|
|
}
|
|
|
|
// Check for redirection.
|
|
/////////////////////////
|
|
if((connection->statusCode / 100) == 3)
|
|
{
|
|
char * location;
|
|
|
|
// Are we over our redirection count?
|
|
/////////////////////////////////////
|
|
if(connection->redirectCount > 10)
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPFileNotFound;
|
|
return;
|
|
}
|
|
|
|
// Find the new location.
|
|
/////////////////////////
|
|
location = strstr(headers, "Location:");
|
|
if(location)
|
|
{
|
|
char * end;
|
|
|
|
// Find the start of the URL.
|
|
/////////////////////////////
|
|
location += 9;
|
|
while(isspace(*location))
|
|
location++;
|
|
|
|
// Find the end.
|
|
////////////////
|
|
for(end = location; *end && !isspace(*end) ; end++) { };
|
|
*end = '\0';
|
|
|
|
// Check if this is not a full URL.
|
|
///////////////////////////////////
|
|
if(*location == '/')
|
|
{
|
|
int len;
|
|
|
|
// Recompose the URL ourselves.
|
|
///////////////////////////////
|
|
len = (int)(strlen(connection->serverAddress) + 13 + strlen(location) + 1);
|
|
connection->redirectURL = (char *)gsimalloc((unsigned int)len);
|
|
if(!connection->redirectURL)
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPOutOfMemory;
|
|
}
|
|
sprintf(connection->redirectURL, "http://%s:%d%s", connection->serverAddress, connection->serverPort, location);
|
|
}
|
|
else
|
|
{
|
|
// Set the redirect URL.
|
|
////////////////////////
|
|
connection->redirectURL = goastrdup(location);
|
|
if(!connection->redirectURL)
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPOutOfMemory;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If we know the file-length, set it.
|
|
//////////////////////////////////////
|
|
contentLength = strstr(headers, "Content-Length:");
|
|
if(contentLength)
|
|
{
|
|
// Verify that the download size is something we can handle
|
|
///////////////////////////////////////////////////////////
|
|
#if (GSI_MAX_INTEGRAL_BITS >= 64)
|
|
char szMaxSize[] = "9223372036854775807"; // == GSI_MAX_I64
|
|
#else
|
|
char szMaxSize[] = "2147483647"; // == GSI_MAX_I32
|
|
#endif
|
|
char* pStart = contentLength+16;
|
|
char* pEnd = pStart;
|
|
int nMaxLen = (int)strlen(szMaxSize);
|
|
|
|
// Skip to the end of the line
|
|
while( pEnd && *pEnd != '\0' && *pEnd != '\n' && *pEnd != '\r' && *pEnd != ' ' )
|
|
pEnd++;
|
|
|
|
if( pEnd-pStart > nMaxLen )
|
|
{
|
|
// Wow, that IS a big number
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPFileToBig;
|
|
return;
|
|
}
|
|
else
|
|
if( pEnd-pStart == nMaxLen )
|
|
{
|
|
// Same length, maybe a bigger number
|
|
if( strncmp(pStart,szMaxSize,(unsigned int)(pEnd-pStart)) >= 0 )
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPFileToBig;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Record the full size of the expected download
|
|
////////////////////////////////////////////////
|
|
#if (GSI_MAX_INTEGRAL_BITS >= 64)
|
|
connection->totalSize = _atoi64(pStart);
|
|
#else
|
|
connection->totalSize = atoi(pStart);
|
|
#endif
|
|
}
|
|
|
|
// Check the chunky.
|
|
////////////////////
|
|
connection->chunkedTransfer = (strstr(headers, "Transfer-Encoding: chunked") != NULL)?GHTTPTrue:GHTTPFalse;
|
|
if(connection->chunkedTransfer)
|
|
{
|
|
connection->chunkHeader[0] = '\0';
|
|
connection->chunkHeaderLen = 0;
|
|
connection->chunkBytesLeft = 0;
|
|
connection->chunkReadingState = CRHeader;
|
|
}
|
|
|
|
// If we're just getting headers, or only posting data, we're done.
|
|
///////////////////////////////////////////////////////////////////
|
|
if((connection->type == GHIHEAD) || (connection->type == GHIPOST))
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
return;
|
|
}
|
|
|
|
// We're receiving file data now.
|
|
/////////////////////////////////
|
|
connection->state = GHTTPReceivingFile;
|
|
|
|
// Is this an empty file?
|
|
/////////////////////////
|
|
if(contentLength && !connection->totalSize)
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
return;
|
|
}
|
|
|
|
// If any of the body has arrived, handle it.
|
|
/////////////////////////////////////////////
|
|
if(fileLength > 0)
|
|
ghiProcessIncomingFileData(connection, fileStart, fileLength);
|
|
|
|
// Don't reset the buffer -- we store status and header info
|
|
//ghiResetBuffer(&connection->recvBuffer);
|
|
}
|
|
else if(result == GHIConnClosed)
|
|
{
|
|
// The conn was closed, and we didn't finish the headers - bad.
|
|
///////////////////////////////////////////////////////////////
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPBadResponse;
|
|
connection->socketError = GOAGetLastError(connection->socket);
|
|
}
|
|
}
|
|
|
|
/*******************
|
|
** RECEIVING FILE **
|
|
*******************/
|
|
void ghiDoReceivingFile
|
|
(
|
|
GHIConnection * connection
|
|
)
|
|
{
|
|
char buffer[8192];
|
|
int bufferLen;
|
|
GHIRecvResult result;
|
|
gsi_time start_time = current_time();
|
|
gsi_time running_time = 0;
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Receiving File\n");
|
|
|
|
while(!connection->completed && (running_time < connection->maxRecvTime))
|
|
{
|
|
// Get data.
|
|
////////////
|
|
bufferLen = sizeof(buffer);
|
|
result = ghiDoReceive(connection, buffer, &bufferLen);
|
|
|
|
// Handle error, no data, conn closed.
|
|
//////////////////////////////////////
|
|
if(result == GHIError)
|
|
return;
|
|
if(result == GHINoData)
|
|
return;
|
|
if(result == GHIConnClosed)
|
|
{
|
|
// The file is done (hopefully).
|
|
////////////////////////////////
|
|
connection->completed = GHTTPTrue;
|
|
|
|
if (connection->totalSize > 0 && connection->fileBytesReceived < connection->totalSize)
|
|
connection->result = GHTTPFileIncomplete;
|
|
return;
|
|
}
|
|
|
|
// Check for encryption.
|
|
////////////////////////
|
|
if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None &&
|
|
connection->encryptor.mEncryptOnBuffer == GHTTPTrue)
|
|
{
|
|
char * decryptedData;
|
|
int decryptedLen;
|
|
|
|
// Append new encrypted data to anything we've held over
|
|
// We have to do this because we can't decrypt partial SSL messages
|
|
if (!ghiAppendDataToBuffer(&connection->decodeBuffer, buffer, bufferLen))
|
|
return;
|
|
|
|
// Previously decrypted parts of the file have already been handled.
|
|
connection->recvBuffer.len = connection->recvBuffer.pos;
|
|
|
|
// Decrypt as much as we can
|
|
if (!ghiDecryptReceivedData(connection))
|
|
{
|
|
connection->completed = GHTTPTrue;
|
|
connection->result = GHTTPEncryptionError;
|
|
return;
|
|
}
|
|
|
|
// Check for decrypted data.
|
|
////////////////////////////
|
|
decryptedLen = (connection->recvBuffer.len - connection->recvBuffer.pos);
|
|
if(decryptedLen)
|
|
{
|
|
// Process the data.
|
|
////////////////////
|
|
decryptedData = (connection->recvBuffer.data + connection->recvBuffer.pos);
|
|
if(!ghiProcessIncomingFileData(connection, decryptedData, decryptedLen))
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Process the data.
|
|
////////////////////
|
|
if(!ghiProcessIncomingFileData(connection, buffer, bufferLen))
|
|
return;
|
|
}
|
|
|
|
running_time = current_time() - start_time;
|
|
}
|
|
}
|