Blame |
Last modification |
View Log
| RSS feed
#include "HTTPTransfer.h"
#include "SexyAppBase.h"
#include <process.h>
#include <winsock.h>
using namespace Sexy;
static int gCurTransferId = 1;
HTTPTransfer::HTTPTransfer()
{
mTransferId = gCurTransferId++;
mResult = RESULT_NOT_STARTED;
mContentLength = 0;
mDemoLastKnownResult = mResult;
mThreadRunning = false;
}
HTTPTransfer::~HTTPTransfer()
{
Abort();
while (mThreadRunning)
{
Sleep(20);
}
}
std::string HTTPTransfer::GetAbsURL(const std::string& theBaseURL, const std::string& theRelURL)
{
std::string aURL;
if (theRelURL.substr(0, 7).compare("http://") == 0)
{
aURL = theRelURL;
}
else if (theRelURL.substr(0, 1).compare("/") == 0)
{
int aFirstSlashPos = theBaseURL.find('/', 7);
if (aFirstSlashPos != -1)
{
aURL = theBaseURL.substr(0, aFirstSlashPos) + theRelURL;
}
else
{
aURL = theBaseURL + theRelURL;
}
}
else
{
int aLastSlashPos = theBaseURL.rfind('/');
if (aLastSlashPos >= 7)
{
aURL = theBaseURL.substr(0, aLastSlashPos+1) + theRelURL;
}
else
{
aURL = theBaseURL + "/" + theRelURL;
}
}
return aURL;
}
void HTTPTransfer::Fail(int theResult)
{
mResult = theResult;
mExiting = true;
}
bool HTTPTransfer::SocketWait(bool checkRead, bool checkWrite)
{
while (!mExiting)
{
fd_set aReadSet;
fd_set aWriteSet;
fd_set anExceptSet;
FD_ZERO(&aReadSet);
FD_ZERO(&aWriteSet);
FD_ZERO(&anExceptSet);
if (checkRead)
FD_SET(mSocket, &aReadSet);
if (checkWrite)
FD_SET(mSocket, &aWriteSet);
FD_SET(mSocket, &anExceptSet);
// Check every 100ms
int aReadTime = 100;
TIMEVAL aTimeout;
aTimeout.tv_sec = aReadTime/1000;
aTimeout.tv_usec = (aReadTime%1000)*1000;
int aVal = select(FD_SETSIZE, &aReadSet, &aWriteSet, &anExceptSet, &aTimeout);
if (aVal == SOCKET_ERROR)
{
Fail(RESULT_SOCKET_ERROR);
return false;
}
if (FD_ISSET(mSocket, &anExceptSet))
{
Fail(RESULT_SOCKET_ERROR);
return false;
}
if (FD_ISSET(mSocket, &aReadSet))
return true;
if (FD_ISSET(mSocket, &aWriteSet))
return true;
}
// Return true on abort
return true;
}
void HTTPTransfer::GetThreadProc()
{
WSADATA aDat;
WSAStartup(MAKEWORD(1,1),&aDat);
mSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (mSocket == 0)
Fail(RESULT_SOCKET_ERROR);
// Set non-blocking
ulong anIoctlVal = 1;
ioctlsocket(mSocket, FIONBIO, &anIoctlVal);
if (!mExiting)
{
unsigned long anAddr = inet_addr(mHost.c_str());
if (anAddr == INADDR_NONE)
{
HOSTENT *aHostEnt = gethostbyname(mHost.c_str());
if (aHostEnt != NULL)
memcpy(&anAddr, aHostEnt->h_addr_list[0], 4);
}
if (anAddr == INADDR_NONE)
{
Fail(RESULT_INVALID_ADDR);
}
else
{
SOCKADDR_IN aSockAddrIn;
memset((char*) &aSockAddrIn, 0, sizeof(aSockAddrIn));
aSockAddrIn.sin_family = AF_INET;
aSockAddrIn.sin_addr.s_addr = anAddr;
aSockAddrIn.sin_port = htons(mPort);
if (::connect(mSocket, (sockaddr*) &aSockAddrIn, sizeof(SOCKADDR_IN)) != 0)
{
if (WSAGetLastError() != WSAEWOULDBLOCK)
Fail(RESULT_CONNECT_FAIL);
// Wait for socket to become writable to know we connected
if (!SocketWait(false, true))
Fail(RESULT_CONNECT_FAIL);
}
while ((mSendStr.length() > 0) && (!mExiting))
{
SocketWait(false, true);
int aResult = send(mSocket, mSendStr.c_str(), mSendStr.length(), 0);
if (aResult > 0)
{
mSendStr = mSendStr.substr(aResult);
}
else
{
int anError = WSAGetLastError();
Fail(RESULT_DISCONNECTED);
}
}
bool chunked = false;
bool gotHeader = false;
int aPos = 0;
int aLastPos = 0;
int aChunkLengthLeft = 0;
int aContentLengthLeft = 0;
std::string aRecvStr;
while (!mExiting)
{
// Wait for more characters if we couldn't do any processing with the
// current ones
if (aLastPos == aPos)
{
char aBuffer[257];
SocketWait(true, false);
int aResult = recv(mSocket, aBuffer, 256, 0);
if (aResult > 0)
{
aRecvStr.insert(aRecvStr.end(), aBuffer, aBuffer+aResult);
aBuffer[aResult] = 0;
}
else
{
int anError = WSAGetLastError();
break;
}
}
aLastPos = aPos;
if (!gotHeader)
{
// Still reading header...
int anEndLPos = aRecvStr.find("\r\n", aPos);
if (anEndLPos == -1)
continue; // Not enough for a full line yet
std::string aLine = aRecvStr.substr(aPos, anEndLPos-aPos);
aPos = anEndLPos+2;
if (aLine.substr(0, 7) == "HTTP/1.")
{
std::string aResult = aLine.substr(9, 3);
if (aResult == "404")
{
Fail(RESULT_NOT_FOUND);
}
else if (aResult != "200")
{
Fail(RESULT_HTTP_ERROR);
}
}
if (aLine == "Transfer-Encoding: chunked")
{
chunked = true;
}
char* aCheckStr = "Transfer-Encoding: ";
if (strncmp(aLine.c_str(), aCheckStr, strlen(aCheckStr)) == 0)
{
if (strcmp(aLine.c_str() + strlen(aCheckStr), "identity") != 0)
chunked = true;
}
aCheckStr = "Content-Length: ";
if (strncmp(aLine.c_str(), aCheckStr, strlen(aCheckStr)) == 0)
{
aContentLengthLeft = atoi(aLine.c_str() + strlen(aCheckStr));
mContentLength = aContentLengthLeft;
}
// Header is complete when we get a blank line
if (aLine.length() == 0)
gotHeader = true;
}
else
{
if (chunked)
{
// If it is zero, we need to get the next chunk length
if (aChunkLengthLeft == 0)
{
// Get a hex length
int anEndLPos = aRecvStr.find("\r\n", aPos);
if (anEndLPos == -1)
continue; // Not enough for a full line yet
std::string aLine = aRecvStr.substr(aPos, anEndLPos-aPos);
if (!StringToInt("0x" + Trim(aLine), &aChunkLengthLeft))
break; // End transfer on conversion error
if (aChunkLengthLeft == 0)
break; // Zero-size chunk marks end of chunked transfer
aPos = anEndLPos+2;
}
// Chunk it in
int aCopyLen = min(aChunkLengthLeft, (int)aRecvStr.length() - aPos);
if (aCopyLen > 0)
{
mContent += aRecvStr.substr(aPos, aCopyLen);
aPos += aCopyLen;
aChunkLengthLeft -= aCopyLen;
// There is always a CRLF at the end of the chunk data
if (aChunkLengthLeft == 0)
aPos += 2;
}
}
else
{
if (aContentLengthLeft > 0)
{
int aCopyLen = min(aContentLengthLeft, (int)aRecvStr.length() - aPos);
mContent += aRecvStr.substr(aPos, aCopyLen);
aPos += aCopyLen;
aContentLengthLeft -= aCopyLen;
if (aContentLengthLeft == 0)
{
// We have reached the end at this point
break;
}
}
else
{
// No length specified, we will just read until we close
int aCopyLen = aRecvStr.length() - aPos;
mContent += aRecvStr.substr(aPos, aCopyLen);
aPos += aCopyLen;
}
}
}
}
}
}
closesocket(mSocket);
mSocket = NULL;
WSACleanup();
if (mAborted)
Fail(RESULT_ABORTED);
else if (mResult == RESULT_NOT_COMPLETED)
mResult = RESULT_DONE;
mThreadRunning = false;
mTransferPending = false;
}
void HTTPTransfer::GetThreadProcStub(void *theArg)
{
((HTTPTransfer*) theArg)->GetThreadProc();
}
void HTTPTransfer::PrepareTransfer(const std::string& theURL)
{
Reset();
mTransferId = gCurTransferId++;
mURL = theURL;
mExiting = false;
mAborted = false;
mResult = RESULT_NOT_COMPLETED;
mContent.erase();
mContentLength = 0;
mPort = 80;
int aSSPos = mURL.find("//");
int aPos = 0;
if (aSSPos != -1)
aPos = aSSPos + 2;
if (aSSPos != -1)
mProto = mURL.substr(0, aSSPos-1);
int aSlashPos = mURL.find("/", aPos);
if (aSlashPos != -1)
{
mHost = mURL.substr(aPos, aSlashPos-aPos);
mPath = mURL.substr(aSlashPos);
}
else
{
mHost = mURL.substr(aPos);
mPath = "/";
}
int aSemiPos = (int) mHost.find(':');
if (aSemiPos != -1)
{
mPort = atoi(mHost.substr(aSemiPos + 1).c_str());
mHost = mHost.substr(0, aSemiPos);
}
}
void HTTPTransfer::StartTransfer()
{
mTransferPending = true;
// Don't really start the transfer while in demo playing mode
if ((gSexyAppBase != NULL) && (gSexyAppBase->mPlayingDemoBuffer))
return;
mThreadRunning = true;
_beginthread(GetThreadProcStub, 0, this);
}
void HTTPTransfer::GetHelper(const std::string& theURL)
{
PrepareTransfer(theURL);
mSendStr =
"GET " + mPath + " HTTP/1.0\r\n"
"User-Agent: Mozilla/4.0 (compatible; popcap)\r\n"
"Host: " + mHost + "\r\n"
"Connection: close\r\n" +
"\r\n";
StartTransfer();
}
void HTTPTransfer::PostHelper(const std::string& theURL, const std::string& theParams)
{
PrepareTransfer(theURL);
mSendStr =
"POST " + mPath + " HTTP/1.0\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n" +
"User-Agent: Mozilla/4.0 (compatible; popcap)\r\n"
"Host: " + mHost + "\r\n"
"Content-Length: " + StrFormat("%d", theParams.length()) + "\r\n" +
"Connection: close\r\n" +
"\r\n" + theParams;
StartTransfer();
}
void HTTPTransfer::Get(const std::string& theURL)
{
mSpecifiedBaseURL = "";
mSpecifiedRelURL = theURL;
GetHelper(theURL);
}
void HTTPTransfer::Post(const std::string& theURL, const std::string& theParams)
{
mSpecifiedBaseURL = "";
mSpecifiedRelURL = theURL;
PostHelper(theURL, theParams);
}
void HTTPTransfer::Get(const std::string& theBaseURL, const std::string& theRelURL)
{
mSpecifiedBaseURL = theBaseURL;
mSpecifiedRelURL = theRelURL;
GetHelper(GetAbsURL(theBaseURL, theRelURL));
}
void HTTPTransfer::Post(const std::string& theBaseURL, const std::string& theRelURL, const std::string& theParams)
{
mSpecifiedBaseURL = theBaseURL;
mSpecifiedRelURL = theRelURL;
PostHelper(GetAbsURL(theBaseURL, theRelURL), theParams);
}
void HTTPTransfer::SendRequestString(const std::string& theHost, const std::string& theSendString)
{
mSpecifiedBaseURL.erase();
mSpecifiedRelURL.erase();
PrepareTransfer(theHost);
mSendStr = theSendString;
StartTransfer();
}
void HTTPTransfer::Abort()
{
mAborted = true;
mExiting = true;
}
void HTTPTransfer::Reset()
{
if (mThreadRunning)
{
Abort();
WaitFor();
}
mResult = RESULT_NOT_STARTED;
mDemoLastKnownResult = RESULT_NOT_STARTED;
mTransferId = gCurTransferId++;
mContent.erase();
mExiting = false;
mAborted = false;
mURL.erase();
mSendStr.erase();
mPath.erase();
}
static int aLastThreadId = 0;
void HTTPTransfer::UpdateStatus()
{
// This will save the result data in demo recording mode and load it in (if available)
// in demo playback mode
if (gSexyAppBase != NULL)
{
if (gSexyAppBase->mPlayingDemoBuffer)
{
// We only need to try to get the result if we think we are waiting for one
if (gSexyAppBase->PrepareDemoCommand(false))
{
if ((!gSexyAppBase->mDemoIsShortCmd) && (gSexyAppBase->mDemoCmdNum == DEMO_HTTP_RESULT))
{
int anOldBufferPos = gSexyAppBase->mDemoBuffer.mReadBitPos;
// Since we don't require a demo result entry to be here, we must verify
// that this is referring to us
int aTransferId = gSexyAppBase->mDemoBuffer.ReadLong();
if (aTransferId == mTransferId)
{
// We finally got our result!
mResult = gSexyAppBase->mDemoBuffer.ReadByte();
mContent = gSexyAppBase->mDemoBuffer.ReadString();
mDemoLastKnownResult = mResult;
gSexyAppBase->mDemoNeedsCommand = true;
//TODO:
//OutputDebugString(StrFormat("Found State Change on %s (Id %d) to %d at %d\r\n", mURL.c_str(), mTransferId, mResult, gSexyAppBase->mUpdateCount).c_str());
}
else
{
// Not us, go back
gSexyAppBase->mDemoBuffer.mReadBitPos = anOldBufferPos;
}
}
}
}
else if ((gSexyAppBase->mRecordingDemoBuffer) && (mResult != mDemoLastKnownResult))
{
//TODO:
//OutputDebugString(StrFormat("Recording State Change on %s (Id %d) to %d at %d\r\n", mURL.c_str(), mTransferId, mResult, gSexyAppBase->mUpdateCount).c_str());
// Write out the new result
gSexyAppBase->WriteDemoTimingBlock();
gSexyAppBase->mDemoBuffer.WriteNumBits(0, 1);
gSexyAppBase->mDemoBuffer.WriteNumBits(DEMO_HTTP_RESULT, 5);
gSexyAppBase->mDemoBuffer.WriteLong(mTransferId);
gSexyAppBase->mDemoBuffer.WriteByte(mResult);
// Avoid any threading issues by not allowing access to mContent while in progress
if (mResult == RESULT_NOT_COMPLETED)
gSexyAppBase->mDemoBuffer.WriteString("");
else
gSexyAppBase->mDemoBuffer.WriteString(mContent);
mDemoLastKnownResult = mResult;
}
}
}
void HTTPTransfer::WaitFor()
{
while (mTransferPending)
{
UpdateStatus();
Sleep(20);
}
}
int HTTPTransfer::GetResultCode()
{
UpdateStatus();
return mResult;
}
std::string HTTPTransfer::GetContent()
{
if (mResult == RESULT_NOT_COMPLETED)
return "";
UpdateStatus();
return mContent;
}