// // Copyright (c) 2013-2017 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) // #ifndef BEAST_HTTP_IMPL_BASIC_PARSER_IPP #define BEAST_HTTP_IMPL_BASIC_PARSER_IPP #include #include #include #include #include #include #include #include #include namespace beast { namespace http { template template basic_parser:: basic_parser(basic_parser< isRequest, OtherDerived>&& other) : len_(other.len_) , buf_(std::move(other.buf_)) , buf_len_(other.buf_len_) , skip_(other.skip_) , state_(other.state_) , f_(other.f_) { } template bool basic_parser:: is_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 len_; } template void basic_parser:: skip(bool v) { BOOST_ASSERT(! got_some()); if(v) f_ |= flagSkipBody; else f_ &= ~flagSkipBody; } template template std::size_t basic_parser:: put(ConstBufferSequence const& buffers, error_code& ec) { static_assert(is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); auto const buffer = maybe_flatten(buffers); return put(boost::asio::const_buffers_1{ buffer.data(), buffer.size()}, ec); } template std::size_t basic_parser:: put(boost::asio::const_buffers_1 const& buffer, error_code& ec) { BOOST_ASSERT(state_ != state::complete); using boost::asio::buffer_size; auto p = boost::asio::buffer_cast< char const*>(*buffer.begin()); auto n = buffer_size(*buffer.begin()); auto const p0 = p; auto const p1 = p0 + n; loop: switch(state_) { case state::nothing_yet: if(n == 0) { ec = error::need_more; return 0; } state_ = state::header; BOOST_FALLTHROUGH; case state::header: parse_header(p, n, ec); if(ec) goto done; break; case state::body0: impl().on_body(content_length(), ec); if(ec) goto done; state_ = state::body; BOOST_FALLTHROUGH; case state::body: parse_body(p, n, ec); if(ec) goto done; break; case state::body_to_eof0: impl().on_body(content_length(), ec); if(ec) goto done; state_ = state::body_to_eof; BOOST_FALLTHROUGH; case state::body_to_eof: parse_body_to_eof(p, n, ec); if(ec) goto done; break; case state::chunk_header0: impl().on_body(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: break; } 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::header) { ec = error::partial_message; return; } if(f_ & (flagContentLength | flagChunked)) { if(state_ != state::complete) { ec = error::partial_message; return; } return; } impl().on_complete(ec); if(ec) return; state_ = state::complete; } template template inline string_view basic_parser:: maybe_flatten( ConstBufferSequence const& buffers) { using boost::asio::buffer_cast; using boost::asio::buffer_copy; using boost::asio::buffer_size; auto const p = buffers.begin(); auto const last = buffers.end(); if(p == last) return {nullptr, 0}; if(std::next(p) == last) { // single buffer auto const b = *p; return {buffer_cast(b), buffer_size(b)}; } auto const len = buffer_size(buffers); if(len > buf_len_) { // reallocate buf_.reset(new char[len]); buf_len_ = len; } // flatten buffer_copy(boost::asio::buffer( buf_.get(), buf_len_), buffers); return {buf_.get(), len}; } template inline void basic_parser:: parse_header(char const*& p, std::size_t n, error_code& ec) { if(n < skip_ + 4) { ec = http::error::need_more; return; } auto const term = find_eom( p + skip_, p + n, ec); if(ec) return; if(! term) { skip_ = n - 3; ec = http::error::need_more; return; } skip_ = 0; parse_header(p, term, ec, std::integral_constant{}); if(ec) return; impl().on_header(ec); if(ec) return; if(state_ == state::complete) { impl().on_complete(ec); if(ec) return; } } template void basic_parser:: parse_header(char const*& p, char const* term, error_code& ec, std::true_type) { /* request-line = method SP request-target SP HTTP-version CRLF method = token */ auto const method = parse_method(p); if(method.empty()) { ec = error::bad_method; return; } if(*p++ != ' ') { ec = error::bad_method; return; } auto const target = parse_target(p); if(target.empty()) { ec = error::bad_path; return; } if(*p++ != ' ') { ec = error::bad_path; return; } auto const version = parse_version(p); if(version < 0 || ! parse_crlf(p)) { ec = error::bad_version; return; } if(version >= 11) f_ |= flagHTTP11; impl().on_request(string_to_verb(method), method, target, version, ec); if(ec) return; parse_fields(p, term, ec); if(ec) return; BOOST_ASSERT(p == term); // 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_ > 0) { f_ |= flagHasBody; state_ = state::body0; } else { state_ = state::complete; } } else if(f_ & flagChunked) { f_ |= flagHasBody; state_ = state::chunk_header0; } else { len_ = 0; state_ = state::complete; } } template void basic_parser:: parse_header(char const*& p, char const* term, 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 const version = parse_version(p); if(version < 0 || *p != ' ') { ec = error::bad_version; return; } ++p; auto const status = parse_status(p); if(status < 0 || *p != ' ') { ec = error::bad_status; return; } ++p; auto const reason = parse_reason(p); if(! parse_crlf(p)) { ec = error::bad_reason; return; } if(version >= 11) f_ |= flagHTTP11; impl().on_response( status, reason, version, ec); if(ec) return; parse_fields(p, term, ec); if(ec) return; BOOST_ASSERT(p == term); // 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 { state_ = state::complete; return; } if(f_ & flagContentLength) { if(len_ > 0) { f_ |= flagHasBody; state_ = state::body0; } else { state_ = state::complete; } } else if(f_ & flagChunked) { f_ |= flagHasBody; state_ = state::chunk_header0; } else { f_ |= flagHasBody; f_ |= flagNeedEOF; state_ = state::body_to_eof0; } } template inline void basic_parser:: parse_body(char const*& p, std::size_t n, error_code& ec) { n = beast::detail::clamp(len_, n); impl().on_data(string_view{p, n}, ec); if(ec) return; p += n; len_ -= n; if(len_ > 0) return; impl().on_complete(ec); if(ec) return; state_ = state::complete; } template inline void basic_parser:: parse_body_to_eof(char const*& p, std::size_t n, error_code& ec) { impl().on_data(string_view{p, n}, ec); if(ec) return; p += n; } 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 v; if(! parse_hex(p, v)) { ec = error::bad_chunk; return; } if(v != 0) { if(*p == ';') { // VFALCO TODO Validate extension impl().on_chunk(v, make_string( p, eol - 2), ec); if(ec) return; } else if(p != eol - 2) { ec = error::bad_chunk; return; } len_ = v; 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 v; BOOST_VERIFY(parse_hex(p, v)); eol = find_eol(p, pend, ec); BOOST_ASSERT(! ec); } auto eom = find_eom(p0 + skip_, pend, ec); if(ec) return; if(! eom) { BOOST_ASSERT(n >= 3); skip_ = n - 3; ec = error::need_more; return; } if(*p == ';') { // VFALCO TODO Validate extension impl().on_chunk(0, make_string( p, eol - 2), ec); if(ec) return; } p = eol; parse_fields(p, eom, ec); if(ec) return; BOOST_ASSERT(p == eom); p0 = eom; impl().on_complete(ec); if(ec) return; state_ = state::complete; } template inline void basic_parser:: parse_chunk_body(char const*& p, std::size_t n, error_code& ec) { n = beast::detail::clamp(len_, n); impl().on_data(string_view{p, n}, ec); if(ec) return; p += n; len_ -= n; if(len_ > 0) return; state_ = state::chunk_header; } template void basic_parser:: parse_fields(char const*& p, char const* last, error_code& ec) { /* header-field = field-name ":" OWS field-value OWS field-name = token field-value = *( field-content / obs-fold ) field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] field-vchar = VCHAR / obs-text obs-fold = CRLF 1*( SP / HTAB ) ; obsolete line folding ; see Section 3.2.4 */ for(;;) { auto term = find_eol(p, last, ec); if(ec) return; BOOST_ASSERT(term); if(p == term - 2) { p = term; break; } auto const name = parse_name(p); if(name.empty()) { ec = error::bad_field; return; } if(*p++ != ':') { ec = error::bad_field; return; } if(*term != ' ' && *term != '\t') { auto it2 = term - 2; detail::skip_ows(p, it2); detail::skip_ows_rev(it2, p); auto const f = string_to_field(name); auto const value = make_string(p, it2); do_field(f, value, ec); if(ec) return; impl().on_field(f, name, value, ec); if(ec) return; p = term; } else { // obs-fold for(;;) { auto const it2 = term - 2; detail::skip_ows(p, it2); if(p != it2) break; p = term; if(*p != ' ' && *p != '\t') break; term = find_eol(p, last, ec); if(ec) return; } std::string s; if(p != term) { s.append(p, term - 2); p = term; for(;;) { if(*p != ' ' && *p != '\t') break; s.push_back(' '); detail::skip_ows(p, term - 2); term = find_eol(p, last, ec); if(ec) return; if(p != term - 2) s.append(p, term - 2); p = term; } } auto const f = string_to_field(name); string_view const value{s.data(), s.size()}; do_field(f, value, ec); if(ec) return; impl().on_field(f, name, value, ec); if(ec) return; } } } template void basic_parser:: do_field(field f, string_view value, error_code& ec) { // 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(strieq("close", s)) { f_ |= flagConnectionClose; continue; } if(strieq("keep-alive", s)) { f_ |= flagConnectionKeepAlive; continue; } if(strieq("upgrade", s)) { f_ |= flagConnectionUpgrade; continue; } } return; } for(auto p = value.begin(); p != value.end(); ++p) { if(! is_text(*p)) { ec = error::bad_value; 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.begin(), value.end(), v)) { ec = error::bad_content_length; return; } len_ = 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; } auto const v = token_list{value}; auto const p = std::find_if(v.begin(), v.end(), [&](typename token_list::value_type const& s) { return strieq("chunked", s); }); if(p == v.end()) return; if(std::next(p) != v.end()) return; len_ = 0; f_ |= flagChunked; return; } // Upgrade if(f == field::upgrade) { f_ |= flagUpgrade; ec = {}; return; } } } // http } // beast #endif