726 lines
18 KiB
C++
726 lines
18 KiB
C++
#include "global.h"
|
|||
|
|
#include "CrashHandlerNetworking.h"
|
||
|
|
#include "RageThreads.h"
|
||
|
|
#include "RageLog.h"
|
||
|
|
#include "RageThreads.h"
|
||
|
|
#include "RageTimer.h"
|
||
|
|
#include "RageUtil.h"
|
||
|
|
|
||
|
|
#if defined(WINDOWS)
|
||
|
|
#include <windows.h>
|
||
|
|
#include <winsock2.h>
|
||
|
|
#pragma comment(lib, "wsock32.lib")
|
||
|
|
#pragma comment(lib, "ws2_32.lib")
|
||
|
|
#endif
|
||
|
|
|
||
|
|
/*
|
||
|
|
* This has an arch-like abstraction layout, since it's intended to become one.
|
||
|
|
* It'll be moved out of here as things get polished further. The Winsock and
|
||
|
|
* BSD implementations will be kept separate, since while they look similar,
|
||
|
|
* they're different in the details, especially when you want to cleanly support
|
||
|
|
* cancellation.
|
||
|
|
*
|
||
|
|
* Design notes:
|
||
|
|
*
|
||
|
|
* - Allow cancellation at any time. The only thing more annoying than a laggy
|
||
|
|
* network, is an application that won't immediately respond to cancelling.
|
||
|
|
* (This is even more important here, since we're using this after a crash.)
|
||
|
|
* - Assume some operations and/or some architectures are going to have problems
|
||
|
|
* doing everything nonblockingly, and do everything in a thread. This also allows
|
||
|
|
* more complex network interactions, since it doesn't need to maintain an equally
|
||
|
|
* complex state machine.
|
||
|
|
* - When an operation is cancelled or an error occurs, all operations until the
|
||
|
|
* next call to Close() become no-ops. This allows lenient error checking; it's
|
||
|
|
* guaranteed that further calls to Read or Write will not reset the error state.
|
||
|
|
* - The only function that may be called from another thread without serialization is
|
||
|
|
* Cancel().
|
||
|
|
*
|
||
|
|
* All calls are blocking, except for:
|
||
|
|
*
|
||
|
|
* - Cancel(), which is always nonblocking. The operation in progress, if any,
|
||
|
|
* will be aborted immediately. (The only exception here is incomplete
|
||
|
|
* implementations, which may block);
|
||
|
|
* - Close(), if Shutdown() was called first, since the necessary blocking occurs
|
||
|
|
* during Shutdown;
|
||
|
|
* - Close(), if an error or cancellation occurred.
|
||
|
|
*
|
||
|
|
* Accepting incoming TCP connections is beyond the immediate scope of this interface
|
||
|
|
* (that would involve a second listener class, which would be a factory for NetworkStream).
|
||
|
|
* UDP is probably within the scope of this class, but there are some outstanding design
|
||
|
|
* issues and it's beyond the work I'm doing right now.
|
||
|
|
*/
|
||
|
|
|
||
|
|
class NetworkStream
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
enum ConnectionType
|
||
|
|
{
|
||
|
|
CONN_TCP,
|
||
|
|
CONN_UDP,
|
||
|
|
};
|
||
|
|
NetworkStream() { }
|
||
|
|
virtual ~NetworkStream() { }
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Open a connection. Must be in STATE_IDLE.
|
||
|
|
*/
|
||
|
|
virtual void Open( const RString &sHost, int iPort, ConnectionType ct = CONN_TCP ) = 0;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Close down a connection. Returns to STATE_IDLE.
|
||
|
|
*/
|
||
|
|
virtual void Close() = 0;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Wait for all sent data to be flushed, and shut down the connection.
|
||
|
|
*/
|
||
|
|
virtual void Shutdown() = 0;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Read data. Block until any data is received, then return all data
|
||
|
|
* available. Return the number of bytes read. The return value will
|
||
|
|
* always be >= 0, unless an error or cancellation occured.
|
||
|
|
*/
|
||
|
|
virtual int Read( void *pBuffer, size_t iSize ) = 0;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Write data to the socket. (Design note: we always write all of the
|
||
|
|
* data unless an error or cancellation occurs, and those states are checked
|
||
|
|
* with GetState(). If that happens, the number of bytes written is
|
||
|
|
* meaningless, since it may have simply been buffered and never sent. So,
|
||
|
|
* this function returns no value.)
|
||
|
|
*/
|
||
|
|
virtual void Write( const void *pBuffer, size_t iSize ) = 0;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Cancel the connection. This operation can clear an error state, aborts
|
||
|
|
* any blocking calls, never fails, and will always result in the socket
|
||
|
|
* being in STATE_CANCELLED.
|
||
|
|
*
|
||
|
|
* This is true even if the state was STATE_IDLE. For example, the user
|
||
|
|
* thread may be closing one connection on this object and opening another,
|
||
|
|
* and the UI thread may call Cancel() in between these operations.
|
||
|
|
*
|
||
|
|
* This operation is threadsafe: a socket can be cancelled from any thread
|
||
|
|
* while another thread is reading or writing. This is the only function
|
||
|
|
* that can be called without serialization.
|
||
|
|
*/
|
||
|
|
virtual void Cancel() = 0;
|
||
|
|
|
||
|
|
enum State
|
||
|
|
{
|
||
|
|
/* The stream is closed, and is ready to be opened. */
|
||
|
|
STATE_IDLE,
|
||
|
|
|
||
|
|
/* The stream is connected and able to send and receive data. */
|
||
|
|
STATE_CONNECTED,
|
||
|
|
|
||
|
|
/* The stream has been shut down on either end (either an EOF from
|
||
|
|
* the other end, or shutdown() being called). */
|
||
|
|
STATE_SHUTDOWN,
|
||
|
|
|
||
|
|
/* When in CANCELLED, all blocking calls fail immediately; this state must be
|
||
|
|
* cleared explicitly by calling Close(). */
|
||
|
|
STATE_CANCELLED,
|
||
|
|
STATE_ERROR
|
||
|
|
};
|
||
|
|
|
||
|
|
State GetState() const { return m_State; }
|
||
|
|
RString GetError() const { return m_sError; }
|
||
|
|
|
||
|
|
protected:
|
||
|
|
State m_State;
|
||
|
|
RString m_sError;
|
||
|
|
};
|
||
|
|
|
||
|
|
class NetworkStream_Win32: public NetworkStream
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
NetworkStream_Win32();
|
||
|
|
~NetworkStream_Win32();
|
||
|
|
|
||
|
|
void Open( const RString &sHost, int iPort, ConnectionType ct = CONN_TCP );
|
||
|
|
void Shutdown();
|
||
|
|
void Close();
|
||
|
|
int Read( void *pBuffer, size_t iSize );
|
||
|
|
void Write( const void *pBuffer, size_t iSize );
|
||
|
|
|
||
|
|
void Cancel();
|
||
|
|
|
||
|
|
private:
|
||
|
|
int WaitForCompletionOrCancellation( int iEvent );
|
||
|
|
void SetError( const RString &sError );
|
||
|
|
static RString WinSockErrorToString( int iError );
|
||
|
|
|
||
|
|
SOCKET m_Socket;
|
||
|
|
HANDLE m_hResolve;
|
||
|
|
HWND m_hResolveHwnd;
|
||
|
|
|
||
|
|
/* This event is signalled on cancellation, to wake us up if we're blocking. */
|
||
|
|
HANDLE m_hCompletionEvent;
|
||
|
|
|
||
|
|
RString m_sHost;
|
||
|
|
int m_iPort;
|
||
|
|
|
||
|
|
RageMutex m_Mutex;
|
||
|
|
};
|
||
|
|
|
||
|
|
NetworkStream *CreateNetworkStream()
|
||
|
|
{
|
||
|
|
static bool bInitted = false;
|
||
|
|
if( !bInitted )
|
||
|
|
{
|
||
|
|
bInitted = true;
|
||
|
|
WSADATA WSAData;
|
||
|
|
WORD iVersionRequested = MAKEWORD(2,0);
|
||
|
|
if( WSAStartup(iVersionRequested, &WSAData) != 0 )
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
return new NetworkStream_Win32;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* WinSock implementation of NetworkStream. */
|
||
|
|
NetworkStream_Win32::NetworkStream_Win32():
|
||
|
|
m_Mutex( "NetworkTCPSocket" )
|
||
|
|
{
|
||
|
|
m_iPort = -1;
|
||
|
|
m_State = STATE_IDLE;
|
||
|
|
m_Socket = NULL;
|
||
|
|
#if defined(WINDOWS)
|
||
|
|
m_hResolve = NULL;
|
||
|
|
m_hResolveHwnd = NULL;
|
||
|
|
m_hCompletionEvent = CreateEvent( NULL, true, false, NULL );
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
NetworkStream_Win32::~NetworkStream_Win32()
|
||
|
|
{
|
||
|
|
Close();
|
||
|
|
CloseHandle( m_hCompletionEvent );
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Wait for the specified network event to occur, or cancellation, whichever
|
||
|
|
* happens first. On cancellation, return -1; on error, return the error
|
||
|
|
* code; on success, return 0. */
|
||
|
|
int NetworkStream_Win32::WaitForCompletionOrCancellation( int iEvent )
|
||
|
|
{
|
||
|
|
while(1)
|
||
|
|
{
|
||
|
|
int iRet = WaitForSingleObject( m_hCompletionEvent, INFINITE );
|
||
|
|
if( iRet != WAIT_OBJECT_0 )
|
||
|
|
continue;
|
||
|
|
|
||
|
|
m_Mutex.Lock();
|
||
|
|
|
||
|
|
/* This will reset the event. Do this while we hold the lock. */
|
||
|
|
WSANETWORKEVENTS events;
|
||
|
|
WSAEnumNetworkEvents( m_Socket, m_hCompletionEvent, &events );
|
||
|
|
|
||
|
|
/* Was the event signalled due to cancellation? */
|
||
|
|
if( m_State == STATE_CANCELLED )
|
||
|
|
{
|
||
|
|
m_Mutex.Unlock();
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
m_Mutex.Unlock();
|
||
|
|
|
||
|
|
/* If the socket was closed while we were waiting, stop. */
|
||
|
|
if( (events.lNetworkEvents & (1<<FD_CLOSE_BIT)) )
|
||
|
|
return WSAECONNRESET;
|
||
|
|
|
||
|
|
/* If the event didn't actually occur, keep waiting. */
|
||
|
|
if( !(events.lNetworkEvents & (1<<iEvent)) )
|
||
|
|
continue;
|
||
|
|
|
||
|
|
return events.iErrorCode[iEvent];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
RString NetworkStream_Win32::WinSockErrorToString( int iError )
|
||
|
|
{
|
||
|
|
/* If iError is -1, we were cancelled and WaitForCompletionOrCancellation returned it. We won't
|
||
|
|
* use the error string. */
|
||
|
|
if( iError == -1 )
|
||
|
|
return RString();
|
||
|
|
|
||
|
|
// XXX
|
||
|
|
return ssprintf( "%i", iError );
|
||
|
|
}
|
||
|
|
|
||
|
|
void NetworkStream_Win32::SetError( const RString &sError )
|
||
|
|
{
|
||
|
|
m_Mutex.Lock();
|
||
|
|
if( m_State != STATE_CANCELLED )
|
||
|
|
{
|
||
|
|
m_sError = sError;
|
||
|
|
m_State = STATE_ERROR;
|
||
|
|
}
|
||
|
|
|
||
|
|
if( m_Socket != INVALID_SOCKET )
|
||
|
|
{
|
||
|
|
closesocket( m_Socket );
|
||
|
|
m_Socket = INVALID_SOCKET;
|
||
|
|
}
|
||
|
|
|
||
|
|
m_Mutex.Unlock();
|
||
|
|
}
|
||
|
|
|
||
|
|
/* WSAAsyncGetHostByName returns events through a window. */
|
||
|
|
#include "MessageWindow.h"
|
||
|
|
class ResolveMessageWindow: public MessageWindow
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
ResolveMessageWindow(): MessageWindow("DNS notification window")
|
||
|
|
{
|
||
|
|
m_iResult = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
int GetResult() const { return m_iResult; }
|
||
|
|
|
||
|
|
protected:
|
||
|
|
bool HandleMessage( UINT msg, WPARAM wParam, LPARAM lParam )
|
||
|
|
{
|
||
|
|
if( msg == WM_USER )
|
||
|
|
{
|
||
|
|
m_iResult = WSAGETASYNCERROR( lParam );
|
||
|
|
PostQuitMessage( 0 );
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if( msg == WM_USER+1 )
|
||
|
|
{
|
||
|
|
m_iResult = 0;
|
||
|
|
PostQuitMessage( 0 );
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
int m_iResult;
|
||
|
|
};
|
||
|
|
|
||
|
|
void NetworkStream_Win32::Open( const RString &sHost, int iPort, ConnectionType ct )
|
||
|
|
{
|
||
|
|
m_Mutex.Lock();
|
||
|
|
if( m_State == STATE_CANCELLED )
|
||
|
|
{
|
||
|
|
m_Mutex.Unlock();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Always shut down a stream completely before reusing it. */
|
||
|
|
ASSERT_M( m_State == STATE_IDLE, ssprintf("%s:%i: %i", sHost.c_str(), iPort, m_State) );
|
||
|
|
|
||
|
|
m_sHost = sHost;
|
||
|
|
m_iPort = iPort;
|
||
|
|
|
||
|
|
/* Look up the hostname. */
|
||
|
|
hostent *pHost = NULL;
|
||
|
|
char pBuf[MAXGETHOSTSTRUCT];
|
||
|
|
{
|
||
|
|
pHost = (hostent *) pBuf;
|
||
|
|
|
||
|
|
ResolveMessageWindow mw;
|
||
|
|
m_hResolve = WSAAsyncGetHostByName(
|
||
|
|
mw.GetHwnd(),
|
||
|
|
WM_USER,
|
||
|
|
m_sHost,
|
||
|
|
(char *) pHost,
|
||
|
|
MAXGETHOSTSTRUCT
|
||
|
|
);
|
||
|
|
m_hResolveHwnd = mw.GetHwnd();
|
||
|
|
m_Mutex.Unlock();
|
||
|
|
|
||
|
|
mw.Run();
|
||
|
|
|
||
|
|
m_Mutex.Lock();
|
||
|
|
m_hResolve = NULL;
|
||
|
|
m_hResolveHwnd = NULL;
|
||
|
|
if( m_State == STATE_CANCELLED )
|
||
|
|
{
|
||
|
|
m_Mutex.Unlock();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
int iError = mw.GetResult();
|
||
|
|
if( iError )
|
||
|
|
{
|
||
|
|
SetError( ssprintf("DNS error: %s", WinSockErrorToString(iError).c_str() ) );
|
||
|
|
m_Mutex.Unlock();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
m_Mutex.Unlock();
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
sockaddr_in addr;
|
||
|
|
addr.sin_addr.s_addr = *(DWORD *)pHost->h_addr_list[0];
|
||
|
|
addr.sin_family = PF_INET;
|
||
|
|
addr.sin_port = htons( (uint16_t) iPort );
|
||
|
|
|
||
|
|
m_Mutex.Lock();
|
||
|
|
m_Socket = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
|
||
|
|
if( m_Socket == INVALID_SOCKET )
|
||
|
|
{
|
||
|
|
int iError = WSAGetLastError();
|
||
|
|
SetError( ssprintf("Error creating socket: %s", WinSockErrorToString(iError).c_str() ) );
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Set up the completion event to be signalled when these events occur. This
|
||
|
|
* also sets the socket to nonblocking. */
|
||
|
|
WSAEventSelect( m_Socket, m_hCompletionEvent, FD_CONNECT|FD_READ|FD_WRITE|FD_CLOSE );
|
||
|
|
|
||
|
|
// fcntl( m_Socket, O_NONBLOCK, 1 );
|
||
|
|
|
||
|
|
/* Start opening the connection. */
|
||
|
|
int iResult = connect(m_Socket, (SOCKADDR *) &addr, sizeof(addr));
|
||
|
|
|
||
|
|
m_Mutex.Unlock();
|
||
|
|
|
||
|
|
/* We expect EINPROGRESS/WSAEWOULDBLOCK. */
|
||
|
|
if( iResult == SOCKET_ERROR )
|
||
|
|
{
|
||
|
|
/* Block until the connection attempt completes. */
|
||
|
|
int iError = WSAGetLastError();
|
||
|
|
if( iError == WSAEWOULDBLOCK )
|
||
|
|
iError = WaitForCompletionOrCancellation( FD_CONNECT_BIT );
|
||
|
|
if( iError )
|
||
|
|
{
|
||
|
|
SetError( ssprintf("Couldn't connect: %s", WinSockErrorToString(iError).c_str() ) );
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
m_State = STATE_CONNECTED;
|
||
|
|
}
|
||
|
|
|
||
|
|
void NetworkStream_Win32::Close()
|
||
|
|
{
|
||
|
|
if( m_State == STATE_IDLE )
|
||
|
|
return;
|
||
|
|
|
||
|
|
/* If we have an active, stable connection, make sure we flush any data
|
||
|
|
* completely before closing. If you don't want to do this, call Cancel()
|
||
|
|
* first. */
|
||
|
|
Shutdown();
|
||
|
|
|
||
|
|
m_Mutex.Lock();
|
||
|
|
if( m_State == STATE_CANCELLED )
|
||
|
|
return;
|
||
|
|
|
||
|
|
if( m_Socket != INVALID_SOCKET )
|
||
|
|
closesocket( m_Socket );
|
||
|
|
m_Socket = INVALID_SOCKET;
|
||
|
|
m_State = STATE_IDLE;
|
||
|
|
m_Mutex.Unlock();
|
||
|
|
}
|
||
|
|
|
||
|
|
void NetworkStream_Win32::Shutdown()
|
||
|
|
{
|
||
|
|
/* If we're not in a normal, stable state (eg. an error occurred, or we were
|
||
|
|
* cancelled), don't wait to flush. */
|
||
|
|
if( m_State != STATE_CONNECTED )
|
||
|
|
return;
|
||
|
|
|
||
|
|
shutdown( m_Socket, SD_BOTH );
|
||
|
|
}
|
||
|
|
|
||
|
|
void NetworkStream_Win32::Cancel()
|
||
|
|
{
|
||
|
|
m_Mutex.Lock();
|
||
|
|
|
||
|
|
/* Mark cancellation. */
|
||
|
|
m_State = STATE_CANCELLED;
|
||
|
|
|
||
|
|
/* If resolving, abort the resolve. */
|
||
|
|
if( m_hResolve != NULL )
|
||
|
|
{
|
||
|
|
/* When we cancel the request, no message at all will be sent to the window,
|
||
|
|
* so we need to do it ourself to inform it that it was cancelled. Be sure
|
||
|
|
* to only do this on successful cancel. */
|
||
|
|
if( WSACancelAsyncRequest(m_hResolve) == 0 )
|
||
|
|
PostMessage( m_hResolveHwnd, WM_USER+1, 0, 0 );
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Break out if we're waiting in WaitForCompletionOrCancellation(). */
|
||
|
|
SetEvent( m_hCompletionEvent );
|
||
|
|
|
||
|
|
m_Mutex.Unlock();
|
||
|
|
}
|
||
|
|
|
||
|
|
int NetworkStream_Win32::Read( void *pBuffer, size_t iSize )
|
||
|
|
{
|
||
|
|
if( m_State != STATE_CONNECTED )
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
char *p = (char *) pBuffer;
|
||
|
|
int iRead = 0;
|
||
|
|
while( iSize > 0 )
|
||
|
|
{
|
||
|
|
int iRet = recv( m_Socket, p, iSize, 0 );
|
||
|
|
if( iRet > 0 )
|
||
|
|
{
|
||
|
|
p += iRet;
|
||
|
|
iSize -= iRet;
|
||
|
|
iRead += iRet;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if( iRet == 0 )
|
||
|
|
{
|
||
|
|
/* We hit EOF. */
|
||
|
|
Shutdown();
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
int iError = WSAGetLastError();
|
||
|
|
if( iError == WSAEWOULDBLOCK )
|
||
|
|
{
|
||
|
|
/* There's no date to read. If we've already received some data, return it. */
|
||
|
|
if( iRead > 0 )
|
||
|
|
break;
|
||
|
|
|
||
|
|
iError = WaitForCompletionOrCancellation( FD_READ_BIT );
|
||
|
|
if( iError == 0 )
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* If we're cancelled or hit an error while reading, do return the data we managed
|
||
|
|
* to get. Be sure not to overwrite CANCELLED with ERROR. */
|
||
|
|
SetError( ssprintf("Error reading: %s", WinSockErrorToString(iError).c_str() ) );
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
return iRead;
|
||
|
|
}
|
||
|
|
|
||
|
|
void NetworkStream_Win32::Write( const void *pBuffer, size_t iSize )
|
||
|
|
{
|
||
|
|
if( m_State != STATE_CONNECTED )
|
||
|
|
return;
|
||
|
|
|
||
|
|
const char *p = (const char *) pBuffer;
|
||
|
|
while( iSize > 0 )
|
||
|
|
{
|
||
|
|
int iRet = send( m_Socket, p, iSize, 0 );
|
||
|
|
ASSERT( iRet != 0 );
|
||
|
|
if( iRet > 0 )
|
||
|
|
{
|
||
|
|
p += iRet;
|
||
|
|
iSize -= iRet;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
int iError = WSAGetLastError();
|
||
|
|
if( iError == WSAEWOULDBLOCK )
|
||
|
|
{
|
||
|
|
iError = WaitForCompletionOrCancellation( FD_WRITE_BIT );
|
||
|
|
if( iError == 0 )
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
SetError( ssprintf("Error writing: %s", WinSockErrorToString(iError).c_str() ) );
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Send a set of data over HTTP, as a POST form.
|
||
|
|
*/
|
||
|
|
|
||
|
|
NetworkPostData::NetworkPostData():
|
||
|
|
m_Mutex( "NetworkPostData" )
|
||
|
|
{
|
||
|
|
m_pStream = CreateNetworkStream();
|
||
|
|
}
|
||
|
|
|
||
|
|
NetworkPostData::~NetworkPostData()
|
||
|
|
{
|
||
|
|
delete m_pStream;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Create a MIME multipart data block from the given set of fields. */
|
||
|
|
void NetworkPostData::CreateMimeData( const map<RString,RString> &mapNameToData, RString &sOut, RString &sMimeBoundaryOut )
|
||
|
|
{
|
||
|
|
/* Find a non-conflicting mime boundary. */
|
||
|
|
while(1)
|
||
|
|
{
|
||
|
|
sMimeBoundaryOut = ssprintf( "--%08i", rand() );
|
||
|
|
FOREACHM_CONST( RString, RString, mapNameToData, d )
|
||
|
|
if( d->second.find(sMimeBoundaryOut) != RString::npos )
|
||
|
|
continue;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
FOREACHM_CONST( RString, RString, mapNameToData, d )
|
||
|
|
{
|
||
|
|
sOut += "--" + sMimeBoundaryOut + "\r\n";
|
||
|
|
sOut += ssprintf( "Content-Disposition: form-data; name=\"%s\"\r\n", d->first.c_str() );
|
||
|
|
sOut += "\r\n";
|
||
|
|
sOut += d->second;
|
||
|
|
sOut += "\r\n";
|
||
|
|
}
|
||
|
|
if( sOut.size() )
|
||
|
|
sOut += "--" + sMimeBoundaryOut + "--\r\n";
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
void NetworkPostData::HttpThread()
|
||
|
|
{
|
||
|
|
RString sData, sMimeBoundary;
|
||
|
|
CreateMimeData( m_Data, sData, sMimeBoundary );
|
||
|
|
|
||
|
|
// Stick to HTTP/1.0, since the protocol is simpler.
|
||
|
|
RString sBuf = ssprintf(
|
||
|
|
"%s %s HTTP/1.0\r\n"
|
||
|
|
"Accept: */*\r\n"
|
||
|
|
"User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\r\n"
|
||
|
|
"Host: %s\r\n"
|
||
|
|
"Cache-Control: no-cache\r\n",
|
||
|
|
sData.size()? "POST":"GET",
|
||
|
|
m_sPath.c_str(),
|
||
|
|
m_sHost.c_str() );
|
||
|
|
|
||
|
|
if( sData.size() )
|
||
|
|
{
|
||
|
|
// sBuf += "Content-Type: application/x-www-form-urlencoded\r\n"
|
||
|
|
sBuf += "Content-Type: multipart/form-data; boundary=" + sMimeBoundary + "\r\n";
|
||
|
|
sBuf += ssprintf( "Content-Length: %i\r\n", sData.size() );
|
||
|
|
}
|
||
|
|
sBuf += "\r\n";
|
||
|
|
|
||
|
|
if( sData.size() )
|
||
|
|
sBuf += sData;
|
||
|
|
|
||
|
|
/* The "progress" is currently faked; it shows when we've connected, and when
|
||
|
|
* we've received data. We send and receive too little data to do more. */
|
||
|
|
SetProgress( 0 );
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Begin connecting.
|
||
|
|
*/
|
||
|
|
m_pStream->Open( m_sHost, m_iPort );
|
||
|
|
SetProgress( 0.25f );
|
||
|
|
|
||
|
|
/* Send the form. */
|
||
|
|
m_pStream->Write( sBuf.data(), sBuf.size() );
|
||
|
|
|
||
|
|
/* Read the result. */
|
||
|
|
RString sResult;
|
||
|
|
while( m_pStream->GetState() == NetworkStream::STATE_CONNECTED )
|
||
|
|
{
|
||
|
|
sBuf.clear();
|
||
|
|
void *p = sBuf.GetBuffer( 1024 );
|
||
|
|
int iGot = m_pStream->Read( p, 1024 );
|
||
|
|
if( iGot >= 0 )
|
||
|
|
sBuf.ReleaseBuffer( iGot );
|
||
|
|
if( iGot <= 0 )
|
||
|
|
break;
|
||
|
|
sResult += sBuf;
|
||
|
|
}
|
||
|
|
|
||
|
|
SetProgress( 1.0f );
|
||
|
|
|
||
|
|
/* Parse the results. */
|
||
|
|
int iStart = 0, iSize = -1;
|
||
|
|
map<RString,RString> mapHeaders;
|
||
|
|
while( 1 )
|
||
|
|
{
|
||
|
|
split( sResult, "\n", iStart, iSize, false );
|
||
|
|
if( iStart == (int) sResult.size() )
|
||
|
|
break;
|
||
|
|
|
||
|
|
RString sLine = sResult.substr( iStart, iSize );
|
||
|
|
StripCrnl( sLine );
|
||
|
|
if( sLine.empty() )
|
||
|
|
{
|
||
|
|
m_sResult = sResult.substr( iStart+iSize+1 );
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
m_bFinished = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void NetworkPostData::Start( const RString &sHost, int iPort, const RString &sPath )
|
||
|
|
{
|
||
|
|
m_bFinished = false;
|
||
|
|
m_sHost = sHost;
|
||
|
|
m_iPort = iPort;
|
||
|
|
m_sPath = sPath;
|
||
|
|
m_sStatus.clear();
|
||
|
|
m_fProgress = 0;
|
||
|
|
|
||
|
|
m_Thread.SetName( "HTTP thread" );
|
||
|
|
m_Thread.Create( HttpThread_Start, this );
|
||
|
|
}
|
||
|
|
|
||
|
|
void NetworkPostData::Cancel()
|
||
|
|
{
|
||
|
|
m_pStream->Cancel();
|
||
|
|
m_Thread.Wait();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool NetworkPostData::IsFinished()
|
||
|
|
{
|
||
|
|
if( !m_bFinished )
|
||
|
|
return false;
|
||
|
|
|
||
|
|
m_Thread.Wait();
|
||
|
|
m_bFinished = false;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
RString NetworkPostData::GetStatus() const
|
||
|
|
{
|
||
|
|
LockMut( m_Mutex );
|
||
|
|
return m_sStatus;
|
||
|
|
}
|
||
|
|
|
||
|
|
float NetworkPostData::GetProgress() const
|
||
|
|
{
|
||
|
|
LockMut( m_Mutex );
|
||
|
|
return m_fProgress;
|
||
|
|
}
|
||
|
|
|
||
|
|
void NetworkPostData::SetProgress( float fProgress )
|
||
|
|
{
|
||
|
|
m_Mutex.Lock();
|
||
|
|
m_fProgress = fProgress;
|
||
|
|
m_Mutex.Unlock();
|
||
|
|
}
|
||
|
|
|
||
|
|
void NetworkPostData::SetData( const RString &sKey, const RString &sData )
|
||
|
|
{
|
||
|
|
m_Data[sKey] = sData;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* (c) 2006 Glenn Maynard
|
||
|
|
* All rights reserved.
|
||
|
|
*
|
||
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||
|
|
* copy of this software and associated documentation files (the
|
||
|
|
* "Software"), to deal in the Software without restriction, including
|
||
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||
|
|
* distribute, and/or sell copies of the Software, and to permit persons to
|
||
|
|
* whom the Software is furnished to do so, provided that the above
|
||
|
|
* copyright notice(s) and this permission notice appear in all copies of
|
||
|
|
* the Software and that both the above copyright notice(s) and this
|
||
|
|
* permission notice appear in supporting documentation.
|
||
|
|
*
|
||
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||
|
|
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||
|
|
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||
|
|
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||
|
|
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||
|
|
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||
|
|
* PERFORMANCE OF THIS SOFTWARE.
|
||
|
|
*/
|