Subversion Repositories AndroidProjects

Rev

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;
}