diff --git a/GNUmakefile b/GNUmakefile index 00778a8f..33847059 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -145,7 +145,6 @@ endif INCLUDE = -I.\ -Isource\ -Isource/md5\ - -IWebServer\ -Isource/items\ -Isource/blocks\ -Itolua++-1.0.93/src/lib\ @@ -167,7 +166,7 @@ INCLUDE = -I.\ # 2012_11_08 _X: Removed: squirrel_3_0_1_stable -SOURCES := $(shell find CryptoPP lua-5.1.4 jsoncpp-src-0.5.0 zlib-1.2.7 source tolua++-1.0.93 iniFile WebServer expat '(' -name '*.cpp' -o -name '*.c' ')') +SOURCES := $(shell find CryptoPP lua-5.1.4 jsoncpp-src-0.5.0 zlib-1.2.7 source tolua++-1.0.93 iniFile expat '(' -name '*.cpp' -o -name '*.c' ')') SOURCES := $(filter-out %minigzip.c %lua.c %tolua.c %toluabind.c %LeakFinder.cpp %StackWalker.cpp %example.c,$(SOURCES)) OBJECTS := $(patsubst %.c,$(BUILDDIR)%.o,$(SOURCES)) OBJECTS := $(patsubst %.cpp,$(BUILDDIR)%.o,$(OBJECTS)) diff --git a/VC2008/MCServer.sln b/VC2008/MCServer.sln index 6466fddd..aeabaf25 100644 --- a/VC2008/MCServer.sln +++ b/VC2008/MCServer.sln @@ -2,7 +2,6 @@ Microsoft Visual Studio Solution File, Format Version 10.00 # Visual C++ Express 2008 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MCServer", "MCServer.vcproj", "{32012054-0C96-4C43-AB27-174FF8E72D66}" ProjectSection(ProjectDependencies) = postProject - {9A476537-42C0-4848-AB40-15CFE83D17A8} = {9A476537-42C0-4848-AB40-15CFE83D17A8} {082E8185-7B3A-4945-8C82-9132341A329D} = {082E8185-7B3A-4945-8C82-9132341A329D} {5FCFAF8D-FF2C-456D-A72C-1D76F913AD96} = {5FCFAF8D-FF2C-456D-A72C-1D76F913AD96} {3423EC9A-52E4-4A4D-9753-EDEBC38785EF} = {3423EC9A-52E4-4A4D-9753-EDEBC38785EF} @@ -19,8 +18,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Lua", "Lua.vcproj", "{082E8 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ToLua", "ToLua.vcproj", "{EEAB54AD-114C-4AB8-8482-0A52D502BD35}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WebServer", "WebServer.vcproj", "{9A476537-42C0-4848-AB40-15CFE83D17A8}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CryptoPP", "CryptoPP.vcproj", "{3423EC9A-52E4-4A4D-9753-EDEBC38785EF}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "expat", "expat.vcproj", "{5FCFAF8D-FF2C-456D-A72C-1D76F913AD96}" @@ -73,14 +70,6 @@ Global {EEAB54AD-114C-4AB8-8482-0A52D502BD35}.Release profiled|Win32.Build.0 = Release profiled|Win32 {EEAB54AD-114C-4AB8-8482-0A52D502BD35}.Release|Win32.ActiveCfg = Release|Win32 {EEAB54AD-114C-4AB8-8482-0A52D502BD35}.Release|Win32.Build.0 = Release|Win32 - {9A476537-42C0-4848-AB40-15CFE83D17A8}.Debug profiled|Win32.ActiveCfg = Debug profiled|Win32 - {9A476537-42C0-4848-AB40-15CFE83D17A8}.Debug profiled|Win32.Build.0 = Debug profiled|Win32 - {9A476537-42C0-4848-AB40-15CFE83D17A8}.Debug|Win32.ActiveCfg = Debug|Win32 - {9A476537-42C0-4848-AB40-15CFE83D17A8}.Debug|Win32.Build.0 = Debug|Win32 - {9A476537-42C0-4848-AB40-15CFE83D17A8}.Release profiled|Win32.ActiveCfg = Release profiled|Win32 - {9A476537-42C0-4848-AB40-15CFE83D17A8}.Release profiled|Win32.Build.0 = Release profiled|Win32 - {9A476537-42C0-4848-AB40-15CFE83D17A8}.Release|Win32.ActiveCfg = Release|Win32 - {9A476537-42C0-4848-AB40-15CFE83D17A8}.Release|Win32.Build.0 = Release|Win32 {3423EC9A-52E4-4A4D-9753-EDEBC38785EF}.Debug profiled|Win32.ActiveCfg = Debug|Win32 {3423EC9A-52E4-4A4D-9753-EDEBC38785EF}.Debug profiled|Win32.Build.0 = Debug|Win32 {3423EC9A-52E4-4A4D-9753-EDEBC38785EF}.Debug|Win32.ActiveCfg = Debug|Win32 diff --git a/VC2008/MCServer.vcproj b/VC2008/MCServer.vcproj index c1c2593b..6cc83b4d 100644 --- a/VC2008/MCServer.vcproj +++ b/VC2008/MCServer.vcproj @@ -2716,6 +2716,66 @@ > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - diff --git a/VC2008/WebServer.vcproj b/VC2008/WebServer.vcproj deleted file mode 100644 index 601d2f55..00000000 --- a/VC2008/WebServer.vcproj +++ /dev/null @@ -1,374 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WebServer/Events.cpp b/WebServer/Events.cpp deleted file mode 100644 index 30eb731f..00000000 --- a/WebServer/Events.cpp +++ /dev/null @@ -1,125 +0,0 @@ - -#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules - -#include "Events.h" - - - - - -cEvents::cEvents( unsigned int a_NumEvents /* = 1 */ ) - : m_NumEvents( a_NumEvents ) -#ifndef _WIN32 - , m_bNamed( false ) -#endif -{ - if( m_NumEvents < 1 ) m_NumEvents = 1; - -#ifdef _WIN32 - m_Handle = new HANDLE[ m_NumEvents ]; - for( unsigned int i = 0; i < m_NumEvents; i++) - { - ((HANDLE*)m_Handle)[i] = CreateEvent( 0, FALSE, FALSE, 0 ); - } -#else - m_Handle = new sem_t*[ m_NumEvents ]; - for( unsigned int i = 0; i < m_NumEvents; i++) - { - - sem_t* & HandlePtr = ((sem_t**)m_Handle)[i]; - HandlePtr = new sem_t; - - if( sem_init( HandlePtr, 0, 0 ) ) - { - LOG("WARNING cEvents: Could not create unnamed semaphore, fallback to named."); - m_bNamed = true; - delete HandlePtr; // named semaphores return their own address - - char c_Str[32]; - sprintf( c_Str, "cEvents%p", &HandlePtr ); - HandlePtr = sem_open( c_Str, O_CREAT, 777, 0 ); - if( HandlePtr == SEM_FAILED ) - LOG("ERROR: Could not create Event. (%i)", errno); - else - if( sem_unlink( c_Str ) != 0 ) - LOG("ERROR: Could not unlink cEvents. (%i)", errno); - } - } -#endif -} - - - - - -cEvents::~cEvents() -{ -#ifdef _WIN32 - for( unsigned int i = 0; i < m_NumEvents; i++ ) - { - CloseHandle( ((HANDLE*)m_Handle)[i] ); - } - delete [] (HANDLE*)m_Handle; -#else - for( unsigned int i = 0; i < m_NumEvents; i++ ) - { - if( m_bNamed ) - { - sem_t* & HandlePtr = ((sem_t**)m_Handle)[i]; - char c_Str[32]; - sprintf( c_Str, "cEvents%p", &HandlePtr ); - // LOG("Closing event: %s", c_Str ); - // LOG("Sem ptr: %p", HandlePtr ); - if( sem_close( HandlePtr ) != 0 ) - { - LOG("ERROR: Could not close cEvents. (%i)", errno); - } - } - else - { - sem_destroy( ((sem_t**)m_Handle)[i] ); - delete ((sem_t**)m_Handle)[i]; - } - } - delete [] (sem_t**)m_Handle; m_Handle = 0; -#endif -} - - - - - -void cEvents::Wait() -{ -#ifdef _WIN32 - WaitForMultipleObjects( m_NumEvents, (HANDLE*)m_Handle, true, INFINITE ); -#else - for(unsigned int i = 0; i < m_NumEvents; i++) - { - if( sem_wait( ((sem_t**)m_Handle)[i] ) != 0 ) - { - LOG("ERROR: Could not wait for cEvents. (%i)", errno); - } - } -#endif -} - - - - - -void cEvents::Set(unsigned int a_EventNum /* = 0 */) -{ -#ifdef _WIN32 - SetEvent( ((HANDLE*)m_Handle)[a_EventNum] ); -#else - if( sem_post( ((sem_t**)m_Handle)[a_EventNum] ) != 0 ) - { - LOG("ERROR: Could not set cEvents. (%i)", errno); - } -#endif -} - - - - diff --git a/WebServer/Events.h b/WebServer/Events.h deleted file mode 100644 index 352dc405..00000000 --- a/WebServer/Events.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -class cEvents -{ -public: - cEvents( unsigned int a_NumEvents = 1 ); - ~cEvents(); - - void Wait(); - void Set(unsigned int a_EventNum = 0); -private: - unsigned int m_NumEvents; - void* m_Handle; // HANDLE[] pointer - -#ifndef _WIN32 - bool m_bNamed; -#endif -}; diff --git a/WebServer/Globals.cpp b/WebServer/Globals.cpp deleted file mode 100644 index 13c6ae70..00000000 --- a/WebServer/Globals.cpp +++ /dev/null @@ -1,10 +0,0 @@ - -// Globals.cpp - -// This file is used for precompiled header generation in MSVC environments - -#include "Globals.h" - - - - diff --git a/WebServer/Globals.h b/WebServer/Globals.h deleted file mode 100644 index d60f3472..00000000 --- a/WebServer/Globals.h +++ /dev/null @@ -1,15 +0,0 @@ - -// Globals.h - -// This file gets included from every module in the project, so that global symbols may be introduced easily -// Also used for precompiled header generation in MSVC environments - - - - - -#include "../source/Globals.h" - - - - diff --git a/WebServer/Socket.cpp b/WebServer/Socket.cpp deleted file mode 100644 index 82c76a8f..00000000 --- a/WebServer/Socket.cpp +++ /dev/null @@ -1,376 +0,0 @@ -/* - Socket.cpp - - Copyright (C) 2002-2004 René Nyffenegger - - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. - - 3. This notice may not be removed or altered from any source distribution. - - René Nyffenegger rene.nyffenegger@adp-gmbh.ch -*/ - -/* - Note on point 2: - THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1 -*/ - -#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules - -#include "Socket.h" -#include - -#ifndef _WIN32 - #include - #include - #include - #define SD_SEND 0x01 -#else - #define MSG_NOSIGNAL (0) -#endif - -#ifdef __MAC_NA - #define MSG_NOSIGNAL (0) -#endif - -#ifndef MSG_NOSIGNAL - #define MSG_NOSIGNAL 0 -#endif // MSG_NOSIGNAL - -using namespace std; - -int Socket::nofSockets_= 0; - - - - - -void Socket::Start() -{ - #ifdef _WIN32 - if (!nofSockets_) - { - WSADATA info; - if (WSAStartup(MAKEWORD(2,0), &info)) - { - throw "Could not start WSA"; - } - } - #endif - ++nofSockets_; -} - - - - - -void Socket::End() -{ -#ifdef _WIN32 - WSACleanup(); -#endif -} - - - - - -Socket::Socket() : - s_(INVALID_SOCKET) -{ - Start(); - // UDP: use SOCK_DGRAM instead of SOCK_STREAM - s_ = socket(AF_INET,SOCK_STREAM,0); - - if (!IsValid()) - { -#if !defined(ANDROID_NDK) - throw "INVALID_SOCKET"; -#endif - } - - refCounter_ = new int(1); -} - - - - - -Socket::Socket(SOCKET s) : s_(s) -{ - Start(); - refCounter_ = new int(1); -}; - - - - - -Socket::~Socket() -{ - if (! --(*refCounter_)) - { - Close(); - delete refCounter_; - } - - --nofSockets_; - if (!nofSockets_) - { - End(); - } -} - - - - - -Socket::Socket(const Socket& o) -{ - refCounter_=o.refCounter_; - (*refCounter_)++; - s_ =o.s_; - - nofSockets_++; -} - - - - - -Socket& Socket::operator=(Socket& o) -{ - (*o.refCounter_)++; - - refCounter_ = o.refCounter_; - s_ = o.s_; - - nofSockets_++; - - return *this; -} - - - - - -bool Socket::IsValid(void) const -{ - #ifdef _WIN32 - return (s_ != INVALID_SOCKET); - #else - return (s_ >= 0); - #endif -} - - - - - -void Socket::Close( bool a_WaitSend /* = false */ ) -{ - if (IsValid()) - { - if (a_WaitSend) - { - assert( shutdown(s_, SD_SEND ) == 0 ); - char c; - while( recv(s_, &c, 1, 0 ) > 0 ) - {} - } - - #ifndef _WIN32 - // Linux needs to shut the socket down first, otherwise the socket keeps blocking accept() and select() calls! - // Ref.: http://forum.mc-server.org/showthread.php?tid=344 - if (shutdown(s_, SHUT_RDWR) != 0) - { - LOGWARN("Error on shutting down socket %d", s_); - } - #endif // _WIN32 - - closesocket(s_); - - s_ = INVALID_SOCKET; - } -} - - - - - -std::string Socket::ReceiveLine() -{ - std::string ret; - while (1) - { - char r; - - if (recv(s_, &r, 1, 0) <= 0 ) - { - return ""; - } - ret += r; - if (r == '\n') return ret; - } -} - - - - - -std::string Socket::ReceiveBytes( unsigned int a_Length ) -{ - std::string ret; - while( ret.size() < a_Length ) - { - char r; - - if (recv(s_, &r, 1, 0) <= 0 ) - { - return ""; - } - ret += r; - } - return ret; -} - - - - - -void Socket::SendLine(std::string s) -{ - s += '\n'; - if( send(s_,s.c_str(),s.length(),MSG_NOSIGNAL) <= 0 ) - { - //printf("SendLine Socket error!! \n" ); - Close(); - } -} - - - - - -void Socket::SendBytes(const std::string& s) -{ - if( send(s_,s.c_str(),s.length(), MSG_NOSIGNAL) <= 0 ) - { - //printf("SendBytes Socket error!! \n" ); - Close(); - } -} - - - - - -SocketServer::SocketServer(int port, int connections, TypeSocket type) -{ - sockaddr_in sa; - - memset(&sa, 0, sizeof(sa)); - - sa.sin_family = PF_INET; - sa.sin_port = htons(port); - s_ = socket(AF_INET, SOCK_STREAM, 0); - - if (!IsValid()) - { - LOG("WebServer: INVALID_SOCKET"); - } - - if(type==NonBlockingSocket) - { - return; - } - - #ifdef _WIN32 - char yes = 1; - #else - int yes = 1; - #endif - - if (setsockopt(s_,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) - { - LOG("WebServer: setsockopt == -1"); - return; - } - - /* bind the socket to the internet address */ - if (bind(s_, (sockaddr *)&sa, sizeof(sockaddr_in)) == SOCKET_ERROR) - { - closesocket(s_); - LOG("WebServer: INVALID_SOCKET"); - } - - listen(s_, connections); -} - - - - - -Socket* SocketServer::Accept() -{ - Socket * r = new Socket(accept(s_, 0, 0)); - if (!r->IsValid()) - { - delete r; - r = NULL; - } - - return r; -} - - - - - -SocketSelect::SocketSelect(Socket const * const s1, Socket const * const s2, TypeSocket type) -{ - FD_ZERO(&fds_); - SOCKET Highest = s1->s_; - FD_SET(const_cast(s1)->s_,&fds_); - if(s2) - { - FD_SET(const_cast(s2)->s_,&fds_); - if (s2->s_ > Highest) - { - Highest = s2->s_; - } - } - if (select(Highest + 1, &fds_, NULL, NULL, NULL) == SOCKET_ERROR) - { -#if !defined(ANDROID_NDK) - throw "Error in select"; -#endif - } -} - - - - - -bool SocketSelect::Readable(Socket const* const s) -{ - return (FD_ISSET(s->s_,&fds_) != 0); -} - - - - diff --git a/WebServer/Socket.h b/WebServer/Socket.h deleted file mode 100644 index b144659c..00000000 --- a/WebServer/Socket.h +++ /dev/null @@ -1,127 +0,0 @@ -/* - Socket.h - - Copyright (C) 2002-2004 René Nyffenegger - - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. - - 3. This notice may not be removed or altered from any source distribution. - - René Nyffenegger rene.nyffenegger@adp-gmbh.ch -*/ - -/* - Note on point 2: - THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1 -*/ - -#ifndef SOCKET_H -#define SOCKET_H - - - - - -#ifndef _WIN32 - typedef int SOCKET; - #define SOCKET_ERROR (-1) - #define INVALID_SOCKET (-1) - #define closesocket close -#endif // !_WIN32 - - - - - -enum TypeSocket {BlockingSocket, NonBlockingSocket}; - - - - - -class Socket -{ -public: - - virtual ~Socket(); - Socket(const Socket&); - Socket& operator=(Socket&); - - std::string ReceiveLine(); - std::string ReceiveBytes( unsigned int a_Length ); - - bool IsValid(void) const; - - void Close( bool a_WaitSend = false ); - - // The parameter of SendLine is not a const reference - // because SendLine modifes the std::string passed. - void SendLine (std::string); - - // The parameter of SendBytes is a const reference - // because SendBytes does not modify the std::string passed - // (in contrast to SendLine). - void SendBytes(const std::string&); - -protected: - friend class SocketServer; - friend class SocketSelect; - - Socket(SOCKET s); - Socket(); - - - SOCKET s_; - - int* refCounter_; - -private: - static void Start(); - static void End(); - static int nofSockets_; -}; - - - - - -class SocketServer : - public Socket -{ -public: - SocketServer(int port, int connections, TypeSocket type=BlockingSocket); - - Socket* Accept(); -}; - - - - - -// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winsock/wsapiref_2tiq.asp -class SocketSelect -{ -public: - SocketSelect(Socket const * const s1, Socket const * const s2=NULL, TypeSocket type=BlockingSocket); - - bool Readable(Socket const * const s); - -private: - fd_set fds_; -}; - -#endif diff --git a/WebServer/StdHelpers.cpp b/WebServer/StdHelpers.cpp deleted file mode 100644 index 63e48a7d..00000000 --- a/WebServer/StdHelpers.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - stdHelpers.cpp - - Copyright (C) 2002-2004 René Nyffenegger - - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. - - 3. This notice may not be removed or altered from any source distribution. - - René Nyffenegger rene.nyffenegger@adp-gmbh.ch -*/ - -/* - Note on point 2: - THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1 -*/ - -#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules - -#include "StdHelpers.h" -#include - -std::string ReplaceInStr(const std::string& in, const std::string& search_for, const std::string& replace_with) { - std::string ret = in; - - std::string::size_type pos = ret.find(search_for); - - while (pos != std::string::npos) { - ret = ret.replace(pos, search_for.size(), replace_with); - pos = pos - search_for.size() + replace_with.size() + 1; - pos = ret.find(search_for, pos); - } - - return ret; -} - -// std:toupper and std::tolower are overloaded. Well... -// http://gcc.gnu.org/ml/libstdc++/2002-11/msg00180.html -char toLower_ (char c) { return std::tolower(c); } -char toUpper_ (char c) { return std::toupper(c); } - -void ToUpper(std::string& s) { - std::transform(s.begin(), s.end(), s.begin(),toUpper_); -} - -void ToLower(std::string& s) { - std::transform(s.begin(), s.end(), s.begin(),toLower_); -} diff --git a/WebServer/StdHelpers.h b/WebServer/StdHelpers.h deleted file mode 100644 index 1011881f..00000000 --- a/WebServer/StdHelpers.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - stdHelpers.h - - Copyright (C) 2002-2005 René Nyffenegger - - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. - - 3. This notice may not be removed or altered from any source distribution. - - René Nyffenegger rene.nyffenegger@adp-gmbh.ch -*/ - -/* - Note on point 2: - THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1 -*/ - -#ifndef STDHELPERS_H__ -#define STDHELPERS_H__ - -#include -#include - -std::string ReplaceInStr(const std::string& in, const std::string& search_for, const std::string& replace_with); - -void ToUpper(std::string& s); -void ToLower(std::string& s); - -template -T To(std::string const& s) { - T ret; - - std::stringstream stream; - stream << s; - stream >> ret; - - return ret; -} - -template -std::string StringFrom(T const& t) { - std::string ret; - - std::stringstream stream; - stream << t; - stream >> ret; - - return ret; -} - -#endif \ No newline at end of file diff --git a/WebServer/Tracer.h b/WebServer/Tracer.h deleted file mode 100644 index c13d5128..00000000 --- a/WebServer/Tracer.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - Tracer.h - - Copyright (C) 2002-2004 René Nyffenegger - - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. - - 3. This notice may not be removed or altered from any source distribution. - - The most current version of Tracer.h can be found at - http://www.adp-gmbh.ch/cpp/common/Tracer.html - - René Nyffenegger rene.nyffenegger@adp-gmbh.ch -*/ - -/* - Note on point 2: - THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1 -*/ - -#ifndef TRACER_H__ -#define TRACER_H__ - -#ifdef DO_TRACE - -#include -#include - -#include - -#define StartTrace(x) TraceFunc_::StartTrace_(x) -#define Trace(x) dummy_____for_trace_func______.Trace_(x) -#define Trace2(x,y) dummy_____for_trace_func______.Trace_(x,y) -#define TraceFunc(x) TraceFunc_ dummy_____for_trace_func______(x) -#define TraceFunc2(x,y) TraceFunc_ dummy_____for_trace_func______(x,y) - -class TraceFunc_ { - std::string func_name_; - public: - /* - Calling TraceFunc_ indents the output until the enclosing scope ( {...} ) - is left. - */ - TraceFunc_(std::string const&); - TraceFunc_(std::string const&, std::string const& something); - ~TraceFunc_(); - - /* - Creates the trace output file named by filename. - Must be called before any other tracing function. - Use the StartTrace macro for it. - */ - static void StartTrace_(std::string const& filename); - - template - void Trace_(T const& t) { - DWORD d; - std::string indent_s; - std::stringstream s; - - s << t; - - for (int i=0; i< indent; i++) indent_s += " "; - - ::WriteFile(trace_file_,indent_s.c_str(), indent_s.size(), &d, 0); - ::WriteFile(trace_file_, s.str().c_str(), s.str().size() ,&d, 0); - ::WriteFile(trace_file_,"\x0a",1,&d,0); - } - - template - void Trace_(T const& t, U const& u) { - std::string indent_s; - std::stringstream s; - - s << t; - s << u; - Trace_(s.str()); - } - - static int indent; - /* trace_file_ is a HANDLE for the file in which the traced output goes. - The file is opened (that is, created) by calling StartTrace_. - Better yet, use the StartTrace macro - to create the file. - */ - static HANDLE trace_file_; -}; - -#else - -#define StartTrace(x) -#define Trace(x) -#define Trace2(x,y) -#define TraceFunc(x) -#define TraceFunc2(x,y) - -#endif - -#endif // TRACER_H__ diff --git a/WebServer/UrlHelper.cpp b/WebServer/UrlHelper.cpp deleted file mode 100644 index 3ed5660b..00000000 --- a/WebServer/UrlHelper.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/* - UrlHelper.cpp - - Copyright (C) 2002-2004 René Nyffenegger - - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. - - 3. This notice may not be removed or altered from any source distribution. - - René Nyffenegger rene.nyffenegger@adp-gmbh.ch -*/ - -/* - Note on point 2: - THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1 -*/ - -#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules - -#include "UrlHelper.h" -#include "Tracer.h" -#include "StdHelpers.h" - -#include -#include - -bool RemoveProtocolFromUrl(std::string const& url, std::string& protocol, std::string& rest) { - TraceFunc("RemoveProtocolFromUrl"); - Trace(std::string("url='")+url+"'"); - std::string::size_type pos_colon = url.find(":"); - - if (pos_colon == std::string::npos) { - rest = url; - return false; - } - - if (url.size() < pos_colon + 2) { - rest = url; - return false; - } - - if (url[pos_colon+1] != '/' || - url[pos_colon+2] != '/') { - rest = url; - return false; - } - - protocol = url.substr(0,pos_colon); - rest = url.substr(3+pos_colon); // Skipping three characters ( '://' ) - - return true; -} - -void SplitGetReq(std::string get_req, std::string& path, std::map& params) { - TraceFunc("SplitGetReq"); - - // Remove trailing newlines - if (get_req[get_req.size()-1] == '\x0d' || - get_req[get_req.size()-1] == '\x0a') - get_req=get_req.substr(0, get_req.size()-1); - - if (get_req[get_req.size()-1] == '\x0d' || - get_req[get_req.size()-1] == '\x0a') - get_req=get_req.substr(0, get_req.size()-1); - - // Remove potential Trailing HTTP/1.x - if (get_req.size() > 7) { - if (get_req.substr(get_req.size()-8, 7) == "HTTP/1.") { - get_req=get_req.substr(0, get_req.size()-9); - } - } - - std::string::size_type qm = get_req.find("?"); - if (qm != std::string::npos) { - std::string url_params = get_req.substr(qm+1); - - path = get_req.substr(0, qm); - - // Appending a '&' so that there are as many '&' as name-value pairs. - // It makes it easier to split the url for name value pairs, he he he - url_params += "&"; - - std::string::size_type next_amp = url_params.find("&"); - - while (next_amp != std::string::npos) { - std::string name_value = url_params.substr(0,next_amp); - url_params = url_params.substr(next_amp+1); - next_amp = url_params.find("&"); - - std::string::size_type pos_equal = name_value.find("="); - - std::string nam = name_value.substr(0,pos_equal); - std::string val = name_value.substr(pos_equal+1); - - std::string::size_type pos_plus; - while ( (pos_plus = val.find("+")) != std::string::npos ) { - val.replace(pos_plus, 1, " "); - } - - while ( (pos_plus = val.find("%20")) != std::string::npos ) { - val.replace(pos_plus, 3, " "); - } - - // Replacing %xy notation - std::string::size_type pos_hex = 0; - while ( (pos_hex = val.find("%", pos_hex)) != std::string::npos ) { - std::stringstream h; - h << val.substr(pos_hex+1, 2); - h << std::hex; - - int i; - h>>i; - - std::stringstream f; - f << static_cast(i); - std::string s; - f >> s; - - val.replace(pos_hex, 3, s); - pos_hex ++; - } - - params.insert(std::map::value_type(nam, val)); - } - } - else { - path = get_req; - } -} - -void SplitUrl(std::string const& url, std::string& protocol, std::string& server, std::string& path) { - TraceFunc("SplitUrl"); - RemoveProtocolFromUrl(url, protocol, server); - - if (protocol == "http") { - std::string::size_type pos_slash = server.find("/"); - - if (pos_slash != std::string::npos) { - Trace("slash found"); - path = server.substr(pos_slash); - server = server.substr(0, pos_slash); - } - else { - Trace("slash not found"); - path = "/"; - } - } - else if (protocol == "file") { - path = ReplaceInStr(server, "\\", "/"); - server = ""; - } - else { - std::cerr << "unknown protocol in SplitUrl: '" << protocol << "'" << std::endl; - } -} diff --git a/WebServer/UrlHelper.h b/WebServer/UrlHelper.h deleted file mode 100644 index 12f12c6f..00000000 --- a/WebServer/UrlHelper.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - UrlHelper.h - - Copyright (C) 2002-2004 René Nyffenegger - - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. - - 3. This notice may not be removed or altered from any source distribution. - - René Nyffenegger rene.nyffenegger@adp-gmbh.ch -*/ - -/* - Note on point 2: - THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1 -*/ -#ifndef __URL_HELPER_H__ -#define __URL_HELPER_H__ - -#include -#include - -void SplitUrl (std::string const& url, std::string& protocol, std::string& server, std::string& path); -bool RemoveProtocolFromUrl(std::string const& url, std::string& protocol, std::string& rest); - -void SplitGetReq (std::string et_req, std::string& path, std::map& params); - -#endif diff --git a/WebServer/WebServer.cpp b/WebServer/WebServer.cpp deleted file mode 100644 index eb9a0904..00000000 --- a/WebServer/WebServer.cpp +++ /dev/null @@ -1,498 +0,0 @@ -/* - WebServer.cpp - - Copyright (C) 2003-2007 René Nyffenegger - - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. - - 3. This notice may not be removed or altered from any source distribution. - - René Nyffenegger rene.nyffenegger@adp-gmbh.ch - - Thanks to Tom Lynn who pointed out an error in this source code. -*/ - -/* - Note on point 2: - THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1 -*/ - -#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules - -#include -#ifdef _WIN32 - #include -#endif -#include -#include - - -#include "WebServer.h" -#include "Events.h" -#include "Socket.h" -#include "UrlHelper.h" -#include "base64.h" - - - - - -webserver::request_func webserver::request_func_ = NULL; - - - - - -static std::string EatLine( std::string& a_String ) -{ - std::string RetVal = ""; - unsigned int StringSize = a_String.size(); - const char* c = a_String.c_str(); - - for( unsigned int i = 0; i < StringSize; ++i, ++c) - { - if( *c == '\n' ) - { - RetVal += *c; -// ++i; ++c; -// if( i < StringSize ) -// { -// if( *c == '\r' ) -// { -// RetVal += *c; -// } -// } - break; - } - RetVal += *c; - } - a_String = a_String.substr( RetVal.size() ); - return RetVal; -} - - - - - -// Turns -// "blabla my string with \"quotes\"!" -// into -// blabla my string with "quotes"! -static std::string GetQuotedString( const std::string& a_String ) -{ - std::string RetVal; - - bool bGotFirstQuote = false; - bool bIgnoreNext = false; - unsigned int StrLen = a_String.size(); - for( unsigned int i = 0; i < StrLen; ++i ) - { - if( bIgnoreNext == false ) - { - if( a_String[i] == '\"' ) - { - if( bGotFirstQuote == false ) - { - bGotFirstQuote = true; - } - else - { - break; - } - continue; - } - else if( a_String[i] == '\\' ) // Escape character - { - bIgnoreNext = true; - continue; - } - } - RetVal.push_back( a_String[i] ); - bIgnoreNext = false; - } - - return RetVal; -} - - - - - -void ParseMultipartFormData( webserver::http_request& req, Socket* s) -{ - static const std::string multipart_form_data = "multipart/form-data"; - if(req.content_type_.substr(0, multipart_form_data.size()) == multipart_form_data) // Difficult data... :( - { - AStringVector ContentTypeData = StringSplit( req.content_type_, "; " ); - - std::string boundary; - // Find boundary - for( unsigned int i = 0; i < ContentTypeData.size(); ++i ) - { - static const std::string boundary_ = "boundary="; - if( ContentTypeData[i].substr(0, boundary_.size()) == boundary_ ) // Found boundary - { - boundary = ContentTypeData[i].substr( boundary_.size() ); - } - } - - //LOGINFO("Boundary: %s", boundary.c_str() ); - std::string boundary_start = "--" + boundary; - std::string boundary_end = boundary_start + "--"; - - std::string Content = s->ReceiveBytes( req.content_length_ ); - - //printf("Total content: \n%s\n", Content.c_str() ); - - // Should start with boundary! - std::string line = EatLine( Content ); - if( line.substr(0, boundary_start.size() ) != boundary_start ) - { - // Something was wrong! :( - Content.clear(); - } - - while( !Content.empty() ) - { - webserver::formdata FormData; - - static const std::string content_disposition = "Content-Disposition: "; - static const std::string content_type = "Content-Type: "; - - std::string f_disposition; - - while( 1 ) - { - std::string line = EatLine( Content ); - if( line.empty() ) - break; - - unsigned int pos_cr_lf = line.find_first_of("\x0a\x0d"); - if (pos_cr_lf == 0) break; // Empty line, indicates end of mime thingy - - if( line.substr(0, content_disposition.size() ) == content_disposition ) - { - f_disposition = line.substr(content_disposition.size()); - LOGINFO("Disposition: %s", f_disposition.c_str() ); - } - else if( line.substr(0, content_type.size() ) == content_type ) - { - FormData.content_type_ = line.substr(content_type.size()); - } - - //LOGINFO("Got line: '%s'", line.c_str() ); - } - - // Check if we got the proper headers - if( !f_disposition.empty() ) - { - static const std::string disp_name = "name="; - static const std::string disp_filename = "filename="; - - // Parse the disposition - AStringVector DispositionData = StringSplit( f_disposition, "; " ); - for( unsigned int i = 0; i < DispositionData.size(); ++i ) - { - if( DispositionData[i].substr(0, disp_name.size()) == disp_name ) - { - FormData.name_ = GetQuotedString( DispositionData[i].substr(disp_name.size()) ); - } - else if( DispositionData[i].substr(0, disp_filename.size()) == disp_filename ) - { - FormData.filename_ = GetQuotedString( DispositionData[i].substr(disp_filename.size()) ); - } - } - - std::string ContentValue; - // Parse until boundary_end is found - while( 1 ) - { - std::string line = EatLine( Content ); - if( line.empty() ) - break; - - if( line.substr(0, boundary_end.size() ) == boundary_end ) - { - break; - } - else if( line.substr(0, boundary_start.size() ) == boundary_start ) - { - break; - } - ContentValue.append( line.c_str(), line.size() ); - } - - - FormData.value_ = ContentValue; - } - - req.multipart_formdata_.push_back( FormData ); - } - } -} - - - - - -#ifdef _WIN32 -unsigned webserver::Request(void* ptr_s) -#else -void* webserver::Request(void* ptr_s) -#endif -{ - Socket* s = (reinterpret_cast(ptr_s)); - - std::string line = s->ReceiveLine(); - if (line.empty()) - { - s->Close(); - delete s; - return 0; - } - - http_request req; - std::string path; - std::map params; - size_t posStartPath = 0; - - if (line.find("GET") == 0) - { - req.method_="GET"; - posStartPath = line.find_first_not_of(" ",3); - } - else if (line.find("POST") == 0) - { - req.method_="POST"; - posStartPath = line.find_first_not_of(" ",4); - } - - SplitGetReq(line.substr(posStartPath), path, params); - - req.status_ = "202 OK"; - req.s_ = s; - req.path_ = path; - req.params_ = params; - - static const char authorization[] = "Authorization: Basic "; - static const char accept[] = "Accept: "; - static const char accept_language[] = "Accept-Language: "; - static const char accept_encoding[] = "Accept-Encoding: "; - static const char user_agent[] = "User-Agent: "; - static const char content_length[] = "Content-Length: "; - static const char content_type[] = "Content-Type: "; - - while(1) - { - line=s->ReceiveLine(); - if (line.empty()) - { - break; - } - - unsigned int pos_cr_lf = line.find_first_of("\x0a\x0d"); - if (pos_cr_lf == 0) break; - - line = line.substr(0,pos_cr_lf); - - if (line.compare(0, sizeof(authorization) - 1, authorization) == 0) - { - req.authentication_given_ = true; - std::string encoded = line.substr(sizeof(authorization) - 1); - std::string decoded = base64_decode(encoded); - - unsigned int pos_colon = decoded.find(":"); - - req.username_ = decoded.substr(0, pos_colon); - req.password_ = decoded.substr(pos_colon + 1); - } - else if (line.compare(0, sizeof(accept) - 1, accept) == 0) - { - req.accept_ = line.substr(sizeof(accept) - 1); - } - else if (line.compare(0, sizeof(accept_language) - 1, accept_language) == 0) - { - req.accept_language_ = line.substr(sizeof(accept_language) - 1); - } - else if (line.compare(0, sizeof(accept_encoding) - 1, accept_encoding) == 0) - { - req.accept_encoding_ = line.substr(sizeof(accept_encoding) - 1); - } - else if (line.compare(0, sizeof(user_agent) - 1, user_agent) == 0) - { - req.user_agent_ = line.substr(sizeof(user_agent) - 1); - } - else if (line.compare(0, sizeof(content_length) - 1, content_length) == 0) - { - req.content_length_ = atoi(line.substr(sizeof(content_length) - 1).c_str() ); - } - else if (line.compare(0, sizeof(content_type) - 1, content_type) == 0) - { - req.content_type_ = line.substr(sizeof(content_type) - 1); - } - } - - if( (req.method_.compare("POST") == 0) && (req.content_length_ > 0) ) - { - const char FormUrlEncoded[] = "application/x-www-form-urlencoded"; - // The only content type we can parse at the moment, the default HTML post data - if( req.content_type_.substr(0, strlen(FormUrlEncoded)).compare( FormUrlEncoded ) == 0 ) - { - std::string Content = s->ReceiveBytes( req.content_length_ ); - Content.insert( 0, "/ ?" ); // Little hack, inserts dummy URL so that SplitGetReq() can work with this content - - std::string dummy; - std::map post_params; - SplitGetReq(Content, dummy, post_params); - - req.params_post_ = post_params; - } - else - { - ParseMultipartFormData( req, s ); - } - } - - request_func_(&req); - - std::stringstream str_str; - str_str << req.answer_.size(); - - time_t ltime; - time(<ime); - tm* gmt= gmtime(<ime); - -#ifdef _WIN32 - static const char serverName[] = "MCServerWebAdmin (Windows)"; -#elif __APPLE__ - static const char serverName[] = "MCServerWebAdmin (MacOSX)"; -#else - static const char serverName[] = "MCServerWebAdmin (Linux)"; -#endif - - - char* asctime_remove_nl = std::asctime(gmt); - asctime_remove_nl[24] = 0; - - s->SendBytes("HTTP/1.1 "); - - if (! req.auth_realm_.empty() ) - { - s->SendLine("401 Unauthorized"); - s->SendBytes("WWW-Authenticate: Basic Realm=\""); - s->SendBytes(req.auth_realm_); - s->SendLine("\""); - } - else - { - s->SendLine(req.status_); - } - s->SendLine(std::string("Date: ") + asctime_remove_nl + " GMT"); - s->SendLine(std::string("Server: ") + serverName); - s->SendLine("Connection: close"); - s->SendLine("Content-Type: text/html; charset=ISO-8859-1"); - s->SendLine("Content-Length: " + str_str.str()); - s->SendLine(""); - s->SendLine(req.answer_); - - s->Close( true ); // true = wait for all data to be sent before closing - delete s; - - - return 0; -} - - - - - -void webserver::Stop() -{ - m_bStop = true; - m_Socket->Close(); - m_Events->Wait(); -} - - - - - -bool webserver::Begin() -{ - if (!m_Socket->IsValid()) - { - LOGINFO("WebAdmin: The server socket is invalid. Terminating WebAdmin."); - return false; - } - m_bStop = false; - while ( !m_bStop ) - { - Socket * ptr_s = m_Socket->Accept(); - if (m_bStop) - { - if (ptr_s != 0) - { - ptr_s->Close(); - delete ptr_s; - } - break; - } - if (ptr_s == NULL) - { - LOGINFO("WebAdmin: Accepted socket is NULL. Terminating WebAdmin to avoid busywait."); - return false; - } - - #ifdef _WIN32 - unsigned ret; - HANDLE hHandle = reinterpret_cast(_beginthreadex(NULL, 0, Request, (void *)ptr_s, 0, &ret)); - CloseHandle(hHandle); - #else - // Mattes: TODO: this handle probably leaks! - pthread_t * hHandle = new pthread_t; - pthread_create( hHandle, NULL, Request, ptr_s); - #endif - } - m_Events->Set(); - return true; -} - - - - - -webserver::webserver(unsigned int port_to_listen, request_func r) -{ - m_Socket = new SocketServer(port_to_listen, 1); - - request_func_ = r; - m_Events = new cEvents(); -} - - - - - -webserver::~webserver() -{ - delete m_Socket; - delete m_Events; -} - - - - diff --git a/WebServer/WebServer.h b/WebServer/WebServer.h deleted file mode 100644 index 882624db..00000000 --- a/WebServer/WebServer.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - WebServer.h - - Copyright (C) 2003-2004 René Nyffenegger - - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. - - 3. This notice may not be removed or altered from any source distribution. - - René Nyffenegger rene.nyffenegger@adp-gmbh.ch - -*/ - -/* - Note on point 2: - THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1 -*/ - - -class cEvents; -class Socket; -class SocketServer; -class webserver { -public: - struct formdata - { - std::string name_; - std::string filename_; - std::string content_type_; - std::string value_; - }; - - struct http_request { - - http_request() - : s_( 0 ) - , content_length_( 0 ) - , authentication_given_(false) - {} - - Socket* s_; - std::string method_; - std::string path_; - std::map params_; - std::map params_post_; - - std::string accept_; - std::string accept_language_; - std::string accept_encoding_; - std::string user_agent_; - int content_length_; - std::string content_type_; - std::vector< formdata > multipart_formdata_; - - /* status_: used to transmit server's error status, such as - o 202 OK - o 404 Not Found - and so on */ - std::string status_; - - /* auth_realm_: allows to set the basic realm for an authentication, - no need to additionally set status_ if set */ - std::string auth_realm_; - - std::string answer_; - - /* authentication_given_ is true when the user has entered a username and password. - These can then be read from username_ and password_ */ - bool authentication_given_; - std::string username_; - std::string password_; - }; - - typedef void (*request_func) (http_request*); - webserver(unsigned int port_to_listen, request_func); - ~webserver(); - - bool Begin(); - void Stop(); - -private: - bool m_bStop; - - #ifdef _WIN32 - static unsigned __stdcall Request(void*); - #else - static void* Request(void*); - #endif - - static request_func request_func_; - - cEvents * m_Events; - SocketServer* m_Socket; -}; diff --git a/WebServer/base64.cpp b/WebServer/base64.cpp deleted file mode 100644 index f505ea6c..00000000 --- a/WebServer/base64.cpp +++ /dev/null @@ -1,102 +0,0 @@ - -#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules - -#include "base64.h" -#include -#if defined(ANDROID_NDK) -#include -#endif - -static const std::string base64_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - - -static inline bool is_base64(unsigned char c) { - return (isalnum(c) || (c == '+') || (c == '/')); -} - -std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { - std::string ret; - int i = 0; - int j = 0; - unsigned char char_array_3[3]; - unsigned char char_array_4[4]; - - while (in_len--) { - char_array_3[i++] = *(bytes_to_encode++); - if (i == 3) { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for(i = 0; (i <4) ; i++) - ret += base64_chars[char_array_4[i]]; - i = 0; - } - } - - if (i) - { - for(j = i; j < 3; j++) - char_array_3[j] = '\0'; - - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for (j = 0; (j < i + 1); j++) - ret += base64_chars[char_array_4[j]]; - - while((i++ < 3)) - ret += '='; - - } - - return ret; - -} - -std::string base64_decode(std::string const& encoded_string) { - int in_len = encoded_string.size(); - int i = 0; - int j = 0; - int in_ = 0; - unsigned char char_array_4[4], char_array_3[3]; - std::string ret; - - while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { - char_array_4[i++] = encoded_string[in_]; in_++; - if (i ==4) { - for (i = 0; i <4; i++) - char_array_4[i] = base64_chars.find(char_array_4[i]); - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (i = 0; (i < 3); i++) - ret += char_array_3[i]; - i = 0; - } - } - - if (i) { - for (j = i; j <4; j++) - char_array_4[j] = 0; - - for (j = 0; j <4; j++) - char_array_4[j] = base64_chars.find(char_array_4[j]); - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; - } - - return ret; -} diff --git a/WebServer/base64.h b/WebServer/base64.h deleted file mode 100644 index 65d5db8b..00000000 --- a/WebServer/base64.h +++ /dev/null @@ -1,4 +0,0 @@ -#include - -std::string base64_encode(unsigned char const* , unsigned int len); -std::string base64_decode(std::string const& s); diff --git a/source/Bindings.cpp b/source/Bindings.cpp index d4a8da84..d5482d3f 100644 --- a/source/Bindings.cpp +++ b/source/Bindings.cpp @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 10/04/13 08:36:37. +** Generated automatically by tolua++-1.0.92 on 10/06/13 15:12:14. */ #ifndef __cplusplus @@ -240,18 +240,19 @@ static void tolua_reg_types (lua_State* tolua_S) tolua_usertype(tolua_S,"cCraftingRecipe"); tolua_usertype(tolua_S,"cPlugin"); tolua_usertype(tolua_S,"cItemGrid"); - tolua_usertype(tolua_S,"cBlockArea"); + tolua_usertype(tolua_S,"cHTTPServer::cCallbacks"); tolua_usertype(tolua_S,"cLuaWindow"); tolua_usertype(tolua_S,"cInventory"); tolua_usertype(tolua_S,"cBoundingBox"); tolua_usertype(tolua_S,"cBlockEntityWithItems"); - tolua_usertype(tolua_S,"HTTPFormData"); tolua_usertype(tolua_S,"cTracer"); + tolua_usertype(tolua_S,"HTTPFormData"); + tolua_usertype(tolua_S,"cWindow"); tolua_usertype(tolua_S,"cArrowEntity"); tolua_usertype(tolua_S,"cDropSpenserEntity"); - tolua_usertype(tolua_S,"cWindow"); - tolua_usertype(tolua_S,"Vector3i"); + tolua_usertype(tolua_S,"cBlockArea"); tolua_usertype(tolua_S,"cCraftingGrid"); + tolua_usertype(tolua_S,"Vector3i"); tolua_usertype(tolua_S,"cGroup"); tolua_usertype(tolua_S,"cStringMap"); tolua_usertype(tolua_S,"cBlockEntity"); @@ -18794,38 +18795,6 @@ static int tolua_AllToLua_cWebAdmin_GetMemoryUsage00(lua_State* tolua_S) } #endif //#ifndef TOLUA_DISABLE -/* method: GetPort of class cWebAdmin */ -#ifndef TOLUA_DISABLE_tolua_AllToLua_cWebAdmin_GetPort00 -static int tolua_AllToLua_cWebAdmin_GetPort00(lua_State* tolua_S) -{ -#ifndef TOLUA_RELEASE - tolua_Error tolua_err; - if ( - !tolua_isusertype(tolua_S,1,"cWebAdmin",0,&tolua_err) || - !tolua_isnoobj(tolua_S,2,&tolua_err) - ) - goto tolua_lerror; - else -#endif - { - cWebAdmin* self = (cWebAdmin*) tolua_tousertype(tolua_S,1,0); -#ifndef TOLUA_RELEASE - if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetPort'", NULL); -#endif - { - int tolua_ret = (int) self->GetPort(); - tolua_pushnumber(tolua_S,(lua_Number)tolua_ret); - } - } - return 1; -#ifndef TOLUA_RELEASE - tolua_lerror: - tolua_error(tolua_S,"#ferror in function 'GetPort'.",&tolua_err); - return 0; -#endif -} -#endif //#ifndef TOLUA_DISABLE - /* method: GetPage of class cWebAdmin */ #ifndef TOLUA_DISABLE_tolua_AllToLua_cWebAdmin_GetPage00 static int tolua_AllToLua_cWebAdmin_GetPage00(lua_State* tolua_S) @@ -18870,6 +18839,38 @@ static int tolua_AllToLua_cWebAdmin_GetPage00(lua_State* tolua_S) } #endif //#ifndef TOLUA_DISABLE +/* method: GetDefaultPage of class cWebAdmin */ +#ifndef TOLUA_DISABLE_tolua_AllToLua_cWebAdmin_GetDefaultPage00 +static int tolua_AllToLua_cWebAdmin_GetDefaultPage00(lua_State* tolua_S) +{ +#ifndef TOLUA_RELEASE + tolua_Error tolua_err; + if ( + !tolua_isusertype(tolua_S,1,"cWebAdmin",0,&tolua_err) || + !tolua_isnoobj(tolua_S,2,&tolua_err) + ) + goto tolua_lerror; + else +#endif + { + cWebAdmin* self = (cWebAdmin*) tolua_tousertype(tolua_S,1,0); +#ifndef TOLUA_RELEASE + if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetDefaultPage'", NULL); +#endif + { + AString tolua_ret = (AString) self->GetDefaultPage(); + tolua_pushcppstring(tolua_S,(const char*)tolua_ret); + } + } + return 1; +#ifndef TOLUA_RELEASE + tolua_lerror: + tolua_error(tolua_S,"#ferror in function 'GetDefaultPage'.",&tolua_err); + return 0; +#endif +} +#endif //#ifndef TOLUA_DISABLE + /* method: GetBaseURL of class cWebAdmin */ #ifndef TOLUA_DISABLE_tolua_AllToLua_cWebAdmin_GetBaseURL00 static int tolua_AllToLua_cWebAdmin_GetBaseURL00(lua_State* tolua_S) @@ -30330,11 +30331,11 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S) tolua_variable(tolua_S,"PluginName",tolua_get_sWebAdminPage_PluginName,tolua_set_sWebAdminPage_PluginName); tolua_variable(tolua_S,"TabName",tolua_get_sWebAdminPage_TabName,tolua_set_sWebAdminPage_TabName); tolua_endmodule(tolua_S); - tolua_cclass(tolua_S,"cWebAdmin","cWebAdmin","",NULL); + tolua_cclass(tolua_S,"cWebAdmin","cWebAdmin","cHTTPServer::cCallbacks",NULL); tolua_beginmodule(tolua_S,"cWebAdmin"); tolua_function(tolua_S,"GetMemoryUsage",tolua_AllToLua_cWebAdmin_GetMemoryUsage00); - tolua_function(tolua_S,"GetPort",tolua_AllToLua_cWebAdmin_GetPort00); tolua_function(tolua_S,"GetPage",tolua_AllToLua_cWebAdmin_GetPage00); + tolua_function(tolua_S,"GetDefaultPage",tolua_AllToLua_cWebAdmin_GetDefaultPage00); tolua_function(tolua_S,"GetBaseURL",tolua_AllToLua_cWebAdmin_GetBaseURL00); tolua_endmodule(tolua_S); tolua_cclass(tolua_S,"cWebPlugin","cWebPlugin","",NULL); diff --git a/source/Bindings.h b/source/Bindings.h index 359f5c87..2095ecd5 100644 --- a/source/Bindings.h +++ b/source/Bindings.h @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 10/04/13 08:36:38. +** Generated automatically by tolua++-1.0.92 on 10/06/13 15:12:15. */ /* Exported function */ diff --git a/source/HTTPServer/EnvelopeParser.cpp b/source/HTTPServer/EnvelopeParser.cpp new file mode 100644 index 00000000..8dbe05f1 --- /dev/null +++ b/source/HTTPServer/EnvelopeParser.cpp @@ -0,0 +1,132 @@ + +// EnvelopeParser.cpp + +// Implements the cEnvelopeParser class representing a parser for RFC-822 envelope headers, used both in HTTP and in MIME + +#include "Globals.h" +#include "EnvelopeParser.h" + + + + + +cEnvelopeParser::cEnvelopeParser(cCallbacks & a_Callbacks) : + m_Callbacks(a_Callbacks), + m_IsInHeaders(true) +{ +} + + + + + +int cEnvelopeParser::Parse(const char * a_Data, int a_Size) +{ + if (!m_IsInHeaders) + { + return 0; + } + + // Start searching 1 char from the end of the already received data, if available: + size_t SearchStart = m_IncomingData.size(); + SearchStart = (SearchStart > 1) ? SearchStart - 1 : 0; + + m_IncomingData.append(a_Data, a_Size); + + size_t idxCRLF = m_IncomingData.find("\r\n", SearchStart); + if (idxCRLF == AString::npos) + { + // Not a complete line yet, all input consumed: + return a_Size; + } + + // Parse as many lines as found: + size_t Last = 0; + do + { + if (idxCRLF == Last) + { + // This was the last line of the data. Finish whatever value has been cached and return: + NotifyLast(); + m_IsInHeaders = false; + return a_Size - (m_IncomingData.size() - idxCRLF) + 2; + } + if (!ParseLine(m_IncomingData.c_str() + Last, idxCRLF - Last)) + { + // An error has occurred + m_IsInHeaders = false; + return -1; + } + Last = idxCRLF + 2; + idxCRLF = m_IncomingData.find("\r\n", idxCRLF + 2); + } while (idxCRLF != AString::npos); + m_IncomingData.erase(0, Last); + + // Parsed all lines and still expecting more + return a_Size; +} + + + + + +void cEnvelopeParser::Reset(void) +{ + m_IsInHeaders = true; + m_IncomingData.clear(); + m_LastKey.clear(); + m_LastValue.clear(); +} + + + + + +void cEnvelopeParser::NotifyLast(void) +{ + if (!m_LastKey.empty()) + { + m_Callbacks.OnHeaderLine(m_LastKey, m_LastValue); + m_LastKey.clear(); + } + m_LastValue.clear(); +} + + + + + +bool cEnvelopeParser::ParseLine(const char * a_Data, size_t a_Size) +{ + ASSERT(a_Size > 0); + if (a_Data[0] <= ' ') + { + // This line is a continuation for the previous line + if (m_LastKey.empty()) + { + return false; + } + // Append, including the whitespace in a_Data[0] + m_LastValue.append(a_Data, a_Size); + return true; + } + + // This is a line with a new key: + NotifyLast(); + for (size_t i = 0; i < a_Size; i++) + { + if (a_Data[i] == ':') + { + m_LastKey.assign(a_Data, i); + m_LastValue.assign(a_Data + i + 2, a_Size - i - 2); + return true; + } + } // for i - a_Data[] + + // No colon was found, key-less header?? + return false; +} + + + + diff --git a/source/HTTPServer/EnvelopeParser.h b/source/HTTPServer/EnvelopeParser.h new file mode 100644 index 00000000..6430fbeb --- /dev/null +++ b/source/HTTPServer/EnvelopeParser.h @@ -0,0 +1,69 @@ + +// EnvelopeParser.h + +// Declares the cEnvelopeParser class representing a parser for RFC-822 envelope headers, used both in HTTP and in MIME + + + + + +#pragma once + + + + + +class cEnvelopeParser +{ +public: + class cCallbacks + { + public: + /// Called when a full header line is parsed + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) = 0; + } ; + + + cEnvelopeParser(cCallbacks & a_Callbacks); + + /** Parses the incoming data. + Returns the number of bytes consumed from the input. The bytes not consumed are not part of the envelope header + */ + int Parse(const char * a_Data, int a_Size); + + /// Makes the parser forget everything parsed so far, so that it can be reused for parsing another datastream + void Reset(void); + + /// Returns true if more input is expected for the envelope header + bool IsInHeaders(void) const { return m_IsInHeaders; } + + /// Sets the IsInHeaders flag; used by cMultipartParser to simplify the parser initial conditions + void SetIsInHeaders(bool a_IsInHeaders) { m_IsInHeaders = a_IsInHeaders; } + +public: + /// Callbacks to call for the various events + cCallbacks & m_Callbacks; + + /// Set to true while the parser is still parsing the envelope headers. Once set to true, the parser will not consume any more data. + bool m_IsInHeaders; + + /// Buffer for the incoming data until it is parsed + AString m_IncomingData; + + /// Holds the last parsed key; used for line-wrapped values + AString m_LastKey; + + /// Holds the last parsed value; used for line-wrapped values + AString m_LastValue; + + + /// Notifies the callback of the key/value stored in m_LastKey/m_LastValue, then erases them + void NotifyLast(void); + + /// Parses one line of header data. Returns true if successful + bool ParseLine(const char * a_Data, size_t a_Size); +} ; + + + + diff --git a/source/HTTPServer/HTTPConnection.cpp b/source/HTTPServer/HTTPConnection.cpp new file mode 100644 index 00000000..68afdfc1 --- /dev/null +++ b/source/HTTPServer/HTTPConnection.cpp @@ -0,0 +1,247 @@ + +// HTTPConnection.cpp + +// Implements the cHTTPConnection class representing a single persistent connection in the HTTP server. + +#include "Globals.h" +#include "HTTPConnection.h" +#include "HTTPMessage.h" +#include "HTTPServer.h" + + + + + +cHTTPConnection::cHTTPConnection(cHTTPServer & a_HTTPServer) : + m_HTTPServer(a_HTTPServer), + m_State(wcsRecvHeaders), + m_CurrentRequest(NULL) +{ + // LOGD("HTTP: New connection at %p", this); +} + + + + + +cHTTPConnection::~cHTTPConnection() +{ + // LOGD("HTTP: Del connection at %p", this); +} + + + + + +void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response) +{ + AppendPrintf(m_OutgoingData, "%d %s\r\nContent-Length: 0\r\n\r\n", a_StatusCode, a_Response.c_str()); + m_HTTPServer.NotifyConnectionWrite(*this); + m_State = wcsRecvHeaders; +} + + + + + +void cHTTPConnection::SendNeedAuth(const AString & a_Realm) +{ + AppendPrintf(m_OutgoingData, "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str()); + m_HTTPServer.NotifyConnectionWrite(*this); + m_State = wcsRecvHeaders; +} + + + + + +void cHTTPConnection::Send(const cHTTPResponse & a_Response) +{ + ASSERT(m_State = wcsRecvIdle); + a_Response.AppendToData(m_OutgoingData); + m_State = wcsSendingResp; + m_HTTPServer.NotifyConnectionWrite(*this); +} + + + + + +void cHTTPConnection::Send(const void * a_Data, int a_Size) +{ + ASSERT(m_State == wcsSendingResp); + AppendPrintf(m_OutgoingData, "%x\r\n", a_Size); + m_OutgoingData.append((const char *)a_Data, a_Size); + m_OutgoingData.append("\r\n"); + m_HTTPServer.NotifyConnectionWrite(*this); +} + + + + + +void cHTTPConnection::FinishResponse(void) +{ + ASSERT(m_State == wcsSendingResp); + m_OutgoingData.append("0\r\n\r\n"); + m_State = wcsRecvHeaders; + m_HTTPServer.NotifyConnectionWrite(*this); +} + + + + + +void cHTTPConnection::AwaitNextRequest(void) +{ + switch (m_State) + { + case wcsRecvHeaders: + { + // Nothing has been received yet, or a special response was given (SendStatusAndReason() or SendNeedAuth() ) + break; + } + + case wcsRecvIdle: + { + // The client is waiting for a response, send an "Internal server error": + m_OutgoingData.append("HTTP/1.1 500 Internal Server Error\r\n\r\n"); + m_HTTPServer.NotifyConnectionWrite(*this); + m_State = wcsRecvHeaders; + break; + } + + case wcsSendingResp: + { + // The response headers have been sent, we need to terminate the response body: + m_OutgoingData.append("0\r\n\r\n"); + m_State = wcsRecvHeaders; + break; + } + + default: + { + ASSERT(!"Unhandled state recovery"); + break; + } + } +} + + + + + +void cHTTPConnection::Terminate(void) +{ + if (m_CurrentRequest != NULL) + { + m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); + } + m_HTTPServer.CloseConnection(*this); +} + + + + + +void cHTTPConnection::DataReceived(const char * a_Data, int a_Size) +{ + switch (m_State) + { + case wcsRecvHeaders: + { + if (m_CurrentRequest == NULL) + { + m_CurrentRequest = new cHTTPRequest; + } + + int BytesConsumed = m_CurrentRequest->ParseHeaders(a_Data, a_Size); + if (BytesConsumed < 0) + { + delete m_CurrentRequest; + m_CurrentRequest = NULL; + m_State = wcsInvalid; + m_HTTPServer.CloseConnection(*this); + return; + } + if (m_CurrentRequest->IsInHeaders()) + { + // The request headers are not yet complete + return; + } + + // The request has finished parsing its headers successfully, notify of it: + m_State = wcsRecvBody; + m_HTTPServer.NewRequest(*this, *m_CurrentRequest); + m_CurrentRequestBodyRemaining = m_CurrentRequest->GetContentLength(); + if (m_CurrentRequestBodyRemaining < 0) + { + // The body length was not specified in the request, assume zero + m_CurrentRequestBodyRemaining = 0; + } + + // Process the rest of the incoming data into the request body: + if (a_Size > BytesConsumed) + { + DataReceived(a_Data + BytesConsumed, a_Size - BytesConsumed); + } + else + { + DataReceived("", 0); // If the request has zero body length, let it be processed right-away + } + break; + } + + case wcsRecvBody: + { + ASSERT(m_CurrentRequest != NULL); + if (m_CurrentRequestBodyRemaining > 0) + { + int BytesToConsume = std::min(m_CurrentRequestBodyRemaining, a_Size); + m_HTTPServer.RequestBody(*this, *m_CurrentRequest, a_Data, BytesToConsume); + m_CurrentRequestBodyRemaining -= BytesToConsume; + } + if (m_CurrentRequestBodyRemaining == 0) + { + m_State = wcsRecvIdle; + m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); + delete m_CurrentRequest; + m_CurrentRequest = NULL; + } + break; + } + + default: + { + // TODO: Should we be receiving data in this state? + break; + } + } +} + + + + + +void cHTTPConnection::GetOutgoingData(AString & a_Data) +{ + std::swap(a_Data, m_OutgoingData); +} + + + + + +void cHTTPConnection::SocketClosed(void) +{ + if (m_CurrentRequest != NULL) + { + m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); + } + m_HTTPServer.CloseConnection(*this); +} + + + + + diff --git a/source/HTTPServer/HTTPConnection.h b/source/HTTPServer/HTTPConnection.h new file mode 100644 index 00000000..14603bb7 --- /dev/null +++ b/source/HTTPServer/HTTPConnection.h @@ -0,0 +1,101 @@ + +// HTTPConnection.h + +// Declares the cHTTPConnection class representing a single persistent connection in the HTTP server. + + + + + +#pragma once + +#include "../OSSupport/SocketThreads.h" + + + + + +// fwd: +class cHTTPServer; +class cHTTPResponse; +class cHTTPRequest; + + + + + +class cHTTPConnection : + public cSocketThreads::cCallback +{ +public: + + enum eState + { + wcsRecvHeaders, ///< Receiving request headers (m_CurrentRequest is created if NULL) + wcsRecvBody, ///< Receiving request body (m_CurrentRequest is valid) + wcsRecvIdle, ///< Has received the entire body, waiting to send the response (m_CurrentRequest == NULL) + wcsSendingResp, ///< Sending response body (m_CurrentRequest == NULL) + wcsInvalid, ///< The request was malformed, the connection is closing + } ; + + cHTTPConnection(cHTTPServer & a_HTTPServer); + ~cHTTPConnection(); + + /// Sends HTTP status code together with a_Reason (used for HTTP errors) + void SendStatusAndReason(int a_StatusCode, const AString & a_Reason); + + /// Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm + void SendNeedAuth(const AString & a_Realm); + + /// Sends the headers contained in a_Response + void Send(const cHTTPResponse & a_Response); + + /// Sends the data as the response (may be called multiple times) + void Send(const void * a_Data, int a_Size); + + /// Sends the data as the response (may be called multiple times) + void Send(const AString & a_Data) { Send(a_Data.data(), a_Data.size()); } + + /// Indicates that the current response is finished, gets ready for receiving another request (HTTP 1.1 keepalive) + void FinishResponse(void); + + /// Resets the connection for a new request. Depending on the state, this will send an "InternalServerError" status or a "ResponseEnd" + void AwaitNextRequest(void); + + /// Terminates the connection; finishes any request being currently processed + void Terminate(void); + +protected: + typedef std::map cNameValueMap; + + /// The parent webserver that is to be notified of events on this connection + cHTTPServer & m_HTTPServer; + + /// All the incoming data until the entire request header is parsed + AString m_IncomingHeaderData; + + /// Status in which the request currently is + eState m_State; + + /// Data that is queued for sending, once the socket becomes writable + AString m_OutgoingData; + + /// The request being currently received (valid only between having parsed the headers and finishing receiving the body) + cHTTPRequest * m_CurrentRequest; + + /// Number of bytes that remain to read for the complete body of the message to be received. Valid only in wcsRecvBody + int m_CurrentRequestBodyRemaining; + + + // cSocketThreads::cCallback overrides: + virtual void DataReceived (const char * a_Data, int a_Size) override; // Data is received from the client + virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client + virtual void SocketClosed (void) override; // The socket has been closed for any reason +} ; + +typedef std::vector cHTTPConnections; + + + + + diff --git a/source/HTTPServer/HTTPFormParser.cpp b/source/HTTPServer/HTTPFormParser.cpp new file mode 100644 index 00000000..7db7b4e6 --- /dev/null +++ b/source/HTTPServer/HTTPFormParser.cpp @@ -0,0 +1,278 @@ + +// HTTPFormParser.cpp + +// Implements the cHTTPFormParser class representing a parser for forms sent over HTTP + +#include "Globals.h" +#include "HTTPFormParser.h" +#include "HTTPMessage.h" +#include "MultipartParser.h" +#include "NameValueParser.h" + + + + + +cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks) : + m_Callbacks(a_Callbacks), + m_IsValid(true) +{ + if (a_Request.GetMethod() == "GET") + { + m_Kind = fpkURL; + + // Directly parse the URL in the request: + const AString & URL = a_Request.GetURL(); + size_t idxQM = URL.find('?'); + if (idxQM != AString::npos) + { + Parse(URL.c_str() + idxQM + 1, URL.size() - idxQM - 1); + } + return; + } + if ((a_Request.GetMethod() == "POST") || (a_Request.GetMethod() == "PUT")) + { + if (strncmp(a_Request.GetContentType().c_str(), "application/x-www-form-urlencoded", 33) == 0) + { + m_Kind = fpkFormUrlEncoded; + return; + } + if (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0) + { + m_Kind = fpkMultipart; + BeginMultipart(a_Request); + return; + } + } + // Invalid method / content type combination, this is not a HTTP form + m_IsValid = false; +} + + + + + +void cHTTPFormParser::Parse(const char * a_Data, int a_Size) +{ + if (!m_IsValid) + { + return; + } + + switch (m_Kind) + { + case fpkURL: + case fpkFormUrlEncoded: + { + // This format is used for smaller forms (not file uploads), so we can delay parsing it until Finish() + m_IncomingData.append(a_Data, a_Size); + break; + } + case fpkMultipart: + { + ASSERT(m_MultipartParser.get() != NULL); + m_MultipartParser->Parse(a_Data, a_Size); + break; + } + default: + { + ASSERT(!"Unhandled form kind"); + break; + } + } +} + + + + + +bool cHTTPFormParser::Finish(void) +{ + switch (m_Kind) + { + case fpkURL: + case fpkFormUrlEncoded: + { + // m_IncomingData has all the form data, parse it now: + ParseFormUrlEncoded(); + break; + } + } + return (m_IsValid && m_IncomingData.empty()); +} + + + + + +bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request) +{ + const AString & ContentType = a_Request.GetContentType(); + return ( + (ContentType == "application/x-www-form-urlencoded") || + (strncmp(ContentType.c_str(), "multipart/form-data", 19) == 0) || + ( + (a_Request.GetMethod() == "GET") && + (a_Request.GetURL().find('?') != AString::npos) + ) + ); + return false; +} + + + + + +void cHTTPFormParser::BeginMultipart(const cHTTPRequest & a_Request) +{ + ASSERT(m_MultipartParser.get() == NULL); + m_MultipartParser.reset(new cMultipartParser(a_Request.GetContentType(), *this)); +} + + + + + +void cHTTPFormParser::ParseFormUrlEncoded(void) +{ + // Parse m_IncomingData for all the variables; no more data is incoming, since this is called from Finish() + // This may not be the most performant version, but we don't care, the form data is small enough and we're not a full-fledged web server anyway + AStringVector Lines = StringSplit(m_IncomingData, "&"); + for (AStringVector::iterator itr = Lines.begin(), end = Lines.end(); itr != end; ++itr) + { + AStringVector Components = StringSplit(*itr, "="); + switch (Components.size()) + { + default: + { + // Neither name nor value, or too many "="s, mark this as invalid form: + m_IsValid = false; + return; + } + case 1: + { + // Only name present + (*this)[URLDecode(ReplaceAllCharOccurrences(Components[0], '+', ' '))] = ""; + break; + } + case 2: + { + // name=value format: + (*this)[URLDecode(ReplaceAllCharOccurrences(Components[0], '+', ' '))] = URLDecode(ReplaceAllCharOccurrences(Components[1], '+', ' ')); + break; + } + } + } // for itr - Lines[] + m_IncomingData.clear(); +} + + + + + +void cHTTPFormParser::OnPartStart(void) +{ + m_CurrentPartFileName.clear(); + m_CurrentPartName.clear(); + m_IsCurrentPartFile = false; + m_FileHasBeenAnnounced = false; +} + + + + + +void cHTTPFormParser::OnPartHeader(const AString & a_Key, const AString & a_Value) +{ + if (NoCaseCompare(a_Key, "Content-Disposition") == 0) + { + size_t len = a_Value.size(); + size_t ParamsStart = AString::npos; + for (size_t i = 0; i < len; ++i) + { + if (a_Value[i] > ' ') + { + if (strncmp(a_Value.c_str() + i, "form-data", 9) != 0) + { + // Content disposition is not "form-data", mark the whole form invalid + m_IsValid = false; + return; + } + ParamsStart = a_Value.find(';', i + 9); + break; + } + } + if (ParamsStart == AString::npos) + { + // There is data missing in the Content-Disposition field, mark the whole form invalid: + m_IsValid = false; + return; + } + + // Parse the field name and optional filename from this header: + cNameValueParser Parser(a_Value.data() + ParamsStart, a_Value.size() - ParamsStart); + Parser.Finish(); + m_CurrentPartName = Parser["name"]; + if (!Parser.IsValid() || m_CurrentPartName.empty()) + { + // The required parameter "name" is missing, mark the whole form invalid: + m_IsValid = false; + return; + } + m_CurrentPartFileName = Parser["filename"]; + } +} + + + + + +void cHTTPFormParser::OnPartData(const char * a_Data, int a_Size) +{ + if (m_CurrentPartName.empty()) + { + // Prologue, epilogue or invalid part + return; + } + if (m_CurrentPartFileName.empty()) + { + // This is a variable, store it in the map + iterator itr = find(m_CurrentPartName); + if (itr == end()) + { + (*this)[m_CurrentPartName] = AString(a_Data, a_Size); + } + else + { + itr->second.append(a_Data, a_Size); + } + } + else + { + // This is a file, pass it on through the callbacks + if (!m_FileHasBeenAnnounced) + { + m_Callbacks.OnFileStart(*this, m_CurrentPartFileName); + m_FileHasBeenAnnounced = true; + } + m_Callbacks.OnFileData(*this, a_Data, a_Size); + } +} + + + + + +void cHTTPFormParser::OnPartEnd(void) +{ + if (m_FileHasBeenAnnounced) + { + m_Callbacks.OnFileEnd(*this); + } + m_CurrentPartName.clear(); + m_CurrentPartFileName.clear(); +} + + + + diff --git a/source/HTTPServer/HTTPFormParser.h b/source/HTTPServer/HTTPFormParser.h new file mode 100644 index 00000000..b92ef9d3 --- /dev/null +++ b/source/HTTPServer/HTTPFormParser.h @@ -0,0 +1,107 @@ + +// HTTPFormParser.h + +// Declares the cHTTPFormParser class representing a parser for forms sent over HTTP + + + + +#pragma once + +#include "MultipartParser.h" + + + + + +// fwd: +class cHTTPRequest; + + + + + +class cHTTPFormParser : + public std::map, + public cMultipartParser::cCallbacks +{ +public: + class cCallbacks + { + public: + /// Called when a new file part is encountered in the form data + virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) = 0; + + /// Called when more file data has come for the current file in the form data + virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, int a_Size) = 0; + + /// Called when the current file part has ended in the form data + virtual void OnFileEnd(cHTTPFormParser & a_Parser) = 0; + } ; + + + cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks); + + /// Adds more data into the parser, as the request body is received + void Parse(const char * a_Data, int a_Size); + + /** Notifies that there's no more data incoming and the parser should finish its parsing. + Returns true if parsing successful + */ + bool Finish(void); + + /// Returns true if the headers suggest the request has form data parseable by this class + static bool HasFormData(const cHTTPRequest & a_Request); + +protected: + enum eKind + { + fpkURL, ///< The form has been transmitted as parameters to a GET request + fpkFormUrlEncoded, ///< The form has been POSTed or PUT, with Content-Type of "application/x-www-form-urlencoded" + fpkMultipart, ///< The form has been POSTed or PUT, with Content-Type of "multipart/form-data" + }; + + /// The callbacks to call for incoming file data + cCallbacks & m_Callbacks; + + /// The kind of the parser (decided in the constructor, used in Parse() + eKind m_Kind; + + /// Buffer for the incoming data until it's parsed + AString m_IncomingData; + + /// True if the information received so far is a valid form; set to false on first problem. Further parsing is skipped when false. + bool m_IsValid; + + /// The parser for the multipart data, if used + std::auto_ptr m_MultipartParser; + + /// Name of the currently parsed part in multipart data + AString m_CurrentPartName; + + /// True if the currently parsed part in multipart data is a file + bool m_IsCurrentPartFile; + + /// Filename of the current parsed part in multipart data (for file uploads) + AString m_CurrentPartFileName; + + /// Set to true after m_Callbacks.OnFileStart() has been called, reset to false on PartEnd + bool m_FileHasBeenAnnounced; + + + /// Sets up the object for parsing a fpkMultipart request + void BeginMultipart(const cHTTPRequest & a_Request); + + /// Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds) + void ParseFormUrlEncoded(void); + + // cMultipartParser::cCallbacks overrides: + virtual void OnPartStart (void) override; + virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) override; + virtual void OnPartData (const char * a_Data, int a_Size) override; + virtual void OnPartEnd (void) override; +} ; + + + + diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp new file mode 100644 index 00000000..ab23866e --- /dev/null +++ b/source/HTTPServer/HTTPMessage.cpp @@ -0,0 +1,279 @@ + +// HTTPMessage.cpp + +// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes + +#include "Globals.h" +#include "HTTPMessage.h" + + + + + +// Disable MSVC warnings: +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4355) // 'this' : used in base member initializer list +#endif + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHTTPMessage: + +cHTTPMessage::cHTTPMessage(eKind a_Kind) : + m_Kind(a_Kind), + m_ContentLength(-1) +{ +} + + + + + +void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) +{ + AString Key = a_Key; + StrToLower(Key); + cNameValueMap::iterator itr = m_Headers.find(Key); + if (itr == m_Headers.end()) + { + m_Headers[Key] = a_Value; + } + else + { + // The header-field key is specified multiple times, combine into comma-separated list (RFC 2616 @ 4.2) + itr->second.append(", "); + itr->second.append(a_Value); + } + + // Special processing for well-known headers: + if (Key == "content-type") + { + m_ContentType = m_Headers[Key]; + } + else if (Key == "content-length") + { + m_ContentLength = atoi(m_Headers[Key].c_str()); + } +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHTTPRequest: + +cHTTPRequest::cHTTPRequest(void) : + super(mkRequest), + m_EnvelopeParser(*this), + m_IsValid(true), + m_UserData(NULL), + m_HasAuth(false) +{ +} + + + + + +int cHTTPRequest::ParseHeaders(const char * a_Data, int a_Size) +{ + if (!m_IsValid) + { + return -1; + } + + if (m_Method.empty()) + { + // The first line hasn't been processed yet + int res = ParseRequestLine(a_Data, a_Size); + if ((res < 0) || (res == a_Size)) + { + return res; + } + int res2 = m_EnvelopeParser.Parse(a_Data + res, a_Size - res); + if (res2 < 0) + { + m_IsValid = false; + return res2; + } + return res2 + res; + } + + if (m_EnvelopeParser.IsInHeaders()) + { + int res = m_EnvelopeParser.Parse(a_Data, a_Size); + if (res < 0) + { + m_IsValid = false; + } + return res; + } + return 0; +} + + + + + +AString cHTTPRequest::GetBareURL(void) const +{ + size_t idxQM = m_URL.find('?'); + if (idxQM != AString::npos) + { + return m_URL.substr(0, idxQM); + } + else + { + return m_URL; + } +} + + + + + +int cHTTPRequest::ParseRequestLine(const char * a_Data, int a_Size) +{ + m_IncomingHeaderData.append(a_Data, a_Size); + size_t IdxEnd = m_IncomingHeaderData.size(); + + // Ignore the initial CRLFs (HTTP spec's "should") + size_t LineStart = 0; + while ( + (LineStart < IdxEnd) && + ( + (m_IncomingHeaderData[LineStart] == '\r') || + (m_IncomingHeaderData[LineStart] == '\n') + ) + ) + { + LineStart++; + } + if (LineStart >= IdxEnd) + { + m_IsValid = false; + return -1; + } + + int NumSpaces = 0; + size_t MethodEnd = 0; + size_t URLEnd = 0; + for (size_t i = LineStart; i < IdxEnd; i++) + { + switch (m_IncomingHeaderData[i]) + { + case ' ': + { + switch (NumSpaces) + { + case 0: + { + MethodEnd = i; + break; + } + case 1: + { + URLEnd = i; + break; + } + default: + { + // Too many spaces in the request + m_IsValid = false; + return -1; + } + } + NumSpaces += 1; + break; + } + case '\n': + { + if ((i == 0) || (m_IncomingHeaderData[i - 1] != '\r') || (NumSpaces != 2) || (i < URLEnd + 7)) + { + // LF too early, without a CR, without two preceeding spaces or too soon after the second space + m_IsValid = false; + return -1; + } + // Check that there's HTTP/version at the end + if (strncmp(a_Data + URLEnd + 1, "HTTP/1.", 7) != 0) + { + m_IsValid = false; + return -1; + } + m_Method = m_IncomingHeaderData.substr(LineStart, MethodEnd - LineStart); + m_URL = m_IncomingHeaderData.substr(MethodEnd + 1, URLEnd - MethodEnd - 1); + return i + 1; + } + } // switch (m_IncomingHeaderData[i]) + } // for i - m_IncomingHeaderData[] + + // CRLF hasn't been encountered yet, consider all data consumed + return a_Size; +} + + + + + +void cHTTPRequest::OnHeaderLine(const AString & a_Key, const AString & a_Value) +{ + if ( + (NoCaseCompare(a_Key, "Authorization") == 0) && + (strncmp(a_Value.c_str(), "Basic ", 6) == 0) + ) + { + AString UserPass = Base64Decode(a_Value.substr(6)); + size_t idxCol = UserPass.find(':'); + if (idxCol != AString::npos) + { + m_AuthUsername = UserPass.substr(0, idxCol); + m_AuthPassword = UserPass.substr(idxCol + 1); + m_HasAuth = true; + } + } + AddHeader(a_Key, a_Value); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHTTPResponse: + +cHTTPResponse::cHTTPResponse(void) : + super(mkResponse) +{ +} + + + + + +void cHTTPResponse::AppendToData(AString & a_DataStream) const +{ + a_DataStream.append("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: "); + a_DataStream.append(m_ContentType); + a_DataStream.append("\r\n"); + for (cNameValueMap::const_iterator itr = m_Headers.begin(), end = m_Headers.end(); itr != end; ++itr) + { + if ((itr->first == "Content-Type") || (itr->first == "Content-Length")) + { + continue; + } + a_DataStream.append(itr->first); + a_DataStream.append(": "); + a_DataStream.append(itr->second); + a_DataStream.append("\r\n"); + } // for itr - m_Headers[] + a_DataStream.append("\r\n"); +} + + + + diff --git a/source/HTTPServer/HTTPMessage.h b/source/HTTPServer/HTTPMessage.h new file mode 100644 index 00000000..f5284c53 --- /dev/null +++ b/source/HTTPServer/HTTPMessage.h @@ -0,0 +1,164 @@ + +// HTTPMessage.h + +// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes + + + + + +#pragma once + +#include "EnvelopeParser.h" + + + + + +class cHTTPMessage +{ +public: + enum + { + HTTP_OK = 200, + HTTP_BAD_REQUEST = 400, + } ; + + enum eKind + { + mkRequest, + mkResponse, + } ; + + cHTTPMessage(eKind a_Kind); + + /// Adds a header into the internal map of headers. Recognizes special headers: Content-Type and Content-Length + void AddHeader(const AString & a_Key, const AString & a_Value); + + void SetContentType (const AString & a_ContentType) { m_ContentType = a_ContentType; } + void SetContentLength(int a_ContentLength) { m_ContentLength = a_ContentLength; } + + const AString & GetContentType (void) const { return m_ContentType; } + int GetContentLength(void) const { return m_ContentLength; } + +protected: + typedef std::map cNameValueMap; + + eKind m_Kind; + + cNameValueMap m_Headers; + + /// Type of the content; parsed by AddHeader(), set directly by SetContentLength() + AString m_ContentType; + + /// Length of the content that is to be received. -1 when the object is created, parsed by AddHeader() or set directly by SetContentLength() + int m_ContentLength; +} ; + + + + + +class cHTTPRequest : + public cHTTPMessage, + protected cEnvelopeParser::cCallbacks +{ + typedef cHTTPMessage super; + +public: + cHTTPRequest(void); + + /** Parses the request line and then headers from the received data. + Returns the number of bytes consumed or a negative number for error + */ + int ParseHeaders(const char * a_Data, int a_Size); + + /// Returns true if the request did contain a Content-Length header + bool HasReceivedContentLength(void) const { return (m_ContentLength >= 0); } + + /// Returns the method used in the request + const AString & GetMethod(void) const { return m_Method; } + + /// Returns the URL used in the request + const AString & GetURL(void) const { return m_URL; } + + /// Returns the URL used in the request, without any parameters + AString GetBareURL(void) const; + + /// Sets the UserData pointer that is stored within this request. The request doesn't touch this data (doesn't delete it)! + void SetUserData(void * a_UserData) { m_UserData = a_UserData; } + + /// Retrieves the UserData pointer that has been stored within this request. + void * GetUserData(void) const { return m_UserData; } + + /// Returns true if more data is expected for the request headers + bool IsInHeaders(void) const { return m_EnvelopeParser.IsInHeaders(); } + + /// Returns true if the request did present auth data that was understood by the parser + bool HasAuth(void) const { return m_HasAuth; } + + /// Returns the username that the request presented. Only valid if HasAuth() is true + const AString & GetAuthUsername(void) const { return m_AuthUsername; } + + /// Returns the password that the request presented. Only valid if HasAuth() is true + const AString & GetAuthPassword(void) const { return m_AuthPassword; } + +protected: + /// Parser for the envelope data + cEnvelopeParser m_EnvelopeParser; + + /// True if the data received so far is parsed successfully. When false, all further parsing is skipped + bool m_IsValid; + + /// Bufferred incoming data, while parsing for the request line + AString m_IncomingHeaderData; + + /// Method of the request (GET / PUT / POST / ...) + AString m_Method; + + /// Full URL of the request + AString m_URL; + + /// Data that the HTTPServer callbacks are allowed to store. + void * m_UserData; + + /// Set to true if the request contains auth data that was understood by the parser + bool m_HasAuth; + + /// The username used for auth + AString m_AuthUsername; + + /// The password used for auth + AString m_AuthPassword; + + + /** Parses the incoming data for the first line (RequestLine) + Returns the number of bytes consumed, or -1 for an error + */ + int ParseRequestLine(const char * a_Data, int a_Size); + + // cEnvelopeParser::cCallbacks overrides: + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; +} ; + + + + + +class cHTTPResponse : + public cHTTPMessage +{ + typedef cHTTPMessage super; + +public: + cHTTPResponse(void); + + /** Appends the response to the specified datastream - response line and headers. + The body will be sent later directly through cConnection::Send() + */ + void AppendToData(AString & a_DataStream) const; +} ; + + + + diff --git a/source/HTTPServer/HTTPServer.cpp b/source/HTTPServer/HTTPServer.cpp new file mode 100644 index 00000000..f6f5b0f8 --- /dev/null +++ b/source/HTTPServer/HTTPServer.cpp @@ -0,0 +1,258 @@ + +// HTTPServer.cpp + +// Implements the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing + +#include "Globals.h" +#include "HTTPServer.h" +#include "HTTPMessage.h" +#include "HTTPConnection.h" +#include "HTTPFormParser.h" + + + + + +// Disable MSVC warnings: +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4355) // 'this' : used in base member initializer list +#endif + + + + + +class cDebugCallbacks : + public cHTTPServer::cCallbacks, + protected cHTTPFormParser::cCallbacks +{ + virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override + { + if (cHTTPFormParser::HasFormData(a_Request)) + { + a_Request.SetUserData(new cHTTPFormParser(a_Request, *this)); + } + } + + + virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) override + { + cHTTPFormParser * FormParser = (cHTTPFormParser *)(a_Request.GetUserData()); + if (FormParser != NULL) + { + FormParser->Parse(a_Data, a_Size); + } + } + + + virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override + { + cHTTPFormParser * FormParser = (cHTTPFormParser *)(a_Request.GetUserData()); + if (FormParser != NULL) + { + if (FormParser->Finish()) + { + cHTTPResponse Resp; + Resp.SetContentType("text/html"); + a_Connection.Send(Resp); + a_Connection.Send("\r\n"); + for (cHTTPFormParser::iterator itr = FormParser->begin(), end = FormParser->end(); itr != end; ++itr) + { + a_Connection.Send(Printf("\r\n", itr->first.c_str(), itr->second.c_str())); + } // for itr - FormParser[] + a_Connection.Send("
NameValue
%s
%s
"); + return; + } + + // Parsing failed: + cHTTPResponse Resp; + Resp.SetContentType("text/plain"); + a_Connection.Send(Resp); + a_Connection.Send("Form parsing failed"); + return; + } + + // Test the auth failure and success: + if (a_Request.GetURL() == "/auth") + { + if (!a_Request.HasAuth() || (a_Request.GetAuthUsername() != "a") || (a_Request.GetAuthPassword() != "b")) + { + a_Connection.SendNeedAuth("MCServer WebAdmin"); + return; + } + } + + cHTTPResponse Resp; + Resp.SetContentType("text/plain"); + a_Connection.Send(Resp); + a_Connection.Send("Hello, world"); + } + + + virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) override + { + // TODO + } + + + virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, int a_Size) override + { + // TODO + } + + + virtual void OnFileEnd(cHTTPFormParser & a_Parser) override + { + // TODO + } + +} g_DebugCallbacks; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHTTPServer: + +cHTTPServer::cHTTPServer(void) : + m_ListenThreadIPv4(*this, cSocket::IPv4, "WebServer IPv4"), + m_ListenThreadIPv6(*this, cSocket::IPv6, "WebServer IPv6"), + m_Callbacks(NULL) +{ +} + + + + + +cHTTPServer::~cHTTPServer() +{ + Stop(); +} + + + + + +bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6) +{ + bool HasAnyPort; + HasAnyPort = m_ListenThreadIPv4.Initialize(a_PortsIPv4); + HasAnyPort = m_ListenThreadIPv6.Initialize(a_PortsIPv6) || HasAnyPort; + if (!HasAnyPort) + { + return false; + } + + return true; +} + + + + + +bool cHTTPServer::Start(cCallbacks & a_Callbacks) +{ + m_Callbacks = &a_Callbacks; + if (!m_ListenThreadIPv4.Start()) + { + return false; + } + if (!m_ListenThreadIPv6.Start()) + { + m_ListenThreadIPv4.Stop(); + return false; + } + return true; +} + + + + + +void cHTTPServer::Stop(void) +{ + m_ListenThreadIPv4.Stop(); + m_ListenThreadIPv6.Stop(); + + // Drop all current connections: + cCSLock Lock(m_CSConnections); + while (!m_Connections.empty()) + { + m_Connections.front()->Terminate(); + } // for itr - m_Connections[] +} + + + + + +void cHTTPServer::OnConnectionAccepted(cSocket & a_Socket) +{ + cHTTPConnection * Connection = new cHTTPConnection(*this); + m_SocketThreads.AddClient(a_Socket, Connection); + cCSLock Lock(m_CSConnections); + m_Connections.push_back(Connection); +} + + + + + +void cHTTPServer::CloseConnection(cHTTPConnection & a_Connection) +{ + m_SocketThreads.RemoveClient(&a_Connection); + cCSLock Lock(m_CSConnections); + for (cHTTPConnections::iterator itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr) + { + if (*itr == &a_Connection) + { + m_Connections.erase(itr); + break; + } + } + delete &a_Connection; +} + + + + + +void cHTTPServer::NotifyConnectionWrite(cHTTPConnection & a_Connection) +{ + m_SocketThreads.NotifyWrite(&a_Connection); +} + + + + + +void cHTTPServer::NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +{ + m_Callbacks->OnRequestBegun(a_Connection, a_Request); +} + + + + + +void cHTTPServer::RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) +{ + m_Callbacks->OnRequestBody(a_Connection, a_Request, a_Data, a_Size); +} + + + + + +void cHTTPServer::RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +{ + m_Callbacks->OnRequestFinished(a_Connection, a_Request); + a_Connection.AwaitNextRequest(); +} + + + + diff --git a/source/HTTPServer/HTTPServer.h b/source/HTTPServer/HTTPServer.h new file mode 100644 index 00000000..fea2a902 --- /dev/null +++ b/source/HTTPServer/HTTPServer.h @@ -0,0 +1,101 @@ + +// HTTPServer.h + +// Declares the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing + + + + + +#pragma once + +#include "../OSSupport/ListenThread.h" +#include "../OSSupport/SocketThreads.h" +#include "../../iniFile/iniFile.h" + + + + + +// fwd: +class cHTTPMessage; +class cHTTPRequest; +class cHTTPResponse; +class cHTTPConnection; + +typedef std::vector cHTTPConnections; + + + + + + +class cHTTPServer : + public cListenThread::cCallback +{ +public: + class cCallbacks + { + public: + /** Called when a new request arrives over a connection and its headers have been parsed. + The request body needn't have arrived yet. + */ + virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0; + + /// Called when another part of request body has arrived. + virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) = 0; + + /// Called when the request body has been fully received in previous calls to OnRequestBody() + virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0; + } ; + + cHTTPServer(void); + ~cHTTPServer(); + + /// Initializes the server on the specified ports + bool Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6); + + /// Starts the server and assigns the callbacks to use for incoming requests + bool Start(cCallbacks & a_Callbacks); + + /// Stops the server, drops all current connections + void Stop(void); + +protected: + friend class cHTTPConnection; + + cListenThread m_ListenThreadIPv4; + cListenThread m_ListenThreadIPv6; + + cSocketThreads m_SocketThreads; + + cCriticalSection m_CSConnections; + cHTTPConnections m_Connections; ///< All the connections that are currently being serviced + + /// The callbacks to call for various events + cCallbacks * m_Callbacks; + + + // cListenThread::cCallback overrides: + virtual void OnConnectionAccepted(cSocket & a_Socket) override; + + /// Called by cHTTPConnection to close the connection (presumably due to an error) + void CloseConnection(cHTTPConnection & a_Connection); + + /// Called by cHTTPConnection to notify SocketThreads that there's data to be sent for the connection + void NotifyConnectionWrite(cHTTPConnection & a_Connection); + + /// Called by cHTTPConnection when it finishes parsing the request header + void NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request); + + /// Called by cHTTPConenction when it receives more data for the request body + void RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size); + + /// Called by cHTTPConnection when it detects that the request has finished (all of its body has been received) + void RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request); +} ; + + + + + diff --git a/source/HTTPServer/MultipartParser.cpp b/source/HTTPServer/MultipartParser.cpp new file mode 100644 index 00000000..b49f6ec0 --- /dev/null +++ b/source/HTTPServer/MultipartParser.cpp @@ -0,0 +1,256 @@ + +// MultipartParser.cpp + +// Implements the cMultipartParser class that parses messages in "multipart/*" encoding into the separate parts + +#include "Globals.h" +#include "MultipartParser.h" +#include "NameValueParser.h" + + + + + +// Disable MSVC warnings: +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4355) // 'this' : used in base member initializer list +#endif + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// self-test: + +#if 0 + +class cMultipartParserTest : + public cMultipartParser::cCallbacks +{ +public: + cMultipartParserTest(void) + { + cMultipartParser Parser("multipart/mixed; boundary=\"MyBoundaryString\"; foo=bar", *this); + const char Data[] = +"ThisIsIgnoredPrologue\r\n\ +--MyBoundaryString\r\n\ +\r\n\ +Body with confusing strings\r\n\ +--NotABoundary\r\n\ +--MyBoundaryStringWithPostfix\r\n\ +--\r\n\ +--MyBoundaryString\r\n\ +content-disposition: inline\r\n\ +\r\n\ +This is body\r\n\ +--MyBoundaryString\r\n\ +\r\n\ +Headerless body with trailing CRLF\r\n\ +\r\n\ +--MyBoundaryString--\r\n\ +ThisIsIgnoredEpilogue"; + printf("Multipart parsing test commencing.\n"); + Parser.Parse(Data, sizeof(Data) - 1); + // DEBUG: Check if the onscreen output corresponds with the data above + printf("Multipart parsing test finished\n"); + } + + virtual void OnPartStart(void) override + { + printf("Starting a new part\n"); + } + + + virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) override + { + printf(" Hdr: \"%s\"=\"%s\"\n", a_Key.c_str(), a_Value.c_str()); + } + + + virtual void OnPartData(const char * a_Data, int a_Size) override + { + printf(" Data: %d bytes, \"%.*s\"\n", a_Size, a_Size, a_Data); + } + + + virtual void OnPartEnd(void) override + { + printf("Part end\n"); + } +} g_Test; + +#endif + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cMultipartParser: + + +cMultipartParser::cMultipartParser(const AString & a_ContentType, cCallbacks & a_Callbacks) : + m_Callbacks(a_Callbacks), + m_IsValid(true), + m_EnvelopeParser(*this), + m_HasHadData(false) +{ + static AString s_Multipart = "multipart/"; + + // Check that the content type is multipart: + AString ContentType(a_ContentType); + if (strncmp(ContentType.c_str(), "multipart/", 10) != 0) + { + m_IsValid = false; + return; + } + size_t idxSC = ContentType.find(';', 10); + if (idxSC == AString::npos) + { + m_IsValid = false; + return; + } + + // Find the multipart boundary: + ContentType.erase(0, idxSC + 1); + cNameValueParser CTParser(ContentType.c_str(), ContentType.size()); + CTParser.Finish(); + if (!CTParser.IsValid()) + { + m_IsValid = false; + return; + } + m_Boundary = CTParser["boundary"]; + m_IsValid = !m_Boundary.empty(); + if (!m_IsValid) + { + return; + } + + // Set the envelope parser for parsing the body, so that our Parse() function parses the ignored prefix data as a body + m_EnvelopeParser.SetIsInHeaders(false); + + // Append an initial CRLF to the incoming data, so that a body starting with the boundary line will get caught + m_IncomingData.assign("\r\n"); + + /* + m_Boundary = AString("\r\n--") + m_Boundary + m_BoundaryEnd = m_Boundary + "--\r\n"; + m_Boundary = m_Boundary + "\r\n"; + */ +} + + + + + +void cMultipartParser::Parse(const char * a_Data, int a_Size) +{ + // Skip parsing if invalid + if (!m_IsValid) + { + return; + } + + // Append to buffer, then parse it: + m_IncomingData.append(a_Data, a_Size); + while (true) + { + if (m_EnvelopeParser.IsInHeaders()) + { + int BytesConsumed = m_EnvelopeParser.Parse(m_IncomingData.data(), m_IncomingData.size()); + if (BytesConsumed < 0) + { + m_IsValid = false; + return; + } + if ((BytesConsumed == a_Size) && m_EnvelopeParser.IsInHeaders()) + { + // All the incoming data has been consumed and still waiting for more + return; + } + m_IncomingData.erase(0, BytesConsumed); + } + + // Search for boundary / boundary end: + size_t idxBoundary = m_IncomingData.find("\r\n--"); + if (idxBoundary == AString::npos) + { + // Boundary string start not present, present as much data to the part callback as possible + if (m_IncomingData.size() > m_Boundary.size() + 8) + { + size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8; + m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport); + m_IncomingData.erase(0, BytesToReport); + } + return; + } + if (idxBoundary > 0) + { + m_Callbacks.OnPartData(m_IncomingData.data(), idxBoundary); + m_IncomingData.erase(0, idxBoundary); + } + idxBoundary = 4; + size_t LineEnd = m_IncomingData.find("\r\n", idxBoundary); + if (LineEnd == AString::npos) + { + // Not a complete line yet, present as much data to the part callback as possible + if (m_IncomingData.size() > m_Boundary.size() + 8) + { + size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8; + m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport); + m_IncomingData.erase(0, BytesToReport); + } + return; + } + if ( + (LineEnd - idxBoundary != m_Boundary.size()) && // Line length not equal to boundary + (LineEnd - idxBoundary != m_Boundary.size() + 2) // Line length not equal to boundary end + ) + { + // Got a line, but it's not a boundary, report it as data: + m_Callbacks.OnPartData(m_IncomingData.data(), LineEnd); + m_IncomingData.erase(0, LineEnd); + continue; + } + + if (strncmp(m_IncomingData.c_str() + idxBoundary, m_Boundary.c_str(), m_Boundary.size()) == 0) + { + // Boundary or BoundaryEnd found: + m_Callbacks.OnPartEnd(); + size_t idxSlash = idxBoundary + m_Boundary.size(); + if ((m_IncomingData[idxSlash] == '-') && (m_IncomingData[idxSlash + 1] == '-')) + { + // This was the last part + m_Callbacks.OnPartData(m_IncomingData.data() + idxSlash + 4, m_IncomingData.size() - idxSlash - 4); + m_IncomingData.clear(); + return; + } + m_Callbacks.OnPartStart(); + m_IncomingData.erase(0, LineEnd + 2); + + // Keep parsing for the headers that may have come with this data: + m_EnvelopeParser.Reset(); + continue; + } + + // It's a line, but not a boundary. It can be fully sent to the data receiver, since a boundary cannot cross lines + m_Callbacks.OnPartData(m_IncomingData.c_str(), LineEnd); + m_IncomingData.erase(0, LineEnd); + } // while (true) +} + + + + + +void cMultipartParser::OnHeaderLine(const AString & a_Key, const AString & a_Value) +{ + m_Callbacks.OnPartHeader(a_Key, a_Value); +} + + + + diff --git a/source/HTTPServer/MultipartParser.h b/source/HTTPServer/MultipartParser.h new file mode 100644 index 00000000..d853929e --- /dev/null +++ b/source/HTTPServer/MultipartParser.h @@ -0,0 +1,76 @@ + +// MultipartParser.h + +// Declares the cMultipartParser class that parses messages in "multipart/*" encoding into the separate parts + + + + + +#pragma once + +#include "EnvelopeParser.h" + + + + + +class cMultipartParser : + protected cEnvelopeParser::cCallbacks +{ +public: + class cCallbacks + { + public: + /// Called when a new part starts + virtual void OnPartStart(void) = 0; + + /// Called when a complete header line is received for a part + virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) = 0; + + /// Called when body for a part is received + virtual void OnPartData(const char * a_Data, int a_Size) = 0; + + /// Called when the current part ends + virtual void OnPartEnd(void) = 0; + } ; + + /// Creates the parser, expects to find the boundary in a_ContentType + cMultipartParser(const AString & a_ContentType, cCallbacks & a_Callbacks); + + /// Parses more incoming data + void Parse(const char * a_Data, int a_Size); + +protected: + /// The callbacks to call for various parsing events + cCallbacks & m_Callbacks; + + /// True if the data parsed so far is valid; if false, further parsing is skipped + bool m_IsValid; + + /// Parser for each part's envelope + cEnvelopeParser m_EnvelopeParser; + + /// Buffer for the incoming data until it is parsed + AString m_IncomingData; + + /// The boundary, excluding both the initial "--" and the terminating CRLF + AString m_Boundary; + + /// Set to true if some data for the current part has already been signalized to m_Callbacks. Used for proper CRLF inserting. + bool m_HasHadData; + + + /// Parse one line of incoming data. The CRLF has already been stripped from a_Data / a_Size + void ParseLine(const char * a_Data, int a_Size); + + /// Parse one line of incoming data in the headers section of a part. The CRLF has already been stripped from a_Data / a_Size + void ParseHeaderLine(const char * a_Data, int a_Size); + + // cEnvelopeParser overrides: + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; +} ; + + + + diff --git a/source/HTTPServer/NameValueParser.cpp b/source/HTTPServer/NameValueParser.cpp new file mode 100644 index 00000000..a27f07d1 --- /dev/null +++ b/source/HTTPServer/NameValueParser.cpp @@ -0,0 +1,412 @@ + +// NameValueParser.cpp + +// Implements the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap + +#include "Globals.h" +#include "NameValueParser.h" + + + + + + +// DEBUG: Self-test + +#if 0 + +class cNameValueParserTest +{ +public: + cNameValueParserTest(void) + { + const char Data[] = " Name1=Value1;Name2 = Value 2; Name3 =\"Value 3\"; Name4 =\'Value 4\'; Name5=\"Confusing; isn\'t it?\""; + + // Now try parsing char-by-char, to debug transitions across datachunk boundaries: + cNameValueParser Parser2; + for (int i = 0; i < sizeof(Data) - 1; i++) + { + Parser2.Parse(Data + i, 1); + } + Parser2.Finish(); + + // Parse as a single chunk of data: + cNameValueParser Parser(Data, sizeof(Data) - 1); + + // Use the debugger to inspect the Parser variable + + // Check that the two parsers have the same content: + for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr) + { + ASSERT(Parser2[itr->first] == itr->second); + } // for itr - Parser[] + + // Try parsing in 2-char chunks: + cNameValueParser Parser3; + for (int i = 0; i < sizeof(Data) - 2; i += 2) + { + Parser3.Parse(Data + i, 2); + } + if ((sizeof(Data) % 2) == 0) // There are even number of chars, including the NUL, so the data has an odd length. Parse one more char + { + Parser3.Parse(Data + sizeof(Data) - 2, 1); + } + Parser3.Finish(); + + // Check that the third parser has the same content: + for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr) + { + ASSERT(Parser3[itr->first] == itr->second); + } // for itr - Parser[] + + printf("cNameValueParserTest done"); + } +} g_Test; + +#endif + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cNameValueParser: + +cNameValueParser::cNameValueParser(bool a_AllowsKeyOnly) : + m_State(psKeySpace), + m_AllowsKeyOnly(a_AllowsKeyOnly) +{ +} + + + + + +cNameValueParser::cNameValueParser(const char * a_Data, int a_Size, bool a_AllowsKeyOnly) : + m_State(psKeySpace), + m_AllowsKeyOnly(a_AllowsKeyOnly) +{ + Parse(a_Data, a_Size); +} + + + + + +void cNameValueParser::Parse(const char * a_Data, int a_Size) +{ + ASSERT(m_State != psFinished); // Calling Parse() after Finish() is wrong! + + if ((m_State == psInvalid) || (m_State == psFinished)) + { + return; + } + int Last = 0; + for (int i = 0; i < a_Size;) + { + switch (m_State) + { + case psKeySpace: + { + // Skip whitespace until a non-whitespace is found, then start the key: + while ((i < a_Size) && (a_Data[i] <= ' ')) + { + i++; + } + if ((i < a_Size) && (a_Data[i] > ' ')) + { + m_State = psKey; + Last = i; + } + break; + } + + case psKey: + { + // Read the key until whitespace or an equal sign: + while (i < a_Size) + { + if (a_Data[i] == '=') + { + m_CurrentKey.append(a_Data + Last, i - Last); + i++; + Last = i; + m_State = psEqual; + break; + } + else if (a_Data[i] <= ' ') + { + m_CurrentKey.append(a_Data + Last, i - Last); + i++; + Last = i; + m_State = psEqualSpace; + break; + } + else if (a_Data[i] == ';') + { + if (!m_AllowsKeyOnly) + { + m_State = psInvalid; + return; + } + m_CurrentKey.append(a_Data + Last, i - Last); + i++; + Last = i; + (*this)[m_CurrentKey] = ""; + m_CurrentKey.clear(); + m_State = psKeySpace; + break; + } + else if ((a_Data[i] == '\"') || (a_Data[i] == '\'')) + { + m_State = psInvalid; + return; + } + i++; + } // while (i < a_Size) + if (i == a_Size) + { + // Still the key, ran out of data to parse, store the part of the key parsed so far: + m_CurrentKey.append(a_Data + Last, a_Size - Last); + return; + } + break; + } + + case psEqualSpace: + { + // The space before the expected equal sign; the current key is already assigned + while (i < a_Size) + { + if (a_Data[i] == '=') + { + m_State = psEqual; + i++; + Last = i; + break; + } + else if (a_Data[i] == ';') + { + // Key-only + if (!m_AllowsKeyOnly) + { + m_State = psInvalid; + return; + } + i++; + Last = i; + (*this)[m_CurrentKey] = ""; + m_CurrentKey.clear(); + m_State = psKeySpace; + break; + } + else if (a_Data[i] > ' ') + { + m_State = psInvalid; + return; + } + i++; + } // while (i < a_Size) + break; + } // case psEqualSpace + + case psEqual: + { + // just parsed the equal-sign + while (i < a_Size) + { + if (a_Data[i] == ';') + { + if (!m_AllowsKeyOnly) + { + m_State = psInvalid; + return; + } + i++; + Last = i; + (*this)[m_CurrentKey] = ""; + m_CurrentKey.clear(); + m_State = psKeySpace; + break; + } + else if (a_Data[i] == '\"') + { + i++; + Last = i; + m_State = psValueInDQuotes; + break; + } + else if (a_Data[i] == '\'') + { + i++; + Last = i; + m_State = psValueInSQuotes; + break; + } + else + { + m_CurrentValue.push_back(a_Data[i]); + i++; + Last = i; + m_State = psValueRaw; + break; + } + i++; + } // while (i < a_Size) + break; + } // case psEqual + + case psValueInDQuotes: + { + while (i < a_Size) + { + if (a_Data[i] == '\"') + { + m_CurrentValue.append(a_Data + Last, i - Last); + (*this)[m_CurrentKey] = m_CurrentValue; + m_CurrentKey.clear(); + m_CurrentValue.clear(); + m_State = psAfterValue; + i++; + Last = i; + break; + } + i++; + } // while (i < a_Size) + if (i == a_Size) + { + m_CurrentValue.append(a_Data + Last, a_Size - Last); + } + break; + } // case psValueInDQuotes + + case psValueInSQuotes: + { + while (i < a_Size) + { + if (a_Data[i] == '\'') + { + m_CurrentValue.append(a_Data + Last, i - Last); + (*this)[m_CurrentKey] = m_CurrentValue; + m_CurrentKey.clear(); + m_CurrentValue.clear(); + m_State = psAfterValue; + i++; + Last = i; + break; + } + i++; + } // while (i < a_Size) + if (i == a_Size) + { + m_CurrentValue.append(a_Data + Last, a_Size - Last); + } + break; + } // case psValueInSQuotes + + case psValueRaw: + { + while (i < a_Size) + { + if (a_Data[i] == ';') + { + m_CurrentValue.append(a_Data + Last, i - Last); + (*this)[m_CurrentKey] = m_CurrentValue; + m_CurrentKey.clear(); + m_CurrentValue.clear(); + m_State = psKeySpace; + i++; + Last = i; + break; + } + i++; + } + if (i == a_Size) + { + m_CurrentValue.append(a_Data + Last, a_Size - Last); + } + break; + } // case psValueRaw + + case psAfterValue: + { + // Between the closing DQuote or SQuote and the terminating semicolon + while (i < a_Size) + { + if (a_Data[i] == ';') + { + m_State = psKeySpace; + i++; + Last = i; + break; + } + else if (a_Data[i] < ' ') + { + i++; + continue; + } + m_State = psInvalid; + return; + } // while (i < a_Size) + break; + } + } // switch (m_State) + } // for i - a_Data[] +} + + + + + +bool cNameValueParser::Finish(void) +{ + switch (m_State) + { + case psInvalid: + { + return false; + } + case psFinished: + { + return true; + } + case psKey: + case psEqualSpace: + case psEqual: + { + if ((m_AllowsKeyOnly) && !m_CurrentKey.empty()) + { + (*this)[m_CurrentKey] = ""; + m_State = psFinished; + return true; + } + m_State = psInvalid; + return false; + } + case psValueRaw: + { + (*this)[m_CurrentKey] = m_CurrentValue; + m_State = psFinished; + return true; + } + case psValueInDQuotes: + case psValueInSQuotes: + { + // Missing the terminating quotes, this is an error + m_State = psInvalid; + return false; + } + case psKeySpace: + case psAfterValue: + { + m_State = psFinished; + return true; + } + } + ASSERT(!"Unhandled parser state!"); + return false; +} + + + + diff --git a/source/HTTPServer/NameValueParser.h b/source/HTTPServer/NameValueParser.h new file mode 100644 index 00000000..07dc0b94 --- /dev/null +++ b/source/HTTPServer/NameValueParser.h @@ -0,0 +1,70 @@ + +// NameValueParser.h + +// Declares the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap + + + + + +#pragma once + + + + + +class cNameValueParser : + public std::map +{ +public: + /// Creates an empty parser + cNameValueParser(bool a_AllowsKeyOnly = true); + + /// Creates an empty parser, then parses the data given. Doesn't call Finish(), so more data can be parsed later + cNameValueParser(const char * a_Data, int a_Size, bool a_AllowsKeyOnly = true); + + /// Parses the data given + void Parse(const char * a_Data, int a_Size); + + /// Notifies the parser that no more data will be coming. Returns true if the parser state is valid + bool Finish(void); + + /// Returns true if the data parsed so far was valid + bool IsValid(void) const { return (m_State != psInvalid); } + + /// Returns true if the parser expects no more data + bool IsFinished(void) const { return ((m_State == psInvalid) || (m_State == psFinished)); } + +protected: + enum eState + { + psKeySpace, ///< Parsing the space in front of the next key + psKey, ///< Currently adding more chars to the key in m_CurrentKey + psEqualSpace, ///< Space after m_CurrentKey + psEqual, ///< Just parsed the = sign after a name + psValueInSQuotes, ///< Just parsed a Single-quote sign after the Equal sign + psValueInDQuotes, ///< Just parsed a Double-quote sign after the Equal sign + psValueRaw, ///< Just parsed a raw value without a quote + psAfterValue, ///< Just finished parsing the value, waiting for semicolon or data end + psInvalid, ///< The parser has encountered an invalid input; further parsing is skipped + psFinished, ///< The parser has already been instructed to finish and doesn't expect any more data + } ; + + /// The current state of the parser + eState m_State; + + /// If true, the parser will accept keys without an equal sign and the value + bool m_AllowsKeyOnly; + + /// Buffer for the current Key + AString m_CurrentKey; + + /// Buffer for the current Value; + AString m_CurrentValue; + + +} ; + + + + diff --git a/source/OSSupport/ListenThread.cpp b/source/OSSupport/ListenThread.cpp index 0890aabc..ba319876 100644 --- a/source/OSSupport/ListenThread.cpp +++ b/source/OSSupport/ListenThread.cpp @@ -224,7 +224,10 @@ void cListenThread::Execute(void) if (itr->IsValid() && FD_ISSET(itr->GetSocket(), &fdRead)) { cSocket Client = (m_Family == cSocket::IPv4) ? itr->AcceptIPv4() : itr->AcceptIPv6(); - m_Callback.OnConnectionAccepted(Client); + if (Client.IsValid()) + { + m_Callback.OnConnectionAccepted(Client); + } } } // for itr - m_Sockets[] } // while (!m_ShouldTerminate) diff --git a/source/Root.cpp b/source/Root.cpp index c2a402b7..26e6d347 100644 --- a/source/Root.cpp +++ b/source/Root.cpp @@ -134,17 +134,9 @@ void cRoot::Start(void) } IniFile.WriteFile(); - cIniFile WebIniFile("webadmin.ini"); - if (!WebIniFile.ReadFile()) - { - LOGWARNING("webadmin.ini inaccessible, wabadmin is disabled"); - } - - if (WebIniFile.GetValueB("WebAdmin", "Enabled", false)) - { - LOGD("Creating WebAdmin..."); - m_WebAdmin = new cWebAdmin(8080); - } + LOG("Initialising WebAdmin..."); + m_WebAdmin = new cWebAdmin(); + m_WebAdmin->Init(); LOGD("Loading settings..."); m_GroupManager = new cGroupManager(); @@ -173,6 +165,9 @@ void cRoot::Start(void) LOGD("Finalising startup..."); m_Server->Start(); + + LOG("Starting WebAdmin..."); + m_WebAdmin->Start(); #if !defined(ANDROID_NDK) LOGD("Starting InputThread..."); diff --git a/source/Root.h b/source/Root.h index 194b1cbb..e5197ce2 100644 --- a/source/Root.h +++ b/source/Root.h @@ -2,6 +2,7 @@ #pragma once #include "Authenticator.h" +#include "HTTPServer/HTTPServer.h" @@ -141,6 +142,7 @@ private: cWebAdmin * m_WebAdmin; cPluginManager * m_PluginManager; cAuthenticator m_Authenticator; + cHTTPServer m_HTTPServer; cMCLogger * m_Log; diff --git a/source/StringUtils.cpp b/source/StringUtils.cpp index cb91a4da..d52b1323 100644 --- a/source/StringUtils.cpp +++ b/source/StringUtils.cpp @@ -196,6 +196,23 @@ AString & StrToUpper(AString & s) +AString & StrToLower(AString & s) +{ + AString::iterator i = s.begin(); + AString::iterator end = s.end(); + + while (i != end) + { + *i = (char)tolower(*i); + ++i; + } + return s; +} + + + + + int NoCaseCompare(const AString & s1, const AString & s2) { #ifdef _MSC_VER @@ -658,3 +675,141 @@ AString StripColorCodes(const AString & a_Message) + +AString URLDecode(const AString & a_String) +{ + AString res; + size_t len = a_String.length(); + res.reserve(len); + for (size_t i = 0; i < len; i++) + { + char ch = a_String[i]; + if ((ch != '%') || (i > len - 3)) + { + res.push_back(ch); + continue; + } + // Decode the hex value: + char hi = a_String[i + 1], lo = a_String[i + 2]; + if ((hi >= '0') && (hi <= '9')) + { + hi = hi - '0'; + } + else if ((hi >= 'a') && (hi <= 'f')) + { + hi = hi - 'a' + 10; + } + else if ((hi >= 'A') && (hi <= 'F')) + { + hi = hi - 'F' + 10; + } + else + { + res.push_back(ch); + continue; + } + if ((lo >= '0') && (lo <= '9')) + { + lo = lo - '0'; + } + else if ((lo >= 'a') && (lo <= 'f')) + { + lo = lo - 'a' + 10; + } + else if ((lo >= 'A') && (lo <= 'F')) + { + lo = lo - 'A' + 10; + } + else + { + res.push_back(ch); + continue; + } + res.push_back((hi << 4) | lo); + i += 2; + } // for i - a_String[] + return res; +} + + + + + +AString ReplaceAllCharOccurrences(const AString & a_String, char a_From, char a_To) +{ + AString res(a_String); + std::replace(res.begin(), res.end(), a_From, a_To); + return res; +} + + + + + +/// Converts one Hex character in a Base64 encoding into the data value +static inline int UnBase64(char c) +{ + if (c >='A' && c <= 'Z') + { + return c - 'A'; + } + if (c >='a' && c <= 'z') + { + return c - 'a' + 26; + } + if (c >= '0' && c <= '9') + { + return c - '0' + 52; + } + if (c == '+') + { + return 62; + } + if (c == '/') + { + return 63; + } + if (c == '=') + { + return -1; + } + return -2; +} + + + + + +AString Base64Decode(const AString & a_Base64String) +{ + AString res; + size_t i, len = a_Base64String.size(); + int o, c; + res.resize((len * 4) / 3 + 5, 0); // Approximate the upper bound on the result length + for (o = 0, i = 0; i < len; i++) + { + c = UnBase64(a_Base64String[i]); + if (c >= 0) + { + switch (o & 7) + { + case 0: res[o >> 3] |= (c << 2); break; + case 6: res[o >> 3] |= (c >> 4); res[(o >> 3) + 1] |= (c << 4); break; + case 4: res[o >> 3] |= (c >> 2); res[(o >> 3) + 1] |= (c << 6); break; + case 2: res[o >> 3] |= c; break; + } + o += 6; + } + if (c == -1) + { + // Error while decoding, invalid input. Return as much as we've decoded: + res.resize(o >> 3); + return res; + } + } + res.resize(o >> 3); + return res;} + + + + diff --git a/source/StringUtils.h b/source/StringUtils.h index 211799e9..ec9ba96c 100644 --- a/source/StringUtils.h +++ b/source/StringUtils.h @@ -45,6 +45,9 @@ extern AString TrimString(const AString & str); // tolua_export /// In-place string conversion to uppercase; returns the same string extern AString & StrToUpper(AString & s); +/// In-place string conversion to lowercase; returns the same string +extern AString & StrToLower(AString & s); + /// Case-insensitive string comparison; returns 0 if the strings are the same extern int NoCaseCompare(const AString & s1, const AString & s2); // tolua_export @@ -72,6 +75,15 @@ extern AString EscapeString(const AString & a_Message); // tolua_export /// Removes all control codes used by MC for colors and styles extern AString StripColorCodes(const AString & a_Message); // tolua_export +/// URL-Decodes the given string, replacing all "%HH" into the correct characters. Invalid % sequences are left intact +extern AString URLDecode(const AString & a_String); // Cannot export to Lua automatically - would generated an extra return value + +/// Replaces all occurrences of char a_From inside a_String with char a_To. +extern AString ReplaceAllCharOccurrences(const AString & a_String, char a_From, char a_To); // Needn't export to Lua, since Lua doesn't have chars anyway + +/// Decodes a Base64-encoded string into the raw data +extern AString Base64Decode(const AString & a_Base64String); + // If you have any other string helper functions, declare them here diff --git a/source/WebAdmin.cpp b/source/WebAdmin.cpp index ef62961a..c917ec65 100644 --- a/source/WebAdmin.cpp +++ b/source/WebAdmin.cpp @@ -14,7 +14,8 @@ #include "Server.h" #include "Root.h" -#include "../iniFile/iniFile.h" +#include "HTTPServer/HTTPMessage.h" +#include "HTTPServer/HTTPConnection.h" #ifdef _WIN32 #include @@ -49,37 +50,11 @@ public: -cWebAdmin * WebAdmin = NULL; - - - - - -cWebAdmin::cWebAdmin( int a_Port /* = 8080 */ ) : - m_Port(a_Port), - m_bConnected(false), - m_TemplateScript("") +cWebAdmin::cWebAdmin(void) : + m_IsInitialized(false), + m_TemplateScript(""), + m_IniFile("webadmin.ini") { - WebAdmin = this; - m_Event = new cEvent(); - Init( m_Port ); -} - - - - - -cWebAdmin::~cWebAdmin() -{ - - WebAdmin = 0; - m_WebServer->Stop(); - - delete m_WebServer; - delete m_IniFile; - - m_Event->Wait(); - delete m_Event; } @@ -105,189 +80,36 @@ void cWebAdmin::RemovePlugin( cWebPlugin * a_Plugin ) -void cWebAdmin::Request_Handler(webserver::http_request* r) +bool cWebAdmin::Init(void) { - if( WebAdmin == 0 ) return; - LOG("Path: %s", r->path_.c_str() ); - - if (r->path_ == "/") + if (!m_IniFile.ReadFile()) { - r->answer_ += "

MCServer WebAdmin

"; - r->answer_ += "
"; - r->answer_ += "
"; - r->answer_ += ""; - r->answer_ += "
"; - r->answer_ += "
"; - return; - } - - if (r->path_.empty() || r->path_[0] != '/') - { - r->answer_ += "

Bad request

"; - r->answer_ += "

"; - r->answer_ = r->path_; // TODO: Shouldn't we sanitize this? Possible security issue. - r->answer_ += "

"; - return; + return false; } - AStringVector Split = StringSplit(r->path_.substr(1), "/"); - - if (Split.empty() || (Split[0] != "webadmin" && Split[0] != "~webadmin")) - { - r->answer_ += "

Bad request

"; - return; - } + AString PortsIPv4 = m_IniFile.GetValue("WebAdmin", "Port", "8080"); + AString PortsIPv6 = m_IniFile.GetValue("WebAdmin", "PortsIPv6", ""); - if (!r->authentication_given_) + if (!m_HTTPServer.Initialize(PortsIPv4, PortsIPv6)) { - r->answer_ += "no auth"; - r->auth_realm_ = "MCServer WebAdmin"; - } - - bool bDontShowTemplate = false; - if (Split[0] == "~webadmin") - { - bDontShowTemplate = true; - } - - AString UserPassword = WebAdmin->m_IniFile->GetValue( "User:"+r->username_, "Password", ""); - if ((UserPassword != "") && (r->password_ == UserPassword)) - { - AString Template; - - HTTPTemplateRequest TemplateRequest; - TemplateRequest.Request.Username = r->username_; - TemplateRequest.Request.Method = r->method_; - TemplateRequest.Request.Params = r->params_; - TemplateRequest.Request.PostParams = r->params_post_; - TemplateRequest.Request.Path = r->path_.substr(1); - - for( unsigned int i = 0; i < r->multipart_formdata_.size(); ++i ) - { - webserver::formdata& fd = r->multipart_formdata_[i]; - - HTTPFormData HTTPfd;//( fd.value_ ); - HTTPfd.Value = fd.value_; - HTTPfd.Type = fd.content_type_; - HTTPfd.Name = fd.name_; - LOGINFO("Form data name: %s", fd.name_.c_str() ); - TemplateRequest.Request.FormData[ fd.name_ ] = HTTPfd; - } - - // Try to get the template from the Lua template script - bool bLuaTemplateSuccessful = false; - if (!bDontShowTemplate) - { - bLuaTemplateSuccessful = WebAdmin->m_TemplateScript.Call("ShowPage", WebAdmin, &TemplateRequest, cLuaState::Return, Template); - } - - if (!bLuaTemplateSuccessful) - { - AString BaseURL = WebAdmin->GetBaseURL(Split); - AString Menu; - Template = bDontShowTemplate ? "{CONTENT}" : WebAdmin->GetTemplate(); - AString FoundPlugin; - - for (PluginList::iterator itr = WebAdmin->m_Plugins.begin(); itr != WebAdmin->m_Plugins.end(); ++itr) - { - cWebPlugin* WebPlugin = *itr; - std::list< std::pair > NameList = WebPlugin->GetTabNames(); - for( std::list< std::pair >::iterator Names = NameList.begin(); Names != NameList.end(); ++Names ) - { - Menu += "
  • " + (*Names).first + "
  • "; - } - } - - sWebAdminPage Page = WebAdmin->GetPage(TemplateRequest.Request); - AString Content = Page.Content; - FoundPlugin = Page.PluginName; - if (!Page.TabName.empty()) - FoundPlugin += " - " + Page.TabName; - - if( FoundPlugin.empty() ) // Default page - { - Content.clear(); - FoundPlugin = "Current Game"; - Content += "

    Server Name:

    "; - Content += "

    " + AString( cRoot::Get()->GetServer()->GetServerID() ) + "

    "; - - Content += "

    Plugins:

      "; - cPluginManager* PM = cRoot::Get()->GetPluginManager(); - if( PM ) - { - const cPluginManager::PluginMap & List = PM->GetAllPlugins(); - for( cPluginManager::PluginMap::const_iterator itr = List.begin(); itr != List.end(); ++itr ) - { - if( itr->second == NULL ) continue; - AString VersionNum; - AppendPrintf(Content, "
    • %s V.%i
    • ", itr->second->GetName().c_str(), itr->second->GetVersion()); - } - } - Content += "
    "; - Content += "

    Players:

      "; - - cPlayerAccum PlayerAccum; - cWorld * World = cRoot::Get()->GetDefaultWorld(); // TODO - Create a list of worlds and players - if( World != NULL ) - { - World->ForEachPlayer(PlayerAccum); - Content.append(PlayerAccum.m_Contents); - } - Content += "

    "; - } - - - - if (!bDontShowTemplate && (Split.size() > 1)) - { - Content += "\n

    Go back

    "; - } - - int MemUsageKiB = GetMemoryUsage(); - if (MemUsageKiB > 0) - { - ReplaceString(Template, "{MEM}", Printf("%.02f", (double)MemUsageKiB / 1024)); - ReplaceString(Template, "{MEMKIB}", Printf("%d", MemUsageKiB)); - } - else - { - ReplaceString(Template, "{MEM}", "unknown"); - ReplaceString(Template, "{MEMKIB}", "unknown"); - } - ReplaceString(Template, "{USERNAME}", r->username_); - ReplaceString(Template, "{MENU}", Menu); - ReplaceString(Template, "{PLUGIN_NAME}", FoundPlugin); - ReplaceString(Template, "{CONTENT}", Content); - ReplaceString(Template, "{TITLE}", "MCServer"); - - AString NumChunks; - Printf(NumChunks, "%d", cRoot::Get()->GetTotalChunkCount()); - ReplaceString(Template, "{NUMCHUNKS}", NumChunks); - } - - r->answer_ = Template; - } - else - { - r->answer_ += "Wrong username/password"; - r->auth_realm_ = "MCServer WebAdmin"; + return false; } + m_IsInitialized = true; + return true; } -bool cWebAdmin::Init(int a_Port) +bool cWebAdmin::Start(void) { - m_Port = a_Port; - - m_IniFile = new cIniFile("webadmin.ini"); - if (m_IniFile->ReadFile()) + if (!m_IsInitialized) { - m_Port = m_IniFile->GetValueI("WebAdmin", "Port", 8080); + // Not initialized + return false; } - + // Initialize the WebAdmin template script and load the file m_TemplateScript.Create(); if (!m_TemplateScript.LoadFile(FILE_IO_PREFIX "webadmin/template.lua")) @@ -296,46 +118,7 @@ bool cWebAdmin::Init(int a_Port) m_TemplateScript.Close(); } - - LOGINFO("Starting WebAdmin on port %i", m_Port); - -#ifdef _WIN32 - HANDLE hThread = CreateThread( - NULL, // default security - 0, // default stack size - ListenThread, // name of the thread function - this, // thread parameters - 0, // default startup flags - NULL); - CloseHandle( hThread ); // Just close the handle immediately -#else - pthread_t LstnThread; - pthread_create( &LstnThread, 0, ListenThread, this); -#endif - - return true; -} - - - - - -#ifdef _WIN32 -DWORD WINAPI cWebAdmin::ListenThread(LPVOID lpParam) -#else -void *cWebAdmin::ListenThread( void *lpParam ) -#endif -{ - cWebAdmin* self = (cWebAdmin*)lpParam; - - self->m_WebServer = new webserver(self->m_Port, Request_Handler ); - if (!self->m_WebServer->Begin()) - { - LOGWARN("WebServer failed to start! WebAdmin is disabled"); - } - - self->m_Event->Set(); - return 0; + return m_HTTPServer.Start(*this); } @@ -364,7 +147,156 @@ AString cWebAdmin::GetTemplate() -sWebAdminPage cWebAdmin::GetPage(const HTTPRequest& a_Request) +void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +{ + if (!a_Request.HasAuth()) + { + a_Connection.SendNeedAuth("MCServer WebAdmin"); + return; + } + + // Check auth: + AString UserPassword = m_IniFile.GetValue("User:" + a_Request.GetAuthUsername(), "Password", ""); + if ((UserPassword == "") || (a_Request.GetAuthPassword() != UserPassword)) + { + a_Connection.SendNeedAuth("MCServer WebAdmin - bad username or password"); + return; + } + + // Check if the contents should be wrapped in the template: + AString URL = a_Request.GetBareURL(); + ASSERT(URL.length() > 0); + bool ShouldWrapInTemplate = ((URL.length() > 1) && (URL[1] != '~')); + + // Retrieve the request data: + cWebadminRequestData * Data = (cWebadminRequestData *)(a_Request.GetUserData()); + if (Data == NULL) + { + a_Connection.SendStatusAndReason(500, "Bad UserData"); + return; + } + + // Wrap it all up for the Lua call: + AString Template; + HTTPTemplateRequest TemplateRequest; + TemplateRequest.Request.Username = a_Request.GetAuthUsername(); + TemplateRequest.Request.Method = a_Request.GetMethod(); + TemplateRequest.Request.Path = URL.substr(1); + + if (Data->m_Form.Finish()) + { + for (cHTTPFormParser::const_iterator itr = Data->m_Form.begin(), end = Data->m_Form.end(); itr != end; ++itr) + { + HTTPFormData HTTPfd; + HTTPfd.Value = itr->second; + HTTPfd.Type = ""; + HTTPfd.Name = itr->first; + TemplateRequest.Request.FormData[itr->first] = HTTPfd; + TemplateRequest.Request.PostParams[itr->first] = itr->second; + TemplateRequest.Request.Params[itr->first] = itr->second; + } // for itr - Data->m_Form[] + } + + // Try to get the template from the Lua template script + if (ShouldWrapInTemplate) + { + if (m_TemplateScript.Call("ShowPage", this, &TemplateRequest, cLuaState::Return, Template)) + { + cHTTPResponse Resp; + Resp.SetContentType("text/html"); + a_Connection.Send(Resp); + a_Connection.Send(Template.c_str(), Template.length()); + return; + } + a_Connection.SendStatusAndReason(500, "m_TemplateScript failed"); + return; + } + + AString BaseURL = GetBaseURL(URL); + AString Menu; + Template = "{CONTENT}"; + AString FoundPlugin; + + for (PluginList::iterator itr = m_Plugins.begin(); itr != m_Plugins.end(); ++itr) + { + cWebPlugin * WebPlugin = *itr; + std::list< std::pair > NameList = WebPlugin->GetTabNames(); + for (std::list< std::pair >::iterator Names = NameList.begin(); Names != NameList.end(); ++Names) + { + Menu += "
  • " + (*Names).first + "
  • "; + } + } + + sWebAdminPage Page = GetPage(TemplateRequest.Request); + AString Content = Page.Content; + FoundPlugin = Page.PluginName; + if (!Page.TabName.empty()) + { + FoundPlugin += " - " + Page.TabName; + } + + if (FoundPlugin.empty()) // Default page + { + Content = GetDefaultPage(); + } + + if (ShouldWrapInTemplate && (URL.size() > 1)) + { + Content += "\n

    Go back

    "; + } + + int MemUsageKiB = GetMemoryUsage(); + if (MemUsageKiB > 0) + { + ReplaceString(Template, "{MEM}", Printf("%.02f", (double)MemUsageKiB / 1024)); + ReplaceString(Template, "{MEMKIB}", Printf("%d", MemUsageKiB)); + } + else + { + ReplaceString(Template, "{MEM}", "unknown"); + ReplaceString(Template, "{MEMKIB}", "unknown"); + } + ReplaceString(Template, "{USERNAME}", a_Request.GetAuthUsername()); + ReplaceString(Template, "{MENU}", Menu); + ReplaceString(Template, "{PLUGIN_NAME}", FoundPlugin); + ReplaceString(Template, "{CONTENT}", Content); + ReplaceString(Template, "{TITLE}", "MCServer"); + + AString NumChunks; + Printf(NumChunks, "%d", cRoot::Get()->GetTotalChunkCount()); + ReplaceString(Template, "{NUMCHUNKS}", NumChunks); + + cHTTPResponse Resp; + Resp.SetContentType("text/html"); + a_Connection.Send(Resp); + a_Connection.Send(Template.c_str(), Template.length()); +} + + + + + +void cWebAdmin::HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +{ + static const char LoginForm[] = \ + "

    MCServer WebAdmin

    " \ + "
    " \ + "
    " \ + "" \ + "
    " \ + "
    "; + cHTTPResponse Resp; + Resp.SetContentType("text/html"); + a_Connection.Send(Resp); + a_Connection.Send(LoginForm, sizeof(LoginForm) - 1); + a_Connection.FinishResponse(); +} + + + + + +sWebAdminPage cWebAdmin::GetPage(const HTTPRequest & a_Request) { sWebAdminPage Page; AStringVector Split = StringSplit(a_Request.Path, "/"); @@ -373,7 +305,7 @@ sWebAdminPage cWebAdmin::GetPage(const HTTPRequest& a_Request) AString FoundPlugin; if (Split.size() > 1) { - for (PluginList::iterator itr = WebAdmin->m_Plugins.begin(); itr != WebAdmin->m_Plugins.end(); ++itr) + for (PluginList::iterator itr = m_Plugins.begin(); itr != m_Plugins.end(); ++itr) { if ((*itr)->GetWebTitle() == Split[1]) { @@ -396,6 +328,41 @@ sWebAdminPage cWebAdmin::GetPage(const HTTPRequest& a_Request) +AString cWebAdmin::GetDefaultPage(void) +{ + AString Content; + Content += "

    Server Name:

    "; + Content += "

    " + AString( cRoot::Get()->GetServer()->GetServerID() ) + "

    "; + + Content += "

    Plugins:

      "; + cPluginManager * PM = cPluginManager::Get(); + const cPluginManager::PluginMap & List = PM->GetAllPlugins(); + for (cPluginManager::PluginMap::const_iterator itr = List.begin(); itr != List.end(); ++itr) + { + if (itr->second == NULL) + { + continue; + } + AString VersionNum; + AppendPrintf(Content, "
    • %s V.%i
    • ", itr->second->GetName().c_str(), itr->second->GetVersion()); + } + Content += "
    "; + Content += "

    Players:

      "; + + cPlayerAccum PlayerAccum; + cWorld * World = cRoot::Get()->GetDefaultWorld(); // TODO - Create a list of worlds and players + if( World != NULL ) + { + World->ForEachPlayer(PlayerAccum); + Content.append(PlayerAccum.m_Contents); + } + Content += "

    "; + return Content; +} + + + + AString cWebAdmin::GetBaseURL( const AString& a_URL ) { return GetBaseURL(StringSplit(a_URL, "/")); @@ -474,3 +441,81 @@ int cWebAdmin::GetMemoryUsage(void) + +void cWebAdmin::OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +{ + const AString & URL = a_Request.GetURL(); + if ( + (strncmp(URL.c_str(), "/webadmin", 9) == 0) || + (strncmp(URL.c_str(), "/~webadmin", 10) == 0) + ) + { + a_Request.SetUserData(new cWebadminRequestData(a_Request)); + return; + } + if (URL == "/") + { + // The root needs no body handler and is fully handled in the OnRequestFinished() call + return; + } + // TODO: Handle other requests +} + + + + + +void cWebAdmin::OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) +{ + cRequestData * Data = (cRequestData *)(a_Request.GetUserData()); + if (Data == NULL) + { + return; + } + Data->OnBody(a_Data, a_Size); +} + + + + + +void cWebAdmin::OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +{ + const AString & URL = a_Request.GetURL(); + if ( + (strncmp(URL.c_str(), "/webadmin", 9) == 0) || + (strncmp(URL.c_str(), "/~webadmin", 10) == 0) + ) + { + HandleWebadminRequest(a_Connection, a_Request); + } + else if (URL == "/") + { + // The root needs no body handler and is fully handled in the OnRequestFinished() call + HandleRootRequest(a_Connection, a_Request); + } + else + { + // TODO: Handle other requests + } + + // Delete any request data assigned to the request: + cRequestData * Data = (cRequestData *)(a_Request.GetUserData()); + delete Data; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cWebAdmin::cWebadminRequestData + +void cWebAdmin::cWebadminRequestData::OnBody(const char * a_Data, int a_Size) +{ + m_Form.Parse(a_Data, a_Size); +} + + + + diff --git a/source/WebAdmin.h b/source/WebAdmin.h index 7b710bd3..16b5dd4d 100644 --- a/source/WebAdmin.h +++ b/source/WebAdmin.h @@ -1,8 +1,26 @@ + +// WebAdmin.h + +// Declares the cWebAdmin class representing the admin interface over http protocol, and related services (API) + #pragma once -#include "../WebServer/WebServer.h" #include "OSSupport/Socket.h" #include "LuaState.h" +#include "../iniFile/iniFile.h" +#include "HTTPServer/HTTPServer.h" +#include "HTTPServer/HTTPFormParser.h" + + + + + +// Disable MSVC warnings: +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4355) // 'this' : used in base member initializer list +#endif + @@ -10,7 +28,6 @@ // fwd: class cStringMap; class cEvent; -class cIniFile; class cWebPlugin; @@ -73,7 +90,8 @@ struct sWebAdminPage // tolua_begin -class cWebAdmin +class cWebAdmin : + public cHTTPServer::cCallbacks { public: // tolua_end @@ -81,60 +99,109 @@ public: typedef std::list< cWebPlugin* > PluginList; - cWebAdmin( int a_Port = 8080 ); - ~cWebAdmin(); + cWebAdmin(void); - bool Init( int a_Port ); + /// Initializes the object. Returns true if successfully initialized and ready to start + bool Init(void); + + /// Starts the HTTP server taking care of the admin. Returns true if successful + bool Start(void); - void AddPlugin( cWebPlugin* a_Plugin ); - void RemovePlugin( cWebPlugin* a_Plugin ); + void AddPlugin( cWebPlugin* a_Plugin ); + void RemovePlugin( cWebPlugin* a_Plugin ); // TODO: Convert this to the auto-locking callback mechanism used for looping players in worlds and such PluginList GetPlugins() const { return m_Plugins; } // >> EXPORTED IN MANUALBINDINGS << - static void Request_Handler(webserver::http_request* r); - // tolua_begin /// Returns the amount of currently used memory, in KiB, or -1 if it cannot be queried static int GetMemoryUsage(void); - int GetPort() { return m_Port; } - sWebAdminPage GetPage(const HTTPRequest& a_Request); + + /// Returns the contents of the default page - the list of plugins and players + AString GetDefaultPage(void); + AString GetBaseURL(const AString& a_URL); // tolua_end AString GetBaseURL(const AStringVector& a_URLSplit); - -private: - int m_Port; +protected: + /// Common base class for request body data handlers + class cRequestData + { + public: + virtual ~cRequestData() {} // Force a virtual destructor in all descendants + + /// Called when a new chunk of body data is received + virtual void OnBody(const char * a_Data, int a_Size) = 0; + } ; + + /// The body handler for requests in the "/webadmin" and "/~webadmin" paths + class cWebadminRequestData : + public cRequestData, + public cHTTPFormParser::cCallbacks + { + public: + cHTTPFormParser m_Form; + + + cWebadminRequestData(cHTTPRequest & a_Request) : + m_Form(a_Request, *this) + { + } + + // cRequestData overrides: + virtual void OnBody(const char * a_Data, int a_Size) override; - bool m_bConnected; - cSocket m_ListenSocket; + // cHTTPFormParser::cCallbacks overrides. Files are ignored: + virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) override {} + virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, int a_Size) override {} + virtual void OnFileEnd(cHTTPFormParser & a_Parser) override {} + } ; + + + /// Set to true if Init() succeeds and the webadmin isn't to be disabled + bool m_IsInitialized; - cIniFile * m_IniFile; + /// The webadmin.ini file, used for the settings and allowed logins + cIniFile m_IniFile; + PluginList m_Plugins; - cEvent * m_Event; - - webserver * m_WebServer; - /// The Lua template script to provide templates: cLuaState m_TemplateScript; + + /// The HTTP server which provides the underlying HTTP parsing, serialization and events + cHTTPServer m_HTTPServer; - #ifdef _WIN32 - static DWORD WINAPI ListenThread(LPVOID lpParam); - #else - static void * ListenThread(void * lpParam); - #endif - - AString GetTemplate(); + AString GetTemplate(void); + + /// Handles requests coming to the "/webadmin" or "/~webadmin" URLs + void HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request); + + /// Handles requests for the root page + void HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request); + + // cHTTPServer::cCallbacks overrides: + virtual void OnRequestBegun (cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override; + virtual void OnRequestBody (cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) override; + virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override; } ; // tolua_export + +// Revert MSVC warnings back to orignal state: +#if defined(_MSC_VER) + #pragma warning(pop) +#endif + + + +