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. --- source/Root.cpp | 6 +- source/Root.h | 2 + source/WebServer.cpp | 341 +++++++++++++++++++++++++++++++++++++++++++++++++++ source/WebServer.h | 122 ++++++++++++++++++ 4 files changed, 467 insertions(+), 4 deletions(-) create mode 100644 source/WebServer.cpp create mode 100644 source/WebServer.h (limited to 'source') 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 From f4efcb90808603bbfce5a149f5490bd6fceb880f Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Sep 2013 18:14:26 +0200 Subject: Rewritten HTTPServer to split into cHTTPConnection, cHTTPRequest and cHTTPResponse classes. --- 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 -------------- 8 files changed, 811 insertions(+), 466 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 (limited to 'source') diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp new file mode 100644 index 000000000..b784cb941 --- /dev/null +++ b/source/HTTPServer/HTTPMessage.cpp @@ -0,0 +1,285 @@ + +// HTTPMessage.cpp + +// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes + +#include "Globals.h" +#include "HTTPMessage.h" + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHTTPMessage: + +cHTTPMessage::cHTTPMessage(eKind a_Kind) : + m_Kind(a_Kind) +{ +} + + + + + +void cHTTPMessage::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); + } + + // 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); -} ; - - - - - -- cgit v1.2.3 From d0b9e817956a57389f17a3d8e00df51cbe8cc309 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Sep 2013 19:34:46 +0200 Subject: Split cHTTPConnection implementation into a separate file. --- source/HTTPServer/HTTPConnection.cpp | 147 +++++++++++++++++++++++++++++++++++ source/HTTPServer/HTTPConnection.h | 88 +++++++++++++++++++++ source/HTTPServer/HTTPServer.cpp | 140 +-------------------------------- source/HTTPServer/HTTPServer.h | 68 +--------------- 4 files changed, 238 insertions(+), 205 deletions(-) create mode 100644 source/HTTPServer/HTTPConnection.cpp create mode 100644 source/HTTPServer/HTTPConnection.h (limited to 'source') diff --git a/source/HTTPServer/HTTPConnection.cpp b/source/HTTPServer/HTTPConnection.cpp new file mode 100644 index 000000000..f7318c6ae --- /dev/null +++ b/source/HTTPServer/HTTPConnection.cpp @@ -0,0 +1,147 @@ + +// 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) +{ +} + + + + +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); + } +} + + + + + diff --git a/source/HTTPServer/HTTPConnection.h b/source/HTTPServer/HTTPConnection.h new file mode 100644 index 000000000..e2df5de46 --- /dev/null +++ b/source/HTTPServer/HTTPConnection.h @@ -0,0 +1,88 @@ + +// 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 == 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; + + + // 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/HTTPServer.cpp b/source/HTTPServer/HTTPServer.cpp index e68032bc2..980cad14f 100644 --- a/source/HTTPServer/HTTPServer.cpp +++ b/source/HTTPServer/HTTPServer.cpp @@ -6,6 +6,7 @@ #include "Globals.h" #include "HTTPServer.h" #include "HTTPMessage.h" +#include "HTTPConnection.h" @@ -21,145 +22,6 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// 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"), diff --git a/source/HTTPServer/HTTPServer.h b/source/HTTPServer/HTTPServer.h index 9287a79e8..fd4782267 100644 --- a/source/HTTPServer/HTTPServer.h +++ b/source/HTTPServer/HTTPServer.h @@ -18,80 +18,16 @@ // fwd: -class cHTTPServer; class cHTTPMessage; class cHTTPRequest; class cHTTPResponse; +class cHTTPConnection; +typedef std::vector cHTTPConnections; -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 : -- cgit v1.2.3 From 0c3fd5e77d681c25757efaab6acb305d0b5630c1 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Sep 2013 20:33:18 +0200 Subject: Fixed parsing and implemented write nofitication. The web connection finally works with a browser. --- source/HTTPServer/HTTPConnection.cpp | 26 ++++++++++++++++++++++++-- source/HTTPServer/HTTPConnection.h | 3 +++ source/HTTPServer/HTTPMessage.cpp | 16 ++++++++++------ source/HTTPServer/HTTPMessage.h | 3 --- source/HTTPServer/HTTPServer.cpp | 11 ++++++++++- source/HTTPServer/HTTPServer.h | 5 ++++- 6 files changed, 51 insertions(+), 13 deletions(-) (limited to 'source') diff --git a/source/HTTPServer/HTTPConnection.cpp b/source/HTTPServer/HTTPConnection.cpp index f7318c6ae..59fe8f878 100644 --- a/source/HTTPServer/HTTPConnection.cpp +++ b/source/HTTPServer/HTTPConnection.cpp @@ -25,6 +25,7 @@ cHTTPConnection::cHTTPConnection(cHTTPServer & a_HTTPServer) : 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()); + m_HTTPServer.NotifyConnectionWrite(*this); } @@ -36,6 +37,7 @@ void cHTTPConnection::Send(const cHTTPResponse & a_Response) ASSERT(m_State = wcsRecvIdle); a_Response.AppendToData(m_OutgoingData); m_State = wcsSendingResp; + m_HTTPServer.NotifyConnectionWrite(*this); } @@ -47,6 +49,8 @@ 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); } @@ -56,8 +60,9 @@ void cHTTPConnection::Send(const void * a_Data, int a_Size) void cHTTPConnection::FinishResponse(void) { ASSERT(m_State == wcsSendingResp); - m_OutgoingData.append("0\r\n"); + m_OutgoingData.append("0\r\n\r\n"); m_State = wcsRecvHeaders; + m_HTTPServer.NotifyConnectionWrite(*this); } @@ -95,12 +100,19 @@ void cHTTPConnection::DataReceived(const char * a_Data, int a_Size) } m_State = wcsRecvBody; m_HTTPServer.NewRequest(*this, *m_CurrentRequest); + m_CurrentRequestBodyRemaining = m_CurrentRequest->GetContentLength(); // 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()); + m_IncomingHeaderData.clear(); + } + else + { + m_IncomingHeaderData.clear(); + DataReceived("", 0); // If the request has zero body length, let it be processed right-away } break; } @@ -108,7 +120,17 @@ void cHTTPConnection::DataReceived(const char * a_Data, int a_Size) case wcsRecvBody: { ASSERT(m_CurrentRequest != NULL); - // TODO: Receive the body, and the next request (If HTTP/1.1 keepalive) + 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_HTTPServer.RequestFinished(*this, *m_CurrentRequest); + m_State = wcsRecvIdle; + } break; } diff --git a/source/HTTPServer/HTTPConnection.h b/source/HTTPServer/HTTPConnection.h index e2df5de46..d8ecdf1d9 100644 --- a/source/HTTPServer/HTTPConnection.h +++ b/source/HTTPServer/HTTPConnection.h @@ -72,6 +72,9 @@ protected: /// 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: diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp index b784cb941..b2e21c712 100644 --- a/source/HTTPServer/HTTPMessage.cpp +++ b/source/HTTPServer/HTTPMessage.cpp @@ -88,7 +88,11 @@ bool cHTTPRequest::ParseHeaders(const char * a_IncomingData, size_t a_IdxEnd) End -= Next; } - return HasReceivedContentLength(); + if (!HasReceivedContentLength()) + { + SetContentLength(0); + } + return true; } @@ -125,12 +129,12 @@ size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd) { case 0: { - m_Method.assign(a_Data, Last, i - Last - 1); + m_Method.assign(a_Data, Last, i - Last); break; } case 1: { - m_URL.assign(a_Data, Last, i - Last - 1); + m_URL.assign(a_Data, Last, i - Last); break; } default: @@ -145,7 +149,7 @@ size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd) } case '\n': { - if ((i == 0) || (a_Data[i] != '\r') || (NumSpaces != 2) || (i < Last + 7)) + if ((i == 0) || (a_Data[i - 1] != '\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; @@ -155,7 +159,7 @@ size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd) { return AString::npos; } - return i; + return i + 1; } } // switch (a_Data[i]) } // for i - a_Data[] @@ -263,7 +267,7 @@ cHTTPResponse::cHTTPResponse(void) : void cHTTPResponse::AppendToData(AString & a_DataStream) const { - a_DataStream.append("200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: "); + 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) diff --git a/source/HTTPServer/HTTPMessage.h b/source/HTTPServer/HTTPMessage.h index a3c4f96d1..fc7b621fe 100644 --- a/source/HTTPServer/HTTPMessage.h +++ b/source/HTTPServer/HTTPMessage.h @@ -78,9 +78,6 @@ protected: /// 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 */ diff --git a/source/HTTPServer/HTTPServer.cpp b/source/HTTPServer/HTTPServer.cpp index 980cad14f..d518df10d 100644 --- a/source/HTTPServer/HTTPServer.cpp +++ b/source/HTTPServer/HTTPServer.cpp @@ -94,6 +94,15 @@ void cHTTPServer::CloseConnection(cHTTPConnection & a_Connection) +void cHTTPServer::NotifyConnectionWrite(cHTTPConnection & a_Connection) +{ + m_SocketThreads.NotifyWrite(&a_Connection); +} + + + + + void cHTTPServer::NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) { // TODO @@ -103,7 +112,7 @@ void cHTTPServer::NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Re -void cHTTPServer::RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +void cHTTPServer::RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) { // TODO } diff --git a/source/HTTPServer/HTTPServer.h b/source/HTTPServer/HTTPServer.h index fd4782267..2d0acc386 100644 --- a/source/HTTPServer/HTTPServer.h +++ b/source/HTTPServer/HTTPServer.h @@ -55,11 +55,14 @@ protected: /// 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); + 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); -- cgit v1.2.3 From 8c57c5c1f22e54884100e0de1378e4522665a79c Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Sep 2013 20:48:44 +0200 Subject: Fixed leaking HTTPRequest objects --- source/HTTPServer/HTTPConnection.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'source') diff --git a/source/HTTPServer/HTTPConnection.cpp b/source/HTTPServer/HTTPConnection.cpp index 59fe8f878..2265d970f 100644 --- a/source/HTTPServer/HTTPConnection.cpp +++ b/source/HTTPServer/HTTPConnection.cpp @@ -129,6 +129,8 @@ void cHTTPConnection::DataReceived(const char * a_Data, int a_Size) if (m_CurrentRequestBodyRemaining == 0) { m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); + delete m_CurrentRequest; + m_CurrentRequest = NULL; m_State = wcsRecvIdle; } break; -- cgit v1.2.3 From 5cf8fc12ae000cd2d2b54a2bf158f82bdb8a0e67 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Sep 2013 21:28:41 +0200 Subject: Added cHTTPServer callbacks; fixed keep-alives. The HTTP server now calls callbacks specified in its start function (debugified atm.) and it processes multiple requests on a single connection. --- source/HTTPServer/HTTPConnection.cpp | 35 +++++++++++++++- source/HTTPServer/HTTPConnection.h | 5 ++- source/HTTPServer/HTTPServer.cpp | 80 +++++++++++++++++++++++++++++++----- source/HTTPServer/HTTPServer.h | 25 +++++++++++ 4 files changed, 132 insertions(+), 13 deletions(-) (limited to 'source') diff --git a/source/HTTPServer/HTTPConnection.cpp b/source/HTTPServer/HTTPConnection.cpp index 2265d970f..c36b07d3d 100644 --- a/source/HTTPServer/HTTPConnection.cpp +++ b/source/HTTPServer/HTTPConnection.cpp @@ -69,6 +69,39 @@ void cHTTPConnection::FinishResponse(void) +void cHTTPConnection::AwaitNextRequest(void) +{ + switch (m_State) + { + 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::DataReceived(const char * a_Data, int a_Size) { switch (m_State) @@ -128,10 +161,10 @@ void cHTTPConnection::DataReceived(const char * a_Data, int a_Size) } if (m_CurrentRequestBodyRemaining == 0) { + m_State = wcsRecvIdle; m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); delete m_CurrentRequest; m_CurrentRequest = NULL; - m_State = wcsRecvIdle; } break; } diff --git a/source/HTTPServer/HTTPConnection.h b/source/HTTPServer/HTTPConnection.h index d8ecdf1d9..46c36a8a2 100644 --- a/source/HTTPServer/HTTPConnection.h +++ b/source/HTTPServer/HTTPConnection.h @@ -52,9 +52,12 @@ public: /// 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) + /// 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); + protected: typedef std::map cNameValueMap; diff --git a/source/HTTPServer/HTTPServer.cpp b/source/HTTPServer/HTTPServer.cpp index d518df10d..8494d6fce 100644 --- a/source/HTTPServer/HTTPServer.cpp +++ b/source/HTTPServer/HTTPServer.cpp @@ -22,10 +22,43 @@ +class cDebugCallbacks : + public cHTTPServer::cCallbacks +{ + virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override + { + // Nothing needed + } + + + virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) override + { + // TODO + } + + + virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override + { + cHTTPResponse Resp; + Resp.SetContentType("text/plain"); + a_Connection.Send(Resp); + a_Connection.Send("Hello, world"); + } + + +} g_DebugCallbacks; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHTTPServer: + cHTTPServer::cHTTPServer(void) : m_ListenThreadIPv4(*this, cSocket::IPv4, "WebServer IPv4"), m_ListenThreadIPv6(*this, cSocket::IPv6, "WebServer IPv6"), - m_SocketThreads() + m_Callbacks(NULL) { } @@ -48,6 +81,20 @@ bool cHTTPServer::Initialize(cIniFile & a_IniFile) LOG("WebAdmin is disabled"); return false; } + + // DEBUG: + return Start(g_DebugCallbacks); + + return true; +} + + + + + +bool cHTTPServer::Start(cCallbacks & a_Callbacks) +{ + m_Callbacks = &a_Callbacks; if (!m_ListenThreadIPv4.Start()) { return false; @@ -64,6 +111,23 @@ bool cHTTPServer::Initialize(cIniFile & a_IniFile) +void cHTTPServer::Stop(void) +{ + m_ListenThreadIPv4.Stop(); + m_ListenThreadIPv6.Stop(); + + // Drop all current connections: + cCSLock Lock(m_CSConnections); + for (cHTTPConnections::iterator itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr) + { + m_SocketThreads.RemoveClient(*itr); + } // for itr - m_Connections[] +} + + + + + void cHTTPServer::OnConnectionAccepted(cSocket & a_Socket) { cHTTPConnection * Connection = new cHTTPConnection(*this); @@ -105,7 +169,7 @@ void cHTTPServer::NotifyConnectionWrite(cHTTPConnection & a_Connection) void cHTTPServer::NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) { - // TODO + m_Callbacks->OnRequestBegun(a_Connection, a_Request); } @@ -114,7 +178,7 @@ void cHTTPServer::NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Re void cHTTPServer::RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) { - // TODO + m_Callbacks->OnRequestBody(a_Connection, a_Request, a_Data, a_Size); } @@ -123,14 +187,8 @@ void cHTTPServer::RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_R 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(); + m_Callbacks->OnRequestFinished(a_Connection, a_Request); + a_Connection.AwaitNextRequest(); } diff --git a/source/HTTPServer/HTTPServer.h b/source/HTTPServer/HTTPServer.h index 2d0acc386..efe60809d 100644 --- a/source/HTTPServer/HTTPServer.h +++ b/source/HTTPServer/HTTPServer.h @@ -34,10 +34,31 @@ 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); bool Initialize(cIniFile & a_IniFile); + /// 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; @@ -48,6 +69,10 @@ protected: 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; -- cgit v1.2.3 From c22ea7efff5d611d8293eff895b2ff1b234aa5a6 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Sep 2013 21:38:54 +0200 Subject: Added UserData to cHTTPRequest. Callbacks may store one pointer of per-request data in the cHTTPRequest object. The object doesn't touch this data (doesn't own it). --- source/HTTPServer/HTTPMessage.cpp | 3 ++- source/HTTPServer/HTTPMessage.h | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'source') diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp index b2e21c712..3ad838ac0 100644 --- a/source/HTTPServer/HTTPMessage.cpp +++ b/source/HTTPServer/HTTPMessage.cpp @@ -55,7 +55,8 @@ void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) // cHTTPRequest: cHTTPRequest::cHTTPRequest(void) : - super(mkRequest) + super(mkRequest), + m_UserData(NULL) { } diff --git a/source/HTTPServer/HTTPMessage.h b/source/HTTPServer/HTTPMessage.h index fc7b621fe..7b1a27eaa 100644 --- a/source/HTTPServer/HTTPMessage.h +++ b/source/HTTPServer/HTTPMessage.h @@ -71,6 +71,12 @@ public: /// Returns true if the request did contain a Content-Length header bool HasReceivedContentLength(void) const { return (m_ContentLength >= 0); } + /// 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; } + protected: /// Method of the request (GET / PUT / POST / ...) AString m_Method; @@ -78,6 +84,10 @@ protected: /// Full URL of the request AString m_URL; + /// Data that the HTTPServer callbacks are allowed to store. + void * m_UserData; + + /** Parses the RequestLine out of a_Data, up to index a_IdxEnd Returns the index to the next line, or npos if invalid request */ -- cgit v1.2.3 From 3b473f7a67a8feb694856dd705454fdb9945b319 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sat, 28 Sep 2013 19:28:19 +0200 Subject: Added URLDecode() and ReplaceAllCharOccurrences() to StringUtils. --- source/StringUtils.cpp | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ source/StringUtils.h | 6 +++++ 2 files changed, 76 insertions(+) (limited to 'source') diff --git a/source/StringUtils.cpp b/source/StringUtils.cpp index cb91a4da7..d53d25866 100644 --- a/source/StringUtils.cpp +++ b/source/StringUtils.cpp @@ -658,3 +658,73 @@ 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; +} + + + + diff --git a/source/StringUtils.h b/source/StringUtils.h index 211799e91..929e6fd5b 100644 --- a/source/StringUtils.h +++ b/source/StringUtils.h @@ -72,6 +72,12 @@ 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 + // If you have any other string helper functions, declare them here -- cgit v1.2.3 From 8130e6dd5439e381aae18532ede48441a4b46155 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sat, 28 Sep 2013 19:30:25 +0200 Subject: Created basic cHTTPFormParser. It can parse forms in the application/x-www-form-urlencoded encoding, used for forms without file uploads. --- source/HTTPServer/HTTPFormParser.cpp | 211 +++++++++++++++++++++++++++++++++++ source/HTTPServer/HTTPFormParser.h | 64 +++++++++++ source/HTTPServer/HTTPMessage.cpp | 1 + source/HTTPServer/HTTPMessage.h | 6 + source/HTTPServer/HTTPServer.cpp | 36 +++++- 5 files changed, 316 insertions(+), 2 deletions(-) create mode 100644 source/HTTPServer/HTTPFormParser.cpp create mode 100644 source/HTTPServer/HTTPFormParser.h (limited to 'source') diff --git a/source/HTTPServer/HTTPFormParser.cpp b/source/HTTPServer/HTTPFormParser.cpp new file mode 100644 index 000000000..3412bcc94 --- /dev/null +++ b/source/HTTPServer/HTTPFormParser.cpp @@ -0,0 +1,211 @@ + +// HTTPFormParser.cpp + +// Implements the cHTTPFormParser class representing a parser for forms sent over HTTP + +#include "Globals.h" +#include "HTTPFormParser.h" +#include "HTTPMessage.h" + + + + + +cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) : + 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 (a_Request.GetContentType() == "application/x-www-form-urlencoded") + { + m_Kind = fpkFormUrlEncoded; + return; + } + if (a_Request.GetContentType() == "multipart/form-data") + { + m_Kind = fpkMultipart; + return; + } + } + ASSERT(!"Unhandled request method"); +} + + + + + +void cHTTPFormParser::Parse(const char * a_Data, int a_Size) +{ + m_IncomingData.append(a_Data, a_Size); + 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() + break; + } + case fpkMultipart: + { + ParseMultipart(); + 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) +{ + return ( + (a_Request.GetContentType() == "application/x-www-form-urlencoded") || + (a_Request.GetContentType() == "multipart/form-data") || + ( + (a_Request.GetMethod() == "GET") && + (a_Request.GetURL().find('?') != AString::npos) + ) + ); + return false; +} + + + + + +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(); + + /* + size_t len = m_IncomingData.size(); + if (len == 0) + { + // No values in the form, consider this valid, too. + return; + } + size_t len1 = len - 1; + + for (size_t i = 0; i < len; ) + { + char ch = m_IncomingData[i]; + AString Name; + AString Value; + while ((i < len1) && (ch != '=') && (ch != '&')) + { + if (ch == '+') + { + ch = ' '; + } + Name.push_back(ch); + ch = m_IncomingData[++i]; + } + if (i == len1) + { + Value.push_back(ch); + } + + if (ch == '=') + { + ch = m_IncomingData[++i]; + while ((i < len1) && (ch != '&')) + { + if (ch == '+') + { + ch = ' '; + } + Value.push_back(ch); + ch = m_IncomingData[++i]; + } + if (i == len1) + { + Value.push_back(ch); + } + } + (*this)[URLDecode(Name)] = URLDecode(Value); + if (ch == '&') + { + ++i; + } + } // for i - m_IncomingData[] + */ +} + + + + + +void cHTTPFormParser::ParseMultipart(void) +{ + // TODO +} + + + + diff --git a/source/HTTPServer/HTTPFormParser.h b/source/HTTPServer/HTTPFormParser.h new file mode 100644 index 000000000..72a7dfc05 --- /dev/null +++ b/source/HTTPServer/HTTPFormParser.h @@ -0,0 +1,64 @@ + +// HTTPFormParser.h + +// Declares the cHTTPFormParser class representing a parser for forms sent over HTTP + + + + +#pragma once + + + + + +// fwd: +class cHTTPRequest; + + + + + +class cHTTPFormParser : + public std::map +{ +public: + cHTTPFormParser(cHTTPRequest & a_Request); + + /// 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/*". Currently unsupported + }; + + /// The kind of the parser (decided in the constructor, used in Parse() + eKind m_Kind; + + AString m_IncomingData; + + bool m_IsValid; + + + /// Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds) + void ParseFormUrlEncoded(void); + + /// Parses m_IncomingData as multipart data (fpkMultipart kind) + void ParseMultipart(void); +} ; + + + + diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp index 3ad838ac0..72c603295 100644 --- a/source/HTTPServer/HTTPMessage.cpp +++ b/source/HTTPServer/HTTPMessage.cpp @@ -99,6 +99,7 @@ bool cHTTPRequest::ParseHeaders(const char * a_IncomingData, size_t a_IdxEnd) + size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd) { // Ignore the initial CRLFs (HTTP spec's "should") diff --git a/source/HTTPServer/HTTPMessage.h b/source/HTTPServer/HTTPMessage.h index 7b1a27eaa..1c2514739 100644 --- a/source/HTTPServer/HTTPMessage.h +++ b/source/HTTPServer/HTTPMessage.h @@ -71,6 +71,12 @@ public: /// 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; } + /// 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; } diff --git a/source/HTTPServer/HTTPServer.cpp b/source/HTTPServer/HTTPServer.cpp index 8494d6fce..86fd545f6 100644 --- a/source/HTTPServer/HTTPServer.cpp +++ b/source/HTTPServer/HTTPServer.cpp @@ -7,6 +7,7 @@ #include "HTTPServer.h" #include "HTTPMessage.h" #include "HTTPConnection.h" +#include "HTTPFormParser.h" @@ -27,18 +28,49 @@ class cDebugCallbacks : { virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override { - // Nothing needed + if (cHTTPFormParser::HasFormData(a_Request)) + { + a_Request.SetUserData(new cHTTPFormParser(a_Request)); + } } virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) override { - // TODO + 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"); + } + cHTTPResponse Resp; Resp.SetContentType("text/plain"); a_Connection.Send(Resp); -- cgit v1.2.3 From b883a0b514f91e62dd0be4924e609b1bb0b53f4c Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sat, 28 Sep 2013 20:06:35 +0200 Subject: Fixed recognition of multipart-form-data forms. --- source/HTTPServer/HTTPFormParser.cpp | 4 ++-- source/HTTPServer/HTTPServer.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'source') diff --git a/source/HTTPServer/HTTPFormParser.cpp b/source/HTTPServer/HTTPFormParser.cpp index 3412bcc94..6f6dc02b2 100644 --- a/source/HTTPServer/HTTPFormParser.cpp +++ b/source/HTTPServer/HTTPFormParser.cpp @@ -34,7 +34,7 @@ cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) : m_Kind = fpkFormUrlEncoded; return; } - if (a_Request.GetContentType() == "multipart/form-data") + if (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0) { m_Kind = fpkMultipart; return; @@ -98,7 +98,7 @@ bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request) { return ( (a_Request.GetContentType() == "application/x-www-form-urlencoded") || - (a_Request.GetContentType() == "multipart/form-data") || + (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0) || ( (a_Request.GetMethod() == "GET") && (a_Request.GetURL().find('?') != AString::npos) diff --git a/source/HTTPServer/HTTPServer.cpp b/source/HTTPServer/HTTPServer.cpp index 86fd545f6..ac21acb24 100644 --- a/source/HTTPServer/HTTPServer.cpp +++ b/source/HTTPServer/HTTPServer.cpp @@ -69,6 +69,7 @@ class cDebugCallbacks : Resp.SetContentType("text/plain"); a_Connection.Send(Resp); a_Connection.Send("Form parsing failed"); + return; } cHTTPResponse Resp; -- cgit v1.2.3 From bb0fb0aa3055d797e58bc17d515e55a447c9e6a3 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sat, 28 Sep 2013 23:02:16 +0200 Subject: Improved the HTTPFormParser code. No change to the functionality. --- source/HTTPServer/HTTPFormParser.cpp | 69 +++++++----------------------------- source/HTTPServer/HTTPFormParser.h | 4 +++ 2 files changed, 16 insertions(+), 57 deletions(-) (limited to 'source') diff --git a/source/HTTPServer/HTTPFormParser.cpp b/source/HTTPServer/HTTPFormParser.cpp index 6f6dc02b2..631424391 100644 --- a/source/HTTPServer/HTTPFormParser.cpp +++ b/source/HTTPServer/HTTPFormParser.cpp @@ -11,6 +11,13 @@ +AString cHTTPFormParser::m_FormURLEncoded("application/x-www-form-urlencoded"); +AString cHTTPFormParser::m_MultipartFormData("multipart/form-data"); + + + + + cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) : m_IsValid(true) { @@ -29,12 +36,12 @@ cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) : } if ((a_Request.GetMethod() == "POST") || (a_Request.GetMethod() == "PUT")) { - if (a_Request.GetContentType() == "application/x-www-form-urlencoded") + if (a_Request.GetContentType() == m_FormURLEncoded) { m_Kind = fpkFormUrlEncoded; return; } - if (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0) + if (a_Request.GetContentType().substr(0, m_MultipartFormData.length()) == m_MultipartFormData) { m_Kind = fpkMultipart; return; @@ -96,9 +103,10 @@ bool cHTTPFormParser::Finish(void) bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request) { + const AString & ContentType = a_Request.GetContentType(); return ( - (a_Request.GetContentType() == "application/x-www-form-urlencoded") || - (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0) || + (ContentType == m_FormURLEncoded) || + (ContentType.substr(0, m_MultipartFormData.length()) == m_MultipartFormData) || ( (a_Request.GetMethod() == "GET") && (a_Request.GetURL().find('?') != AString::npos) @@ -142,59 +150,6 @@ void cHTTPFormParser::ParseFormUrlEncoded(void) } } // for itr - Lines[] m_IncomingData.clear(); - - /* - size_t len = m_IncomingData.size(); - if (len == 0) - { - // No values in the form, consider this valid, too. - return; - } - size_t len1 = len - 1; - - for (size_t i = 0; i < len; ) - { - char ch = m_IncomingData[i]; - AString Name; - AString Value; - while ((i < len1) && (ch != '=') && (ch != '&')) - { - if (ch == '+') - { - ch = ' '; - } - Name.push_back(ch); - ch = m_IncomingData[++i]; - } - if (i == len1) - { - Value.push_back(ch); - } - - if (ch == '=') - { - ch = m_IncomingData[++i]; - while ((i < len1) && (ch != '&')) - { - if (ch == '+') - { - ch = ' '; - } - Value.push_back(ch); - ch = m_IncomingData[++i]; - } - if (i == len1) - { - Value.push_back(ch); - } - } - (*this)[URLDecode(Name)] = URLDecode(Value); - if (ch == '&') - { - ++i; - } - } // for i - m_IncomingData[] - */ } diff --git a/source/HTTPServer/HTTPFormParser.h b/source/HTTPServer/HTTPFormParser.h index 72a7dfc05..01446e865 100644 --- a/source/HTTPServer/HTTPFormParser.h +++ b/source/HTTPServer/HTTPFormParser.h @@ -50,6 +50,10 @@ protected: AString m_IncomingData; bool m_IsValid; + + /// Simple static objects to hold the various strings for comparison with request's content-type + static AString m_FormURLEncoded; + static AString m_MultipartFormData; /// Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds) -- cgit v1.2.3 From e31343297ee648c799a4d30b578259719f21ede5 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Mon, 30 Sep 2013 19:59:40 +0200 Subject: Added StrToLower(), URLDecode() and ReplaceAllCharOccurrences(). --- source/StringUtils.cpp | 17 +++++++++++++++++ source/StringUtils.h | 3 +++ 2 files changed, 20 insertions(+) (limited to 'source') diff --git a/source/StringUtils.cpp b/source/StringUtils.cpp index d53d25866..c62bb3acb 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 diff --git a/source/StringUtils.h b/source/StringUtils.h index 929e6fd5b..e35e58c9f 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 -- cgit v1.2.3 From 58f5ac84aba8f1f95a1c39721bd18080fb3c455a Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 4 Oct 2013 09:20:15 +0200 Subject: Added cNameValueParser. --- source/HTTPServer/NameValueParser.cpp | 412 ++++++++++++++++++++++++++++++++++ source/HTTPServer/NameValueParser.h | 70 ++++++ 2 files changed, 482 insertions(+) create mode 100644 source/HTTPServer/NameValueParser.cpp create mode 100644 source/HTTPServer/NameValueParser.h (limited to 'source') diff --git a/source/HTTPServer/NameValueParser.cpp b/source/HTTPServer/NameValueParser.cpp new file mode 100644 index 000000000..a27f07d19 --- /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 000000000..111cb6052 --- /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 + 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; + + +} ; + + + + -- cgit v1.2.3 From d8229a55316183b523c61f28f88d64e6c8f3a96a Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 4 Oct 2013 09:30:15 +0200 Subject: Added cEnvelopeParser and cMultipartParser. --- source/HTTPServer/EnvelopeParser.cpp | 132 ++++++++++++++++++ source/HTTPServer/EnvelopeParser.h | 69 +++++++++ source/HTTPServer/MultipartParser.cpp | 255 ++++++++++++++++++++++++++++++++++ source/HTTPServer/MultipartParser.h | 76 ++++++++++ 4 files changed, 532 insertions(+) create mode 100644 source/HTTPServer/EnvelopeParser.cpp create mode 100644 source/HTTPServer/EnvelopeParser.h create mode 100644 source/HTTPServer/MultipartParser.cpp create mode 100644 source/HTTPServer/MultipartParser.h (limited to 'source') diff --git a/source/HTTPServer/EnvelopeParser.cpp b/source/HTTPServer/EnvelopeParser.cpp new file mode 100644 index 000000000..8dbe05f14 --- /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 000000000..6430fbebf --- /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/MultipartParser.cpp b/source/HTTPServer/MultipartParser.cpp new file mode 100644 index 000000000..7df151dc3 --- /dev/null +++ b/source/HTTPServer/MultipartParser.cpp @@ -0,0 +1,255 @@ + +// 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()); + 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 000000000..d853929ed --- /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; +} ; + + + + -- cgit v1.2.3 From 9a33732f6afc31d3682d7c2a3259f4c1986beb26 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 4 Oct 2013 13:02:56 +0200 Subject: Fixed MultiPartParser's boundary parsing. --- source/HTTPServer/MultipartParser.cpp | 1 + source/HTTPServer/NameValueParser.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'source') diff --git a/source/HTTPServer/MultipartParser.cpp b/source/HTTPServer/MultipartParser.cpp index 7df151dc3..b49f6ec07 100644 --- a/source/HTTPServer/MultipartParser.cpp +++ b/source/HTTPServer/MultipartParser.cpp @@ -116,6 +116,7 @@ cMultipartParser::cMultipartParser(const AString & a_ContentType, cCallbacks & a // Find the multipart boundary: ContentType.erase(0, idxSC + 1); cNameValueParser CTParser(ContentType.c_str(), ContentType.size()); + CTParser.Finish(); if (!CTParser.IsValid()) { m_IsValid = false; diff --git a/source/HTTPServer/NameValueParser.h b/source/HTTPServer/NameValueParser.h index 111cb6052..07dc0b942 100644 --- a/source/HTTPServer/NameValueParser.h +++ b/source/HTTPServer/NameValueParser.h @@ -20,7 +20,7 @@ public: /// Creates an empty parser cNameValueParser(bool a_AllowsKeyOnly = true); - /// Creates an empty parser, then parses the data given + /// 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 -- cgit v1.2.3 From 1012fd82fda9e9bc75d2308a3c68cb3b3738bf1b Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 4 Oct 2013 13:07:57 +0200 Subject: HTTP Server can now parse multipart/form-data forms; better architecture. --- source/HTTPServer/HTTPConnection.cpp | 38 +++---- source/HTTPServer/HTTPConnection.h | 2 +- source/HTTPServer/HTTPFormParser.cpp | 143 ++++++++++++++++++++++--- source/HTTPServer/HTTPFormParser.h | 59 +++++++++-- source/HTTPServer/HTTPMessage.cpp | 198 ++++++++++++++--------------------- source/HTTPServer/HTTPMessage.h | 41 +++++--- source/HTTPServer/HTTPServer.cpp | 22 +++- 7 files changed, 318 insertions(+), 185 deletions(-) (limited to 'source') diff --git a/source/HTTPServer/HTTPConnection.cpp b/source/HTTPServer/HTTPConnection.cpp index c36b07d3d..61f4b3a31 100644 --- a/source/HTTPServer/HTTPConnection.cpp +++ b/source/HTTPServer/HTTPConnection.cpp @@ -108,22 +108,13 @@ void cHTTPConnection::DataReceived(const char * a_Data, int a_Size) { 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) + if (m_CurrentRequest == NULL) { - return; + m_CurrentRequest = new cHTTPRequest; } - m_CurrentRequest = new cHTTPRequest; - if (!m_CurrentRequest->ParseHeaders(m_IncomingHeaderData.c_str(), idxEnd + 2)) + + int BytesConsumed = m_CurrentRequest->ParseHeaders(a_Data, a_Size); + if (BytesConsumed < 0) { delete m_CurrentRequest; m_CurrentRequest = NULL; @@ -131,20 +122,29 @@ void cHTTPConnection::DataReceived(const char * a_Data, int a_Size) 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 (m_IncomingHeaderData.size() > idxEnd + 4) + if (a_Size > BytesConsumed) { - m_IncomingHeaderData.erase(0, idxEnd + 4); - DataReceived(m_IncomingHeaderData.c_str(), m_IncomingHeaderData.size()); - m_IncomingHeaderData.clear(); + DataReceived(a_Data + BytesConsumed, a_Size - BytesConsumed); } else { - m_IncomingHeaderData.clear(); DataReceived("", 0); // If the request has zero body length, let it be processed right-away } break; diff --git a/source/HTTPServer/HTTPConnection.h b/source/HTTPServer/HTTPConnection.h index 46c36a8a2..9e05d342b 100644 --- a/source/HTTPServer/HTTPConnection.h +++ b/source/HTTPServer/HTTPConnection.h @@ -31,7 +31,7 @@ public: enum eState { - wcsRecvHeaders, ///< Receiving request headers (m_CurrentRequest == NULL) + 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) diff --git a/source/HTTPServer/HTTPFormParser.cpp b/source/HTTPServer/HTTPFormParser.cpp index 631424391..85a789f7d 100644 --- a/source/HTTPServer/HTTPFormParser.cpp +++ b/source/HTTPServer/HTTPFormParser.cpp @@ -6,19 +6,15 @@ #include "Globals.h" #include "HTTPFormParser.h" #include "HTTPMessage.h" +#include "MultipartParser.h" +#include "NameValueParser.h" -AString cHTTPFormParser::m_FormURLEncoded("application/x-www-form-urlencoded"); -AString cHTTPFormParser::m_MultipartFormData("multipart/form-data"); - - - - - -cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) : +cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks) : + m_Callbacks(a_Callbacks), m_IsValid(true) { if (a_Request.GetMethod() == "GET") @@ -36,14 +32,15 @@ cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) : } if ((a_Request.GetMethod() == "POST") || (a_Request.GetMethod() == "PUT")) { - if (a_Request.GetContentType() == m_FormURLEncoded) + if (a_Request.GetContentType() == "application/x-www-form-urlencoded") { m_Kind = fpkFormUrlEncoded; return; } - if (a_Request.GetContentType().substr(0, m_MultipartFormData.length()) == m_MultipartFormData) + if (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0) { m_Kind = fpkMultipart; + BeginMultipart(a_Request); return; } } @@ -56,18 +53,24 @@ cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) : void cHTTPFormParser::Parse(const char * a_Data, int a_Size) { - m_IncomingData.append(a_Data, 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: { - ParseMultipart(); + ASSERT(m_MultipartParser.get() != NULL); + m_MultipartParser->Parse(a_Data, a_Size); break; } default: @@ -105,8 +108,8 @@ bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request) { const AString & ContentType = a_Request.GetContentType(); return ( - (ContentType == m_FormURLEncoded) || - (ContentType.substr(0, m_MultipartFormData.length()) == m_MultipartFormData) || + (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) @@ -119,6 +122,16 @@ bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request) +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() @@ -156,9 +169,107 @@ void cHTTPFormParser::ParseFormUrlEncoded(void) -void cHTTPFormParser::ParseMultipart(void) +void cHTTPFormParser::OnPartStart(void) { - // TODO + 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 index 01446e865..b92ef9d3c 100644 --- a/source/HTTPServer/HTTPFormParser.h +++ b/source/HTTPServer/HTTPFormParser.h @@ -8,6 +8,8 @@ #pragma once +#include "MultipartParser.h" + @@ -20,10 +22,25 @@ class cHTTPRequest; class cHTTPFormParser : - public std::map + public std::map, + public cMultipartParser::cCallbacks { public: - cHTTPFormParser(cHTTPRequest & a_Request); + 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); @@ -41,26 +58,48 @@ protected: { 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/*". Currently unsupported + 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; - /// Simple static objects to hold the various strings for comparison with request's content-type - static AString m_FormURLEncoded; - static AString m_MultipartFormData; - + /// 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); - /// Parses m_IncomingData as multipart data (fpkMultipart kind) - void ParseMultipart(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 index 72c603295..ed5c87e84 100644 --- a/source/HTTPServer/HTTPMessage.cpp +++ b/source/HTTPServer/HTTPMessage.cpp @@ -10,11 +10,22 @@ +// 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_Kind(a_Kind), + m_ContentLength(-1) { } @@ -24,10 +35,12 @@ cHTTPMessage::cHTTPMessage(eKind a_Kind) : void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) { - cNameValueMap::iterator itr = m_Headers.find(a_Key); + AString Key = a_Key; + StrToLower(Key); + cNameValueMap::iterator itr = m_Headers.find(Key); if (itr == m_Headers.end()) { - m_Headers[a_Key] = a_Value; + m_Headers[Key] = a_Value; } else { @@ -37,13 +50,13 @@ void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) } // Special processing for well-known headers: - if (a_Key == "Content-Type") + if (Key == "content-type") { - m_ContentType = m_Headers["Content-Type"]; + m_ContentType = m_Headers[Key]; } - else if (a_Key == "Content-Length") + else if (Key == "content-length") { - m_ContentLength = atoi(m_Headers["Content-Length"].c_str()); + m_ContentLength = atoi(m_Headers[Key].c_str()); } } @@ -56,6 +69,8 @@ void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) cHTTPRequest::cHTTPRequest(void) : super(mkRequest), + m_EnvelopeParser(*this), + m_IsValid(true), m_UserData(NULL) { } @@ -64,66 +79,75 @@ cHTTPRequest::cHTTPRequest(void) : -bool cHTTPRequest::ParseHeaders(const char * a_IncomingData, size_t a_IdxEnd) +int cHTTPRequest::ParseHeaders(const char * a_Data, int a_Size) { - // The first line contains the method and the URL: - size_t Next = ParseRequestLine(a_IncomingData, a_IdxEnd); - if (Next == AString::npos) + if (!m_IsValid) { - return false; + return -1; } - // The following lines contain headers: - AString Key; - const char * Data = a_IncomingData + Next; - size_t End = a_IdxEnd - Next; - while (End > 0) + if (m_Method.empty()) { - Next = ParseHeaderField(Data, End, Key); - if (Next == AString::npos) + // 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) { - return false; + m_IsValid = false; + return res2; } - ASSERT(End >= Next); - Data += Next; - End -= Next; + return res2 + res; } - if (!HasReceivedContentLength()) + if (m_EnvelopeParser.IsInHeaders()) { - SetContentLength(0); + int res = m_EnvelopeParser.Parse(a_Data, a_Size); + if (res < 0) + { + m_IsValid = false; + } + return res; } - return true; + return 0; } -size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd) +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 < a_IdxEnd) && + (LineStart < IdxEnd) && ( - (a_Data[LineStart] == '\r') || - (a_Data[LineStart] == '\n') + (m_IncomingHeaderData[LineStart] == '\r') || + (m_IncomingHeaderData[LineStart] == '\n') ) ) { LineStart++; } - if (LineStart >= a_IdxEnd) + if (LineStart >= IdxEnd) { - return AString::npos; + m_IsValid = false; + return -1; } - size_t Last = LineStart; int NumSpaces = 0; - for (size_t i = LineStart; i < a_IdxEnd; i++) + size_t MethodEnd = 0; + size_t URLEnd = 0; + for (size_t i = LineStart; i < IdxEnd; i++) { - switch (a_Data[i]) + switch (m_IncomingHeaderData[i]) { case ' ': { @@ -131,124 +155,56 @@ size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd) { case 0: { - m_Method.assign(a_Data, Last, i - Last); + MethodEnd = i; break; } case 1: { - m_URL.assign(a_Data, Last, i - Last); + URLEnd = i; break; } default: { // Too many spaces in the request - return AString::npos; + m_IsValid = false; + return -1; } } - Last = i + 1; NumSpaces += 1; break; } case '\n': { - if ((i == 0) || (a_Data[i - 1] != '\r') || (NumSpaces != 2) || (i < Last + 7)) + 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 - return AString::npos; + m_IsValid = false; + return -1; } // Check that there's HTTP/version at the end - if (strncmp(a_Data + Last, "HTTP/1.", 7) != 0) - { - return AString::npos; - } - return i + 1; - } - } // 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')) + if (strncmp(a_Data + URLEnd + 1, "HTTP/1.", 7) != 0) { - // Invalid header field - no colon or no CR before LF - return AString::npos; + m_IsValid = false; + return -1; } - AString Value(a_Data, ValueIdx + 1, i - ValueIdx - 2); - AddHeader(Key, Value); - a_Key = Key; + m_Method = m_IncomingHeaderData.substr(LineStart, MethodEnd - LineStart); + m_URL = m_IncomingHeaderData.substr(MethodEnd + 1, URLEnd - MethodEnd - 1); 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) + } // switch (m_IncomingHeaderData[i]) } // for i - m_IncomingHeaderData[] - // No header found, return the end-of-data index: - return a_IdxEnd; + + // CRLF hasn't been encountered yet, consider all data consumed + return a_Size; } -size_t cHTTPRequest::ParseHeaderFieldContinuation(const char * a_Data, size_t a_IdxEnd, AString & a_Key) +void cHTTPRequest::OnHeaderLine(const AString & a_Key, const AString & a_Value) { - 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; + AddHeader(a_Key, a_Value); } diff --git a/source/HTTPServer/HTTPMessage.h b/source/HTTPServer/HTTPMessage.h index 1c2514739..ef8e12ca4 100644 --- a/source/HTTPServer/HTTPMessage.h +++ b/source/HTTPServer/HTTPMessage.h @@ -9,6 +9,8 @@ #pragma once +#include "EnvelopeParser.h" + @@ -58,15 +60,18 @@ protected: class cHTTPRequest : - public cHTTPMessage + public cHTTPMessage, + protected cEnvelopeParser::cCallbacks { 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); + /** 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); } @@ -83,7 +88,19 @@ public: /// 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(); } + 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; @@ -94,21 +111,13 @@ protected: void * m_UserData; - /** Parses the RequestLine out of a_Data, up to index a_IdxEnd - Returns the index to the next line, or npos if invalid request + /** Parses the incoming data for the first line (RequestLine) + Returns the number of bytes consumed, or -1 for an error */ - size_t ParseRequestLine(const char * a_Data, size_t a_IdxEnd); + int ParseRequestLine(const char * a_Data, int a_Size); - /** 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); + // cEnvelopeParser::cCallbacks overrides: + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; } ; diff --git a/source/HTTPServer/HTTPServer.cpp b/source/HTTPServer/HTTPServer.cpp index ac21acb24..4102d1047 100644 --- a/source/HTTPServer/HTTPServer.cpp +++ b/source/HTTPServer/HTTPServer.cpp @@ -24,13 +24,14 @@ class cDebugCallbacks : - public cHTTPServer::cCallbacks + 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)); + a_Request.SetUserData(new cHTTPFormParser(a_Request, *this)); } } @@ -79,6 +80,23 @@ class cDebugCallbacks : } + 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; -- cgit v1.2.3 From db3d83b38dd61b90466a0721fa9104e742f3fb8b Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 4 Oct 2013 20:28:30 +0200 Subject: Added Basic auth support to cHTTPRequest. --- source/HTTPServer/HTTPConnection.cpp | 18 ++++++++++ source/HTTPServer/HTTPConnection.h | 3 ++ source/HTTPServer/HTTPMessage.cpp | 17 ++++++++- source/HTTPServer/HTTPMessage.h | 18 ++++++++++ source/HTTPServer/HTTPServer.cpp | 10 ++++++ source/StringUtils.cpp | 68 ++++++++++++++++++++++++++++++++++++ source/StringUtils.h | 3 ++ 7 files changed, 136 insertions(+), 1 deletion(-) (limited to 'source') diff --git a/source/HTTPServer/HTTPConnection.cpp b/source/HTTPServer/HTTPConnection.cpp index 61f4b3a31..e3a6be494 100644 --- a/source/HTTPServer/HTTPConnection.cpp +++ b/source/HTTPServer/HTTPConnection.cpp @@ -26,6 +26,18 @@ void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Re { AppendPrintf(m_OutgoingData, "%d %s\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\n\r\n", a_Realm.c_str()); + m_HTTPServer.NotifyConnectionWrite(*this); + m_State = wcsRecvHeaders; } @@ -73,6 +85,12 @@ 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": diff --git a/source/HTTPServer/HTTPConnection.h b/source/HTTPServer/HTTPConnection.h index 9e05d342b..941a29000 100644 --- a/source/HTTPServer/HTTPConnection.h +++ b/source/HTTPServer/HTTPConnection.h @@ -43,6 +43,9 @@ public: /// 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); diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp index ed5c87e84..6cf9464a3 100644 --- a/source/HTTPServer/HTTPMessage.cpp +++ b/source/HTTPServer/HTTPMessage.cpp @@ -71,7 +71,8 @@ cHTTPRequest::cHTTPRequest(void) : super(mkRequest), m_EnvelopeParser(*this), m_IsValid(true), - m_UserData(NULL) + m_UserData(NULL), + m_HasAuth(false) { } @@ -204,6 +205,20 @@ int cHTTPRequest::ParseRequestLine(const char * a_Data, int 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); } diff --git a/source/HTTPServer/HTTPMessage.h b/source/HTTPServer/HTTPMessage.h index ef8e12ca4..151eb468f 100644 --- a/source/HTTPServer/HTTPMessage.h +++ b/source/HTTPServer/HTTPMessage.h @@ -91,6 +91,15 @@ public: /// 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; @@ -110,6 +119,15 @@ protected: /// 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 diff --git a/source/HTTPServer/HTTPServer.cpp b/source/HTTPServer/HTTPServer.cpp index 4102d1047..43bb751c4 100644 --- a/source/HTTPServer/HTTPServer.cpp +++ b/source/HTTPServer/HTTPServer.cpp @@ -73,6 +73,16 @@ class cDebugCallbacks : 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); diff --git a/source/StringUtils.cpp b/source/StringUtils.cpp index c62bb3acb..530eda086 100644 --- a/source/StringUtils.cpp +++ b/source/StringUtils.cpp @@ -745,3 +745,71 @@ AString ReplaceAllCharOccurrences(const AString & a_String, char a_From, char a_ + +/// 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 ERROR_SUCCESS; + } + } + res.resize(o >> 3); + return res;} + + + + diff --git a/source/StringUtils.h b/source/StringUtils.h index e35e58c9f..ec9ba96ce 100644 --- a/source/StringUtils.h +++ b/source/StringUtils.h @@ -81,6 +81,9 @@ extern AString URLDecode(const AString & a_String); // Cannot export to Lua aut /// 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 -- cgit v1.2.3 From 2b8bddbdc315abf109c9767025448d64cb5c8224 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sat, 5 Oct 2013 21:52:14 +0200 Subject: cHTTPConnection sends Content-Length with HTTP errors, too. --- source/HTTPServer/HTTPConnection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'source') diff --git a/source/HTTPServer/HTTPConnection.cpp b/source/HTTPServer/HTTPConnection.cpp index e3a6be494..2addf4cfc 100644 --- a/source/HTTPServer/HTTPConnection.cpp +++ b/source/HTTPServer/HTTPConnection.cpp @@ -24,7 +24,7 @@ cHTTPConnection::cHTTPConnection(cHTTPServer & a_HTTPServer) : 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()); + 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; } @@ -35,7 +35,7 @@ void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Re void cHTTPConnection::SendNeedAuth(const AString & a_Realm) { - AppendPrintf(m_OutgoingData, "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\n\r\n", a_Realm.c_str()); + 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; } -- cgit v1.2.3 From 20d07a683f589753f311302580ba0fa9e2ca2c4e Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sat, 5 Oct 2013 21:52:45 +0200 Subject: Fixed Base64Decode() returning wrong value. --- source/StringUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'source') diff --git a/source/StringUtils.cpp b/source/StringUtils.cpp index 530eda086..d52b1323f 100644 --- a/source/StringUtils.cpp +++ b/source/StringUtils.cpp @@ -804,7 +804,7 @@ AString Base64Decode(const AString & a_Base64String) { // Error while decoding, invalid input. Return as much as we've decoded: res.resize(o >> 3); - return ERROR_SUCCESS; + return res; } } res.resize(o >> 3); -- cgit v1.2.3 From b5c90d7b20fede4e643e96417684c7c009d063cb Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sat, 5 Oct 2013 23:08:16 +0200 Subject: WebAdmin uses the new HTTP functionality. This is a partial implementation of #183. --- source/Bindings.cpp | 46 +--- source/Bindings.h | 2 +- source/HTTPServer/HTTPFormParser.cpp | 5 +- source/HTTPServer/HTTPMessage.cpp | 17 ++ source/HTTPServer/HTTPMessage.h | 3 + source/HTTPServer/HTTPServer.cpp | 15 +- source/HTTPServer/HTTPServer.h | 3 +- source/Root.cpp | 15 +- source/WebAdmin.cpp | 493 ++++++++++++++++++++--------------- source/WebAdmin.h | 116 +++++++-- 10 files changed, 414 insertions(+), 301 deletions(-) (limited to 'source') diff --git a/source/Bindings.cpp b/source/Bindings.cpp index 24289865a..7895fe16b 100644 --- a/source/Bindings.cpp +++ b/source/Bindings.cpp @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 09/21/13 17:37:22. +** Generated automatically by tolua++-1.0.92 on 10/05/13 18:34:12. */ #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"); @@ -18666,38 +18667,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) @@ -30233,10 +30202,9 @@ 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,"GetBaseURL",tolua_AllToLua_cWebAdmin_GetBaseURL00); tolua_endmodule(tolua_S); diff --git a/source/Bindings.h b/source/Bindings.h index 0fa3665a3..933cd2ffd 100644 --- a/source/Bindings.h +++ b/source/Bindings.h @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 09/21/13 17:37:22. +** Generated automatically by tolua++-1.0.92 on 10/05/13 18:34:12. */ /* Exported function */ diff --git a/source/HTTPServer/HTTPFormParser.cpp b/source/HTTPServer/HTTPFormParser.cpp index 85a789f7d..7db7b4e6d 100644 --- a/source/HTTPServer/HTTPFormParser.cpp +++ b/source/HTTPServer/HTTPFormParser.cpp @@ -32,7 +32,7 @@ cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callba } if ((a_Request.GetMethod() == "POST") || (a_Request.GetMethod() == "PUT")) { - if (a_Request.GetContentType() == "application/x-www-form-urlencoded") + if (strncmp(a_Request.GetContentType().c_str(), "application/x-www-form-urlencoded", 33) == 0) { m_Kind = fpkFormUrlEncoded; return; @@ -44,7 +44,8 @@ cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callba return; } } - ASSERT(!"Unhandled request method"); + // Invalid method / content type combination, this is not a HTTP form + m_IsValid = false; } diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp index 6cf9464a3..ab23866e6 100644 --- a/source/HTTPServer/HTTPMessage.cpp +++ b/source/HTTPServer/HTTPMessage.cpp @@ -120,6 +120,23 @@ int cHTTPRequest::ParseHeaders(const char * a_Data, int a_Size) +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); diff --git a/source/HTTPServer/HTTPMessage.h b/source/HTTPServer/HTTPMessage.h index 151eb468f..f5284c535 100644 --- a/source/HTTPServer/HTTPMessage.h +++ b/source/HTTPServer/HTTPMessage.h @@ -82,6 +82,9 @@ public: /// 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; } diff --git a/source/HTTPServer/HTTPServer.cpp b/source/HTTPServer/HTTPServer.cpp index 43bb751c4..7e216c629 100644 --- a/source/HTTPServer/HTTPServer.cpp +++ b/source/HTTPServer/HTTPServer.cpp @@ -127,25 +127,16 @@ cHTTPServer::cHTTPServer(void) : -bool cHTTPServer::Initialize(cIniFile & a_IniFile) +bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6) { - 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; + HasAnyPort = m_ListenThreadIPv4.Initialize(a_PortsIPv4); + HasAnyPort = m_ListenThreadIPv6.Initialize(a_PortsIPv6) || HasAnyPort; if (!HasAnyPort) { - LOG("WebAdmin is disabled"); return false; } - // DEBUG: - return Start(g_DebugCallbacks); - return true; } diff --git a/source/HTTPServer/HTTPServer.h b/source/HTTPServer/HTTPServer.h index efe60809d..2ecb75fdd 100644 --- a/source/HTTPServer/HTTPServer.h +++ b/source/HTTPServer/HTTPServer.h @@ -51,7 +51,8 @@ public: cHTTPServer(void); - bool Initialize(cIniFile & a_IniFile); + /// 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); diff --git a/source/Root.cpp b/source/Root.cpp index 821dd0928..57e5dfcc4 100644 --- a/source/Root.cpp +++ b/source/Root.cpp @@ -130,15 +130,9 @@ void cRoot::Start(void) } IniFile.WriteFile(); - cIniFile WebIniFile("webadmin.ini"); - if (!WebIniFile.ReadFile()) - { - LOGWARNING("webadmin.ini inaccessible, wabadmin is disabled"); - } - else - { - m_HTTPServer.Initialize(WebIniFile); - } + LOG("Initialising WebAdmin..."); + m_WebAdmin = new cWebAdmin(); + m_WebAdmin->Init(); LOG("Loading settings..."); m_GroupManager = new cGroupManager(); @@ -167,6 +161,9 @@ void cRoot::Start(void) LOG("Starting server..."); m_Server->Start(); + + LOG("Starting WebAdmin..."); + m_WebAdmin->Start(); #if !defined(ANDROID_NDK) LOG("Starting InputThread..."); diff --git a/source/WebAdmin.cpp b/source/WebAdmin.cpp index e53fb84e6..44ba38c8d 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 @@ -55,14 +56,13 @@ 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 ); } @@ -73,10 +73,6 @@ cWebAdmin::~cWebAdmin() { WebAdmin = 0; - m_WebServer->Stop(); - - delete m_WebServer; - delete m_IniFile; m_Event->Wait(); delete m_Event; @@ -105,189 +101,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_ == "/") - { - r->answer_ += "

