summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.gitattributes4
-rw-r--r--CMakeLists.txt5
-rw-r--r--src/Bindings/ManualBindings.cpp2
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/HTTP/CMakeLists.txt (renamed from src/HTTPServer/CMakeLists.txt)14
-rw-r--r--src/HTTP/EnvelopeParser.cpp (renamed from src/HTTPServer/EnvelopeParser.cpp)6
-rw-r--r--src/HTTP/EnvelopeParser.h (renamed from src/HTTPServer/EnvelopeParser.h)0
-rw-r--r--src/HTTP/HTTPFormParser.cpp (renamed from src/HTTPServer/HTTPFormParser.cpp)6
-rw-r--r--src/HTTP/HTTPFormParser.h (renamed from src/HTTPServer/HTTPFormParser.h)8
-rw-r--r--src/HTTP/HTTPMessage.cpp159
-rw-r--r--src/HTTP/HTTPMessage.h (renamed from src/HTTPServer/HTTPMessage.h)114
-rw-r--r--src/HTTP/HTTPMessageParser.cpp222
-rw-r--r--src/HTTP/HTTPMessageParser.h125
-rw-r--r--src/HTTP/HTTPServer.cpp (renamed from src/HTTPServer/HTTPServer.cpp)115
-rw-r--r--src/HTTP/HTTPServer.h (renamed from src/HTTPServer/HTTPServer.h)28
-rw-r--r--src/HTTP/HTTPServerConnection.cpp240
-rw-r--r--src/HTTP/HTTPServerConnection.h (renamed from src/HTTPServer/HTTPConnection.h)64
-rw-r--r--src/HTTP/MultipartParser.cpp (renamed from src/HTTPServer/MultipartParser.cpp)0
-rw-r--r--src/HTTP/MultipartParser.h (renamed from src/HTTPServer/MultipartParser.h)0
-rw-r--r--src/HTTP/NameValueParser.cpp (renamed from src/HTTPServer/NameValueParser.cpp)0
-rw-r--r--src/HTTP/NameValueParser.h (renamed from src/HTTPServer/NameValueParser.h)0
-rw-r--r--src/HTTP/SslHTTPServerConnection.cpp (renamed from src/HTTPServer/SslHTTPConnection.cpp)12
-rw-r--r--src/HTTP/SslHTTPServerConnection.h (renamed from src/HTTPServer/SslHTTPConnection.h)16
-rw-r--r--src/HTTP/TransferEncodingParser.cpp394
-rw-r--r--src/HTTP/TransferEncodingParser.h76
-rw-r--r--src/HTTP/UrlParser.cpp (renamed from src/HTTPServer/UrlParser.cpp)0
-rw-r--r--src/HTTP/UrlParser.h (renamed from src/HTTPServer/UrlParser.h)0
-rw-r--r--src/HTTPServer/HTTPConnection.cpp278
-rw-r--r--src/HTTPServer/HTTPMessage.cpp290
-rw-r--r--src/Root.h2
-rw-r--r--src/WebAdmin.cpp84
-rw-r--r--src/WebAdmin.h56
-rw-r--r--tests/CMakeLists.txt1
-rw-r--r--tests/HTTP/CMakeLists.txt60
-rw-r--r--tests/HTTP/HTTPMessageParser_file.cpp153
-rw-r--r--tests/HTTP/HTTPRequest1.data5
-rw-r--r--tests/HTTP/HTTPRequest2.data3
-rw-r--r--tests/HTTP/HTTPRequestParser_file.cpp153
-rw-r--r--tests/HTTP/HTTPResponse1.data10
-rw-r--r--tests/HTTP/HTTPResponse2.data15
-rw-r--r--tests/HTTP/HTTPResponseParser_file.cpp153
41 files changed, 1973 insertions, 902 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..6470a6393
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+# Set the *.data files to be checked out as binary files.
+# Used for the HTTP test data files, they need to have the CRLF line endings
+# even on Linux, because they're HTTP protocol dumps.
+*.data binary
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3234e93d7..2b5abb362 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -127,6 +127,9 @@ if(${SELF_TEST})
add_definitions(-DSELF_TEST)
endif()
+# Build all dependent libraries as static:
+SET(CMAKE_BUILD_STATIC_LIBRARIES ON)
+
@@ -263,6 +266,7 @@ if (MSVC)
if (${SELF_TEST})
set_target_properties(
Network
+ HTTP
PROPERTIES FOLDER Lib
)
set_target_properties(
@@ -274,6 +278,7 @@ if (MSVC)
creatable-exe
EchoServer
Google-exe
+ HTTPMessageParser_file-exe
LoadablePieces
NameLookup
PROPERTIES FOLDER Tests
diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp
index 110f22f1c..523244ed2 100644
--- a/src/Bindings/ManualBindings.cpp
+++ b/src/Bindings/ManualBindings.cpp
@@ -36,7 +36,7 @@
#include "../StringCompression.h"
#include "../CommandOutput.h"
#include "../BuildInfo.h"
-#include "../HTTPServer/UrlParser.h"
+#include "../HTTP/UrlParser.h"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 3d9e10aa5..5c57be1c9 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -8,7 +8,7 @@ include_directories (SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/../lib/polarssl/include
include_directories (SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/../lib/libevent/include")
set(FOLDERS
- OSSupport HTTPServer Items Blocks Protocol Generating PolarSSL++ Bindings
+ OSSupport HTTP Items Blocks Protocol Generating PolarSSL++ Bindings
WorldStorage Mobs Entities Simulator Simulator/IncrementalRedstoneSimulator
BlockEntities UI Noise
)
diff --git a/src/HTTPServer/CMakeLists.txt b/src/HTTP/CMakeLists.txt
index a08a1fcf8..acb3ac2cd 100644
--- a/src/HTTPServer/CMakeLists.txt
+++ b/src/HTTP/CMakeLists.txt
@@ -6,31 +6,35 @@ include_directories ("${PROJECT_SOURCE_DIR}/../")
SET (SRCS
EnvelopeParser.cpp
- HTTPConnection.cpp
HTTPFormParser.cpp
HTTPMessage.cpp
+ HTTPMessageParser.cpp
HTTPServer.cpp
+ HTTPServerConnection.cpp
MultipartParser.cpp
NameValueParser.cpp
- SslHTTPConnection.cpp
+ SslHTTPServerConnection.cpp
+ TransferEncodingParser.cpp
UrlParser.cpp
)
SET (HDRS
EnvelopeParser.h
- HTTPConnection.h
HTTPFormParser.h
HTTPMessage.h
+ HTTPMessageParser.h
HTTPServer.h
+ HTTPServerConnection.h
MultipartParser.h
NameValueParser.h
- SslHTTPConnection.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(HTTPConnection.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum")
+ 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()
diff --git a/src/HTTPServer/EnvelopeParser.cpp b/src/HTTP/EnvelopeParser.cpp
index 407e9dcfc..1c49b643f 100644
--- a/src/HTTPServer/EnvelopeParser.cpp
+++ b/src/HTTP/EnvelopeParser.cpp
@@ -28,12 +28,12 @@ size_t cEnvelopeParser::Parse(const char * a_Data, size_t a_Size)
}
// Start searching 1 char from the end of the already received data, if available:
- size_t SearchStart = m_IncomingData.size();
- SearchStart = (SearchStart > 1) ? SearchStart - 1 : 0;
+ 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);
+ size_t idxCRLF = m_IncomingData.find("\r\n", searchStart);
if (idxCRLF == AString::npos)
{
// Not a complete line yet, all input consumed:
diff --git a/src/HTTPServer/EnvelopeParser.h b/src/HTTP/EnvelopeParser.h
index 2fa930539..2fa930539 100644
--- a/src/HTTPServer/EnvelopeParser.h
+++ b/src/HTTP/EnvelopeParser.h
diff --git a/src/HTTPServer/HTTPFormParser.cpp b/src/HTTP/HTTPFormParser.cpp
index 497f84033..ea5da3c18 100644
--- a/src/HTTPServer/HTTPFormParser.cpp
+++ b/src/HTTP/HTTPFormParser.cpp
@@ -13,7 +13,7 @@
-cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks) :
+cHTTPFormParser::cHTTPFormParser(const cHTTPIncomingRequest & a_Request, cCallbacks & a_Callbacks) :
m_Callbacks(a_Callbacks),
m_IsValid(true),
m_IsCurrentPartFile(false),
@@ -121,7 +121,7 @@ bool cHTTPFormParser::Finish(void)
-bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request)
+bool cHTTPFormParser::HasFormData(const cHTTPIncomingRequest & a_Request)
{
const AString & ContentType = a_Request.GetContentType();
return (
@@ -138,7 +138,7 @@ bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request)
-void cHTTPFormParser::BeginMultipart(const cHTTPRequest & a_Request)
+void cHTTPFormParser::BeginMultipart(const cHTTPIncomingRequest & a_Request)
{
ASSERT(m_MultipartParser.get() == nullptr);
m_MultipartParser.reset(new cMultipartParser(a_Request.GetContentType(), *this));
diff --git a/src/HTTPServer/HTTPFormParser.h b/src/HTTP/HTTPFormParser.h
index f6cbe047f..6bf3e7d78 100644
--- a/src/HTTPServer/HTTPFormParser.h
+++ b/src/HTTP/HTTPFormParser.h
@@ -15,7 +15,7 @@
// fwd:
-class cHTTPRequest;
+class cHTTPIncomingRequest;
@@ -51,7 +51,7 @@ public:
/** Creates a parser that is tied to a request and notifies of various events using a callback mechanism */
- cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks);
+ 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);
@@ -64,7 +64,7 @@ public:
bool Finish(void);
/** Returns true if the headers suggest the request has form data parseable by this class */
- static bool HasFormData(const cHTTPRequest & a_Request);
+ static bool HasFormData(const cHTTPIncomingRequest & a_Request);
protected:
@@ -97,7 +97,7 @@ protected:
/** Sets up the object for parsing a fpkMultipart request */
- void BeginMultipart(const cHTTPRequest & a_Request);
+ void BeginMultipart(const cHTTPIncomingRequest & a_Request);
/** Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds) */
void ParseFormUrlEncoded(void);
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/HTTPServer/HTTPMessage.h b/src/HTTP/HTTPMessage.h
index 72ecb67d1..9afcd5b97 100644
--- a/src/HTTPServer/HTTPMessage.h
+++ b/src/HTTP/HTTPMessage.h
@@ -36,7 +36,7 @@ public:
virtual ~cHTTPMessage() {}
/** Adds a header into the internal map of headers. Recognizes special headers: Content-Type and Content-Length */
- void AddHeader(const AString & a_Key, const AString & a_Value);
+ 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; }
@@ -49,7 +49,8 @@ protected:
eKind m_Kind;
- cNameValueMap m_Headers;
+ /** Map of headers, with their keys lowercased. */
+ AStringMap m_Headers;
/** Type of the content; parsed by AddHeader(), set directly by SetContentLength() */
AString m_ContentType;
@@ -64,22 +65,42 @@ protected:
-class cHTTPRequest :
- public cHTTPMessage,
- protected cEnvelopeParser::cCallbacks
+/** Stores outgoing response headers and serializes them to an HTTP data stream. */
+class cHTTPOutgoingResponse :
+ public cHTTPMessage
{
typedef cHTTPMessage super;
public:
- cHTTPRequest(void);
+ 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;
+} ;
+
- /** Parses the request line and then headers from the received data.
- Returns the number of bytes consumed or AString::npos number for error
- */
- size_t ParseHeaders(const char * a_Data, size_t a_Size);
- /** Returns true if the request did contain a Content-Length header */
- bool HasReceivedContentLength(void) const { return (m_ContentLength != AString::npos); }
+
+
+/** 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; }
@@ -87,20 +108,10 @@ public:
/** Returns the URL used in the request */
const AString & GetURL(void) const { return m_URL; }
- /** Returns the URL used in the request, without any parameters */
- AString GetBareURL(void) const;
-
- /** Sets the UserData pointer that is stored within this request.
- The request doesn't touch this data (doesn't delete it)! */
- void SetUserData(void * a_UserData) { m_UserData = a_UserData; }
-
- /** Retrieves the UserData pointer that has been stored within this request. */
- void * GetUserData(void) const { return m_UserData; }
+ /** Returns the path part of the URL. */
+ AString GetURLPath(void) const;
- /** Returns true if more data is expected for the request headers */
- bool IsInHeaders(void) const { return m_EnvelopeParser.IsInHeaders(); }
-
- /** Returns true if the request did present auth data that was understood by the parser */
+ /** 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 */
@@ -111,15 +122,17 @@ public:
bool DoesAllowKeepAlive(void) const { return m_AllowKeepAlive; }
-protected:
- /** Parser for the envelope data */
- cEnvelopeParser m_EnvelopeParser;
+ /** Attaches any kind of data to this request, to be later retrieved by GetUserData(). */
+ void SetUserData(cUserDataPtr a_UserData) { m_UserData = a_UserData; }
- /** True if the data received so far is parsed successfully. When false, all further parsing is skipped */
- bool m_IsValid;
+ /** Returns the data attached to this request by the class client. */
+ cUserDataPtr GetUserData(void) { return m_UserData; }
- /** Bufferred incoming data, while parsing for the request line */
- AString m_IncomingHeaderData;
+ /** 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;
@@ -127,9 +140,6 @@ protected:
/** Full URL of the request */
AString m_URL;
- /** Data that the HTTPServer callbacks are allowed to store. */
- void * m_UserData;
-
/** Set to true if the request contains auth data that was understood by the parser */
bool m_HasAuth;
@@ -143,34 +153,6 @@ protected:
If false, the server will close the connection once the request is finished */
bool m_AllowKeepAlive;
-
- /** Parses the incoming data for the first line (RequestLine)
- Returns the number of bytes consumed, or AString::npos for an error
- */
- size_t ParseRequestLine(const char * a_Data, size_t a_Size);
-
- // cEnvelopeParser::cCallbacks overrides:
- virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override;
-} ;
-
-
-
-
-
-class cHTTPResponse :
- public cHTTPMessage
-{
- typedef cHTTPMessage super;
-
-public:
- cHTTPResponse(void);
-
- /** Appends the response to the specified datastream - response line and headers.
- The body will be sent later directly through cConnection::Send()
- */
- void AppendToData(AString & a_DataStream) const;
-} ;
-
-
-
-
+ /** 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/HTTPServer/HTTPServer.cpp b/src/HTTP/HTTPServer.cpp
index 814a87fe4..5a5bee045 100644
--- a/src/HTTPServer/HTTPServer.cpp
+++ b/src/HTTP/HTTPServer.cpp
@@ -5,10 +5,10 @@
#include "Globals.h"
#include "HTTPServer.h"
-#include "HTTPMessage.h"
-#include "HTTPConnection.h"
+#include "HTTPMessageParser.h"
+#include "HTTPServerConnection.h"
#include "HTTPFormParser.h"
-#include "SslHTTPConnection.h"
+#include "SslHTTPServerConnection.h"
@@ -24,102 +24,6 @@
-class cDebugCallbacks :
- public cHTTPServer::cCallbacks,
- protected cHTTPFormParser::cCallbacks
-{
- virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override
- {
- UNUSED(a_Connection);
-
- if (cHTTPFormParser::HasFormData(a_Request))
- {
- a_Request.SetUserData(new cHTTPFormParser(a_Request, *this));
- }
- }
-
-
- virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) override
- {
- UNUSED(a_Connection);
-
- cHTTPFormParser * FormParser = reinterpret_cast<cHTTPFormParser *>(a_Request.GetUserData());
- if (FormParser != nullptr)
- {
- FormParser->Parse(a_Data, a_Size);
- }
- }
-
-
- virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override
- {
- cHTTPFormParser * FormParser = reinterpret_cast<cHTTPFormParser *>(a_Request.GetUserData());
- if (FormParser != nullptr)
- {
- if (FormParser->Finish())
- {
- cHTTPResponse Resp;
- Resp.SetContentType("text/html");
- a_Connection.Send(Resp);
- a_Connection.Send("<html><body><table border=1 cellspacing=0><tr><th>Name</th><th>Value</th></tr>\r\n");
- for (cHTTPFormParser::iterator itr = FormParser->begin(), end = FormParser->end(); itr != end; ++itr)
- {
- a_Connection.Send(Printf("<tr><td valign=\"top\"><pre>%s</pre></td><td valign=\"top\"><pre>%s</pre></td></tr>\r\n", itr->first.c_str(), itr->second.c_str()));
- } // for itr - FormParser[]
- a_Connection.Send("</table></body></html>");
- return;
- }
-
- // Parsing failed:
- cHTTPResponse Resp;
- Resp.SetContentType("text/plain");
- a_Connection.Send(Resp);
- a_Connection.Send("Form parsing failed");
- return;
- }
-
- // Test the auth failure and success:
- if (a_Request.GetURL() == "/auth")
- {
- if (!a_Request.HasAuth() || (a_Request.GetAuthUsername() != "a") || (a_Request.GetAuthPassword() != "b"))
- {
- a_Connection.SendNeedAuth("Cuberite WebAdmin");
- return;
- }
- }
-
- cHTTPResponse Resp;
- Resp.SetContentType("text/plain");
- a_Connection.Send(Resp);
- a_Connection.Send("Hello, world");
- }
-
-
- virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) override
- {
- // TODO
- }
-
-
- virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, size_t a_Size) override
- {
- // TODO
- }
-
-
- virtual void OnFileEnd(cHTTPFormParser & a_Parser) override
- {
- // TODO
- }
-
-};
-
-static cDebugCallbacks g_DebugCallbacks;
-
-
-
-
-
////////////////////////////////////////////////////////////////////////////////
// cHTTPServerListenCallbacks:
@@ -268,11 +172,11 @@ cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_Remo
if (m_Cert.get() != nullptr)
{
- return std::make_shared<cSslHTTPConnection>(*this, m_Cert, m_CertPrivKey);
+ return std::make_shared<cSslHTTPServerConnection>(*this, m_Cert, m_CertPrivKey);
}
else
{
- return std::make_shared<cHTTPConnection>(*this);
+ return std::make_shared<cHTTPServerConnection>(*this);
}
}
@@ -280,7 +184,7 @@ cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_Remo
-void cHTTPServer::NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
+void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
m_Callbacks->OnRequestBegun(a_Connection, a_Request);
}
@@ -289,19 +193,18 @@ void cHTTPServer::NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Re
-void cHTTPServer::RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size)
+void cHTTPServer::RequestBody(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const void * a_Data, size_t a_Size)
{
- m_Callbacks->OnRequestBody(a_Connection, a_Request, a_Data, a_Size);
+ m_Callbacks->OnRequestBody(a_Connection, a_Request, reinterpret_cast<const char *>(a_Data), a_Size);
}
-void cHTTPServer::RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
+void cHTTPServer::RequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
m_Callbacks->OnRequestFinished(a_Connection, a_Request);
- a_Connection.AwaitNextRequest();
}
diff --git a/src/HTTPServer/HTTPServer.h b/src/HTTP/HTTPServer.h
index 2a094b413..1de0a6ce9 100644
--- a/src/HTTPServer/HTTPServer.h
+++ b/src/HTTP/HTTPServer.h
@@ -21,11 +21,9 @@
// fwd:
class cHTTPMessage;
-class cHTTPRequest;
-class cHTTPResponse;
-class cHTTPConnection;
-
-typedef std::vector<cHTTPConnection *> cHTTPConnections;
+class cHTTPRequestParser;
+class cHTTPIncomingRequest;
+class cHTTPServerConnection;
@@ -42,14 +40,14 @@ public:
/** 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(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0;
+ 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(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) = 0;
+ 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(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0;
+ virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) = 0;
} ;
cHTTPServer(void);
@@ -65,8 +63,8 @@ public:
void Stop(void);
protected:
- friend class cHTTPConnection;
- friend class cSslHTTPConnection;
+ friend class cHTTPServerConnection;
+ friend class cSslHTTPServerConnection;
friend class cHTTPServerListenCallbacks;
/** The cNetwork API handle for the listening socket. */
@@ -86,15 +84,15 @@ protected:
Returns the connection instance to be used as the cTCPLink callbacks. */
cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort);
- /** Called by cHTTPConnection when it finishes parsing the request header */
- void NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
+ /** 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(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size);
+ void RequestBody(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const void * a_Data, size_t a_Size);
- /** Called by cHTTPConnection when it detects that the request has finished (all of its body has been received) */
- void RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
+ /** 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/HTTPServer/HTTPConnection.h b/src/HTTP/HTTPServerConnection.h
index 414075411..4390471d0 100644
--- a/src/HTTPServer/HTTPConnection.h
+++ b/src/HTTP/HTTPServerConnection.h
@@ -10,6 +10,7 @@
#pragma once
#include "../OSSupport/Network.h"
+#include "HTTPMessageParser.h"
@@ -17,39 +18,34 @@
// fwd:
class cHTTPServer;
-class cHTTPResponse;
-class cHTTPRequest;
+class cHTTPOutgoingResponse;
+class cHTTPIncomingRequest;
-
-class cHTTPConnection :
- public cTCPLink::cCallbacks
+class cHTTPServerConnection :
+ public cTCPLink::cCallbacks,
+ public cHTTPMessageParser::cCallbacks
{
public:
+ /** Creates a new instance, connected to the specified HTTP server instance */
+ cHTTPServerConnection(cHTTPServer & a_HTTPServer);
- enum eState
- {
- wcsRecvHeaders, ///< Receiving request headers (m_CurrentRequest is created if nullptr)
- wcsRecvBody, ///< Receiving request body (m_CurrentRequest is valid)
- wcsRecvIdle, ///< Has received the entire body, waiting to send the response (m_CurrentRequest == nullptr)
- wcsSendingResp, ///< Sending response body (m_CurrentRequest == nullptr)
- wcsInvalid, ///< The request was malformed, the connection is closing
- } ;
-
- cHTTPConnection(cHTTPServer & a_HTTPServer);
- virtual ~cHTTPConnection();
+ // 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. */
+ 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 */
+ /** 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 cHTTPResponse & 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);
@@ -57,13 +53,10 @@ public:
/** Sends the data as the response (may be called multiple times) */
void Send(const AString & a_Data) { Send(a_Data.data(), a_Data.size()); }
- /** Indicates that the current response is finished, gets ready for receiving another request (HTTP 1.1 keepalive) */
+ /** Indicates that the current response is finished, gets ready for receiving another request (HTTP 1.1 keepalive).
+ Clears the current request (since it's finished by this call). */
void FinishResponse(void);
- /** Resets the internal connection state for a new request.
- Depending on the state, this will send an "InternalServerError" status or a "ResponseEnd" */
- void AwaitNextRequest(void);
-
/** Terminates the connection; finishes any request being currently processed */
void Terminate(void);
@@ -73,19 +66,12 @@ protected:
/** The parent webserver that is to be notified of events on this connection */
cHTTPServer & m_HTTPServer;
- /** All the incoming data until the entire request header is parsed */
- AString m_IncomingHeaderData;
-
- /** Status in which the request currently is */
- eState m_State;
+ /** 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. */
- cHTTPRequest * m_CurrentRequest;
-
- /** Number of bytes that remain to read for the complete body of the message to be received.
- Valid only in wcsRecvBody */
- size_t m_CurrentRequestBodyRemaining;
+ std::unique_ptr<cHTTPIncomingRequest> m_CurrentRequest;
/** The network link attached to this connection. */
cTCPLinkPtr m_Link;
@@ -104,6 +90,14 @@ protected:
/** 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);
@@ -116,7 +110,7 @@ protected:
}
} ;
-typedef std::vector<cHTTPConnection *> cHTTPConnections;
+typedef std::vector<cHTTPServerConnection *> cHTTPServerConnections;
diff --git a/src/HTTPServer/MultipartParser.cpp b/src/HTTP/MultipartParser.cpp
index 09f4fd02a..09f4fd02a 100644
--- a/src/HTTPServer/MultipartParser.cpp
+++ b/src/HTTP/MultipartParser.cpp
diff --git a/src/HTTPServer/MultipartParser.h b/src/HTTP/MultipartParser.h
index 4f20b2bed..4f20b2bed 100644
--- a/src/HTTPServer/MultipartParser.h
+++ b/src/HTTP/MultipartParser.h
diff --git a/src/HTTPServer/NameValueParser.cpp b/src/HTTP/NameValueParser.cpp
index f759c4d21..f759c4d21 100644
--- a/src/HTTPServer/NameValueParser.cpp
+++ b/src/HTTP/NameValueParser.cpp
diff --git a/src/HTTPServer/NameValueParser.h b/src/HTTP/NameValueParser.h
index e205079db..e205079db 100644
--- a/src/HTTPServer/NameValueParser.h
+++ b/src/HTTP/NameValueParser.h
diff --git a/src/HTTPServer/SslHTTPConnection.cpp b/src/HTTP/SslHTTPServerConnection.cpp
index 1cbe02312..547e6de3a 100644
--- a/src/HTTPServer/SslHTTPConnection.cpp
+++ b/src/HTTP/SslHTTPServerConnection.cpp
@@ -1,17 +1,17 @@
// SslHTTPConnection.cpp
-// Implements the cSslHTTPConnection class representing a HTTP connection made over a SSL link
+// Implements the cSslHTTPServerConnection class representing a HTTP connection made over a SSL link
#include "Globals.h"
-#include "SslHTTPConnection.h"
+#include "SslHTTPServerConnection.h"
#include "HTTPServer.h"
-cSslHTTPConnection::cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey) :
+cSslHTTPServerConnection::cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey) :
super(a_HTTPServer),
m_Ssl(64000),
m_Cert(a_Cert),
@@ -25,7 +25,7 @@ cSslHTTPConnection::cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509Ce
-cSslHTTPConnection::~cSslHTTPConnection()
+cSslHTTPServerConnection::~cSslHTTPServerConnection()
{
m_Ssl.NotifyClose();
}
@@ -34,7 +34,7 @@ cSslHTTPConnection::~cSslHTTPConnection()
-void cSslHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size)
+void cSslHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size)
{
// Process the received data:
const char * Data = a_Data;
@@ -77,7 +77,7 @@ void cSslHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size)
-void cSslHTTPConnection::SendData(const void * a_Data, size_t a_Size)
+void cSslHTTPServerConnection::SendData(const void * a_Data, size_t a_Size)
{
const char * OutgoingData = reinterpret_cast<const char *>(a_Data);
size_t pos = 0;
diff --git a/src/HTTPServer/SslHTTPConnection.h b/src/HTTP/SslHTTPServerConnection.h
index b35bd8ba0..eceb80fb7 100644
--- a/src/HTTPServer/SslHTTPConnection.h
+++ b/src/HTTP/SslHTTPServerConnection.h
@@ -1,7 +1,7 @@
-// SslHTTPConnection.h
+// SslHTTPServerConnection.h
-// Declared the cSslHTTPConnection class representing a HTTP connection made over a SSL link
+// Declares the cSslHTTPServerConnection class representing a HTTP connection made over an SSL link
@@ -9,24 +9,24 @@
#pragma once
-#include "HTTPConnection.h"
+#include "HTTPServerConnection.h"
#include "PolarSSL++/BufferedSslContext.h"
-class cSslHTTPConnection :
- public cHTTPConnection
+class cSslHTTPServerConnection :
+ public cHTTPServerConnection
{
- typedef cHTTPConnection super;
+ 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. */
- cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey);
+ cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey);
- ~cSslHTTPConnection();
+ ~cSslHTTPServerConnection();
protected:
cBufferedSslContext m_Ssl;
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/HTTPServer/UrlParser.cpp b/src/HTTP/UrlParser.cpp
index 05db3e413..05db3e413 100644
--- a/src/HTTPServer/UrlParser.cpp
+++ b/src/HTTP/UrlParser.cpp
diff --git a/src/HTTPServer/UrlParser.h b/src/HTTP/UrlParser.h
index 15a63e05d..15a63e05d 100644
--- a/src/HTTPServer/UrlParser.h
+++ b/src/HTTP/UrlParser.h
diff --git a/src/HTTPServer/HTTPConnection.cpp b/src/HTTPServer/HTTPConnection.cpp
deleted file mode 100644
index 0db154139..000000000
--- a/src/HTTPServer/HTTPConnection.cpp
+++ /dev/null
@@ -1,278 +0,0 @@
-
-// HTTPConnection.cpp
-
-// Implements the cHTTPConnection class representing a single persistent connection in the HTTP server.
-
-#include "Globals.h"
-#include "HTTPConnection.h"
-#include "HTTPMessage.h"
-#include "HTTPServer.h"
-
-
-
-
-
-cHTTPConnection::cHTTPConnection(cHTTPServer & a_HTTPServer) :
- m_HTTPServer(a_HTTPServer),
- m_State(wcsRecvHeaders),
- m_CurrentRequest(nullptr),
- m_CurrentRequestBodyRemaining(0)
-{
- // LOGD("HTTP: New connection at %p", this);
-}
-
-
-
-
-
-cHTTPConnection::~cHTTPConnection()
-{
- // LOGD("HTTP: Connection deleting: %p", this);
- delete m_CurrentRequest;
- m_CurrentRequest = nullptr;
-}
-
-
-
-
-
-void cHTTPConnection::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_State = wcsRecvHeaders;
-}
-
-
-
-
-
-void cHTTPConnection::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_State = wcsRecvHeaders;
-}
-
-
-
-
-
-void cHTTPConnection::Send(const cHTTPResponse & a_Response)
-{
- ASSERT(m_State == wcsRecvIdle);
- AString toSend;
- a_Response.AppendToData(toSend);
- m_State = wcsSendingResp;
- SendData(toSend);
-}
-
-
-
-
-
-void cHTTPConnection::Send(const void * a_Data, size_t a_Size)
-{
- ASSERT(m_State == wcsSendingResp);
- // 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 cHTTPConnection::FinishResponse(void)
-{
- ASSERT(m_State == wcsSendingResp);
- SendData("0\r\n\r\n");
- m_State = wcsRecvHeaders;
-}
-
-
-
-
-
-void cHTTPConnection::AwaitNextRequest(void)
-{
- switch (m_State)
- {
- case wcsRecvHeaders:
- {
- // Nothing has been received yet, or a special response was given (SendStatusAndReason() or SendNeedAuth())
- break;
- }
-
- case wcsRecvIdle:
- {
- // The client is waiting for a response, send an "Internal server error":
- SendData("HTTP/1.1 500 Internal Server Error\r\n\r\n");
- m_State = wcsRecvHeaders;
- break;
- }
-
- case wcsSendingResp:
- {
- // The response headers have been sent, we need to terminate the response body:
- SendData("0\r\n\r\n");
- m_State = wcsRecvHeaders;
- break;
- }
-
- default:
- {
- ASSERT(!"Unhandled state recovery");
- break;
- }
- }
-}
-
-
-
-
-
-void cHTTPConnection::Terminate(void)
-{
- if (m_CurrentRequest != nullptr)
- {
- m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
- }
- m_Link.reset();
-}
-
-
-
-
-
-void cHTTPConnection::OnLinkCreated(cTCPLinkPtr a_Link)
-{
- ASSERT(m_Link == nullptr);
- m_Link = a_Link;
-}
-
-
-
-
-
-void cHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size)
-{
- ASSERT(m_Link != nullptr);
-
- switch (m_State)
- {
- case wcsRecvHeaders:
- {
- if (m_CurrentRequest == nullptr)
- {
- m_CurrentRequest = new cHTTPRequest;
- }
-
- size_t BytesConsumed = m_CurrentRequest->ParseHeaders(a_Data, a_Size);
- if (BytesConsumed == AString::npos)
- {
- delete m_CurrentRequest;
- m_CurrentRequest = nullptr;
- m_State = wcsInvalid;
- m_Link->Close();
- m_Link.reset();
- return;
- }
- if (m_CurrentRequest->IsInHeaders())
- {
- // The request headers are not yet complete
- return;
- }
-
- // The request has finished parsing its headers successfully, notify of it:
- m_State = wcsRecvBody;
- m_HTTPServer.NewRequest(*this, *m_CurrentRequest);
- m_CurrentRequestBodyRemaining = m_CurrentRequest->GetContentLength();
- if (m_CurrentRequestBodyRemaining == AString::npos)
- {
- // The body length was not specified in the request, assume zero
- m_CurrentRequestBodyRemaining = 0;
- }
-
- // Process the rest of the incoming data into the request body:
- if (a_Size > BytesConsumed)
- {
- cHTTPConnection::OnReceivedData(a_Data + BytesConsumed, a_Size - BytesConsumed);
- return;
- }
- else
- {
- cHTTPConnection::OnReceivedData("", 0); // If the request has zero body length, let it be processed right-away
- return;
- }
- }
-
- case wcsRecvBody:
- {
- ASSERT(m_CurrentRequest != nullptr);
- if (m_CurrentRequestBodyRemaining > 0)
- {
- size_t BytesToConsume = std::min(m_CurrentRequestBodyRemaining, static_cast<size_t>(a_Size));
- m_HTTPServer.RequestBody(*this, *m_CurrentRequest, a_Data, BytesToConsume);
- m_CurrentRequestBodyRemaining -= BytesToConsume;
- }
- if (m_CurrentRequestBodyRemaining == 0)
- {
- m_State = wcsRecvIdle;
- m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
- if (!m_CurrentRequest->DoesAllowKeepAlive())
- {
- m_State = wcsInvalid;
- m_Link->Close();
- m_Link.reset();
- return;
- }
- delete m_CurrentRequest;
- m_CurrentRequest = nullptr;
- }
- break;
- }
-
- default:
- {
- // TODO: Should we be receiving data in this state?
- break;
- }
- }
-}
-
-
-
-
-
-void cHTTPConnection::OnRemoteClosed(void)
-{
- if (m_CurrentRequest != nullptr)
- {
- m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
- }
- m_Link.reset();
-}
-
-
-
-
-
-
-void cHTTPConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
-{
- OnRemoteClosed();
-}
-
-
-
-
-void cHTTPConnection::SendData(const void * a_Data, size_t a_Size)
-{
- m_Link->Send(a_Data, a_Size);
-}
-
-
-
-
diff --git a/src/HTTPServer/HTTPMessage.cpp b/src/HTTPServer/HTTPMessage.cpp
deleted file mode 100644
index 360145a9a..000000000
--- a/src/HTTPServer/HTTPMessage.cpp
+++ /dev/null
@@ -1,290 +0,0 @@
-
-// 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)
-{
- AString Key = StrToLower(a_Key);
- cNameValueMap::iterator itr = m_Headers.find(Key);
- if (itr == m_Headers.end())
- {
- m_Headers[Key] = a_Value;
- }
- else
- {
- // The header-field key is specified multiple times, combine into comma-separated list (RFC 2616 @ 4.2)
- itr->second.append(", ");
- itr->second.append(a_Value);
- }
-
- // Special processing for well-known headers:
- if (Key == "content-type")
- {
- m_ContentType = m_Headers[Key];
- }
- else if (Key == "content-length")
- {
- if (!StringToInteger(m_Headers[Key], m_ContentLength))
- {
- m_ContentLength = 0;
- }
- }
-}
-
-
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-// cHTTPRequest:
-
-cHTTPRequest::cHTTPRequest(void) :
- super(mkRequest),
- m_EnvelopeParser(*this),
- m_IsValid(true),
- m_UserData(nullptr),
- m_HasAuth(false),
- m_AllowKeepAlive(false)
-{
-}
-
-
-
-
-
-size_t cHTTPRequest::ParseHeaders(const char * a_Data, size_t a_Size)
-{
- if (!m_IsValid)
- {
- return AString::npos;
- }
-
- if (m_Method.empty())
- {
- // The first line hasn't been processed yet
- size_t res = ParseRequestLine(a_Data, a_Size);
- ASSERT((res == AString::npos) || (res <= a_Size));
- if ((res == AString::npos) || (res == a_Size))
- {
- return res;
- }
- size_t res2 = m_EnvelopeParser.Parse(a_Data + res, a_Size - res);
- ASSERT((res2 == AString::npos) || (res2 <= a_Size - res));
- if (res2 == AString::npos)
- {
- m_IsValid = false;
- return res2;
- }
- return res2 + res;
- }
-
- if (m_EnvelopeParser.IsInHeaders())
- {
- size_t res = m_EnvelopeParser.Parse(a_Data, a_Size);
- ASSERT((res == AString::npos) || (res <= a_Size));
- if (res == AString::npos)
- {
- m_IsValid = false;
- }
- return res;
- }
- return 0;
-}
-
-
-
-
-
-AString cHTTPRequest::GetBareURL(void) const
-{
- size_t idxQM = m_URL.find('?');
- if (idxQM != AString::npos)
- {
- return m_URL.substr(0, idxQM);
- }
- else
- {
- return m_URL;
- }
-}
-
-
-
-
-
-size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_Size)
-{
- auto inBufferSoFar = m_IncomingHeaderData.size();
- m_IncomingHeaderData.append(a_Data, a_Size);
- auto IdxEnd = m_IncomingHeaderData.size();
-
- // Ignore the initial CRLFs (HTTP spec's "should")
- size_t LineStart = 0;
- while (
- (LineStart < IdxEnd) &&
- (
- (m_IncomingHeaderData[LineStart] == '\r') ||
- (m_IncomingHeaderData[LineStart] == '\n')
- )
- )
- {
- LineStart++;
- }
- if (LineStart >= IdxEnd)
- {
- m_IsValid = false;
- return AString::npos;
- }
-
- int NumSpaces = 0;
- size_t MethodEnd = 0;
- size_t URLEnd = 0;
- for (size_t i = LineStart; i < IdxEnd; i++)
- {
- switch (m_IncomingHeaderData[i])
- {
- case ' ':
- {
- switch (NumSpaces)
- {
- case 0:
- {
- MethodEnd = i;
- break;
- }
- case 1:
- {
- URLEnd = i;
- break;
- }
- default:
- {
- // Too many spaces in the request
- m_IsValid = false;
- return AString::npos;
- }
- }
- NumSpaces += 1;
- break;
- }
- case '\n':
- {
- if ((i == 0) || (m_IncomingHeaderData[i - 1] != '\r') || (NumSpaces != 2) || (i < URLEnd + 7))
- {
- // LF too early, without a CR, without two preceeding spaces or too soon after the second space
- m_IsValid = false;
- return AString::npos;
- }
- // Check that there's HTTP / version at the end
- if (strncmp(m_IncomingHeaderData.c_str() + URLEnd + 1, "HTTP/1.", 7) != 0)
- {
- m_IsValid = false;
- return AString::npos;
- }
- m_Method = m_IncomingHeaderData.substr(LineStart, MethodEnd - LineStart);
- m_URL = m_IncomingHeaderData.substr(MethodEnd + 1, URLEnd - MethodEnd - 1);
- return i + 1 - inBufferSoFar;
- }
- } // switch (m_IncomingHeaderData[i])
- } // for i - m_IncomingHeaderData[]
-
- // CRLF hasn't been encountered yet, consider all data consumed
- return a_Size;
-}
-
-
-
-
-
-void cHTTPRequest::OnHeaderLine(const AString & a_Key, const AString & a_Value)
-{
- if (
- (NoCaseCompare(a_Key, "Authorization") == 0) &&
- (strncmp(a_Value.c_str(), "Basic ", 6) == 0)
- )
- {
- AString UserPass = Base64Decode(a_Value.substr(6));
- size_t idxCol = UserPass.find(':');
- if (idxCol != AString::npos)
- {
- m_AuthUsername = UserPass.substr(0, idxCol);
- m_AuthPassword = UserPass.substr(idxCol + 1);
- m_HasAuth = true;
- }
- }
- if ((a_Key == "Connection") && (NoCaseCompare(a_Value, "keep-alive") == 0))
- {
- m_AllowKeepAlive = true;
- }
- AddHeader(a_Key, a_Value);
-}
-
-
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-// cHTTPResponse:
-
-cHTTPResponse::cHTTPResponse(void) :
- super(mkResponse)
-{
-}
-
-
-
-
-
-void cHTTPResponse::AppendToData(AString & a_DataStream) const
-{
- a_DataStream.append("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: ");
- a_DataStream.append(m_ContentType);
- a_DataStream.append("\r\n");
- for (cNameValueMap::const_iterator itr = m_Headers.begin(), end = m_Headers.end(); itr != end; ++itr)
- {
- if ((itr->first == "Content-Type") || (itr->first == "Content-Length"))
- {
- continue;
- }
- a_DataStream.append(itr->first);
- a_DataStream.append(": ");
- a_DataStream.append(itr->second);
- a_DataStream.append("\r\n");
- } // for itr - m_Headers[]
- a_DataStream.append("\r\n");
-}
-
-
-
-
diff --git a/src/Root.h b/src/Root.h
index 24c8216d9..a828dc3fa 100644
--- a/src/Root.h
+++ b/src/Root.h
@@ -3,7 +3,7 @@
#include "Protocol/Authenticator.h"
#include "Protocol/MojangAPI.h"
-#include "HTTPServer/HTTPServer.h"
+#include "HTTP/HTTPServer.h"
#include "Defines.h"
#include "RankManager.h"
#include <thread>
diff --git a/src/WebAdmin.cpp b/src/WebAdmin.cpp
index 29e1bbccf..08e90ea13 100644
--- a/src/WebAdmin.cpp
+++ b/src/WebAdmin.cpp
@@ -12,8 +12,8 @@
#include "Server.h"
#include "Root.h"
-#include "HTTPServer/HTTPMessage.h"
-#include "HTTPServer/HTTPConnection.h"
+#include "HTTP/HTTPServerConnection.h"
+#include "HTTP/HTTPFormParser.h"
@@ -50,6 +50,40 @@ public:
////////////////////////////////////////////////////////////////////////////////
+// cWebadminRequestData
+
+/** The form parser callbacks for requests in the "/webadmin" and "/~webadmin" paths */
+class cWebadminRequestData :
+ public cHTTPFormParser::cCallbacks,
+ public cHTTPIncomingRequest::cUserData
+{
+public:
+ cHTTPFormParser m_Form;
+
+
+ cWebadminRequestData(const cHTTPIncomingRequest & a_Request):
+ m_Form(a_Request, *this)
+ {
+ }
+
+ // cHTTPFormParser::cCallbacks overrides. Files are ignored:
+ virtual void OnFileStart(cHTTPFormParser &, const AString & a_FileName) override
+ {
+ UNUSED(a_FileName);
+ }
+ virtual void OnFileData(cHTTPFormParser &, const char * a_Data, size_t a_Size) override
+ {
+ UNUSED(a_Data);
+ UNUSED(a_Size);
+ }
+ virtual void OnFileEnd(cHTTPFormParser &) override {}
+} ;
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
// cWebAdmin:
cWebAdmin::cWebAdmin(void) :
@@ -212,7 +246,7 @@ bool cWebAdmin::LoadLoginTemplate(void)
-void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
+void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
if (!a_Request.HasAuth())
{
@@ -229,12 +263,12 @@ void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPReque
}
// Check if the contents should be wrapped in the template:
- AString BareURL = a_Request.GetBareURL();
+ auto BareURL = a_Request.GetURLPath();
ASSERT(BareURL.length() > 0);
bool ShouldWrapInTemplate = ((BareURL.length() > 1) && (BareURL[1] != '~'));
// Retrieve the request data:
- cWebadminRequestData * Data = reinterpret_cast<cWebadminRequestData *>(a_Request.GetUserData());
+ auto Data = std::static_pointer_cast<cWebadminRequestData>(a_Request.GetUserData());
if (Data == nullptr)
{
a_Connection.SendStatusAndReason(500, "Bad UserData");
@@ -280,7 +314,7 @@ void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPReque
{
if (m_TemplateScript.Call("ShowPage", this, &TemplateRequest, cLuaState::Return, Template))
{
- cHTTPResponse Resp;
+ cHTTPOutgoingResponse Resp;
Resp.SetContentType("text/html");
a_Connection.Send(Resp);
a_Connection.Send(Template.c_str(), Template.length());
@@ -339,21 +373,22 @@ void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPReque
Printf(NumChunks, "%d", cRoot::Get()->GetTotalChunkCount());
ReplaceString(Template, "{NUMCHUNKS}", NumChunks);
- cHTTPResponse Resp;
+ cHTTPOutgoingResponse Resp;
Resp.SetContentType("text/html");
a_Connection.Send(Resp);
a_Connection.Send(Template.c_str(), Template.length());
+ a_Connection.FinishResponse();
}
-void cWebAdmin::HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
+void cWebAdmin::HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
UNUSED(a_Request);
- cHTTPResponse Resp;
+ cHTTPOutgoingResponse Resp;
Resp.SetContentType("text/html");
a_Connection.Send(Resp);
a_Connection.Send(m_LoginTemplate);
@@ -364,7 +399,7 @@ void cWebAdmin::HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest &
-void cWebAdmin::HandleFileRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
+void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
AString FileURL = a_Request.GetURL();
std::replace(FileURL.begin(), FileURL.end(), '\\', '/');
@@ -406,7 +441,7 @@ void cWebAdmin::HandleFileRequest(cHTTPConnection & a_Connection, cHTTPRequest &
}
// Send the response:
- cHTTPResponse Resp;
+ cHTTPOutgoingResponse Resp;
Resp.SetContentType(ContentType);
a_Connection.Send(Resp);
a_Connection.Send(Content);
@@ -621,7 +656,7 @@ AString cWebAdmin::GetBaseURL(const AStringVector & a_URLSplit)
-void cWebAdmin::OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
+void cWebAdmin::OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
UNUSED(a_Connection);
const AString & URL = a_Request.GetURL();
@@ -630,7 +665,7 @@ void cWebAdmin::OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_
(strncmp(URL.c_str(), "/~webadmin", 10) == 0)
)
{
- a_Request.SetUserData(new cWebadminRequestData(a_Request));
+ a_Request.SetUserData(std::make_shared<cWebadminRequestData>(a_Request));
return;
}
if (URL == "/")
@@ -645,22 +680,22 @@ void cWebAdmin::OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_
-void cWebAdmin::OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size)
+void cWebAdmin::OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const char * a_Data, size_t a_Size)
{
UNUSED(a_Connection);
- cRequestData * Data = reinterpret_cast<cRequestData *>(a_Request.GetUserData());
+ auto Data = std::static_pointer_cast<cWebadminRequestData>(a_Request.GetUserData());
if (Data == nullptr)
{
return;
}
- Data->OnBody(a_Data, a_Size);
+ Data->m_Form.Parse(a_Data, a_Size);
}
-void cWebAdmin::OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
+void cWebAdmin::OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
const AString & URL = a_Request.GetURL();
if (
@@ -679,24 +714,9 @@ void cWebAdmin::OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest &
{
HandleFileRequest(a_Connection, a_Request);
}
-
- // Delete any request data assigned to the request:
- cRequestData * Data = reinterpret_cast<cRequestData *>(a_Request.GetUserData());
- delete Data;
- Data = nullptr;
}
-////////////////////////////////////////////////////////////////////////////////
-// cWebAdmin::cWebadminRequestData
-
-void cWebAdmin::cWebadminRequestData::OnBody(const char * a_Data, size_t a_Size)
-{
- m_Form.Parse(a_Data, a_Size);
-}
-
-
-
diff --git a/src/WebAdmin.h b/src/WebAdmin.h
index a3ebac43c..5e48f597c 100644
--- a/src/WebAdmin.h
+++ b/src/WebAdmin.h
@@ -7,8 +7,8 @@
#include "Bindings/LuaState.h"
#include "IniFile.h"
-#include "HTTPServer/HTTPServer.h"
-#include "HTTPServer/HTTPFormParser.h"
+#include "HTTP/HTTPServer.h"
+#include "HTTP/HTTPMessage.h"
@@ -171,46 +171,6 @@ public:
static AString GetContentTypeFromFileExt(const AString & a_FileExtension);
protected:
- /** Common base class for request body data handlers */
- class cRequestData
- {
- public:
- virtual ~cRequestData() {} // Force a virtual destructor in all descendants
-
- /** Called when a new chunk of body data is received */
- virtual void OnBody(const char * a_Data, size_t a_Size) = 0;
- } ;
-
- /** The body handler for requests in the "/webadmin" and "/~webadmin" paths */
- class cWebadminRequestData :
- public cRequestData,
- public cHTTPFormParser::cCallbacks
- {
- public:
- cHTTPFormParser m_Form;
-
-
- cWebadminRequestData(cHTTPRequest & a_Request) :
- m_Form(a_Request, *this)
- {
- }
-
- // cRequestData overrides:
- virtual void OnBody(const char * a_Data, size_t a_Size) override;
-
- // cHTTPFormParser::cCallbacks overrides. Files are ignored:
- virtual void OnFileStart(cHTTPFormParser &, const AString & a_FileName) override
- {
- UNUSED(a_FileName);
- }
- virtual void OnFileData(cHTTPFormParser &, const char * a_Data, size_t a_Size) override
- {
- UNUSED(a_Data);
- UNUSED(a_Size);
- }
- virtual void OnFileEnd(cHTTPFormParser &) override {}
- } ;
-
/** Set to true if Init() succeeds and the webadmin isn't to be disabled */
bool m_IsInitialized;
@@ -236,18 +196,18 @@ protected:
cHTTPServer m_HTTPServer;
/** Handles requests coming to the "/webadmin" or "/~webadmin" URLs */
- void HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
+ void HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request);
/** Handles requests for the root page */
- void HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
+ void HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request);
/** Handles requests for a file */
- void HandleFileRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
+ void HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request);
// cHTTPServer::cCallbacks overrides:
- virtual void OnRequestBegun (cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override;
- virtual void OnRequestBody (cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) override;
- virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override;
+ virtual void OnRequestBegun (cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) override;
+ virtual void OnRequestBody (cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const char * a_Data, size_t a_Size) override;
+ virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) override;
} ; // tolua_export
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index bdab4bc58..63eb8ae3a 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -9,5 +9,6 @@ endif()
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_subdirectory(ChunkData)
+add_subdirectory(HTTP)
add_subdirectory(Network)
add_subdirectory(LoadablePieces)
diff --git a/tests/HTTP/CMakeLists.txt b/tests/HTTP/CMakeLists.txt
new file mode 100644
index 000000000..233e5c0cb
--- /dev/null
+++ b/tests/HTTP/CMakeLists.txt
@@ -0,0 +1,60 @@
+cmake_minimum_required (VERSION 2.6)
+
+enable_testing()
+
+include_directories(${CMAKE_SOURCE_DIR}/src/)
+include_directories(${CMAKE_SOURCE_DIR}/lib/libevent/include)
+
+add_definitions(-DTEST_GLOBALS=1)
+
+# Create a single HTTP library that contains all the HTTP code:
+set (HTTP_SRCS
+ ${CMAKE_SOURCE_DIR}/src/HTTP/EnvelopeParser.cpp
+ ${CMAKE_SOURCE_DIR}/src/HTTP/HTTPMessage.cpp
+ ${CMAKE_SOURCE_DIR}/src/HTTP/HTTPMessageParser.cpp
+ ${CMAKE_SOURCE_DIR}/src/HTTP/TransferEncodingParser.cpp
+ ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp
+)
+
+set (HTTP_HDRS
+ ${CMAKE_SOURCE_DIR}/src/HTTP/EnvelopeParser.h
+ ${CMAKE_SOURCE_DIR}/src/HTTP/HTTPMessage.h
+ ${CMAKE_SOURCE_DIR}/src/HTTP/HTTPMessageParser.h
+ ${CMAKE_SOURCE_DIR}/src/HTTP/TransferEncodingParser.h
+ ${CMAKE_SOURCE_DIR}/src/StringUtils.h
+)
+
+add_library(HTTP
+ ${HTTP_SRCS}
+ ${HTTP_HDRS}
+)
+
+if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
+ add_flags_cxx("-Wno-error=conversion -Wno-error=old-style-cast")
+endif()
+
+
+
+
+
+# Define individual tests:
+
+# HTTPMessageParser_file: Feed file contents into a cHTTPResponseParser and print the callbacks as they're called:
+add_executable(HTTPMessageParser_file-exe HTTPMessageParser_file.cpp)
+target_link_libraries(HTTPMessageParser_file-exe HTTP)
+
+# Test parsing the response file in 2-byte chunks (should go from response line parsing through headers parsing to body parsing, each within a different step):
+add_test(NAME HTTPMessageParser_file-test1-2 COMMAND HTTPMessageParser_file-exe HTTPResponse1.data 2)
+
+# Test parsing the response file in 128-byte chunks (should parse response line and part of headers in one step, the rest in another step):
+add_test(NAME HTTPMessageParser_file-test1-128 COMMAND HTTPMessageParser_file-exe HTTPResponse1.data 128)
+
+# Test parsing a chunked-encoding response:
+add_test(NAME HTTPMessageParser_file-test2 COMMAND HTTPMessageParser_file-exe HTTPResponse2.data)
+
+# Test parsing the request file in 2-byte chunks (should go from request line parsing through headers parsing to body parsing, each within a different step):
+add_test(NAME HTTPMessageParser_file-test3-2 COMMAND HTTPMessageParser_file-exe HTTPRequest1.data 2)
+
+# Test parsing the request file in 512-byte chunks (should process everything in a single call):
+add_test(NAME HTTPMessageParser_file-test4-512 COMMAND HTTPMessageParser_file-exe HTTPRequest1.data 512)
+
diff --git a/tests/HTTP/HTTPMessageParser_file.cpp b/tests/HTTP/HTTPMessageParser_file.cpp
new file mode 100644
index 000000000..cd6dfa605
--- /dev/null
+++ b/tests/HTTP/HTTPMessageParser_file.cpp
@@ -0,0 +1,153 @@
+
+// HTTPMessageParser_file.cpp
+
+// Implements a test that feeds file contents into a cHTTPMessageParser instance and prints all callbacks
+
+#include "Globals.h"
+#include "HTTP/HTTPMessageParser.h"
+
+
+
+
+
+/** Maximum size of the input buffer, through which the file is read */
+static const size_t MAX_BUF = 4096;
+
+
+
+
+
+class cCallbacks:
+ public cHTTPMessageParser::cCallbacks
+{
+ typedef cHTTPMessageParser::cCallbacks Super;
+public:
+ cCallbacks(void)
+ {
+ printf("cCallbacks created\n");
+ }
+
+ // cHTTPResponseParser::cCallbacks overrides:
+ virtual void OnError(const AString & a_ErrorDescription) override
+ {
+ printf("Error: \"%s\"\n", a_ErrorDescription.c_str());
+ }
+
+ /** Called when the first line (request / status) is fully parsed. */
+ virtual void OnFirstLine(const AString & a_FirstLine) override
+ {
+ printf("First line: \"%s\"\n", a_FirstLine.c_str());
+ }
+
+ /** Called when a single header line is parsed. */
+ virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override
+ {
+ printf("Header line: \"%s\": \"%s\"\n", a_Key.c_str(), a_Value.c_str());
+ }
+
+ /** Called when all the headers have been parsed. */
+ virtual void OnHeadersFinished(void) override
+ {
+ printf("Headers finished\n");
+ }
+
+ /** Called for each chunk of the incoming body data. */
+ virtual void OnBodyData(const void * a_Data, size_t a_Size) override
+ {
+ AString hexDump;
+ CreateHexDump(hexDump, a_Data, a_Size, 16);
+ printf("Body data: %u bytes\n%s", static_cast<unsigned>(a_Size), hexDump.c_str());
+ }
+
+ virtual void OnBodyFinished(void) override
+ {
+ printf("Body finished\n");
+ }
+};
+
+
+
+
+
+int main(int argc, char * argv[])
+{
+ printf("HTTPMessageParser_file beginning\n");
+
+ // Open the input file:
+ if (argc <= 1)
+ {
+ printf("Usage: %s <filename> [<buffersize>]\n", argv[0]);
+ return 1;
+ }
+ FILE * f;
+ if (strcmp(argv[1], "-") == 0)
+ {
+ f = stdin;
+ }
+ else
+ {
+ f = fopen(argv[1], "rb");
+ if (f == nullptr)
+ {
+ printf("Cannot open file \"%s\". Aborting.\n", argv[1]);
+ return 2;
+ }
+ }
+
+ // If a third param is present, use it as the buffer size
+ size_t bufSize = MAX_BUF;
+ if (argc >= 3)
+ {
+ if (!StringToInteger(argv[2], bufSize) || (bufSize == 0))
+ {
+ bufSize = MAX_BUF;
+ printf("\"%s\" is not a valid buffer size, using the default of %u instead.\n", argv[2], static_cast<unsigned>(bufSize));
+ }
+ if (bufSize > MAX_BUF)
+ {
+ bufSize = MAX_BUF;
+ printf("\"%s\" is too large, maximum buffer size is %u. Using the size %u instead.\n", argv[2], static_cast<unsigned>(bufSize), static_cast<unsigned>(bufSize));
+ }
+ }
+
+ // Feed the file contents into the parser:
+ cCallbacks callbacks;
+ cHTTPMessageParser parser(callbacks);
+ while (true)
+ {
+ char buf[MAX_BUF];
+ auto numBytes = fread(buf, 1, bufSize, f);
+ if (numBytes == 0)
+ {
+ printf("Read 0 bytes from file (EOF?), terminating\n");
+ break;
+ }
+ auto numConsumed = parser.Parse(buf, numBytes);
+ if (numConsumed == AString::npos)
+ {
+ printf("Parser indicates there was an error, terminating parsing.\n");
+ break;
+ }
+ ASSERT(numConsumed <= numBytes);
+ if (numConsumed < numBytes)
+ {
+ printf("Parser indicates stream end, but there's more data (at least %u bytes) in the file.\n", static_cast<unsigned>(numBytes - numConsumed));
+ }
+ }
+ if (!parser.IsFinished())
+ {
+ printf("Parser indicates an incomplete stream.\n");
+ }
+
+ // Close the input file:
+ if (f != stdin)
+ {
+ fclose(f);
+ }
+
+ return 0;
+}
+
+
+
+
diff --git a/tests/HTTP/HTTPRequest1.data b/tests/HTTP/HTTPRequest1.data
new file mode 100644
index 000000000..cac43c06c
--- /dev/null
+++ b/tests/HTTP/HTTPRequest1.data
@@ -0,0 +1,5 @@
+GET /some/url HTTP/1.1
+Note: This is a test of a regular request
+Content-Length: 3
+
+bla \ No newline at end of file
diff --git a/tests/HTTP/HTTPRequest2.data b/tests/HTTP/HTTPRequest2.data
new file mode 100644
index 000000000..f7dbede4f
--- /dev/null
+++ b/tests/HTTP/HTTPRequest2.data
@@ -0,0 +1,3 @@
+GET /some/url HTTP/1.1
+Note: This is a test of a regular body-less request
+
diff --git a/tests/HTTP/HTTPRequestParser_file.cpp b/tests/HTTP/HTTPRequestParser_file.cpp
new file mode 100644
index 000000000..98f11e8b1
--- /dev/null
+++ b/tests/HTTP/HTTPRequestParser_file.cpp
@@ -0,0 +1,153 @@
+
+// HTTPResponseParser_file.cpp
+
+// Implements a test that feeds file contents into a cHTTPResponseParser instance and prints all callbacks
+
+#include "Globals.h"
+#include "HTTP/HTTPRequestParser.h"
+
+
+
+
+
+/** Maximum size of the input buffer, through which the file is read */
+static const size_t MAX_BUF = 4096;
+
+
+
+
+
+class cCallbacks:
+ public cHTTPRequestParser::cCallbacks
+{
+ typedef cHTTPResponseParser::cCallbacks Super;
+public:
+ cCallbacks(void)
+ {
+ printf("cCallbacks created\n");
+ }
+
+ // cHTTPResponseParser::cCallbacks overrides:
+ virtual void OnError(const AString & a_ErrorDescription) override
+ {
+ printf("Error: \"%s\"\n", a_ErrorDescription.c_str());
+ }
+
+ /** Called when the status line is fully parsed. */
+ virtual void OnStatusLine(const AString & a_StatusLine) override
+ {
+ printf("Status line: \"%s\"\n", a_StatusLine.c_str());
+ }
+
+ /** Called when a single header line is parsed. */
+ virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override
+ {
+ printf("Header line: \"%s\": \"%s\"\n", a_Key.c_str(), a_Value.c_str());
+ }
+
+ /** Called when all the headers have been parsed. */
+ virtual void OnHeadersFinished(void) override
+ {
+ printf("Headers finished\n");
+ }
+
+ /** Called for each chunk of the incoming body data. */
+ virtual void OnBodyData(const void * a_Data, size_t a_Size) override
+ {
+ AString hexDump;
+ CreateHexDump(hexDump, a_Data, a_Size, 16);
+ printf("Body data: %u bytes\n%s", static_cast<unsigned>(a_Size), hexDump.c_str());
+ }
+
+ virtual void OnBodyFinished(void) override
+ {
+ printf("Body finished\n");
+ }
+};
+
+
+
+
+
+int main(int argc, char * argv[])
+{
+ printf("HTTPResponseParser_file beginning\n");
+
+ // Open the input file:
+ if (argc <= 1)
+ {
+ printf("Usage: %s <filename> [<buffersize>]\n", argv[0]);
+ return 1;
+ }
+ FILE * f;
+ if (strcmp(argv[1], "-") == 0)
+ {
+ f = stdin;
+ }
+ else
+ {
+ f = fopen(argv[1], "rb");
+ if (f == nullptr)
+ {
+ printf("Cannot open file \"%s\". Aborting.\n", argv[1]);
+ return 2;
+ }
+ }
+
+ // If a third param is present, use it as the buffer size
+ size_t bufSize = MAX_BUF;
+ if (argc >= 3)
+ {
+ if (!StringToInteger(argv[2], bufSize) || (bufSize == 0))
+ {
+ bufSize = MAX_BUF;
+ printf("\"%s\" is not a valid buffer size, using the default of %u instead.\n", argv[2], static_cast<unsigned>(bufSize));
+ }
+ if (bufSize > MAX_BUF)
+ {
+ bufSize = MAX_BUF;
+ printf("\"%s\" is too large, maximum buffer size is %u. Using the size %u instead.\n", argv[2], static_cast<unsigned>(bufSize), static_cast<unsigned>(bufSize));
+ }
+ }
+
+ // Feed the file contents into the parser:
+ cCallbacks callbacks;
+ cHTTPResponseParser parser(callbacks);
+ while (!feof(f))
+ {
+ char buf[MAX_BUF];
+ auto numBytes = fread(buf, 1, bufSize, f);
+ if (numBytes == 0)
+ {
+ printf("Read 0 bytes from file (EOF?), terminating\n");
+ break;
+ }
+ auto numConsumed = parser.Parse(buf, numBytes);
+ if (numConsumed == AString::npos)
+ {
+ printf("Parser indicates there was an error, terminating parsing.\n");
+ break;
+ }
+ ASSERT(numConsumed <= numBytes);
+ if (numConsumed < numBytes)
+ {
+ printf("Parser indicates stream end, but there's more data (at least %u bytes) in the file.\n", static_cast<unsigned>(numBytes - numConsumed));
+ }
+ }
+ if (!parser.IsFinished())
+ {
+ printf("Parser indicates an incomplete stream.\n");
+ }
+
+ // Close the input file:
+ if (f != stdin)
+ {
+ fclose(f);
+ }
+
+ return 0;
+}
+
+
+
+
diff --git a/tests/HTTP/HTTPResponse1.data b/tests/HTTP/HTTPResponse1.data
new file mode 100644
index 000000000..97e0b23c3
--- /dev/null
+++ b/tests/HTTP/HTTPResponse1.data
@@ -0,0 +1,10 @@
+HTTP/1.0 200 OK
+Note: This is a test of a regular response with Content-Length set
+ (identity transfer encoding)
+Note2: The above header also tests multi-line header lines
+Note3: The data is 2 bytes longer than the actual request, parser should indicate 2 extra bytes at the end
+Header1: Value1
+Header2: Value2
+Content-Length: 3
+
+bla
diff --git a/tests/HTTP/HTTPResponse2.data b/tests/HTTP/HTTPResponse2.data
new file mode 100644
index 000000000..d708c4758
--- /dev/null
+++ b/tests/HTTP/HTTPResponse2.data
@@ -0,0 +1,15 @@
+HTTP/1.0 200 OK
+Note: This is a Chunked transfer encoding test
+Header2: Value2
+Transfer-Encoding: chunked
+
+4
+Wiki
+5
+pedia
+e
+ in
+
+chunks.
+0
+
diff --git a/tests/HTTP/HTTPResponseParser_file.cpp b/tests/HTTP/HTTPResponseParser_file.cpp
new file mode 100644
index 000000000..7e8d127b7
--- /dev/null
+++ b/tests/HTTP/HTTPResponseParser_file.cpp
@@ -0,0 +1,153 @@
+
+// HTTPResponseParser_file.cpp
+
+// Implements a test that feeds file contents into a cHTTPResponseParser instance and prints all callbacks
+
+#include "Globals.h"
+#include "HTTP/HTTPResponseParser.h"
+
+
+
+
+
+/** Maximum size of the input buffer, through which the file is read */
+static const size_t MAX_BUF = 4096;
+
+
+
+
+
+class cCallbacks:
+ public cHTTPResponseParser::cCallbacks
+{
+ typedef cHTTPResponseParser::cCallbacks Super;
+public:
+ cCallbacks(void)
+ {
+ printf("cCallbacks created\n");
+ }
+
+ // cHTTPResponseParser::cCallbacks overrides:
+ virtual void OnError(const AString & a_ErrorDescription) override
+ {
+ printf("Error: \"%s\"\n", a_ErrorDescription.c_str());
+ }
+
+ /** Called when the status line is fully parsed. */
+ virtual void OnStatusLine(const AString & a_StatusLine) override
+ {
+ printf("Status line: \"%s\"\n", a_StatusLine.c_str());
+ }
+
+ /** Called when a single header line is parsed. */
+ virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override
+ {
+ printf("Header line: \"%s\": \"%s\"\n", a_Key.c_str(), a_Value.c_str());
+ }
+
+ /** Called when all the headers have been parsed. */
+ virtual void OnHeadersFinished(void) override
+ {
+ printf("Headers finished\n");
+ }
+
+ /** Called for each chunk of the incoming body data. */
+ virtual void OnBodyData(const void * a_Data, size_t a_Size) override
+ {
+ AString hexDump;
+ CreateHexDump(hexDump, a_Data, a_Size, 16);
+ printf("Body data: %u bytes\n%s", static_cast<unsigned>(a_Size), hexDump.c_str());
+ }
+
+ virtual void OnBodyFinished(void) override
+ {
+ printf("Body finished\n");
+ }
+};
+
+
+
+
+
+int main(int argc, char * argv[])
+{
+ printf("HTTPResponseParser_file beginning\n");
+
+ // Open the input file:
+ if (argc <= 1)
+ {
+ printf("Usage: %s <filename> [<buffersize>]\n", argv[0]);
+ return 1;
+ }
+ FILE * f;
+ if (strcmp(argv[1], "-") == 0)
+ {
+ f = stdin;
+ }
+ else
+ {
+ f = fopen(argv[1], "rb");
+ if (f == nullptr)
+ {
+ printf("Cannot open file \"%s\". Aborting.\n", argv[1]);
+ return 2;
+ }
+ }
+
+ // If a third param is present, use it as the buffer size
+ size_t bufSize = MAX_BUF;
+ if (argc >= 3)
+ {
+ if (!StringToInteger(argv[2], bufSize) || (bufSize == 0))
+ {
+ bufSize = MAX_BUF;
+ printf("\"%s\" is not a valid buffer size, using the default of %u instead.\n", argv[2], static_cast<unsigned>(bufSize));
+ }
+ if (bufSize > MAX_BUF)
+ {
+ bufSize = MAX_BUF;
+ printf("\"%s\" is too large, maximum buffer size is %u. Using the size %u instead.\n", argv[2], static_cast<unsigned>(bufSize), static_cast<unsigned>(bufSize));
+ }
+ }
+
+ // Feed the file contents into the parser:
+ cCallbacks callbacks;
+ cHTTPResponseParser parser(callbacks);
+ while (!feof(f))
+ {
+ char buf[MAX_BUF];
+ auto numBytes = fread(buf, 1, bufSize, f);
+ if (numBytes == 0)
+ {
+ printf("Read 0 bytes from file (EOF?), terminating\n");
+ break;
+ }
+ auto numConsumed = parser.Parse(buf, numBytes);
+ if (numConsumed == AString::npos)
+ {
+ printf("Parser indicates there was an error, terminating parsing.\n");
+ break;
+ }
+ ASSERT(numConsumed <= numBytes);
+ if (numConsumed < numBytes)
+ {
+ printf("Parser indicates stream end, but there's more data (at least %u bytes) in the file.\n", static_cast<unsigned>(numBytes - numConsumed));
+ }
+ }
+ if (!parser.IsFinished())
+ {
+ printf("Parser indicates an incomplete stream.\n");
+ }
+
+ // Close the input file:
+ if (f != stdin)
+ {
+ fclose(f);
+ }
+
+ return 0;
+}
+
+
+
+