From 2a5227afbb24c7aaddc7c28a6d1b8b1c5c46b265 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 15:24:55 -0400 Subject: [PATCH] The last batch of WHITE SPACE loops. ...okay, enough of this running gag. --- src/Foreach.h | 3 - .../Win32/CrashHandlerNetworking.cpp | 1532 ++++++++--------- 2 files changed, 766 insertions(+), 769 deletions(-) diff --git a/src/Foreach.h b/src/Foreach.h index 7fadd94350..9a752c8930 100644 --- a/src/Foreach.h +++ b/src/Foreach.h @@ -11,9 +11,6 @@ for( vector::const_iterator var = (vect).begin(); var != (vect).end(); /** @brief General foreach loop iterating over a map. */ #define FOREACHM( keyType, valType, vect, var ) \ for( map::iterator var = (vect).begin(); var != (vect).end(); ++var ) -/** @brief General foreach loop iterating over a map, using a constant iterator. */ -#define FOREACHM_CONST( keyType, valType, vect, var ) \ -for( map::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) #endif diff --git a/src/archutils/Win32/CrashHandlerNetworking.cpp b/src/archutils/Win32/CrashHandlerNetworking.cpp index a137df636c..c00e846b11 100644 --- a/src/archutils/Win32/CrashHandlerNetworking.cpp +++ b/src/archutils/Win32/CrashHandlerNetworking.cpp @@ -1,766 +1,766 @@ -#include "global.h" -#include "CrashHandlerNetworking.h" -#include "RageThreads.h" -#include "RageLog.h" -#include "RageThreads.h" -#include "RageTimer.h" -#include "RageUtil.h" -#include "Foreach.h" - -#if defined(WINDOWS) -#include -#include -#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; -} - -/** @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 = 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 event didn't actually occur, keep waiting. - if( (events.lNetworkEvents & (1<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 != 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. - 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 map &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 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. - */ +#include "global.h" +#include "CrashHandlerNetworking.h" +#include "RageThreads.h" +#include "RageLog.h" +#include "RageThreads.h" +#include "RageTimer.h" +#include "RageUtil.h" +#include "Foreach.h" + +#if defined(WINDOWS) +#include +#include +#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; +} + +/** @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 = 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 event didn't actually occur, keep waiting. + if( (events.lNetworkEvents & (1<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 != 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. + 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 map &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(); + 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 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. + */