// // 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 #include #include #include #include #include #include #include #include #include namespace boost { namespace beast { namespace http { template bool basic_parser:: 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 boost::optional basic_parser:: content_length() const { BOOST_ASSERT(is_header_done()); if(! (f_ & flagContentLength)) return boost::none; return len0_; } template boost::optional basic_parser:: content_length_remaining() const { BOOST_ASSERT(is_header_done()); if(! (f_ & flagContentLength)) return boost::none; return len_; } template void basic_parser:: skip(bool v) { BOOST_ASSERT(! got_some()); if(v) f_ |= flagSkipBody; else f_ &= ~flagSkipBody; } template std::size_t basic_parser:: put(net::const_buffer buffer, error_code& ec) { BOOST_ASSERT(state_ != state::complete); auto p = static_cast(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) { 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)( header_limit_, n), ec, is_request{}); if(ec) { if(ec == error::need_more) { if(n >= header_limit_) { 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(p1 - p); if(p >= p1) { 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)( header_limit_, n), ec); if(ec) { if(ec == error::need_more) { if(n >= header_limit_) { 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{}); 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(p1 - p); goto loop; } done: return static_cast(p - p0); } template void basic_parser:: put_eof(error_code& ec) { BOOST_ASSERT(got_some()); if( state_ == state::start_line || state_ == state::fields) { ec = error::partial_message; return; } if(f_ & (flagContentLength | flagChunked)) { if(state_ != state::complete) { ec = error::partial_message; return; } ec = {}; return; } ec = {}; this->on_finish_impl(ec); if(ec) return; state_ = state::complete; } template void basic_parser:: 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) { ec = error::need_more; return; } auto const term = find_eom(p + skip_, p + n); if(! term) { skip_ = n - 3; if(skip_ + 4 > header_limit_) { ec = error::header_limit; return; } ec = error::need_more; return; } skip_ = 0; } template void basic_parser:: 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) { ec = error::bad_version; return; } if(p + 2 > last) { ec = error::need_more; return; } if(p[0] != '\r' || p[1] != '\n') { 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 void basic_parser:: 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) { ec = error::bad_version; return; } // SP if(p + 1 > last) { ec = error::need_more; return; } if(*p++ != ' ') { 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 void basic_parser:: 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 buf; auto p = in; for(;;) { if(p + 2 > last) { ec = error::need_more; return; } if(p[0] == '\r') { if(p[1] != '\n') 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 void basic_parser:: 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(len_ > body_limit_) { 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 void basic_parser:: 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(len_ > body_limit_) { 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 void basic_parser:: 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 void basic_parser:: parse_body_to_eof(char const*& p, std::size_t n, error_code& ec) { if(n > body_limit_) { ec = error::body_limit; return; } body_limit_ = body_limit_ - n; ec = {}; n = this->on_body_impl(string_view{p, n}, ec); p += n; if(ec) return; } template void basic_parser:: 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) { 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)) { ec = error::bad_chunk; return; } } eol = find_eol(p0 + skip_, pend, ec); if(ec) return; if(! eol) { 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)) { ec = error::bad_chunk; return; } if(size != 0) { if(size > body_limit_) { 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 ) { 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; ec = error::need_more; return; } auto const start = p; parse_chunk_extensions(p, pend, ec); if(ec) return; if(p != eol - 2) { 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 void basic_parser:: 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 void basic_parser:: 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? 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) { if(f_ & flagContentLength) { // duplicate ec = error::bad_content_length; return; } if(f_ & flagChunked) { // conflicting field ec = error::bad_content_length; return; } std::uint64_t v; if(! parse_dec(value, v)) { ec = error::bad_content_length; return; } ec = {}; len_ = v; len0_ = v; f_ |= flagContentLength; return; } // Transfer-Encoding if(f == field::transfer_encoding) { if(f_ & flagChunked) { // duplicate ec = error::bad_transfer_encoding; return; } if(f_ & flagContentLength) { // conflicting field 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; template class http::basic_parser; #endif } // http } // beast } // boost #endif