summaryrefslogtreecommitdiffstats
path: root/source/HTTPServer
diff options
context:
space:
mode:
Diffstat (limited to 'source/HTTPServer')
-rw-r--r--source/HTTPServer/EnvelopeParser.cpp132
-rw-r--r--source/HTTPServer/EnvelopeParser.h69
-rw-r--r--source/HTTPServer/HTTPConnection.cpp247
-rw-r--r--source/HTTPServer/HTTPConnection.h101
-rw-r--r--source/HTTPServer/HTTPFormParser.cpp278
-rw-r--r--source/HTTPServer/HTTPFormParser.h107
-rw-r--r--source/HTTPServer/HTTPMessage.cpp279
-rw-r--r--source/HTTPServer/HTTPMessage.h164
-rw-r--r--source/HTTPServer/HTTPServer.cpp258
-rw-r--r--source/HTTPServer/HTTPServer.h101
-rw-r--r--source/HTTPServer/MultipartParser.cpp256
-rw-r--r--source/HTTPServer/MultipartParser.h76
-rw-r--r--source/HTTPServer/NameValueParser.cpp412
-rw-r--r--source/HTTPServer/NameValueParser.h70
14 files changed, 2550 insertions, 0 deletions
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/HTTPConnection.cpp b/source/HTTPServer/HTTPConnection.cpp
new file mode 100644
index 000000000..68afdfc11
--- /dev/null
+++ b/source/HTTPServer/HTTPConnection.cpp
@@ -0,0 +1,247 @@
+
+// HTTPConnection.cpp
+
+// Implements the cHTTPConnection class representing a single persistent connection in the HTTP server.
+
+#include "Globals.h"
+#include "HTTPConnection.h"
+#include "HTTPMessage.h"
+#include "HTTPServer.h"
+
+
+
+
+
+cHTTPConnection::cHTTPConnection(cHTTPServer & a_HTTPServer) :
+ m_HTTPServer(a_HTTPServer),
+ m_State(wcsRecvHeaders),
+ m_CurrentRequest(NULL)
+{
+ // LOGD("HTTP: New connection at %p", this);
+}
+
+
+
+
+
+cHTTPConnection::~cHTTPConnection()
+{
+ // LOGD("HTTP: Del connection at %p", this);
+}
+
+
+
+
+
+void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response)
+{
+ AppendPrintf(m_OutgoingData, "%d %s\r\nContent-Length: 0\r\n\r\n", a_StatusCode, a_Response.c_str());
+ m_HTTPServer.NotifyConnectionWrite(*this);
+ m_State = wcsRecvHeaders;
+}
+
+
+
+
+
+void cHTTPConnection::SendNeedAuth(const AString & a_Realm)
+{
+ AppendPrintf(m_OutgoingData, "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str());
+ m_HTTPServer.NotifyConnectionWrite(*this);
+ m_State = wcsRecvHeaders;
+}
+
+
+
+
+
+void cHTTPConnection::Send(const cHTTPResponse & a_Response)
+{
+ ASSERT(m_State = wcsRecvIdle);
+ a_Response.AppendToData(m_OutgoingData);
+ m_State = wcsSendingResp;
+ m_HTTPServer.NotifyConnectionWrite(*this);
+}
+
+
+
+
+
+void cHTTPConnection::Send(const void * a_Data, int a_Size)
+{
+ ASSERT(m_State == wcsSendingResp);
+ AppendPrintf(m_OutgoingData, "%x\r\n", a_Size);
+ m_OutgoingData.append((const char *)a_Data, a_Size);
+ m_OutgoingData.append("\r\n");
+ m_HTTPServer.NotifyConnectionWrite(*this);
+}
+
+
+
+
+
+void cHTTPConnection::FinishResponse(void)
+{
+ ASSERT(m_State == wcsSendingResp);
+ m_OutgoingData.append("0\r\n\r\n");
+ m_State = wcsRecvHeaders;
+ m_HTTPServer.NotifyConnectionWrite(*this);
+}
+
+
+
+
+
+void cHTTPConnection::AwaitNextRequest(void)
+{
+ switch (m_State)
+ {
+ case wcsRecvHeaders:
+ {
+ // Nothing has been received yet, or a special response was given (SendStatusAndReason() or SendNeedAuth() )
+ break;
+ }
+
+ case wcsRecvIdle:
+ {
+ // The client is waiting for a response, send an "Internal server error":
+ m_OutgoingData.append("HTTP/1.1 500 Internal Server Error\r\n\r\n");
+ m_HTTPServer.NotifyConnectionWrite(*this);
+ m_State = wcsRecvHeaders;
+ break;
+ }
+
+ case wcsSendingResp:
+ {
+ // The response headers have been sent, we need to terminate the response body:
+ m_OutgoingData.append("0\r\n\r\n");
+ m_State = wcsRecvHeaders;
+ break;
+ }
+
+ default:
+ {
+ ASSERT(!"Unhandled state recovery");
+ break;
+ }
+ }
+}
+
+
+
+
+
+void cHTTPConnection::Terminate(void)
+{
+ if (m_CurrentRequest != NULL)
+ {
+ m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
+ }
+ m_HTTPServer.CloseConnection(*this);
+}
+
+
+
+
+
+void cHTTPConnection::DataReceived(const char * a_Data, int a_Size)
+{
+ switch (m_State)
+ {
+ case wcsRecvHeaders:
+ {
+ if (m_CurrentRequest == NULL)
+ {
+ m_CurrentRequest = new cHTTPRequest;
+ }
+
+ int BytesConsumed = m_CurrentRequest->ParseHeaders(a_Data, a_Size);
+ if (BytesConsumed < 0)
+ {
+ delete m_CurrentRequest;
+ m_CurrentRequest = NULL;
+ m_State = wcsInvalid;
+ m_HTTPServer.CloseConnection(*this);
+ return;
+ }
+ if (m_CurrentRequest->IsInHeaders())
+ {
+ // The request headers are not yet complete
+ return;
+ }
+
+ // The request has finished parsing its headers successfully, notify of it:
+ m_State = wcsRecvBody;
+ m_HTTPServer.NewRequest(*this, *m_CurrentRequest);
+ m_CurrentRequestBodyRemaining = m_CurrentRequest->GetContentLength();
+ if (m_CurrentRequestBodyRemaining < 0)
+ {
+ // The body length was not specified in the request, assume zero
+ m_CurrentRequestBodyRemaining = 0;
+ }
+
+ // Process the rest of the incoming data into the request body:
+ if (a_Size > BytesConsumed)
+ {
+ DataReceived(a_Data + BytesConsumed, a_Size - BytesConsumed);
+ }
+ else
+ {
+ DataReceived("", 0); // If the request has zero body length, let it be processed right-away
+ }
+ break;
+ }
+
+ case wcsRecvBody:
+ {
+ ASSERT(m_CurrentRequest != NULL);
+ if (m_CurrentRequestBodyRemaining > 0)
+ {
+ int BytesToConsume = std::min(m_CurrentRequestBodyRemaining, a_Size);
+ m_HTTPServer.RequestBody(*this, *m_CurrentRequest, a_Data, BytesToConsume);
+ m_CurrentRequestBodyRemaining -= BytesToConsume;
+ }
+ if (m_CurrentRequestBodyRemaining == 0)
+ {
+ m_State = wcsRecvIdle;
+ m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
+ delete m_CurrentRequest;
+ m_CurrentRequest = NULL;
+ }
+ break;
+ }
+
+ default:
+ {
+ // TODO: Should we be receiving data in this state?
+ break;
+ }
+ }
+}
+
+
+
+
+
+void cHTTPConnection::GetOutgoingData(AString & a_Data)
+{
+ std::swap(a_Data, m_OutgoingData);
+}
+
+
+
+
+
+void cHTTPConnection::SocketClosed(void)
+{
+ if (m_CurrentRequest != NULL)
+ {
+ m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
+ }
+ m_HTTPServer.CloseConnection(*this);
+}
+
+
+
+
+
diff --git a/source/HTTPServer/HTTPConnection.h b/source/HTTPServer/HTTPConnection.h
new file mode 100644
index 000000000..14603bb70
--- /dev/null
+++ b/source/HTTPServer/HTTPConnection.h
@@ -0,0 +1,101 @@
+
+// HTTPConnection.h
+
+// Declares the cHTTPConnection class representing a single persistent connection in the HTTP server.
+
+
+
+
+
+#pragma once
+
+#include "../OSSupport/SocketThreads.h"
+
+
+
+
+
+// fwd:
+class cHTTPServer;
+class cHTTPResponse;
+class cHTTPRequest;
+
+
+
+
+
+class cHTTPConnection :
+ public cSocketThreads::cCallback
+{
+public:
+
+ enum eState
+ {
+ wcsRecvHeaders, ///< Receiving request headers (m_CurrentRequest is created if NULL)
+ wcsRecvBody, ///< Receiving request body (m_CurrentRequest is valid)
+ wcsRecvIdle, ///< Has received the entire body, waiting to send the response (m_CurrentRequest == NULL)
+ wcsSendingResp, ///< Sending response body (m_CurrentRequest == NULL)
+ wcsInvalid, ///< The request was malformed, the connection is closing
+ } ;
+
+ cHTTPConnection(cHTTPServer & a_HTTPServer);
+ ~cHTTPConnection();
+
+ /// Sends HTTP status code together with a_Reason (used for HTTP errors)
+ void SendStatusAndReason(int a_StatusCode, const AString & a_Reason);
+
+ /// Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm
+ void SendNeedAuth(const AString & a_Realm);
+
+ /// Sends the headers contained in a_Response
+ void Send(const cHTTPResponse & a_Response);
+
+ /// Sends the data as the response (may be called multiple times)
+ void Send(const void * a_Data, int a_Size);
+
+ /// Sends the data as the response (may be called multiple times)
+ void Send(const AString & a_Data) { Send(a_Data.data(), a_Data.size()); }
+
+ /// Indicates that the current response is finished, gets ready for receiving another request (HTTP 1.1 keepalive)
+ void FinishResponse(void);
+
+ /// Resets the connection for a new request. Depending on the state, this will send an "InternalServerError" status or a "ResponseEnd"
+ void AwaitNextRequest(void);
+
+ /// Terminates the connection; finishes any request being currently processed
+ void Terminate(void);
+
+protected:
+ typedef std::map<AString, AString> cNameValueMap;
+
+ /// The parent webserver that is to be notified of events on this connection
+ cHTTPServer & m_HTTPServer;
+
+ /// All the incoming data until the entire request header is parsed
+ AString m_IncomingHeaderData;
+
+ /// Status in which the request currently is
+ eState m_State;
+
+ /// Data that is queued for sending, once the socket becomes writable
+ AString m_OutgoingData;
+
+ /// The request being currently received (valid only between having parsed the headers and finishing receiving the body)
+ cHTTPRequest * m_CurrentRequest;
+
+ /// Number of bytes that remain to read for the complete body of the message to be received. Valid only in wcsRecvBody
+ int m_CurrentRequestBodyRemaining;
+
+
+ // cSocketThreads::cCallback overrides:
+ virtual void DataReceived (const char * a_Data, int a_Size) override; // Data is received from the client
+ virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client
+ virtual void SocketClosed (void) override; // The socket has been closed for any reason
+} ;
+
+typedef std::vector<cHTTPConnection *> cHTTPConnections;
+
+
+
+
+
diff --git a/source/HTTPServer/HTTPFormParser.cpp b/source/HTTPServer/HTTPFormParser.cpp
new file mode 100644
index 000000000..7db7b4e6d
--- /dev/null
+++ b/source/HTTPServer/HTTPFormParser.cpp
@@ -0,0 +1,278 @@
+
+// HTTPFormParser.cpp
+
+// Implements the cHTTPFormParser class representing a parser for forms sent over HTTP
+
+#include "Globals.h"
+#include "HTTPFormParser.h"
+#include "HTTPMessage.h"
+#include "MultipartParser.h"
+#include "NameValueParser.h"
+
+
+
+
+
+cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks) :
+ m_Callbacks(a_Callbacks),
+ m_IsValid(true)
+{
+ if (a_Request.GetMethod() == "GET")
+ {
+ m_Kind = fpkURL;
+
+ // Directly parse the URL in the request:
+ const AString & URL = a_Request.GetURL();
+ size_t idxQM = URL.find('?');
+ if (idxQM != AString::npos)
+ {
+ Parse(URL.c_str() + idxQM + 1, URL.size() - idxQM - 1);
+ }
+ return;
+ }
+ if ((a_Request.GetMethod() == "POST") || (a_Request.GetMethod() == "PUT"))
+ {
+ if (strncmp(a_Request.GetContentType().c_str(), "application/x-www-form-urlencoded", 33) == 0)
+ {
+ m_Kind = fpkFormUrlEncoded;
+ return;
+ }
+ if (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0)
+ {
+ m_Kind = fpkMultipart;
+ BeginMultipart(a_Request);
+ return;
+ }
+ }
+ // Invalid method / content type combination, this is not a HTTP form
+ m_IsValid = false;
+}
+
+
+
+
+
+void cHTTPFormParser::Parse(const char * a_Data, int a_Size)
+{
+ if (!m_IsValid)
+ {
+ return;
+ }
+
+ switch (m_Kind)
+ {
+ case fpkURL:
+ case fpkFormUrlEncoded:
+ {
+ // This format is used for smaller forms (not file uploads), so we can delay parsing it until Finish()
+ m_IncomingData.append(a_Data, a_Size);
+ break;
+ }
+ case fpkMultipart:
+ {
+ ASSERT(m_MultipartParser.get() != NULL);
+ m_MultipartParser->Parse(a_Data, a_Size);
+ break;
+ }
+ default:
+ {
+ ASSERT(!"Unhandled form kind");
+ break;
+ }
+ }
+}
+
+
+
+
+
+bool cHTTPFormParser::Finish(void)
+{
+ switch (m_Kind)
+ {
+ case fpkURL:
+ case fpkFormUrlEncoded:
+ {
+ // m_IncomingData has all the form data, parse it now:
+ ParseFormUrlEncoded();
+ break;
+ }
+ }
+ return (m_IsValid && m_IncomingData.empty());
+}
+
+
+
+
+
+bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request)
+{
+ const AString & ContentType = a_Request.GetContentType();
+ return (
+ (ContentType == "application/x-www-form-urlencoded") ||
+ (strncmp(ContentType.c_str(), "multipart/form-data", 19) == 0) ||
+ (
+ (a_Request.GetMethod() == "GET") &&
+ (a_Request.GetURL().find('?') != AString::npos)
+ )
+ );
+ return false;
+}
+
+
+
+
+
+void cHTTPFormParser::BeginMultipart(const cHTTPRequest & a_Request)
+{
+ ASSERT(m_MultipartParser.get() == NULL);
+ m_MultipartParser.reset(new cMultipartParser(a_Request.GetContentType(), *this));
+}
+
+
+
+
+
+void cHTTPFormParser::ParseFormUrlEncoded(void)
+{
+ // Parse m_IncomingData for all the variables; no more data is incoming, since this is called from Finish()
+ // This may not be the most performant version, but we don't care, the form data is small enough and we're not a full-fledged web server anyway
+ AStringVector Lines = StringSplit(m_IncomingData, "&");
+ for (AStringVector::iterator itr = Lines.begin(), end = Lines.end(); itr != end; ++itr)
+ {
+ AStringVector Components = StringSplit(*itr, "=");
+ switch (Components.size())
+ {
+ default:
+ {
+ // Neither name nor value, or too many "="s, mark this as invalid form:
+ m_IsValid = false;
+ return;
+ }
+ case 1:
+ {
+ // Only name present
+ (*this)[URLDecode(ReplaceAllCharOccurrences(Components[0], '+', ' '))] = "";
+ break;
+ }
+ case 2:
+ {
+ // name=value format:
+ (*this)[URLDecode(ReplaceAllCharOccurrences(Components[0], '+', ' '))] = URLDecode(ReplaceAllCharOccurrences(Components[1], '+', ' '));
+ break;
+ }
+ }
+ } // for itr - Lines[]
+ m_IncomingData.clear();
+}
+
+
+
+
+
+void cHTTPFormParser::OnPartStart(void)
+{
+ m_CurrentPartFileName.clear();
+ m_CurrentPartName.clear();
+ m_IsCurrentPartFile = false;
+ m_FileHasBeenAnnounced = false;
+}
+
+
+
+
+
+void cHTTPFormParser::OnPartHeader(const AString & a_Key, const AString & a_Value)
+{
+ if (NoCaseCompare(a_Key, "Content-Disposition") == 0)
+ {
+ size_t len = a_Value.size();
+ size_t ParamsStart = AString::npos;
+ for (size_t i = 0; i < len; ++i)
+ {
+ if (a_Value[i] > ' ')
+ {
+ if (strncmp(a_Value.c_str() + i, "form-data", 9) != 0)
+ {
+ // Content disposition is not "form-data", mark the whole form invalid
+ m_IsValid = false;
+ return;
+ }
+ ParamsStart = a_Value.find(';', i + 9);
+ break;
+ }
+ }
+ if (ParamsStart == AString::npos)
+ {
+ // There is data missing in the Content-Disposition field, mark the whole form invalid:
+ m_IsValid = false;
+ return;
+ }
+
+ // Parse the field name and optional filename from this header:
+ cNameValueParser Parser(a_Value.data() + ParamsStart, a_Value.size() - ParamsStart);
+ Parser.Finish();
+ m_CurrentPartName = Parser["name"];
+ if (!Parser.IsValid() || m_CurrentPartName.empty())
+ {
+ // The required parameter "name" is missing, mark the whole form invalid:
+ m_IsValid = false;
+ return;
+ }
+ m_CurrentPartFileName = Parser["filename"];
+ }
+}
+
+
+
+
+
+void cHTTPFormParser::OnPartData(const char * a_Data, int a_Size)
+{
+ if (m_CurrentPartName.empty())
+ {
+ // Prologue, epilogue or invalid part
+ return;
+ }
+ if (m_CurrentPartFileName.empty())
+ {
+ // This is a variable, store it in the map
+ iterator itr = find(m_CurrentPartName);
+ if (itr == end())
+ {
+ (*this)[m_CurrentPartName] = AString(a_Data, a_Size);
+ }
+ else
+ {
+ itr->second.append(a_Data, a_Size);
+ }
+ }
+ else
+ {
+ // This is a file, pass it on through the callbacks
+ if (!m_FileHasBeenAnnounced)
+ {
+ m_Callbacks.OnFileStart(*this, m_CurrentPartFileName);
+ m_FileHasBeenAnnounced = true;
+ }
+ m_Callbacks.OnFileData(*this, a_Data, a_Size);
+ }
+}
+
+
+
+
+
+void cHTTPFormParser::OnPartEnd(void)
+{
+ if (m_FileHasBeenAnnounced)
+ {
+ m_Callbacks.OnFileEnd(*this);
+ }
+ m_CurrentPartName.clear();
+ m_CurrentPartFileName.clear();
+}
+
+
+
+
diff --git a/source/HTTPServer/HTTPFormParser.h b/source/HTTPServer/HTTPFormParser.h
new file mode 100644
index 000000000..b92ef9d3c
--- /dev/null
+++ b/source/HTTPServer/HTTPFormParser.h
@@ -0,0 +1,107 @@
+
+// HTTPFormParser.h
+
+// Declares the cHTTPFormParser class representing a parser for forms sent over HTTP
+
+
+
+
+#pragma once
+
+#include "MultipartParser.h"
+
+
+
+
+
+// fwd:
+class cHTTPRequest;
+
+
+
+
+
+class cHTTPFormParser :
+ public std::map<AString, AString>,
+ public cMultipartParser::cCallbacks
+{
+public:
+ class cCallbacks
+ {
+ public:
+ /// Called when a new file part is encountered in the form data
+ virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) = 0;
+
+ /// Called when more file data has come for the current file in the form data
+ virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, int a_Size) = 0;
+
+ /// Called when the current file part has ended in the form data
+ virtual void OnFileEnd(cHTTPFormParser & a_Parser) = 0;
+ } ;
+
+
+ cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks);
+
+ /// Adds more data into the parser, as the request body is received
+ void Parse(const char * a_Data, int a_Size);
+
+ /** Notifies that there's no more data incoming and the parser should finish its parsing.
+ Returns true if parsing successful
+ */
+ bool Finish(void);
+
+ /// Returns true if the headers suggest the request has form data parseable by this class
+ static bool HasFormData(const cHTTPRequest & a_Request);
+
+protected:
+ enum eKind
+ {
+ fpkURL, ///< The form has been transmitted as parameters to a GET request
+ fpkFormUrlEncoded, ///< The form has been POSTed or PUT, with Content-Type of "application/x-www-form-urlencoded"
+ fpkMultipart, ///< The form has been POSTed or PUT, with Content-Type of "multipart/form-data"
+ };
+
+ /// The callbacks to call for incoming file data
+ cCallbacks & m_Callbacks;
+
+ /// The kind of the parser (decided in the constructor, used in Parse()
+ eKind m_Kind;
+
+ /// Buffer for the incoming data until it's parsed
+ AString m_IncomingData;
+
+ /// True if the information received so far is a valid form; set to false on first problem. Further parsing is skipped when false.
+ bool m_IsValid;
+
+ /// The parser for the multipart data, if used
+ std::auto_ptr<cMultipartParser> m_MultipartParser;
+
+ /// Name of the currently parsed part in multipart data
+ AString m_CurrentPartName;
+
+ /// True if the currently parsed part in multipart data is a file
+ bool m_IsCurrentPartFile;
+
+ /// Filename of the current parsed part in multipart data (for file uploads)
+ AString m_CurrentPartFileName;
+
+ /// Set to true after m_Callbacks.OnFileStart() has been called, reset to false on PartEnd
+ bool m_FileHasBeenAnnounced;
+
+
+ /// Sets up the object for parsing a fpkMultipart request
+ void BeginMultipart(const cHTTPRequest & a_Request);
+
+ /// Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds)
+ void ParseFormUrlEncoded(void);
+
+ // cMultipartParser::cCallbacks overrides:
+ virtual void OnPartStart (void) override;
+ virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) override;
+ virtual void OnPartData (const char * a_Data, int a_Size) override;
+ virtual void OnPartEnd (void) override;
+} ;
+
+
+
+
diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp
new file mode 100644
index 000000000..ab23866e6
--- /dev/null
+++ b/source/HTTPServer/HTTPMessage.cpp
@@ -0,0 +1,279 @@
+
+// HTTPMessage.cpp
+
+// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes
+
+#include "Globals.h"
+#include "HTTPMessage.h"
+
+
+
+
+
+// Disable MSVC warnings:
+#if defined(_MSC_VER)
+ #pragma warning(push)
+ #pragma warning(disable:4355) // 'this' : used in base member initializer list
+#endif
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cHTTPMessage:
+
+cHTTPMessage::cHTTPMessage(eKind a_Kind) :
+ m_Kind(a_Kind),
+ m_ContentLength(-1)
+{
+}
+
+
+
+
+
+void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value)
+{
+ AString Key = a_Key;
+ StrToLower(Key);
+ cNameValueMap::iterator itr = m_Headers.find(Key);
+ if (itr == m_Headers.end())
+ {
+ m_Headers[Key] = a_Value;
+ }
+ else
+ {
+ // The header-field key is specified multiple times, combine into comma-separated list (RFC 2616 @ 4.2)
+ itr->second.append(", ");
+ itr->second.append(a_Value);
+ }
+
+ // Special processing for well-known headers:
+ if (Key == "content-type")
+ {
+ m_ContentType = m_Headers[Key];
+ }
+ else if (Key == "content-length")
+ {
+ m_ContentLength = atoi(m_Headers[Key].c_str());
+ }
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cHTTPRequest:
+
+cHTTPRequest::cHTTPRequest(void) :
+ super(mkRequest),
+ m_EnvelopeParser(*this),
+ m_IsValid(true),
+ m_UserData(NULL),
+ m_HasAuth(false)
+{
+}
+
+
+
+
+
+int cHTTPRequest::ParseHeaders(const char * a_Data, int a_Size)
+{
+ if (!m_IsValid)
+ {
+ return -1;
+ }
+
+ if (m_Method.empty())
+ {
+ // The first line hasn't been processed yet
+ int res = ParseRequestLine(a_Data, a_Size);
+ if ((res < 0) || (res == a_Size))
+ {
+ return res;
+ }
+ int res2 = m_EnvelopeParser.Parse(a_Data + res, a_Size - res);
+ if (res2 < 0)
+ {
+ m_IsValid = false;
+ return res2;
+ }
+ return res2 + res;
+ }
+
+ if (m_EnvelopeParser.IsInHeaders())
+ {
+ int res = m_EnvelopeParser.Parse(a_Data, a_Size);
+ if (res < 0)
+ {
+ m_IsValid = false;
+ }
+ return res;
+ }
+ return 0;
+}
+
+
+
+
+
+AString cHTTPRequest::GetBareURL(void) const
+{
+ size_t idxQM = m_URL.find('?');
+ if (idxQM != AString::npos)
+ {
+ return m_URL.substr(0, idxQM);
+ }
+ else
+ {
+ return m_URL;
+ }
+}
+
+
+
+
+
+int cHTTPRequest::ParseRequestLine(const char * a_Data, int a_Size)
+{
+ m_IncomingHeaderData.append(a_Data, a_Size);
+ size_t IdxEnd = m_IncomingHeaderData.size();
+
+ // Ignore the initial CRLFs (HTTP spec's "should")
+ size_t LineStart = 0;
+ while (
+ (LineStart < IdxEnd) &&
+ (
+ (m_IncomingHeaderData[LineStart] == '\r') ||
+ (m_IncomingHeaderData[LineStart] == '\n')
+ )
+ )
+ {
+ LineStart++;
+ }
+ if (LineStart >= IdxEnd)
+ {
+ m_IsValid = false;
+ return -1;
+ }
+
+ int NumSpaces = 0;
+ size_t MethodEnd = 0;
+ size_t URLEnd = 0;
+ for (size_t i = LineStart; i < IdxEnd; i++)
+ {
+ switch (m_IncomingHeaderData[i])
+ {
+ case ' ':
+ {
+ switch (NumSpaces)
+ {
+ case 0:
+ {
+ MethodEnd = i;
+ break;
+ }
+ case 1:
+ {
+ URLEnd = i;
+ break;
+ }
+ default:
+ {
+ // Too many spaces in the request
+ m_IsValid = false;
+ return -1;
+ }
+ }
+ NumSpaces += 1;
+ break;
+ }
+ case '\n':
+ {
+ if ((i == 0) || (m_IncomingHeaderData[i - 1] != '\r') || (NumSpaces != 2) || (i < URLEnd + 7))
+ {
+ // LF too early, without a CR, without two preceeding spaces or too soon after the second space
+ m_IsValid = false;
+ return -1;
+ }
+ // Check that there's HTTP/version at the end
+ if (strncmp(a_Data + URLEnd + 1, "HTTP/1.", 7) != 0)
+ {
+ m_IsValid = false;
+ return -1;
+ }
+ m_Method = m_IncomingHeaderData.substr(LineStart, MethodEnd - LineStart);
+ m_URL = m_IncomingHeaderData.substr(MethodEnd + 1, URLEnd - MethodEnd - 1);
+ return i + 1;
+ }
+ } // switch (m_IncomingHeaderData[i])
+ } // for i - m_IncomingHeaderData[]
+
+ // CRLF hasn't been encountered yet, consider all data consumed
+ return a_Size;
+}
+
+
+
+
+
+void cHTTPRequest::OnHeaderLine(const AString & a_Key, const AString & a_Value)
+{
+ if (
+ (NoCaseCompare(a_Key, "Authorization") == 0) &&
+ (strncmp(a_Value.c_str(), "Basic ", 6) == 0)
+ )
+ {
+ AString UserPass = Base64Decode(a_Value.substr(6));
+ size_t idxCol = UserPass.find(':');
+ if (idxCol != AString::npos)
+ {
+ m_AuthUsername = UserPass.substr(0, idxCol);
+ m_AuthPassword = UserPass.substr(idxCol + 1);
+ m_HasAuth = true;
+ }
+ }
+ AddHeader(a_Key, a_Value);
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cHTTPResponse:
+
+cHTTPResponse::cHTTPResponse(void) :
+ super(mkResponse)
+{
+}
+
+
+
+
+
+void cHTTPResponse::AppendToData(AString & a_DataStream) const
+{
+ a_DataStream.append("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: ");
+ a_DataStream.append(m_ContentType);
+ a_DataStream.append("\r\n");
+ for (cNameValueMap::const_iterator itr = m_Headers.begin(), end = m_Headers.end(); itr != end; ++itr)
+ {
+ if ((itr->first == "Content-Type") || (itr->first == "Content-Length"))
+ {
+ continue;
+ }
+ a_DataStream.append(itr->first);
+ a_DataStream.append(": ");
+ a_DataStream.append(itr->second);
+ a_DataStream.append("\r\n");
+ } // for itr - m_Headers[]
+ a_DataStream.append("\r\n");
+}
+
+
+
+
diff --git a/source/HTTPServer/HTTPMessage.h b/source/HTTPServer/HTTPMessage.h
new file mode 100644
index 000000000..f5284c535
--- /dev/null
+++ b/source/HTTPServer/HTTPMessage.h
@@ -0,0 +1,164 @@
+
+// HTTPMessage.h
+
+// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes
+
+
+
+
+
+#pragma once
+
+#include "EnvelopeParser.h"
+
+
+
+
+
+class cHTTPMessage
+{
+public:
+ enum
+ {
+ HTTP_OK = 200,
+ HTTP_BAD_REQUEST = 400,
+ } ;
+
+ enum eKind
+ {
+ mkRequest,
+ mkResponse,
+ } ;
+
+ cHTTPMessage(eKind a_Kind);
+
+ /// Adds a header into the internal map of headers. Recognizes special headers: Content-Type and Content-Length
+ void AddHeader(const AString & a_Key, const AString & a_Value);
+
+ void SetContentType (const AString & a_ContentType) { m_ContentType = a_ContentType; }
+ void SetContentLength(int a_ContentLength) { m_ContentLength = a_ContentLength; }
+
+ const AString & GetContentType (void) const { return m_ContentType; }
+ int GetContentLength(void) const { return m_ContentLength; }
+
+protected:
+ typedef std::map<AString, AString> cNameValueMap;
+
+ eKind m_Kind;
+
+ cNameValueMap m_Headers;
+
+ /// Type of the content; parsed by AddHeader(), set directly by SetContentLength()
+ AString m_ContentType;
+
+ /// Length of the content that is to be received. -1 when the object is created, parsed by AddHeader() or set directly by SetContentLength()
+ int m_ContentLength;
+} ;
+
+
+
+
+
+class cHTTPRequest :
+ public cHTTPMessage,
+ protected cEnvelopeParser::cCallbacks
+{
+ typedef cHTTPMessage super;
+
+public:
+ cHTTPRequest(void);
+
+ /** Parses the request line and then headers from the received data.
+ Returns the number of bytes consumed or a negative number for error
+ */
+ int ParseHeaders(const char * a_Data, int a_Size);
+
+ /// Returns true if the request did contain a Content-Length header
+ bool HasReceivedContentLength(void) const { return (m_ContentLength >= 0); }
+
+ /// Returns the method used in the request
+ const AString & GetMethod(void) const { return m_Method; }
+
+ /// Returns the URL used in the request
+ const AString & GetURL(void) const { return m_URL; }
+
+ /// Returns the URL used in the request, without any parameters
+ AString GetBareURL(void) const;
+
+ /// Sets the UserData pointer that is stored within this request. The request doesn't touch this data (doesn't delete it)!
+ void SetUserData(void * a_UserData) { m_UserData = a_UserData; }
+
+ /// Retrieves the UserData pointer that has been stored within this request.
+ void * GetUserData(void) const { return m_UserData; }
+
+ /// Returns true if more data is expected for the request headers
+ bool IsInHeaders(void) const { return m_EnvelopeParser.IsInHeaders(); }
+
+ /// Returns true if the request did present auth data that was understood by the parser
+ bool HasAuth(void) const { return m_HasAuth; }
+
+ /// Returns the username that the request presented. Only valid if HasAuth() is true
+ const AString & GetAuthUsername(void) const { return m_AuthUsername; }
+
+ /// Returns the password that the request presented. Only valid if HasAuth() is true
+ const AString & GetAuthPassword(void) const { return m_AuthPassword; }
+
+protected:
+ /// Parser for the envelope data
+ cEnvelopeParser m_EnvelopeParser;
+
+ /// True if the data received so far is parsed successfully. When false, all further parsing is skipped
+ bool m_IsValid;
+
+ /// Bufferred incoming data, while parsing for the request line
+ AString m_IncomingHeaderData;
+
+ /// Method of the request (GET / PUT / POST / ...)
+ AString m_Method;
+
+ /// Full URL of the request
+ AString m_URL;
+
+ /// Data that the HTTPServer callbacks are allowed to store.
+ void * m_UserData;
+
+ /// Set to true if the request contains auth data that was understood by the parser
+ bool m_HasAuth;
+
+ /// The username used for auth
+ AString m_AuthUsername;
+
+ /// The password used for auth
+ AString m_AuthPassword;
+
+
+ /** Parses the incoming data for the first line (RequestLine)
+ Returns the number of bytes consumed, or -1 for an error
+ */
+ int ParseRequestLine(const char * a_Data, int a_Size);
+
+ // cEnvelopeParser::cCallbacks overrides:
+ virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override;
+} ;
+
+
+
+
+
+class cHTTPResponse :
+ public cHTTPMessage
+{
+ typedef cHTTPMessage super;
+
+public:
+ cHTTPResponse(void);
+
+ /** Appends the response to the specified datastream - response line and headers.
+ The body will be sent later directly through cConnection::Send()
+ */
+ void AppendToData(AString & a_DataStream) const;
+} ;
+
+
+
+
diff --git a/source/HTTPServer/HTTPServer.cpp b/source/HTTPServer/HTTPServer.cpp
new file mode 100644
index 000000000..f6f5b0f8b
--- /dev/null
+++ b/source/HTTPServer/HTTPServer.cpp
@@ -0,0 +1,258 @@
+
+// HTTPServer.cpp
+
+// Implements the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing
+
+#include "Globals.h"
+#include "HTTPServer.h"
+#include "HTTPMessage.h"
+#include "HTTPConnection.h"
+#include "HTTPFormParser.h"
+
+
+
+
+
+// Disable MSVC warnings:
+#if defined(_MSC_VER)
+ #pragma warning(push)
+ #pragma warning(disable:4355) // 'this' : used in base member initializer list
+#endif
+
+
+
+
+
+class cDebugCallbacks :
+ public cHTTPServer::cCallbacks,
+ protected cHTTPFormParser::cCallbacks
+{
+ virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override
+ {
+ if (cHTTPFormParser::HasFormData(a_Request))
+ {
+ a_Request.SetUserData(new cHTTPFormParser(a_Request, *this));
+ }
+ }
+
+
+ virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) override
+ {
+ cHTTPFormParser * FormParser = (cHTTPFormParser *)(a_Request.GetUserData());
+ if (FormParser != NULL)
+ {
+ FormParser->Parse(a_Data, a_Size);
+ }
+ }
+
+
+ virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override
+ {
+ cHTTPFormParser * FormParser = (cHTTPFormParser *)(a_Request.GetUserData());
+ if (FormParser != NULL)
+ {
+ if (FormParser->Finish())
+ {
+ cHTTPResponse Resp;
+ Resp.SetContentType("text/html");
+ a_Connection.Send(Resp);
+ a_Connection.Send("<html><body><table border=1 cellspacing=0><tr><th>Name</th><th>Value</th></tr>\r\n");
+ for (cHTTPFormParser::iterator itr = FormParser->begin(), end = FormParser->end(); itr != end; ++itr)
+ {
+ a_Connection.Send(Printf("<tr><td valign=\"top\"><pre>%s</pre></td><td valign=\"top\"><pre>%s</pre></td></tr>\r\n", itr->first.c_str(), itr->second.c_str()));
+ } // for itr - FormParser[]
+ a_Connection.Send("</table></body></html>");
+ return;
+ }
+
+ // Parsing failed:
+ cHTTPResponse Resp;
+ Resp.SetContentType("text/plain");
+ a_Connection.Send(Resp);
+ a_Connection.Send("Form parsing failed");
+ return;
+ }
+
+ // Test the auth failure and success:
+ if (a_Request.GetURL() == "/auth")
+ {
+ if (!a_Request.HasAuth() || (a_Request.GetAuthUsername() != "a") || (a_Request.GetAuthPassword() != "b"))
+ {
+ a_Connection.SendNeedAuth("MCServer WebAdmin");
+ return;
+ }
+ }
+
+ cHTTPResponse Resp;
+ Resp.SetContentType("text/plain");
+ a_Connection.Send(Resp);
+ a_Connection.Send("Hello, world");
+ }
+
+
+ virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) override
+ {
+ // TODO
+ }
+
+
+ virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, int a_Size) override
+ {
+ // TODO
+ }
+
+
+ virtual void OnFileEnd(cHTTPFormParser & a_Parser) override
+ {
+ // TODO
+ }
+
+} g_DebugCallbacks;
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cHTTPServer:
+
+cHTTPServer::cHTTPServer(void) :
+ m_ListenThreadIPv4(*this, cSocket::IPv4, "WebServer IPv4"),
+ m_ListenThreadIPv6(*this, cSocket::IPv6, "WebServer IPv6"),
+ m_Callbacks(NULL)
+{
+}
+
+
+
+
+
+cHTTPServer::~cHTTPServer()
+{
+ Stop();
+}
+
+
+
+
+
+bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6)
+{
+ bool HasAnyPort;
+ HasAnyPort = m_ListenThreadIPv4.Initialize(a_PortsIPv4);
+ HasAnyPort = m_ListenThreadIPv6.Initialize(a_PortsIPv6) || HasAnyPort;
+ if (!HasAnyPort)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+
+
+
+bool cHTTPServer::Start(cCallbacks & a_Callbacks)
+{
+ m_Callbacks = &a_Callbacks;
+ if (!m_ListenThreadIPv4.Start())
+ {
+ return false;
+ }
+ if (!m_ListenThreadIPv6.Start())
+ {
+ m_ListenThreadIPv4.Stop();
+ return false;
+ }
+ return true;
+}
+
+
+
+
+
+void cHTTPServer::Stop(void)
+{
+ m_ListenThreadIPv4.Stop();
+ m_ListenThreadIPv6.Stop();
+
+ // Drop all current connections:
+ cCSLock Lock(m_CSConnections);
+ while (!m_Connections.empty())
+ {
+ m_Connections.front()->Terminate();
+ } // for itr - m_Connections[]
+}
+
+
+
+
+
+void cHTTPServer::OnConnectionAccepted(cSocket & a_Socket)
+{
+ cHTTPConnection * Connection = new cHTTPConnection(*this);
+ m_SocketThreads.AddClient(a_Socket, Connection);
+ cCSLock Lock(m_CSConnections);
+ m_Connections.push_back(Connection);
+}
+
+
+
+
+
+void cHTTPServer::CloseConnection(cHTTPConnection & a_Connection)
+{
+ m_SocketThreads.RemoveClient(&a_Connection);
+ cCSLock Lock(m_CSConnections);
+ for (cHTTPConnections::iterator itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr)
+ {
+ if (*itr == &a_Connection)
+ {
+ m_Connections.erase(itr);
+ break;
+ }
+ }
+ delete &a_Connection;
+}
+
+
+
+
+
+void cHTTPServer::NotifyConnectionWrite(cHTTPConnection & a_Connection)
+{
+ m_SocketThreads.NotifyWrite(&a_Connection);
+}
+
+
+
+
+
+void cHTTPServer::NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
+{
+ m_Callbacks->OnRequestBegun(a_Connection, a_Request);
+}
+
+
+
+
+
+void cHTTPServer::RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size)
+{
+ m_Callbacks->OnRequestBody(a_Connection, a_Request, a_Data, a_Size);
+}
+
+
+
+
+
+void cHTTPServer::RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
+{
+ m_Callbacks->OnRequestFinished(a_Connection, a_Request);
+ a_Connection.AwaitNextRequest();
+}
+
+
+
+
diff --git a/source/HTTPServer/HTTPServer.h b/source/HTTPServer/HTTPServer.h
new file mode 100644
index 000000000..fea2a9029
--- /dev/null
+++ b/source/HTTPServer/HTTPServer.h
@@ -0,0 +1,101 @@
+
+// HTTPServer.h
+
+// Declares the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing
+
+
+
+
+
+#pragma once
+
+#include "../OSSupport/ListenThread.h"
+#include "../OSSupport/SocketThreads.h"
+#include "../../iniFile/iniFile.h"
+
+
+
+
+
+// fwd:
+class cHTTPMessage;
+class cHTTPRequest;
+class cHTTPResponse;
+class cHTTPConnection;
+
+typedef std::vector<cHTTPConnection *> cHTTPConnections;
+
+
+
+
+
+
+class cHTTPServer :
+ public cListenThread::cCallback
+{
+public:
+ class cCallbacks
+ {
+ public:
+ /** Called when a new request arrives over a connection and its headers have been parsed.
+ The request body needn't have arrived yet.
+ */
+ virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0;
+
+ /// Called when another part of request body has arrived.
+ virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) = 0;
+
+ /// Called when the request body has been fully received in previous calls to OnRequestBody()
+ virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0;
+ } ;
+
+ cHTTPServer(void);
+ ~cHTTPServer();
+
+ /// Initializes the server on the specified ports
+ bool Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6);
+
+ /// Starts the server and assigns the callbacks to use for incoming requests
+ bool Start(cCallbacks & a_Callbacks);
+
+ /// Stops the server, drops all current connections
+ void Stop(void);
+
+protected:
+ friend class cHTTPConnection;
+
+ cListenThread m_ListenThreadIPv4;
+ cListenThread m_ListenThreadIPv6;
+
+ cSocketThreads m_SocketThreads;
+
+ cCriticalSection m_CSConnections;
+ cHTTPConnections m_Connections; ///< All the connections that are currently being serviced
+
+ /// The callbacks to call for various events
+ cCallbacks * m_Callbacks;
+
+
+ // cListenThread::cCallback overrides:
+ virtual void OnConnectionAccepted(cSocket & a_Socket) override;
+
+ /// Called by cHTTPConnection to close the connection (presumably due to an error)
+ void CloseConnection(cHTTPConnection & a_Connection);
+
+ /// Called by cHTTPConnection to notify SocketThreads that there's data to be sent for the connection
+ void NotifyConnectionWrite(cHTTPConnection & a_Connection);
+
+ /// Called by cHTTPConnection when it finishes parsing the request header
+ void NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
+
+ /// Called by cHTTPConenction when it receives more data for the request body
+ void RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size);
+
+ /// Called by cHTTPConnection when it detects that the request has finished (all of its body has been received)
+ void RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
+} ;
+
+
+
+
+
diff --git a/source/HTTPServer/MultipartParser.cpp b/source/HTTPServer/MultipartParser.cpp
new file mode 100644
index 000000000..b49f6ec07
--- /dev/null
+++ b/source/HTTPServer/MultipartParser.cpp
@@ -0,0 +1,256 @@
+
+// MultipartParser.cpp
+
+// Implements the cMultipartParser class that parses messages in "multipart/*" encoding into the separate parts
+
+#include "Globals.h"
+#include "MultipartParser.h"
+#include "NameValueParser.h"
+
+
+
+
+
+// Disable MSVC warnings:
+#if defined(_MSC_VER)
+ #pragma warning(push)
+ #pragma warning(disable:4355) // 'this' : used in base member initializer list
+#endif
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// self-test:
+
+#if 0
+
+class cMultipartParserTest :
+ public cMultipartParser::cCallbacks
+{
+public:
+ cMultipartParserTest(void)
+ {
+ cMultipartParser Parser("multipart/mixed; boundary=\"MyBoundaryString\"; foo=bar", *this);
+ const char Data[] =
+"ThisIsIgnoredPrologue\r\n\
+--MyBoundaryString\r\n\
+\r\n\
+Body with confusing strings\r\n\
+--NotABoundary\r\n\
+--MyBoundaryStringWithPostfix\r\n\
+--\r\n\
+--MyBoundaryString\r\n\
+content-disposition: inline\r\n\
+\r\n\
+This is body\r\n\
+--MyBoundaryString\r\n\
+\r\n\
+Headerless body with trailing CRLF\r\n\
+\r\n\
+--MyBoundaryString--\r\n\
+ThisIsIgnoredEpilogue";
+ printf("Multipart parsing test commencing.\n");
+ Parser.Parse(Data, sizeof(Data) - 1);
+ // DEBUG: Check if the onscreen output corresponds with the data above
+ printf("Multipart parsing test finished\n");
+ }
+
+ virtual void OnPartStart(void) override
+ {
+ printf("Starting a new part\n");
+ }
+
+
+ virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) override
+ {
+ printf(" Hdr: \"%s\"=\"%s\"\n", a_Key.c_str(), a_Value.c_str());
+ }
+
+
+ virtual void OnPartData(const char * a_Data, int a_Size) override
+ {
+ printf(" Data: %d bytes, \"%.*s\"\n", a_Size, a_Size, a_Data);
+ }
+
+
+ virtual void OnPartEnd(void) override
+ {
+ printf("Part end\n");
+ }
+} g_Test;
+
+#endif
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cMultipartParser:
+
+
+cMultipartParser::cMultipartParser(const AString & a_ContentType, cCallbacks & a_Callbacks) :
+ m_Callbacks(a_Callbacks),
+ m_IsValid(true),
+ m_EnvelopeParser(*this),
+ m_HasHadData(false)
+{
+ static AString s_Multipart = "multipart/";
+
+ // Check that the content type is multipart:
+ AString ContentType(a_ContentType);
+ if (strncmp(ContentType.c_str(), "multipart/", 10) != 0)
+ {
+ m_IsValid = false;
+ return;
+ }
+ size_t idxSC = ContentType.find(';', 10);
+ if (idxSC == AString::npos)
+ {
+ m_IsValid = false;
+ return;
+ }
+
+ // Find the multipart boundary:
+ ContentType.erase(0, idxSC + 1);
+ cNameValueParser CTParser(ContentType.c_str(), ContentType.size());
+ CTParser.Finish();
+ if (!CTParser.IsValid())
+ {
+ m_IsValid = false;
+ return;
+ }
+ m_Boundary = CTParser["boundary"];
+ m_IsValid = !m_Boundary.empty();
+ if (!m_IsValid)
+ {
+ return;
+ }
+
+ // Set the envelope parser for parsing the body, so that our Parse() function parses the ignored prefix data as a body
+ m_EnvelopeParser.SetIsInHeaders(false);
+
+ // Append an initial CRLF to the incoming data, so that a body starting with the boundary line will get caught
+ m_IncomingData.assign("\r\n");
+
+ /*
+ m_Boundary = AString("\r\n--") + m_Boundary
+ m_BoundaryEnd = m_Boundary + "--\r\n";
+ m_Boundary = m_Boundary + "\r\n";
+ */
+}
+
+
+
+
+
+void cMultipartParser::Parse(const char * a_Data, int a_Size)
+{
+ // Skip parsing if invalid
+ if (!m_IsValid)
+ {
+ return;
+ }
+
+ // Append to buffer, then parse it:
+ m_IncomingData.append(a_Data, a_Size);
+ while (true)
+ {
+ if (m_EnvelopeParser.IsInHeaders())
+ {
+ int BytesConsumed = m_EnvelopeParser.Parse(m_IncomingData.data(), m_IncomingData.size());
+ if (BytesConsumed < 0)
+ {
+ m_IsValid = false;
+ return;
+ }
+ if ((BytesConsumed == a_Size) && m_EnvelopeParser.IsInHeaders())
+ {
+ // All the incoming data has been consumed and still waiting for more
+ return;
+ }
+ m_IncomingData.erase(0, BytesConsumed);
+ }
+
+ // Search for boundary / boundary end:
+ size_t idxBoundary = m_IncomingData.find("\r\n--");
+ if (idxBoundary == AString::npos)
+ {
+ // Boundary string start not present, present as much data to the part callback as possible
+ if (m_IncomingData.size() > m_Boundary.size() + 8)
+ {
+ size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8;
+ m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport);
+ m_IncomingData.erase(0, BytesToReport);
+ }
+ return;
+ }
+ if (idxBoundary > 0)
+ {
+ m_Callbacks.OnPartData(m_IncomingData.data(), idxBoundary);
+ m_IncomingData.erase(0, idxBoundary);
+ }
+ idxBoundary = 4;
+ size_t LineEnd = m_IncomingData.find("\r\n", idxBoundary);
+ if (LineEnd == AString::npos)
+ {
+ // Not a complete line yet, present as much data to the part callback as possible
+ if (m_IncomingData.size() > m_Boundary.size() + 8)
+ {
+ size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8;
+ m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport);
+ m_IncomingData.erase(0, BytesToReport);
+ }
+ return;
+ }
+ if (
+ (LineEnd - idxBoundary != m_Boundary.size()) && // Line length not equal to boundary
+ (LineEnd - idxBoundary != m_Boundary.size() + 2) // Line length not equal to boundary end
+ )
+ {
+ // Got a line, but it's not a boundary, report it as data:
+ m_Callbacks.OnPartData(m_IncomingData.data(), LineEnd);
+ m_IncomingData.erase(0, LineEnd);
+ continue;
+ }
+
+ if (strncmp(m_IncomingData.c_str() + idxBoundary, m_Boundary.c_str(), m_Boundary.size()) == 0)
+ {
+ // Boundary or BoundaryEnd found:
+ m_Callbacks.OnPartEnd();
+ size_t idxSlash = idxBoundary + m_Boundary.size();
+ if ((m_IncomingData[idxSlash] == '-') && (m_IncomingData[idxSlash + 1] == '-'))
+ {
+ // This was the last part
+ m_Callbacks.OnPartData(m_IncomingData.data() + idxSlash + 4, m_IncomingData.size() - idxSlash - 4);
+ m_IncomingData.clear();
+ return;
+ }
+ m_Callbacks.OnPartStart();
+ m_IncomingData.erase(0, LineEnd + 2);
+
+ // Keep parsing for the headers that may have come with this data:
+ m_EnvelopeParser.Reset();
+ continue;
+ }
+
+ // It's a line, but not a boundary. It can be fully sent to the data receiver, since a boundary cannot cross lines
+ m_Callbacks.OnPartData(m_IncomingData.c_str(), LineEnd);
+ m_IncomingData.erase(0, LineEnd);
+ } // while (true)
+}
+
+
+
+
+
+void cMultipartParser::OnHeaderLine(const AString & a_Key, const AString & a_Value)
+{
+ m_Callbacks.OnPartHeader(a_Key, a_Value);
+}
+
+
+
+
diff --git a/source/HTTPServer/MultipartParser.h b/source/HTTPServer/MultipartParser.h
new file mode 100644
index 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;
+} ;
+
+
+
+
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..07dc0b942
--- /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<AString, AString>
+{
+public:
+ /// Creates an empty parser
+ cNameValueParser(bool a_AllowsKeyOnly = true);
+
+ /// Creates an empty parser, then parses the data given. Doesn't call Finish(), so more data can be parsed later
+ cNameValueParser(const char * a_Data, int a_Size, bool a_AllowsKeyOnly = true);
+
+ /// Parses the data given
+ void Parse(const char * a_Data, int a_Size);
+
+ /// Notifies the parser that no more data will be coming. Returns true if the parser state is valid
+ bool Finish(void);
+
+ /// Returns true if the data parsed so far was valid
+ bool IsValid(void) const { return (m_State != psInvalid); }
+
+ /// Returns true if the parser expects no more data
+ bool IsFinished(void) const { return ((m_State == psInvalid) || (m_State == psFinished)); }
+
+protected:
+ enum eState
+ {
+ psKeySpace, ///< Parsing the space in front of the next key
+ psKey, ///< Currently adding more chars to the key in m_CurrentKey
+ psEqualSpace, ///< Space after m_CurrentKey
+ psEqual, ///< Just parsed the = sign after a name
+ psValueInSQuotes, ///< Just parsed a Single-quote sign after the Equal sign
+ psValueInDQuotes, ///< Just parsed a Double-quote sign after the Equal sign
+ psValueRaw, ///< Just parsed a raw value without a quote
+ psAfterValue, ///< Just finished parsing the value, waiting for semicolon or data end
+ psInvalid, ///< The parser has encountered an invalid input; further parsing is skipped
+ psFinished, ///< The parser has already been instructed to finish and doesn't expect any more data
+ } ;
+
+ /// The current state of the parser
+ eState m_State;
+
+ /// If true, the parser will accept keys without an equal sign and the value
+ bool m_AllowsKeyOnly;
+
+ /// Buffer for the current Key
+ AString m_CurrentKey;
+
+ /// Buffer for the current Value;
+ AString m_CurrentValue;
+
+
+} ;
+
+
+
+