]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5451] Extracted base class from HttpRequestParser.
authorMarcin Siodelski <marcin@isc.org>
Tue, 19 Dec 2017 15:40:07 +0000 (16:40 +0100)
committerMarcin Siodelski <marcin@isc.org>
Tue, 19 Dec 2017 15:40:07 +0000 (16:40 +0100)
src/lib/http/Makefile.am
src/lib/http/http_message_parser_base.cc [new file with mode: 0644]
src/lib/http/http_message_parser_base.h [new file with mode: 0644]
src/lib/http/request_parser.cc
src/lib/http/request_parser.h

index 13cca2f3152ce6677be312b1c080f0745ab041ba..42937507f562f1cc41b14d7d412e16e7b8f8aa44 100644 (file)
@@ -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 (file)
index 0000000..25178ec
--- /dev/null
@@ -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 <http/http_message_parser_base.h>
+#include <boost/bind.hpp>
+
+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<const char*>(buf),
+                       static_cast<const char*>(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<void(const char c)>
+                                            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<int>(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 (file)
index 0000000..e909e8f
--- /dev/null
@@ -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 <exceptions/exceptions.h>
+#include <http/http_message.h>
+#include <util/state_model.h>
+#include <boost/function.hpp>
+#include <list>
+#include <string>
+
+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<void(const char c)>
+                              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<char> buffer_;
+
+    /// @brief Error message set by @ref onModelFailure.
+    std::string error_message_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
index f77a9eef4bb1b358a938dd355e7b3667e8ca53cf..dd5e095d7622c67163ce1aa2c9af80619c99d315 100644 (file)
@@ -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<const char*>(buf),
-                       static_cast<const char*>(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<int>(event)));
 }
 
-void
-HttpRequestParser::stateWithReadHandler(const std::string& handler_name,
-                                        boost::function<void(const char c)>
-                                        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
index 0cbce1e4bebafe3a0653e740ad29cec206a1b018..f65572de5ac8f53cd8d964b661ccd962c81603c0 100644 (file)
@@ -7,14 +7,9 @@
 #ifndef HTTP_REQUEST_PARSER_H
 #define HTTP_REQUEST_PARSER_H
 
-#include <exceptions/exceptions.h>
+#include <http/http_message_parser_base.h>
 #include <http/request.h>
-#include <util/state_model.h>
-#include <boost/function.hpp>
 #include <boost/shared_ptr.hpp>
-#include <list>
-#include <stdint.h>
-#include <string>
 
 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<HttpRequestParser> 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<HttpRequestParser> 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<void(const char c)>
-                              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<char> 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