summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattes D <github@xoft.cz>2016-01-01 16:42:22 +0100
committerMattes D <github@xoft.cz>2016-03-01 16:19:56 +0100
commitfce68dc8f39dfceab3e80513390bae8fa936a6b9 (patch)
treeb79948729243bd71d77d6eb6062145e82d3b9941
parentMoved cHTTPRequest to a separate file, renamed to cHTTPRequestParser. (diff)
downloadcuberite-fce68dc8f39dfceab3e80513390bae8fa936a6b9.tar
cuberite-fce68dc8f39dfceab3e80513390bae8fa936a6b9.tar.gz
cuberite-fce68dc8f39dfceab3e80513390bae8fa936a6b9.tar.bz2
cuberite-fce68dc8f39dfceab3e80513390bae8fa936a6b9.tar.lz
cuberite-fce68dc8f39dfceab3e80513390bae8fa936a6b9.tar.xz
cuberite-fce68dc8f39dfceab3e80513390bae8fa936a6b9.tar.zst
cuberite-fce68dc8f39dfceab3e80513390bae8fa936a6b9.zip
-rw-r--r--src/HTTPServer/CMakeLists.txt4
-rw-r--r--src/HTTPServer/HTTPMessage.h1
-rw-r--r--src/HTTPServer/HTTPResponseParser.cpp177
-rw-r--r--src/HTTPServer/HTTPResponseParser.h118
-rw-r--r--src/HTTPServer/TransferEncodingParser.cpp132
-rw-r--r--src/HTTPServer/TransferEncodingParser.h76
6 files changed, 508 insertions, 0 deletions
diff --git a/src/HTTPServer/CMakeLists.txt b/src/HTTPServer/CMakeLists.txt
index 719c3949e..4597f6eda 100644
--- a/src/HTTPServer/CMakeLists.txt
+++ b/src/HTTPServer/CMakeLists.txt
@@ -9,11 +9,13 @@ SET (SRCS
HTTPFormParser.cpp
HTTPMessage.cpp
HTTPRequestParser.cpp
+ HTTPResponseParser.cpp
HTTPServer.cpp
HTTPServerConnection.cpp
MultipartParser.cpp
NameValueParser.cpp
SslHTTPServerConnection.cpp
+ TransferEncodingParser.cpp
UrlParser.cpp
)
@@ -22,11 +24,13 @@ SET (HDRS
HTTPFormParser.h
HTTPMessage.h
HTTPRequestParser.h
+ HTTPResponseParser.h
HTTPServer.h
HTTPServerConnection.h
MultipartParser.h
NameValueParser.h
SslHTTPServerConnection.h
+ TransferEncodingParser.h
UrlParser.h
)
diff --git a/src/HTTPServer/HTTPMessage.h b/src/HTTPServer/HTTPMessage.h
index 4af5e471b..50b1f9e19 100644
--- a/src/HTTPServer/HTTPMessage.h
+++ b/src/HTTPServer/HTTPMessage.h
@@ -49,6 +49,7 @@ protected:
eKind m_Kind;
+ /** Map of headers, with their keys lowercased. */
AStringMap m_Headers;
/** Type of the content; parsed by AddHeader(), set directly by SetContentLength() */
diff --git a/src/HTTPServer/HTTPResponseParser.cpp b/src/HTTPServer/HTTPResponseParser.cpp
new file mode 100644
index 000000000..b3ce9dab6
--- /dev/null
+++ b/src/HTTPServer/HTTPResponseParser.cpp
@@ -0,0 +1,177 @@
+
+// HTTPResponseParser.cpp
+
+// Implements the cHTTPResponseParser class representing the parser for incoming HTTP responses
+
+#include "Globals.h"
+#include "HTTPResponseParser.h"
+
+
+
+
+
+cHTTPResponseParser::cHTTPResponseParser(cHTTPResponseParser::cCallbacks & a_Callbacks):
+ Super(mkResponse),
+ m_Callbacks(a_Callbacks),
+ m_IsInHeaders(true),
+ m_IsFinished(false),
+ m_EnvelopeParser(*this)
+{
+}
+
+
+
+
+
+size_t cHTTPResponseParser::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 a_Size;
+ }
+
+ // If still waiting for the status line, add to buffer and try parsing it:
+ if (m_StatusLine.empty())
+ {
+ m_Buffer.append(a_Data, a_Size);
+ if (!ParseStatusLine())
+ {
+ // All data used, but not a complete status line yet.
+ return 0;
+ }
+ if (m_HasHadError)
+ {
+ return AString::npos;
+ }
+ // Status line completed, feed the rest of the buffer into the envelope parser:
+ auto bytesConsumed = m_EnvelopeParser.Parse(m_Buffer.data(), m_Buffer.size());
+ if (bytesConsumed == AString::npos)
+ {
+ m_HasHadError = true;
+ m_Callbacks.OnError("Failed to parse the envelope");
+ return AString::npos;
+ }
+ m_Buffer.erase(0, bytesConsumed);
+ if (!m_Buffer.empty())
+ {
+ // Headers finished and there's still data left in the buffer, process it as message body:
+ m_IsInHeaders = false;
+ return ParseBody(m_Buffer.data(), m_Buffer.size());
+ }
+ return 0;
+ } // if (m_StatusLine.empty())
+
+ // If still parsing headers, send them to the envelope parser:
+ if (m_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 (bytesConsumed < a_Size)
+ {
+ // Headers finished and there's still data left in the buffer, process it as message body:
+ HeadersFinished();
+ return ParseBody(a_Data + bytesConsumed, a_Size - bytesConsumed);
+ }
+ return 0;
+ }
+
+ // Already parsing the body
+ return ParseBody(a_Data, a_Size);
+}
+
+
+
+
+
+bool cHTTPResponseParser::ParseStatusLine(void)
+{
+ auto idxLineEnd = m_Buffer.find("\r\n");
+ if (idxLineEnd == AString::npos)
+ {
+ // Not a complete line yet
+ return false;
+ }
+ m_StatusLine = m_Buffer.substr(0, idxLineEnd);
+ m_Buffer.erase(0, idxLineEnd + 2);
+ m_Callbacks.OnStatusLine(m_StatusLine);
+ return true;
+}
+
+
+
+
+size_t cHTTPResponseParser::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
+ return AString::npos;
+ }
+
+ // Parse the body using the transfer encoding parser:
+ return m_TransferEncodingParser->Parse(a_Data, a_Size);
+}
+
+
+
+
+
+void cHTTPResponseParser::HeadersFinished(void)
+{
+ m_IsInHeaders = false;
+ m_Callbacks.OnHeadersFinished();
+
+ auto transferEncoding = m_Headers.find("transfer-encoding");
+ if (transferEncoding == m_Headers.end())
+ {
+ m_TransferEncodingParser = cTransferEncodingParser::Create(*this, "identity", m_ContentLength);
+ }
+}
+
+
+
+
+
+void cHTTPResponseParser::OnHeaderLine(const AString & a_Key, const AString & a_Value)
+{
+ AddHeader(a_Key, a_Value);
+ m_Callbacks.OnHeaderLine(a_Key, a_Value);
+}
+
+
+
+
+
+void cHTTPResponseParser::OnError(const AString & a_ErrorDescription)
+{
+ m_HasHadError = true;
+ m_Callbacks.OnError(a_ErrorDescription);
+}
+
+
+
+
+
+void cHTTPResponseParser::OnBodyData(const void * a_Data, size_t a_Size)
+{
+ m_Callbacks.OnBodyData(a_Data, a_Size);
+}
+
+
+
+
+
+void cHTTPResponseParser::OnBodyFinished(void)
+{
+ m_Callbacks.OnBodyFinished();
+}
+
+
+
+
diff --git a/src/HTTPServer/HTTPResponseParser.h b/src/HTTPServer/HTTPResponseParser.h
new file mode 100644
index 000000000..19652ccef
--- /dev/null
+++ b/src/HTTPServer/HTTPResponseParser.h
@@ -0,0 +1,118 @@
+
+// HTTPResponseParser.h
+
+// Declares the cHTTPResponseParser class representing the parser for incoming HTTP responses
+
+
+
+
+#pragma once
+
+#include "HTTPMessage.h"
+#include "TransferEncodingParser.h"
+
+
+
+
+
+class cHTTPResponseParser:
+ public cHTTPMessage,
+ protected cEnvelopeParser::cCallbacks,
+ protected cTransferEncodingParser::cCallbacks
+{
+ typedef cHTTPMessage Super;
+
+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 status line is fully parsed. */
+ virtual void OnStatusLine(const AString & a_StatusLine) = 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;
+ };
+
+ cHTTPResponseParser(cCallbacks & a_Callbacks);
+
+ /** 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 response.
+ Returns AString::npos on an 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; }
+
+
+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 parser is still parsing the status or headers. */
+ bool m_IsInHeaders;
+
+ /** True if the response has been fully parsed. */
+ bool m_IsFinished;
+
+ /** The complete status line of the response. Empty if not parsed yet. */
+ AString m_StatusLine;
+
+ /** 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;
+
+
+ /** Parses the status line out of the m_Buffer.
+ Removes the status line from m_Buffer, if appropriate.
+ Returns true if the entire status line has been parsed. */
+ bool ParseStatusLine(void);
+
+ /** Parses the message body.
+ Processes transfer encoding and calls the callbacks for body data.
+ Returns the number of bytes from the end of a_Data that is already not part of this response.
+ Returns AString::npos on 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/TransferEncodingParser.cpp b/src/HTTPServer/TransferEncodingParser.cpp
new file mode 100644
index 000000000..8b703fd42
--- /dev/null
+++ b/src/HTTPServer/TransferEncodingParser.cpp
@@ -0,0 +1,132 @@
+
+// TransferEncodingParser.cpp
+
+// Implements the cTransferEncodingParser class and its descendants representing the parser for the various transfer encodings (chunked etc.)
+
+#include "Globals.h"
+#include "TransferEncodingParser.h"
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cChunkedTEParser:
+
+class cChunkedTEParser:
+ public cTransferEncodingParser
+{
+ typedef cTransferEncodingParser Super;
+
+public:
+ cChunkedTEParser(cCallbacks & a_Callbacks):
+ Super(a_Callbacks),
+ m_IsFinished(false)
+ {
+ }
+
+
+protected:
+
+ /** True if the datastream has finished (zero-length chunk received). */
+ bool m_IsFinished;
+
+
+ // cTransferEncodingParser overrides:
+ virtual size_t Parse(const char * a_Data, size_t a_Size) override
+ {
+ // TODO
+ m_Callbacks.OnError("cChunkedTEParser not implemented yet");
+ return AString::npos;
+ }
+
+ virtual void Finish(void) override
+ {
+ if (!m_IsFinished)
+ {
+ m_Callbacks.OnError("ChunkedTransferEncoding: Finish signal received before the data stream ended");
+ }
+ m_IsFinished = true;
+ }
+};
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// 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.empty())
+ {
+ return std::make_shared<cIdentityTEParser>(a_Callbacks, a_ContentLength);
+ }
+ return nullptr;
+}
+
+
+
+
diff --git a/src/HTTPServer/TransferEncodingParser.h b/src/HTTPServer/TransferEncodingParser.h
new file mode 100644
index 000000000..ce3d01df7
--- /dev/null
+++ b/src/HTTPServer/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)
+ {
+ }
+};
+
+
+
+