From: Marcin Siodelski Date: Tue, 19 Dec 2017 15:40:07 +0000 (+0100) Subject: [5451] Extracted base class from HttpRequestParser. X-Git-Tag: trac5457_base~4^2~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=375b0c1cb319cffcf1dab471f9f287ccb047936d;p=thirdparty%2Fkea.git [5451] Extracted base class from HttpRequestParser. --- diff --git a/src/lib/http/Makefile.am b/src/lib/http/Makefile.am index 13cca2f315..42937507f5 100644 --- a/src/lib/http/Makefile.am +++ b/src/lib/http/Makefile.am @@ -30,6 +30,7 @@ libkea_http_la_SOURCES += header_context.h libkea_http_la_SOURCES += http_acceptor.h libkea_http_la_SOURCES += http_header.cc http_header.h libkea_http_la_SOURCES += http_message.cc http_message.h +libkea_http_la_SOURCES += http_message_parser_base.cc http_message_parser_base.h libkea_http_la_SOURCES += http_types.h libkea_http_la_SOURCES += listener.cc listener.h libkea_http_la_SOURCES += post_request.cc post_request.h diff --git a/src/lib/http/http_message_parser_base.cc b/src/lib/http/http_message_parser_base.cc new file mode 100644 index 0000000000..25178ecb63 --- /dev/null +++ b/src/lib/http/http_message_parser_base.cc @@ -0,0 +1,253 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include + +using namespace isc::util; + +namespace isc { +namespace http { + +const int HttpMessageParserBase::HTTP_PARSE_OK_ST; +const int HttpMessageParserBase::HTTP_PARSE_FAILED_ST; + +const int HttpMessageParserBase::DATA_READ_OK_EVT; +const int HttpMessageParserBase::NEED_MORE_DATA_EVT; +const int HttpMessageParserBase::MORE_DATA_PROVIDED_EVT; +const int HttpMessageParserBase::HTTP_PARSE_OK_EVT; +const int HttpMessageParserBase::HTTP_PARSE_FAILED_EVT; + + +HttpMessageParserBase::HttpMessageParserBase(HttpMessage& message) + : StateModel(), message_(message), buffer_(), error_message_() { +} + +void +HttpMessageParserBase::poll() { + try { + // Run the parser until it runs out of input data or until + // parsing completes. + do { + getState(getCurrState())->run(); + + } while (!isModelDone() && (getNextEvent() != NOP_EVT) && + (getNextEvent() != NEED_MORE_DATA_EVT)); + } catch (const std::exception& ex) { + abortModel(ex.what()); + } +} + +bool +HttpMessageParserBase::needData() const { + return ((getNextEvent() == NEED_MORE_DATA_EVT) || + (getNextEvent() == START_EVT)); +} + +bool +HttpMessageParserBase::httpParseOk() const { + return ((getNextEvent() == END_EVT) && + (getLastEvent() == HTTP_PARSE_OK_EVT)); +} + +void +HttpMessageParserBase::postBuffer(const void* buf, const size_t buf_size) { + if (buf_size > 0) { + // The next event is NEED_MORE_DATA_EVT when the parser wants to + // signal that more data is needed. This method is called to supply + // more data and thus it should change the next event to + // MORE_DATA_PROVIDED_EVT. + if (getNextEvent() == NEED_MORE_DATA_EVT) { + transition(getCurrState(), MORE_DATA_PROVIDED_EVT); + } + buffer_.insert(buffer_.end(), static_cast(buf), + static_cast(buf) + buf_size); + } +} + +void +HttpMessageParserBase::defineEvents() { + StateModel::defineEvents(); + + // Define HTTP parser specific events. + defineEvent(DATA_READ_OK_EVT, "DATA_READ_OK_EVT"); + defineEvent(NEED_MORE_DATA_EVT, "NEED_MORE_DATA_EVT"); + defineEvent(MORE_DATA_PROVIDED_EVT, "MORE_DATA_PROVIDED_EVT"); + defineEvent(HTTP_PARSE_OK_EVT, "HTTP_PARSE_OK_EVT"); + defineEvent(HTTP_PARSE_FAILED_EVT, "HTTP_PARSE_FAILED_EVT"); +} + +void +HttpMessageParserBase::verifyEvents() { + StateModel::verifyEvents(); + + getEvent(DATA_READ_OK_EVT); + getEvent(NEED_MORE_DATA_EVT); + getEvent(MORE_DATA_PROVIDED_EVT); + getEvent(HTTP_PARSE_OK_EVT); + getEvent(HTTP_PARSE_FAILED_EVT); +} + +void +HttpMessageParserBase::defineStates() { + // Call parent class implementation first. + StateModel::defineStates(); + + defineState(HTTP_PARSE_OK_ST, "HTTP_PARSE_OK_ST", + boost::bind(&HttpMessageParserBase::parseEndedHandler, this)); + + defineState(HTTP_PARSE_FAILED_ST, "HTTP_PARSE_FAILED_ST", + boost::bind(&HttpMessageParserBase::parseEndedHandler, this)); +} + +void +HttpMessageParserBase::stateWithReadHandler(const std::string& handler_name, + boost::function + after_read_logic) { + char c = getNextFromBuffer(); + // Do nothing if we reached the end of buffer. + if (getNextEvent() != NEED_MORE_DATA_EVT) { + switch(getNextEvent()) { + case DATA_READ_OK_EVT: + case MORE_DATA_PROVIDED_EVT: + after_read_logic(c); + break; + default: + invalidEventError(handler_name, getNextEvent()); + } + } +} + +void +HttpMessageParserBase::parseFailure(const std::string& error_msg) { + error_message_ = error_msg + " : " + getContextStr(); + transition(HTTP_PARSE_FAILED_ST, HTTP_PARSE_FAILED_EVT); +} + +void +HttpMessageParserBase::onModelFailure(const std::string& explanation) { + if (error_message_.empty()) { + error_message_ = explanation; + } +} + +char +HttpMessageParserBase::getNextFromBuffer() { + unsigned int ev = getNextEvent(); + char c = '\0'; + // The caller should always provide additional data when the + // NEED_MORE_DATA_EVT occurs. If the next event is still + // NEED_MORE_DATA_EVT it indicates that the caller hasn't provided + // the data. + if (ev == NEED_MORE_DATA_EVT) { + isc_throw(HttpMessageParserBaseError, + "HTTP request parser requires new data to progress, but no data" + " have been provided. The transaction is aborted to avoid" + " a deadlock. This is a Kea HTTP server logic error!"); + + } else { + // Try to pop next character from the buffer. + const bool data_exist = popNextFromBuffer(c); + if (!data_exist) { + // There is no more data so it is really not possible that we're + // at MORE_DATA_PROVIDED_EVT. + if (ev == MORE_DATA_PROVIDED_EVT) { + isc_throw(HttpMessageParserBaseError, + "HTTP server state indicates that new data have been" + " provided to be parsed, but the transaction buffer" + " contains no new data. This is a Kea HTTP server logic" + " error!"); + + } else { + // If there is no more data we should set NEED_MORE_DATA_EVT + // event to indicate that new data should be provided. + transition(getCurrState(), NEED_MORE_DATA_EVT); + } + } + } + return (c); +} + +void +HttpMessageParserBase::invalidEventError(const std::string& handler_name, + const unsigned int event) { + isc_throw(HttpMessageParserBaseError, handler_name << ": " + << " invalid event " << getEventLabel(static_cast(event))); +} + +void +HttpMessageParserBase::parseEndedHandler() { + switch(getNextEvent()) { + case HTTP_PARSE_OK_EVT: + message_.finalize(); + transition(END_ST, END_EVT); + break; + case HTTP_PARSE_FAILED_EVT: + abortModel("HTTP request parsing failed"); + break; + + default: + invalidEventError("parseEndedHandler", getNextEvent()); + } +} + +bool +HttpMessageParserBase::popNextFromBuffer(char& next) { + // If there are any characters in the buffer, pop next. + if (!buffer_.empty()) { + next = buffer_.front(); + buffer_.pop_front(); + return (true); + } + return (false); +} + + +bool +HttpMessageParserBase::isChar(const char c) const { + // was (c >= 0) && (c <= 127) + return (c >= 0); +} + +bool +HttpMessageParserBase::isCtl(const char c) const { + return (((c >= 0) && (c <= 31)) || (c == 127)); +} + +bool +HttpMessageParserBase::isSpecial(const char c) const { + switch (c) { + case '(': + case ')': + case '<': + case '>': + case '@': + case ',': + case ';': + case ':': + case '\\': + case '"': + case '/': + case '[': + case ']': + case '?': + case '=': + case '{': + case '}': + case ' ': + case '\t': + return true; + + default: + ; + } + + return false; +} + + +} // end of namespace isc::http +} // end of namespace isc diff --git a/src/lib/http/http_message_parser_base.h b/src/lib/http/http_message_parser_base.h new file mode 100644 index 0000000000..e909e8ff64 --- /dev/null +++ b/src/lib/http/http_message_parser_base.h @@ -0,0 +1,270 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef HTTP_MESSAGE_PARSER_BASE_H +#define HTTP_MESSAGE_PARSER_BASE_H + +#include +#include +#include +#include +#include +#include + +namespace isc { +namespace http { + +/// @brief Exception thrown when an error during parsing HTTP message +/// has occurred. +/// +/// The most common errors are due to receiving malformed requests. +class HttpMessageParserBaseError : public Exception { +public: + HttpMessageParserBaseError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Base class for the HTTP message parsers. +/// +/// This is a base class for @c HttpRequestParser and @c HttpResponseParser +/// classes. It provides common states, events and functionality for processing +/// the received HTTP messages. +/// +/// This class must not be used directly. Instead, an instance of the +/// derived class should be used. +/// +/// HTTP uses TCP as a transport which is asynchronous in nature, i.e. the +/// HTTP message is received in chunks and multiple TCP connections can be +/// established at the same time. Multiplexing between these connections +/// requires providing a separate state machine per connection to "remember" +/// the state of each transaction when the parser is waiting for asynchronous +/// data to be delivered. While the parser is waiting for the data, it can +/// parse requests received over other connections. This class provides means +/// for parsing partial data received over the specific connection and +/// interrupting data parsing to switch to a different context. +/// +/// A new method @ref HttpMessageParserBase::poll has been created to run the +/// parser's state machine as long as there are unparsed data in the parser's +/// internal buffer. This method returns control to the caller when the parser +/// runs out of data in this buffer. The caller must feed the buffer by calling +/// @ref HttpMessageParserBase::postBuffer and then run +/// @ref HttpMessageParserBase::poll again. +/// +/// In case, the caller provides more data than indicated by the "Content-Length" +/// header the parser will return from @c poll() after parsing the data which +/// constitute the HTTP request and not parse the extraneous data. The caller +/// should test the @ref HttpMessageParserBase::needData and +/// @ref HttpMessageParserBase::httpParseOk to determine whether parsing has +/// completed. +/// +/// The @ref util::StateModel::runModel must not be used to run the parser +/// state machine, thus it is made private method. +class HttpMessageParserBase : public util::StateModel { +public: + + /// @name States supported by the HttpMessageParserBase. + /// + //@{ + + /// @brief Parsing successfully completed. + static const int HTTP_PARSE_OK_ST = SM_DERIVED_STATE_MIN + 1000; + + /// @brief Parsing failed. + static const int HTTP_PARSE_FAILED_ST = SM_DERIVED_STATE_MIN + 1001; + + //@} + + /// @name Events used during HTTP message parsing. + /// + //@{ + + /// @brief Chunk of data successfully read and parsed. + static const int DATA_READ_OK_EVT = SM_DERIVED_EVENT_MIN + 1; + + /// @brief Unable to proceed with parsing until new data is provided. + static const int NEED_MORE_DATA_EVT = SM_DERIVED_EVENT_MIN + 2; + + /// @brief New data provided and parsing should continue. + static const int MORE_DATA_PROVIDED_EVT = SM_DERIVED_EVENT_MIN + 3; + + /// @brief Parsing HTTP request successful. + static const int HTTP_PARSE_OK_EVT = SM_DERIVED_EVENT_MIN + 1000; + + /// @brief Parsing HTTP request failed. + static const int HTTP_PARSE_FAILED_EVT = SM_DERIVED_EVENT_MIN + 1001; + + //@} + + /// @brief Constructor. + /// + /// @param message Reference to the HTTP request or response message. + HttpMessageParserBase(HttpMessage& message); + + /// @brief Run the parser as long as the amount of data is sufficient. + /// + /// The data to be parsed should be provided by calling + /// @ref HttpMessageParserBase::postBuffer. When the parser reaches the end + /// of the data buffer the @ref HttpMessageParserBase::poll sets the next + /// event to @ref NEED_MORE_DATA_EVT and returns. The caller should then invoke + /// @ref HttpMessageParserBase::postBuffer again to provide more data to the + /// parser, and call @ref HttpMessageParserBase::poll to continue parsing. + /// + /// This method also returns when parsing completes or fails. The last + /// event can be examined to check whether parsing was successful or not. + void poll(); + + /// @brief Returns true if the parser needs more data to continue. + /// + /// @return true if the next event is NEED_MORE_DATA_EVT. + bool needData() const; + + /// @brief Returns true if the message has been parsed successfully. + bool httpParseOk() const; + + /// @brief Returns error message. + std::string getErrorMessage() const { + return (error_message_); + } + + /// @brief Provides more input data to the parser. + /// + /// This method must be called prior to calling @ref HttpMessageParserBase::poll + /// to deliver data to be parsed. HTTP messages are received over TCP and + /// multiple reads may be necessary to retrieve the entire request. There is + /// no need to accumulate the entire request to start parsing it. A chunk + /// of data can be provided to the parser using this method and parsed right + /// away using @ref HttpMessageParserBase::poll. + /// + /// @param buf A pointer to the buffer holding the data. + /// @param buf_size Size of the data within the buffer. + void postBuffer(const void* buf, const size_t buf_size); + +private: + + /// @brief Make @ref runModel private to make sure that the caller uses + /// @ref poll method instead. + using StateModel::runModel; + +protected: + + /// @brief Define events used by the parser. + virtual void defineEvents(); + + /// @brief Verifies events used by the parser. + virtual void verifyEvents(); + + /// @brief Defines states of the parser. + virtual void defineStates(); + + /// @brief Generic parser handler which reads a single byte of data and + /// parses it using specified callback function. + /// + /// This generic handler is used in most of the parser states to parse a + /// single byte of input data. If there is no more data it simply returns. + /// Otherwise, if the next event is DATA_READ_OK_EVT or + /// MORE_DATA_PROVIDED_EVT, it calls the provided callback function to + /// parse the new byte of data. For all other states it throws an exception. + /// + /// @param handler_name Name of the handler function which called this + /// method. + /// @param after_read_logic Callback function to parse the byte of data. + /// This callback function implements state specific logic. + /// + /// @throw HttpRequestParserError when invalid event occurred. + void stateWithReadHandler(const std::string& handler_name, + boost::function + after_read_logic); + + /// @brief Transition parser to failure state. + /// + /// This method transitions the parser to @ref HTTP_PARSE_FAILED_ST and + /// sets next event to HTTP_PARSE_FAILED_EVT. + /// + /// @param error_msg Error message explaining the failure. + void parseFailure(const std::string& error_msg); + + /// @brief A method called when parsing fails. + /// + /// @param explanation Error message explaining the reason for parsing + /// failure. + virtual void onModelFailure(const std::string& explanation); + + /// @brief Retrieves next byte of data from the buffer. + /// + /// During normal operation, when there is no more data in the buffer, + /// the parser sets NEED_MORE_DATA_EVT as next event to signal the need for + /// calling @ref HttpMessageParserBase::postBuffer. + /// + /// @throw HttpMessageParserBaseError If current event is already set to + /// NEED_MORE_DATA_EVT or MORE_DATA_PROVIDED_EVT. In the former case, it + /// indicates that the caller failed to provide new data using + /// @ref HttpMessageParserBase::postBuffer. The latter case is highly unlikely + /// as it indicates that no new data were provided but the state of the + /// parser was changed from NEED_MORE_DATA_EVT or the data were provided + /// but the data buffer is empty. In both cases, it is an internal server + /// error. + char getNextFromBuffer(); + + /// @brief This method is called when invalid event occurred in a particular + /// parser state. + /// + /// This method simply throws @ref HttpMessageParserBaseError informing about + /// invalid event occurring for the particular parser state. The error + /// message includes the name of the handler in which the exception + /// has been thrown. It also includes the event which caused the + /// exception. + /// + /// @param handler_name Name of the handler in which the exception is + /// thrown. + /// @param event An event which caused the exception. + /// + /// @throw HttpMessageParserBaseError. + void invalidEventError(const std::string& handler_name, + const unsigned int event); + + /// @brief Handler for HTTP_PARSE_OK_ST and HTTP_PARSE_FAILED_ST. + /// + /// If parsing is successful, it calls @ref HttpRequest::create to validate + /// the HTTP request. In both cases it transitions the parser to the END_ST. + void parseEndedHandler(); + + /// @brief Tries to read next byte from buffer. + /// + /// @param [out] next A reference to the variable where read data should be + /// stored. + /// + /// @return true if character was successfully read, false otherwise. + bool popNextFromBuffer(char& next); + + /// @brief Checks if specified value is a character. + /// + /// @return true, if specified value is a character. + bool isChar(const char c) const; + + /// @brief Checks if specified value is a control value. + /// + /// @return true, if specified value is a control value. + bool isCtl(const char c) const; + + /// @brief Checks if specified value is a special character. + /// + /// @return true, if specified value is a special character. + bool isSpecial(const char c) const; + + /// @brief Reference to the parsed HTTP message. + HttpMessage& message_; + + /// @brief Internal buffer from which parser reads data. + std::list buffer_; + + /// @brief Error message set by @ref onModelFailure. + std::string error_message_; +}; + +} // end of namespace isc::http +} // end of namespace isc + +#endif diff --git a/src/lib/http/request_parser.cc b/src/lib/http/request_parser.cc index f77a9eef4b..dd5e095d76 100644 --- a/src/lib/http/request_parser.cc +++ b/src/lib/http/request_parser.cc @@ -35,18 +35,10 @@ const int HttpRequestParser::HEADER_VALUE_ST; const int HttpRequestParser::EXPECTING_NEW_LINE2_ST; const int HttpRequestParser::EXPECTING_NEW_LINE3_ST; const int HttpRequestParser::HTTP_BODY_ST; -const int HttpRequestParser::HTTP_PARSE_OK_ST; -const int HttpRequestParser::HTTP_PARSE_FAILED_ST; - -const int HttpRequestParser::DATA_READ_OK_EVT; -const int HttpRequestParser::NEED_MORE_DATA_EVT; -const int HttpRequestParser::MORE_DATA_PROVIDED_EVT; -const int HttpRequestParser::HTTP_PARSE_OK_EVT; -const int HttpRequestParser::HTTP_PARSE_FAILED_EVT; HttpRequestParser::HttpRequestParser(HttpRequest& request) - : StateModel(), buffer_(), request_(request), - context_(request_.context()), error_message_() { + : HttpMessageParserBase(request), request_(request), + context_(request_.context()) { } void @@ -61,75 +53,10 @@ HttpRequestParser::initModel() { postNextEvent(START_EVT); } -void -HttpRequestParser::poll() { - try { - // Run the parser until it runs out of input data or until - // parsing completes. - do { - getState(getCurrState())->run(); - - } while (!isModelDone() && (getNextEvent() != NOP_EVT) && - (getNextEvent() != NEED_MORE_DATA_EVT)); - } catch (const std::exception& ex) { - abortModel(ex.what()); - } -} - -bool -HttpRequestParser::needData() const { - return ((getNextEvent() == NEED_MORE_DATA_EVT) || - (getNextEvent() == START_EVT)); -} - -bool -HttpRequestParser::httpParseOk() const { - return ((getNextEvent() == END_EVT) && - (getLastEvent() == HTTP_PARSE_OK_EVT)); -} - -void -HttpRequestParser::postBuffer(const void* buf, const size_t buf_size) { - if (buf_size > 0) { - // The next event is NEED_MORE_DATA_EVT when the parser wants to - // signal that more data is needed. This method is called to supply - // more data and thus it should change the next event to - // MORE_DATA_PROVIDED_EVT. - if (getNextEvent() == NEED_MORE_DATA_EVT) { - transition(getCurrState(), MORE_DATA_PROVIDED_EVT); - } - buffer_.insert(buffer_.end(), static_cast(buf), - static_cast(buf) + buf_size); - } -} - -void -HttpRequestParser::defineEvents() { - StateModel::defineEvents(); - - // Define HTTP parser specific events. - defineEvent(DATA_READ_OK_EVT, "DATA_READ_OK_EVT"); - defineEvent(NEED_MORE_DATA_EVT, "NEED_MORE_DATA_EVT"); - defineEvent(MORE_DATA_PROVIDED_EVT, "MORE_DATA_PROVIDED_EVT"); - defineEvent(HTTP_PARSE_OK_EVT, "HTTP_PARSE_OK_EVT"); - defineEvent(HTTP_PARSE_FAILED_EVT, "HTTP_PARSE_FAILED_EVT"); -} - -void -HttpRequestParser::verifyEvents() { - StateModel::verifyEvents(); - - getEvent(DATA_READ_OK_EVT); - getEvent(NEED_MORE_DATA_EVT); - getEvent(MORE_DATA_PROVIDED_EVT); - getEvent(HTTP_PARSE_OK_EVT); - getEvent(HTTP_PARSE_FAILED_EVT); -} - void HttpRequestParser::defineStates() { // Call parent class implementation first. - StateModel::defineStates(); + HttpMessageParserBase::defineStates(); // Define HTTP parser specific states. defineState(RECEIVE_START_ST, "RECEIVE_START_ST", @@ -210,90 +137,8 @@ HttpRequestParser::defineStates() { defineState(HTTP_BODY_ST, "HTTP_BODY_ST", boost::bind(&HttpRequestParser::bodyHandler, this)); - - defineState(HTTP_PARSE_OK_ST, "HTTP_PARSE_OK_ST", - boost::bind(&HttpRequestParser::parseEndedHandler, this)); - - defineState(HTTP_PARSE_FAILED_ST, "HTTP_PARSE_FAILED_ST", - boost::bind(&HttpRequestParser::parseEndedHandler, this)); -} - -void -HttpRequestParser::parseFailure(const std::string& error_msg) { - error_message_ = error_msg + " : " + getContextStr(); - transition(HTTP_PARSE_FAILED_ST, HTTP_PARSE_FAILED_EVT); -} - -void -HttpRequestParser::onModelFailure(const std::string& explanation) { - if (error_message_.empty()) { - error_message_ = explanation; - } -} - -char -HttpRequestParser::getNextFromBuffer() { - unsigned int ev = getNextEvent(); - char c = '\0'; - // The caller should always provide additional data when the - // NEED_MORE_DATA_EVT occurs. If the next event is still - // NEED_MORE_DATA_EVT it indicates that the caller hasn't provided - // the data. - if (ev == NEED_MORE_DATA_EVT) { - isc_throw(HttpRequestParserError, - "HTTP request parser requires new data to progress, but no data" - " have been provided. The transaction is aborted to avoid" - " a deadlock. This is a Kea HTTP server logic error!"); - - } else { - // Try to pop next character from the buffer. - const bool data_exist = popNextFromBuffer(c); - if (!data_exist) { - // There is no more data so it is really not possible that we're - // at MORE_DATA_PROVIDED_EVT. - if (ev == MORE_DATA_PROVIDED_EVT) { - isc_throw(HttpRequestParserError, - "HTTP server state indicates that new data have been" - " provided to be parsed, but the transaction buffer" - " contains no new data. This is a Kea HTTP server logic" - " error!"); - - } else { - // If there is no more data we should set NEED_MORE_DATA_EVT - // event to indicate that new data should be provided. - transition(getCurrState(), NEED_MORE_DATA_EVT); - } - } - } - return (c); -} - -void -HttpRequestParser::invalidEventError(const std::string& handler_name, - const unsigned int event) { - isc_throw(HttpRequestParserError, handler_name << ": " - << " invalid event " << getEventLabel(static_cast(event))); } -void -HttpRequestParser::stateWithReadHandler(const std::string& handler_name, - boost::function - after_read_logic) { - char c = getNextFromBuffer(); - // Do nothing if we reached the end of buffer. - if (getNextEvent() != NEED_MORE_DATA_EVT) { - switch(getNextEvent()) { - case DATA_READ_OK_EVT: - case MORE_DATA_PROVIDED_EVT: - after_read_logic(c); - break; - default: - invalidEventError(handler_name, getNextEvent()); - } - } -} - - void HttpRequestParser::receiveStartHandler() { char c = getNextFromBuffer(); @@ -600,77 +445,5 @@ HttpRequestParser::bodyHandler() { }); } - -void -HttpRequestParser::parseEndedHandler() { - switch(getNextEvent()) { - case HTTP_PARSE_OK_EVT: - request_.finalize(); - transition(END_ST, END_EVT); - break; - case HTTP_PARSE_FAILED_EVT: - abortModel("HTTP request parsing failed"); - break; - - default: - invalidEventError("parseEndedHandler", getNextEvent()); - } -} - -bool -HttpRequestParser::popNextFromBuffer(char& next) { - // If there are any characters in the buffer, pop next. - if (!buffer_.empty()) { - next = buffer_.front(); - buffer_.pop_front(); - return (true); - } - return (false); -} - - -bool -HttpRequestParser::isChar(const char c) const { - // was (c >= 0) && (c <= 127) - return (c >= 0); -} - -bool -HttpRequestParser::isCtl(const char c) const { - return (((c >= 0) && (c <= 31)) || (c == 127)); -} - -bool -HttpRequestParser::isSpecial(const char c) const { - switch (c) { - case '(': - case ')': - case '<': - case '>': - case '@': - case ',': - case ';': - case ':': - case '\\': - case '"': - case '/': - case '[': - case ']': - case '?': - case '=': - case '{': - case '}': - case ' ': - case '\t': - return true; - - default: - ; - } - - return false; -} - - } // namespace http } // namespace isc diff --git a/src/lib/http/request_parser.h b/src/lib/http/request_parser.h index 0cbce1e4be..f65572de5a 100644 --- a/src/lib/http/request_parser.h +++ b/src/lib/http/request_parser.h @@ -7,14 +7,9 @@ #ifndef HTTP_REQUEST_PARSER_H #define HTTP_REQUEST_PARSER_H -#include +#include #include -#include -#include #include -#include -#include -#include namespace isc { namespace http { @@ -23,10 +18,10 @@ namespace http { /// has occurred. /// /// The most common errors are due to receiving malformed requests. -class HttpRequestParserError : public Exception { +class HttpRequestParserError : public HttpMessageParserBaseError { public: HttpRequestParserError(const char* file, size_t line, const char* what) : - isc::Exception(file, line, what) { }; + HttpMessageParserBaseError(file, line, what) { }; }; class HttpRequestParser; @@ -37,22 +32,12 @@ typedef boost::shared_ptr HttpRequestParserPtr; /// @brief A generic parser for HTTP requests. /// /// This class implements a parser for HTTP requests. The parser derives from -/// @ref isc::util::StateModel class and implements its own state machine on +/// @ref HttpMessageParserBase class and implements its own state machine on /// top of it. The states of the parser reflect various parts of the HTTP /// message being parsed, e.g. parsing HTTP method, parsing URI, parsing /// message body etc. The descriptions of all parser states are provided /// below together with the constants defining these states. /// -/// HTTP uses TCP as a transport which is asynchronous in nature, i.e. the -/// HTTP message is received in chunks and multiple TCP connections can be -/// established at the same time. Multiplexing between these connections -/// requires providing a separate state machine per connection to "remember" -/// the state of each transaction when the parser is waiting for asynchronous -/// data to be delivered. While the parser is waiting for the data, it can -/// parse requests received over other connections. This class provides means -/// for parsing partial data received over the specific connection and -/// interrupting data parsing to switch to a different context. -/// /// The request parser validates the syntax of the received message as it /// progresses with parsing the data. Though, it doesn't interpret the received /// data until the whole message is parsed. In most cases we want to apply some @@ -76,24 +61,7 @@ typedef boost::shared_ptr HttpRequestParserPtr; /// If any of these restrictions is not met in the received message, an /// exception will be thrown, thereby @ref HttpRequestParser will fail parsing /// the message. -/// -/// A new method @ref HttpRequestParser::poll has been created to run the -/// parser's state machine as long as there are unparsed data in the parser's -/// internal buffer. This method returns control to the caller when the parser -/// runs out of data in this buffer. The caller must feed the buffer by calling -/// @ref HttpRequestParser::postBuffer and then run @ref HttpRequestParser::poll -/// again. -/// -/// In case the caller provides more data than indicated by the "Content-Length" -/// header the parser will return from poll() after parsing the data which -/// constitute the HTTP request and not parse the extraneous data. The caller -/// should test the @ref HttpRequestParser::needData and -/// @ref HttpRequestParser::httpParseOk to determine whether parsing has -/// completed. -/// -/// The @ref util::StateModel::runModel must not be used to run the -/// @ref HttpRequestParser state machine, thus it is made private method. -class HttpRequestParser : public util::StateModel { +class HttpRequestParser : public HttpMessageParserBase { public: /// @name States supported by the HttpRequestParser. @@ -164,36 +132,9 @@ public: /// @brief Parsing body of a HTTP message. static const int HTTP_BODY_ST = SM_DERIVED_STATE_MIN + 21; - /// @brief Parsing successfully completed. - static const int HTTP_PARSE_OK_ST = SM_DERIVED_STATE_MIN + 100; - - /// @brief Parsing failed. - static const int HTTP_PARSE_FAILED_ST = SM_DERIVED_STATE_MIN + 101; - //@} - /// @name Events used during HTTP message parsing. - /// - //@{ - - /// @brief Chunk of data successfully read and parsed. - static const int DATA_READ_OK_EVT = SM_DERIVED_EVENT_MIN + 1; - - /// @brief Unable to proceed with parsing until new data is provided. - static const int NEED_MORE_DATA_EVT = SM_DERIVED_EVENT_MIN + 2; - - /// @brief New data provided and parsing should continue. - static const int MORE_DATA_PROVIDED_EVT = SM_DERIVED_EVENT_MIN + 3; - - /// @brief Parsing HTTP request successful. - static const int HTTP_PARSE_OK_EVT = SM_DERIVED_EVENT_MIN + 100; - - /// @brief Parsing HTTP request failed. - static const int HTTP_PARSE_FAILED_EVT = SM_DERIVED_EVENT_MIN + 101; - - //@} - /// @brief Constructor. /// /// Creates new instance of the parser. @@ -210,126 +151,11 @@ public: /// states and events, and sets the initial model state to RECEIVE_START_ST. void initModel(); - /// @brief Run the parser as long as the amount of data is sufficient. - /// - /// The data to be parsed should be provided by calling - /// @ref HttpRequestParser::postBuffer. When the parser reaches the end of - /// the data buffer the @ref HttpRequestParser::poll sets the next event to - /// @ref NEED_MORE_DATA_EVT and returns. The caller should then invoke - /// @ref HttpRequestParser::postBuffer again to provide more data to the - /// parser, and call @ref HttpRequestParser::poll to continue parsing. - /// - /// This method also returns when parsing completes or fails. The last - /// event can be examined to check whether parsing was successful or not. - void poll(); - - /// @brief Returns true if the parser needs more data to continue. - /// - /// @return true if the next event is NEED_MORE_DATA_EVT. - bool needData() const; - - /// @brief Returns true if a request has been parsed successfully. - bool httpParseOk() const; - - /// @brief Returns error message. - std::string getErrorMessage() const { - return (error_message_); - } - - /// @brief Provides more input data to the parser. - /// - /// This method must be called prior to calling @ref HttpRequestParser::poll - /// to deliver data to be parsed. HTTP requests are received over TCP and - /// multiple reads may be necessary to retrieve the entire request. There is - /// no need to accumulate the entire request to start parsing it. A chunk - /// of data can be provided to the parser using this method and parsed right - /// away using @ref HttpRequestParser::poll. - /// - /// @param buf A pointer to the buffer holding the data. - /// @param buf_size Size of the data within the buffer. - void postBuffer(const void* buf, const size_t buf_size); - private: - /// @brief Make @ref runModel private to make sure that the caller uses - /// @ref poll method instead. - using StateModel::runModel; - - /// @brief Define events used by the parser. - virtual void defineEvents(); - - /// @brief Verifies events used by the parser. - virtual void verifyEvents(); - /// @brief Defines states of the parser. virtual void defineStates(); - /// @brief Transition parser to failure state. - /// - /// This method transitions the parser to @ref HTTP_PARSE_FAILED_ST and - /// sets next event to HTTP_PARSE_FAILED_EVT. - /// - /// @param error_msg Error message explaining the failure. - void parseFailure(const std::string& error_msg); - - /// @brief A method called when parsing fails. - /// - /// @param explanation Error message explaining the reason for parsing - /// failure. - virtual void onModelFailure(const std::string& explanation); - - /// @brief Retrieves next byte of data from the buffer. - /// - /// During normal operation, when there is no more data in the buffer, - /// the parser sets NEED_MORE_DATA_EVT as next event to signal the need for - /// calling @ref HttpRequestParser::postBuffer. - /// - /// @throw HttpRequestParserError If current event is already set to - /// NEED_MORE_DATA_EVT or MORE_DATA_PROVIDED_EVT. In the former case, it - /// indicates that the caller failed to provide new data using - /// @ref HttpRequestParser::postBuffer. The latter case is highly unlikely - /// as it indicates that no new data were provided but the state of the - /// parser was changed from NEED_MORE_DATA_EVT or the data were provided - /// but the data buffer is empty. In both cases, it is an internal server - /// error. - char getNextFromBuffer(); - - /// @brief This method is called when invalid event occurred in a particular - /// parser state. - /// - /// This method simply throws @ref HttpRequestParserError informing about - /// invalid event occurring for the particular parser state. The error - /// message includes the name of the handler in which the exception - /// has been thrown. It also includes the event which caused the - /// exception. - /// - /// @param handler_name Name of the handler in which the exception is - /// thrown. - /// @param event An event which caused the exception. - /// - /// @throw HttpRequestParserError. - void invalidEventError(const std::string& handler_name, - const unsigned int event); - - /// @brief Generic parser handler which reads a single byte of data and - /// parses it using specified callback function. - /// - /// This generic handler is used in most of the parser states to parse a - /// single byte of input data. If there is no more data it simply returns. - /// Otherwise, if the next event is DATA_READ_OK_EVT or - /// MORE_DATA_PROVIDED_EVT, it calls the provided callback function to - /// parse the new byte of data. For all other states it throws an exception. - /// - /// @param handler_name Name of the handler function which called this - /// method. - /// @param after_read_logic Callback function to parse the byte of data. - /// This callback function implements state specific logic. - /// - /// @throw HttpRequestParserError when invalid event occurred. - void stateWithReadHandler(const std::string& handler_name, - boost::function - after_read_logic); - /// @name State handlers. /// //@{ @@ -414,46 +240,11 @@ private: /// @brief Handler for HTTP_BODY_ST. void bodyHandler(); - /// @brief Handler for HTTP_PARSE_OK_ST and HTTP_PARSE_FAILED_ST. - /// - /// If parsing is successful, it calls @ref HttpRequest::create to validate - /// the HTTP request. In both cases it transitions the parser to the END_ST. - void parseEndedHandler(); - - /// @brief Tries to read next byte from buffer. - /// - /// @param [out] next A reference to the variable where read data should be - /// stored. - /// - /// @return true if character was successfully read, false otherwise. - bool popNextFromBuffer(char& next); - - /// @brief Checks if specified value is a character. - /// - /// @return true, if specified value is a character. - bool isChar(const char c) const; - - /// @brief Checks if specified value is a control value. - /// - /// @return true, if specified value is a control value. - bool isCtl(const char c) const; - - /// @brief Checks if specified value is a special character. - /// - /// @return true, if specified value is a special character. - bool isSpecial(const char c) const; - - /// @brief Internal buffer from which parser reads data. - std::list buffer_; - /// @brief Reference to the request object specified in the constructor. HttpRequest& request_; /// @brief Pointer to the internal context of the @ref HttpRequest object. HttpRequestContextPtr context_; - - /// @brief Error message set by @ref onModelFailure. - std::string error_message_; }; } // namespace http