summaryrefslogtreecommitdiffstats
path: root/src/lib/http/response_parser.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/http/response_parser.cc')
-rw-r--r--src/lib/http/response_parser.cc461
1 files changed, 461 insertions, 0 deletions
diff --git a/src/lib/http/response_parser.cc b/src/lib/http/response_parser.cc
new file mode 100644
index 0000000..b2ab797
--- /dev/null
+++ b/src/lib/http/response_parser.cc
@@ -0,0 +1,461 @@
+// Copyright (C) 2017-2020 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 <config.h>
+
+#include <http/response_parser.h>
+#include <functional>
+
+using namespace isc::util;
+
+namespace isc {
+namespace http {
+
+const int HttpResponseParser::RECEIVE_START_ST;
+const int HttpResponseParser::HTTP_VERSION_H_ST;
+const int HttpResponseParser::HTTP_VERSION_T1_ST;
+const int HttpResponseParser::HTTP_VERSION_T2_ST;
+const int HttpResponseParser::HTTP_VERSION_P_ST;
+const int HttpResponseParser::HTTP_VERSION_SLASH_ST;
+const int HttpResponseParser::HTTP_VERSION_MAJOR_START_ST;
+const int HttpResponseParser::HTTP_VERSION_MAJOR_ST;
+const int HttpResponseParser::HTTP_VERSION_MINOR_START_ST;
+const int HttpResponseParser::HTTP_VERSION_MINOR_ST;
+const int HttpResponseParser::HTTP_STATUS_CODE_START_ST;
+const int HttpResponseParser::HTTP_STATUS_CODE_ST;
+const int HttpResponseParser::HTTP_PHRASE_START_ST;
+const int HttpResponseParser::HTTP_PHRASE_ST;
+const int HttpResponseParser::EXPECTING_NEW_LINE1_ST;
+const int HttpResponseParser::HEADER_LINE_START_ST;
+const int HttpResponseParser::HEADER_LWS_ST;
+const int HttpResponseParser::HEADER_NAME_ST;
+const int HttpResponseParser::SPACE_BEFORE_HEADER_VALUE_ST;
+const int HttpResponseParser::HEADER_VALUE_ST;
+const int HttpResponseParser::EXPECTING_NEW_LINE2_ST;
+const int HttpResponseParser::EXPECTING_NEW_LINE3_ST;
+const int HttpResponseParser::HTTP_BODY_ST;
+
+HttpResponseParser::HttpResponseParser(HttpResponse& response)
+ : HttpMessageParserBase(response), response_(response),
+ context_(response.context()) {
+}
+
+void
+HttpResponseParser::initModel() {
+ // Initialize dictionaries of events and states.
+ initDictionaries();
+
+ // Set the current state to starting state and enter the run loop.
+ setState(RECEIVE_START_ST);
+
+ // Parsing starts from here.
+ postNextEvent(START_EVT);
+}
+
+void
+HttpResponseParser::defineStates() {
+ // Call parent class implementation first.
+ HttpMessageParserBase::defineStates();
+
+ // Define HTTP parser specific states.
+ defineState(RECEIVE_START_ST, "RECEIVE_START_ST",
+ std::bind(&HttpResponseParser::receiveStartHandler, this));
+
+ defineState(HTTP_VERSION_T1_ST, "HTTP_VERSION_T1_ST",
+ std::bind(&HttpResponseParser::versionHTTPHandler, this, 'T',
+ HTTP_VERSION_T2_ST));
+
+ defineState(HTTP_VERSION_T2_ST, "HTTP_VERSION_T2_ST",
+ std::bind(&HttpResponseParser::versionHTTPHandler, this, 'T',
+ HTTP_VERSION_P_ST));
+
+ defineState(HTTP_VERSION_P_ST, "HTTP_VERSION_P_ST",
+ std::bind(&HttpResponseParser::versionHTTPHandler, this, 'P',
+ HTTP_VERSION_SLASH_ST));
+
+ defineState(HTTP_VERSION_SLASH_ST, "HTTP_VERSION_SLASH_ST",
+ std::bind(&HttpResponseParser::versionHTTPHandler, this, '/',
+ HTTP_VERSION_MAJOR_ST));
+
+ defineState(HTTP_VERSION_MAJOR_START_ST, "HTTP_VERSION_MAJOR_START_ST",
+ std::bind(&HttpResponseParser::numberStartHandler, this,
+ HTTP_VERSION_MAJOR_ST,
+ "HTTP version",
+ &context_->http_version_major_));
+
+ defineState(HTTP_VERSION_MAJOR_ST, "HTTP_VERSION_MAJOR_ST",
+ std::bind(&HttpResponseParser::numberHandler, this,
+ '.', HTTP_VERSION_MINOR_START_ST,
+ "HTTP version",
+ &context_->http_version_major_));
+
+ defineState(HTTP_VERSION_MINOR_START_ST, "HTTP_VERSION_MINOR_START_ST",
+ std::bind(&HttpResponseParser::numberStartHandler, this,
+ HTTP_VERSION_MINOR_ST,
+ "HTTP version",
+ &context_->http_version_minor_));
+
+ defineState(HTTP_VERSION_MINOR_ST, "HTTP_VERSION_MINOR_ST",
+ std::bind(&HttpResponseParser::numberHandler, this,
+ ' ', HTTP_STATUS_CODE_START_ST,
+ "HTTP version",
+ &context_->http_version_minor_));
+
+ defineState(HTTP_STATUS_CODE_START_ST, "HTTP_STATUS_CODE_START_ST",
+ std::bind(&HttpResponseParser::numberStartHandler, this,
+ HTTP_STATUS_CODE_ST,
+ "HTTP status code",
+ &context_->status_code_));
+
+ defineState(HTTP_STATUS_CODE_ST, "HTTP_STATUS_CODE_ST",
+ std::bind(&HttpResponseParser::numberHandler, this,
+ ' ', HTTP_PHRASE_START_ST,
+ "HTTP status code",
+ &context_->status_code_));
+
+ defineState(HTTP_PHRASE_START_ST, "HTTP_PHRASE_START_ST",
+ std::bind(&HttpResponseParser::phraseStartHandler, this));
+
+ defineState(HTTP_PHRASE_ST, "HTTP_PHRASE_ST",
+ std::bind(&HttpResponseParser::phraseHandler, this));
+
+ defineState(EXPECTING_NEW_LINE1_ST, "EXPECTING_NEW_LINE1_ST",
+ std::bind(&HttpResponseParser::expectingNewLineHandler, this,
+ HEADER_LINE_START_ST));
+
+ defineState(HEADER_LINE_START_ST, "HEADER_LINE_START_ST",
+ std::bind(&HttpResponseParser::headerLineStartHandler, this));
+
+ defineState(HEADER_LWS_ST, "HEADER_LWS_ST",
+ std::bind(&HttpResponseParser::headerLwsHandler, this));
+
+ defineState(HEADER_NAME_ST, "HEADER_NAME_ST",
+ std::bind(&HttpResponseParser::headerNameHandler, this));
+
+ defineState(SPACE_BEFORE_HEADER_VALUE_ST, "SPACE_BEFORE_HEADER_VALUE_ST",
+ std::bind(&HttpResponseParser::spaceBeforeHeaderValueHandler, this));
+
+ defineState(HEADER_VALUE_ST, "HEADER_VALUE_ST",
+ std::bind(&HttpResponseParser::headerValueHandler, this));
+
+ defineState(EXPECTING_NEW_LINE2_ST, "EXPECTING_NEW_LINE2",
+ std::bind(&HttpResponseParser::expectingNewLineHandler, this,
+ HEADER_LINE_START_ST));
+
+ defineState(EXPECTING_NEW_LINE3_ST, "EXPECTING_NEW_LINE3_ST",
+ std::bind(&HttpResponseParser::expectingNewLineHandler, this,
+ HTTP_PARSE_OK_ST));
+
+ defineState(HTTP_BODY_ST, "HTTP_BODY_ST",
+ std::bind(&HttpResponseParser::bodyHandler, this));
+}
+
+void
+HttpResponseParser::receiveStartHandler() {
+ std::string bytes;
+ getNextFromBuffer(bytes);
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch(getNextEvent()) {
+ case START_EVT:
+ if (bytes[0] == 'H') {
+ transition(HTTP_VERSION_T1_ST, DATA_READ_OK_EVT);
+
+ } else {
+ parseFailure("unexpected first character " + std::string(1, bytes[0]) +
+ ": expected \'H\'");
+ }
+ break;
+
+ default:
+ invalidEventError("receiveStartHandler", getNextEvent());
+ }
+ }
+}
+
+void
+HttpResponseParser::versionHTTPHandler(const char expected_letter,
+ const unsigned int next_state) {
+ stateWithReadHandler("versionHTTPHandler",
+ [this, expected_letter, next_state](const char c) {
+ // We're handling one of the letters: 'H', 'T' or 'P'.
+ if (c == expected_letter) {
+ // The HTTP version is specified as "HTTP/X.Y". If the current
+ // character is a slash we're starting to parse major HTTP version
+ // number. Let's reset the version numbers.
+ if (c == '/') {
+ context_->http_version_major_ = 0;
+ context_->http_version_minor_ = 0;
+ }
+ // In all cases, let's transition to next specified state.
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else {
+ // Unexpected character found. Parsing fails.
+ parseFailure("unexpected character " + std::string(1, c) +
+ " in HTTP version string");
+ }
+ });
+}
+
+void
+HttpResponseParser::numberStartHandler(const unsigned int next_state,
+ const std::string& number_name,
+ unsigned int* storage) {
+ stateWithReadHandler("numberStartHandler",
+ [this, next_state, number_name, storage](const char c) mutable {
+ // HTTP version number must be a digit.
+ if (isdigit(c)) {
+ // Update the version number using new digit being parsed.
+ *storage = *storage * 10 + c - '0';
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else {
+ parseFailure("expected digit in " + number_name + ", found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpResponseParser::numberHandler(const char following_character,
+ const unsigned int next_state,
+ const std::string& number_name,
+ unsigned int* const storage) {
+ stateWithReadHandler("numberHandler",
+ [this, following_character, number_name, next_state, storage](const char c)
+ mutable {
+ // We're getting to the end of the version number, let's transition
+ // to next state.
+ if (c == following_character) {
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else if (isdigit(c)) {
+ // Current character is a digit, so update the version number.
+ *storage = *storage * 10 + c - '0';
+
+ } else {
+ parseFailure("expected digit in " + number_name + ", found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpResponseParser::phraseStartHandler() {
+ stateWithReadHandler("phraseStartHandler", [this](const char c) {
+ if (!isChar(c) || isCtl(c)) {
+ parseFailure("invalid first character " + std::string(1, c) +
+ " in HTTP phrase");
+ } else {
+ context_->phrase_.push_back(c);
+ transition(HTTP_PHRASE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::phraseHandler() {
+ stateWithReadHandler("phraseHandler", [this](const char c) {
+ if (c == '\r') {
+ transition(EXPECTING_NEW_LINE1_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " in HTTP phrase");
+
+ } else {
+ context_->phrase_.push_back(c);
+ transition(HTTP_PHRASE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::expectingNewLineHandler(const unsigned int next_state) {
+ stateWithReadHandler("expectingNewLineHandler", [this, next_state](const char c) {
+ // Only a new line character is allowed in this state.
+ if (c == '\n') {
+ // If next state is HTTP_PARSE_OK_ST it means that we're
+ // parsing 3rd new line in the HTTP request message. This
+ // terminates the HTTP request (if there is no body) or marks the
+ // beginning of the body.
+ if (next_state == HTTP_PARSE_OK_ST) {
+ // Whether there is a body in this message or not, we should
+ // parse the HTTP headers to validate it and to check if there
+ // is "Content-Length" specified. The "Content-Length" is
+ // required for parsing body.
+ response_.create();
+ try {
+ // This will throw exception if there is no Content-Length.
+ uint64_t content_length =
+ response_.getHeaderValueAsUint64("Content-Length");
+ if (content_length > 0) {
+ // There is body in this request, so let's parse it.
+ transition(HTTP_BODY_ST, DATA_READ_OK_EVT);
+ } else {
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ } catch (const std::exception& ex) {
+ // There is no body in this message. If the body is required
+ // parsing fails.
+ if (response_.requiresBody()) {
+ parseFailure("HTTP message lacks a body");
+
+ } else {
+ // Body not required so simply terminate parsing.
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ }
+
+ } else {
+ // This is 1st or 2nd new line, so let's transition to the
+ // next state required by this handler.
+ transition(next_state, DATA_READ_OK_EVT);
+ }
+ } else {
+ parseFailure("expecting new line after CR, found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpResponseParser::headerLineStartHandler() {
+ stateWithReadHandler("headerLineStartHandler", [this](const char c) {
+ // If we're parsing HTTP headers and we found CR it marks the
+ // end of headers section.
+ if (c == '\r') {
+ transition(EXPECTING_NEW_LINE3_ST, DATA_READ_OK_EVT);
+
+ } else if (!context_->headers_.empty() && ((c == ' ') || (c == '\t'))) {
+ // New line in headers section followed by space or tab is an LWS,
+ // a line break within header value.
+ transition(HEADER_LWS_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " in header name");
+
+ } else {
+ // Update header name with the parsed letter.
+ context_->headers_.push_back(HttpHeaderContext());
+ context_->headers_.back().name_.push_back(c);
+ transition(HEADER_NAME_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::headerLwsHandler() {
+ stateWithReadHandler("headerLwsHandler", [this](const char c) {
+ if (c == '\r') {
+ // Found CR during parsing a header value. Next value
+ // should be new line.
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if ((c == ' ') || (c == '\t')) {
+ // Space and tab is used to mark LWS. Simply swallow
+ // this character.
+ transition(getCurrState(), DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header " +
+ context_->headers_.back().name_);
+
+ } else {
+ // We're parsing header value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::headerNameHandler() {
+ stateWithReadHandler("headerNameHandler", [this](const char c) {
+ // Colon follows header name and it has its own state.
+ if (c == ':') {
+ transition(SPACE_BEFORE_HEADER_VALUE_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " found in the HTTP header name");
+
+ } else {
+ // Parsing a header name, so update it.
+ context_->headers_.back().name_.push_back(c);
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::spaceBeforeHeaderValueHandler() {
+ stateWithReadHandler("spaceBeforeHeaderValueHandler", [this](const char c) {
+ if (c == ' ') {
+ // Remove leading whitespace from the header value.
+ transition(getCurrState(), DATA_READ_OK_EVT);
+
+ } else if (c == '\r') {
+ // If CR found during parsing header value, it marks the end
+ // of this value.
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header "
+ + context_->headers_.back().name_);
+
+ } else {
+ // Still parsing the value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::headerValueHandler() {
+ stateWithReadHandler("headerValueHandler", [this](const char c) {
+ // If CR found during parsing header value, it marks the end
+ // of this value.
+ if (c == '\r') {
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header "
+ + context_->headers_.back().name_);
+
+ } else {
+ // Still parsing the value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::bodyHandler() {
+ stateWithMultiReadHandler("bodyHandler", [this](const std::string& body) {
+ // We don't validate the body at this stage. Simply record the
+ // number of characters specified within "Content-Length".
+ context_->body_ += body;
+ size_t content_length = response_.getHeaderValueAsUint64("Content-Length");
+ if (context_->body_.length() < content_length) {
+ transition(HTTP_BODY_ST, DATA_READ_OK_EVT);
+
+ } else {
+ // If there was some extraneous data, ignore it.
+ if (context_->body_.length() > content_length) {
+ context_->body_.resize(content_length);
+ }
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ });
+}
+
+
+} // end of namespace isc::http
+} // end of namespace isc