//
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//

#ifndef BOOST_BEAST_HTTP_IMPL_BASIC_PARSER_IPP
#define BOOST_BEAST_HTTP_IMPL_BASIC_PARSER_IPP

#include <boost/beast/http/basic_parser.hpp>
#include <boost/beast/http/error.hpp>
#include <boost/beast/http/rfc7230.hpp>
#include <boost/beast/core/buffer_traits.hpp>
#include <boost/beast/core/detail/clamp.hpp>
#include <boost/beast/core/detail/config.hpp>
#include <boost/beast/core/detail/string.hpp>
#include <boost/asio/buffer.hpp>
#include <algorithm>
#include <utility>

namespace boost {
namespace beast {
namespace http {

template<bool isRequest>
bool
basic_parser<isRequest>::
keep_alive() const
{
    BOOST_ASSERT(is_header_done());
    if(f_ & flagHTTP11)
    {
        if(f_ & flagConnectionClose)
            return false;
    }
    else
    {
        if(! (f_ & flagConnectionKeepAlive))
            return false;
    }
    return (f_ & flagNeedEOF) == 0;
}

template<bool isRequest>
boost::optional<std::uint64_t>
basic_parser<isRequest>::
content_length() const
{
    BOOST_ASSERT(is_header_done());
    return content_length_unchecked();
}

template<bool isRequest>
boost::optional<std::uint64_t>
basic_parser<isRequest>::
content_length_remaining() const
{
    BOOST_ASSERT(is_header_done());
    if(! (f_ & flagContentLength))
        return boost::none;
    return len_;
}

template<bool isRequest>
void
basic_parser<isRequest>::
skip(bool v)
{
    BOOST_ASSERT(! got_some());
    if(v)
        f_ |= flagSkipBody;
    else
        f_ &= ~flagSkipBody;
}

template<bool isRequest>
std::size_t
basic_parser<isRequest>::
put(net::const_buffer buffer,
    error_code& ec)
{
    // If this goes off you have tried to parse more data after the parser
    // has completed. A common cause of this is re-using a parser, which is
    // not supported. If you need to re-use a parser, consider storing it
    // in an optional. Then reset() and emplace() prior to parsing each new
    // message.
    BOOST_ASSERT(!is_done());
    if (is_done())
    {
        BOOST_BEAST_ASSIGN_EC(ec, error::stale_parser);
        return 0;
    }
    auto p = static_cast<char const*>(buffer.data());
    auto n = buffer.size();
    auto const p0 = p;
    auto const p1 = p0 + n;
    ec = {};
loop:
    switch(state_)
    {
    case state::nothing_yet:
        if(n == 0)
        {
            BOOST_BEAST_ASSIGN_EC(ec, error::need_more);
            return 0;
        }
        state_ = state::start_line;
        BOOST_FALLTHROUGH;

    case state::start_line:
    {
        maybe_need_more(p, n, ec);
        if(ec)
            goto done;
        parse_start_line(p, p + (std::min<std::size_t>)(
            header_limit_, n), ec, is_request{});
        if(ec)
        {
            if(ec == error::need_more)
            {
                if(n >= header_limit_)
                {
                    BOOST_BEAST_ASSIGN_EC(ec, error::header_limit);
                    goto done;
                }
                if(p + 3 <= p1)
                    skip_ = static_cast<
                        std::size_t>(p1 - p - 3);
            }
            goto done;
        }
        BOOST_ASSERT(! is_done());
        n = static_cast<std::size_t>(p1 - p);
        if(p >= p1)
        {
            BOOST_BEAST_ASSIGN_EC(ec, error::need_more);
            goto done;
        }
        BOOST_FALLTHROUGH;
    }

    case state::fields:
        maybe_need_more(p, n, ec);
        if(ec)
            goto done;
        parse_fields(p, p + (std::min<std::size_t>)(
            header_limit_, n), ec);
        if(ec)
        {
            if(ec == error::need_more)
            {
                if(n >= header_limit_)
                {
                    BOOST_BEAST_ASSIGN_EC(ec, error::header_limit);
                    goto done;
                }
                if(p + 3 <= p1)
                    skip_ = static_cast<
                        std::size_t>(p1 - p - 3);
            }
            goto done;
        }
        finish_header(ec, is_request{});
        if(ec)
            goto done;
        break;

    case state::body0:
        BOOST_ASSERT(! skip_);
        this->on_body_init_impl(content_length(), ec);
        if(ec)
            goto done;
        state_ = state::body;
        BOOST_FALLTHROUGH;

    case state::body:
        BOOST_ASSERT(! skip_);
        parse_body(p, n, ec);
        if(ec)
            goto done;
        break;

    case state::body_to_eof0:
        BOOST_ASSERT(! skip_);
        this->on_body_init_impl(content_length(), ec);
        if(ec)
            goto done;
        state_ = state::body_to_eof;
        BOOST_FALLTHROUGH;

    case state::body_to_eof:
        BOOST_ASSERT(! skip_);
        parse_body_to_eof(p, n, ec);
        if(ec)
            goto done;
        break;

    case state::chunk_header0:
        this->on_body_init_impl(content_length(), ec);
        if(ec)
            goto done;
        state_ = state::chunk_header;
        BOOST_FALLTHROUGH;

    case state::chunk_header:
        parse_chunk_header(p, n, ec);
        if(ec)
            goto done;
        break;

    case state::chunk_body:
        parse_chunk_body(p, n, ec);
        if(ec)
            goto done;
        break;

    case state::complete:
        ec = {};
        goto done;
    }
    if(p < p1 && ! is_done() && eager())
    {
        n = static_cast<std::size_t>(p1 - p);
        goto loop;
    }
done:
    return static_cast<std::size_t>(p - p0);
}

template<bool isRequest>
void
basic_parser<isRequest>::
put_eof(error_code& ec)
{
    BOOST_ASSERT(got_some());
    if( state_ == state::start_line ||
        state_ == state::fields)
    {
        BOOST_BEAST_ASSIGN_EC(ec, error::partial_message);
        return;
    }
    if(f_ & (flagContentLength | flagChunked))
    {
        if(state_ != state::complete)
        {
            BOOST_BEAST_ASSIGN_EC(ec, error::partial_message);
            return;
        }
        ec = {};
        return;
    }
    ec = {};
    this->on_finish_impl(ec);
    if(ec)
        return;
    state_ = state::complete;
}

template<bool isRequest>
void
basic_parser<isRequest>::
maybe_need_more(
    char const* p, std::size_t n,
        error_code& ec)
{
    if(skip_ == 0)
        return;
    if( n > header_limit_)
        n = header_limit_;
    if(n < skip_ + 4)
    {
        BOOST_BEAST_ASSIGN_EC(ec, error::need_more);
        return;
    }
    auto const term =
        find_eom(p + skip_, p + n);
    if(! term)
    {
        skip_ = n - 3;
        if(skip_ + 4 > header_limit_)
        {
            BOOST_BEAST_ASSIGN_EC(ec, error::header_limit);
            return;
        }
        BOOST_BEAST_ASSIGN_EC(ec, error::need_more);
        return;
    }
    skip_ = 0;
}

template<bool isRequest>
void
basic_parser<isRequest>::
parse_start_line(
    char const*& in, char const* last,
    error_code& ec, std::true_type)
{
/*
    request-line   = method SP request-target SP HTTP-version CRLF
    method         = token
*/
    auto p = in;

    string_view method;
    parse_method(p, last, method, ec);
    if(ec)
        return;

    string_view target;
    parse_target(p, last, target, ec);
    if(ec)
        return;

    int version = 0;
    parse_version(p, last, version, ec);
    if(ec)
        return;
    if(version < 10 || version > 11)
    {
        BOOST_BEAST_ASSIGN_EC(ec, error::bad_version);
        return;
    }

    if(p + 2 > last)
    {
        BOOST_BEAST_ASSIGN_EC(ec, error::need_more);
        return;
    }
    if(p[0] != '\r' || p[1] != '\n')
    {
        BOOST_BEAST_ASSIGN_EC(ec, error::bad_version);
        return;
    }
    p += 2;

    if(version >= 11)
        f_ |= flagHTTP11;

    this->on_request_impl(string_to_verb(method),
        method, target, version, ec);
    if(ec)
        return;

    in = p;
    state_ = state::fields;
}

template<bool isRequest>
void
basic_parser<isRequest>::
parse_start_line(
    char const*& in, char const* last,
    error_code& ec, std::false_type)
{
/*
     status-line    = HTTP-version SP status-code SP reason-phrase CRLF
     status-code    = 3*DIGIT
     reason-phrase  = *( HTAB / SP / VCHAR / obs-text )
*/
    auto p = in;

    int version = 0;
    parse_version(p, last, version, ec);
    if(ec)
        return;
    if(version < 10 || version > 11)
    {
        BOOST_BEAST_ASSIGN_EC(ec, error::bad_version);
        return;
    }

    // SP
    if(p + 1 > last)
    {
        BOOST_BEAST_ASSIGN_EC(ec, error::need_more);
        return;
    }
    if(*p++ != ' ')
    {
        BOOST_BEAST_ASSIGN_EC(ec, error::bad_version);
        return;
    }

    parse_status(p, last, status_, ec);
    if(ec)
        return;

    // parse reason CRLF
    string_view reason;
    parse_reason(p, last, reason, ec);
    if(ec)
        return;

    if(version >= 11)
        f_ |= flagHTTP11;

    this->on_response_impl(
        status_, reason, version, ec);
    if(ec)
        return;

    in = p;
    state_ = state::fields;
}

template<bool isRequest>
void
basic_parser<isRequest>::
parse_fields(char const*& in,
    char const* last, error_code& ec)
{
    string_view name;
    string_view value;
    // https://stackoverflow.com/questions/686217/maximum-on-http-header-values
    beast::detail::char_buffer<max_obs_fold> buf;
    auto p = in;
    for(;;)
    {
        if(p + 2 > last)
        {
            BOOST_BEAST_ASSIGN_EC(ec, error::need_more);
            return;
        }
        if(p[0] == '\r')
        {
            if(p[1] != '\n')
            {
                BOOST_BEAST_ASSIGN_EC(ec, error::bad_line_ending);
            }
            in = p + 2;
            return;
        }
        parse_field(p, last, name, value, buf, ec);
        if(ec)
            return;
        auto const f = string_to_field(name);
        do_field(f, value, ec);
        if(ec)
            return;
        this->on_field_impl(f, name, value, ec);
        if(ec)
            return;
        in = p;
    }
}

template<bool isRequest>
void
basic_parser<isRequest>::
finish_header(error_code& ec, std::true_type)
{
    // RFC 7230 section 3.3
    // https://tools.ietf.org/html/rfc7230#section-3.3

    if(f_ & flagSkipBody)
    {
        state_ = state::complete;
    }
    else if(f_ & flagContentLength)
    {
        if(body_limit_.has_value() &&
           len_ > body_limit_)
        {
            BOOST_BEAST_ASSIGN_EC(ec, error::body_limit);
            return;
        }
        if(len_ > 0)
        {
            f_ |= flagHasBody;
            state_ = state::body0;
        }
        else
        {
            state_ = state::complete;
        }
    }
    else if(f_ & flagChunked)
    {
        f_ |= flagHasBody;
        state_ = state::chunk_header0;
    }
    else
    {
        len_ = 0;
        len0_ = 0;
        state_ = state::complete;
    }

    ec = {};
    this->on_header_impl(ec);
    if(ec)
        return;
    if(state_ == state::complete)
    {
        this->on_finish_impl(ec);
        if(ec)
            return;
    }
}

template<bool isRequest>
void
basic_parser<isRequest>::
finish_header(error_code& ec, std::false_type)
{
    // RFC 7230 section 3.3
    // https://tools.ietf.org/html/rfc7230#section-3.3

    if( (f_ & flagSkipBody) ||  // e.g. response to a HEAD request
        status_  / 100 == 1 ||   // 1xx e.g. Continue
        status_ == 204 ||        // No Content
        status_ == 304)          // Not Modified
    {
        // VFALCO Content-Length may be present, but we
        //        treat the message as not having a body.
        //        https://github.com/boostorg/beast/issues/692
        state_ = state::complete;
    }
    else if(f_ & flagContentLength)
    {
        if(len_ > 0)
        {
            f_ |= flagHasBody;
            state_ = state::body0;

            if(body_limit_.has_value() &&
               len_ > body_limit_)
            {
                BOOST_BEAST_ASSIGN_EC(ec, error::body_limit);
                return;
            }
        }
        else
        {
            state_ = state::complete;
        }
    }
    else if(f_ & flagChunked)
    {
        f_ |= flagHasBody;
        state_ = state::chunk_header0;
    }
    else
    {
        f_ |= flagHasBody;
        f_ |= flagNeedEOF;
        state_ = state::body_to_eof0;
    }

    ec = {};
    this->on_header_impl(ec);
    if(ec)
        return;
    if(state_ == state::complete)
    {
        this->on_finish_impl(ec);
        if(ec)
            return;
    }
}

template<bool isRequest>
void
basic_parser<isRequest>::
parse_body(char const*& p,
    std::size_t n, error_code& ec)
{
    ec = {};
    n = this->on_body_impl(string_view{p,
        beast::detail::clamp(len_, n)}, ec);
    p += n;
    len_ -= n;
    if(ec)
        return;
    if(len_ > 0)
        return;
    this->on_finish_impl(ec);
    if(ec)
        return;
    state_ = state::complete;
}

template<bool isRequest>
void
basic_parser<isRequest>::
parse_body_to_eof(char const*& p,
    std::size_t n, error_code& ec)
{
    if(body_limit_.has_value())
    {
        if (n > *body_limit_)
        {
            BOOST_BEAST_ASSIGN_EC(ec, error::body_limit);
            return;
        }
        *body_limit_ -= n;
    }
    ec = {};
    n = this->on_body_impl(string_view{p, n}, ec);
    p += n;
    if(ec)
        return;
}

template<bool isRequest>
void
basic_parser<isRequest>::
parse_chunk_header(char const*& p0,
    std::size_t n, error_code& ec)
{
/*
    chunked-body   = *chunk last-chunk trailer-part CRLF

    chunk          = chunk-size [ chunk-ext ] CRLF chunk-data CRLF
    last-chunk     = 1*("0") [ chunk-ext ] CRLF
    trailer-part   = *( header-field CRLF )

    chunk-size     = 1*HEXDIG
    chunk-data     = 1*OCTET ; a sequence of chunk-size octets
    chunk-ext      = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
    chunk-ext-name = token
    chunk-ext-val  = token / quoted-string
*/

    auto p = p0;
    auto const pend = p + n;
    char const* eol;

    if(! (f_ & flagFinalChunk))
    {
        if(n < skip_ + 2)
        {
            BOOST_BEAST_ASSIGN_EC(ec, error::need_more);
            return;
        }
        if(f_ & flagExpectCRLF)
        {
            // Treat the last CRLF in a chunk as
            // part of the next chunk, so p can
            // be parsed in one call instead of two.
            if(! parse_crlf(p))
            {
                BOOST_BEAST_ASSIGN_EC(ec, error::bad_chunk);
                return;
            }
        }
        eol = find_eol(p0 + skip_, pend, ec);
        if(ec)
            return;
        if(! eol)
        {
            BOOST_BEAST_ASSIGN_EC(ec, error::need_more);
            skip_ = n - 1;
            return;
        }
        skip_ = static_cast<
            std::size_t>(eol - 2 - p0);

        std::uint64_t size;
        if(! parse_hex(p, size))
        {
            BOOST_BEAST_ASSIGN_EC(ec, error::bad_chunk);
            return;
        }
        if(size != 0)
        {
            if (body_limit_.has_value())
            {
                if (size > *body_limit_)
                {
                    BOOST_BEAST_ASSIGN_EC(ec, error::body_limit);
                    return;
                }
                *body_limit_ -= size;
            }
            auto const start = p;
            parse_chunk_extensions(p, pend, ec);
            if(ec)
                return;
            if(p != eol -2 )
            {
                BOOST_BEAST_ASSIGN_EC(ec, error::bad_chunk_extension);
                return;
            }
            auto const ext = make_string(start, p);
            this->on_chunk_header_impl(size, ext, ec);
            if(ec)
                return;
            len_ = size;
            skip_ = 2;
            p0 = eol;
            f_ |= flagExpectCRLF;
            state_ = state::chunk_body;
            return;
        }

        f_ |= flagFinalChunk;
    }
    else
    {
        BOOST_ASSERT(n >= 5);
        if(f_ & flagExpectCRLF)
            BOOST_VERIFY(parse_crlf(p));
        std::uint64_t size;
        BOOST_VERIFY(parse_hex(p, size));
        eol = find_eol(p, pend, ec);
        BOOST_ASSERT(! ec);
    }

    auto eom = find_eom(p0 + skip_, pend);
    if(! eom)
    {
        BOOST_ASSERT(n >= 3);
        skip_ = n - 3;
        BOOST_BEAST_ASSIGN_EC(ec, error::need_more);
        return;
    }

    auto const start = p;
    parse_chunk_extensions(p, pend, ec);
    if(ec)
        return;
    if(p != eol - 2)
    {
        BOOST_BEAST_ASSIGN_EC(ec, error::bad_chunk_extension);
        return;
    }
    auto const ext = make_string(start, p);
    this->on_chunk_header_impl(0, ext, ec);
    if(ec)
        return;
    p = eol;
    parse_fields(p, eom, ec);
    if(ec)
        return;
    BOOST_ASSERT(p == eom);
    p0 = eom;

    this->on_finish_impl(ec);
    if(ec)
        return;
    state_ = state::complete;
}

template<bool isRequest>
void
basic_parser<isRequest>::
parse_chunk_body(char const*& p,
    std::size_t n, error_code& ec)
{
    ec = {};
    n = this->on_chunk_body_impl(
        len_, string_view{p,
            beast::detail::clamp(len_, n)}, ec);
    p += n;
    len_ -= n;
    if(len_ == 0)
        state_ = state::chunk_header;
}

template<bool isRequest>
void
basic_parser<isRequest>::
do_field(field f,
    string_view value, error_code& ec)
{
    using namespace beast::detail::string_literals;
    // Connection
    if(f == field::connection ||
        f == field::proxy_connection)
    {
        auto const list = opt_token_list{value};
        if(! validate_list(list))
        {
            // VFALCO Should this be a field specific error?
            BOOST_BEAST_ASSIGN_EC(ec, error::bad_value);
            return;
        }
        for(auto const& s : list)
        {
            if(beast::iequals("close"_sv, s))
            {
                f_ |= flagConnectionClose;
                continue;
            }

            if(beast::iequals("keep-alive"_sv, s))
            {
                f_ |= flagConnectionKeepAlive;
                continue;
            }

            if(beast::iequals("upgrade"_sv, s))
            {
                f_ |= flagConnectionUpgrade;
                continue;
            }
        }
        ec = {};
        return;
    }

    // Content-Length
    if(f == field::content_length)
    {
        auto bad_content_length = [&ec]
        {
            BOOST_BEAST_ASSIGN_EC(ec, error::bad_content_length);
        };

        auto multiple_content_length = [&ec]
        {
            BOOST_BEAST_ASSIGN_EC(ec, error::multiple_content_length);
        };

        // conflicting field
        if(f_ & flagChunked)
            return bad_content_length();

        // Content-length may be a comma-separated list of integers
        auto tokens_unprocessed = 1 +
            std::count(value.begin(), value.end(), ',');
        auto tokens = opt_token_list(value);
        if (tokens.begin() == tokens.end() ||
            !validate_list(tokens))
                return bad_content_length();

        auto existing = this->content_length_unchecked();
        for (auto tok : tokens)
        {
            std::uint64_t v;
            if (!parse_dec(tok, v))
                return bad_content_length();
            --tokens_unprocessed;
            if (existing.has_value())
            {
                if (v != *existing)
                    return multiple_content_length();
            }
            else
            {
                existing = v;
            }
        }

        if (tokens_unprocessed)
            return bad_content_length();

        BOOST_ASSERT(existing.has_value());
        ec = {};
        len_ = *existing;
        len0_ = *existing;
        f_ |= flagContentLength;
        return;
    }

    // Transfer-Encoding
    if(f == field::transfer_encoding)
    {
        if(f_ & flagChunked)
        {
            // duplicate
            BOOST_BEAST_ASSIGN_EC(ec, error::bad_transfer_encoding);
            return;
        }

        if(f_ & flagContentLength)
        {
            // conflicting field
            BOOST_BEAST_ASSIGN_EC(ec, error::bad_transfer_encoding);
            return;
        }

        ec = {};
        auto const v = token_list{value};
        auto const p = std::find_if(v.begin(), v.end(),
            [&](string_view const& s)
            {
                return beast::iequals("chunked"_sv, s);
            });
        if(p == v.end())
            return;
        if(std::next(p) != v.end())
            return;
        len_ = 0;
        f_ |= flagChunked;
        return;
    }

    // Upgrade
    if(f == field::upgrade)
    {
        ec = {};
        f_ |= flagUpgrade;
        return;
    }

    ec = {};
}

#ifdef BOOST_BEAST_SOURCE
template class http::basic_parser<true>;
template class http::basic_parser<false>;
#endif

} // http
} // beast
} // boost

#endif
