773 lines
21 KiB
C++
773 lines
21 KiB
C++
#include "global.h"
|
|
#include "CrashHandlerNetworking.h"
|
|
#include "RageThreads.h"
|
|
#include "RageLog.h"
|
|
#include "RageThreads.h"
|
|
#include "RageTimer.h"
|
|
#include "RageUtil.h"
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
|
|
#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 nullptr;
|
|
}
|
|
|
|
return new NetworkStream_Win32;
|
|
}
|
|
|
|
/** @brief 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 = nullptr;
|
|
m_hResolveHwnd = nullptr;
|
|
m_hCompletionEvent = CreateEvent( nullptr, true, false, nullptr );
|
|
#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 event didn't actually occur, keep waiting.
|
|
if( (events.lNetworkEvents & (1<<iEvent)) )
|
|
return events.iErrorCode[iEvent];
|
|
|
|
/* If the socket was closed while we were waiting, stop. Note that when the
|
|
* connection closes immediately after sending data, we'll receive both this
|
|
* message and FD_READ at the same time. Only do this if the event we really
|
|
* want hasn't happened yet. */
|
|
if( (events.lNetworkEvents & (1<<FD_CLOSE_BIT)) )
|
|
return WSAECONNRESET;
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
switch( iError )
|
|
{
|
|
case WSAEINTR: return "Interrupted function call.";
|
|
case WSAEACCES: return "Permission denied.";
|
|
case WSAEFAULT: return "Bad address.";
|
|
case WSAEINVAL: return "Invalid argument.";
|
|
case WSAEMFILE: return "Too many open files.";
|
|
case WSAEWOULDBLOCK: return "Resource temporarily unavailable.";
|
|
case WSAEINPROGRESS: return "Operation now in progress.";
|
|
case WSAEALREADY: return "Operation already in progress.";
|
|
case WSAENOTSOCK: return "Socket operation on nonsocket.";
|
|
case WSAEDESTADDRREQ: return "Destination address required.";
|
|
case WSAEMSGSIZE: return "Message too long.";
|
|
case WSAEPROTOTYPE: return "Protocol wrong type for socket.";
|
|
case WSAENOPROTOOPT: return "Bad protocol option.";
|
|
case WSAEPROTONOSUPPORT: return "Protocol not supported.";
|
|
case WSAESOCKTNOSUPPORT: return "Socket type not supported.";
|
|
case WSAEOPNOTSUPP: return "Operation not supported.";
|
|
case WSAEPFNOSUPPORT: return "Protocol family not supported.";
|
|
case WSAEAFNOSUPPORT: return "Address family not supported by protocol family.";
|
|
case WSAEADDRINUSE: return "Address already in use.";
|
|
case WSAEADDRNOTAVAIL: return "Cannot assign requested address.";
|
|
case WSAENETDOWN: return "Network is down.";
|
|
case WSAENETUNREACH: return "Network is unreachable.";
|
|
case WSAENETRESET: return "Network dropped connection on reset.";
|
|
case WSAECONNABORTED: return "Software caused connection abort.";
|
|
case WSAECONNRESET: return "Connection reset by peer.";
|
|
case WSAENOBUFS: return "No buffer space available.";
|
|
case WSAEISCONN: return "Socket is already connected.";
|
|
case WSAENOTCONN: return "Socket is not connected.";
|
|
case WSAESHUTDOWN: return "Cannot send after socket shutdown.";
|
|
case WSAETIMEDOUT: return "Connection timed out.";
|
|
case WSAECONNREFUSED: return "Connection refused.";
|
|
case WSAEHOSTDOWN: return "Host is down.";
|
|
case WSAEHOSTUNREACH: return "No route to host.";
|
|
case WSAEPROCLIM: return "Too many processes.";
|
|
case WSASYSNOTREADY: return "Network subsystem is unavailable.";
|
|
case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range.";
|
|
case WSANOTINITIALISED: return "Successful WSAStartup not yet performed.";
|
|
case WSAEDISCON: return "Graceful shutdown in progress. ";
|
|
case WSATYPE_NOT_FOUND: return "Class type not found.";
|
|
case WSAHOST_NOT_FOUND: return "Host not found.";
|
|
case WSATRY_AGAIN: return "Nonauthoritative host not found.";
|
|
case WSANO_RECOVERY: return "This is a nonrecoverable error.";
|
|
case WSANO_DATA: return "Valid name, no data record of requested type.";
|
|
case WSA_INVALID_HANDLE: return "Specified event object handle is invalid.";
|
|
case WSA_INVALID_PARAMETER: return "One or more parameters are invalid.";
|
|
case WSA_IO_INCOMPLETE: return "Overlapped I/O event object not in signaled state.";
|
|
case WSA_IO_PENDING: return "Overlapped operations will complete later.";
|
|
case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available.";
|
|
case WSA_OPERATION_ABORTED: return "Overlapped operation aborted.";
|
|
case WSASYSCALLFAILURE: return "System call failure.";
|
|
default: return ssprintf( "unknown Winsock error %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 );
|
|
StopRunning();
|
|
return true;
|
|
}
|
|
|
|
if( msg == WM_USER+1 )
|
|
{
|
|
m_iResult = 0;
|
|
StopRunning();
|
|
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 = nullptr;
|
|
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 = nullptr;
|
|
m_hResolveHwnd = nullptr;
|
|
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 );
|
|
m_State = STATE_SHUTDOWN;
|
|
}
|
|
|
|
void NetworkStream_Win32::Cancel()
|
|
{
|
|
m_Mutex.Lock();
|
|
|
|
// Mark cancellation.
|
|
m_State = STATE_CANCELLED;
|
|
|
|
// If resolving, abort the resolve.
|
|
if( m_hResolve != nullptr )
|
|
{
|
|
/* 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.
|
|
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 the other side closed the connection, just return EOF.
|
|
if( iError == WSAECONNRESET )
|
|
break;
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
/** @brief Send a set of data over HTTP, as a POST form. */
|
|
NetworkPostData::NetworkPostData():
|
|
m_Mutex( "NetworkPostData" )
|
|
{
|
|
m_pStream = CreateNetworkStream();
|
|
}
|
|
|
|
NetworkPostData::~NetworkPostData()
|
|
{
|
|
delete m_pStream;
|
|
}
|
|
|
|
/** @brief Create a MIME multipart data block from the given set of fields. */
|
|
void NetworkPostData::CreateMimeData( const std::map<RString, RString> &mapNameToData, RString &sOut, RString &sMimeBoundaryOut )
|
|
{
|
|
// Find a non-conflicting mime boundary.
|
|
while(1)
|
|
{
|
|
sMimeBoundaryOut = ssprintf( "--%08i", rand() );
|
|
for (auto const &d : mapNameToData)
|
|
if( d.second.find(sMimeBoundaryOut) != RString::npos )
|
|
continue;
|
|
break;
|
|
}
|
|
|
|
for (auto const &d : mapNameToData)
|
|
{
|
|
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();
|
|
char * buffer = new char[1024];
|
|
std::fill(buffer, buffer + 1024, '\0');
|
|
int iGot = m_pStream->Read( static_cast<void *>(buffer), 1024 );
|
|
RString tmp = buffer;
|
|
delete[] buffer;
|
|
if (iGot < 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
sResult += tmp;
|
|
}
|
|
|
|
SetProgress( 1.0f );
|
|
|
|
// Parse the results.
|
|
int iStart = 0, iSize = -1;
|
|
std::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;
|
|
}
|
|
|
|
RString NetworkPostData::GetError() const
|
|
{
|
|
return m_pStream->GetError();
|
|
}
|
|
|
|
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.
|
|
*/
|