summaryrefslogtreecommitdiff
path: root/osframework/source/SexyAppFramework/HTTPTransfer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'osframework/source/SexyAppFramework/HTTPTransfer.cpp')
-rw-r--r--osframework/source/SexyAppFramework/HTTPTransfer.cpp574
1 files changed, 574 insertions, 0 deletions
diff --git a/osframework/source/SexyAppFramework/HTTPTransfer.cpp b/osframework/source/SexyAppFramework/HTTPTransfer.cpp
new file mode 100644
index 0000000..f5445f3
--- /dev/null
+++ b/osframework/source/SexyAppFramework/HTTPTransfer.cpp
@@ -0,0 +1,574 @@
+#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;
+}