From f4efcb90808603bbfce5a149f5490bd6fceb880f Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Sep 2013 18:14:26 +0200 Subject: [PATCH] Rewritten HTTPServer to split into cHTTPConnection, cHTTPRequest and cHTTPResponse classes. --- VC2008/MCServer.vcproj | 28 ++- source/HTTPServer/HTTPMessage.cpp | 285 +++++++++++++++++++++++++ source/HTTPServer/HTTPMessage.h | 121 +++++++++++ source/HTTPServer/HTTPServer.cpp | 267 +++++++++++++++++++++++ source/HTTPServer/HTTPServer.h | 135 ++++++++++++ source/Root.cpp | 2 +- source/Root.h | 4 +- source/WebServer.cpp | 341 ------------------------------ source/WebServer.h | 122 ----------- 9 files changed, 831 insertions(+), 474 deletions(-) create mode 100644 source/HTTPServer/HTTPMessage.cpp create mode 100644 source/HTTPServer/HTTPMessage.h create mode 100644 source/HTTPServer/HTTPServer.cpp create mode 100644 source/HTTPServer/HTTPServer.h delete mode 100644 source/WebServer.cpp delete mode 100644 source/WebServer.h diff --git a/VC2008/MCServer.vcproj b/VC2008/MCServer.vcproj index 7fc10fb32..7aba32b1d 100644 --- a/VC2008/MCServer.vcproj +++ b/VC2008/MCServer.vcproj @@ -900,14 +900,6 @@ RelativePath="..\source\WebAdmin.h" > - - - - @@ -2724,6 +2716,26 @@ > + + + + + + + + + + second.append(", "); + itr->second.append(a_Value); + } + + // Special processing for well-known headers: + if (a_Key == "Content-Type") + { + m_ContentType = m_Headers["Content-Type"]; + } + else if (a_Key == "Content-Length") + { + m_ContentLength = atoi(m_Headers["Content-Length"].c_str()); + } +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHTTPRequest: + +cHTTPRequest::cHTTPRequest(void) : + super(mkRequest) +{ +} + + + + + +bool cHTTPRequest::ParseHeaders(const char * a_IncomingData, size_t a_IdxEnd) +{ + // The first line contains the method and the URL: + size_t Next = ParseRequestLine(a_IncomingData, a_IdxEnd); + if (Next == AString::npos) + { + return false; + } + + // The following lines contain headers: + AString Key; + const char * Data = a_IncomingData + Next; + size_t End = a_IdxEnd - Next; + while (End > 0) + { + Next = ParseHeaderField(Data, End, Key); + if (Next == AString::npos) + { + return false; + } + ASSERT(End >= Next); + Data += Next; + End -= Next; + } + + return HasReceivedContentLength(); +} + + + + +size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd) +{ + // Ignore the initial CRLFs (HTTP spec's "should") + size_t LineStart = 0; + while ( + (LineStart < a_IdxEnd) && + ( + (a_Data[LineStart] == '\r') || + (a_Data[LineStart] == '\n') + ) + ) + { + LineStart++; + } + if (LineStart >= a_IdxEnd) + { + return AString::npos; + } + + size_t Last = LineStart; + int NumSpaces = 0; + for (size_t i = LineStart; i < a_IdxEnd; i++) + { + switch (a_Data[i]) + { + case ' ': + { + switch (NumSpaces) + { + case 0: + { + m_Method.assign(a_Data, Last, i - Last - 1); + break; + } + case 1: + { + m_URL.assign(a_Data, Last, i - Last - 1); + break; + } + default: + { + // Too many spaces in the request + return AString::npos; + } + } + Last = i + 1; + NumSpaces += 1; + break; + } + case '\n': + { + if ((i == 0) || (a_Data[i] != '\r') || (NumSpaces != 2) || (i < Last + 7)) + { + // LF too early, without a CR, without two preceeding spaces or too soon after the second space + return AString::npos; + } + // Check that there's HTTP/version at the end + if (strncmp(a_Data + Last, "HTTP/1.", 7) != 0) + { + return AString::npos; + } + return i; + } + } // switch (a_Data[i]) + } // for i - a_Data[] + return AString::npos; +} + + + + + +size_t cHTTPRequest::ParseHeaderField(const char * a_Data, size_t a_IdxEnd, AString & a_Key) +{ + if (*a_Data <= ' ') + { + size_t res = ParseHeaderFieldContinuation(a_Data + 1, a_IdxEnd - 1, a_Key); + return (res == AString::npos) ? res : (res + 1); + } + size_t ValueIdx = 0; + AString Key; + for (size_t i = 0; i < a_IdxEnd; i++) + { + switch (a_Data[i]) + { + case '\n': + { + if ((ValueIdx == 0) || (i < ValueIdx - 2) || (i == 0) || (a_Data[i - 1] != '\r')) + { + // Invalid header field - no colon or no CR before LF + return AString::npos; + } + AString Value(a_Data, ValueIdx + 1, i - ValueIdx - 2); + AddHeader(Key, Value); + a_Key = Key; + return i + 1; + } + case ':': + { + if (ValueIdx == 0) + { + Key.assign(a_Data, 0, i); + 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 cHTTPRequest::ParseHeaderFieldContinuation(const char * a_Data, size_t a_IdxEnd, AString & a_Key) +{ + size_t Start = 0; + for (size_t i = 0; i < a_IdxEnd; i++) + { + if ((a_Data[i] > ' ') && (Start == 0)) + { + Start = i; + } + else if (a_Data[i] == '\n') + { + if ((i == 0) || (a_Data[i - 1] != '\r')) + { + // There wasn't a CR before this LF + return AString::npos; + } + AString Value(a_Data, 0, 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; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHTTPResponse: + +cHTTPResponse::cHTTPResponse(void) : + super(mkResponse) +{ +} + + + + + +void cHTTPResponse::AppendToData(AString & a_DataStream) const +{ + a_DataStream.append("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 000000000..a3c4f96d1 --- /dev/null +++ b/source/HTTPServer/HTTPMessage.h @@ -0,0 +1,121 @@ + +// HTTPMessage.h + +// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes + + + + + +#pragma once + + + + + +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 +{ + typedef cHTTPMessage super; + +public: + cHTTPRequest(void); + + /// Parses the headers information from the received data in the specified string of incoming data. Returns true if successful. + bool ParseHeaders(const char * a_IncomingData, size_t a_idxEnd); + + /// Returns true if the request did contain a Content-Length header + bool HasReceivedContentLength(void) const { return (m_ContentLength >= 0); } + +protected: + /// Method of the request (GET / PUT / POST / ...) + AString m_Method; + + /// Full URL of the request + AString m_URL; + + /// Number of bytes that remain to read for the complete body of the message to be received + int m_BodyRemaining; + + /** Parses the RequestLine out of a_Data, up to index a_IdxEnd + Returns the index to the next line, or npos if invalid request + */ + size_t ParseRequestLine(const char * a_Data, size_t a_IdxEnd); + + /** Parses one header field out of a_Data, up to offset a_IdxEnd. + Returns the index to the next line (relative to a_Data), or npos if invalid request. + a_Key is set to the key that was parsed (used for multi-line headers) + */ + size_t ParseHeaderField(const char * a_Data, 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(const char * a_Data, size_t a_IdxEnd, AString & a_Key); +} ; + + + + + +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 000000000..e68032bc2 --- /dev/null +++ b/source/HTTPServer/HTTPServer.cpp @@ -0,0 +1,267 @@ + +// 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" + + + + + +// Disable MSVC warnings: +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4355) // 'this' : used in base member initializer list +#endif + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHTTPConnection: + +cHTTPConnection::cHTTPConnection(cHTTPServer & a_HTTPServer) : + m_HTTPServer(a_HTTPServer), + m_State(wcsRecvHeaders), + m_CurrentRequest(NULL) +{ +} + + + + +void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response) +{ + AppendPrintf(m_OutgoingData, "%d %s\r\n\r\n", a_StatusCode, a_Response.c_str()); +} + + + + + +void cHTTPConnection::Send(const cHTTPResponse & a_Response) +{ + ASSERT(m_State = wcsRecvIdle); + a_Response.AppendToData(m_OutgoingData); + m_State = wcsSendingResp; +} + + + + + +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); +} + + + + + +void cHTTPConnection::FinishResponse(void) +{ + ASSERT(m_State == wcsSendingResp); + m_OutgoingData.append("0\r\n"); + m_State = wcsRecvHeaders; +} + + + + + +void cHTTPConnection::DataReceived(const char * a_Data, int a_Size) +{ + switch (m_State) + { + case wcsRecvHeaders: + { + ASSERT(m_CurrentRequest == NULL); + + // 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) + { + return; + } + m_CurrentRequest = new cHTTPRequest; + if (!m_CurrentRequest->ParseHeaders(m_IncomingHeaderData.c_str(), idxEnd + 2)) + { + delete m_CurrentRequest; + m_CurrentRequest = NULL; + m_State = wcsInvalid; + m_HTTPServer.CloseConnection(*this); + return; + } + m_State = wcsRecvBody; + m_HTTPServer.NewRequest(*this, *m_CurrentRequest); + + // Process the rest of the incoming data into the request body: + if (m_IncomingHeaderData.size() > idxEnd + 4) + { + m_IncomingHeaderData.erase(0, idxEnd + 4); + DataReceived(m_IncomingHeaderData.c_str(), m_IncomingHeaderData.size()); + } + break; + } + + case wcsRecvBody: + { + ASSERT(m_CurrentRequest != NULL); + // TODO: Receive the body, and the next request (If HTTP/1.1 keepalive) + 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); + } +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHTTPServer: + +cHTTPServer::cHTTPServer(void) : + m_ListenThreadIPv4(*this, cSocket::IPv4, "WebServer IPv4"), + m_ListenThreadIPv6(*this, cSocket::IPv6, "WebServer IPv6"), + m_SocketThreads() +{ +} + + + + + +bool cHTTPServer::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 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; + } + } +} + + + + + +void cHTTPServer::NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +{ + // TODO +} + + + + + +void cHTTPServer::RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +{ + // TODO +} + + + + + +void cHTTPServer::RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +{ + // TODO + + // DEBUG: Send a debug response: + cHTTPResponse Resp; + Resp.SetContentType("text/plain"); + a_Connection.Send(Resp); + a_Connection.Send("Hello"); + a_Connection.FinishResponse(); +} + + + + diff --git a/source/HTTPServer/HTTPServer.h b/source/HTTPServer/HTTPServer.h new file mode 100644 index 000000000..9287a79e8 --- /dev/null +++ b/source/HTTPServer/HTTPServer.h @@ -0,0 +1,135 @@ + +// 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 cHTTPServer; +class cHTTPMessage; +class cHTTPRequest; +class cHTTPResponse; + + + + + +class cHTTPConnection : + public cSocketThreads::cCallback +{ +public: + + enum eState + { + wcsRecvHeaders, ///< Receiving request headers (m_CurrentRequest == 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); + + /// Sends HTTP status code together with a_Reason (used for HTTP errors) + void SendStatusAndReason(int a_StatusCode, const AString & a_Reason); + + /// 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()); } + + /// Finishes sending current response, gets ready for receiving another request (HTTP 1.1 keepalive) + void FinishResponse(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; + + + /// Parses the header in m_IncomingData until the specified end mark + void ParseHeader(size_t a_IdxEnd); + + /// Sends the response status and headers. Transition from wrsRecvBody to wrsSendingResp. + void SendRespHeaders(void); + + // 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; + + + + +class cHTTPServer : + public cListenThread::cCallback +{ +public: + cHTTPServer(void); + + bool Initialize(cIniFile & a_IniFile); + +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 + + // 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 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); + + /// 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/Root.cpp b/source/Root.cpp index 823bd8e13..821dd0928 100644 --- a/source/Root.cpp +++ b/source/Root.cpp @@ -137,7 +137,7 @@ void cRoot::Start(void) } else { - m_WebServer.Initialize(WebIniFile); + m_HTTPServer.Initialize(WebIniFile); } LOG("Loading settings..."); diff --git a/source/Root.h b/source/Root.h index 48a3a760c..e5197ce2b 100644 --- a/source/Root.h +++ b/source/Root.h @@ -2,7 +2,7 @@ #pragma once #include "Authenticator.h" -#include "WebServer.h" +#include "HTTPServer/HTTPServer.h" @@ -142,7 +142,7 @@ private: cWebAdmin * m_WebAdmin; cPluginManager * m_PluginManager; cAuthenticator m_Authenticator; - cWebServer m_WebServer; + cHTTPServer m_HTTPServer; cMCLogger * m_Log; diff --git a/source/WebServer.cpp b/source/WebServer.cpp deleted file mode 100644 index 8f3fa26ae..000000000 --- a/source/WebServer.cpp +++ /dev/null @@ -1,341 +0,0 @@ - -// 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 deleted file mode 100644 index 1a10e4461..000000000 --- a/source/WebServer.h +++ /dev/null @@ -1,122 +0,0 @@ - -// 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); -} ; - - - - -