// Copyright (C) 2016-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/request_parser.h>
#include <functional>
#include <iostream>

using namespace isc::util;

namespace isc {
namespace http {

const int HttpRequestParser::RECEIVE_START_ST;
const int HttpRequestParser::HTTP_METHOD_ST;
const int HttpRequestParser::HTTP_URI_ST;
const int HttpRequestParser::HTTP_VERSION_H_ST;
const int HttpRequestParser::HTTP_VERSION_T1_ST;
const int HttpRequestParser::HTTP_VERSION_T2_ST;
const int HttpRequestParser::HTTP_VERSION_P_ST;
const int HttpRequestParser::HTTP_VERSION_SLASH_ST;
const int HttpRequestParser::HTTP_VERSION_MAJOR_START_ST;
const int HttpRequestParser::HTTP_VERSION_MAJOR_ST;
const int HttpRequestParser::HTTP_VERSION_MINOR_START_ST;
const int HttpRequestParser::HTTP_VERSION_MINOR_ST;
const int HttpRequestParser::EXPECTING_NEW_LINE1_ST;
const int HttpRequestParser::HEADER_LINE_START_ST;
const int HttpRequestParser::HEADER_LWS_ST;
const int HttpRequestParser::HEADER_NAME_ST;
const int HttpRequestParser::SPACE_BEFORE_HEADER_VALUE_ST;
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;

HttpRequestParser::HttpRequestParser(HttpRequest& request)
    : HttpMessageParserBase(request), request_(request),
      context_(request_.context()) {
}

void
HttpRequestParser::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
HttpRequestParser::defineStates() {
    // Call parent class implementation first.
    HttpMessageParserBase::defineStates();

    // Define HTTP parser specific states.
    defineState(RECEIVE_START_ST, "RECEIVE_START_ST",
                std::bind(&HttpRequestParser::receiveStartHandler, this));

    defineState(HTTP_METHOD_ST, "HTTP_METHOD_ST",
                std::bind(&HttpRequestParser::httpMethodHandler, this));

    defineState(HTTP_URI_ST, "HTTP_URI_ST",
                std::bind(&HttpRequestParser::uriHandler, this));

    defineState(HTTP_VERSION_H_ST, "HTTP_VERSION_H_ST",
                std::bind(&HttpRequestParser::versionHTTPHandler, this, 'H',
                          HTTP_VERSION_T1_ST));

    defineState(HTTP_VERSION_T1_ST, "HTTP_VERSION_T1_ST",
                std::bind(&HttpRequestParser::versionHTTPHandler, this, 'T',
                          HTTP_VERSION_T2_ST));

    defineState(HTTP_VERSION_T2_ST, "HTTP_VERSION_T2_ST",
                std::bind(&HttpRequestParser::versionHTTPHandler, this, 'T',
                          HTTP_VERSION_P_ST));

    defineState(HTTP_VERSION_P_ST, "HTTP_VERSION_P_ST",
                std::bind(&HttpRequestParser::versionHTTPHandler, this, 'P',
                          HTTP_VERSION_SLASH_ST));

    defineState(HTTP_VERSION_SLASH_ST, "HTTP_VERSION_SLASH_ST",
                std::bind(&HttpRequestParser::versionHTTPHandler, this, '/',
                          HTTP_VERSION_MAJOR_ST));

    defineState(HTTP_VERSION_MAJOR_START_ST, "HTTP_VERSION_MAJOR_START_ST",
                std::bind(&HttpRequestParser::versionNumberStartHandler, this,
                          HTTP_VERSION_MAJOR_ST,
                          &context_->http_version_major_));

    defineState(HTTP_VERSION_MAJOR_ST, "HTTP_VERSION_MAJOR_ST",
                std::bind(&HttpRequestParser::versionNumberHandler, this,
                          '.', HTTP_VERSION_MINOR_START_ST,
                          &context_->http_version_major_));

    defineState(HTTP_VERSION_MINOR_START_ST, "HTTP_VERSION_MINOR_START_ST",
                std::bind(&HttpRequestParser::versionNumberStartHandler, this,
                          HTTP_VERSION_MINOR_ST,
                          &context_->http_version_minor_));

    defineState(HTTP_VERSION_MINOR_ST, "HTTP_VERSION_MINOR_ST",
                std::bind(&HttpRequestParser::versionNumberHandler, this,
                          '\r', EXPECTING_NEW_LINE1_ST,
                          &context_->http_version_minor_));

    defineState(EXPECTING_NEW_LINE1_ST, "EXPECTING_NEW_LINE1_ST",
                std::bind(&HttpRequestParser::expectingNewLineHandler, this,
                          HEADER_LINE_START_ST));

    defineState(HEADER_LINE_START_ST, "HEADER_LINE_START_ST",
                std::bind(&HttpRequestParser::headerLineStartHandler, this));

    defineState(HEADER_LWS_ST, "HEADER_LWS_ST",
                std::bind(&HttpRequestParser::headerLwsHandler, this));

    defineState(HEADER_NAME_ST, "HEADER_NAME_ST",
                std::bind(&HttpRequestParser::headerNameHandler, this));

    defineState(SPACE_BEFORE_HEADER_VALUE_ST, "SPACE_BEFORE_HEADER_VALUE_ST",
                std::bind(&HttpRequestParser::spaceBeforeHeaderValueHandler, this));

    defineState(HEADER_VALUE_ST, "HEADER_VALUE_ST",
                std::bind(&HttpRequestParser::headerValueHandler, this));

    defineState(EXPECTING_NEW_LINE2_ST, "EXPECTING_NEW_LINE2",
                std::bind(&HttpRequestParser::expectingNewLineHandler, this,
                          HEADER_LINE_START_ST));

    defineState(EXPECTING_NEW_LINE3_ST, "EXPECTING_NEW_LINE3_ST",
                std::bind(&HttpRequestParser::expectingNewLineHandler, this,
                          HTTP_PARSE_OK_ST));

    defineState(HTTP_BODY_ST, "HTTP_BODY_ST",
                std::bind(&HttpRequestParser::bodyHandler, this));
}

void
HttpRequestParser::receiveStartHandler() {
    std::string bytes;
    getNextFromBuffer(bytes);
    if (getNextEvent() != NEED_MORE_DATA_EVT) {
        switch(getNextEvent()) {
        case START_EVT:
            // The first byte should contain a first character of the
            // HTTP method name.
            if (!isChar(bytes[0]) || isCtl(bytes[0]) || isSpecial(bytes[0])) {
                parseFailure("invalid first character " + std::string(1, bytes[0]) +
                             " in HTTP method name");

            } else {
                context_->method_.push_back(bytes[0]);
                transition(HTTP_METHOD_ST, DATA_READ_OK_EVT);
            }
            break;

        default:
            invalidEventError("receiveStartHandler", getNextEvent());
        }
    }
}

void
HttpRequestParser::httpMethodHandler() {
    stateWithReadHandler("httpMethodHandler", [this](const char c) {
        // Space character terminates the HTTP method name. Next thing
        // is the URI.
        if (c == ' ') {
            transition(HTTP_URI_ST, DATA_READ_OK_EVT);

        } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
            parseFailure("invalid character " + std::string(1, c) +
                         " in HTTP method name");

        } else {
            // Still parsing the method. Append the next character to the
            // method name.
            context_->method_.push_back(c);
            transition(getCurrState(), DATA_READ_OK_EVT);
        }
    });
}

void
HttpRequestParser::uriHandler() {
    stateWithReadHandler("uriHandler", [this](const char c) {
        // Space character terminates the URI.
        if (c == ' ') {
            transition(HTTP_VERSION_H_ST, DATA_READ_OK_EVT);

        } else if (isCtl(c)) {
            parseFailure("control character found in HTTP URI");

        } else {
            // Still parsing the URI. Append the next character to the
            // method name.
            context_->uri_.push_back(c);
            transition(HTTP_URI_ST, DATA_READ_OK_EVT);
        }
    });
}

void
HttpRequestParser::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
HttpRequestParser::versionNumberStartHandler(const unsigned int next_state,
                                             unsigned int* storage) {
    stateWithReadHandler("versionNumberStartHandler",
                         [this, next_state, 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 HTTP version, found " +
                         std::string(1, c));
        }
    });
}

void
HttpRequestParser::versionNumberHandler(const char following_character,
                                        const unsigned int next_state,
                                        unsigned int* const storage) {
    stateWithReadHandler("versionNumberHandler",
                         [this, following_character, 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 HTTP version, found " +
                         std::string(1, c));
        }
    });
}

void
HttpRequestParser::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.
                request_.create();
                try {
                    // This will throw exception if there is no Content-Length.
                    uint64_t content_length =
                        request_.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 (request_.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
HttpRequestParser::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 parse letter.
            context_->headers_.push_back(HttpHeaderContext());
            context_->headers_.back().name_.push_back(c);
            transition(HEADER_NAME_ST, DATA_READ_OK_EVT);
        }
    });
}

void
HttpRequestParser::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
HttpRequestParser::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
HttpRequestParser::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
HttpRequestParser::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
HttpRequestParser::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 = request_.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);
        }
    });
}

} // namespace http
} // namespace isc
