summaryrefslogtreecommitdiff
path: root/boost/beast/http/impl/basic_parser.ipp
diff options
context:
space:
mode:
Diffstat (limited to 'boost/beast/http/impl/basic_parser.ipp')
-rw-r--r--boost/beast/http/impl/basic_parser.ipp928
1 files changed, 928 insertions, 0 deletions
diff --git a/boost/beast/http/impl/basic_parser.ipp b/boost/beast/http/impl/basic_parser.ipp
new file mode 100644
index 0000000000..355a76fb16
--- /dev/null
+++ b/boost/beast/http/impl/basic_parser.ipp
@@ -0,0 +1,928 @@
+//
+// Copyright (c) 2016-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)
+//
+// 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/core/static_string.hpp>
+#include <boost/beast/core/type_traits.hpp>
+#include <boost/beast/core/detail/clamp.hpp>
+#include <boost/beast/core/detail/config.hpp>
+#include <boost/beast/http/error.hpp>
+#include <boost/beast/http/rfc7230.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/make_unique.hpp>
+#include <algorithm>
+#include <utility>
+
+namespace boost {
+namespace beast {
+namespace http {
+
+template<bool isRequest, class Derived>
+template<class OtherDerived>
+basic_parser<isRequest, Derived>::
+basic_parser(basic_parser<
+ isRequest, OtherDerived>&& other)
+ : body_limit_(other.body_limit_)
+ , len_(other.len_)
+ , buf_(std::move(other.buf_))
+ , buf_len_(other.buf_len_)
+ , skip_(other.skip_)
+ , header_limit_(other.header_limit_)
+ , status_(other.status_)
+ , state_(other.state_)
+ , f_(other.f_)
+{
+}
+
+template<bool isRequest, class Derived>
+bool
+basic_parser<isRequest, Derived>::
+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, class Derived>
+boost::optional<std::uint64_t>
+basic_parser<isRequest, Derived>::
+content_length() const
+{
+ BOOST_ASSERT(is_header_done());
+ if(! (f_ & flagContentLength))
+ return boost::none;
+ return len_;
+}
+
+template<bool isRequest, class Derived>
+void
+basic_parser<isRequest, Derived>::
+skip(bool v)
+{
+ BOOST_ASSERT(! got_some());
+ if(v)
+ f_ |= flagSkipBody;
+ else
+ f_ &= ~flagSkipBody;
+}
+
+template<bool isRequest, class Derived>
+template<class ConstBufferSequence>
+std::size_t
+basic_parser<isRequest, Derived>::
+put(ConstBufferSequence const& buffers,
+ error_code& ec)
+{
+ static_assert(boost::asio::is_const_buffer_sequence<
+ ConstBufferSequence>::value,
+ "ConstBufferSequence requirements not met");
+ using boost::asio::buffer_copy;
+ using boost::asio::buffer_size;
+ auto const p = boost::asio::buffer_sequence_begin(buffers);
+ auto const last = boost::asio::buffer_sequence_end(buffers);
+ if(p == last)
+ {
+ ec.assign(0, ec.category());
+ return 0;
+ }
+ if(std::next(p) == last)
+ {
+ // single buffer
+ return put(boost::asio::const_buffer(*p), ec);
+ }
+ auto const size = buffer_size(buffers);
+ if(size <= max_stack_buffer)
+ return put_from_stack(size, buffers, ec);
+ if(size > buf_len_)
+ {
+ // reallocate
+ buf_ = boost::make_unique_noinit<char[]>(size);
+ buf_len_ = size;
+ }
+ // flatten
+ buffer_copy(boost::asio::buffer(
+ buf_.get(), buf_len_), buffers);
+ return put(boost::asio::const_buffer{
+ buf_.get(), buf_len_}, ec);
+}
+
+template<bool isRequest, class Derived>
+std::size_t
+basic_parser<isRequest, Derived>::
+put(boost::asio::const_buffer const& buffer,
+ error_code& ec)
+{
+ BOOST_ASSERT(state_ != state::complete);
+ using boost::asio::buffer_size;
+ auto p = reinterpret_cast<
+ char const*>(buffer.data());
+ auto n = buffer.size();
+ auto const p0 = p;
+ auto const p1 = p0 + n;
+ ec.assign(0, ec.category());
+loop:
+ switch(state_)
+ {
+ case state::nothing_yet:
+ if(n == 0)
+ {
+ ec = error::need_more;
+ return 0;
+ }
+ state_ = state::start_line;
+ BOOST_BEAST_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_)
+ {
+ 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)
+ {
+ ec = error::need_more;
+ goto done;
+ }
+ BOOST_BEAST_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_)
+ {
+ 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_);
+ impl().on_body_init_impl(content_length(), ec);
+ if(ec)
+ goto done;
+ state_ = state::body;
+ BOOST_BEAST_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_);
+ impl().on_body_init_impl(content_length(), ec);
+ if(ec)
+ goto done;
+ state_ = state::body_to_eof;
+ BOOST_BEAST_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:
+ impl().on_body_init_impl(content_length(), ec);
+ if(ec)
+ goto done;
+ state_ = state::chunk_header;
+ BOOST_BEAST_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.assign(0, ec.category());
+ 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, class Derived>
+void
+basic_parser<isRequest, Derived>::
+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.assign(0, ec.category());
+ return;
+ }
+ impl().on_finish_impl(ec);
+ if(ec)
+ return;
+ state_ = state::complete;
+}
+
+template<bool isRequest, class Derived>
+template<class ConstBufferSequence>
+std::size_t
+basic_parser<isRequest, Derived>::
+put_from_stack(std::size_t size,
+ ConstBufferSequence const& buffers,
+ error_code& ec)
+{
+ char buf[max_stack_buffer];
+ using boost::asio::buffer;
+ using boost::asio::buffer_copy;
+ buffer_copy(buffer(buf, sizeof(buf)), buffers);
+ return put(boost::asio::const_buffer{
+ buf, size}, ec);
+}
+
+template<bool isRequest, class Derived>
+inline
+void
+basic_parser<isRequest, Derived>::
+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<bool isRequest, class Derived>
+inline
+void
+basic_parser<isRequest, Derived>::
+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;
+
+ impl().on_request_impl(string_to_verb(method),
+ method, target, version, ec);
+ if(ec)
+ return;
+
+ in = p;
+ state_ = state::fields;
+}
+
+template<bool isRequest, class Derived>
+inline
+void
+basic_parser<isRequest, Derived>::
+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;
+
+ impl().on_response_impl(
+ status_, reason, version, ec);
+ if(ec)
+ return;
+
+ in = p;
+ state_ = state::fields;
+}
+
+template<bool isRequest, class Derived>
+void
+basic_parser<isRequest, Derived>::
+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
+ static_string<max_obs_fold> 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;
+ impl().on_field_impl(f, name, value, ec);
+ if(ec)
+ return;
+ in = p;
+ }
+}
+
+template<bool isRequest, class Derived>
+inline
+void
+basic_parser<isRequest, Derived>::
+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;
+ state_ = state::complete;
+ }
+
+ impl().on_header_impl(ec);
+ if(ec)
+ return;
+ if(state_ == state::complete)
+ {
+ impl().on_finish_impl(ec);
+ if(ec)
+ return;
+ }
+}
+
+template<bool isRequest, class Derived>
+inline
+void
+basic_parser<isRequest, Derived>::
+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_ > 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
+ {
+ f_ |= flagHasBody;
+ f_ |= flagNeedEOF;
+ state_ = state::body_to_eof0;
+ }
+
+ impl().on_header_impl(ec);
+ if(ec)
+ return;
+ if(state_ == state::complete)
+ {
+ impl().on_finish_impl(ec);
+ if(ec)
+ return;
+ }
+}
+
+template<bool isRequest, class Derived>
+inline
+void
+basic_parser<isRequest, Derived>::
+parse_body(char const*& p,
+ std::size_t n, error_code& ec)
+{
+ n = impl().on_body_impl(string_view{p,
+ beast::detail::clamp(len_, n)}, ec);
+ p += n;
+ len_ -= n;
+ if(ec)
+ return;
+ if(len_ > 0)
+ return;
+ impl().on_finish_impl(ec);
+ if(ec)
+ return;
+ state_ = state::complete;
+}
+
+template<bool isRequest, class Derived>
+inline
+void
+basic_parser<isRequest, Derived>::
+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;
+ n = impl().on_body_impl(string_view{p, n}, ec);
+ p += n;
+ if(ec)
+ return;
+}
+
+template<bool isRequest, class Derived>
+void
+basic_parser<isRequest, Derived>::
+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);
+ impl().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);
+ impl().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;
+
+ impl().on_finish_impl(ec);
+ if(ec)
+ return;
+ state_ = state::complete;
+}
+
+template<bool isRequest, class Derived>
+inline
+void
+basic_parser<isRequest, Derived>::
+parse_chunk_body(char const*& p,
+ std::size_t n, error_code& ec)
+{
+ n = impl().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, class Derived>
+void
+basic_parser<isRequest, Derived>::
+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(iequals({"close", 5}, s))
+ {
+ f_ |= flagConnectionClose;
+ continue;
+ }
+
+ if(iequals({"keep-alive", 10}, s))
+ {
+ f_ |= flagConnectionKeepAlive;
+ continue;
+ }
+
+ if(iequals({"upgrade", 7}, s))
+ {
+ f_ |= flagConnectionUpgrade;
+ continue;
+ }
+ }
+ ec.assign(0, ec.category());
+ 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;
+ }
+
+ ec.assign(0, ec.category());
+ 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;
+ }
+
+ ec.assign(0, ec.category());
+ auto const v = token_list{value};
+ auto const p = std::find_if(v.begin(), v.end(),
+ [&](typename token_list::value_type const& s)
+ {
+ return iequals({"chunked", 7}, s);
+ });
+ if(p == v.end())
+ return;
+ if(std::next(p) != v.end())
+ return;
+ len_ = 0;
+ f_ |= flagChunked;
+ return;
+ }
+
+ // Upgrade
+ if(f == field::upgrade)
+ {
+ ec.assign(0, ec.category());
+ f_ |= flagUpgrade;
+ return;
+ }
+
+ ec.assign(0, ec.category());
+}
+
+} // http
+} // beast
+} // boost
+
+#endif