summaryrefslogtreecommitdiffstats
path: root/src/HTTP
diff options
context:
space:
mode:
Diffstat (limited to 'src/HTTP')
-rw-r--r--src/HTTP/CMakeLists.txt43
-rw-r--r--src/HTTP/EnvelopeParser.cpp132
-rw-r--r--src/HTTP/EnvelopeParser.h73
-rw-r--r--src/HTTP/HTTPFormParser.cpp293
-rw-r--r--src/HTTP/HTTPFormParser.h114
-rw-r--r--src/HTTP/HTTPMessage.cpp159
-rw-r--r--src/HTTP/HTTPMessage.h158
-rw-r--r--src/HTTP/HTTPMessageParser.cpp222
-rw-r--r--src/HTTP/HTTPMessageParser.h125
-rw-r--r--src/HTTP/HTTPServer.cpp212
-rw-r--r--src/HTTP/HTTPServer.h101
-rw-r--r--src/HTTP/HTTPServerConnection.cpp240
-rw-r--r--src/HTTP/HTTPServerConnection.h118
-rw-r--r--src/HTTP/MultipartParser.cpp254
-rw-r--r--src/HTTP/MultipartParser.h79
-rw-r--r--src/HTTP/NameValueParser.cpp413
-rw-r--r--src/HTTP/NameValueParser.h70
-rw-r--r--src/HTTP/SslHTTPServerConnection.cpp115
-rw-r--r--src/HTTP/SslHTTPServerConnection.h47
-rw-r--r--src/HTTP/TransferEncodingParser.cpp394
-rw-r--r--src/HTTP/TransferEncodingParser.h76
-rw-r--r--src/HTTP/UrlParser.cpp200
-rw-r--r--src/HTTP/UrlParser.h58
23 files changed, 3696 insertions, 0 deletions
diff --git a/src/HTTP/CMakeLists.txt b/src/HTTP/CMakeLists.txt
new file mode 100644
index 000000000..acb3ac2cd
--- /dev/null
+++ b/src/HTTP/CMakeLists.txt
@@ -0,0 +1,43 @@
+
+cmake_minimum_required (VERSION 2.6)
+project (Cuberite)
+
+include_directories ("${PROJECT_SOURCE_DIR}/../")
+
+SET (SRCS
+ EnvelopeParser.cpp
+ HTTPFormParser.cpp
+ HTTPMessage.cpp
+ HTTPMessageParser.cpp
+ HTTPServer.cpp
+ HTTPServerConnection.cpp
+ MultipartParser.cpp
+ NameValueParser.cpp
+ SslHTTPServerConnection.cpp
+ TransferEncodingParser.cpp
+ UrlParser.cpp
+)
+
+SET (HDRS
+ EnvelopeParser.h
+ HTTPFormParser.h
+ HTTPMessage.h
+ HTTPMessageParser.h
+ HTTPServer.h
+ HTTPServerConnection.h
+ MultipartParser.h
+ NameValueParser.h
+ SslHTTPServerConnection.h
+ TransferEncodingParser.h
+ UrlParser.h
+)
+
+if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
+ set_source_files_properties(HTTPServer.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=global-constructors ")
+ set_source_files_properties(HTTPServerConnection.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum")
+ set_source_files_properties(HTTPMessage.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=tautological-compare")
+endif()
+
+if(NOT MSVC)
+ add_library(HTTPServer ${SRCS} ${HDRS})
+endif()
diff --git a/src/HTTP/EnvelopeParser.cpp b/src/HTTP/EnvelopeParser.cpp
new file mode 100644
index 000000000..1c49b643f
--- /dev/null
+++ b/src/HTTP/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)
+{
+}
+
+
+
+
+
+size_t cEnvelopeParser::Parse(const char * a_Data, size_t a_Size)
+{
+ if (!m_IsInHeaders)
+ {
+ return 0;
+ }
+
+ // Start searching 1 char from the end of the already received data, if available:
+ auto 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 AString::npos;
+ }
+ 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/src/HTTP/EnvelopeParser.h b/src/HTTP/EnvelopeParser.h
new file mode 100644
index 000000000..2fa930539
--- /dev/null
+++ b/src/HTTP/EnvelopeParser.h
@@ -0,0 +1,73 @@
+
+// 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:
+ // Force a virtual destructor in descendants:
+ virtual ~cCallbacks() {}
+
+ /** 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.
+ Returns AString::npos on error
+ */
+ size_t Parse(const char * a_Data, size_t 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/src/HTTP/HTTPFormParser.cpp b/src/HTTP/HTTPFormParser.cpp
new file mode 100644
index 000000000..ea5da3c18
--- /dev/null
+++ b/src/HTTP/HTTPFormParser.cpp
@@ -0,0 +1,293 @@
+
+// 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(const cHTTPIncomingRequest & a_Request, cCallbacks & a_Callbacks) :
+ m_Callbacks(a_Callbacks),
+ m_IsValid(true),
+ m_IsCurrentPartFile(false),
+ m_FileHasBeenAnnounced(false)
+{
+ 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;
+}
+
+
+
+
+
+cHTTPFormParser::cHTTPFormParser(eKind a_Kind, const char * a_Data, size_t a_Size, cCallbacks & a_Callbacks) :
+ m_Callbacks(a_Callbacks),
+ m_Kind(a_Kind),
+ m_IsValid(true),
+ m_IsCurrentPartFile(false),
+ m_FileHasBeenAnnounced(false)
+{
+ Parse(a_Data, a_Size);
+}
+
+
+
+
+
+void cHTTPFormParser::Parse(const char * a_Data, size_t 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() != nullptr);
+ m_MultipartParser->Parse(a_Data, a_Size);
+ break;
+ }
+ }
+}
+
+
+
+
+
+bool cHTTPFormParser::Finish(void)
+{
+ switch (m_Kind)
+ {
+ case fpkURL:
+ case fpkFormUrlEncoded:
+ {
+ // m_IncomingData has all the form data, parse it now:
+ ParseFormUrlEncoded();
+ break;
+ }
+ case fpkMultipart:
+ {
+ // Nothing needed for other formats
+ break;
+ }
+ }
+ return (m_IsValid && m_IncomingData.empty());
+}
+
+
+
+
+
+bool cHTTPFormParser::HasFormData(const cHTTPIncomingRequest & 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)
+ )
+ );
+}
+
+
+
+
+
+void cHTTPFormParser::BeginMultipart(const cHTTPIncomingRequest & a_Request)
+{
+ ASSERT(m_MultipartParser.get() == nullptr);
+ 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, size_t 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/src/HTTP/HTTPFormParser.h b/src/HTTP/HTTPFormParser.h
new file mode 100644
index 000000000..6bf3e7d78
--- /dev/null
+++ b/src/HTTP/HTTPFormParser.h
@@ -0,0 +1,114 @@
+
+// HTTPFormParser.h
+
+// Declares the cHTTPFormParser class representing a parser for forms sent over HTTP
+
+
+
+
+#pragma once
+
+#include "MultipartParser.h"
+
+
+
+
+
+// fwd:
+class cHTTPIncomingRequest;
+
+
+
+
+
+class cHTTPFormParser :
+ public std::map<AString, AString>,
+ public cMultipartParser::cCallbacks
+{
+public:
+ 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"
+ } ;
+
+ class cCallbacks
+ {
+ public:
+ // Force a virtual destructor in descendants:
+ virtual ~cCallbacks() {}
+
+ /** 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, size_t a_Size) = 0;
+
+ /** Called when the current file part has ended in the form data */
+ virtual void OnFileEnd(cHTTPFormParser & a_Parser) = 0;
+ } ;
+
+
+ /** Creates a parser that is tied to a request and notifies of various events using a callback mechanism */
+ cHTTPFormParser(const cHTTPIncomingRequest & a_Request, cCallbacks & a_Callbacks);
+
+ /** Creates a parser with the specified content type that reads data from a string */
+ cHTTPFormParser(eKind a_Kind, const char * a_Data, size_t a_Size, cCallbacks & a_Callbacks);
+
+ /** Adds more data into the parser, as the request body is received */
+ void Parse(const char * a_Data, size_t 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 cHTTPIncomingRequest & a_Request);
+
+protected:
+
+ /** 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::unique_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 cHTTPIncomingRequest & 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, size_t a_Size) override;
+ virtual void OnPartEnd (void) override;
+} ;
+
+
+
+
diff --git a/src/HTTP/HTTPMessage.cpp b/src/HTTP/HTTPMessage.cpp
new file mode 100644
index 000000000..cf7525b6c
--- /dev/null
+++ b/src/HTTP/HTTPMessage.cpp
@@ -0,0 +1,159 @@
+
+// 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(AString::npos)
+{
+}
+
+
+
+
+
+void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value)
+{
+ auto Key = StrToLower(a_Key);
+ auto 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")
+ {
+ if (!StringToInteger(m_Headers[Key], m_ContentLength))
+ {
+ m_ContentLength = 0;
+ }
+ }
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cHTTPOutgoingResponse:
+
+cHTTPOutgoingResponse::cHTTPOutgoingResponse(void) :
+ super(mkResponse)
+{
+}
+
+
+
+
+
+void cHTTPOutgoingResponse::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 (auto itr = m_Headers.cbegin(), end = m_Headers.cend(); 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");
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cHTTPIncomingRequest:
+
+cHTTPIncomingRequest::cHTTPIncomingRequest(const AString & a_Method, const AString & a_URL):
+ Super(mkRequest),
+ m_Method(a_Method),
+ m_URL(a_URL)
+{
+}
+
+
+
+
+
+AString cHTTPIncomingRequest::GetURLPath(void) const
+{
+ auto idxQuestionMark = m_URL.find('?');
+ if (idxQuestionMark == AString::npos)
+ {
+ return m_URL;
+ }
+ else
+ {
+ return m_URL.substr(0, idxQuestionMark);
+ }
+}
+
+
+
+
+
+void cHTTPIncomingRequest::AddHeader(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;
+ }
+ }
+ if ((a_Key == "Connection") && (NoCaseCompare(a_Value, "keep-alive") == 0))
+ {
+ m_AllowKeepAlive = true;
+ }
+}
+
+
+
+
diff --git a/src/HTTP/HTTPMessage.h b/src/HTTP/HTTPMessage.h
new file mode 100644
index 000000000..9afcd5b97
--- /dev/null
+++ b/src/HTTP/HTTPMessage.h
@@ -0,0 +1,158 @@
+
+// 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 eStatus
+ {
+ HTTP_OK = 200,
+ HTTP_BAD_REQUEST = 400,
+ } ;
+
+ enum eKind
+ {
+ mkRequest,
+ mkResponse,
+ } ;
+
+ cHTTPMessage(eKind a_Kind);
+
+ // Force a virtual destructor in all descendants
+ virtual ~cHTTPMessage() {}
+
+ /** Adds a header into the internal map of headers. Recognizes special headers: Content-Type and Content-Length */
+ virtual void AddHeader(const AString & a_Key, const AString & a_Value);
+
+ void SetContentType (const AString & a_ContentType) { m_ContentType = a_ContentType; }
+ void SetContentLength(size_t a_ContentLength) { m_ContentLength = a_ContentLength; }
+
+ const AString & GetContentType (void) const { return m_ContentType; }
+ size_t GetContentLength(void) const { return m_ContentLength; }
+
+protected:
+ typedef std::map<AString, AString> cNameValueMap;
+
+ eKind m_Kind;
+
+ /** Map of headers, with their keys lowercased. */
+ AStringMap 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.
+ AString::npos when the object is created.
+ Parsed by AddHeader() or set directly by SetContentLength() */
+ size_t m_ContentLength;
+} ;
+
+
+
+
+
+/** Stores outgoing response headers and serializes them to an HTTP data stream. */
+class cHTTPOutgoingResponse :
+ public cHTTPMessage
+{
+ typedef cHTTPMessage super;
+
+public:
+ cHTTPOutgoingResponse(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;
+} ;
+
+
+
+
+
+/** Provides storage for an incoming HTTP request. */
+class cHTTPIncomingRequest:
+ public cHTTPMessage
+{
+ typedef cHTTPMessage Super;
+public:
+ /** Base class for anything that can be used as the UserData for the request. */
+ class cUserData
+ {
+ public:
+ // Force a virtual destructor in descendants:
+ virtual ~cUserData() {}
+ };
+ typedef SharedPtr<cUserData> cUserDataPtr;
+
+
+ /** Creates a new instance of the class, containing the method and URL provided by the client. */
+ cHTTPIncomingRequest(const AString & a_Method, const AString & a_URL);
+
+ /** 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 path part of the URL. */
+ AString GetURLPath(void) const;
+
+ /** Returns true if the request has had the Auth header present. */
+ 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; }
+
+ bool DoesAllowKeepAlive(void) const { return m_AllowKeepAlive; }
+
+ /** Attaches any kind of data to this request, to be later retrieved by GetUserData(). */
+ void SetUserData(cUserDataPtr a_UserData) { m_UserData = a_UserData; }
+
+ /** Returns the data attached to this request by the class client. */
+ cUserDataPtr GetUserData(void) { return m_UserData; }
+
+ /** Adds the specified header into the internal list of headers.
+ Overrides the parent to add recognizing additional headers: auth and keepalive. */
+ virtual void AddHeader(const AString & a_Key, const AString & a_Value) override;
+
+protected:
+
+ /** Method of the request (GET / PUT / POST / ...) */
+ AString m_Method;
+
+ /** Full URL of the request */
+ AString m_URL;
+
+ /** 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;
+
+ /** Set to true if the request indicated that it supports keepalives.
+ If false, the server will close the connection once the request is finished */
+ bool m_AllowKeepAlive;
+
+ /** Any data attached to the request by the class client. */
+ cUserDataPtr m_UserData;
+};
diff --git a/src/HTTP/HTTPMessageParser.cpp b/src/HTTP/HTTPMessageParser.cpp
new file mode 100644
index 000000000..10d3d4ad9
--- /dev/null
+++ b/src/HTTP/HTTPMessageParser.cpp
@@ -0,0 +1,222 @@
+
+// HTTPMessageParser.cpp
+
+// Implements the cHTTPMessageParser class that parses HTTP messages (request or response) being pushed into the parser,
+// and reports the individual parts via callbacks
+
+#include "Globals.h"
+#include "HTTPMessageParser.h"
+
+
+
+
+
+cHTTPMessageParser::cHTTPMessageParser(cHTTPMessageParser::cCallbacks & a_Callbacks):
+ m_Callbacks(a_Callbacks),
+ m_EnvelopeParser(*this)
+{
+ Reset();
+}
+
+
+
+
+
+size_t cHTTPMessageParser::Parse(const char * a_Data, size_t a_Size)
+{
+ // If parsing already finished or errorred, let the caller keep all the data:
+ if (m_IsFinished || m_HasHadError)
+ {
+ return 0;
+ }
+
+ // If still waiting for the status line, add to buffer and try parsing it:
+ auto inBufferSoFar = m_Buffer.size();
+ if (m_FirstLine.empty())
+ {
+ m_Buffer.append(a_Data, a_Size);
+ auto bytesConsumedFirstLine = ParseFirstLine();
+ ASSERT(bytesConsumedFirstLine <= inBufferSoFar + a_Size); // Haven't consumed more data than there is in the buffer
+ ASSERT(bytesConsumedFirstLine > inBufferSoFar); // Have consumed at least the previous buffer contents
+ if (m_FirstLine.empty())
+ {
+ // All data used, but not a complete status line yet.
+ return a_Size;
+ }
+ if (m_HasHadError)
+ {
+ return AString::npos;
+ }
+ // Status line completed, feed the rest of the buffer into the envelope parser:
+ auto bytesConsumedEnvelope = m_EnvelopeParser.Parse(m_Buffer.data(), m_Buffer.size());
+ if (bytesConsumedEnvelope == AString::npos)
+ {
+ m_HasHadError = true;
+ m_Callbacks.OnError("Failed to parse the envelope");
+ return AString::npos;
+ }
+ ASSERT(bytesConsumedEnvelope <= bytesConsumedFirstLine + a_Size); // Haven't consumed more data than there was in the buffer
+ m_Buffer.erase(0, bytesConsumedEnvelope);
+ if (!m_EnvelopeParser.IsInHeaders())
+ {
+ HeadersFinished();
+ // Process any data still left in the buffer as message body:
+ auto bytesConsumedBody = ParseBody(m_Buffer.data(), m_Buffer.size());
+ if (bytesConsumedBody == AString::npos)
+ {
+ // Error has already been reported by ParseBody, just bail out:
+ return AString::npos;
+ }
+ return bytesConsumedBody + bytesConsumedEnvelope + bytesConsumedFirstLine - inBufferSoFar;
+ }
+ return a_Size;
+ } // if (m_FirstLine.empty())
+
+ // If still parsing headers, send them to the envelope parser:
+ if (m_EnvelopeParser.IsInHeaders())
+ {
+ auto bytesConsumed = m_EnvelopeParser.Parse(a_Data, a_Size);
+ if (bytesConsumed == AString::npos)
+ {
+ m_HasHadError = true;
+ m_Callbacks.OnError("Failed to parse the envelope");
+ return AString::npos;
+ }
+ if (!m_EnvelopeParser.IsInHeaders())
+ {
+ HeadersFinished();
+ // Process any data still left as message body:
+ auto bytesConsumedBody = ParseBody(a_Data + bytesConsumed, a_Size - bytesConsumed);
+ if (bytesConsumedBody == AString::npos)
+ {
+ // Error has already been reported by ParseBody, just bail out:
+ return AString::npos;
+ }
+ }
+ return a_Size;
+ }
+
+ // Already parsing the body
+ return ParseBody(a_Data, a_Size);
+}
+
+
+
+
+
+void cHTTPMessageParser::Reset(void)
+{
+ m_HasHadError = false;
+ m_IsFinished = false;
+ m_FirstLine.clear();
+ m_Buffer.clear();
+ m_EnvelopeParser.Reset();
+ m_TransferEncodingParser.reset();
+ m_TransferEncoding.clear();
+ m_ContentLength = 0;
+}
+
+
+
+
+size_t cHTTPMessageParser::ParseFirstLine(void)
+{
+ auto idxLineEnd = m_Buffer.find("\r\n");
+ if (idxLineEnd == AString::npos)
+ {
+ // Not a complete line yet
+ return m_Buffer.size();
+ }
+ m_FirstLine = m_Buffer.substr(0, idxLineEnd);
+ m_Buffer.erase(0, idxLineEnd + 2);
+ m_Callbacks.OnFirstLine(m_FirstLine);
+ return idxLineEnd + 2;
+}
+
+
+
+
+size_t cHTTPMessageParser::ParseBody(const char * a_Data, size_t a_Size)
+{
+ if (m_TransferEncodingParser == nullptr)
+ {
+ // We have no Transfer-encoding parser assigned. This should have happened when finishing the envelope
+ OnError("No transfer encoding parser");
+ return AString::npos;
+ }
+
+ // Parse the body using the transfer encoding parser:
+ // (Note that TE parser returns the number of bytes left, while we return the number of bytes consumed)
+ return a_Size - m_TransferEncodingParser->Parse(a_Data, a_Size);
+}
+
+
+
+
+
+void cHTTPMessageParser::HeadersFinished(void)
+{
+ m_Callbacks.OnHeadersFinished();
+ m_TransferEncodingParser = cTransferEncodingParser::Create(*this, m_TransferEncoding, m_ContentLength);
+ if (m_TransferEncodingParser == nullptr)
+ {
+ OnError(Printf("Unknown transfer encoding: %s", m_TransferEncoding.c_str()));
+ return;
+ }
+}
+
+
+
+
+
+void cHTTPMessageParser::OnHeaderLine(const AString & a_Key, const AString & a_Value)
+{
+ m_Callbacks.OnHeaderLine(a_Key, a_Value);
+ auto Key = StrToLower(a_Key);
+ if (Key == "content-length")
+ {
+ if (!StringToInteger(a_Value, m_ContentLength))
+ {
+ OnError(Printf("Invalid content length header value: \"%s\"", a_Value.c_str()));
+ }
+ return;
+ }
+ if (Key == "transfer-encoding")
+ {
+ m_TransferEncoding = a_Value;
+ return;
+ }
+}
+
+
+
+
+
+void cHTTPMessageParser::OnError(const AString & a_ErrorDescription)
+{
+ m_HasHadError = true;
+ m_Callbacks.OnError(a_ErrorDescription);
+}
+
+
+
+
+
+void cHTTPMessageParser::OnBodyData(const void * a_Data, size_t a_Size)
+{
+ m_Callbacks.OnBodyData(a_Data, a_Size);
+}
+
+
+
+
+
+void cHTTPMessageParser::OnBodyFinished(void)
+{
+ m_IsFinished = true;
+ m_Callbacks.OnBodyFinished();
+}
+
+
+
+
diff --git a/src/HTTP/HTTPMessageParser.h b/src/HTTP/HTTPMessageParser.h
new file mode 100644
index 000000000..f07de0492
--- /dev/null
+++ b/src/HTTP/HTTPMessageParser.h
@@ -0,0 +1,125 @@
+
+// HTTPMessageParser.h
+
+// Declares the cHTTPMessageParser class that parses HTTP messages (request or response) being pushed into the parser,
+// and reports the individual parts via callbacks
+
+
+
+
+
+#pragma once
+
+#include "EnvelopeParser.h"
+#include "TransferEncodingParser.h"
+
+
+
+
+
+class cHTTPMessageParser:
+ protected cEnvelopeParser::cCallbacks,
+ protected cTransferEncodingParser::cCallbacks
+{
+public:
+ class cCallbacks
+ {
+ public:
+ // Force a virtual destructor in descendants:
+ virtual ~cCallbacks() {}
+
+ /** Called when an error has occured while parsing. */
+ virtual void OnError(const AString & a_ErrorDescription) = 0;
+
+ /** Called when the first line (request / status) is fully parsed. */
+ virtual void OnFirstLine(const AString & a_FirstLine) = 0;
+
+ /** Called when a single header line is parsed. */
+ virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) = 0;
+
+ /** Called when all the headers have been parsed. */
+ virtual void OnHeadersFinished(void) = 0;
+
+ /** Called for each chunk of the incoming body data. */
+ virtual void OnBodyData(const void * a_Data, size_t a_Size) = 0;
+
+ /** Called when the entire body has been reported by OnBodyData(). */
+ virtual void OnBodyFinished(void) = 0;
+ };
+
+ /** Creates a new parser instance that will use the specified callbacks for reporting. */
+ cHTTPMessageParser(cCallbacks & a_Callbacks);
+
+ /** Parses the incoming data and calls the appropriate callbacks.
+ Returns the number of bytes consumed or AString::npos number for error. */
+ size_t Parse(const char * a_Data, size_t a_Size);
+
+ /** Called when the server indicates no more data will be sent (HTTP 1.0 socket closed).
+ Finishes all parsing and calls apropriate callbacks (error if incomplete response). */
+ void Finish(void);
+
+ /** Returns true if the entire response has been already parsed. */
+ bool IsFinished(void) const { return m_IsFinished; }
+
+ /** Resets the parser to the initial state, so that a new request can be parsed. */
+ void Reset(void);
+
+
+protected:
+
+ /** The callbacks used for reporting. */
+ cCallbacks & m_Callbacks;
+
+ /** Set to true if an error has been encountered by the parser. */
+ bool m_HasHadError;
+
+ /** True if the response has been fully parsed. */
+ bool m_IsFinished;
+
+ /** The complete first line of the response. Empty if not parsed yet. */
+ AString m_FirstLine;
+
+ /** Buffer for the incoming data until the status line is parsed. */
+ AString m_Buffer;
+
+ /** Parser for the envelope data (headers) */
+ cEnvelopeParser m_EnvelopeParser;
+
+ /** The specific parser for the transfer encoding used by this response. */
+ cTransferEncodingParserPtr m_TransferEncodingParser;
+
+ /** The transfer encoding to be used by the parser.
+ Filled while parsing headers, used when headers are finished. */
+ AString m_TransferEncoding;
+
+ /** The content length, parsed from the headers, if available.
+ Unused for chunked encoding.
+ Filled while parsing headers, used when headers are finished. */
+ size_t m_ContentLength;
+
+
+ /** Parses the first line out of m_Buffer.
+ Removes the first line from m_Buffer, if appropriate.
+ Returns the number of bytes consumed out of m_Buffer, or AString::npos number for error. */
+ size_t ParseFirstLine(void);
+
+ /** Parses the message body.
+ Processes transfer encoding and calls the callbacks for body data.
+ Returns the number of bytes consumed or AString::npos number for error. */
+ size_t ParseBody(const char * a_Data, size_t a_Size);
+
+ /** Called internally when the headers-parsing has just finished. */
+ void HeadersFinished(void);
+
+ // cEnvelopeParser::cCallbacks overrides:
+ virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override;
+
+ // cTransferEncodingParser::cCallbacks overrides:
+ virtual void OnError(const AString & a_ErrorDescription) override;
+ virtual void OnBodyData(const void * a_Data, size_t a_Size) override;
+ virtual void OnBodyFinished(void) override;
+};
+
+
+
+
diff --git a/src/HTTP/HTTPServer.cpp b/src/HTTP/HTTPServer.cpp
new file mode 100644
index 000000000..5a5bee045
--- /dev/null
+++ b/src/HTTP/HTTPServer.cpp
@@ -0,0 +1,212 @@
+
+// HTTPServer.cpp
+
+// Implements the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing
+
+#include "Globals.h"
+#include "HTTPServer.h"
+#include "HTTPMessageParser.h"
+#include "HTTPServerConnection.h"
+#include "HTTPFormParser.h"
+#include "SslHTTPServerConnection.h"
+
+
+
+
+
+// Disable MSVC warnings:
+#if defined(_MSC_VER)
+ #pragma warning(push)
+ #pragma warning(disable:4355) // 'this' : used in base member initializer list
+#endif
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cHTTPServerListenCallbacks:
+
+class cHTTPServerListenCallbacks:
+ public cNetwork::cListenCallbacks
+{
+public:
+ cHTTPServerListenCallbacks(cHTTPServer & a_HTTPServer, UInt16 a_Port):
+ m_HTTPServer(a_HTTPServer),
+ m_Port(a_Port)
+ {
+ }
+
+protected:
+ /** The HTTP server instance that we're attached to. */
+ cHTTPServer & m_HTTPServer;
+
+ /** The port for which this instance is responsible. */
+ UInt16 m_Port;
+
+ // cNetwork::cListenCallbacks overrides:
+ virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override
+ {
+ return m_HTTPServer.OnIncomingConnection(a_RemoteIPAddress, a_RemotePort);
+ }
+ virtual void OnAccepted(cTCPLink & a_Link) override {}
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
+ {
+ LOGWARNING("HTTP server error on port %d: %d (%s)", m_Port, a_ErrorCode, a_ErrorMsg.c_str());
+ }
+};
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cHTTPServer:
+
+cHTTPServer::cHTTPServer(void) :
+ m_Callbacks(nullptr)
+{
+}
+
+
+
+
+
+cHTTPServer::~cHTTPServer()
+{
+ Stop();
+}
+
+
+
+
+
+bool cHTTPServer::Initialize(void)
+{
+ // Read the HTTPS cert + key:
+ AString CertFile = cFile::ReadWholeFile("webadmin/httpscert.crt");
+ AString KeyFile = cFile::ReadWholeFile("webadmin/httpskey.pem");
+ if (!CertFile.empty() && !KeyFile.empty())
+ {
+ m_Cert.reset(new cX509Cert);
+ int res = m_Cert->Parse(CertFile.data(), CertFile.size());
+ if (res == 0)
+ {
+ m_CertPrivKey.reset(new cCryptoKey);
+ int res2 = m_CertPrivKey->ParsePrivate(KeyFile.data(), KeyFile.size(), "");
+ if (res2 != 0)
+ {
+ // Reading the private key failed, reset the cert:
+ LOGWARNING("WebServer: Cannot read HTTPS certificate private key: -0x%x", -res2);
+ m_Cert.reset();
+ }
+ }
+ else
+ {
+ LOGWARNING("WebServer: Cannot read HTTPS certificate: -0x%x", -res);
+ }
+ }
+
+ // Notify the admin about the HTTPS / HTTP status
+ if (m_Cert.get() == nullptr)
+ {
+ LOGWARNING("WebServer: The server is running in unsecured HTTP mode.");
+ LOGINFO("Put a valid HTTPS certificate in file 'webadmin/httpscert.crt' and its corresponding private key to 'webadmin/httpskey.pem' (without any password) to enable HTTPS support");
+ }
+ else
+ {
+ LOGINFO("WebServer: The server is running in secure HTTPS mode.");
+ }
+ return true;
+}
+
+
+
+
+
+bool cHTTPServer::Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports)
+{
+ m_Callbacks = &a_Callbacks;
+
+ // Open up requested ports:
+ for (auto port : a_Ports)
+ {
+ UInt16 PortNum;
+ if (!StringToInteger(port, PortNum))
+ {
+ LOGWARNING("WebServer: Invalid port value: \"%s\". Ignoring.", port.c_str());
+ continue;
+ }
+ auto Handle = cNetwork::Listen(PortNum, std::make_shared<cHTTPServerListenCallbacks>(*this, PortNum));
+ if (Handle->IsListening())
+ {
+ m_ServerHandles.push_back(Handle);
+ }
+ } // for port - a_Ports[]
+
+ // Report success if at least one port opened successfully:
+ return !m_ServerHandles.empty();
+}
+
+
+
+
+
+void cHTTPServer::Stop(void)
+{
+ for (auto handle : m_ServerHandles)
+ {
+ handle->Close();
+ }
+ m_ServerHandles.clear();
+}
+
+
+
+
+
+cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort)
+{
+ UNUSED(a_RemoteIPAddress);
+ UNUSED(a_RemotePort);
+
+ if (m_Cert.get() != nullptr)
+ {
+ return std::make_shared<cSslHTTPServerConnection>(*this, m_Cert, m_CertPrivKey);
+ }
+ else
+ {
+ return std::make_shared<cHTTPServerConnection>(*this);
+ }
+}
+
+
+
+
+
+void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
+{
+ m_Callbacks->OnRequestBegun(a_Connection, a_Request);
+}
+
+
+
+
+
+void cHTTPServer::RequestBody(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const void * a_Data, size_t a_Size)
+{
+ m_Callbacks->OnRequestBody(a_Connection, a_Request, reinterpret_cast<const char *>(a_Data), a_Size);
+}
+
+
+
+
+
+void cHTTPServer::RequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
+{
+ m_Callbacks->OnRequestFinished(a_Connection, a_Request);
+}
+
+
+
+
diff --git a/src/HTTP/HTTPServer.h b/src/HTTP/HTTPServer.h
new file mode 100644
index 000000000..1de0a6ce9
--- /dev/null
+++ b/src/HTTP/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/Network.h"
+#include "../IniFile.h"
+#include "PolarSSL++/RsaPrivateKey.h"
+#include "PolarSSL++/CryptoKey.h"
+#include "PolarSSL++/X509Cert.h"
+
+
+
+
+
+// fwd:
+class cHTTPMessage;
+class cHTTPRequestParser;
+class cHTTPIncomingRequest;
+class cHTTPServerConnection;
+
+
+
+
+
+
+class cHTTPServer
+{
+public:
+ class cCallbacks
+ {
+ public:
+ virtual ~cCallbacks() {}
+
+ /** Called when a new request arrives over a connection and all its headers have been parsed.
+ The request body needn't have arrived yet. */
+ virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) = 0;
+
+ /** Called when another part of request body has arrived.
+ May be called multiple times for a single request. */
+ virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const char * a_Data, size_t a_Size) = 0;
+
+ /** Called when the request body has been fully received in previous calls to OnRequestBody() */
+ virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) = 0;
+ } ;
+
+ cHTTPServer(void);
+ virtual ~cHTTPServer();
+
+ /** Initializes the server - reads the cert files etc. */
+ bool Initialize(void);
+
+ /** Starts the server and assigns the callbacks to use for incoming requests */
+ bool Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports);
+
+ /** Stops the server, drops all current connections */
+ void Stop(void);
+
+protected:
+ friend class cHTTPServerConnection;
+ friend class cSslHTTPServerConnection;
+ friend class cHTTPServerListenCallbacks;
+
+ /** The cNetwork API handle for the listening socket. */
+ cServerHandlePtrs m_ServerHandles;
+
+ /** The callbacks to call for various events */
+ cCallbacks * m_Callbacks;
+
+ /** The server certificate to use for the SSL connections */
+ cX509CertPtr m_Cert;
+
+ /** The private key for m_Cert. */
+ cCryptoKeyPtr m_CertPrivKey;
+
+
+ /** Called by cHTTPServerListenCallbacks when there's a new incoming connection.
+ Returns the connection instance to be used as the cTCPLink callbacks. */
+ cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort);
+
+ /** Called by cHTTPServerConnection when it finishes parsing the request header */
+ void NewRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request);
+
+ /** Called by cHTTPConenction when it receives more data for the request body.
+ May be called multiple times for a single request. */
+ void RequestBody(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const void * a_Data, size_t a_Size);
+
+ /** Called by cHTTPServerConnection when it detects that the request has finished (all of its body has been received) */
+ void RequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request);
+} ;
+
+
+
+
+
diff --git a/src/HTTP/HTTPServerConnection.cpp b/src/HTTP/HTTPServerConnection.cpp
new file mode 100644
index 000000000..9edec2886
--- /dev/null
+++ b/src/HTTP/HTTPServerConnection.cpp
@@ -0,0 +1,240 @@
+
+// HTTPConnection.cpp
+
+// Implements the cHTTPServerConnection class representing a single persistent connection in the HTTP server.
+
+#include "Globals.h"
+#include "HTTPServerConnection.h"
+#include "HTTPMessage.h"
+#include "HTTPMessageParser.h"
+#include "HTTPServer.h"
+
+
+
+
+
+cHTTPServerConnection::cHTTPServerConnection(cHTTPServer & a_HTTPServer) :
+ m_HTTPServer(a_HTTPServer),
+ m_Parser(*this),
+ m_CurrentRequest(nullptr)
+{
+ // LOGD("HTTP: New connection at %p", this);
+}
+
+
+
+
+
+cHTTPServerConnection::~cHTTPServerConnection()
+{
+ // LOGD("HTTP: Connection deleting: %p", this);
+ m_CurrentRequest.reset();
+}
+
+
+
+
+
+void cHTTPServerConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response)
+{
+ SendData(Printf("HTTP/1.1 %d %s\r\n", a_StatusCode, a_Response.c_str()));
+ SendData(Printf("Content-Length: %u\r\n\r\n", static_cast<unsigned>(a_Response.size())));
+ SendData(a_Response.data(), a_Response.size());
+ m_CurrentRequest.reset();
+ m_Parser.Reset();
+}
+
+
+
+
+
+void cHTTPServerConnection::SendNeedAuth(const AString & a_Realm)
+{
+ SendData(Printf("HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str()));
+ m_CurrentRequest.reset();
+ m_Parser.Reset();
+}
+
+
+
+
+
+void cHTTPServerConnection::Send(const cHTTPOutgoingResponse & a_Response)
+{
+ ASSERT(m_CurrentRequest != nullptr);
+ AString toSend;
+ a_Response.AppendToData(toSend);
+ SendData(toSend);
+}
+
+
+
+
+
+void cHTTPServerConnection::Send(const void * a_Data, size_t a_Size)
+{
+ ASSERT(m_CurrentRequest != nullptr);
+ // We're sending in Chunked transfer encoding
+ SendData(Printf(SIZE_T_FMT_HEX "\r\n", a_Size));
+ SendData(a_Data, a_Size);
+ SendData("\r\n");
+}
+
+
+
+
+
+void cHTTPServerConnection::FinishResponse(void)
+{
+ ASSERT(m_CurrentRequest != nullptr);
+ SendData("0\r\n\r\n");
+ m_CurrentRequest.reset();
+ m_Parser.Reset();
+}
+
+
+
+
+
+void cHTTPServerConnection::Terminate(void)
+{
+ if (m_CurrentRequest != nullptr)
+ {
+ m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
+ }
+ m_Link.reset();
+}
+
+
+
+
+
+void cHTTPServerConnection::OnLinkCreated(cTCPLinkPtr a_Link)
+{
+ ASSERT(m_Link == nullptr);
+ m_Link = a_Link;
+}
+
+
+
+
+
+void cHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size)
+{
+ ASSERT(m_Link != nullptr);
+
+ m_Parser.Parse(a_Data, a_Size);
+}
+
+
+
+
+
+void cHTTPServerConnection::OnRemoteClosed(void)
+{
+ if (m_CurrentRequest != nullptr)
+ {
+ m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
+ }
+ m_Link.reset();
+}
+
+
+
+
+
+
+void cHTTPServerConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
+{
+ OnRemoteClosed();
+}
+
+
+
+
+
+void cHTTPServerConnection::OnError(const AString & a_ErrorDescription)
+{
+ OnRemoteClosed();
+}
+
+
+
+
+
+void cHTTPServerConnection::OnFirstLine(const AString & a_FirstLine)
+{
+ // Create a new request object for this request:
+ auto split = StringSplit(a_FirstLine, " ");
+ if (split.size() < 2)
+ {
+ // Invalid request line. We need at least the Method and URL
+ OnRemoteClosed();
+ return;
+ }
+ m_CurrentRequest.reset(new cHTTPIncomingRequest(split[0], split[1]));
+}
+
+
+
+
+
+void cHTTPServerConnection::OnHeaderLine(const AString & a_Key, const AString & a_Value)
+{
+ if (m_CurrentRequest == nullptr)
+ {
+ return;
+ }
+ m_CurrentRequest->AddHeader(a_Key, a_Value);
+}
+
+
+
+
+
+void cHTTPServerConnection::OnHeadersFinished(void)
+{
+ if (m_CurrentRequest == nullptr)
+ {
+ return;
+ }
+ m_HTTPServer.NewRequest(*this, *m_CurrentRequest);
+}
+
+
+
+
+
+void cHTTPServerConnection::OnBodyData(const void * a_Data, size_t a_Size)
+{
+ if (m_CurrentRequest == nullptr)
+ {
+ return;
+ }
+ m_HTTPServer.RequestBody(*this, *m_CurrentRequest, a_Data, a_Size);
+}
+
+
+
+
+
+void cHTTPServerConnection::OnBodyFinished(void)
+{
+ // Process the request and reset:
+ m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
+ m_CurrentRequest.reset();
+ m_Parser.Reset();
+}
+
+
+
+
+
+void cHTTPServerConnection::SendData(const void * a_Data, size_t a_Size)
+{
+ m_Link->Send(a_Data, a_Size);
+}
+
+
+
+
diff --git a/src/HTTP/HTTPServerConnection.h b/src/HTTP/HTTPServerConnection.h
new file mode 100644
index 000000000..4390471d0
--- /dev/null
+++ b/src/HTTP/HTTPServerConnection.h
@@ -0,0 +1,118 @@
+
+// HTTPConnection.h
+
+// Declares the cHTTPConnection class representing a single persistent connection in the HTTP server.
+
+
+
+
+
+#pragma once
+
+#include "../OSSupport/Network.h"
+#include "HTTPMessageParser.h"
+
+
+
+
+
+// fwd:
+class cHTTPServer;
+class cHTTPOutgoingResponse;
+class cHTTPIncomingRequest;
+
+
+
+
+class cHTTPServerConnection :
+ public cTCPLink::cCallbacks,
+ public cHTTPMessageParser::cCallbacks
+{
+public:
+ /** Creates a new instance, connected to the specified HTTP server instance */
+ cHTTPServerConnection(cHTTPServer & a_HTTPServer);
+
+ // Force a virtual destructor in all descendants
+ virtual ~cHTTPServerConnection();
+
+ /** Sends HTTP status code together with a_Reason (used for HTTP errors).
+ Sends the a_Reason as the body as well, so that browsers display it.
+ Clears the current request (since it's finished by this call). */
+ void SendStatusAndReason(int a_StatusCode, const AString & a_Reason);
+
+ /** Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm.
+ Clears the current request (since it's finished by this call). */
+ void SendNeedAuth(const AString & a_Realm);
+
+ /** Sends the headers contained in a_Response */
+ void Send(const cHTTPOutgoingResponse & a_Response);
+
+ /** Sends the data as the response (may be called multiple times) */
+ void Send(const void * a_Data, size_t 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).
+ Clears the current request (since it's finished by this call). */
+ void FinishResponse(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;
+
+ /** The parser responsible for reading the requests. */
+ cHTTPMessageParser m_Parser;
+
+ /** The request being currently received
+ Valid only between having parsed the headers and finishing receiving the body. */
+ std::unique_ptr<cHTTPIncomingRequest> m_CurrentRequest;
+
+ /** The network link attached to this connection. */
+ cTCPLinkPtr m_Link;
+
+
+ // cTCPLink::cCallbacks overrides:
+ /** The link instance has been created, remember it. */
+ virtual void OnLinkCreated(cTCPLinkPtr a_Link) override;
+
+ /** Data is received from the client. */
+ virtual void OnReceivedData(const char * a_Data, size_t a_Size) override;
+
+ /** The socket has been closed for any reason. */
+ virtual void OnRemoteClosed(void) override;
+
+ /** An error has occurred on the socket. */
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
+
+ // cHTTPMessageParser::cCallbacks overrides:
+ virtual void OnError(const AString & a_ErrorDescription) override;
+ virtual void OnFirstLine(const AString & a_FirstLine) override;
+ virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override;
+ virtual void OnHeadersFinished(void) override;
+ virtual void OnBodyData(const void * a_Data, size_t a_Size) override;
+ virtual void OnBodyFinished(void) override;
+
+ // Overridable:
+ /** Called to send raw data over the link. Descendants may provide data transformations (SSL etc.) */
+ virtual void SendData(const void * a_Data, size_t a_Size);
+
+ /** Sends the raw data over the link.
+ Descendants may provide data transformations (SSL etc.) via the overridable SendData() function. */
+ void SendData(const AString & a_Data)
+ {
+ SendData(a_Data.data(), a_Data.size());
+ }
+} ;
+
+typedef std::vector<cHTTPServerConnection *> cHTTPServerConnections;
+
+
+
+
+
diff --git a/src/HTTP/MultipartParser.cpp b/src/HTTP/MultipartParser.cpp
new file mode 100644
index 000000000..09f4fd02a
--- /dev/null
+++ b/src/HTTP/MultipartParser.cpp
@@ -0,0 +1,254 @@
+
+// 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)
+{
+ // 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, size_t a_Size)
+{
+ // Skip parsing if invalid
+ if (!m_IsValid)
+ {
+ return;
+ }
+
+ // Append to buffer, then parse it:
+ m_IncomingData.append(a_Data, a_Size);
+ for (;;)
+ {
+ if (m_EnvelopeParser.IsInHeaders())
+ {
+ size_t BytesConsumed = m_EnvelopeParser.Parse(m_IncomingData.data(), m_IncomingData.size());
+ if (BytesConsumed == AString::npos)
+ {
+ 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/src/HTTP/MultipartParser.h b/src/HTTP/MultipartParser.h
new file mode 100644
index 000000000..4f20b2bed
--- /dev/null
+++ b/src/HTTP/MultipartParser.h
@@ -0,0 +1,79 @@
+
+// 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:
+ // Force a virtual destructor in descendants:
+ virtual ~cCallbacks() {}
+
+ /** 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, size_t 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, size_t 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, size_t 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, size_t a_Size);
+
+ // cEnvelopeParser overrides:
+ virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override;
+} ;
+
+
+
+
diff --git a/src/HTTP/NameValueParser.cpp b/src/HTTP/NameValueParser.cpp
new file mode 100644
index 000000000..f759c4d21
--- /dev/null
+++ b/src/HTTP/NameValueParser.cpp
@@ -0,0 +1,413 @@
+
+// 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 (size_t 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, size_t a_Size, bool a_AllowsKeyOnly) :
+ m_State(psKeySpace),
+ m_AllowsKeyOnly(a_AllowsKeyOnly)
+{
+ Parse(a_Data, a_Size);
+}
+
+
+
+
+
+void cNameValueParser::Parse(const char * a_Data, size_t a_Size)
+{
+ ASSERT(m_State != psFinished); // Calling Parse() after Finish() is wrong!
+
+ size_t Last = 0;
+ for (size_t i = 0; i < a_Size;)
+ {
+ switch (m_State)
+ {
+ case psInvalid:
+ case psFinished:
+ {
+ return;
+ }
+
+ 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;
+ }
+ } // 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/src/HTTP/NameValueParser.h b/src/HTTP/NameValueParser.h
new file mode 100644
index 000000000..e205079db
--- /dev/null
+++ b/src/HTTP/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, size_t a_Size, bool a_AllowsKeyOnly = true);
+
+ /** Parses the data given */
+ void Parse(const char * a_Data, size_t a_Size);
+
+ /** Notifies the parser that no more data will be coming. Returns true if the parser state is valid */
+ bool Finish(void);
+
+ /** Returns true if the data parsed so far was valid */
+ bool IsValid(void) const { return (m_State != psInvalid); }
+
+ /** Returns true if the parser expects no more data */
+ bool IsFinished(void) const { return ((m_State == psInvalid) || (m_State == psFinished)); }
+
+protected:
+ enum eState
+ {
+ psKeySpace, ///< Parsing the space in front of the next key
+ psKey, ///< Currently adding more chars to the key in m_CurrentKey
+ psEqualSpace, ///< Space after m_CurrentKey
+ psEqual, ///< Just parsed the = sign after a name
+ psValueInSQuotes, ///< Just parsed a Single-quote sign after the Equal sign
+ psValueInDQuotes, ///< Just parsed a Double-quote sign after the Equal sign
+ psValueRaw, ///< Just parsed a raw value without a quote
+ psAfterValue, ///< Just finished parsing the value, waiting for semicolon or data end
+ psInvalid, ///< The parser has encountered an invalid input; further parsing is skipped
+ psFinished, ///< The parser has already been instructed to finish and doesn't expect any more data
+ } ;
+
+ /** The current state of the parser */
+ eState m_State;
+
+ /** If true, the parser will accept keys without an equal sign and the value */
+ bool m_AllowsKeyOnly;
+
+ /** Buffer for the current Key */
+ AString m_CurrentKey;
+
+ /** Buffer for the current Value; */
+ AString m_CurrentValue;
+
+
+} ;
+
+
+
+
diff --git a/src/HTTP/SslHTTPServerConnection.cpp b/src/HTTP/SslHTTPServerConnection.cpp
new file mode 100644
index 000000000..547e6de3a
--- /dev/null
+++ b/src/HTTP/SslHTTPServerConnection.cpp
@@ -0,0 +1,115 @@
+
+// SslHTTPConnection.cpp
+
+// Implements the cSslHTTPServerConnection class representing a HTTP connection made over a SSL link
+
+#include "Globals.h"
+#include "SslHTTPServerConnection.h"
+#include "HTTPServer.h"
+
+
+
+
+
+cSslHTTPServerConnection::cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey) :
+ super(a_HTTPServer),
+ m_Ssl(64000),
+ m_Cert(a_Cert),
+ m_PrivateKey(a_PrivateKey)
+{
+ m_Ssl.Initialize(false);
+ m_Ssl.SetOwnCert(a_Cert, a_PrivateKey);
+}
+
+
+
+
+
+cSslHTTPServerConnection::~cSslHTTPServerConnection()
+{
+ m_Ssl.NotifyClose();
+}
+
+
+
+
+
+void cSslHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size)
+{
+ // Process the received data:
+ const char * Data = a_Data;
+ size_t Size = a_Size;
+ for (;;)
+ {
+ // Try to write as many bytes into Ssl's "incoming" buffer as possible:
+ size_t BytesWritten = 0;
+ if (Size > 0)
+ {
+ BytesWritten = m_Ssl.WriteIncoming(Data, Size);
+ Data += BytesWritten;
+ Size -= BytesWritten;
+ }
+
+ // Try to read as many bytes from SSL's decryption as possible:
+ char Buffer[32000];
+ int NumRead = m_Ssl.ReadPlain(Buffer, sizeof(Buffer));
+ if (NumRead > 0)
+ {
+ super::OnReceivedData(Buffer, static_cast<size_t>(NumRead));
+ // The link may have closed while processing the data, bail out:
+ return;
+ }
+ else if (NumRead == POLARSSL_ERR_NET_WANT_READ)
+ {
+ // SSL requires us to send data to peer first, do so by "sending" empty data:
+ SendData(nullptr, 0);
+ }
+
+ // If both failed, bail out:
+ if ((BytesWritten == 0) && (NumRead <= 0))
+ {
+ return;
+ }
+ }
+}
+
+
+
+
+
+void cSslHTTPServerConnection::SendData(const void * a_Data, size_t a_Size)
+{
+ const char * OutgoingData = reinterpret_cast<const char *>(a_Data);
+ size_t pos = 0;
+ for (;;)
+ {
+ // Write as many bytes from our buffer to SSL's encryption as possible:
+ int NumWritten = 0;
+ if (pos < a_Size)
+ {
+ NumWritten = m_Ssl.WritePlain(OutgoingData + pos, a_Size - pos);
+ if (NumWritten > 0)
+ {
+ pos += static_cast<size_t>(NumWritten);
+ }
+ }
+
+ // Read as many bytes from SSL's "outgoing" buffer as possible:
+ char Buffer[32000];
+ size_t NumBytes = m_Ssl.ReadOutgoing(Buffer, sizeof(Buffer));
+ if (NumBytes > 0)
+ {
+ m_Link->Send(Buffer, NumBytes);
+ }
+
+ // If both failed, bail out:
+ if ((NumWritten <= 0) && (NumBytes == 0))
+ {
+ return;
+ }
+ }
+}
+
+
+
+
diff --git a/src/HTTP/SslHTTPServerConnection.h b/src/HTTP/SslHTTPServerConnection.h
new file mode 100644
index 000000000..eceb80fb7
--- /dev/null
+++ b/src/HTTP/SslHTTPServerConnection.h
@@ -0,0 +1,47 @@
+
+// SslHTTPServerConnection.h
+
+// Declares the cSslHTTPServerConnection class representing a HTTP connection made over an SSL link
+
+
+
+
+
+#pragma once
+
+#include "HTTPServerConnection.h"
+#include "PolarSSL++/BufferedSslContext.h"
+
+
+
+
+
+class cSslHTTPServerConnection :
+ public cHTTPServerConnection
+{
+ typedef cHTTPServerConnection super;
+
+public:
+ /** Creates a new connection on the specified server.
+ Sends the specified cert as the server certificate, uses the private key for decryption. */
+ cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey);
+
+ ~cSslHTTPServerConnection();
+
+protected:
+ cBufferedSslContext m_Ssl;
+
+ /** The certificate to send to the client */
+ cX509CertPtr m_Cert;
+
+ /** The private key used for the certificate */
+ cCryptoKeyPtr m_PrivateKey;
+
+ // cHTTPConnection overrides:
+ virtual void OnReceivedData(const char * a_Data, size_t a_Size) override; // Data is received from the client
+ virtual void SendData(const void * a_Data, size_t a_Size) override; // Data is to be sent to client
+} ;
+
+
+
+
diff --git a/src/HTTP/TransferEncodingParser.cpp b/src/HTTP/TransferEncodingParser.cpp
new file mode 100644
index 000000000..a14900e9c
--- /dev/null
+++ b/src/HTTP/TransferEncodingParser.cpp
@@ -0,0 +1,394 @@
+
+// TransferEncodingParser.cpp
+
+// Implements the cTransferEncodingParser class and its descendants representing the parsers for the various transfer encodings (chunked etc.)
+
+#include "Globals.h"
+#include "TransferEncodingParser.h"
+#include "EnvelopeParser.h"
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cChunkedTEParser:
+
+class cChunkedTEParser:
+ public cTransferEncodingParser,
+ public cEnvelopeParser::cCallbacks
+{
+ typedef cTransferEncodingParser Super;
+
+public:
+ cChunkedTEParser(Super::cCallbacks & a_Callbacks):
+ Super(a_Callbacks),
+ m_State(psChunkLength),
+ m_ChunkDataLengthLeft(0),
+ m_TrailerParser(*this)
+ {
+ }
+
+
+protected:
+ enum eState
+ {
+ psChunkLength, ///< Parsing the chunk length hex number
+ psChunkLengthTrailer, ///< Any trailer (chunk extension) specified after the chunk length
+ psChunkLengthLF, ///< The LF character after the CR character terminating the chunk length
+ psChunkData, ///< Relaying chunk data
+ psChunkDataCR, ///< Skipping the extra CR character after chunk data
+ psChunkDataLF, ///< Skipping the extra LF character after chunk data
+ psTrailer, ///< Received an empty chunk, parsing the trailer (through the envelope parser)
+ psFinished, ///< The parser has finished parsing, either successfully or with an error
+ };
+
+ /** The current state of the parser (parsing chunk length / chunk data). */
+ eState m_State;
+
+ /** Number of bytes that still belong to the chunk currently being parsed.
+ When in psChunkLength, the value is the currently parsed length digits. */
+ size_t m_ChunkDataLengthLeft;
+
+ /** The parser used for the last (empty) chunk's trailer data */
+ cEnvelopeParser m_TrailerParser;
+
+
+ /** Calls the OnError callback and sets parser state to finished. */
+ void Error(const AString & a_ErrorMsg)
+ {
+ m_State = psFinished;
+ m_Callbacks.OnError(a_ErrorMsg);
+ }
+
+
+ /** Parses the incoming data, the current state is psChunkLength.
+ Stops parsing when either the chunk length has been read, or there is no more data in the input.
+ Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */
+ size_t ParseChunkLength(const char * a_Data, size_t a_Size)
+ {
+ // Expected input: <hexnumber>[;<trailer>]<CR><LF>
+ // Only the hexnumber is parsed into m_ChunkDataLengthLeft, the rest is postponed into psChunkLengthTrailer or psChunkLengthLF
+ for (size_t i = 0; i < a_Size; i++)
+ {
+ switch (a_Data[i])
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ m_ChunkDataLengthLeft = m_ChunkDataLengthLeft * 16 + static_cast<decltype(m_ChunkDataLengthLeft)>(a_Data[i] - '0');
+ break;
+ }
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ {
+ m_ChunkDataLengthLeft = m_ChunkDataLengthLeft * 16 + static_cast<decltype(m_ChunkDataLengthLeft)>(a_Data[i] - 'a' + 10);
+ break;
+ }
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ {
+ m_ChunkDataLengthLeft = m_ChunkDataLengthLeft * 16 + static_cast<decltype(m_ChunkDataLengthLeft)>(a_Data[i] - 'A' + 10);
+ break;
+ }
+ case '\r':
+ {
+ m_State = psChunkLengthLF;
+ return i + 1;
+ }
+ case ';':
+ {
+ m_State = psChunkLengthTrailer;
+ return i + 1;
+ }
+ default:
+ {
+ Error(Printf("Invalid character in chunk length line: 0x%x", a_Data[i]));
+ return AString::npos;
+ }
+ } // switch (a_Data[i])
+ } // for i - a_Data[]
+ return a_Size;
+ }
+
+
+ /** Parses the incoming data, the current state is psChunkLengthTrailer.
+ Stops parsing when either the chunk length trailer has been read, or there is no more data in the input.
+ Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */
+ size_t ParseChunkLengthTrailer(const char * a_Data, size_t a_Size)
+ {
+ // Expected input: <trailer><CR><LF>
+ // The LF itself is not parsed, it is instead postponed into psChunkLengthLF
+ for (size_t i = 0; i < a_Size; i++)
+ {
+ switch (a_Data[i])
+ {
+ case '\r':
+ {
+ m_State = psChunkLengthLF;
+ return i;
+ }
+ default:
+ {
+ if (a_Data[i] < 32)
+ {
+ // Only printable characters are allowed in the trailer
+ Error(Printf("Invalid character in chunk length line: 0x%x", a_Data[i]));
+ return AString::npos;
+ }
+ }
+ } // switch (a_Data[i])
+ } // for i - a_Data[]
+ return a_Size;
+ }
+
+
+ /** Parses the incoming data, the current state is psChunkLengthLF.
+ Only the LF character is expected, if found, moves to psChunkData, otherwise issues an error.
+ If the chunk length that just finished reading is equal to 0, signals the end of stream (via psTrailer).
+ Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */
+ size_t ParseChunkLengthLF(const char * a_Data, size_t a_Size)
+ {
+ // Expected input: <LF>
+ if (a_Size == 0)
+ {
+ return 0;
+ }
+ if (a_Data[0] == '\n')
+ {
+ if (m_ChunkDataLengthLeft == 0)
+ {
+ m_State = psTrailer;
+ }
+ else
+ {
+ m_State = psChunkData;
+ }
+ return 1;
+ }
+ Error(Printf("Invalid character past chunk length's CR: 0x%x", a_Data[0]));
+ return AString::npos;
+ }
+
+
+ /** Consumes as much chunk data from the input as possible.
+ Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error() handler). */
+ size_t ParseChunkData(const char * a_Data, size_t a_Size)
+ {
+ ASSERT(m_ChunkDataLengthLeft > 0);
+ auto bytes = std::min(a_Size, m_ChunkDataLengthLeft);
+ m_ChunkDataLengthLeft -= bytes;
+ m_Callbacks.OnBodyData(a_Data, bytes);
+ if (m_ChunkDataLengthLeft == 0)
+ {
+ m_State = psChunkDataCR;
+ }
+ return bytes;
+ }
+
+
+ /** Parses the incoming data, the current state is psChunkDataCR.
+ Only the CR character is expected, if found, moves to psChunkDataLF, otherwise issues an error.
+ Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */
+ size_t ParseChunkDataCR(const char * a_Data, size_t a_Size)
+ {
+ // Expected input: <CR>
+ if (a_Size == 0)
+ {
+ return 0;
+ }
+ if (a_Data[0] == '\r')
+ {
+ m_State = psChunkDataLF;
+ return 1;
+ }
+ Error(Printf("Invalid character past chunk data: 0x%x", a_Data[0]));
+ return AString::npos;
+ }
+
+
+
+
+ /** Parses the incoming data, the current state is psChunkDataCR.
+ Only the CR character is expected, if found, moves to psChunkDataLF, otherwise issues an error.
+ Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */
+ size_t ParseChunkDataLF(const char * a_Data, size_t a_Size)
+ {
+ // Expected input: <LF>
+ if (a_Size == 0)
+ {
+ return 0;
+ }
+ if (a_Data[0] == '\n')
+ {
+ m_State = psChunkLength;
+ return 1;
+ }
+ Error(Printf("Invalid character past chunk data's CR: 0x%x", a_Data[0]));
+ return AString::npos;
+ }
+
+
+ /** Parses the incoming data, the current state is psChunkDataCR.
+ The trailer is normally a set of "Header: Value" lines, terminated by an empty line. Use the m_TrailerParser for that.
+ Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */
+ size_t ParseTrailer(const char * a_Data, size_t a_Size)
+ {
+ auto res = m_TrailerParser.Parse(a_Data, a_Size);
+ if (res == AString::npos)
+ {
+ Error("Error while parsing the trailer");
+ }
+ if ((res < a_Size) || !m_TrailerParser.IsInHeaders())
+ {
+ m_Callbacks.OnBodyFinished();
+ m_State = psFinished;
+ }
+ return res;
+ }
+
+
+ // cTransferEncodingParser overrides:
+ virtual size_t Parse(const char * a_Data, size_t a_Size) override
+ {
+ while ((a_Size > 0) && (m_State != psFinished))
+ {
+ size_t consumed = 0;
+ switch (m_State)
+ {
+ case psChunkLength: consumed = ParseChunkLength (a_Data, a_Size); break;
+ case psChunkLengthTrailer: consumed = ParseChunkLengthTrailer(a_Data, a_Size); break;
+ case psChunkLengthLF: consumed = ParseChunkLengthLF (a_Data, a_Size); break;
+ case psChunkData: consumed = ParseChunkData (a_Data, a_Size); break;
+ case psChunkDataCR: consumed = ParseChunkDataCR (a_Data, a_Size); break;
+ case psChunkDataLF: consumed = ParseChunkDataLF (a_Data, a_Size); break;
+ case psTrailer: consumed = ParseTrailer (a_Data, a_Size); break;
+ case psFinished: consumed = 0; break; // Not supposed to happen, but Clang complains without it
+ }
+ if (consumed == AString::npos)
+ {
+ return AString::npos;
+ }
+ a_Data += consumed;
+ a_Size -= consumed;
+ }
+ return a_Size;
+ }
+
+ virtual void Finish(void) override
+ {
+ if (m_State != psFinished)
+ {
+ Error(Printf("ChunkedTransferEncoding: Finish signal received before the data stream ended (state: %d)", m_State));
+ }
+ m_State = psFinished;
+ }
+
+
+ // cEnvelopeParser::cCallbacks overrides:
+ virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override
+ {
+ // Ignored
+ }
+};
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cIdentityTEParser:
+
+class cIdentityTEParser:
+ public cTransferEncodingParser
+{
+ typedef cTransferEncodingParser Super;
+
+public:
+ cIdentityTEParser(cCallbacks & a_Callbacks, size_t a_ContentLength):
+ Super(a_Callbacks),
+ m_BytesLeft(a_ContentLength)
+ {
+ }
+
+
+protected:
+ /** How many bytes of content are left before the message ends. */
+ size_t m_BytesLeft;
+
+ // cTransferEncodingParser overrides:
+ virtual size_t Parse(const char * a_Data, size_t a_Size) override
+ {
+ auto size = std::min(a_Size, m_BytesLeft);
+ if (size > 0)
+ {
+ m_Callbacks.OnBodyData(a_Data, size);
+ }
+ m_BytesLeft -= size;
+ if (m_BytesLeft == 0)
+ {
+ m_Callbacks.OnBodyFinished();
+ }
+ return a_Size - size;
+ }
+
+ virtual void Finish(void) override
+ {
+ if (m_BytesLeft > 0)
+ {
+ m_Callbacks.OnError("IdentityTransferEncoding: body was truncated");
+ }
+ else
+ {
+ // BodyFinished has already been called, just bail out
+ }
+ }
+};
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cTransferEncodingParser:
+
+cTransferEncodingParserPtr cTransferEncodingParser::Create(
+ cCallbacks & a_Callbacks,
+ const AString & a_TransferEncoding,
+ size_t a_ContentLength
+)
+{
+ if (a_TransferEncoding == "chunked")
+ {
+ return std::make_shared<cChunkedTEParser>(a_Callbacks);
+ }
+ if (a_TransferEncoding == "identity")
+ {
+ return std::make_shared<cIdentityTEParser>(a_Callbacks, a_ContentLength);
+ }
+ if (a_TransferEncoding.empty())
+ {
+ return std::make_shared<cIdentityTEParser>(a_Callbacks, a_ContentLength);
+ }
+ return nullptr;
+}
+
+
+
+
diff --git a/src/HTTP/TransferEncodingParser.h b/src/HTTP/TransferEncodingParser.h
new file mode 100644
index 000000000..ce3d01df7
--- /dev/null
+++ b/src/HTTP/TransferEncodingParser.h
@@ -0,0 +1,76 @@
+
+// TransferEncodingParser.h
+
+// Declares the cTransferEncodingParser class representing the parser for the various transfer encodings (chunked etc.)
+
+#pragma once
+
+
+
+
+
+// fwd:
+class cTransferEncodingParser;
+typedef SharedPtr<cTransferEncodingParser> cTransferEncodingParserPtr;
+
+
+
+
+
+/** Used as both the interface that all the parsers share and the (static) factory creating such parsers. */
+class cTransferEncodingParser
+{
+public:
+ class cCallbacks
+ {
+ public:
+ // Force a virtual destructor in descendants
+ virtual ~cCallbacks() {}
+
+ /** Called when an error has occured while parsing. */
+ virtual void OnError(const AString & a_ErrorDescription) = 0;
+
+ /** Called for each chunk of the incoming body data. */
+ virtual void OnBodyData(const void * a_Data, size_t a_Size) = 0;
+
+ /** Called when the entire body has been reported by OnBodyData(). */
+ virtual void OnBodyFinished(void) = 0;
+ };
+
+
+ // Force a virtual destructor in all descendants
+ virtual ~cTransferEncodingParser() {}
+
+ /** Parses the incoming data and calls the appropriate callbacks.
+ Returns the number of bytes from the end of a_Data that is already not part of this message (if the parser can detect it).
+ Returns AString::npos on an error. */
+ virtual size_t Parse(const char * a_Data, size_t a_Size) = 0;
+
+ /** To be called when the stream is terminated from the source (connection closed).
+ Flushes any buffers and calls appropriate callbacks. */
+ virtual void Finish(void) = 0;
+
+ /** Creates a new parser for the specified encoding.
+ If the encoding is not known, returns a nullptr.
+ a_ContentLength is the length of the content, received in a Content-Length header.
+ It is used for the Identity encoding, it is ignored for the Chunked encoding. */
+ static cTransferEncodingParserPtr Create(
+ cCallbacks & a_Callbacks,
+ const AString & a_TransferEncoding,
+ size_t a_ContentLength
+ );
+
+protected:
+ /** The callbacks used to report progress. */
+ cCallbacks & m_Callbacks;
+
+
+ cTransferEncodingParser(cCallbacks & a_Callbacks):
+ m_Callbacks(a_Callbacks)
+ {
+ }
+};
+
+
+
+
diff --git a/src/HTTP/UrlParser.cpp b/src/HTTP/UrlParser.cpp
new file mode 100644
index 000000000..05db3e413
--- /dev/null
+++ b/src/HTTP/UrlParser.cpp
@@ -0,0 +1,200 @@
+
+// UrlParser.cpp
+
+// Implements the cUrlParser class that parses string URL into individual parts
+
+#include "Globals.h"
+#include "UrlParser.h"
+
+
+
+
+
+UInt16 cUrlParser::GetDefaultPort(const AString & a_Scheme)
+{
+ if (a_Scheme == "http")
+ {
+ return 80;
+ }
+ else if (a_Scheme == "https")
+ {
+ return 443;
+ }
+ else if (a_Scheme == "ftp")
+ {
+ return 21;
+ }
+ else if (a_Scheme == "mailto")
+ {
+ return 25;
+ }
+ return 0;
+}
+
+
+
+
+
+std::pair<bool, AString> cUrlParser::ParseAuthorityPart(
+ const AString & a_AuthorityPart,
+ AString & a_Username,
+ AString & a_Password,
+ AString & a_Host,
+ UInt16 & a_Port
+)
+{
+ /*
+ a_AuthorityPart format:
+ [user:password@]host[:port]
+ host can be an IPv4, hostname, or an IPv6 enclosed in brackets
+ Assume only the password can contain an additional at-sign
+ */
+
+ // Split the authority on the last at-sign, if present:
+ auto idxLastAtSign = a_AuthorityPart.find_last_of('@');
+ auto credPart = (idxLastAtSign == AString::npos) ? AString() : a_AuthorityPart.substr(0, idxLastAtSign);
+ auto srvrPart = (idxLastAtSign == AString::npos) ? a_AuthorityPart : a_AuthorityPart.substr(idxLastAtSign + 1);
+
+ // User credentials are completely optional:
+ auto idxCredColon = credPart.find(':');
+ a_Username = credPart.substr(0, idxCredColon);
+ a_Password = (idxCredColon == AString::npos) ? AString() : credPart.substr(idxCredColon + 1);
+
+ // Host can be a hostname, IPv4 or [IPv6]. If in brackets, search for the closing bracket first
+ if (srvrPart.empty())
+ {
+ // No host information at all. Bail out with success
+ a_Host.clear();
+ return std::make_pair(true, AString());
+ }
+ if (srvrPart[0] == '[')
+ {
+ // [IPv6] host, search for the closing bracket
+ auto idxClosingBracket = srvrPart.find(']');
+ if (idxClosingBracket == AString::npos)
+ {
+ return std::make_pair(false, "Invalid IPv6-like address, missing closing bracket");
+ }
+ a_Host = srvrPart.substr(0, idxClosingBracket);
+ auto portPart = srvrPart.substr(idxClosingBracket + 1);
+ if (portPart.empty())
+ {
+ // No port was specified, return success
+ return std::make_pair(true, AString());
+ }
+ if (portPart[0] != ':')
+ {
+ return std::make_pair(false, "Invalid port format after IPv6 address, mising colon");
+ }
+ if (!StringToInteger(portPart.substr(2), a_Port))
+ {
+ return std::make_pair(false, "Failed to parse port number after IPv6 address");
+ }
+ return std::make_pair(true, AString());
+ }
+
+ // Not an [IPv6] address, split on the last colon:
+ auto idxLastColon = srvrPart.find_last_of(':');
+ a_Host = srvrPart.substr(0, idxLastColon);
+ if (idxLastColon == AString::npos)
+ {
+ // No port was specified, return success
+ return std::make_pair(true, AString());
+ }
+ auto portPart = srvrPart.substr(idxLastColon + 1);
+ if (!StringToInteger(portPart, a_Port))
+ {
+ return std::make_pair(false, "Failed to parse port number after hostname");
+ }
+ return std::make_pair(true, AString());
+}
+
+
+
+
+
+std::pair<bool, AString> cUrlParser::Parse(
+ const AString & a_Url,
+ AString & a_Scheme,
+ AString & a_Username,
+ AString & a_Password,
+ AString & a_Host,
+ UInt16 & a_Port,
+ AString & a_Path,
+ AString & a_Query,
+ AString & a_Fragment
+)
+{
+ // Find the scheme - the text before the first colon:
+ auto idxColon = a_Url.find(':');
+ if (idxColon == AString::npos)
+ {
+ return std::make_pair(false, "Cannot parse the Scheme part of the URL");
+ }
+ a_Scheme = StrToLower(a_Url.substr(0, idxColon));
+ a_Port = GetDefaultPort(a_Scheme);
+ if (a_Port == 0)
+ {
+ return std::make_pair(false, Printf("Unknown URL scheme: \"%s\"", a_Scheme.c_str()));
+ }
+
+ // If the next two chars are a double-slash, skip them:
+ auto authStart = idxColon + 1;
+ if (a_Url.substr(authStart, 2) == "//")
+ {
+ authStart += 2;
+ }
+
+ // The Authority part follows the Scheme, until the first slash:
+ auto idxFirstSlash = a_Url.find('/', authStart + 1);
+ if (idxFirstSlash == AString::npos)
+ {
+ // No slash, the whole end of the Url is the authority part
+ idxFirstSlash = a_Url.size();
+ }
+
+ // Parse the Authority part into individual components:
+ auto res = ParseAuthorityPart(
+ a_Url.substr(authStart, idxFirstSlash - authStart),
+ a_Username, a_Password,
+ a_Host, a_Port
+ );
+ if (!res.first)
+ {
+ return res;
+ }
+
+ // Parse the rest into a path, query and fragment:
+ a_Path.clear();
+ a_Query.clear();
+ a_Fragment.clear();
+ if (idxFirstSlash == a_Url.size())
+ {
+ // No additional data, bail out with success
+ return std::make_pair(true, AString());
+ }
+ auto idxPathEnd = a_Url.find_first_of("?#", idxFirstSlash + 1);
+ if (idxPathEnd == AString::npos)
+ {
+ a_Path = a_Url.substr(idxFirstSlash);
+ return std::make_pair(true, AString());
+ }
+ a_Path = a_Url.substr(idxFirstSlash, idxPathEnd - idxFirstSlash);
+ auto idxHash = a_Url.find('#', idxPathEnd);
+ if (idxHash == AString::npos)
+ {
+ a_Query = a_Url.substr(idxPathEnd + 1);
+ return std::make_pair(true, AString());
+ }
+ if (idxHash > idxPathEnd)
+ {
+ a_Query = a_Url.substr(idxPathEnd + 1, idxHash - idxPathEnd - 1);
+ }
+ a_Fragment = a_Url.substr(idxHash + 1);
+ return std::make_pair(true, AString());
+}
+
+
+
+
+
diff --git a/src/HTTP/UrlParser.h b/src/HTTP/UrlParser.h
new file mode 100644
index 000000000..15a63e05d
--- /dev/null
+++ b/src/HTTP/UrlParser.h
@@ -0,0 +1,58 @@
+
+// UrlParser.h
+
+// Declares the cUrlParser class that parses string URL into individual parts
+
+
+
+
+
+#pragma once
+
+
+
+
+
+class cUrlParser
+{
+public:
+ /** Returns true if the specified scheme (http, ftp, mailto, ...) is recognized by the URL parser.
+ Is case sensitive, known schemes are always lowercase. */
+ static bool IsKnownScheme(const AString & a_Scheme) { return (GetDefaultPort(a_Scheme) > 0); }
+
+ /** Returns the default port used by the specified scheme / protocol.
+ If the scheme is not known, 0 is returned. */
+ static UInt16 GetDefaultPort(const AString & a_Scheme);
+
+ /** Parses the given Authority part of an URL into individual components.
+ Returns true on success,
+ returns false and error message on failure. */
+ static std::pair<bool, AString> ParseAuthorityPart(
+ const AString & a_AuthorityPart,
+ AString & a_Username,
+ AString & a_Password,
+ AString & a_Host,
+ UInt16 & a_Port
+ );
+
+ /** Parses the given URL into individual components.
+ Returns true on success,
+ returns false and error message on failure.
+ Fails if the scheme (protocol) is not known.
+ If port is missing, the default port for the specific scheme is applied. */
+ static std::pair<bool, AString> Parse(
+ const AString & a_Url,
+ AString & a_Scheme,
+ AString & a_Username,
+ AString & a_Password,
+ AString & a_Host,
+ UInt16 & a_Port,
+ AString & a_Path,
+ AString & a_Query,
+ AString & a_Fragment
+ );
+};
+
+
+
+