MCServer WebAdmin

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

Bad request

"; - r->answer_ += "

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

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

Bad request

"; - return; + return false; } - if (!r->authentication_given_) - { - r->answer_ += "no auth"; - r->auth_realm_ = "MCServer WebAdmin"; - } - - bool bDontShowTemplate = false; - if (Split[0] == "~webadmin") - { - bDontShowTemplate = true; - } + AString PortsIPv4 = m_IniFile.GetValue("WebAdmin", "Port", "8080"); + AString PortsIPv6 = m_IniFile.GetValue("WebAdmin", "PortsIPv6", ""); - 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 + if (!m_HTTPServer.Initialize(PortsIPv4, PortsIPv6)) { - 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,75 +139,185 @@ bool cWebAdmin::Init(int a_Port) m_TemplateScript.Close(); } - - LOG("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; + return m_HTTPServer.Start(*this); } -#ifdef _WIN32 -DWORD WINAPI cWebAdmin::ListenThread(LPVOID lpParam) -#else -void *cWebAdmin::ListenThread( void *lpParam ) -#endif +AString cWebAdmin::GetTemplate() { - cWebAdmin* self = (cWebAdmin*)lpParam; + AString retVal = ""; - self->m_WebServer = new webserver(self->m_Port, Request_Handler ); - if (!self->m_WebServer->Begin()) + char SourceFile[] = "webadmin/template.html"; + + cFile f; + if (!f.Open(SourceFile, cFile::fmRead)) { - LOGWARN("WebServer failed to start! WebAdmin is disabled"); + return ""; } - self->m_Event->Set(); - return 0; + // copy the file into the buffer: + f.ReadRestOfFile(retVal); + + return retVal; } -AString cWebAdmin::GetTemplate() +void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) { - AString retVal = ""; + if (!a_Request.HasAuth()) + { + a_Connection.SendNeedAuth("MCServer WebAdmin"); + return; + } - char SourceFile[] = "webadmin/template.html"; + // 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 (WebAdmin->m_TemplateScript.Call("ShowPage", WebAdmin, &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; - cFile f; - if (!f.Open(SourceFile, cFile::fmRead)) + for (PluginList::iterator itr = m_Plugins.begin(); itr != m_Plugins.end(); ++itr) { - return ""; + 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 + "
  • "; + } } - // copy the file into the buffer: - f.ReadRestOfFile(retVal); + sWebAdminPage Page = GetPage(TemplateRequest.Request); + AString Content = Page.Content; + FoundPlugin = Page.PluginName; + if (!Page.TabName.empty()) + { + FoundPlugin += " - " + Page.TabName; + } - return retVal; + 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 cWebAdmin::GetPage(const HTTPRequest & a_Request) { sWebAdminPage Page; AStringVector Split = StringSplit(a_Request.Path, "/"); @@ -396,6 +349,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 +462,76 @@ 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); + return; + } + if (URL == "/") + { + // The root needs no body handler and is fully handled in the OnRequestFinished() call + HandleRootRequest(a_Connection, a_Request); + return; + } + // TODO: Handle other requests +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// 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 7b710bd3b..b85d8d059 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,49 +99,85 @@ public: typedef std::list< cWebPlugin* > PluginList; - cWebAdmin( int a_Port = 8080 ); + cWebAdmin(void); ~cWebAdmin(); - 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; - - bool m_bConnected; - cSocket m_ListenSocket; +protected: + /// Common base class for request body data handlers + class cRequestData + { + public: + /// 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; + + // 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 @@ -132,9 +186,29 @@ private: 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 + + + + -- cgit v1.2.3 From fe582b69d5fd330aac4e812d3c5337ffcc8d098b Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sun, 6 Oct 2013 14:38:10 +0200 Subject: Removed remnants of the old webserver. --- source/WebAdmin.cpp | 25 ++----------------------- source/WebAdmin.h | 9 --------- 2 files changed, 2 insertions(+), 32 deletions(-) (limited to 'source') diff --git a/source/WebAdmin.cpp b/source/WebAdmin.cpp index 44ba38c8d..378a5966c 100644 --- a/source/WebAdmin.cpp +++ b/source/WebAdmin.cpp @@ -50,32 +50,11 @@ public: -cWebAdmin * WebAdmin = NULL; - - - - - cWebAdmin::cWebAdmin(void) : m_IsInitialized(false), m_TemplateScript(""), m_IniFile("webadmin.ini") { - WebAdmin = this; - m_Event = new cEvent(); -} - - - - - -cWebAdmin::~cWebAdmin() -{ - - WebAdmin = 0; - - m_Event->Wait(); - delete m_Event; } @@ -221,7 +200,7 @@ void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPReque // Try to get the template from the Lua template script if (ShouldWrapInTemplate) { - if (WebAdmin->m_TemplateScript.Call("ShowPage", WebAdmin, &TemplateRequest, cLuaState::Return, Template)) + if (m_TemplateScript.Call("ShowPage", this, &TemplateRequest, cLuaState::Return, Template)) { cHTTPResponse Resp; Resp.SetContentType("text/html"); @@ -326,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]) { diff --git a/source/WebAdmin.h b/source/WebAdmin.h index b85d8d059..271f819d6 100644 --- a/source/WebAdmin.h +++ b/source/WebAdmin.h @@ -100,7 +100,6 @@ public: cWebAdmin(void); - ~cWebAdmin(); /// Initializes the object. Returns true if successfully initialized and ready to start bool Init(void); @@ -171,8 +170,6 @@ protected: PluginList m_Plugins; - cEvent * m_Event; - /// The Lua template script to provide templates: cLuaState m_TemplateScript; @@ -180,12 +177,6 @@ protected: cHTTPServer m_HTTPServer; - #ifdef _WIN32 - static DWORD WINAPI ListenThread(LPVOID lpParam); - #else - static void * ListenThread(void * lpParam); - #endif - AString GetTemplate(void); /// Handles requests coming to the "/webadmin" or "/~webadmin" URLs -- cgit v1.2.3 From d147935853307d97d1380ecab103df4d5f51bfb6 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sun, 6 Oct 2013 15:44:40 +0200 Subject: Added proper shutdown to HTTPServer. --- source/HTTPServer/HTTPServer.cpp | 9 +++++++++ source/HTTPServer/HTTPServer.h | 1 + 2 files changed, 10 insertions(+) (limited to 'source') diff --git a/source/HTTPServer/HTTPServer.cpp b/source/HTTPServer/HTTPServer.cpp index 7e216c629..9636eb59f 100644 --- a/source/HTTPServer/HTTPServer.cpp +++ b/source/HTTPServer/HTTPServer.cpp @@ -127,6 +127,15 @@ cHTTPServer::cHTTPServer(void) : +cHTTPServer::~cHTTPServer() +{ + Stop(); +} + + + + + bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6) { bool HasAnyPort; diff --git a/source/HTTPServer/HTTPServer.h b/source/HTTPServer/HTTPServer.h index 2ecb75fdd..fea2a9029 100644 --- a/source/HTTPServer/HTTPServer.h +++ b/source/HTTPServer/HTTPServer.h @@ -50,6 +50,7 @@ public: } ; cHTTPServer(void); + ~cHTTPServer(); /// Initializes the server on the specified ports bool Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6); -- cgit v1.2.3 From 4bf596a586687de8e4abbc62afa6a7c51cdc311c Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sun, 6 Oct 2013 16:18:15 +0200 Subject: cListenThread: Fixed thread termination. --- source/OSSupport/ListenThread.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'source') diff --git a/source/OSSupport/ListenThread.cpp b/source/OSSupport/ListenThread.cpp index 0890aabc8..ba3198764 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) -- cgit v1.2.3 From f55b77a98a41ba784109842cde39ba0e1d2bc262 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sun, 6 Oct 2013 16:40:28 +0200 Subject: Fixed memory leaks in the HTTP framework --- source/HTTPServer/HTTPConnection.cpp | 25 +++++++++++++++++++++++++ source/HTTPServer/HTTPConnection.h | 4 ++++ source/HTTPServer/HTTPServer.cpp | 5 +++-- source/WebAdmin.cpp | 13 +++++++++---- source/WebAdmin.h | 2 ++ 5 files changed, 43 insertions(+), 6 deletions(-) (limited to 'source') diff --git a/source/HTTPServer/HTTPConnection.cpp b/source/HTTPServer/HTTPConnection.cpp index 2addf4cfc..68afdfc11 100644 --- a/source/HTTPServer/HTTPConnection.cpp +++ b/source/HTTPServer/HTTPConnection.cpp @@ -17,11 +17,22 @@ cHTTPConnection::cHTTPConnection(cHTTPServer & 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()); @@ -120,6 +131,19 @@ void cHTTPConnection::AwaitNextRequest(void) +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) @@ -214,6 +238,7 @@ void cHTTPConnection::SocketClosed(void) { m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); } + m_HTTPServer.CloseConnection(*this); } diff --git a/source/HTTPServer/HTTPConnection.h b/source/HTTPServer/HTTPConnection.h index 941a29000..14603bb70 100644 --- a/source/HTTPServer/HTTPConnection.h +++ b/source/HTTPServer/HTTPConnection.h @@ -39,6 +39,7 @@ public: } ; 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); @@ -61,6 +62,9 @@ public: /// 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; diff --git a/source/HTTPServer/HTTPServer.cpp b/source/HTTPServer/HTTPServer.cpp index 9636eb59f..f6f5b0f8b 100644 --- a/source/HTTPServer/HTTPServer.cpp +++ b/source/HTTPServer/HTTPServer.cpp @@ -179,9 +179,9 @@ void cHTTPServer::Stop(void) // Drop all current connections: cCSLock Lock(m_CSConnections); - for (cHTTPConnections::iterator itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr) + while (!m_Connections.empty()) { - m_SocketThreads.RemoveClient(*itr); + m_Connections.front()->Terminate(); } // for itr - m_Connections[] } @@ -213,6 +213,7 @@ void cHTTPServer::CloseConnection(cHTTPConnection & a_Connection) break; } } + delete &a_Connection; } diff --git a/source/WebAdmin.cpp b/source/WebAdmin.cpp index 378a5966c..c917ec658 100644 --- a/source/WebAdmin.cpp +++ b/source/WebAdmin.cpp @@ -488,15 +488,20 @@ void cWebAdmin::OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & ) { HandleWebadminRequest(a_Connection, a_Request); - return; } - if (URL == "/") + else if (URL == "/") { // The root needs no body handler and is fully handled in the OnRequestFinished() call HandleRootRequest(a_Connection, a_Request); - return; } - // TODO: Handle other requests + else + { + // TODO: Handle other requests + } + + // Delete any request data assigned to the request: + cRequestData * Data = (cRequestData *)(a_Request.GetUserData()); + delete Data; } diff --git a/source/WebAdmin.h b/source/WebAdmin.h index 271f819d6..16b5dd4dc 100644 --- a/source/WebAdmin.h +++ b/source/WebAdmin.h @@ -134,6 +134,8 @@ protected: 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; } ; -- cgit v1.2.3