From 52d18b4559cbaca949f722aa6901a6eb5f505f02 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sat, 20 Feb 2016 11:50:52 +0100 Subject: WebAdmin uses the new HTTP parser framework. --- src/HTTP/CMakeLists.txt | 6 +- src/HTTP/HTTPFormParser.cpp | 8 +- src/HTTP/HTTPFormParser.h | 8 +- src/HTTP/HTTPMessage.cpp | 57 ++++++++++ src/HTTP/HTTPMessage.h | 76 ++++++++++++- src/HTTP/HTTPMessageParser.cpp | 222 +++++++++++++++++++++++++++++++++++++ src/HTTP/HTTPMessageParser.h | 125 +++++++++++++++++++++ src/HTTP/HTTPRequestParser.cpp | 196 --------------------------------- src/HTTP/HTTPRequestParser.h | 109 ------------------- src/HTTP/HTTPResponseParser.cpp | 191 -------------------------------- src/HTTP/HTTPResponseParser.h | 116 -------------------- src/HTTP/HTTPServer.cpp | 107 +----------------- src/HTTP/HTTPServer.h | 13 ++- src/HTTP/HTTPServerConnection.cpp | 224 ++++++++++++++++---------------------- src/HTTP/HTTPServerConnection.h | 54 ++++----- 15 files changed, 618 insertions(+), 894 deletions(-) create mode 100644 src/HTTP/HTTPMessageParser.cpp create mode 100644 src/HTTP/HTTPMessageParser.h delete mode 100644 src/HTTP/HTTPRequestParser.cpp delete mode 100644 src/HTTP/HTTPRequestParser.h delete mode 100644 src/HTTP/HTTPResponseParser.cpp delete mode 100644 src/HTTP/HTTPResponseParser.h (limited to 'src/HTTP') diff --git a/src/HTTP/CMakeLists.txt b/src/HTTP/CMakeLists.txt index 4597f6eda..acb3ac2cd 100644 --- a/src/HTTP/CMakeLists.txt +++ b/src/HTTP/CMakeLists.txt @@ -8,8 +8,7 @@ SET (SRCS EnvelopeParser.cpp HTTPFormParser.cpp HTTPMessage.cpp - HTTPRequestParser.cpp - HTTPResponseParser.cpp + HTTPMessageParser.cpp HTTPServer.cpp HTTPServerConnection.cpp MultipartParser.cpp @@ -23,8 +22,7 @@ SET (HDRS EnvelopeParser.h HTTPFormParser.h HTTPMessage.h - HTTPRequestParser.h - HTTPResponseParser.h + HTTPMessageParser.h HTTPServer.h HTTPServerConnection.h MultipartParser.h diff --git a/src/HTTP/HTTPFormParser.cpp b/src/HTTP/HTTPFormParser.cpp index ddf87f540..ea5da3c18 100644 --- a/src/HTTP/HTTPFormParser.cpp +++ b/src/HTTP/HTTPFormParser.cpp @@ -5,7 +5,7 @@ #include "Globals.h" #include "HTTPFormParser.h" -#include "HTTPRequestParser.h" +#include "HTTPMessage.h" #include "MultipartParser.h" #include "NameValueParser.h" @@ -13,7 +13,7 @@ -cHTTPFormParser::cHTTPFormParser(cHTTPRequestParser & 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 cHTTPRequestParser & a_Request) +bool cHTTPFormParser::HasFormData(const cHTTPIncomingRequest & a_Request) { const AString & ContentType = a_Request.GetContentType(); return ( @@ -138,7 +138,7 @@ bool cHTTPFormParser::HasFormData(const cHTTPRequestParser & a_Request) -void cHTTPFormParser::BeginMultipart(const cHTTPRequestParser & 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/HTTP/HTTPFormParser.h b/src/HTTP/HTTPFormParser.h index fde6df696..6bf3e7d78 100644 --- a/src/HTTP/HTTPFormParser.h +++ b/src/HTTP/HTTPFormParser.h @@ -15,7 +15,7 @@ // fwd: -class cHTTPRequestParser; +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(cHTTPRequestParser & 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 cHTTPRequestParser & 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 cHTTPRequestParser & 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 index ca63397dd..5a7b86315 100644 --- a/src/HTTP/HTTPMessage.cpp +++ b/src/HTTP/HTTPMessage.cpp @@ -100,3 +100,60 @@ void cHTTPResponse::AppendToData(AString & a_DataStream) const + +//////////////////////////////////////////////////////////////////////////////// +// cHTTPIncomingRequest: + +cHTTPIncomingRequest::cHTTPIncomingRequest(const AString & a_Method, const AString & a_URL): + Super(mkRequest), + m_Method(a_Method), + m_URL(a_URL) +{ +} + + + + + +AString cHTTPIncomingRequest::GetURLPath(void) const +{ + auto idxQuestionMark = m_URL.find('?'); + if (idxQuestionMark == AString::npos) + { + return m_URL; + } + else + { + return m_URL.substr(0, idxQuestionMark); + } +} + + + + + +void cHTTPIncomingRequest::AddHeader(const AString & a_Key, const AString & a_Value) +{ + if ( + (NoCaseCompare(a_Key, "Authorization") == 0) && + (strncmp(a_Value.c_str(), "Basic ", 6) == 0) + ) + { + AString UserPass = Base64Decode(a_Value.substr(6)); + size_t idxCol = UserPass.find(':'); + if (idxCol != AString::npos) + { + m_AuthUsername = UserPass.substr(0, idxCol); + m_AuthPassword = UserPass.substr(idxCol + 1); + m_HasAuth = true; + } + } + if ((a_Key == "Connection") && (NoCaseCompare(a_Value, "keep-alive") == 0)) + { + m_AllowKeepAlive = true; + } +} + + + + diff --git a/src/HTTP/HTTPMessage.h b/src/HTTP/HTTPMessage.h index 50b1f9e19..683734b67 100644 --- a/src/HTTP/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; } @@ -82,3 +82,77 @@ public: + +/** 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 cUserDataPtr; + + + /** Creates a new instance of the class, containing the method and URL provided by the client. */ + cHTTPIncomingRequest(const AString & a_Method, const AString & a_URL); + + /** Returns the method used in the request */ + const AString & GetMethod(void) const { return m_Method; } + + /** Returns the URL used in the request */ + const AString & GetURL(void) const { return m_URL; } + + /** Returns the path part of the URL. */ + AString GetURLPath(void) const; + + /** Returns true if the request has had the Auth header present. */ + bool HasAuth(void) const { return m_HasAuth; } + + /** Returns the username that the request presented. Only valid if HasAuth() is true */ + const AString & GetAuthUsername(void) const { return m_AuthUsername; } + + /** Returns the password that the request presented. Only valid if HasAuth() is true */ + const AString & GetAuthPassword(void) const { return m_AuthPassword; } + + bool DoesAllowKeepAlive(void) const { return m_AllowKeepAlive; } + + /** Attaches any kind of data to this request, to be later retrieved by GetUserData(). */ + void SetUserData(cUserDataPtr a_UserData) { m_UserData = a_UserData; } + + /** Returns the data attached to this request by the class client. */ + cUserDataPtr GetUserData(void) { return m_UserData; } + + /** Adds the specified header into the internal list of headers. + Overrides the parent to add recognizing additional headers: auth and keepalive. */ + virtual void AddHeader(const AString & a_Key, const AString & a_Value) override; + +protected: + + /** Method of the request (GET / PUT / POST / ...) */ + AString m_Method; + + /** Full URL of the request */ + AString m_URL; + + /** Set to true if the request contains auth data that was understood by the parser */ + bool m_HasAuth; + + /** The username used for auth */ + AString m_AuthUsername; + + /** The password used for auth */ + AString m_AuthPassword; + + /** Set to true if the request indicated that it supports keepalives. + If false, the server will close the connection once the request is finished */ + bool m_AllowKeepAlive; + + /** Any data attached to the request by the class client. */ + cUserDataPtr m_UserData; +}; diff --git a/src/HTTP/HTTPMessageParser.cpp b/src/HTTP/HTTPMessageParser.cpp new file mode 100644 index 000000000..10d3d4ad9 --- /dev/null +++ b/src/HTTP/HTTPMessageParser.cpp @@ -0,0 +1,222 @@ + +// HTTPMessageParser.cpp + +// Implements the cHTTPMessageParser class that parses HTTP messages (request or response) being pushed into the parser, +// and reports the individual parts via callbacks + +#include "Globals.h" +#include "HTTPMessageParser.h" + + + + + +cHTTPMessageParser::cHTTPMessageParser(cHTTPMessageParser::cCallbacks & a_Callbacks): + m_Callbacks(a_Callbacks), + m_EnvelopeParser(*this) +{ + Reset(); +} + + + + + +size_t cHTTPMessageParser::Parse(const char * a_Data, size_t a_Size) +{ + // If parsing already finished or errorred, let the caller keep all the data: + if (m_IsFinished || m_HasHadError) + { + return 0; + } + + // If still waiting for the status line, add to buffer and try parsing it: + auto inBufferSoFar = m_Buffer.size(); + if (m_FirstLine.empty()) + { + m_Buffer.append(a_Data, a_Size); + auto bytesConsumedFirstLine = ParseFirstLine(); + ASSERT(bytesConsumedFirstLine <= inBufferSoFar + a_Size); // Haven't consumed more data than there is in the buffer + ASSERT(bytesConsumedFirstLine > inBufferSoFar); // Have consumed at least the previous buffer contents + if (m_FirstLine.empty()) + { + // All data used, but not a complete status line yet. + return a_Size; + } + if (m_HasHadError) + { + return AString::npos; + } + // Status line completed, feed the rest of the buffer into the envelope parser: + auto bytesConsumedEnvelope = m_EnvelopeParser.Parse(m_Buffer.data(), m_Buffer.size()); + if (bytesConsumedEnvelope == AString::npos) + { + m_HasHadError = true; + m_Callbacks.OnError("Failed to parse the envelope"); + return AString::npos; + } + ASSERT(bytesConsumedEnvelope <= bytesConsumedFirstLine + a_Size); // Haven't consumed more data than there was in the buffer + m_Buffer.erase(0, bytesConsumedEnvelope); + if (!m_EnvelopeParser.IsInHeaders()) + { + HeadersFinished(); + // Process any data still left in the buffer as message body: + auto bytesConsumedBody = ParseBody(m_Buffer.data(), m_Buffer.size()); + if (bytesConsumedBody == AString::npos) + { + // Error has already been reported by ParseBody, just bail out: + return AString::npos; + } + return bytesConsumedBody + bytesConsumedEnvelope + bytesConsumedFirstLine - inBufferSoFar; + } + return a_Size; + } // if (m_FirstLine.empty()) + + // If still parsing headers, send them to the envelope parser: + if (m_EnvelopeParser.IsInHeaders()) + { + auto bytesConsumed = m_EnvelopeParser.Parse(a_Data, a_Size); + if (bytesConsumed == AString::npos) + { + m_HasHadError = true; + m_Callbacks.OnError("Failed to parse the envelope"); + return AString::npos; + } + if (!m_EnvelopeParser.IsInHeaders()) + { + HeadersFinished(); + // Process any data still left as message body: + auto bytesConsumedBody = ParseBody(a_Data + bytesConsumed, a_Size - bytesConsumed); + if (bytesConsumedBody == AString::npos) + { + // Error has already been reported by ParseBody, just bail out: + return AString::npos; + } + } + return a_Size; + } + + // Already parsing the body + return ParseBody(a_Data, a_Size); +} + + + + + +void cHTTPMessageParser::Reset(void) +{ + m_HasHadError = false; + m_IsFinished = false; + m_FirstLine.clear(); + m_Buffer.clear(); + m_EnvelopeParser.Reset(); + m_TransferEncodingParser.reset(); + m_TransferEncoding.clear(); + m_ContentLength = 0; +} + + + + +size_t cHTTPMessageParser::ParseFirstLine(void) +{ + auto idxLineEnd = m_Buffer.find("\r\n"); + if (idxLineEnd == AString::npos) + { + // Not a complete line yet + return m_Buffer.size(); + } + m_FirstLine = m_Buffer.substr(0, idxLineEnd); + m_Buffer.erase(0, idxLineEnd + 2); + m_Callbacks.OnFirstLine(m_FirstLine); + return idxLineEnd + 2; +} + + + + +size_t cHTTPMessageParser::ParseBody(const char * a_Data, size_t a_Size) +{ + if (m_TransferEncodingParser == nullptr) + { + // We have no Transfer-encoding parser assigned. This should have happened when finishing the envelope + OnError("No transfer encoding parser"); + return AString::npos; + } + + // Parse the body using the transfer encoding parser: + // (Note that TE parser returns the number of bytes left, while we return the number of bytes consumed) + return a_Size - m_TransferEncodingParser->Parse(a_Data, a_Size); +} + + + + + +void cHTTPMessageParser::HeadersFinished(void) +{ + m_Callbacks.OnHeadersFinished(); + m_TransferEncodingParser = cTransferEncodingParser::Create(*this, m_TransferEncoding, m_ContentLength); + if (m_TransferEncodingParser == nullptr) + { + OnError(Printf("Unknown transfer encoding: %s", m_TransferEncoding.c_str())); + return; + } +} + + + + + +void cHTTPMessageParser::OnHeaderLine(const AString & a_Key, const AString & a_Value) +{ + m_Callbacks.OnHeaderLine(a_Key, a_Value); + auto Key = StrToLower(a_Key); + if (Key == "content-length") + { + if (!StringToInteger(a_Value, m_ContentLength)) + { + OnError(Printf("Invalid content length header value: \"%s\"", a_Value.c_str())); + } + return; + } + if (Key == "transfer-encoding") + { + m_TransferEncoding = a_Value; + return; + } +} + + + + + +void cHTTPMessageParser::OnError(const AString & a_ErrorDescription) +{ + m_HasHadError = true; + m_Callbacks.OnError(a_ErrorDescription); +} + + + + + +void cHTTPMessageParser::OnBodyData(const void * a_Data, size_t a_Size) +{ + m_Callbacks.OnBodyData(a_Data, a_Size); +} + + + + + +void cHTTPMessageParser::OnBodyFinished(void) +{ + m_IsFinished = true; + m_Callbacks.OnBodyFinished(); +} + + + + diff --git a/src/HTTP/HTTPMessageParser.h b/src/HTTP/HTTPMessageParser.h new file mode 100644 index 000000000..f07de0492 --- /dev/null +++ b/src/HTTP/HTTPMessageParser.h @@ -0,0 +1,125 @@ + +// HTTPMessageParser.h + +// Declares the cHTTPMessageParser class that parses HTTP messages (request or response) being pushed into the parser, +// and reports the individual parts via callbacks + + + + + +#pragma once + +#include "EnvelopeParser.h" +#include "TransferEncodingParser.h" + + + + + +class cHTTPMessageParser: + protected cEnvelopeParser::cCallbacks, + protected cTransferEncodingParser::cCallbacks +{ +public: + class cCallbacks + { + public: + // Force a virtual destructor in descendants: + virtual ~cCallbacks() {} + + /** Called when an error has occured while parsing. */ + virtual void OnError(const AString & a_ErrorDescription) = 0; + + /** Called when the first line (request / status) is fully parsed. */ + virtual void OnFirstLine(const AString & a_FirstLine) = 0; + + /** Called when a single header line is parsed. */ + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) = 0; + + /** Called when all the headers have been parsed. */ + virtual void OnHeadersFinished(void) = 0; + + /** Called for each chunk of the incoming body data. */ + virtual void OnBodyData(const void * a_Data, size_t a_Size) = 0; + + /** Called when the entire body has been reported by OnBodyData(). */ + virtual void OnBodyFinished(void) = 0; + }; + + /** Creates a new parser instance that will use the specified callbacks for reporting. */ + cHTTPMessageParser(cCallbacks & a_Callbacks); + + /** Parses the incoming data and calls the appropriate callbacks. + Returns the number of bytes consumed or AString::npos number for error. */ + size_t Parse(const char * a_Data, size_t a_Size); + + /** Called when the server indicates no more data will be sent (HTTP 1.0 socket closed). + Finishes all parsing and calls apropriate callbacks (error if incomplete response). */ + void Finish(void); + + /** Returns true if the entire response has been already parsed. */ + bool IsFinished(void) const { return m_IsFinished; } + + /** Resets the parser to the initial state, so that a new request can be parsed. */ + void Reset(void); + + +protected: + + /** The callbacks used for reporting. */ + cCallbacks & m_Callbacks; + + /** Set to true if an error has been encountered by the parser. */ + bool m_HasHadError; + + /** True if the response has been fully parsed. */ + bool m_IsFinished; + + /** The complete first line of the response. Empty if not parsed yet. */ + AString m_FirstLine; + + /** Buffer for the incoming data until the status line is parsed. */ + AString m_Buffer; + + /** Parser for the envelope data (headers) */ + cEnvelopeParser m_EnvelopeParser; + + /** The specific parser for the transfer encoding used by this response. */ + cTransferEncodingParserPtr m_TransferEncodingParser; + + /** The transfer encoding to be used by the parser. + Filled while parsing headers, used when headers are finished. */ + AString m_TransferEncoding; + + /** The content length, parsed from the headers, if available. + Unused for chunked encoding. + Filled while parsing headers, used when headers are finished. */ + size_t m_ContentLength; + + + /** Parses the first line out of m_Buffer. + Removes the first line from m_Buffer, if appropriate. + Returns the number of bytes consumed out of m_Buffer, or AString::npos number for error. */ + size_t ParseFirstLine(void); + + /** Parses the message body. + Processes transfer encoding and calls the callbacks for body data. + Returns the number of bytes consumed or AString::npos number for error. */ + size_t ParseBody(const char * a_Data, size_t a_Size); + + /** Called internally when the headers-parsing has just finished. */ + void HeadersFinished(void); + + // cEnvelopeParser::cCallbacks overrides: + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; + + // cTransferEncodingParser::cCallbacks overrides: + virtual void OnError(const AString & a_ErrorDescription) override; + virtual void OnBodyData(const void * a_Data, size_t a_Size) override; + virtual void OnBodyFinished(void) override; +}; + + + + diff --git a/src/HTTP/HTTPRequestParser.cpp b/src/HTTP/HTTPRequestParser.cpp deleted file mode 100644 index bab463832..000000000 --- a/src/HTTP/HTTPRequestParser.cpp +++ /dev/null @@ -1,196 +0,0 @@ - -// HTTPRequestParser.cpp - -// Implements the cHTTPRequestParser class representing the parser for incoming HTTP requests - -#include "Globals.h" -#include "HTTPRequestParser.h" - - - - - -cHTTPRequestParser::cHTTPRequestParser(void) : - super(mkRequest), - m_EnvelopeParser(*this), - m_IsValid(true), - m_UserData(nullptr), - m_HasAuth(false), - m_AllowKeepAlive(false) -{ -} - - - - - -size_t cHTTPRequestParser::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 cHTTPRequestParser::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 cHTTPRequestParser::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 cHTTPRequestParser::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); -} - - - - diff --git a/src/HTTP/HTTPRequestParser.h b/src/HTTP/HTTPRequestParser.h deleted file mode 100644 index 1b06d7b8b..000000000 --- a/src/HTTP/HTTPRequestParser.h +++ /dev/null @@ -1,109 +0,0 @@ - -// HTTPRequestParser.h - -// Declares the cHTTPRequestParser class representing the parser for incoming HTTP requests - - - - -#pragma once - -#include "HTTPMessage.h" -#include "EnvelopeParser.h" - - - - - -class cHTTPRequestParser : - public cHTTPMessage, - protected cEnvelopeParser::cCallbacks -{ - typedef cHTTPMessage super; - -public: - cHTTPRequestParser(void); - - /** Parses the request line and then headers from the received data. - Returns the number of bytes consumed or AString::npos number for error. - Once it has fully parsed all the headers, doesn't consume any more data. */ - 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); } - - /** Returns the method used in the request */ - const AString & GetMethod(void) const { return m_Method; } - - /** Returns the URL used in the request */ - const AString & GetURL(void) const { return m_URL; } - - /** Returns the URL used in the request, without any parameters */ - AString GetBareURL(void) const; - - /** Sets the UserData pointer that is stored within this request. - The request doesn't touch this data (doesn't delete it)! */ - void SetUserData(void * a_UserData) { m_UserData = a_UserData; } - - /** Retrieves the UserData pointer that has been stored within this request. */ - void * GetUserData(void) const { return m_UserData; } - - /** Returns true if more data is expected for the request headers */ - bool IsInHeaders(void) const { return m_EnvelopeParser.IsInHeaders(); } - - /** Returns true if the request did present auth data that was understood by the parser */ - bool HasAuth(void) const { return m_HasAuth; } - - /** Returns the username that the request presented. Only valid if HasAuth() is true */ - const AString & GetAuthUsername(void) const { return m_AuthUsername; } - - /** Returns the password that the request presented. Only valid if HasAuth() is true */ - const AString & GetAuthPassword(void) const { return m_AuthPassword; } - - bool DoesAllowKeepAlive(void) const { return m_AllowKeepAlive; } - -protected: - /** Parser for the envelope data */ - cEnvelopeParser m_EnvelopeParser; - - /** True if the data received so far is parsed successfully. When false, all further parsing is skipped */ - bool m_IsValid; - - /** Bufferred incoming data, while parsing for the request line */ - AString m_IncomingHeaderData; - - /** Method of the request (GET / PUT / POST / ...) */ - AString m_Method; - - /** Full URL of the request */ - AString m_URL; - - /** Data that the HTTPServer callbacks are allowed to store. */ - void * m_UserData; - - /** Set to true if the request contains auth data that was understood by the parser */ - bool m_HasAuth; - - /** The username used for auth */ - AString m_AuthUsername; - - /** The password used for auth */ - AString m_AuthPassword; - - /** Set to true if the request indicated that it supports keepalives. - If false, the server will close the connection once the request is finished */ - bool m_AllowKeepAlive; - - - /** 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; -} ; - - - - diff --git a/src/HTTP/HTTPResponseParser.cpp b/src/HTTP/HTTPResponseParser.cpp deleted file mode 100644 index 5469666b6..000000000 --- a/src/HTTP/HTTPResponseParser.cpp +++ /dev/null @@ -1,191 +0,0 @@ - -// 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_HasHadError(false), - 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 0; - } - - // If still waiting for the status line, add to buffer and try parsing it: - auto inBufferSoFar = m_Buffer.size(); - if (m_StatusLine.empty()) - { - m_Buffer.append(a_Data, a_Size); - if (!ParseStatusLine()) - { - // 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 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; - } - ASSERT(bytesConsumed < inBufferSoFar + a_Size); - 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: - HeadersFinished(); - auto res = ParseBody(m_Buffer.data(), m_Buffer.size()); - if (res == AString::npos) - { - return AString::npos; - } - return res + bytesConsumed - inBufferSoFar; - } - return a_Size; - } // 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 bytesConsumed + ParseBody(a_Data + bytesConsumed, a_Size - bytesConsumed); - } - return a_Size; - } - - // 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: - // (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 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); - } - else - { - m_TransferEncodingParser = cTransferEncodingParser::Create(*this, transferEncoding->second, 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_IsFinished = true; - m_Callbacks.OnBodyFinished(); -} - - - - diff --git a/src/HTTP/HTTPResponseParser.h b/src/HTTP/HTTPResponseParser.h deleted file mode 100644 index 1d867ecc5..000000000 --- a/src/HTTP/HTTPResponseParser.h +++ /dev/null @@ -1,116 +0,0 @@ - -// 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 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; } - - -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 consumed or AString::npos number for error. */ - size_t ParseBody(const char * a_Data, size_t a_Size); - - /** Called internally when the headers-parsing has just finished. */ - void HeadersFinished(void); - - // cEnvelopeParser::cCallbacks overrides: - virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; - - // cTransferEncodingParser::cCallbacks overrides: - virtual void OnError(const AString & a_ErrorDescription) override; - virtual void OnBodyData(const void * a_Data, size_t a_Size) override; - virtual void OnBodyFinished(void) override; -}; - - - - diff --git a/src/HTTP/HTTPServer.cpp b/src/HTTP/HTTPServer.cpp index 741f5f1c5..5a5bee045 100644 --- a/src/HTTP/HTTPServer.cpp +++ b/src/HTTP/HTTPServer.cpp @@ -5,7 +5,7 @@ #include "Globals.h" #include "HTTPServer.h" -#include "HTTPRequestParser.h" +#include "HTTPMessageParser.h" #include "HTTPServerConnection.h" #include "HTTPFormParser.h" #include "SslHTTPServerConnection.h" @@ -24,102 +24,6 @@ -class cDebugCallbacks : - public cHTTPServer::cCallbacks, - protected cHTTPFormParser::cCallbacks -{ - virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override - { - UNUSED(a_Connection); - - if (cHTTPFormParser::HasFormData(a_Request)) - { - a_Request.SetUserData(new cHTTPFormParser(a_Request, *this)); - } - } - - - virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) override - { - UNUSED(a_Connection); - - cHTTPFormParser * FormParser = reinterpret_cast(a_Request.GetUserData()); - if (FormParser != nullptr) - { - FormParser->Parse(a_Data, a_Size); - } - } - - - virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override - { - cHTTPFormParser * FormParser = reinterpret_cast(a_Request.GetUserData()); - if (FormParser != nullptr) - { - if (FormParser->Finish()) - { - cHTTPResponse Resp; - Resp.SetContentType("text/html"); - a_Connection.Send(Resp); - a_Connection.Send("\r\n"); - for (cHTTPFormParser::iterator itr = FormParser->begin(), end = FormParser->end(); itr != end; ++itr) - { - a_Connection.Send(Printf("\r\n", itr->first.c_str(), itr->second.c_str())); - } // for itr - FormParser[] - a_Connection.Send("
NameValue
%s
%s
"); - 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: @@ -280,7 +184,7 @@ cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_Remo -void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & 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(cHTTPServerConnection & a_Connection, cHTTPRequestP -void cHTTPServer::RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & 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(a_Data), a_Size); } -void cHTTPServer::RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & 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/HTTP/HTTPServer.h b/src/HTTP/HTTPServer.h index 15202a50e..d06e13cac 100644 --- a/src/HTTP/HTTPServer.h +++ b/src/HTTP/HTTPServer.h @@ -22,6 +22,7 @@ // fwd: class cHTTPMessage; class cHTTPRequestParser; +class cHTTPIncomingRequest; class cHTTPResponse; class cHTTPServerConnection; @@ -40,14 +41,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(cHTTPServerConnection & a_Connection, cHTTPRequestParser & 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(cHTTPServerConnection & a_Connection, cHTTPRequestParser & 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(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) = 0; + virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) = 0; } ; cHTTPServer(void); @@ -85,14 +86,14 @@ protected: cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort); /** Called by cHTTPServerConnection when it finishes parsing the request header */ - void NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); + void NewRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request); /** Called by cHTTPConenction when it receives more data for the request body. May be called multiple times for a single request. */ - void RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & 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 cHTTPServerConnection when it detects that the request has finished (all of its body has been received) */ - void RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); + void RequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request); } ; diff --git a/src/HTTP/HTTPServerConnection.cpp b/src/HTTP/HTTPServerConnection.cpp index 0ee9f3ce7..0439c8b93 100644 --- a/src/HTTP/HTTPServerConnection.cpp +++ b/src/HTTP/HTTPServerConnection.cpp @@ -5,7 +5,8 @@ #include "Globals.h" #include "HTTPServerConnection.h" -#include "HTTPRequestParser.h" +#include "HTTPMessage.h" +#include "HTTPMessageParser.h" #include "HTTPServer.h" @@ -14,9 +15,8 @@ cHTTPServerConnection::cHTTPServerConnection(cHTTPServer & a_HTTPServer) : m_HTTPServer(a_HTTPServer), - m_State(wcsRecvHeaders), - m_CurrentRequest(nullptr), - m_CurrentRequestBodyRemaining(0) + m_Parser(*this), + m_CurrentRequest(nullptr) { // LOGD("HTTP: New connection at %p", this); } @@ -28,8 +28,7 @@ cHTTPServerConnection::cHTTPServerConnection(cHTTPServer & a_HTTPServer) : cHTTPServerConnection::~cHTTPServerConnection() { // LOGD("HTTP: Connection deleting: %p", this); - delete m_CurrentRequest; - m_CurrentRequest = nullptr; + m_CurrentRequest.reset(); } @@ -41,7 +40,8 @@ void cHTTPServerConnection::SendStatusAndReason(int a_StatusCode, const AString 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(a_Response.size()))); SendData(a_Response.data(), a_Response.size()); - m_State = wcsRecvHeaders; + m_CurrentRequest.reset(); + m_Parser.Reset(); } @@ -51,7 +51,8 @@ void cHTTPServerConnection::SendStatusAndReason(int a_StatusCode, const AString 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_State = wcsRecvHeaders; + m_CurrentRequest.reset(); + m_Parser.Reset(); } @@ -60,10 +61,9 @@ void cHTTPServerConnection::SendNeedAuth(const AString & a_Realm) void cHTTPServerConnection::Send(const cHTTPResponse & a_Response) { - ASSERT(m_State == wcsRecvIdle); + ASSERT(m_CurrentRequest != nullptr); AString toSend; a_Response.AppendToData(toSend); - m_State = wcsSendingResp; SendData(toSend); } @@ -73,7 +73,7 @@ void cHTTPServerConnection::Send(const cHTTPResponse & a_Response) void cHTTPServerConnection::Send(const void * a_Data, size_t a_Size) { - ASSERT(m_State == wcsSendingResp); + 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); @@ -86,47 +86,10 @@ void cHTTPServerConnection::Send(const void * a_Data, size_t a_Size) void cHTTPServerConnection::FinishResponse(void) { - ASSERT(m_State == wcsSendingResp); + ASSERT(m_CurrentRequest != nullptr); SendData("0\r\n\r\n"); - m_State = wcsRecvHeaders; -} - - - - - -void cHTTPServerConnection::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; - } - } + m_CurrentRequest.reset(); + m_Parser.Reset(); } @@ -160,86 +123,7 @@ void cHTTPServerConnection::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 cHTTPRequestParser; - } - - 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) - { - cHTTPServerConnection::OnReceivedData(a_Data + BytesConsumed, a_Size - BytesConsumed); - return; - } - else - { - cHTTPServerConnection::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(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; - } - } + m_Parser.Parse(a_Data, a_Size); } @@ -268,6 +152,84 @@ void cHTTPServerConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg) + +void cHTTPServerConnection::OnError(const AString & a_ErrorDescription) +{ + OnRemoteClosed(); +} + + + + + +void cHTTPServerConnection::OnFirstLine(const AString & a_FirstLine) +{ + // Create a new request object for this request: + auto split = StringSplit(a_FirstLine, " "); + if (split.size() < 2) + { + // Invalid request line. We need at least the Method and URL + OnRemoteClosed(); + return; + } + m_CurrentRequest.reset(new cHTTPIncomingRequest(split[0], split[1])); +} + + + + + +void cHTTPServerConnection::OnHeaderLine(const AString & a_Key, const AString & a_Value) +{ + if (m_CurrentRequest == nullptr) + { + return; + } + m_CurrentRequest->AddHeader(a_Key, a_Value); +} + + + + + +void cHTTPServerConnection::OnHeadersFinished(void) +{ + if (m_CurrentRequest == nullptr) + { + return; + } + m_HTTPServer.NewRequest(*this, *m_CurrentRequest); +} + + + + + +void cHTTPServerConnection::OnBodyData(const void * a_Data, size_t a_Size) +{ + if (m_CurrentRequest == nullptr) + { + return; + } + m_HTTPServer.RequestBody(*this, *m_CurrentRequest, a_Data, a_Size); +} + + + + + +void cHTTPServerConnection::OnBodyFinished(void) +{ + // Process the request and reset: + m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); + m_CurrentRequest.reset(); + m_Parser.Reset(); +} + + + + + void cHTTPServerConnection::SendData(const void * a_Data, size_t a_Size) { m_Link->Send(a_Data, a_Size); diff --git a/src/HTTP/HTTPServerConnection.h b/src/HTTP/HTTPServerConnection.h index af2abb73f..62d3b7182 100644 --- a/src/HTTP/HTTPServerConnection.h +++ b/src/HTTP/HTTPServerConnection.h @@ -10,6 +10,7 @@ #pragma once #include "../OSSupport/Network.h" +#include "HTTPMessageParser.h" @@ -18,34 +19,29 @@ // fwd: class cHTTPServer; class cHTTPResponse; -class cHTTPRequestParser; - +class cHTTPIncomingRequest; class cHTTPServerConnection : - public cTCPLink::cCallbacks + public cTCPLink::cCallbacks, + public cHTTPMessageParser::cCallbacks { public: - - 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 - } ; - + /** Creates a new instance, connected to the specified HTTP server instance */ cHTTPServerConnection(cHTTPServer & a_HTTPServer); + + // Force a virtual destructor in all descendants virtual ~cHTTPServerConnection(); /** Sends HTTP status code together with a_Reason (used for HTTP errors). - Sends the a_Reason as the body as well, so that browsers display it. */ + 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 */ @@ -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. */ - cHTTPRequestParser * 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 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); -- cgit v1.2.3