From 11e0c73ffd23a506c68ae351641a7ca74085ca81 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Tue, 24 Sep 2013 20:52:37 +0200 Subject: Implemented basic HTTP message header parsing. --- VC2008/MCServer.vcproj | 8 ++ source/Root.cpp | 6 +- source/Root.h | 2 + source/WebServer.cpp | 341 +++++++++++++++++++++++++++++++++++++++++++++++++ source/WebServer.h | 122 ++++++++++++++++++ 5 files changed, 475 insertions(+), 4 deletions(-) create mode 100644 source/WebServer.cpp create mode 100644 source/WebServer.h diff --git a/VC2008/MCServer.vcproj b/VC2008/MCServer.vcproj index c1c2593bf..7fc10fb32 100644 --- a/VC2008/MCServer.vcproj +++ b/VC2008/MCServer.vcproj @@ -900,6 +900,14 @@ RelativePath="..\source\WebAdmin.h" > + + + + diff --git a/source/Root.cpp b/source/Root.cpp index 3933535f1..823bd8e13 100644 --- a/source/Root.cpp +++ b/source/Root.cpp @@ -135,11 +135,9 @@ void cRoot::Start(void) { LOGWARNING("webadmin.ini inaccessible, wabadmin is disabled"); } - - if (WebIniFile.GetValueB("WebAdmin", "Enabled", false)) + else { - LOG("Creating WebAdmin..."); - m_WebAdmin = new cWebAdmin(8080); + m_WebServer.Initialize(WebIniFile); } LOG("Loading settings..."); diff --git a/source/Root.h b/source/Root.h index 194b1cbb5..48a3a760c 100644 --- a/source/Root.h +++ b/source/Root.h @@ -2,6 +2,7 @@ #pragma once #include "Authenticator.h" +#include "WebServer.h" @@ -141,6 +142,7 @@ private: cWebAdmin * m_WebAdmin; cPluginManager * m_PluginManager; cAuthenticator m_Authenticator; + cWebServer m_WebServer; cMCLogger * m_Log; diff --git a/source/WebServer.cpp b/source/WebServer.cpp new file mode 100644 index 000000000..8f3fa26ae --- /dev/null +++ b/source/WebServer.cpp @@ -0,0 +1,341 @@ + +// WebServer.cpp + +// Implements the cWebServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing + +#include "Globals.h" +#include "WebServer.h" + + + + + +// Disable MSVC warnings: +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4355) // 'this' : used in base member initializer list +#endif + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cWebRequest: + +cWebRequest::cWebRequest(cWebServer & a_WebServer) : + m_WebServer(a_WebServer), + m_IsReceivingHeaders(true) +{ +} + + + + +void cWebRequest::SendStatusAndReason(int a_StatusCode, const AString & a_Response) +{ + AppendPrintf(m_OutgoingData, "%d %s\r\n\r\n", a_StatusCode, a_Response.c_str()); +} + + + + + +void cWebRequest::ParseHeader(size_t a_IdxEnd) +{ + size_t Next = ParseRequestLine(a_IdxEnd); + if (Next == AString::npos) + { + SendStatusAndReason(HTTP_BAD_REQUEST, "Bad request"); + return; + } + + AString Key; + while (Next < a_IdxEnd) + { + Next = ParseHeaderField(Next, a_IdxEnd, Key); + if (Next == AString::npos) + { + SendStatusAndReason(HTTP_BAD_REQUEST, "Bad request"); + return; + } + } + + m_WebServer.RequestReady(this); +} + + + + +size_t cWebRequest::ParseRequestLine(size_t a_IdxEnd) +{ + // Ignore the initial CRLFs (HTTP spec's "should") + size_t LineStart = 0; + while ( + (LineStart < a_IdxEnd) && + ( + (m_IncomingHeaderData[LineStart] == '\r') || + (m_IncomingHeaderData[LineStart] == '\n') + ) + ) + { + LineStart++; + } + if (LineStart >= a_IdxEnd) + { + return AString::npos; + } + + // Get the Request-Line + size_t LineEnd = m_IncomingHeaderData.find("\r\n", LineStart); + if (LineEnd == AString::npos) + { + return AString::npos; + } + AString RequestLine = m_IncomingHeaderData.substr(LineStart, LineEnd - LineStart); + + // Find the method: + size_t Space = RequestLine.find(" ", LineStart); + if (Space == AString::npos) + { + return AString::npos; + } + m_Method = RequestLine.substr(0, Space); + + // Find the URL: + size_t Space2 = RequestLine.find(" ", Space + 1); + if (Space2 == AString::npos) + { + return AString::npos; + } + m_URL = RequestLine.substr(Space, Space2 - Space); + + // Check that there's HTTP/version at the end + if (strncmp(RequestLine.c_str() + Space2 + 1, "HTTP/1.", 7) != 0) + { + return AString::npos; + } + + return LineEnd + 2; +} + + + + + +size_t cWebRequest::ParseHeaderField(size_t a_IdxStart, size_t a_IdxEnd, AString & a_Key) +{ + if (a_IdxStart >= a_IdxEnd) + { + return a_IdxEnd; + } + if (m_IncomingHeaderData[a_IdxStart] <= ' ') + { + return ParseHeaderFieldContinuation(a_IdxStart + 1, a_IdxEnd, a_Key); + } + size_t ValueIdx = 0; + AString Key; + for (size_t i = a_IdxStart; i < a_IdxEnd; i++) + { + switch (m_IncomingHeaderData[i]) + { + case '\n': + { + if ((ValueIdx == 0) || (i < ValueIdx - 2) || (i < a_IdxStart + 1) || (m_IncomingHeaderData[i - 1] != '\r')) + { + // Invalid header field - no colon or no CR before LF + return AString::npos; + } + AString Value = m_IncomingHeaderData.substr(ValueIdx + 1, i - ValueIdx - 2); + AddHeader(Key, Value); + a_Key = Key; + return i + 1; + } + case ':': + { + if (ValueIdx == 0) + { + Key = m_IncomingHeaderData.substr(a_IdxStart, i - a_IdxStart); + ValueIdx = i; + } + break; + } + case ' ': + case '\t': + { + if (ValueIdx == i - 1) + { + // Value has started in this char, but it is whitespace, so move the start one char further + ValueIdx = i; + } + } + } // switch (char) + } // for i - m_IncomingHeaderData[] + // No header found, return the end-of-data index: + return a_IdxEnd; +} + + + + + +size_t cWebRequest::ParseHeaderFieldContinuation(size_t a_IdxStart, size_t a_IdxEnd, AString & a_Key) +{ + size_t Start = a_IdxStart; + for (size_t i = a_IdxStart; i < a_IdxEnd; i++) + { + if ((m_IncomingHeaderData[i] > ' ') && (Start == a_IdxStart)) + { + Start = i; + } + else if (m_IncomingHeaderData[i] == '\n') + { + if ((i < a_IdxStart + 1) || (m_IncomingHeaderData[i - 1] != '\r')) + { + // There wasn't a CR before this LF + return AString::npos; + } + AString Value = m_IncomingHeaderData.substr(Start, i - Start - 1); + AddHeader(a_Key, Value); + return i + 1; + } + } + // LF not found, how? We found it at the header end (CRLFCRLF) + ASSERT(!"LF not found, wtf?"); + return AString::npos; +} + + + + + +void cWebRequest::AddHeader(const AString & a_Key, const AString & a_Value) +{ + cNameValueMap::iterator itr = m_Headers.find(a_Key); + if (itr == m_Headers.end()) + { + m_Headers[a_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); + } +} + + + + + +void cWebRequest::DataReceived(const char * a_Data, int a_Size) +{ + if (m_IsReceivingHeaders) + { + // Start searching 3 chars from the end of the already received data, if available: + size_t SearchStart = m_IncomingHeaderData.size(); + SearchStart = (SearchStart > 3) ? SearchStart - 3 : 0; + + m_IncomingHeaderData.append(a_Data, a_Size); + + // Parse the header, if it is complete: + size_t idxEnd = m_IncomingHeaderData.find("\r\n\r\n", SearchStart); + if (idxEnd != AString::npos) + { + ParseHeader(idxEnd + 2); + m_IsReceivingHeaders = false; + } + } + else + { + // TODO: Receive the body, and the next request (If HTTP/1.1 keepalive + } +} + + + + + +void cWebRequest::GetOutgoingData(AString & a_Data) +{ + std::swap(a_Data, m_OutgoingData); +} + + + + + +void cWebRequest::SocketClosed(void) +{ + // TODO: m_WebServer.RequestFinished(this); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cWebServer: + +cWebServer::cWebServer(void) : + m_ListenThreadIPv4(*this, cSocket::IPv4, "WebServer IPv4"), + m_ListenThreadIPv6(*this, cSocket::IPv6, "WebServer IPv6"), + m_SocketThreads() +{ +} + + + + + +bool cWebServer::Initialize(cIniFile & a_IniFile) +{ + if (!a_IniFile.GetValueSetB("WebAdmin", "Enabled", false)) + { + // The WebAdmin is disabled + return true; + } + bool HasAnyPort; + HasAnyPort = m_ListenThreadIPv4.Initialize(a_IniFile.GetValueSet("WebAdmin", "Port", "8081")); + HasAnyPort = m_ListenThreadIPv6.Initialize(a_IniFile.GetValueSet("WebAdmin", "PortsIPv6", "8082, 3300")) || HasAnyPort; + if (!HasAnyPort) + { + LOG("WebAdmin is disabled"); + return false; + } + if (!m_ListenThreadIPv4.Start()) + { + return false; + } + if (!m_ListenThreadIPv6.Start()) + { + m_ListenThreadIPv4.Stop(); + return false; + } + return true; +} + + + + + +void cWebServer::OnConnectionAccepted(cSocket & a_Socket) +{ + cWebRequest * Request = new cWebRequest(*this); + m_SocketThreads.AddClient(a_Socket, Request); + cCSLock Lock(m_CSRequests); + m_Requests.push_back(Request); +} + + + + + +void cWebServer::RequestReady(cWebRequest * a_Request) +{ + a_Request->SendStatusAndReason(cWebRequest::HTTP_OK, "Hello"); +} + + + + diff --git a/source/WebServer.h b/source/WebServer.h new file mode 100644 index 000000000..1a10e4461 --- /dev/null +++ b/source/WebServer.h @@ -0,0 +1,122 @@ + +// WebServer.h + +// Declares the cWebServer 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 cWebServer; + + + + + +class cWebRequest : + public cSocketThreads::cCallback +{ +public: + enum + { + HTTP_OK = 200, + HTTP_BAD_REQUEST = 400, + } ; + + cWebRequest(cWebServer & a_WebServer); + + /// Sends HTTP status code together with a_Reason + void SendStatusAndReason(int a_StatusCode, const AString & a_Reason); + +protected: + typedef std::map cNameValueMap; + + cWebServer & m_WebServer; + + AString m_Method; ///< Method of the request (GET / PUT / POST / ...) + AString m_URL; ///< Full URL of the request + cNameValueMap m_Headers; ///< All the headers the request has come with + + AString m_IncomingHeaderData; ///< All the incoming data until the entire header is parsed + + /// Set to true when the header haven't been received yet. If false, receiving the optional body. + bool m_IsReceivingHeaders; + + /// Data that is queued for sending, once the socket becomes writable + AString m_OutgoingData; + + + /// Parses the header in m_IncomingData until the specified end mark + void ParseHeader(size_t a_IdxEnd); + + /** Parses the RequestLine out of m_IncomingHeaderData, up to index a_IdxEnd + Returns the index to the next line, or npos if invalid request + */ + size_t ParseRequestLine(size_t a_IdxEnd); + + /** Parses one header field out of m_IncomingHeaderData, starting at the specified offset, up to offset a_IdxEnd. + Returns the index to the next line, or npos if invalid request. + a_Key is set to the key that was parsed (used for multi-line headers) + */ + size_t ParseHeaderField(size_t a_IdxStart, size_t a_IdxEnd, AString & a_Key); + + /** Parses one header field that is known to be a continuation of previous header. + Returns the index to the next line, or npos if invalid request. + */ + size_t ParseHeaderFieldContinuation(size_t a_IdxStart, size_t a_IdxEnd, AString & a_Key); + + /// Adds a header into m_Headers; appends if key already exists + void AddHeader(const AString & a_Key, const AString & a_Value); + + // 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 cWebRequests; + + + + +class cWebServer : + public cListenThread::cCallback +{ +public: + cWebServer(void); + + bool Initialize(cIniFile & a_IniFile); + +protected: + friend class cWebRequest; + + cListenThread m_ListenThreadIPv4; + cListenThread m_ListenThreadIPv6; + + cSocketThreads m_SocketThreads; + + cCriticalSection m_CSRequests; + cWebRequests m_Requests; ///< All the requests that are currently being serviced + + // cListenThread::cCallback overrides: + virtual void OnConnectionAccepted(cSocket & a_Socket) override; + + /// Called by cWebRequest when it finishes parsing its header + void RequestReady(cWebRequest * a_Request); +} ; + + + + + -- cgit v1.2.3