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(a_Callbacks); + } + if (a_TransferEncoding.empty()) + { + return std::make_shared(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 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) + { + } +}; + + + +