diff options
author | DongHun Kwak <dh0128.kwak@samsung.com> | 2019-12-05 15:11:01 +0900 |
---|---|---|
committer | DongHun Kwak <dh0128.kwak@samsung.com> | 2019-12-05 15:11:01 +0900 |
commit | 3fdc3e5ee96dca5b11d1694975a65200787eab86 (patch) | |
tree | 5c1733853892b8397d67706fa453a9bd978d2102 /boost/beast/http/impl | |
parent | 88e602c57797660ebe0f9e15dbd64c1ff16dead3 (diff) | |
download | boost-3fdc3e5ee96dca5b11d1694975a65200787eab86.tar.gz boost-3fdc3e5ee96dca5b11d1694975a65200787eab86.tar.bz2 boost-3fdc3e5ee96dca5b11d1694975a65200787eab86.zip |
Imported Upstream version 1.66.0upstream/1.66.0
Diffstat (limited to 'boost/beast/http/impl')
-rw-r--r-- | boost/beast/http/impl/basic_parser.ipp | 928 | ||||
-rw-r--r-- | boost/beast/http/impl/chunk_encode.ipp | 707 | ||||
-rw-r--r-- | boost/beast/http/impl/error.ipp | 120 | ||||
-rw-r--r-- | boost/beast/http/impl/field.ipp | 561 | ||||
-rw-r--r-- | boost/beast/http/impl/fields.ipp | 1380 | ||||
-rw-r--r-- | boost/beast/http/impl/file_body_win32.ipp | 584 | ||||
-rw-r--r-- | boost/beast/http/impl/message.ipp | 437 | ||||
-rw-r--r-- | boost/beast/http/impl/parser.ipp | 56 | ||||
-rw-r--r-- | boost/beast/http/impl/read.ipp | 821 | ||||
-rw-r--r-- | boost/beast/http/impl/rfc7230.ipp | 572 | ||||
-rw-r--r-- | boost/beast/http/impl/serializer.ipp | 430 | ||||
-rw-r--r-- | boost/beast/http/impl/status.ipp | 252 | ||||
-rw-r--r-- | boost/beast/http/impl/verb.ipp | 337 | ||||
-rw-r--r-- | boost/beast/http/impl/write.ipp | 887 |
14 files changed, 8072 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 diff --git a/boost/beast/http/impl/chunk_encode.ipp b/boost/beast/http/impl/chunk_encode.ipp new file mode 100644 index 0000000000..51296041f7 --- /dev/null +++ b/boost/beast/http/impl/chunk_encode.ipp @@ -0,0 +1,707 @@ +// +// 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_CHUNK_ENCODE_IPP +#define BOOST_BEAST_HTTP_IMPL_CHUNK_ENCODE_IPP + +#include <boost/beast/core/detail/varint.hpp> +#include <boost/beast/http/error.hpp> +#include <boost/beast/http/detail/rfc7230.hpp> +#include <algorithm> + +namespace boost { +namespace beast { +namespace http { + +inline +chunk_header:: +chunk_header(std::size_t size) + : view_( + size, + boost::asio::const_buffer{nullptr, 0}, + chunk_crlf{}) +{ + BOOST_ASSERT(size > 0); +} + +inline +chunk_header:: +chunk_header( + std::size_t size, + string_view extensions) + : view_( + size, + boost::asio::const_buffer{ + extensions.data(), extensions.size()}, + chunk_crlf{}) +{ + BOOST_ASSERT(size > 0); +} + +template<class ChunkExtensions, class> +chunk_header:: +chunk_header( + std::size_t size, + ChunkExtensions&& extensions) + : exts_(std::make_shared<detail::chunk_extensions_impl< + typename std::decay<ChunkExtensions>::type>>( + std::forward<ChunkExtensions>(extensions))) + , view_( + size, + exts_->str(), + chunk_crlf{}) +{ + static_assert( + detail::is_chunk_extensions<ChunkExtensions>::value, + "ChunkExtensions requirements not met"); + BOOST_ASSERT(size > 0); +} + +template<class ChunkExtensions, class Allocator, class> +chunk_header:: +chunk_header( + std::size_t size, + ChunkExtensions&& extensions, + Allocator const& allocator) + : exts_(std::allocate_shared<detail::chunk_extensions_impl< + typename std::decay<ChunkExtensions>::type>>(allocator, + std::forward<ChunkExtensions>(extensions))) + , view_( + size, + exts_->str(), + chunk_crlf{}) +{ + static_assert( + detail::is_chunk_extensions<ChunkExtensions>::value, + "ChunkExtensions requirements not met"); + BOOST_ASSERT(size > 0); +} + +//------------------------------------------------------------------------------ + +template<class ConstBufferSequence> +chunk_body<ConstBufferSequence>:: +chunk_body(ConstBufferSequence const& buffers) + : view_( + boost::asio::buffer_size(buffers), + boost::asio::const_buffer{nullptr, 0}, + chunk_crlf{}, + buffers, + chunk_crlf{}) +{ +} + +template<class ConstBufferSequence> +chunk_body<ConstBufferSequence>:: +chunk_body( + ConstBufferSequence const& buffers, + string_view extensions) + : view_( + boost::asio::buffer_size(buffers), + boost::asio::const_buffer{ + extensions.data(), extensions.size()}, + chunk_crlf{}, + buffers, + chunk_crlf{}) +{ +} + +template<class ConstBufferSequence> +template<class ChunkExtensions, class> +chunk_body<ConstBufferSequence>:: +chunk_body( + ConstBufferSequence const& buffers, + ChunkExtensions&& extensions) + : exts_(std::make_shared<detail::chunk_extensions_impl< + typename std::decay<ChunkExtensions>::type>>( + std::forward<ChunkExtensions>(extensions))) + , view_( + boost::asio::buffer_size(buffers), + exts_->str(), + chunk_crlf{}, + buffers, + chunk_crlf{}) +{ +} + +template<class ConstBufferSequence> +template<class ChunkExtensions, class Allocator, class> +chunk_body<ConstBufferSequence>:: +chunk_body( + ConstBufferSequence const& buffers, + ChunkExtensions&& extensions, + Allocator const& allocator) + : exts_(std::allocate_shared<detail::chunk_extensions_impl< + typename std::decay<ChunkExtensions>::type>>(allocator, + std::forward<ChunkExtensions>(extensions))) + , view_( + boost::asio::buffer_size(buffers), + exts_->str(), + chunk_crlf{}, + buffers, + chunk_crlf{}) +{ +} + +//------------------------------------------------------------------------------ + +template<class Trailer> +template<class Allocator> +auto +chunk_last<Trailer>:: +prepare(Trailer const& trailer, Allocator const& allocator) -> + buffers_type +{ + auto sp = std::allocate_shared<typename + Trailer::writer>(allocator, trailer); + sp_ = sp; + return sp->get(); +} + +template<class Trailer> +auto +chunk_last<Trailer>:: +prepare(Trailer const& trailer, std::true_type) -> + buffers_type +{ + auto sp = std::make_shared< + typename Trailer::writer>(trailer); + sp_ = sp; + return sp->get(); +} + +template<class Trailer> +auto +chunk_last<Trailer>:: +prepare(Trailer const& trailer, std::false_type) -> + buffers_type +{ + return trailer; +} + +template<class Trailer> +chunk_last<Trailer>:: +chunk_last() + : view_( + detail::chunk_size0{}, + Trailer{}) +{ +} + +template<class Trailer> +chunk_last<Trailer>:: +chunk_last(Trailer const& trailer) + : view_( + detail::chunk_size0{}, + prepare(trailer, is_fields<Trailer>{})) +{ +} + +template<class Trailer> +template<class DeducedTrailer, class Allocator, class> +chunk_last<Trailer>:: +chunk_last( + DeducedTrailer const& trailer, Allocator const& allocator) + : view_( + detail::chunk_size0{}, + prepare(trailer, allocator)) +{ +} + +//------------------------------------------------------------------------------ + +template<class Allocator> +class basic_chunk_extensions<Allocator>::const_iterator +{ + friend class basic_chunk_extensions; + + using iter_type = char const*; + + iter_type it_; + typename basic_chunk_extensions::value_type value_; + + explicit + const_iterator(iter_type it) + : it_(it) + { + } + + void + increment(); + +public: + using value_type = typename + basic_chunk_extensions::value_type; + using pointer = value_type const*; + using reference = value_type const&; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::forward_iterator_tag; + + const_iterator() = default; + const_iterator(const_iterator&& other) = default; + const_iterator(const_iterator const& other) = default; + const_iterator& operator=(const_iterator&& other) = default; + const_iterator& operator=(const_iterator const& other) = default; + + bool + operator==(const_iterator const& other) const + { + return it_ == other.it_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*(); + + pointer + operator->() + { + return &(**this); + } + + const_iterator& + operator++() + { + increment(); + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + increment(); + return temp; + } +}; + +template<class Allocator> +void +basic_chunk_extensions<Allocator>:: +const_iterator:: +increment() +{ + using beast::detail::varint_read; + auto n = varint_read(it_); + it_ += n; + n = varint_read(it_); + it_ += n; +} + +template<class Allocator> +auto +basic_chunk_extensions<Allocator>:: +const_iterator:: +operator*() -> + reference +{ + using beast::detail::varint_read; + auto it = it_; + auto n = varint_read(it); + value_.first = string_view{it, n}; + it += n; + n = varint_read(it); + value_.second = string_view{it, n}; + return value_; +} + +//------------------------------------------------------------------------------ + +template<class Allocator> +template<class FwdIt> +FwdIt +basic_chunk_extensions<Allocator>:: +do_parse(FwdIt it, FwdIt last, error_code& ec) +{ +/* + chunk-ext = *( BWS ";" BWS chunk-ext-name [ BWS "=" BWS chunk-ext-val ] ) + BWS = *( SP / HTAB ) ; "Bad White Space" + chunk-ext-name = token + chunk-ext-val = token / quoted-string + token = 1*tchar + quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE + qdtext = HTAB / SP / "!" / %x23-5B ; '#'-'[' / %x5D-7E ; ']'-'~' / obs-text + quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + obs-text = %x80-FF + + https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4667 +*/ + using beast::detail::varint_size; + using beast::detail::varint_write; + using CharT = char; + using Traits = std::char_traits<CharT>; + range_.reserve(static_cast<std::size_t>( + std::distance(it, last) * 1.2)); + range_.resize(0); + auto const emit_string = + [this](FwdIt from, FwdIt to) + { + auto const len = + std::distance(from, to); + auto const offset = range_.size(); + range_.resize( + offset + + varint_size(len) + + len); + auto dest = &range_[offset]; + varint_write(dest, len); + Traits::copy(dest, from, len); + }; + auto const emit_string_plus_empty = + [this](FwdIt from, FwdIt to) + { + auto const len = + std::distance(from, to); + auto const offset = range_.size(); + range_.resize( + offset + + varint_size(len) + + len + + varint_size(0)); + auto dest = &range_[offset]; + varint_write(dest, len); + Traits::copy(dest, from, len); + dest += len; + varint_write(dest, 0); + }; + auto const emit_empty_string = + [this] + { + auto const offset = range_.size(); + range_.resize(offset + varint_size(0)); + auto dest = &range_[offset]; + varint_write(dest, 0); + }; +loop: + if(it == last) + { + ec.assign(0, ec.category()); + return it; + } + // BWS + if(*it == ' ' || *it == '\t') + { + for(;;) + { + ++it; + if(it == last) + { + ec = error::bad_chunk_extension; + return it; + } + if(*it != ' ' && *it != '\t') + break; + } + } + // ';' + if(*it != ';') + { + ec = error::bad_chunk_extension; + return it; + } +semi: + ++it; // skip ';' + // BWS + for(;;) + { + if(it == last) + { + ec = error::bad_chunk_extension; + return it; + } + if(*it != ' ' && *it != '\t') + break; + ++it; + } + // chunk-ext-name + { + if(! detail::is_token_char(*it)) + { + ec = error::bad_chunk_extension; + return it; + } + auto const first = it; + for(;;) + { + ++it; + if(it == last) + { + emit_string_plus_empty(first, it); + return it; + } + if(! detail::is_token_char(*it)) + break; + } + emit_string(first, it); + } + // BWS [ ";" / "=" ] + for(;;) + { + if(*it != ' ' && *it != '\t') + break; + ++it; + if(it == last) + { + ec = error::bad_chunk_extension; + return it; + } + } + if(*it == ';') + { + emit_empty_string(); + goto semi; + } + if(*it != '=') + { + ec = error::bad_chunk_extension; + return it; + } + ++it; // skip '=' + // BWS + for(;;) + { + if(it == last) + { + ec = error::bad_chunk_extension; + return it; + } + if(*it != ' ' && *it != '\t') + break; + ++it; + } + // chunk-ext-val + if(*it != '"') + { + // token + if(! detail::is_token_char(*it)) + { + ec = error::bad_chunk_extension; + return it; + } + auto const first = it; + for(;;) + { + ++it; + if(it == last) + break; + if(! detail::is_token_char(*it)) + break; + } + emit_string(first, it); + if(it == last) + return it; + } + else + { + // quoted-string + auto const first = ++it; // skip DQUOTE + // first pass, count chars + std::size_t len = 0; + for(;;) + { + if(it == last) + { + ec = error::bad_chunk_extension; + return it; + } + if(*it == '"') + break; + if(*it == '\\') + { + ++it; + if(it == last) + { + ec = error::bad_chunk_extension; + return it; + } + } + ++len; + ++it; + } + // now build the string + auto const offset = range_.size(); + range_.resize( + offset + + varint_size(len) + + len); + auto dest = &range_[offset]; + varint_write(dest, len); + it = first; + for(;;) + { + BOOST_ASSERT(it != last); + if(*it == '"') + break; + if(*it == '\\') + { + ++it; + BOOST_ASSERT(it != last); + } + Traits::assign(*dest++, *it++); + } + ++it; // skip DQUOTE + } + goto loop; +} + +template<class Allocator> +void +basic_chunk_extensions<Allocator>:: +do_insert(string_view name, string_view value) +{ +/* + chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) + chunk-ext-name = token + chunk-ext-val = token / quoted-string + token = 1*tchar + quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE + qdtext = HTAB / SP / "!" / %x23-5B ; '#'-'[' / %x5D-7E ; ']'-'~' / obs-text + quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + obs-text = %x80-FF +*/ + if(value.empty()) + { + s_.reserve(1 + name.size()); + s_.push_back(';'); + s_.append(name.data(), name.size()); + return; + } + + bool is_token = true; + for(auto const c : value) + { + if(! detail::is_token_char(c)) + { + is_token = false; + break; + } + } + if(is_token) + { + // token + s_.reserve(1 + name.size() + 1 + value.size()); + s_.push_back(';'); + s_.append(name.data(), name.size()); + s_.push_back('='); + s_.append(value.data(), value.size()); + } + else + { + // quoted-string + s_.reserve( + 1 + name.size() + 1 + + 1 + value.size() + 20 + 1); + s_.push_back(';'); + s_.append(name.data(), name.size()); + s_.append("=\"", 2); + for(auto const c : value) + { + if(c == '\\') + s_.append(R"(\\)", 2); + else if(c == '\"') + s_.append(R"(\")", 2); + else + s_.push_back(c); + } + s_.push_back('"'); + } +} + +template<class Allocator> +void +basic_chunk_extensions<Allocator>:: +parse(string_view s, error_code& ec) +{ + do_parse(s.data(), s.data() + s.size(), ec); + if(! ec) + { + s_.clear(); + for(auto const& v : *this) + do_insert(v.first, v.second); + } +} + +template<class Allocator> +void +basic_chunk_extensions<Allocator>:: +insert(string_view name) +{ + do_insert(name, {}); + + using beast::detail::varint_size; + using beast::detail::varint_write; + auto const offset = range_.size(); + range_.resize( + offset + + varint_size(name.size()) + + name.size() + + varint_size(0)); + auto dest = &range_[offset]; + varint_write(dest, name.size()); + std::memcpy(dest, name.data(), name.size()); + dest += name.size(); + varint_write(dest, 0); +} + +template<class Allocator> +void +basic_chunk_extensions<Allocator>:: +insert(string_view name, string_view value) +{ + do_insert(name, value); + + using beast::detail::varint_size; + using beast::detail::varint_write; + auto const offset = range_.size(); + range_.resize( + offset + + varint_size(name.size()) + + name.size() + + varint_size(value.size()) + + value.size()); + auto dest = &range_[offset]; + varint_write(dest, name.size()); + std::memcpy(dest, name.data(), name.size()); + dest += name.size(); + varint_write(dest, value.size()); + std::memcpy(dest, value.data(), value.size()); +} + +template<class Allocator> +inline +auto +basic_chunk_extensions<Allocator>:: +begin() const -> + const_iterator +{ + return const_iterator{range_.data()}; +} + +template<class Allocator> +inline +auto +basic_chunk_extensions<Allocator>:: +end() const -> + const_iterator +{ + return const_iterator{ + range_.data() + range_.size()}; +} + +} // http +} // beast +} // boost + +#endif diff --git a/boost/beast/http/impl/error.ipp b/boost/beast/http/impl/error.ipp new file mode 100644 index 0000000000..45075f5c18 --- /dev/null +++ b/boost/beast/http/impl/error.ipp @@ -0,0 +1,120 @@ +// +// 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_ERROR_IPP +#define BOOST_BEAST_HTTP_IMPL_ERROR_IPP + +#include <type_traits> + +namespace boost { + +namespace system { +template<> +struct is_error_code_enum<beast::http::error> +{ + static bool const value = true; +}; +} // system + +namespace beast { +namespace http { +namespace detail { + +class http_error_category : public error_category +{ +public: + const char* + name() const noexcept override + { + return "beast.http"; + } + + std::string + message(int ev) const override + { + switch(static_cast<error>(ev)) + { + case error::end_of_stream: return "end of stream"; + case error::partial_message: return "partial message"; + case error::need_more: return "need more"; + case error::unexpected_body: return "unexpected body"; + case error::need_buffer: return "need buffer"; + case error::end_of_chunk: return "end of chunk"; + case error::buffer_overflow: return "buffer overflow"; + case error::header_limit: return "header limit exceeded"; + case error::body_limit: return "body limit exceeded"; + case error::bad_alloc: return "bad alloc"; + case error::bad_line_ending: return "bad line ending"; + case error::bad_method: return "bad method"; + case error::bad_target: return "bad target"; + case error::bad_version: return "bad version"; + case error::bad_status: return "bad status"; + case error::bad_reason: return "bad reason"; + case error::bad_field: return "bad field"; + case error::bad_value: return "bad value"; + case error::bad_content_length: return "bad Content-Length"; + case error::bad_transfer_encoding: return "bad Transfer-Encoding"; + case error::bad_chunk: return "bad chunk"; + case error::bad_chunk_extension: return "bad chunk extension"; + case error::bad_obs_fold: return "bad obs-fold"; + + default: + return "beast.http error"; + } + } + + error_condition + default_error_condition( + int ev) const noexcept override + { + return error_condition{ev, *this}; + } + + bool + equivalent(int ev, + error_condition const& condition + ) const noexcept override + { + return condition.value() == ev && + &condition.category() == this; + } + + bool + equivalent(error_code const& error, + int ev) const noexcept override + { + return error.value() == ev && + &error.category() == this; + } +}; + +inline +error_category const& +get_http_error_category() +{ + static http_error_category const cat{}; + return cat; +} + +} // detail + +inline +error_code +make_error_code(error ev) +{ + return error_code{ + static_cast<std::underlying_type<error>::type>(ev), + detail::get_http_error_category()}; +} + +} // http +} // beast +} // boost + +#endif diff --git a/boost/beast/http/impl/field.ipp b/boost/beast/http/impl/field.ipp new file mode 100644 index 0000000000..8da470ff24 --- /dev/null +++ b/boost/beast/http/impl/field.ipp @@ -0,0 +1,561 @@ +// +// 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_FIELD_IPP +#define BOOST_BEAST_HTTP_IMPL_FIELD_IPP + +#include <boost/beast/core/string.hpp> +#include <algorithm> +#include <array> +#include <unordered_map> +#include <vector> +#include <boost/assert.hpp> + +namespace boost { +namespace beast { +namespace http { + +namespace detail { + +struct field_table +{ + using array_type = + std::array<string_view, 352>; + + struct hash + { + std::size_t + operator()(string_view s) const + { + auto const n = s.size(); + return + beast::detail::ascii_tolower(s[0]) * + beast::detail::ascii_tolower(s[n/2]) ^ + beast::detail::ascii_tolower(s[n-1]); // hist[] = 331, 10, max_load_factor = 0.15f + } + }; + + struct iequal + { + // assumes inputs have equal length + bool + operator()( + string_view lhs, + string_view rhs) const + { + auto p1 = lhs.data(); + auto p2 = rhs.data(); + auto pend = lhs.end(); + char a, b; + while(p1 < pend) + { + a = *p1++; + b = *p2++; + if(a != b) + goto slow; + } + return true; + + while(p1 < pend) + { + slow: + if( beast::detail::ascii_tolower(a) != + beast::detail::ascii_tolower(b)) + return false; + a = *p1++; + b = *p2++; + } + return true; + } + }; + + using map_type = std::unordered_map< + string_view, field, hash, iequal>; + + array_type by_name_; + std::vector<map_type> by_size_; +/* + From: + + https://www.iana.org/assignments/message-headers/message-headers.xhtml +*/ + field_table() + : by_name_({{ + "<unknown-field>", + "A-IM", + "Accept", + "Accept-Additions", + "Accept-Charset", + "Accept-Datetime", + "Accept-Encoding", + "Accept-Features", + "Accept-Language", + "Accept-Patch", + "Accept-Post", + "Accept-Ranges", + "Access-Control", + "Access-Control-Allow-Credentials", + "Access-Control-Allow-Headers", + "Access-Control-Allow-Methods", + "Access-Control-Allow-Origin", + "Access-Control-Max-Age", + "Access-Control-Request-Headers", + "Access-Control-Request-Method", + "Age", + "Allow", + "ALPN", + "Also-Control", + "Alt-Svc", + "Alt-Used", + "Alternate-Recipient", + "Alternates", + "Apparently-To", + "Apply-To-Redirect-Ref", + "Approved", + "Archive", + "Archived-At", + "Article-Names", + "Article-Updates", + "Authentication-Control", + "Authentication-Info", + "Authentication-Results", + "Authorization", + "Auto-Submitted", + "Autoforwarded", + "Autosubmitted", + "Base", + "Bcc", + "Body", + "C-Ext", + "C-Man", + "C-Opt", + "C-PEP", + "C-PEP-Info", + "Cache-Control", + "CalDAV-Timezones", + "Cancel-Key", + "Cancel-Lock", + "Cc", + "Close", + "Comments", + "Compliance", + "Connection", + "Content-Alternative", + "Content-Base", + "Content-Description", + "Content-Disposition", + "Content-Duration", + "Content-Encoding", + "Content-features", + "Content-ID", + "Content-Identifier", + "Content-Language", + "Content-Length", + "Content-Location", + "Content-MD5", + "Content-Range", + "Content-Return", + "Content-Script-Type", + "Content-Style-Type", + "Content-Transfer-Encoding", + "Content-Type", + "Content-Version", + "Control", + "Conversion", + "Conversion-With-Loss", + "Cookie", + "Cookie2", + "Cost", + "DASL", + "Date", + "Date-Received", + "DAV", + "Default-Style", + "Deferred-Delivery", + "Delivery-Date", + "Delta-Base", + "Depth", + "Derived-From", + "Destination", + "Differential-ID", + "Digest", + "Discarded-X400-IPMS-Extensions", + "Discarded-X400-MTS-Extensions", + "Disclose-Recipients", + "Disposition-Notification-Options", + "Disposition-Notification-To", + "Distribution", + "DKIM-Signature", + "DL-Expansion-History", + "Downgraded-Bcc", + "Downgraded-Cc", + "Downgraded-Disposition-Notification-To", + "Downgraded-Final-Recipient", + "Downgraded-From", + "Downgraded-In-Reply-To", + "Downgraded-Mail-From", + "Downgraded-Message-Id", + "Downgraded-Original-Recipient", + "Downgraded-Rcpt-To", + "Downgraded-References", + "Downgraded-Reply-To", + "Downgraded-Resent-Bcc", + "Downgraded-Resent-Cc", + "Downgraded-Resent-From", + "Downgraded-Resent-Reply-To", + "Downgraded-Resent-Sender", + "Downgraded-Resent-To", + "Downgraded-Return-Path", + "Downgraded-Sender", + "Downgraded-To", + "EDIINT-Features", + "Eesst-Version", + "Encoding", + "Encrypted", + "Errors-To", + "ETag", + "Expect", + "Expires", + "Expiry-Date", + "Ext", + "Followup-To", + "Forwarded", + "From", + "Generate-Delivery-Report", + "GetProfile", + "Hobareg", + "Host", + "HTTP2-Settings", + "If", + "If-Match", + "If-Modified-Since", + "If-None-Match", + "If-Range", + "If-Schedule-Tag-Match", + "If-Unmodified-Since", + "IM", + "Importance", + "In-Reply-To", + "Incomplete-Copy", + "Injection-Date", + "Injection-Info", + "Jabber-ID", + "Keep-Alive", + "Keywords", + "Label", + "Language", + "Last-Modified", + "Latest-Delivery-Time", + "Lines", + "Link", + "List-Archive", + "List-Help", + "List-ID", + "List-Owner", + "List-Post", + "List-Subscribe", + "List-Unsubscribe", + "List-Unsubscribe-Post", + "Location", + "Lock-Token", + "Man", + "Max-Forwards", + "Memento-Datetime", + "Message-Context", + "Message-ID", + "Message-Type", + "Meter", + "Method-Check", + "Method-Check-Expires", + "MIME-Version", + "MMHS-Acp127-Message-Identifier", + "MMHS-Authorizing-Users", + "MMHS-Codress-Message-Indicator", + "MMHS-Copy-Precedence", + "MMHS-Exempted-Address", + "MMHS-Extended-Authorisation-Info", + "MMHS-Handling-Instructions", + "MMHS-Message-Instructions", + "MMHS-Message-Type", + "MMHS-Originator-PLAD", + "MMHS-Originator-Reference", + "MMHS-Other-Recipients-Indicator-CC", + "MMHS-Other-Recipients-Indicator-To", + "MMHS-Primary-Precedence", + "MMHS-Subject-Indicator-Codes", + "MT-Priority", + "Negotiate", + "Newsgroups", + "NNTP-Posting-Date", + "NNTP-Posting-Host", + "Non-Compliance", + "Obsoletes", + "Opt", + "Optional", + "Optional-WWW-Authenticate", + "Ordering-Type", + "Organization", + "Origin", + "Original-Encoded-Information-Types", + "Original-From", + "Original-Message-ID", + "Original-Recipient", + "Original-Sender", + "Original-Subject", + "Originator-Return-Address", + "Overwrite", + "P3P", + "Path", + "PEP", + "Pep-Info", + "PICS-Label", + "Position", + "Posting-Version", + "Pragma", + "Prefer", + "Preference-Applied", + "Prevent-NonDelivery-Report", + "Priority", + "Privicon", + "ProfileObject", + "Protocol", + "Protocol-Info", + "Protocol-Query", + "Protocol-Request", + "Proxy-Authenticate", + "Proxy-Authentication-Info", + "Proxy-Authorization", + "Proxy-Connection", + "Proxy-Features", + "Proxy-Instruction", + "Public", + "Public-Key-Pins", + "Public-Key-Pins-Report-Only", + "Range", + "Received", + "Received-SPF", + "Redirect-Ref", + "References", + "Referer", + "Referer-Root", + "Relay-Version", + "Reply-By", + "Reply-To", + "Require-Recipient-Valid-Since", + "Resent-Bcc", + "Resent-Cc", + "Resent-Date", + "Resent-From", + "Resent-Message-ID", + "Resent-Reply-To", + "Resent-Sender", + "Resent-To", + "Resolution-Hint", + "Resolver-Location", + "Retry-After", + "Return-Path", + "Safe", + "Schedule-Reply", + "Schedule-Tag", + "Sec-WebSocket-Accept", + "Sec-WebSocket-Extensions", + "Sec-WebSocket-Key", + "Sec-WebSocket-Protocol", + "Sec-WebSocket-Version", + "Security-Scheme", + "See-Also", + "Sender", + "Sensitivity", + "Server", + "Set-Cookie", + "Set-Cookie2", + "SetProfile", + "SIO-Label", + "SIO-Label-History", + "SLUG", + "SoapAction", + "Solicitation", + "Status-URI", + "Strict-Transport-Security", + "Subject", + "SubOK", + "Subst", + "Summary", + "Supersedes", + "Surrogate-Capability", + "Surrogate-Control", + "TCN", + "TE", + "Timeout", + "Title", + "To", + "Topic", + "Trailer", + "Transfer-Encoding", + "TTL", + "UA-Color", + "UA-Media", + "UA-Pixels", + "UA-Resolution", + "UA-Windowpixels", + "Upgrade", + "Urgency", + "URI", + "User-Agent", + "Variant-Vary", + "Vary", + "VBR-Info", + "Version", + "Via", + "Want-Digest", + "Warning", + "WWW-Authenticate", + "X-Archived-At", + "X-Device-Accept", + "X-Device-Accept-Charset", + "X-Device-Accept-Encoding", + "X-Device-Accept-Language", + "X-Device-User-Agent", + "X-Frame-Options", + "X-Mittente", + "X-PGP-Sig", + "X-Ricevuta", + "X-Riferimento-Message-ID", + "X-TipoRicevuta", + "X-Trasporto", + "X-VerificaSicurezza", + "X400-Content-Identifier", + "X400-Content-Return", + "X400-Content-Type", + "X400-MTS-Identifier", + "X400-Originator", + "X400-Received", + "X400-Recipients", + "X400-Trace", + "Xref" + }}) + { + // find the longest field length + std::size_t high = 0; + for(auto const& s : by_name_) + if(high < s.size()) + high = s.size(); + // build by_size map + // skip field::unknown + by_size_.resize(high + 1); + for(auto& map : by_size_) + map.max_load_factor(.15f); + for(std::size_t i = 1; + i < by_name_.size(); ++i) + { + auto const& s = by_name_[i]; + by_size_[s.size()].emplace( + s, static_cast<field>(i)); + } + +#if 0 + // This snippet calculates the performance + // of the hash function and map settings + { + std::vector<std::size_t> hist; + for(auto const& map : by_size_) + { + for(std::size_t i = 0; i < map.bucket_count(); ++i) + { + auto const n = map.bucket_size(i); + if(n > 0) + { + if(hist.size() < n) + hist.resize(n); + ++hist[n-1]; + } + } + } + } +#endif + } + + field + string_to_field(string_view s) const + { + if(s.size() >= by_size_.size()) + return field::unknown; + auto const& map = by_size_[s.size()]; + if(map.empty()) + return field::unknown; + auto it = map.find(s); + if(it == map.end()) + return field::unknown; + return it->second; + } + + // + // Deprecated + // + + using const_iterator = + array_type::const_iterator; + + std::size_t + size() const + { + return by_name_.size(); + } + + const_iterator + begin() const + { + return by_name_.begin(); + } + + const_iterator + end() const + { + return by_name_.end(); + } +}; + +inline +field_table const& +get_field_table() +{ + static field_table const tab; + return tab; +} + +template<class = void> +string_view +to_string(field f) +{ + auto const& v = get_field_table(); + BOOST_ASSERT(static_cast<unsigned>(f) < v.size()); + return v.begin()[static_cast<unsigned>(f)]; +} + +} // detail + +inline +string_view +to_string(field f) +{ + return detail::to_string(f); +} + +inline +field +string_to_field(string_view s) +{ + return detail::get_field_table().string_to_field(s); +} + +} // http +} // beast +} // boost + +#endif diff --git a/boost/beast/http/impl/fields.ipp b/boost/beast/http/impl/fields.ipp new file mode 100644 index 0000000000..67d7cc45b4 --- /dev/null +++ b/boost/beast/http/impl/fields.ipp @@ -0,0 +1,1380 @@ +// +// 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_FIELDS_IPP +#define BOOST_BEAST_HTTP_IMPL_FIELDS_IPP + +#include <boost/beast/core/buffers_cat.hpp> +#include <boost/beast/core/string.hpp> +#include <boost/beast/core/static_string.hpp> +#include <boost/beast/core/detail/buffers_ref.hpp> +#include <boost/beast/http/verb.hpp> +#include <boost/beast/http/rfc7230.hpp> +#include <boost/beast/http/status.hpp> +#include <boost/beast/http/chunk_encode.hpp> +#include <boost/throw_exception.hpp> +#include <stdexcept> +#include <string> + +#if defined(BOOST_LIBSTDCXX_VERSION) && BOOST_LIBSTDCXX_VERSION < 60000 + // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437 +#ifndef BOOST_BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR +#define BOOST_BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR +#endif +#endif + +namespace boost { +namespace beast { +namespace http { + +template<class Allocator> +class basic_fields<Allocator>::writer +{ +public: + using iter_type = typename list_t::const_iterator; + + struct field_iterator + { + iter_type it_; + + using value_type = boost::asio::const_buffer; + using pointer = value_type const*; + using reference = value_type const; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + field_iterator() = default; + field_iterator(field_iterator&& other) = default; + field_iterator(field_iterator const& other) = default; + field_iterator& operator=(field_iterator&& other) = default; + field_iterator& operator=(field_iterator const& other) = default; + + explicit + field_iterator(iter_type it) + : it_(it) + { + } + + bool + operator==(field_iterator const& other) const + { + return it_ == other.it_; + } + + bool + operator!=(field_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return it_->buffer(); + } + + field_iterator& + operator++() + { + ++it_; + return *this; + } + + field_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + field_iterator& + operator--() + { + --it_; + return *this; + } + + field_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } + }; + + class field_range + { + field_iterator first_; + field_iterator last_; + + public: + using const_iterator = + field_iterator; + + using value_type = + typename const_iterator::value_type; + + field_range(iter_type first, iter_type last) + : first_(first) + , last_(last) + { + } + + const_iterator + begin() const + { + return first_; + } + + const_iterator + end() const + { + return last_; + } + }; + + using view_type = buffers_cat_view< + boost::asio::const_buffer, + boost::asio::const_buffer, + boost::asio::const_buffer, + field_range, + chunk_crlf>; + + basic_fields const& f_; + boost::optional<view_type> view_; + char buf_[13]; + +public: + using const_buffers_type = + beast::detail::buffers_ref<view_type>; + + writer(basic_fields const& f, + unsigned version, verb v); + + writer(basic_fields const& f, + unsigned version, unsigned code); + + writer(basic_fields const& f); + + const_buffers_type + get() const + { + return const_buffers_type(*view_); + } +}; + +template<class Allocator> +basic_fields<Allocator>::writer:: +writer(basic_fields const& f) + : f_(f) +{ + view_.emplace( + boost::asio::const_buffer{nullptr, 0}, + boost::asio::const_buffer{nullptr, 0}, + boost::asio::const_buffer{nullptr, 0}, + field_range(f_.list_.begin(), f_.list_.end()), + chunk_crlf()); +} + +template<class Allocator> +basic_fields<Allocator>::writer:: +writer(basic_fields const& f, + unsigned version, verb v) + : f_(f) +{ +/* + request + "<method>" + " <target>" + " HTTP/X.Y\r\n" (11 chars) +*/ + string_view sv; + if(v == verb::unknown) + sv = f_.get_method_impl(); + else + sv = to_string(v); + + // target_or_reason_ has a leading SP + + buf_[0] = ' '; + buf_[1] = 'H'; + buf_[2] = 'T'; + buf_[3] = 'T'; + buf_[4] = 'P'; + buf_[5] = '/'; + buf_[6] = '0' + static_cast<char>(version / 10); + buf_[7] = '.'; + buf_[8] = '0' + static_cast<char>(version % 10); + buf_[9] = '\r'; + buf_[10]= '\n'; + + view_.emplace( + boost::asio::const_buffer{sv.data(), sv.size()}, + boost::asio::const_buffer{ + f_.target_or_reason_.data(), + f_.target_or_reason_.size()}, + boost::asio::const_buffer{buf_, 11}, + field_range(f_.list_.begin(), f_.list_.end()), + chunk_crlf()); +} + +template<class Allocator> +basic_fields<Allocator>::writer:: +writer(basic_fields const& f, + unsigned version, unsigned code) + : f_(f) +{ +/* + response + "HTTP/X.Y ### " (13 chars) + "<reason>" + "\r\n" +*/ + buf_[0] = 'H'; + buf_[1] = 'T'; + buf_[2] = 'T'; + buf_[3] = 'P'; + buf_[4] = '/'; + buf_[5] = '0' + static_cast<char>(version / 10); + buf_[6] = '.'; + buf_[7] = '0' + static_cast<char>(version % 10); + buf_[8] = ' '; + buf_[9] = '0' + static_cast<char>(code / 100); + buf_[10]= '0' + static_cast<char>((code / 10) % 10); + buf_[11]= '0' + static_cast<char>(code % 10); + buf_[12]= ' '; + + string_view sv; + if(! f_.target_or_reason_.empty()) + sv = f_.target_or_reason_; + else + sv = obsolete_reason(static_cast<status>(code)); + + view_.emplace( + boost::asio::const_buffer{buf_, 13}, + boost::asio::const_buffer{sv.data(), sv.size()}, + boost::asio::const_buffer{"\r\n", 2}, + field_range(f_.list_.begin(), f_.list_.end()), + chunk_crlf{}); +} + +//------------------------------------------------------------------------------ + +template<class Allocator> +basic_fields<Allocator>:: +value_type:: +value_type(field name, + string_view sname, string_view value) + : off_(static_cast<off_t>(sname.size() + 2)) + , len_(static_cast<off_t>(value.size())) + , f_(name) +{ + //BOOST_ASSERT(name == field::unknown || + // iequals(sname, to_string(name))); + char* p = reinterpret_cast<char*>(this + 1); + p[off_-2] = ':'; + p[off_-1] = ' '; + p[off_ + len_] = '\r'; + p[off_ + len_ + 1] = '\n'; + std::memcpy(p, sname.data(), sname.size()); + std::memcpy(p + off_, value.data(), value.size()); +} + +template<class Allocator> +inline +field const +basic_fields<Allocator>:: +value_type:: +name() const +{ + return f_; +} + +template<class Allocator> +inline +string_view const +basic_fields<Allocator>:: +value_type:: +name_string() const +{ + return {reinterpret_cast< + char const*>(this + 1), + static_cast<std::size_t>(off_ - 2)}; +} + +template<class Allocator> +inline +string_view const +basic_fields<Allocator>:: +value_type:: +value() const +{ + return {reinterpret_cast< + char const*>(this + 1) + off_, + static_cast<std::size_t>(len_)}; +} + +template<class Allocator> +inline +boost::asio::const_buffer +basic_fields<Allocator>:: +value_type:: +buffer() const +{ + return boost::asio::const_buffer{ + reinterpret_cast<char const*>(this + 1), + static_cast<std::size_t>(off_) + len_ + 2}; +} + +//------------------------------------------------------------------------------ + +template<class Allocator> +basic_fields<Allocator>:: +~basic_fields() +{ + delete_list(); + realloc_string(method_, {}); + realloc_string( + target_or_reason_, {}); +} + +template<class Allocator> +basic_fields<Allocator>:: +basic_fields(Allocator const& alloc) + : alloc_(alloc) +{ +} + +template<class Allocator> +basic_fields<Allocator>:: +basic_fields(basic_fields&& other) + : alloc_(std::move(other.alloc_)) + , set_(std::move(other.set_)) + , list_(std::move(other.list_)) + , method_(other.method_) + , target_or_reason_(other.target_or_reason_) +{ + other.method_.clear(); + other.target_or_reason_.clear(); +} + +template<class Allocator> +basic_fields<Allocator>:: +basic_fields(basic_fields&& other, Allocator const& alloc) + : alloc_(alloc) +{ + if(alloc_ != other.alloc_) + { + copy_all(other); + other.clear_all(); + } + else + { + set_ = std::move(other.set_); + list_ = std::move(other.list_); + method_ = other.method_; + target_or_reason_ = other.target_or_reason_; + } +} + +template<class Allocator> +basic_fields<Allocator>:: +basic_fields(basic_fields const& other) + : alloc_(alloc_traits:: + select_on_container_copy_construction(other.alloc_)) +{ + copy_all(other); +} + +template<class Allocator> +basic_fields<Allocator>:: +basic_fields(basic_fields const& other, + Allocator const& alloc) + : alloc_(alloc) +{ + copy_all(other); +} + +template<class Allocator> +template<class OtherAlloc> +basic_fields<Allocator>:: +basic_fields(basic_fields<OtherAlloc> const& other) +{ + copy_all(other); +} + +template<class Allocator> +template<class OtherAlloc> +basic_fields<Allocator>:: +basic_fields(basic_fields<OtherAlloc> const& other, + Allocator const& alloc) + : alloc_(alloc) +{ + copy_all(other); +} + +template<class Allocator> +auto +basic_fields<Allocator>:: +operator=(basic_fields&& other) -> + basic_fields& +{ + if(this == &other) + return *this; + move_assign(other, std::integral_constant<bool, + alloc_traits:: propagate_on_container_move_assignment::value>{}); + return *this; +} + +template<class Allocator> +auto +basic_fields<Allocator>:: +operator=(basic_fields const& other) -> + basic_fields& +{ + copy_assign(other, std::integral_constant<bool, + alloc_traits::propagate_on_container_copy_assignment::value>{}); + return *this; +} + +template<class Allocator> +template<class OtherAlloc> +auto +basic_fields<Allocator>:: +operator=(basic_fields<OtherAlloc> const& other) -> + basic_fields& +{ + clear_all(); + copy_all(other); + return *this; +} + +//------------------------------------------------------------------------------ +// +// Element access +// +//------------------------------------------------------------------------------ + +template<class Allocator> +string_view const +basic_fields<Allocator>:: +at(field name) const +{ + BOOST_ASSERT(name != field::unknown); + auto const it = find(name); + if(it == end()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "field not found"}); + return it->value(); +} + +template<class Allocator> +string_view const +basic_fields<Allocator>:: +at(string_view name) const +{ + auto const it = find(name); + if(it == end()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "field not found"}); + return it->value(); +} + +template<class Allocator> +string_view const +basic_fields<Allocator>:: +operator[](field name) const +{ + BOOST_ASSERT(name != field::unknown); + auto const it = find(name); + if(it == end()) + return {}; + return it->value(); +} + +template<class Allocator> +string_view const +basic_fields<Allocator>:: +operator[](string_view name) const +{ + auto const it = find(name); + if(it == end()) + return {}; + return it->value(); +} + +//------------------------------------------------------------------------------ +// +// Modifiers +// +//------------------------------------------------------------------------------ + +template<class Allocator> +void +basic_fields<Allocator>:: +clear() +{ + delete_list(); + set_.clear(); + list_.clear(); +} + +template<class Allocator> +inline +void +basic_fields<Allocator>:: +insert(field name, string_param const& value) +{ + BOOST_ASSERT(name != field::unknown); + insert(name, to_string(name), value); +} + +template<class Allocator> +void +basic_fields<Allocator>:: +insert(string_view sname, string_param const& value) +{ + auto const name = + string_to_field(sname); + insert(name, sname, value); +} + +template<class Allocator> +void +basic_fields<Allocator>:: +insert(field name, + string_view sname, string_param const& value) +{ + auto& e = new_element(name, sname, + static_cast<string_view>(value)); + auto const before = + set_.upper_bound(sname, key_compare{}); + if(before == set_.begin()) + { + BOOST_ASSERT(count(sname) == 0); + set_.insert_before(before, e); + list_.push_back(e); + return; + } + auto const last = std::prev(before); + // VFALCO is it worth comparing `field name` first? + if(! iequals(sname, last->name_string())) + { + BOOST_ASSERT(count(sname) == 0); + set_.insert_before(before, e); + list_.push_back(e); + return; + } + // keep duplicate fields together in the list + set_.insert_before(before, e); + list_.insert(++list_.iterator_to(*last), e); +} + +template<class Allocator> +void +basic_fields<Allocator>:: +set(field name, string_param const& value) +{ + BOOST_ASSERT(name != field::unknown); + set_element(new_element(name, to_string(name), + static_cast<string_view>(value))); +} + +template<class Allocator> +void +basic_fields<Allocator>:: +set(string_view sname, string_param const& value) +{ + set_element(new_element( + string_to_field(sname), sname, + static_cast<string_view>(value))); +} + +template<class Allocator> +auto +basic_fields<Allocator>:: +erase(const_iterator pos) -> + const_iterator +{ + auto next = pos.iter(); + auto& e = *next++; + set_.erase(e); + list_.erase(e); + delete_element(e); + return next; +} + +template<class Allocator> +std::size_t +basic_fields<Allocator>:: +erase(field name) +{ + BOOST_ASSERT(name != field::unknown); + return erase(to_string(name)); +} + +template<class Allocator> +std::size_t +basic_fields<Allocator>:: +erase(string_view name) +{ + std::size_t n =0; + set_.erase_and_dispose(name, key_compare{}, + [&](value_type* e) + { + ++n; + list_.erase(list_.iterator_to(*e)); + delete_element(*e); + }); + return n; +} + +template<class Allocator> +void +basic_fields<Allocator>:: +swap(basic_fields<Allocator>& other) +{ + swap(other, std::integral_constant<bool, + alloc_traits::propagate_on_container_swap::value>{}); +} + +template<class Allocator> +void +swap( + basic_fields<Allocator>& lhs, + basic_fields<Allocator>& rhs) +{ + lhs.swap(rhs); +} + +//------------------------------------------------------------------------------ +// +// Lookup +// +//------------------------------------------------------------------------------ + +template<class Allocator> +inline +std::size_t +basic_fields<Allocator>:: +count(field name) const +{ + BOOST_ASSERT(name != field::unknown); + return count(to_string(name)); +} + +template<class Allocator> +std::size_t +basic_fields<Allocator>:: +count(string_view name) const +{ + return set_.count(name, key_compare{}); +} + +template<class Allocator> +inline +auto +basic_fields<Allocator>:: +find(field name) const -> + const_iterator +{ + BOOST_ASSERT(name != field::unknown); + return find(to_string(name)); +} + +template<class Allocator> +auto +basic_fields<Allocator>:: +find(string_view name) const -> + const_iterator +{ + auto const it = set_.find( + name, key_compare{}); + if(it == set_.end()) + return list_.end(); + return list_.iterator_to(*it); +} + +template<class Allocator> +inline +auto +basic_fields<Allocator>:: +equal_range(field name) const -> + std::pair<const_iterator, const_iterator> +{ + BOOST_ASSERT(name != field::unknown); + return equal_range(to_string(name)); +} + +template<class Allocator> +auto +basic_fields<Allocator>:: +equal_range(string_view name) const -> + std::pair<const_iterator, const_iterator> +{ + auto result = + set_.equal_range(name, key_compare{}); + if(result.first == result.second) + return {list_.end(), list_.end()}; + return { + list_.iterator_to(*result.first), + ++list_.iterator_to(*(--result.second))}; +} + +//------------------------------------------------------------------------------ + +namespace detail { + +// Filter a token list +// +template<class String, class Pred> +void +filter_token_list( + String& s, + string_view value, + Pred&& pred) +{ + token_list te{value}; + auto it = te.begin(); + auto last = te.end(); + if(it == last) + return; + while(pred(*it)) + if(++it == last) + return; + s.append(it->data(), it->size()); + while(++it != last) + { + if(! pred(*it)) + { + s.append(", "); + s.append(it->data(), it->size()); + } + } +} + +// Filter the last item in a token list +template<class String, class Pred> +void +filter_token_list_last( + String& s, + string_view value, + Pred&& pred) +{ + token_list te{value}; + if(te.begin() != te.end()) + { + auto it = te.begin(); + auto next = std::next(it); + if(next == te.end()) + { + if(! pred(*it)) + s.append(it->data(), it->size()); + return; + } + s.append(it->data(), it->size()); + for(;;) + { + it = next; + next = std::next(it); + if(next == te.end()) + { + if(! pred(*it)) + { + s.append(", "); + s.append(it->data(), it->size()); + } + return; + } + s.append(", "); + s.append(it->data(), it->size()); + } + } +} + +template<class String> +void +keep_alive_impl( + String& s, string_view value, + unsigned version, bool keep_alive) +{ + if(version < 11) + { + if(keep_alive) + { + // remove close + filter_token_list(s, value, + [](string_view s) + { + return iequals(s, "close"); + }); + // add keep-alive + if(s.empty()) + s.append("keep-alive"); + else if(! token_list{value}.exists("keep-alive")) + s.append(", keep-alive"); + } + else + { + // remove close and keep-alive + filter_token_list(s, value, + [](string_view s) + { + return + iequals(s, "close") || + iequals(s, "keep-alive"); + }); + } + } + else + { + if(keep_alive) + { + // remove close and keep-alive + filter_token_list(s, value, + [](string_view s) + { + return + iequals(s, "close") || + iequals(s, "keep-alive"); + }); + } + else + { + // remove keep-alive + filter_token_list(s, value, + [](string_view s) + { + return iequals(s, "keep-alive"); + }); + // add close + if(s.empty()) + s.append("close"); + else if(! token_list{value}.exists("close")) + s.append(", close"); + } + } +} + +} // detail + +//------------------------------------------------------------------------------ + +// Fields + +template<class Allocator> +inline +string_view +basic_fields<Allocator>:: +get_method_impl() const +{ + return method_; +} + +template<class Allocator> +inline +string_view +basic_fields<Allocator>:: +get_target_impl() const +{ + if(target_or_reason_.empty()) + return target_or_reason_; + return { + target_or_reason_.data() + 1, + target_or_reason_.size() - 1}; +} + +template<class Allocator> +inline +string_view +basic_fields<Allocator>:: +get_reason_impl() const +{ + return target_or_reason_; +} + +template<class Allocator> +bool +basic_fields<Allocator>:: +get_chunked_impl() const +{ + auto const te = token_list{ + (*this)[field::transfer_encoding]}; + for(auto it = te.begin(); it != te.end();) + { + auto const next = std::next(it); + if(next == te.end()) + return iequals(*it, "chunked"); + it = next; + } + return false; +} + +template<class Allocator> +bool +basic_fields<Allocator>:: +get_keep_alive_impl(unsigned version) const +{ + auto const it = find(field::connection); + if(version < 11) + { + if(it == end()) + return false; + return token_list{ + it->value()}.exists("keep-alive"); + } + if(it == end()) + return true; + return ! token_list{ + it->value()}.exists("close"); +} + +template<class Allocator> +bool +basic_fields<Allocator>:: +has_content_length_impl() const +{ + return count(field::content_length) > 0; +} + +template<class Allocator> +inline +void +basic_fields<Allocator>:: +set_method_impl(string_view s) +{ + realloc_string(method_, s); +} + +template<class Allocator> +inline +void +basic_fields<Allocator>:: +set_target_impl(string_view s) +{ + realloc_target( + target_or_reason_, s); +} + +template<class Allocator> +inline +void +basic_fields<Allocator>:: +set_reason_impl(string_view s) +{ + realloc_string( + target_or_reason_, s); +} + +template<class Allocator> +void +basic_fields<Allocator>:: +set_chunked_impl(bool value) +{ + auto it = find(field::transfer_encoding); + if(value) + { + // append "chunked" + if(it == end()) + { + set(field::transfer_encoding, "chunked"); + return; + } + auto const te = token_list{it->value()}; + for(auto itt = te.begin();;) + { + auto const next = std::next(itt); + if(next == te.end()) + { + if(iequals(*itt, "chunked")) + return; // already set + break; + } + itt = next; + } + static_string<max_static_buffer> buf; + if(it->value().size() <= buf.size() + 9) + { + buf.append(it->value().data(), it->value().size()); + buf.append(", chunked", 9); + set(field::transfer_encoding, buf); + } + else + { + #ifdef BOOST_BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR + // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437 + std::string s; + #else + using rebind_type = + typename beast::detail::allocator_traits< + Allocator>::template rebind_alloc<char>; + std::basic_string< + char, + std::char_traits<char>, + rebind_type> s{rebind_type{alloc_}}; + #endif + s.reserve(it->value().size() + 9); + s.append(it->value().data(), it->value().size()); + s.append(", chunked", 9); + set(field::transfer_encoding, s); + } + return; + } + // filter "chunked" + if(it == end()) + return; + try + { + static_string<max_static_buffer> buf; + detail::filter_token_list_last(buf, it->value(), + [](string_view s) + { + return iequals(s, "chunked"); + }); + if(! buf.empty()) + set(field::transfer_encoding, buf); + else + erase(field::transfer_encoding); + } + catch(std::length_error const&) + { + #ifdef BOOST_BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR + // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437 + std::string s; + #else + using rebind_type = + typename beast::detail::allocator_traits< + Allocator>::template rebind_alloc<char>; + std::basic_string< + char, + std::char_traits<char>, + rebind_type> s{rebind_type{alloc_}}; + #endif + s.reserve(it->value().size()); + detail::filter_token_list_last(s, it->value(), + [](string_view s) + { + return iequals(s, "chunked"); + }); + if(! s.empty()) + set(field::transfer_encoding, s); + else + erase(field::transfer_encoding); + } +} + +template<class Allocator> +void +basic_fields<Allocator>:: +set_content_length_impl( + boost::optional<std::uint64_t> const& value) +{ + if(! value) + erase(field::content_length); + else + set(field::content_length, *value); +} + +template<class Allocator> +void +basic_fields<Allocator>:: +set_keep_alive_impl( + unsigned version, bool keep_alive) +{ + // VFALCO What about Proxy-Connection ? + auto const value = (*this)[field::connection]; + try + { + static_string<max_static_buffer> buf; + detail::keep_alive_impl( + buf, value, version, keep_alive); + if(buf.empty()) + erase(field::connection); + else + set(field::connection, buf); + } + catch(std::length_error const&) + { + #ifdef BOOST_BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR + // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437 + std::string s; + #else + using rebind_type = + typename beast::detail::allocator_traits< + Allocator>::template rebind_alloc<char>; + std::basic_string< + char, + std::char_traits<char>, + rebind_type> s{rebind_type{alloc_}}; + #endif + s.reserve(value.size()); + detail::keep_alive_impl( + s, value, version, keep_alive); + if(s.empty()) + erase(field::connection); + else + set(field::connection, s); + } +} + +//------------------------------------------------------------------------------ + +template<class Allocator> +auto +basic_fields<Allocator>:: +new_element(field name, + string_view sname, string_view value) -> + value_type& +{ + if(sname.size() + 2 > + (std::numeric_limits<off_t>::max)()) + BOOST_THROW_EXCEPTION(std::length_error{ + "field name too large"}); + if(value.size() + 2 > + (std::numeric_limits<off_t>::max)()) + BOOST_THROW_EXCEPTION(std::length_error{ + "field value too large"}); + value = detail::trim(value); + std::uint16_t const off = + static_cast<off_t>(sname.size() + 2); + std::uint16_t const len = + static_cast<off_t>(value.size()); + auto const p = alloc_traits::allocate(alloc_, + 1 + (off + len + 2 + sizeof(value_type) - 1) / + sizeof(value_type)); + // VFALCO allocator can't call the constructor because its private + //alloc_traits::construct(alloc_, p, name, sname, value); + new(p) value_type{name, sname, value}; + return *p; +} + +template<class Allocator> +void +basic_fields<Allocator>:: +delete_element(value_type& e) +{ + auto const n = 1 + (e.off_ + e.len_ + 2 + + sizeof(value_type) - 1) / sizeof(value_type); + alloc_traits::destroy(alloc_, &e); + alloc_traits::deallocate(alloc_, &e, n); +} + +template<class Allocator> +void +basic_fields<Allocator>:: +set_element(value_type& e) +{ + auto it = set_.lower_bound( + e.name_string(), key_compare{}); + if(it == set_.end() || ! iequals( + e.name_string(), it->name_string())) + { + set_.insert_before(it, e); + list_.push_back(e); + return; + } + for(;;) + { + auto next = it; + ++next; + set_.erase(it); + list_.erase(list_.iterator_to(*it)); + delete_element(*it); + it = next; + if(it == set_.end() || + ! iequals(e.name_string(), it->name_string())) + break; + } + set_.insert_before(it, e); + list_.push_back(e); +} + +template<class Allocator> +void +basic_fields<Allocator>:: +realloc_string(string_view& dest, string_view s) +{ + if(dest.empty() && s.empty()) + return; + auto a = typename beast::detail::allocator_traits< + Allocator>::template rebind_alloc< + char>(alloc_); + if(! dest.empty()) + { + a.deallocate(const_cast<char*>( + dest.data()), dest.size()); + dest.clear(); + } + if(! s.empty()) + { + auto const p = a.allocate(s.size()); + std::memcpy(p, s.data(), s.size()); + dest = {p, s.size()}; + } +} + +template<class Allocator> +void +basic_fields<Allocator>:: +realloc_target( + string_view& dest, string_view s) +{ + // The target string are stored with an + // extra space at the beginning to help + // the writer class. + if(dest.empty() && s.empty()) + return; + auto a = typename beast::detail::allocator_traits< + Allocator>::template rebind_alloc< + char>(alloc_); + if(! dest.empty()) + { + a.deallocate(const_cast<char*>( + dest.data()), dest.size()); + dest.clear(); + } + if(! s.empty()) + { + auto const p = a.allocate(1 + s.size()); + p[0] = ' '; + std::memcpy(p + 1, s.data(), s.size()); + dest = {p, 1 + s.size()}; + } +} + +template<class Allocator> +template<class OtherAlloc> +void +basic_fields<Allocator>:: +copy_all(basic_fields<OtherAlloc> const& other) +{ + for(auto const& e : other.list_) + insert(e.name(), e.name_string(), e.value()); + realloc_string(method_, other.method_); + realloc_string(target_or_reason_, + other.target_or_reason_); +} + +template<class Allocator> +void +basic_fields<Allocator>:: +clear_all() +{ + clear(); + realloc_string(method_, {}); + realloc_string(target_or_reason_, {}); +} + +template<class Allocator> +void +basic_fields<Allocator>:: +delete_list() +{ + for(auto it = list_.begin(); it != list_.end();) + delete_element(*it++); +} + +//------------------------------------------------------------------------------ + +template<class Allocator> +inline +void +basic_fields<Allocator>:: +move_assign(basic_fields& other, std::true_type) +{ + clear_all(); + set_ = std::move(other.set_); + list_ = std::move(other.list_); + method_ = other.method_; + target_or_reason_ = other.target_or_reason_; + other.method_.clear(); + other.target_or_reason_.clear(); + alloc_ = other.alloc_; +} + +template<class Allocator> +inline +void +basic_fields<Allocator>:: +move_assign(basic_fields& other, std::false_type) +{ + clear_all(); + if(alloc_ != other.alloc_) + { + copy_all(other); + other.clear_all(); + } + else + { + set_ = std::move(other.set_); + list_ = std::move(other.list_); + method_ = other.method_; + target_or_reason_ = other.target_or_reason_; + other.method_.clear(); + other.target_or_reason_.clear(); + } +} + +template<class Allocator> +inline +void +basic_fields<Allocator>:: +copy_assign(basic_fields const& other, std::true_type) +{ + clear_all(); + alloc_ = other.alloc_; + copy_all(other); +} + +template<class Allocator> +inline +void +basic_fields<Allocator>:: +copy_assign(basic_fields const& other, std::false_type) +{ + clear_all(); + copy_all(other); +} + +template<class Allocator> +inline +void +basic_fields<Allocator>:: +swap(basic_fields& other, std::true_type) +{ + using std::swap; + swap(alloc_, other.alloc_); + swap(set_, other.set_); + swap(list_, other.list_); + swap(method_, other.method_); + swap(target_or_reason_, other.target_or_reason_); +} + +template<class Allocator> +inline +void +basic_fields<Allocator>:: +swap(basic_fields& other, std::false_type) +{ + BOOST_ASSERT(alloc_ == other.alloc_); + using std::swap; + swap(set_, other.set_); + swap(list_, other.list_); + swap(method_, other.method_); + swap(target_or_reason_, other.target_or_reason_); +} + +} // http +} // beast +} // boost + +#endif diff --git a/boost/beast/http/impl/file_body_win32.ipp b/boost/beast/http/impl/file_body_win32.ipp new file mode 100644 index 0000000000..e11c443ed2 --- /dev/null +++ b/boost/beast/http/impl/file_body_win32.ipp @@ -0,0 +1,584 @@ +// +// 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_FILE_BODY_WIN32_IPP +#define BOOST_BEAST_HTTP_IMPL_FILE_BODY_WIN32_IPP + +#if BOOST_BEAST_USE_WIN32_FILE + +#include <boost/beast/core/bind_handler.hpp> +#include <boost/beast/core/type_traits.hpp> +#include <boost/beast/core/detail/clamp.hpp> +#include <boost/beast/http/serializer.hpp> +#include <boost/asio/associated_allocator.hpp> +#include <boost/asio/associated_executor.hpp> +#include <boost/asio/async_result.hpp> +#include <boost/asio/basic_stream_socket.hpp> +#include <boost/asio/handler_continuation_hook.hpp> +#include <boost/asio/windows/overlapped_ptr.hpp> +#include <boost/make_unique.hpp> +#include <boost/smart_ptr/make_shared_array.hpp> +#include <boost/winapi/basic_types.hpp> +#include <algorithm> +#include <cstring> + +namespace boost { +namespace beast { +namespace http { + +namespace detail { +template<class, class, bool, class> +class write_some_win32_op; +} // detail + +template<> +struct basic_file_body<file_win32> +{ + using file_type = file_win32; + + class writer; + class reader; + + //-------------------------------------------------------------------------- + + class value_type + { + friend class writer; + friend class reader; + friend struct basic_file_body<file_win32>; + + template<class, class, bool, class> + friend class detail::write_some_win32_op; + template< + class Protocol, bool isRequest, class Fields> + friend + std::size_t + write_some( + boost::asio::basic_stream_socket<Protocol>& sock, + serializer<isRequest, + basic_file_body<file_win32>, Fields>& sr, + error_code& ec); + + file_win32 file_; + std::uint64_t size_ = 0; // cached file size + std::uint64_t first_; // starting offset of the range + std::uint64_t last_; // ending offset of the range + + public: + ~value_type() = default; + value_type() = default; + value_type(value_type&& other) = default; + value_type& operator=(value_type&& other) = default; + + bool + is_open() const + { + return file_.is_open(); + } + + std::uint64_t + size() const + { + return size_; + } + + void + close(); + + void + open(char const* path, file_mode mode, error_code& ec); + + void + reset(file_win32&& file, error_code& ec); + }; + + //-------------------------------------------------------------------------- + + class writer + { + template<class, class, bool, class> + friend class detail::write_some_win32_op; + template< + class Protocol, bool isRequest, class Fields> + friend + std::size_t + write_some( + boost::asio::basic_stream_socket<Protocol>& sock, + serializer<isRequest, + basic_file_body<file_win32>, Fields>& sr, + error_code& ec); + + value_type& body_; // The body we are reading from + std::uint64_t pos_; // The current position in the file + char buf_[4096]; // Small buffer for reading + + public: + using const_buffers_type = + boost::asio::const_buffer; + + template<bool isRequest, class Fields> + writer(message<isRequest, + basic_file_body<file_win32>, Fields>& m) + : body_(m.body()) + { + } + + void + init(error_code&) + { + BOOST_ASSERT(body_.file_.is_open()); + pos_ = body_.first_; + } + + boost::optional<std::pair<const_buffers_type, bool>> + get(error_code& ec) + { + std::size_t const n = (std::min)(sizeof(buf_), + beast::detail::clamp(body_.last_ - pos_)); + if(n == 0) + { + ec.assign(0, ec.category()); + return boost::none; + } + auto const nread = body_.file_.read(buf_, n, ec); + if(ec) + return boost::none; + BOOST_ASSERT(nread != 0); + pos_ += nread; + ec.assign(0, ec.category()); + return {{ + {buf_, nread}, // buffer to return. + pos_ < body_.last_}}; // `true` if there are more buffers. + } + }; + + //-------------------------------------------------------------------------- + + class reader + { + value_type& body_; + + public: + template<bool isRequest, class Fields> + explicit + reader(message<isRequest, basic_file_body, Fields>& m) + : body_(m.body()) + { + } + + void + init(boost::optional< + std::uint64_t> const& content_length, + error_code& ec) + { + // VFALCO We could reserve space in the file + boost::ignore_unused(content_length); + BOOST_ASSERT(body_.file_.is_open()); + ec.assign(0, ec.category()); + } + + template<class ConstBufferSequence> + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec) + { + std::size_t nwritten = 0; + for(auto buffer : beast::detail::buffers_range(buffers)) + { + nwritten += body_.file_.write( + buffer.data(), buffer.size(), ec); + if(ec) + return nwritten; + } + ec.assign(0, ec.category()); + return nwritten; + } + + void + finish(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; + + //-------------------------------------------------------------------------- + + static + std::uint64_t + size(value_type const& body) + { + return body.size(); + } +}; + +//------------------------------------------------------------------------------ + +inline +void +basic_file_body<file_win32>:: +value_type:: +close() +{ + error_code ignored; + file_.close(ignored); +} + +inline +void +basic_file_body<file_win32>:: +value_type:: +open(char const* path, file_mode mode, error_code& ec) +{ + file_.open(path, mode, ec); + if(ec) + return; + size_ = file_.size(ec); + if(ec) + { + close(); + return; + } + first_ = 0; + last_ = size_; +} + +inline +void +basic_file_body<file_win32>:: +value_type:: +reset(file_win32&& file, error_code& ec) +{ + if(file_.is_open()) + { + error_code ignored; + file_.close(ignored); + } + file_ = std::move(file); + if(file_.is_open()) + { + size_ = file_.size(ec); + if(ec) + { + close(); + return; + } + first_ = 0; + last_ = size_; + } +} + +//------------------------------------------------------------------------------ + +namespace detail { + +template<class Unsigned> +inline +boost::winapi::DWORD_ +lowPart(Unsigned n) +{ + return static_cast< + boost::winapi::DWORD_>( + n & 0xffffffff); +} + +template<class Unsigned> +inline +boost::winapi::DWORD_ +highPart(Unsigned n, std::true_type) +{ + return static_cast< + boost::winapi::DWORD_>( + (n>>32)&0xffffffff); +} + +template<class Unsigned> +inline +boost::winapi::DWORD_ +highPart(Unsigned, std::false_type) +{ + return 0; +} + +template<class Unsigned> +inline +boost::winapi::DWORD_ +highPart(Unsigned n) +{ + return highPart(n, std::integral_constant< + bool, (sizeof(Unsigned)>4)>{}); +} + +class null_lambda +{ +public: + template<class ConstBufferSequence> + void + operator()(error_code&, + ConstBufferSequence const&) const + { + BOOST_ASSERT(false); + } +}; + +//------------------------------------------------------------------------------ + +#if BOOST_ASIO_HAS_WINDOWS_OVERLAPPED_PTR + +template< + class Protocol, class Handler, + bool isRequest, class Fields> +class write_some_win32_op +{ + boost::asio::basic_stream_socket<Protocol>& sock_; + serializer<isRequest, + basic_file_body<file_win32>, Fields>& sr_; + std::size_t bytes_transferred_ = 0; + Handler h_; + bool header_ = false; + +public: + write_some_win32_op(write_some_win32_op&&) = default; + write_some_win32_op(write_some_win32_op const&) = default; + + template<class DeducedHandler> + write_some_win32_op( + DeducedHandler&& h, + boost::asio::basic_stream_socket<Protocol>& s, + serializer<isRequest, + basic_file_body<file_win32>,Fields>& sr) + : sock_(s) + , sr_(sr) + , h_(std::forward<DeducedHandler>(h)) + { + } + + using allocator_type = + boost::asio::associated_allocator_t<Handler>; + + allocator_type + get_allocator() const noexcept + { + return boost::asio::get_associated_allocator(h_); + } + + using executor_type = + boost::asio::associated_executor_t<Handler, decltype(std::declval< + boost::asio::basic_stream_socket<Protocol>&>().get_executor())>; + + executor_type + get_executor() const noexcept + { + return boost::asio::get_associated_executor( + h_, sock_.get_executor()); + } + + void + operator()(); + + void + operator()( + error_code ec, + std::size_t bytes_transferred = 0); + + friend + bool asio_handler_is_continuation(write_some_win32_op* op) + { + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->h_)); + } +}; + +template< + class Protocol, class Handler, + bool isRequest, class Fields> +void +write_some_win32_op< + Protocol, Handler, isRequest, Fields>:: +operator()() +{ + if(! sr_.is_header_done()) + { + header_ = true; + sr_.split(true); + return detail::async_write_some( + sock_, sr_, std::move(*this)); + } + if(sr_.get().chunked()) + { + return detail::async_write_some( + sock_, sr_, std::move(*this)); + } + auto& r = sr_.reader_impl(); + boost::winapi::DWORD_ const nNumberOfBytesToWrite = + static_cast<boost::winapi::DWORD_>( + (std::min<std::uint64_t>)( + (std::min<std::uint64_t>)(r.body_.last_ - r.pos_, sr_.limit()), + (std::numeric_limits<boost::winapi::DWORD_>::max)())); + boost::asio::windows::overlapped_ptr overlapped{ + sock_.get_executor().context(), *this}; + auto& ov = *overlapped.get(); + ov.Offset = lowPart(r.pos_); + ov.OffsetHigh = highPart(r.pos_); + auto const bSuccess = ::TransmitFile( + sock_.native_handle(), + sr_.get().body().file_.native_handle(), + nNumberOfBytesToWrite, + 0, + overlapped.get(), + nullptr, + 0); + auto const dwError = ::GetLastError(); + if(! bSuccess && dwError != + boost::winapi::ERROR_IO_PENDING_) + { + // VFALCO This needs review, is 0 the right number? + // completed immediately (with error?) + overlapped.complete(error_code{static_cast<int>( + boost::winapi::GetLastError()), + system_category()}, 0); + return; + } + overlapped.release(); +} + +template< + class Protocol, class Handler, + bool isRequest, class Fields> +void +write_some_win32_op< + Protocol, Handler, isRequest, Fields>:: +operator()( + error_code ec, std::size_t bytes_transferred) +{ + bytes_transferred_ += bytes_transferred; + if(! ec) + { + if(header_) + { + header_ = false; + return (*this)(); + } + auto& r = sr_.reader_impl(); + r.pos_ += bytes_transferred; + BOOST_ASSERT(r.pos_ <= r.body_.last_); + if(r.pos_ >= r.body_.last_) + { + sr_.next(ec, null_lambda{}); + BOOST_ASSERT(! ec); + BOOST_ASSERT(sr_.is_done()); + } + } + h_(ec, bytes_transferred_); +} + +#endif + +} // detail + +//------------------------------------------------------------------------------ + +template<class Protocol, bool isRequest, class Fields> +std::size_t +write_some( + boost::asio::basic_stream_socket<Protocol>& sock, + serializer<isRequest, + basic_file_body<file_win32>, Fields>& sr, + error_code& ec) +{ + if(! sr.is_header_done()) + { + sr.split(true); + auto const bytes_transferred = + detail::write_some(sock, sr, ec); + if(ec) + return bytes_transferred; + return bytes_transferred; + } + if(sr.get().chunked()) + { + auto const bytes_transferred = + detail::write_some(sock, sr, ec); + if(ec) + return bytes_transferred; + return bytes_transferred; + } + auto& r = sr.reader_impl(); + r.body_.file_.seek(r.pos_, ec); + if(ec) + return 0; + boost::winapi::DWORD_ const nNumberOfBytesToWrite = + static_cast<boost::winapi::DWORD_>( + (std::min<std::uint64_t>)( + (std::min<std::uint64_t>)(r.body_.last_ - r.pos_, sr.limit()), + (std::numeric_limits<boost::winapi::DWORD_>::max)())); + auto const bSuccess = ::TransmitFile( + sock.native_handle(), + r.body_.file_.native_handle(), + nNumberOfBytesToWrite, + 0, + nullptr, + nullptr, + 0); + if(! bSuccess) + { + ec.assign(static_cast<int>( + boost::winapi::GetLastError()), + system_category()); + return 0; + } + r.pos_ += nNumberOfBytesToWrite; + BOOST_ASSERT(r.pos_ <= r.body_.last_); + if(r.pos_ < r.body_.last_) + { + ec.assign(0, ec.category()); + } + else + { + sr.next(ec, detail::null_lambda{}); + BOOST_ASSERT(! ec); + BOOST_ASSERT(sr.is_done()); + } + return nNumberOfBytesToWrite; +} + +#if BOOST_ASIO_HAS_WINDOWS_OVERLAPPED_PTR + +template< + class Protocol, + bool isRequest, class Fields, + class WriteHandler> +BOOST_ASIO_INITFN_RESULT_TYPE( + WriteHandler, void(error_code, std::size_t)) +async_write_some( + boost::asio::basic_stream_socket<Protocol>& sock, + serializer<isRequest, + basic_file_body<file_win32>, Fields>& sr, + WriteHandler&& handler) +{ + boost::asio::async_completion<WriteHandler, + void(error_code)> init{handler}; + detail::write_some_win32_op< + Protocol, + BOOST_ASIO_HANDLER_TYPE(WriteHandler, + void(error_code, std::size_t)), + isRequest, Fields>{ + init.completion_handler, sock, sr}(); + return init.result.get(); +} + +#endif + +} // http +} // beast +} // boost + +#endif + +#endif diff --git a/boost/beast/http/impl/message.ipp b/boost/beast/http/impl/message.ipp new file mode 100644 index 0000000000..64d96eb6f1 --- /dev/null +++ b/boost/beast/http/impl/message.ipp @@ -0,0 +1,437 @@ +// +// 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_MESSAGE_IPP +#define BOOST_BEAST_HTTP_IMPL_MESSAGE_IPP + +#include <boost/beast/core/error.hpp> +#include <boost/beast/core/detail/type_traits.hpp> +#include <boost/assert.hpp> +#include <boost/throw_exception.hpp> +#include <stdexcept> + +namespace boost { +namespace beast { +namespace http { + +template<class Fields> +template<class Arg1, class... ArgN, class> +header<true, Fields>:: +header(Arg1&& arg1, ArgN&&... argn) + : Fields(std::forward<Arg1>(arg1), + std::forward<ArgN>(argn)...) +{ +} + +template<class Fields> +inline +verb +header<true, Fields>:: +method() const +{ + return method_; +} + +template<class Fields> +void +header<true, Fields>:: +method(verb v) +{ + if(v == verb::unknown) + BOOST_THROW_EXCEPTION( + std::invalid_argument{"unknown method"}); + method_ = v; + this->set_method_impl({}); +} + +template<class Fields> +string_view +header<true, Fields>:: +method_string() const +{ + if(method_ != verb::unknown) + return to_string(method_); + return this->get_method_impl(); +} + +template<class Fields> +void +header<true, Fields>:: +method_string(string_view s) +{ + method_ = string_to_verb(s); + if(method_ != verb::unknown) + this->set_method_impl({}); + else + this->set_method_impl(s); +} + +template<class Fields> +inline +string_view +header<true, Fields>:: +target() const +{ + return this->get_target_impl(); +} + +template<class Fields> +inline +void +header<true, Fields>:: +target(string_view s) +{ + this->set_target_impl(s); +} + +template<class Fields> +void +swap( + header<true, Fields>& h1, + header<true, Fields>& h2) +{ + using std::swap; + swap( + static_cast<Fields&>(h1), + static_cast<Fields&>(h2)); + swap(h1.version_, h2.version_); + swap(h1.method_, h2.method_); +} + +//------------------------------------------------------------------------------ + +template<class Fields> +template<class Arg1, class... ArgN, class> +header<false, Fields>:: +header(Arg1&& arg1, ArgN&&... argn) + : Fields(std::forward<Arg1>(arg1), + std::forward<ArgN>(argn)...) +{ +} + +template<class Fields> +inline +status +header<false, Fields>:: +result() const +{ + return int_to_status( + static_cast<int>(result_)); +} + +template<class Fields> +inline +void +header<false, Fields>:: +result(status v) +{ + result_ = v; +} + +template<class Fields> +inline +void +header<false, Fields>:: +result(unsigned v) +{ + if(v > 999) + BOOST_THROW_EXCEPTION( + std::invalid_argument{ + "invalid status-code"}); + result_ = static_cast<status>(v); +} + +template<class Fields> +inline +unsigned +header<false, Fields>:: +result_int() const +{ + return static_cast<unsigned>(result_); +} + +template<class Fields> +string_view +header<false, Fields>:: +reason() const +{ + auto const s = this->get_reason_impl(); + if(! s.empty()) + return s; + return obsolete_reason(result_); +} + +template<class Fields> +inline +void +header<false, Fields>:: +reason(string_view s) +{ + this->set_reason_impl(s); +} + +template<class Fields> +void +swap( + header<false, Fields>& h1, + header<false, Fields>& h2) +{ + using std::swap; + swap( + static_cast<Fields&>(h1), + static_cast<Fields&>(h2)); + swap(h1.version_, h2.version_); + swap(h1.result_, h2.result_); +} + +//------------------------------------------------------------------------------ + +template<bool isRequest, class Body, class Fields> +template<class... BodyArgs> +message<isRequest, Body, Fields>:: +message(header_type&& h, BodyArgs&&... body_args) + : header_type(std::move(h)) + , beast::detail::empty_base_optimization< + typename Body::value_type>( + std::forward<BodyArgs>(body_args)...) +{ +} + +template<bool isRequest, class Body, class Fields> +template<class... BodyArgs> +message<isRequest, Body, Fields>:: +message(header_type const& h, BodyArgs&&... body_args) + : header_type(h) + , beast::detail::empty_base_optimization< + typename Body::value_type>( + std::forward<BodyArgs>(body_args)...) +{ +} + +template<bool isRequest, class Body, class Fields> +template<class Version, class> +message<isRequest, Body, Fields>:: +message(verb method, string_view target, Version version) + : header_type(method, target, version) +{ +} + +template<bool isRequest, class Body, class Fields> +template<class Version, class BodyArg, class> +message<isRequest, Body, Fields>:: +message(verb method, string_view target, + Version version, BodyArg&& body_arg) + : header_type(method, target, version) + , beast::detail::empty_base_optimization< + typename Body::value_type>( + std::forward<BodyArg>(body_arg)) +{ +} + +template<bool isRequest, class Body, class Fields> +template<class Version, class BodyArg, class FieldsArg, class> +message<isRequest, Body, Fields>:: +message( + verb method, string_view target, Version version, + BodyArg&& body_arg, + FieldsArg&& fields_arg) + : header_type(method, target, version, + std::forward<FieldsArg>(fields_arg)) + , beast::detail::empty_base_optimization< + typename Body::value_type>( + std::forward<BodyArg>(body_arg)) +{ +} + +template<bool isRequest, class Body, class Fields> +template<class Version, class> +message<isRequest, Body, Fields>:: +message(status result, Version version) + : header_type(result, version) +{ +} + +template<bool isRequest, class Body, class Fields> +template<class Version, class BodyArg, class> +message<isRequest, Body, Fields>:: +message(status result, Version version, + BodyArg&& body_arg) + : header_type(result, version) + , beast::detail::empty_base_optimization< + typename Body::value_type>( + std::forward<BodyArg>(body_arg)) +{ +} + +template<bool isRequest, class Body, class Fields> +template<class Version, class BodyArg, class FieldsArg, class> +message<isRequest, Body, Fields>:: +message(status result, Version version, + BodyArg&& body_arg, FieldsArg&& fields_arg) + : header_type(result, version, + std::forward<FieldsArg>(fields_arg)) + , beast::detail::empty_base_optimization< + typename Body::value_type>( + std::forward<BodyArg>(body_arg)) +{ +} + +template<bool isRequest, class Body, class Fields> +message<isRequest, Body, Fields>:: +message(std::piecewise_construct_t) +{ +} + +template<bool isRequest, class Body, class Fields> +template<class... BodyArgs> +message<isRequest, Body, Fields>:: +message(std::piecewise_construct_t, + std::tuple<BodyArgs...> body_args) + : message(std::piecewise_construct, + body_args, + beast::detail::make_index_sequence< + sizeof...(BodyArgs)>{}) +{ +} + +template<bool isRequest, class Body, class Fields> +template<class... BodyArgs, class... FieldsArgs> +message<isRequest, Body, Fields>:: +message(std::piecewise_construct_t, + std::tuple<BodyArgs...> body_args, + std::tuple<FieldsArgs...> fields_args) + : message(std::piecewise_construct, + body_args, + fields_args, + beast::detail::make_index_sequence< + sizeof...(BodyArgs)>{}, + beast::detail::make_index_sequence< + sizeof...(FieldsArgs)>{}) +{ +} + +template<bool isRequest, class Body, class Fields> +void +message<isRequest, Body, Fields>:: +chunked(bool value) +{ + this->set_chunked_impl(value); + this->set_content_length_impl(boost::none); +} + +template<bool isRequest, class Body, class Fields> +void +message<isRequest, Body, Fields>:: +content_length( + boost::optional<std::uint64_t> const& value) +{ + this->set_content_length_impl(value); + this->set_chunked_impl(false); +} + +template<bool isRequest, class Body, class Fields> +boost::optional<std::uint64_t> +message<isRequest, Body, Fields>:: +payload_size() const +{ + return payload_size(detail::is_body_sized<Body>{}); +} + +template<bool isRequest, class Body, class Fields> +bool +message<isRequest, Body, Fields>:: +need_eof(std::false_type) const +{ + // VFALCO Do we need a way to let the caller say "the body is intentionally skipped"? + if( this->result() == status::no_content || + this->result() == status::not_modified || + to_status_class(this->result()) == + status_class::informational || + has_content_length() || + chunked()) + return ! keep_alive(); + return true; +} + +template<bool isRequest, class Body, class Fields> +void +message<isRequest, Body, Fields>:: +prepare_payload(std::true_type) +{ + auto const n = payload_size(); + if(this->method() == verb::trace && (! n || *n > 0)) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid request body"}); + if(n) + { + if(*n > 0 || + this->method() == verb::options || + this->method() == verb::put || + this->method() == verb::post) + { + this->content_length(n); + } + else + { + this->chunked(false); + } + } + else if(this->version() == 11) + { + this->chunked(true); + } + else + { + this->chunked(false); + } +} + +template<bool isRequest, class Body, class Fields> +void +message<isRequest, Body, Fields>:: +prepare_payload(std::false_type) +{ + auto const n = payload_size(); + if( (! n || *n > 0) && ( + (status_class(this->result()) == status_class::informational || + this->result() == status::no_content || + this->result() == status::not_modified))) + { + // The response body MUST be empty for this case + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid response body"}); + } + if(n) + this->content_length(n); + else if(this->version() == 11) + this->chunked(true); + else + this->chunked(false); +} + +//------------------------------------------------------------------------------ + +template<bool isRequest, class Body, class Fields> +void +swap( + message<isRequest, Body, Fields>& m1, + message<isRequest, Body, Fields>& m2) +{ + using std::swap; + swap( + static_cast<header<isRequest, Fields>&>(m1), + static_cast<header<isRequest, Fields>&>(m2)); + swap(m1.body(), m2.body()); +} + +} // http +} // beast +} // boost + +#endif diff --git a/boost/beast/http/impl/parser.ipp b/boost/beast/http/impl/parser.ipp new file mode 100644 index 0000000000..99745da3f0 --- /dev/null +++ b/boost/beast/http/impl/parser.ipp @@ -0,0 +1,56 @@ +// +// 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_PARSER_IPP +#define BOOST_BEAST_HTTP_IMPL_PARSER_IPP + +#include <boost/throw_exception.hpp> +#include <stdexcept> + +namespace boost { +namespace beast { +namespace http { + +template<bool isRequest, class Body, class Allocator> +parser<isRequest, Body, Allocator>:: +parser() + : wr_(m_) +{ +} + +template<bool isRequest, class Body, class Allocator> +template<class Arg1, class... ArgN, class> +parser<isRequest, Body, Allocator>:: +parser(Arg1&& arg1, ArgN&&... argn) + : m_(std::forward<Arg1>(arg1), + std::forward<ArgN>(argn)...) + , wr_(m_) +{ + m_.clear(); +} + +template<bool isRequest, class Body, class Allocator> +template<class OtherBody, class... Args, class> +parser<isRequest, Body, Allocator>:: +parser(parser<isRequest, OtherBody, Allocator>&& other, + Args&&... args) + : base_type(std::move(other)) + , m_(other.release(), std::forward<Args>(args)...) + , wr_(m_) +{ + if(other.rd_inited_) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "moved-from parser has a body"}); +} + +} // http +} // beast +} // boost + +#endif diff --git a/boost/beast/http/impl/read.ipp b/boost/beast/http/impl/read.ipp new file mode 100644 index 0000000000..5ecfae7d8b --- /dev/null +++ b/boost/beast/http/impl/read.ipp @@ -0,0 +1,821 @@ +// +// 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_READ_IPP_HPP +#define BOOST_BEAST_HTTP_IMPL_READ_IPP_HPP + +#include <boost/beast/http/type_traits.hpp> +#include <boost/beast/http/error.hpp> +#include <boost/beast/http/parser.hpp> +#include <boost/beast/http/read.hpp> +#include <boost/beast/core/bind_handler.hpp> +#include <boost/beast/core/handler_ptr.hpp> +#include <boost/beast/core/read_size.hpp> +#include <boost/beast/core/type_traits.hpp> +#include <boost/asio/associated_allocator.hpp> +#include <boost/asio/associated_executor.hpp> +#include <boost/asio/handler_continuation_hook.hpp> +#include <boost/asio/post.hpp> +#include <boost/assert.hpp> +#include <boost/config.hpp> +#include <boost/optional.hpp> +#include <boost/throw_exception.hpp> + +namespace boost { +namespace beast { +namespace http { + +namespace detail { + +//------------------------------------------------------------------------------ + +template<class Stream, class DynamicBuffer, + bool isRequest, class Derived, class Handler> +class read_some_op +{ + int state_ = 0; + Stream& s_; + DynamicBuffer& b_; + basic_parser<isRequest, Derived>& p_; + boost::optional<typename + DynamicBuffer::mutable_buffers_type> mb_; + std::size_t bytes_transferred_ = 0; + Handler h_; + +public: + read_some_op(read_some_op&&) = default; + read_some_op(read_some_op const&) = default; + + template<class DeducedHandler> + read_some_op(DeducedHandler&& h, Stream& s, + DynamicBuffer& b, basic_parser<isRequest, Derived>& p) + : s_(s) + , b_(b) + , p_(p) + , h_(std::forward<DeducedHandler>(h)) + { + } + + using allocator_type = + boost::asio::associated_allocator_t<Handler>; + + allocator_type + get_allocator() const noexcept + { + return boost::asio::get_associated_allocator(h_); + } + + using executor_type = boost::asio::associated_executor_t< + Handler, decltype(std::declval<Stream&>().get_executor())>; + + executor_type + get_executor() const noexcept + { + return boost::asio::get_associated_executor( + h_, s_.get_executor()); + } + + void + operator()( + error_code ec = {}, + std::size_t bytes_transferred = 0); + + friend + bool asio_handler_is_continuation(read_some_op* op) + { + using boost::asio::asio_handler_is_continuation; + return op->state_ >= 2 ? true : + asio_handler_is_continuation( + std::addressof(op->h_)); + } +}; + +template<class Stream, class DynamicBuffer, + bool isRequest, class Derived, class Handler> +void +read_some_op<Stream, DynamicBuffer, + isRequest, Derived, Handler>:: +operator()( + error_code ec, + std::size_t bytes_transferred) +{ + switch(state_) + { + case 0: + state_ = 1; + if(b_.size() == 0) + goto do_read; + goto do_parse; + + case 1: + state_ = 2; + case 2: + if(ec == boost::asio::error::eof) + { + BOOST_ASSERT(bytes_transferred == 0); + if(p_.got_some()) + { + // caller sees EOF on next read + ec.assign(0, ec.category()); + p_.put_eof(ec); + if(ec) + goto upcall; + BOOST_ASSERT(p_.is_done()); + goto upcall; + } + ec = error::end_of_stream; + goto upcall; + } + if(ec) + goto upcall; + b_.commit(bytes_transferred); + + do_parse: + { + auto const used = p_.put(b_.data(), ec); + bytes_transferred_ += used; + b_.consume(used); + if(! ec || ec != http::error::need_more) + goto do_upcall; + ec.assign(0, ec.category()); + } + + do_read: + try + { + mb_.emplace(b_.prepare( + read_size_or_throw(b_, 65536))); + } + catch(std::length_error const&) + { + ec = error::buffer_overflow; + goto do_upcall; + } + return s_.async_read_some(*mb_, std::move(*this)); + + do_upcall: + if(state_ >= 2) + goto upcall; + state_ = 3; + return boost::asio::post( + s_.get_executor(), + bind_handler(std::move(*this), ec, 0)); + + case 3: + break; + } +upcall: + h_(ec, bytes_transferred_); +} + +//------------------------------------------------------------------------------ + +struct parser_is_done +{ + template<bool isRequest, class Derived> + bool + operator()(basic_parser< + isRequest, Derived> const& p) const + { + return p.is_done(); + } +}; + +struct parser_is_header_done +{ + template<bool isRequest, class Derived> + bool + operator()(basic_parser< + isRequest, Derived> const& p) const + { + return p.is_header_done(); + } +}; + +template<class Stream, class DynamicBuffer, + bool isRequest, class Derived, class Condition, + class Handler> +class read_op +{ + int state_ = 0; + Stream& s_; + DynamicBuffer& b_; + basic_parser<isRequest, Derived>& p_; + std::size_t bytes_transferred_ = 0; + Handler h_; + +public: + read_op(read_op&&) = default; + read_op(read_op const&) = default; + + template<class DeducedHandler> + read_op(DeducedHandler&& h, Stream& s, + DynamicBuffer& b, basic_parser<isRequest, + Derived>& p) + : s_(s) + , b_(b) + , p_(p) + , h_(std::forward<DeducedHandler>(h)) + { + } + + using allocator_type = + boost::asio::associated_allocator_t<Handler>; + + allocator_type + get_allocator() const noexcept + { + return boost::asio::get_associated_allocator(h_); + } + + using executor_type = boost::asio::associated_executor_t< + Handler, decltype(std::declval<Stream&>().get_executor())>; + + executor_type + get_executor() const noexcept + { + return boost::asio::get_associated_executor( + h_, s_.get_executor()); + } + + void + operator()( + error_code ec = {}, + std::size_t bytes_transferred = 0); + + friend + bool asio_handler_is_continuation(read_op* op) + { + using boost::asio::asio_handler_is_continuation; + return op->state_ >= 3 ? true : + asio_handler_is_continuation( + std::addressof(op->h_)); + } +}; + +template<class Stream, class DynamicBuffer, + bool isRequest, class Derived, class Condition, + class Handler> +void +read_op<Stream, DynamicBuffer, + isRequest, Derived, Condition, Handler>:: +operator()( + error_code ec, + std::size_t bytes_transferred) +{ + switch(state_) + { + case 0: + if(Condition{}(p_)) + { + state_ = 1; + return boost::asio::post( + s_.get_executor(), + bind_handler(std::move(*this), ec)); + } + state_ = 2; + + do_read: + return async_read_some( + s_, b_, p_, std::move(*this)); + + case 1: + goto upcall; + + case 2: + case 3: + if(ec) + goto upcall; + bytes_transferred_ += bytes_transferred; + if(Condition{}(p_)) + goto upcall; + state_ = 3; + goto do_read; + } +upcall: + h_(ec, bytes_transferred_); +} + +//------------------------------------------------------------------------------ + +template<class Stream, class DynamicBuffer, + bool isRequest, class Body, class Allocator, + class Handler> +class read_msg_op +{ + using parser_type = + parser<isRequest, Body, Allocator>; + + using message_type = + typename parser_type::value_type; + + struct data + { + int state = 0; + Stream& s; + DynamicBuffer& b; + message_type& m; + parser_type p; + std::size_t bytes_transferred = 0; + + data(Handler&, Stream& s_, + DynamicBuffer& b_, message_type& m_) + : s(s_) + , b(b_) + , m(m_) + , p(std::move(m)) + { + p.eager(true); + } + }; + + handler_ptr<data, Handler> d_; + +public: + read_msg_op(read_msg_op&&) = default; + read_msg_op(read_msg_op const&) = default; + + template<class DeducedHandler, class... Args> + read_msg_op(DeducedHandler&& h, Stream& s, Args&&... args) + : d_(std::forward<DeducedHandler>(h), + s, std::forward<Args>(args)...) + { + } + + using allocator_type = + boost::asio::associated_allocator_t<Handler>; + + allocator_type + get_allocator() const noexcept + { + return boost::asio::get_associated_allocator(d_.handler()); + } + + using executor_type = boost::asio::associated_executor_t< + Handler, decltype(std::declval<Stream&>().get_executor())>; + + executor_type + get_executor() const noexcept + { + return boost::asio::get_associated_executor( + d_.handler(), d_->s.get_executor()); + } + + void + operator()( + error_code ec = {}, + std::size_t bytes_transferred = 0); + + friend + bool asio_handler_is_continuation(read_msg_op* op) + { + using boost::asio::asio_handler_is_continuation; + return op->d_->state >= 2 ? true : + asio_handler_is_continuation( + std::addressof(op->d_.handler())); + } +}; + +template<class Stream, class DynamicBuffer, + bool isRequest, class Body, class Allocator, + class Handler> +void +read_msg_op<Stream, DynamicBuffer, + isRequest, Body, Allocator, Handler>:: +operator()( + error_code ec, + std::size_t bytes_transferred) +{ + auto& d = *d_; + switch(d.state) + { + case 0: + d.state = 1; + + do_read: + return async_read_some( + d.s, d.b, d.p, std::move(*this)); + + case 1: + case 2: + if(ec) + goto upcall; + d.bytes_transferred += + bytes_transferred; + if(d.p.is_done()) + { + d.m = d.p.release(); + goto upcall; + } + d.state = 2; + goto do_read; + } +upcall: + bytes_transferred = d.bytes_transferred; + d_.invoke(ec, bytes_transferred); +} + +} // detail + +//------------------------------------------------------------------------------ + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +std::size_t +read_some( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser<isRequest, Derived>& parser) +{ + static_assert(is_sync_read_stream<SyncReadStream>::value, + "SyncReadStream requirements not met"); + static_assert( + boost::asio::is_dynamic_buffer<DynamicBuffer>::value, + "DynamicBuffer requirements not met"); + BOOST_ASSERT(! parser.is_done()); + error_code ec; + auto const bytes_transferred = + read_some(stream, buffer, parser, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return bytes_transferred; +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +std::size_t +read_some( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser<isRequest, Derived>& parser, + error_code& ec) +{ + static_assert(is_sync_read_stream<SyncReadStream>::value, + "SyncReadStream requirements not met"); + static_assert( + boost::asio::is_dynamic_buffer<DynamicBuffer>::value, + "DynamicBuffer requirements not met"); + BOOST_ASSERT(! parser.is_done()); + std::size_t bytes_transferred = 0; + if(buffer.size() == 0) + goto do_read; + for(;;) + { + // invoke parser + { + auto const n = parser.put(buffer.data(), ec); + bytes_transferred += n; + buffer.consume(n); + if(! ec) + break; + if(ec != http::error::need_more) + break; + } + do_read: + boost::optional<typename + DynamicBuffer::mutable_buffers_type> b; + try + { + b.emplace(buffer.prepare( + read_size_or_throw(buffer, 65536))); + } + catch(std::length_error const&) + { + ec = error::buffer_overflow; + return bytes_transferred; + } + auto const n = stream.read_some(*b, ec); + if(ec == boost::asio::error::eof) + { + BOOST_ASSERT(n == 0); + if(parser.got_some()) + { + // caller sees EOF on next read + parser.put_eof(ec); + if(ec) + break; + BOOST_ASSERT(parser.is_done()); + break; + } + ec = error::end_of_stream; + break; + } + if(ec) + break; + buffer.commit(n); + } + return bytes_transferred; +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +BOOST_ASIO_INITFN_RESULT_TYPE( + ReadHandler, void(error_code, std::size_t)) +async_read_some( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser<isRequest, Derived>& parser, + ReadHandler&& handler) +{ + static_assert(is_async_read_stream<AsyncReadStream>::value, + "AsyncReadStream requirements not met"); + static_assert( + boost::asio::is_dynamic_buffer<DynamicBuffer>::value, + "DynamicBuffer requirements not met"); + BOOST_ASSERT(! parser.is_done()); + boost::asio::async_completion<ReadHandler, + void(error_code, std::size_t)> init{handler}; + detail::read_some_op<AsyncReadStream, + DynamicBuffer, isRequest, Derived, BOOST_ASIO_HANDLER_TYPE( + ReadHandler, void(error_code, std::size_t))>{ + init.completion_handler, stream, buffer, parser}(); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +std::size_t +read_header( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser<isRequest, Derived>& parser) +{ + static_assert(is_sync_read_stream<SyncReadStream>::value, + "SyncReadStream requirements not met"); + static_assert( + boost::asio::is_dynamic_buffer<DynamicBuffer>::value, + "DynamicBuffer requirements not met"); + error_code ec; + auto const bytes_transferred = + read_header(stream, buffer, parser, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return bytes_transferred; +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +std::size_t +read_header( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser<isRequest, Derived>& parser, + error_code& ec) +{ + static_assert(is_sync_read_stream<SyncReadStream>::value, + "SyncReadStream requirements not met"); + static_assert( + boost::asio::is_dynamic_buffer<DynamicBuffer>::value, + "DynamicBuffer requirements not met"); + parser.eager(false); + if(parser.is_header_done()) + { + ec.assign(0, ec.category()); + return 0; + } + std::size_t bytes_transferred = 0; + do + { + bytes_transferred += read_some( + stream, buffer, parser, ec); + if(ec) + return bytes_transferred; + } + while(! parser.is_header_done()); + return bytes_transferred; +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +BOOST_ASIO_INITFN_RESULT_TYPE( + ReadHandler, void(error_code, std::size_t)) +async_read_header( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser<isRequest, Derived>& parser, + ReadHandler&& handler) +{ + static_assert(is_async_read_stream<AsyncReadStream>::value, + "AsyncReadStream requirements not met"); + static_assert( + boost::asio::is_dynamic_buffer<DynamicBuffer>::value, + "DynamicBuffer requirements not met"); + parser.eager(false); + boost::asio::async_completion<ReadHandler, + void(error_code, std::size_t)> init{handler}; + detail::read_op<AsyncReadStream, DynamicBuffer, + isRequest, Derived, detail::parser_is_header_done, + BOOST_ASIO_HANDLER_TYPE(ReadHandler, void(error_code, std::size_t))>{ + init.completion_handler, stream, buffer, parser}(); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +std::size_t +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser<isRequest, Derived>& parser) +{ + static_assert(is_sync_read_stream<SyncReadStream>::value, + "SyncReadStream requirements not met"); + static_assert( + boost::asio::is_dynamic_buffer<DynamicBuffer>::value, + "DynamicBuffer requirements not met"); + error_code ec; + auto const bytes_transferred = + read(stream, buffer, parser, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return bytes_transferred; +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +std::size_t +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser<isRequest, Derived>& parser, + error_code& ec) +{ + static_assert(is_sync_read_stream<SyncReadStream>::value, + "SyncReadStream requirements not met"); + static_assert( + boost::asio::is_dynamic_buffer<DynamicBuffer>::value, + "DynamicBuffer requirements not met"); + parser.eager(true); + if(parser.is_done()) + { + ec.assign(0, ec.category()); + return 0; + } + std::size_t bytes_transferred = 0; + do + { + bytes_transferred += read_some( + stream, buffer, parser, ec); + if(ec) + return bytes_transferred; + } + while(! parser.is_done()); + return bytes_transferred; +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +BOOST_ASIO_INITFN_RESULT_TYPE( + ReadHandler, void(error_code, std::size_t)) +async_read( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser<isRequest, Derived>& parser, + ReadHandler&& handler) +{ + static_assert(is_async_read_stream<AsyncReadStream>::value, + "AsyncReadStream requirements not met"); + static_assert( + boost::asio::is_dynamic_buffer<DynamicBuffer>::value, + "DynamicBuffer requirements not met"); + parser.eager(true); + boost::asio::async_completion< + ReadHandler, + void(error_code, std::size_t)> init{handler}; + detail::read_op<AsyncReadStream, DynamicBuffer, + isRequest, Derived, detail::parser_is_done, + BOOST_ASIO_HANDLER_TYPE(ReadHandler, void(error_code, std::size_t))>{ + init.completion_handler, stream, buffer, parser}(); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Allocator> +std::size_t +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + message<isRequest, Body, basic_fields<Allocator>>& msg) +{ + static_assert(is_sync_read_stream<SyncReadStream>::value, + "SyncReadStream requirements not met"); + static_assert( + boost::asio::is_dynamic_buffer<DynamicBuffer>::value, + "DynamicBuffer requirements not met"); + static_assert(is_body<Body>::value, + "Body requirements not met"); + static_assert(is_body_reader<Body>::value, + "BodyReader requirements not met"); + error_code ec; + auto const bytes_transferred = + read(stream, buffer, msg, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return bytes_transferred; +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Allocator> +std::size_t +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + message<isRequest, Body, basic_fields<Allocator>>& msg, + error_code& ec) +{ + static_assert(is_sync_read_stream<SyncReadStream>::value, + "SyncReadStream requirements not met"); + static_assert( + boost::asio::is_dynamic_buffer<DynamicBuffer>::value, + "DynamicBuffer requirements not met"); + static_assert(is_body<Body>::value, + "Body requirements not met"); + static_assert(is_body_reader<Body>::value, + "BodyReader requirements not met"); + parser<isRequest, Body, Allocator> p{std::move(msg)}; + p.eager(true); + auto const bytes_transferred = + read(stream, buffer, p.base(), ec); + if(ec) + return bytes_transferred; + msg = p.release(); + return bytes_transferred; +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Allocator, + class ReadHandler> +BOOST_ASIO_INITFN_RESULT_TYPE( + ReadHandler, void(error_code, std::size_t)) +async_read( + AsyncReadStream& stream, + DynamicBuffer& buffer, + message<isRequest, Body, basic_fields<Allocator>>& msg, + ReadHandler&& handler) +{ + static_assert(is_async_read_stream<AsyncReadStream>::value, + "AsyncReadStream requirements not met"); + static_assert( + boost::asio::is_dynamic_buffer<DynamicBuffer>::value, + "DynamicBuffer requirements not met"); + static_assert(is_body<Body>::value, + "Body requirements not met"); + static_assert(is_body_reader<Body>::value, + "BodyReader requirements not met"); + boost::asio::async_completion< + ReadHandler, + void(error_code, std::size_t)> init{handler}; + detail::read_msg_op< + AsyncReadStream, + DynamicBuffer, + isRequest, Body, Allocator, + BOOST_ASIO_HANDLER_TYPE( + ReadHandler, void(error_code, std::size_t))>{ + init.completion_handler, stream, buffer, msg}(); + return init.result.get(); +} + +} // http +} // beast +} // boost + +#endif diff --git a/boost/beast/http/impl/rfc7230.ipp b/boost/beast/http/impl/rfc7230.ipp new file mode 100644 index 0000000000..96ec902ceb --- /dev/null +++ b/boost/beast/http/impl/rfc7230.ipp @@ -0,0 +1,572 @@ +// +// 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_RFC7230_IPP +#define BOOST_BEAST_HTTP_IMPL_RFC7230_IPP + +#include <boost/beast/http/detail/rfc7230.hpp> +#include <iterator> + +namespace boost { +namespace beast { +namespace http { + +class param_list::const_iterator +{ + using iter_type = string_view::const_iterator; + + std::string s_; + detail::param_iter pi_; + +public: + using value_type = param_list::value_type; + using pointer = value_type const*; + using reference = value_type const&; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + const_iterator() = default; + + bool + operator==(const_iterator const& other) const + { + return + other.pi_.it == pi_.it && + other.pi_.last == pi_.last && + other.pi_.first == pi_.first; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return pi_.v; + } + + pointer + operator->() const + { + return &*(*this); + } + + const_iterator& + operator++() + { + increment(); + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + +private: + friend class param_list; + + const_iterator(iter_type first, iter_type last) + { + pi_.it = first; + pi_.first = first; + pi_.last = last; + increment(); + } + + template<class = void> + static + std::string + unquote(string_view sr); + + template<class = void> + void + increment(); +}; + +inline +auto +param_list:: +begin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +param_list:: +end() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +inline +auto +param_list:: +cbegin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +param_list:: +cend() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +template<class> +std::string +param_list::const_iterator:: +unquote(string_view sr) +{ + std::string s; + s.reserve(sr.size()); + auto it = sr.begin() + 1; + auto end = sr.end() - 1; + while(it != end) + { + if(*it == '\\') + ++it; + s.push_back(*it); + ++it; + } + return s; +} + +template<class> +void +param_list::const_iterator:: +increment() +{ + s_.clear(); + pi_.increment(); + if(pi_.empty()) + { + pi_.it = pi_.last; + pi_.first = pi_.last; + } + else if(! pi_.v.second.empty() && + pi_.v.second.front() == '"') + { + s_ = unquote(pi_.v.second); + pi_.v.second = string_view{ + s_.data(), s_.size()}; + } +} + +//------------------------------------------------------------------------------ + +class ext_list::const_iterator +{ + ext_list::value_type v_; + iter_type it_; + iter_type first_; + iter_type last_; + +public: + using value_type = ext_list::value_type; + using pointer = value_type const*; + using reference = value_type const&; + using difference_type = std::ptrdiff_t; + using iterator_category = std::forward_iterator_tag; + + const_iterator() = default; + + bool + operator==(const_iterator const& other) const + { + return + other.it_ == it_ && + other.first_ == first_ && + other.last_ == last_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return v_; + } + + pointer + operator->() const + { + return &*(*this); + } + + const_iterator& + operator++() + { + increment(); + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + +private: + friend class ext_list; + + const_iterator(iter_type begin, iter_type end) + { + it_ = begin; + first_ = begin; + last_ = end; + increment(); + } + + template<class = void> + void + increment(); +}; + +inline +auto +ext_list:: +begin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +ext_list:: +end() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +inline +auto +ext_list:: +cbegin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +ext_list:: +cend() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +template<class T> +auto +ext_list:: +find(T const& s) -> + const_iterator +{ + return std::find_if(begin(), end(), + [&s](value_type const& v) + { + return iequals(s, v.first); + }); +} + +template<class T> +bool +ext_list:: +exists(T const& s) +{ + return find(s) != end(); +} + +template<class> +void +ext_list::const_iterator:: +increment() +{ + /* + ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] ) + ext = token param-list + param-list = *( OWS ";" OWS param ) + param = token OWS "=" OWS ( token / quoted-string ) + + chunked;a=b;i=j,gzip;windowBits=12 + x,y + ,,,,,chameleon + */ + auto const err = + [&] + { + it_ = last_; + first_ = last_; + }; + auto need_comma = it_ != first_; + v_.first = {}; + first_ = it_; + for(;;) + { + detail::skip_ows(it_, last_); + if(it_ == last_) + return err(); + auto const c = *it_; + if(detail::is_token_char(c)) + { + if(need_comma) + return err(); + auto const p0 = it_; + for(;;) + { + ++it_; + if(it_ == last_) + break; + if(! detail::is_token_char(*it_)) + break; + } + v_.first = string_view{&*p0, + static_cast<std::size_t>(it_ - p0)}; + detail::param_iter pi; + pi.it = it_; + pi.first = it_; + pi.last = last_; + for(;;) + { + pi.increment(); + if(pi.empty()) + break; + } + v_.second = param_list{string_view{&*it_, + static_cast<std::size_t>(pi.it - it_)}}; + it_ = pi.it; + return; + } + if(c != ',') + return err(); + need_comma = false; + ++it_; + } +} + +//------------------------------------------------------------------------------ + +class token_list::const_iterator +{ + token_list::value_type v_; + iter_type it_; + iter_type first_; + iter_type last_; + +public: + using value_type = token_list::value_type; + using pointer = value_type const*; + using reference = value_type const&; + using difference_type = std::ptrdiff_t; + using iterator_category = std::forward_iterator_tag; + + const_iterator() = default; + + bool + operator==(const_iterator const& other) const + { + return + other.it_ == it_ && + other.first_ == first_ && + other.last_ == last_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return v_; + } + + pointer + operator->() const + { + return &*(*this); + } + + const_iterator& + operator++() + { + increment(); + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + +private: + friend class token_list; + + const_iterator(iter_type begin, iter_type end) + { + it_ = begin; + first_ = begin; + last_ = end; + increment(); + } + + template<class = void> + void + increment(); +}; + +inline +auto +token_list:: +begin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +token_list:: +end() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +inline +auto +token_list:: +cbegin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +token_list:: +cend() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +template<class> +void +token_list::const_iterator:: +increment() +{ + /* + token-list = *( "," OWS ) token *( OWS "," [ OWS ext ] ) + */ + auto const err = + [&] + { + it_ = last_; + first_ = last_; + }; + auto need_comma = it_ != first_; + v_ = {}; + first_ = it_; + for(;;) + { + detail::skip_ows(it_, last_); + if(it_ == last_) + return err(); + auto const c = *it_; + if(detail::is_token_char(c)) + { + if(need_comma) + return err(); + auto const p0 = it_; + for(;;) + { + ++it_; + if(it_ == last_) + break; + if(! detail::is_token_char(*it_)) + break; + } + v_ = string_view{&*p0, + static_cast<std::size_t>(it_ - p0)}; + return; + } + if(c != ',') + return err(); + need_comma = false; + ++it_; + } +} + +template<class T> +bool +token_list:: +exists(T const& s) +{ + return std::find_if(begin(), end(), + [&s](value_type const& v) + { + return iequals(s, v); + } + ) != end(); +} + +template<class Policy> +bool +validate_list(detail::basic_parsed_list< + Policy> const& list) +{ + auto const last = list.end(); + auto it = list.begin(); + if(it.error()) + return false; + while(it != last) + { + ++it; + if(it.error()) + return false; + if(it == last) + break; + } + return true; +} + +} // http +} // beast +} // boost + +#endif + diff --git a/boost/beast/http/impl/serializer.ipp b/boost/beast/http/impl/serializer.ipp new file mode 100644 index 0000000000..11cf857845 --- /dev/null +++ b/boost/beast/http/impl/serializer.ipp @@ -0,0 +1,430 @@ +// +// 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_SERIALIZER_IPP +#define BOOST_BEAST_HTTP_IMPL_SERIALIZER_IPP + +#include <boost/beast/core/detail/buffers_ref.hpp> +#include <boost/beast/http/error.hpp> +#include <boost/beast/http/status.hpp> +#include <boost/beast/core/detail/config.hpp> +#include <boost/assert.hpp> +#include <ostream> + +namespace boost { +namespace beast { +namespace http { + +template< + bool isRequest, class Body, class Fields> +void +serializer<isRequest, Body, Fields>:: +frdinit(std::true_type) +{ + frd_.emplace(m_, m_.version(), m_.method()); +} + +template< + bool isRequest, class Body, class Fields> +void +serializer<isRequest, Body, Fields>:: +frdinit(std::false_type) +{ + frd_.emplace(m_, m_.version(), m_.result_int()); +} + +template< + bool isRequest, class Body, class Fields> +template<std::size_t I, class Visit> +inline +void +serializer<isRequest, Body, Fields>:: +do_visit(error_code& ec, Visit& visit) +{ + pv_.template emplace<I>(limit_, v_.template get<I>()); + visit(ec, beast::detail::make_buffers_ref( + pv_.template get<I>())); +} + +//------------------------------------------------------------------------------ + +template< + bool isRequest, class Body, class Fields> +serializer<isRequest, Body, Fields>:: +serializer(value_type& m) + : m_(m) + , rd_(m_) +{ +} + +template< + bool isRequest, class Body, class Fields> +template<class Visit> +void +serializer<isRequest, Body, Fields>:: +next(error_code& ec, Visit&& visit) +{ + using boost::asio::buffer_size; + switch(s_) + { + case do_construct: + { + frdinit(std::integral_constant<bool, + isRequest>{}); + if(m_.chunked()) + goto go_init_c; + s_ = do_init; + BOOST_BEAST_FALLTHROUGH; + } + + case do_init: + { + rd_.init(ec); + if(ec) + return; + if(split_) + goto go_header_only; + auto result = rd_.get(ec); + if(ec == error::need_more) + goto go_header_only; + if(ec) + return; + if(! result) + goto go_header_only; + more_ = result->second; + v_.template emplace<2>( + boost::in_place_init, + frd_->get(), + result->first); + s_ = do_header; + BOOST_BEAST_FALLTHROUGH; + } + + case do_header: + do_visit<2>(ec, visit); + break; + + go_header_only: + v_.template emplace<1>(frd_->get()); + s_ = do_header_only; + BOOST_BEAST_FALLTHROUGH; + case do_header_only: + do_visit<1>(ec, visit); + break; + + case do_body: + s_ = do_body + 1; + BOOST_BEAST_FALLTHROUGH; + + case do_body + 1: + { + auto result = rd_.get(ec); + if(ec) + return; + if(! result) + goto go_complete; + more_ = result->second; + v_.template emplace<3>(result->first); + s_ = do_body + 2; + BOOST_BEAST_FALLTHROUGH; + } + + case do_body + 2: + do_visit<3>(ec, visit); + break; + + //---------------------------------------------------------------------- + + go_init_c: + s_ = do_init_c; + BOOST_BEAST_FALLTHROUGH; + case do_init_c: + { + rd_.init(ec); + if(ec) + return; + if(split_) + goto go_header_only_c; + auto result = rd_.get(ec); + if(ec == error::need_more) + goto go_header_only_c; + if(ec) + return; + if(! result) + goto go_header_only_c; + more_ = result->second; + if(! more_) + { + // do it all in one buffer + v_.template emplace<7>( + boost::in_place_init, + frd_->get(), + buffer_size(result->first), + boost::asio::const_buffer{nullptr, 0}, + chunk_crlf{}, + result->first, + chunk_crlf{}, + detail::chunk_last(), + boost::asio::const_buffer{nullptr, 0}, + chunk_crlf{}); + goto go_all_c; + } + v_.template emplace<4>( + boost::in_place_init, + frd_->get(), + buffer_size(result->first), + boost::asio::const_buffer{nullptr, 0}, + chunk_crlf{}, + result->first, + chunk_crlf{}); + s_ = do_header_c; + BOOST_BEAST_FALLTHROUGH; + } + + case do_header_c: + do_visit<4>(ec, visit); + break; + + go_header_only_c: + v_.template emplace<1>(frd_->get()); + s_ = do_header_only_c; + case do_header_only_c: + do_visit<1>(ec, visit); + break; + + case do_body_c: + s_ = do_body_c + 1; + BOOST_BEAST_FALLTHROUGH; + + case do_body_c + 1: + { + auto result = rd_.get(ec); + if(ec) + return; + if(! result) + goto go_final_c; + more_ = result->second; + if(! more_) + { + // do it all in one buffer + v_.template emplace<6>( + boost::in_place_init, + buffer_size(result->first), + boost::asio::const_buffer{nullptr, 0}, + chunk_crlf{}, + result->first, + chunk_crlf{}, + detail::chunk_last(), + boost::asio::const_buffer{nullptr, 0}, + chunk_crlf{}); + goto go_body_final_c; + } + v_.template emplace<5>( + boost::in_place_init, + buffer_size(result->first), + boost::asio::const_buffer{nullptr, 0}, + chunk_crlf{}, + result->first, + chunk_crlf{}); + s_ = do_body_c + 2; + BOOST_BEAST_FALLTHROUGH; + } + + case do_body_c + 2: + do_visit<5>(ec, visit); + break; + + go_body_final_c: + s_ = do_body_final_c; + BOOST_BEAST_FALLTHROUGH; + case do_body_final_c: + do_visit<6>(ec, visit); + break; + + go_all_c: + s_ = do_all_c; + BOOST_BEAST_FALLTHROUGH; + case do_all_c: + do_visit<7>(ec, visit); + break; + + go_final_c: + case do_final_c: + v_.template emplace<8>( + boost::in_place_init, + detail::chunk_last(), + boost::asio::const_buffer{nullptr, 0}, + chunk_crlf{}); + s_ = do_final_c + 1; + BOOST_BEAST_FALLTHROUGH; + + case do_final_c + 1: + do_visit<8>(ec, visit); + break; + + //---------------------------------------------------------------------- + + default: + case do_complete: + BOOST_ASSERT(false); + break; + + go_complete: + s_ = do_complete; + break; + } +} + +template< + bool isRequest, class Body, class Fields> +void +serializer<isRequest, Body, Fields>:: +consume(std::size_t n) +{ + using boost::asio::buffer_size; + switch(s_) + { + case do_header: + BOOST_ASSERT( + n <= buffer_size(v_.template get<2>())); + v_.template get<2>().consume(n); + if(buffer_size(v_.template get<2>()) > 0) + break; + header_done_ = true; + v_.reset(); + if(! more_) + goto go_complete; + s_ = do_body + 1; + break; + + case do_header_only: + BOOST_ASSERT( + n <= buffer_size(v_.template get<1>())); + v_.template get<1>().consume(n); + if(buffer_size(v_.template get<1>()) > 0) + break; + frd_ = boost::none; + header_done_ = true; + if(! split_) + goto go_complete; + s_ = do_body; + break; + + case do_body + 2: + { + BOOST_ASSERT( + n <= buffer_size(v_.template get<3>())); + v_.template get<3>().consume(n); + if(buffer_size(v_.template get<3>()) > 0) + break; + v_.reset(); + if(! more_) + goto go_complete; + s_ = do_body + 1; + break; + } + + //---------------------------------------------------------------------- + + case do_header_c: + BOOST_ASSERT( + n <= buffer_size(v_.template get<4>())); + v_.template get<4>().consume(n); + if(buffer_size(v_.template get<4>()) > 0) + break; + header_done_ = true; + v_.reset(); + if(more_) + s_ = do_body_c + 1; + else + s_ = do_final_c; + break; + + case do_header_only_c: + { + BOOST_ASSERT( + n <= buffer_size(v_.template get<1>())); + v_.template get<1>().consume(n); + if(buffer_size(v_.template get<1>()) > 0) + break; + frd_ = boost::none; + header_done_ = true; + if(! split_) + { + s_ = do_final_c; + break; + } + s_ = do_body_c; + break; + } + + case do_body_c + 2: + BOOST_ASSERT( + n <= buffer_size(v_.template get<5>())); + v_.template get<5>().consume(n); + if(buffer_size(v_.template get<5>()) > 0) + break; + v_.reset(); + if(more_) + s_ = do_body_c + 1; + else + s_ = do_final_c; + break; + + case do_body_final_c: + { + BOOST_ASSERT( + n <= buffer_size(v_.template get<6>())); + v_.template get<6>().consume(n); + if(buffer_size(v_.template get<6>()) > 0) + break; + v_.reset(); + s_ = do_complete; + break; + } + + case do_all_c: + { + BOOST_ASSERT( + n <= buffer_size(v_.template get<7>())); + v_.template get<7>().consume(n); + if(buffer_size(v_.template get<7>()) > 0) + break; + header_done_ = true; + v_.reset(); + s_ = do_complete; + break; + } + + case do_final_c + 1: + BOOST_ASSERT(buffer_size(v_.template get<8>())); + v_.template get<8>().consume(n); + if(buffer_size(v_.template get<8>()) > 0) + break; + v_.reset(); + goto go_complete; + + //---------------------------------------------------------------------- + + default: + BOOST_ASSERT(false); + case do_complete: + break; + + go_complete: + s_ = do_complete; + break; + } +} + +} // http +} // beast +} // boost + +#endif diff --git a/boost/beast/http/impl/status.ipp b/boost/beast/http/impl/status.ipp new file mode 100644 index 0000000000..1cc8f34b63 --- /dev/null +++ b/boost/beast/http/impl/status.ipp @@ -0,0 +1,252 @@ +// +// 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_STATUS_IPP +#define BOOST_BEAST_HTTP_IMPL_STATUS_IPP + +#include <boost/beast/core/detail/config.hpp> +#include <boost/throw_exception.hpp> + +namespace boost { +namespace beast { +namespace http { +namespace detail { + +template<class = void> +status +int_to_status(unsigned v) +{ + switch(static_cast<status>(v)) + { + // 1xx + case status::continue_: + case status::switching_protocols: + case status::processing: + BOOST_BEAST_FALLTHROUGH; + + // 2xx + case status::ok: + case status::created: + case status::accepted: + case status::non_authoritative_information: + case status::no_content: + case status::reset_content: + case status::partial_content: + case status::multi_status: + case status::already_reported: + case status::im_used: + BOOST_BEAST_FALLTHROUGH; + + // 3xx + case status::multiple_choices: + case status::moved_permanently: + case status::found: + case status::see_other: + case status::not_modified: + case status::use_proxy: + case status::temporary_redirect: + case status::permanent_redirect: + BOOST_BEAST_FALLTHROUGH; + + // 4xx + case status::bad_request: + case status::unauthorized: + case status::payment_required: + case status::forbidden: + case status::not_found: + case status::method_not_allowed: + case status::not_acceptable: + case status::proxy_authentication_required: + case status::request_timeout: + case status::conflict: + case status::gone: + case status::length_required: + case status::precondition_failed: + case status::payload_too_large: + case status::uri_too_long: + case status::unsupported_media_type: + case status::range_not_satisfiable: + case status::expectation_failed: + case status::misdirected_request: + case status::unprocessable_entity: + case status::locked: + case status::failed_dependency: + case status::upgrade_required: + case status::precondition_required: + case status::too_many_requests: + case status::request_header_fields_too_large: + case status::connection_closed_without_response: + case status::unavailable_for_legal_reasons: + case status::client_closed_request: + BOOST_BEAST_FALLTHROUGH; + + // 5xx + case status::internal_server_error: + case status::not_implemented: + case status::bad_gateway: + case status::service_unavailable: + case status::gateway_timeout: + case status::http_version_not_supported: + case status::variant_also_negotiates: + case status::insufficient_storage: + case status::loop_detected: + case status::not_extended: + case status::network_authentication_required: + case status::network_connect_timeout_error: + return static_cast<status>(v); + + default: + break; + } + return status::unknown; +} + +template<class = void> +string_view +status_to_string(unsigned v) +{ + switch(static_cast<status>(v)) + { + // 1xx + case status::continue_: return "Continue"; + case status::switching_protocols: return "Switching Protocols"; + case status::processing: return "Processing"; + + // 2xx + case status::ok: return "OK"; + case status::created: return "Created"; + case status::accepted: return "Accepted"; + case status::non_authoritative_information: return "Non-Authoritative Information"; + case status::no_content: return "No Content"; + case status::reset_content: return "Reset Content"; + case status::partial_content: return "Partial Content"; + case status::multi_status: return "Multi-Status"; + case status::already_reported: return "Already Reported"; + case status::im_used: return "IM Used"; + + // 3xx + case status::multiple_choices: return "Multiple Choices"; + case status::moved_permanently: return "Moved Permanently"; + case status::found: return "Found"; + case status::see_other: return "See Other"; + case status::not_modified: return "Not Modified"; + case status::use_proxy: return "Use Proxy"; + case status::temporary_redirect: return "Temporary Redirect"; + case status::permanent_redirect: return "Permanent Redirect"; + + // 4xx + case status::bad_request: return "Bad Request"; + case status::unauthorized: return "Unauthorized"; + case status::payment_required: return "Payment Required"; + case status::forbidden: return "Forbidden"; + case status::not_found: return "Not Found"; + case status::method_not_allowed: return "Method Not Allowed"; + case status::not_acceptable: return "Not Acceptable"; + case status::proxy_authentication_required: return "Proxy Authentication Required"; + case status::request_timeout: return "Request Timeout"; + case status::conflict: return "Conflict"; + case status::gone: return "Gone"; + case status::length_required: return "Length Required"; + case status::precondition_failed: return "Precondition Failed"; + case status::payload_too_large: return "Payload Too Large"; + case status::uri_too_long: return "URI Too Long"; + case status::unsupported_media_type: return "Unsupported Media Type"; + case status::range_not_satisfiable: return "Range Not Satisfiable"; + case status::expectation_failed: return "Expectation Failed"; + case status::misdirected_request: return "Misdirected Request"; + case status::unprocessable_entity: return "Unprocessable Entity"; + case status::locked: return "Locked"; + case status::failed_dependency: return "Failed Dependency"; + case status::upgrade_required: return "Upgrade Required"; + case status::precondition_required: return "Precondition Required"; + case status::too_many_requests: return "Too Many Requests"; + case status::request_header_fields_too_large: return "Request Header Fields Too Large"; + case status::connection_closed_without_response: return "Connection Closed Without Response"; + case status::unavailable_for_legal_reasons: return "Unavailable For Legal Reasons"; + case status::client_closed_request: return "Client Closed Request"; + // 5xx + case status::internal_server_error: return "Internal Server Error"; + case status::not_implemented: return "Not Implemented"; + case status::bad_gateway: return "Bad Gateway"; + case status::service_unavailable: return "Service Unavailable"; + case status::gateway_timeout: return "Gateway Timeout"; + case status::http_version_not_supported: return "HTTP Version Not Supported"; + case status::variant_also_negotiates: return "Variant Also Negotiates"; + case status::insufficient_storage: return "Insufficient Storage"; + case status::loop_detected: return "Loop Detected"; + case status::not_extended: return "Not Extended"; + case status::network_authentication_required: return "Network Authentication Required"; + case status::network_connect_timeout_error: return "Network Connect Timeout Error"; + + default: + break; + } + return "<unknown-status>"; +} + +template<class = void> +status_class +to_status_class(unsigned v) +{ + switch(v / 100) + { + case 1: return status_class::informational; + case 2: return status_class::successful; + case 3: return status_class::redirection; + case 4: return status_class::client_error; + case 5: return status_class::server_error; + default: + break; + } + return status_class::unknown; +} + +} // detail + +inline +status +int_to_status(unsigned v) +{ + return detail::int_to_status(v); +} + +inline +status_class +to_status_class(unsigned v) +{ + return detail::to_status_class(v); +} + +inline +status_class +to_status_class(status v) +{ + return to_status_class(static_cast<int>(v)); +} + +inline +string_view +obsolete_reason(status v) +{ + return detail::status_to_string( + static_cast<unsigned>(v)); +} + +inline +std::ostream& +operator<<(std::ostream& os, status v) +{ + return os << obsolete_reason(v); +} + +} // http +} // beast +} // boost + +#endif diff --git a/boost/beast/http/impl/verb.ipp b/boost/beast/http/impl/verb.ipp new file mode 100644 index 0000000000..820609165b --- /dev/null +++ b/boost/beast/http/impl/verb.ipp @@ -0,0 +1,337 @@ +// +// 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_VERB_IPP +#define BOOST_BEAST_HTTP_IMPL_VERB_IPP + +#include <boost/beast/core/detail/config.hpp> +#include <boost/throw_exception.hpp> +#include <stdexcept> + +namespace boost { +namespace beast { +namespace http { + +namespace detail { + +template<class = void> +inline +string_view +verb_to_string(verb v) +{ + switch(v) + { + case verb::delete_: return "DELETE"; + case verb::get: return "GET"; + case verb::head: return "HEAD"; + case verb::post: return "POST"; + case verb::put: return "PUT"; + case verb::connect: return "CONNECT"; + case verb::options: return "OPTIONS"; + case verb::trace: return "TRACE"; + + case verb::copy: return "COPY"; + case verb::lock: return "LOCK"; + case verb::mkcol: return "MKCOL"; + case verb::move: return "MOVE"; + case verb::propfind: return "PROPFIND"; + case verb::proppatch: return "PROPPATCH"; + case verb::search: return "SEARCH"; + case verb::unlock: return "UNLOCK"; + case verb::bind: return "BIND"; + case verb::rebind: return "REBIND"; + case verb::unbind: return "UNBIND"; + case verb::acl: return "ACL"; + + case verb::report: return "REPORT"; + case verb::mkactivity: return "MKACTIVITY"; + case verb::checkout: return "CHECKOUT"; + case verb::merge: return "MERGE"; + + case verb::msearch: return "M-SEARCH"; + case verb::notify: return "NOTIFY"; + case verb::subscribe: return "SUBSCRIBE"; + case verb::unsubscribe: return "UNSUBSCRIBE"; + + case verb::patch: return "PATCH"; + case verb::purge: return "PURGE"; + + case verb::mkcalendar: return "MKCALENDAR"; + + case verb::link: return "LINK"; + case verb::unlink: return "UNLINK"; + + case verb::unknown: + return "<unknown>"; + } + + BOOST_THROW_EXCEPTION(std::invalid_argument{"unknown verb"}); +} + +template<class = void> +verb +string_to_verb(string_view v) +{ +/* + ACL + BIND + CHECKOUT + CONNECT + COPY + DELETE + GET + HEAD + LINK + LOCK + M-SEARCH + MERGE + MKACTIVITY + MKCALENDAR + MKCOL + MOVE + NOTIFY + OPTIONS + PATCH + POST + PROPFIND + PROPPATCH + PURGE + PUT + REBIND + REPORT + SEARCH + SUBSCRIBE + TRACE + UNBIND + UNLINK + UNLOCK + UNSUBSCRIBE +*/ + if(v.size() < 3) + return verb::unknown; + // s must be null terminated + auto const eq = + [](string_view sv, char const* s) + { + auto p = sv.data(); + for(;;) + { + if(*s != *p) + return false; + ++s; + ++p; + if(! *s) + return p == sv.end(); + } + }; + auto c = v[0]; + v.remove_prefix(1); + switch(c) + { + case 'A': + if(v == "CL") + return verb::acl; + break; + + case 'B': + if(v == "IND") + return verb::bind; + break; + + case 'C': + c = v[0]; + v.remove_prefix(1); + switch(c) + { + case 'H': + if(eq(v, "ECKOUT")) + return verb::checkout; + break; + + case 'O': + if(eq(v, "NNECT")) + return verb::connect; + if(eq(v, "PY")) + return verb::copy; + BOOST_BEAST_FALLTHROUGH; + + default: + break; + } + break; + + case 'D': + if(eq(v, "ELETE")) + return verb::delete_; + break; + + case 'G': + if(eq(v, "ET")) + return verb::get; + break; + + case 'H': + if(eq(v, "EAD")) + return verb::head; + break; + + case 'L': + if(eq(v, "INK")) + return verb::link; + if(eq(v, "OCK")) + return verb::lock; + break; + + case 'M': + c = v[0]; + v.remove_prefix(1); + switch(c) + { + case '-': + if(eq(v, "SEARCH")) + return verb::msearch; + break; + + case 'E': + if(eq(v, "RGE")) + return verb::merge; + break; + + case 'K': + if(eq(v, "ACTIVITY")) + return verb::mkactivity; + if(v[0] == 'C') + { + v.remove_prefix(1); + if(eq(v, "ALENDAR")) + return verb::mkcalendar; + if(eq(v, "OL")) + return verb::mkcol; + break; + } + break; + + case 'O': + if(eq(v, "VE")) + return verb::move; + BOOST_BEAST_FALLTHROUGH; + + default: + break; + } + break; + + case 'N': + if(eq(v, "OTIFY")) + return verb::notify; + break; + + case 'O': + if(eq(v, "PTIONS")) + return verb::options; + break; + + case 'P': + c = v[0]; + v.remove_prefix(1); + switch(c) + { + case 'A': + if(eq(v, "TCH")) + return verb::patch; + break; + + case 'O': + if(eq(v, "ST")) + return verb::post; + break; + + case 'R': + if(eq(v, "OPFIND")) + return verb::propfind; + if(eq(v, "OPPATCH")) + return verb::proppatch; + break; + + case 'U': + if(eq(v, "RGE")) + return verb::purge; + if(eq(v, "T")) + return verb::put; + BOOST_BEAST_FALLTHROUGH; + + default: + break; + } + break; + + case 'R': + if(v[0] != 'E') + break; + v.remove_prefix(1); + if(eq(v, "BIND")) + return verb::rebind; + if(eq(v, "PORT")) + return verb::report; + break; + + case 'S': + if(eq(v, "EARCH")) + return verb::search; + if(eq(v, "UBSCRIBE")) + return verb::subscribe; + break; + + case 'T': + if(eq(v, "RACE")) + return verb::trace; + break; + + case 'U': + if(v[0] != 'N') + break; + v.remove_prefix(1); + if(eq(v, "BIND")) + return verb::unbind; + if(eq(v, "LINK")) + return verb::unlink; + if(eq(v, "LOCK")) + return verb::unlock; + if(eq(v, "SUBSCRIBE")) + return verb::unsubscribe; + break; + + default: + break; + } + + return verb::unknown; +} + +} // detail + +inline +string_view +to_string(verb v) +{ + return detail::verb_to_string(v); +} + +inline +verb +string_to_verb(string_view s) +{ + return detail::string_to_verb(s); +} + +} // http +} // beast +} // boost + +#endif diff --git a/boost/beast/http/impl/write.ipp b/boost/beast/http/impl/write.ipp new file mode 100644 index 0000000000..9d5be277ec --- /dev/null +++ b/boost/beast/http/impl/write.ipp @@ -0,0 +1,887 @@ +// +// 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_WRITE_IPP +#define BOOST_BEAST_HTTP_IMPL_WRITE_IPP + +#include <boost/beast/http/type_traits.hpp> +#include <boost/beast/core/bind_handler.hpp> +#include <boost/beast/core/ostream.hpp> +#include <boost/beast/core/handler_ptr.hpp> +#include <boost/beast/core/type_traits.hpp> +#include <boost/beast/core/detail/config.hpp> +#include <boost/asio/associated_allocator.hpp> +#include <boost/asio/associated_executor.hpp> +#include <boost/asio/handler_continuation_hook.hpp> +#include <boost/asio/post.hpp> +#include <boost/asio/write.hpp> +#include <boost/optional.hpp> +#include <boost/throw_exception.hpp> +#include <ostream> +#include <sstream> + +namespace boost { +namespace beast { +namespace http { +namespace detail { + +template< + class Stream, class Handler, + bool isRequest, class Body, class Fields> +class write_some_op +{ + Stream& s_; + serializer<isRequest,Body, Fields>& sr_; + Handler h_; + + class lambda + { + write_some_op& op_; + + public: + bool invoked = false; + + explicit + lambda(write_some_op& op) + : op_(op) + { + } + + template<class ConstBufferSequence> + void + operator()(error_code& ec, + ConstBufferSequence const& buffers) + { + invoked = true; + ec.assign(0, ec.category()); + return op_.s_.async_write_some( + buffers, std::move(op_)); + } + }; + +public: + write_some_op(write_some_op&&) = default; + write_some_op(write_some_op const&) = default; + + template<class DeducedHandler> + write_some_op(DeducedHandler&& h, Stream& s, + serializer<isRequest, Body, Fields>& sr) + : s_(s) + , sr_(sr) + , h_(std::forward<DeducedHandler>(h)) + { + } + + using allocator_type = + boost::asio::associated_allocator_t<Handler>; + + allocator_type + get_allocator() const noexcept + { + return boost::asio::get_associated_allocator(h_); + } + + using executor_type = boost::asio::associated_executor_t< + Handler, decltype(std::declval<Stream&>().get_executor())>; + + executor_type + get_executor() const noexcept + { + return boost::asio::get_associated_executor( + h_, s_.get_executor()); + } + + void + operator()(); + + void + operator()( + error_code ec, + std::size_t bytes_transferred); + + friend + bool asio_handler_is_continuation(write_some_op* op) + { + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->h_)); + } +}; + +template< + class Stream, class Handler, + bool isRequest, class Body, class Fields> +void +write_some_op< + Stream, Handler, isRequest, Body, Fields>:: +operator()() +{ + error_code ec; + if(! sr_.is_done()) + { + lambda f{*this}; + sr_.next(ec, f); + if(ec) + { + BOOST_ASSERT(! f.invoked); + return boost::asio::post( + s_.get_executor(), + bind_handler(std::move(*this), ec, 0)); + } + if(f.invoked) + { + // *this has been moved from, + // cannot access members here. + return; + } + // What else could it be? + BOOST_ASSERT(sr_.is_done()); + } + return boost::asio::post( + s_.get_executor(), + bind_handler(std::move(*this), ec, 0)); +} + +template< + class Stream, class Handler, + bool isRequest, class Body, class Fields> +void +write_some_op< + Stream, Handler, isRequest, Body, Fields>:: +operator()( + error_code ec, std::size_t bytes_transferred) +{ + if(! ec) + sr_.consume(bytes_transferred); + h_(ec, bytes_transferred); +} + +//------------------------------------------------------------------------------ + +struct serializer_is_header_done +{ + template< + bool isRequest, class Body, class Fields> + bool + operator()( + serializer<isRequest, Body, Fields>& sr) const + { + return sr.is_header_done(); + } +}; + +struct serializer_is_done +{ + template< + bool isRequest, class Body, class Fields> + bool + operator()( + serializer<isRequest, Body, Fields>& sr) const + { + return sr.is_done(); + } +}; + +//------------------------------------------------------------------------------ + +template< + class Stream, class Handler, class Predicate, + bool isRequest, class Body, class Fields> +class write_op +{ + int state_ = 0; + Stream& s_; + serializer<isRequest, Body, Fields>& sr_; + std::size_t bytes_transferred_ = 0; + Handler h_; + +public: + write_op(write_op&&) = default; + write_op(write_op const&) = default; + + template<class DeducedHandler> + write_op(DeducedHandler&& h, Stream& s, + serializer<isRequest, Body, Fields>& sr) + : s_(s) + , sr_(sr) + , h_(std::forward<DeducedHandler>(h)) + { + } + + using allocator_type = + boost::asio::associated_allocator_t<Handler>; + + allocator_type + get_allocator() const noexcept + { + return boost::asio::get_associated_allocator(h_); + } + + using executor_type = boost::asio::associated_executor_t< + Handler, decltype(std::declval<Stream&>().get_executor())>; + + executor_type + get_executor() const noexcept + { + return boost::asio::get_associated_executor( + h_, s_.get_executor()); + } + + void + operator()( + error_code ec = {}, + std::size_t bytes_transferred = 0); + + friend + bool asio_handler_is_continuation(write_op* op) + { + using boost::asio::asio_handler_is_continuation; + return op->state_ >= 3 || + asio_handler_is_continuation( + std::addressof(op->h_)); + } +}; + +template< + class Stream, class Handler, class Predicate, + bool isRequest, class Body, class Fields> +void +write_op<Stream, Handler, Predicate, + isRequest, Body, Fields>:: +operator()( + error_code ec, std::size_t bytes_transferred) +{ + if(ec) + goto upcall; + switch(state_) + { + case 0: + { + if(Predicate{}(sr_)) + { + state_ = 1; + return boost::asio::post( + s_.get_executor(), + bind_handler(std::move(*this), ec, 0)); + } + state_ = 2; + return beast::http::async_write_some( + s_, sr_, std::move(*this)); + } + + case 1: + goto upcall; + + case 2: + state_ = 3; + BOOST_BEAST_FALLTHROUGH; + + case 3: + { + bytes_transferred_ += bytes_transferred; + if(Predicate{}(sr_)) + goto upcall; + return beast::http::async_write_some( + s_, sr_, std::move(*this)); + } + } +upcall: + h_(ec, bytes_transferred_); +} + +//------------------------------------------------------------------------------ + +template<class Stream, class Handler, + bool isRequest, class Body, class Fields> +class write_msg_op +{ + struct data + { + Stream& s; + serializer<isRequest, Body, Fields> sr; + + data(Handler&, Stream& s_, message< + isRequest, Body, Fields>& m_) + : s(s_) + , sr(m_) + { + } + }; + + handler_ptr<data, Handler> d_; + +public: + write_msg_op(write_msg_op&&) = default; + write_msg_op(write_msg_op const&) = default; + + template<class DeducedHandler, class... Args> + write_msg_op(DeducedHandler&& h, Stream& s, Args&&... args) + : d_(std::forward<DeducedHandler>(h), + s, std::forward<Args>(args)...) + { + } + + using allocator_type = + boost::asio::associated_allocator_t<Handler>; + + allocator_type + get_allocator() const noexcept + { + return boost::asio::get_associated_allocator(d_.handler()); + } + + using executor_type = boost::asio::associated_executor_t< + Handler, decltype(std::declval<Stream&>().get_executor())>; + + executor_type + get_executor() const noexcept + { + return boost::asio::get_associated_executor( + d_.handler(), d_->s.get_executor()); + } + + void + operator()(); + + void + operator()( + error_code ec, std::size_t bytes_transferred); + + friend + bool asio_handler_is_continuation(write_msg_op* op) + { + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->d_.handler())); + } +}; + +template<class Stream, class Handler, + bool isRequest, class Body, class Fields> +void +write_msg_op< + Stream, Handler, isRequest, Body, Fields>:: +operator()() +{ + auto& d = *d_; + return async_write(d.s, d.sr, std::move(*this)); +} + +template<class Stream, class Handler, + bool isRequest, class Body, class Fields> +void +write_msg_op< + Stream, Handler, isRequest, Body, Fields>:: +operator()(error_code ec, std::size_t bytes_transferred) +{ + d_.invoke(ec, bytes_transferred); +} + +//------------------------------------------------------------------------------ + +template<class Stream> +class write_some_lambda +{ + Stream& stream_; + +public: + bool invoked = false; + std::size_t bytes_transferred = 0; + + explicit + write_some_lambda(Stream& stream) + : stream_(stream) + { + } + + template<class ConstBufferSequence> + void + operator()(error_code& ec, + ConstBufferSequence const& buffers) + { + invoked = true; + bytes_transferred = + stream_.write_some(buffers, ec); + } +}; + +template<class Stream> +class write_lambda +{ + Stream& stream_; + +public: + bool invoked = false; + std::size_t bytes_transferred = 0; + + explicit + write_lambda(Stream& stream) + : stream_(stream) + { + } + + template<class ConstBufferSequence> + void + operator()(error_code& ec, + ConstBufferSequence const& buffers) + { + invoked = true; + bytes_transferred = boost::asio::write( + stream_, buffers, ec); + } +}; + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields> +std::size_t +write_some( + SyncWriteStream& stream, + serializer<isRequest, Body, Fields>& sr, + error_code& ec) +{ + if(! sr.is_done()) + { + write_some_lambda<SyncWriteStream> f{stream}; + sr.next(ec, f); + if(ec) + return f.bytes_transferred; + if(f.invoked) + sr.consume(f.bytes_transferred); + return f.bytes_transferred; + } + ec.assign(0, ec.category()); + return 0; +} + +template< + class AsyncWriteStream, + bool isRequest, class Body, class Fields, + class WriteHandler> +BOOST_ASIO_INITFN_RESULT_TYPE( + WriteHandler, void(error_code, std::size_t)) +async_write_some( + AsyncWriteStream& stream, + serializer<isRequest, Body, Fields>& sr, + WriteHandler&& handler) +{ + boost::asio::async_completion< + WriteHandler, + void(error_code, std::size_t)> init{handler}; + detail::write_some_op< + AsyncWriteStream, + BOOST_ASIO_HANDLER_TYPE(WriteHandler, + void(error_code, std::size_t)), + isRequest, Body, Fields>{ + init.completion_handler, stream, sr}(); + return init.result.get(); +} + +} // detail + +//------------------------------------------------------------------------------ + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields> +std::size_t +write_some( + SyncWriteStream& stream, + serializer<isRequest, Body, Fields>& sr) +{ + static_assert(is_sync_write_stream<SyncWriteStream>::value, + "SyncWriteStream requirements not met"); + static_assert(is_body<Body>::value, + "Body requirements not met"); + static_assert(is_body_writer<Body>::value, + "BodyWriter requirements not met"); + error_code ec; + auto const bytes_transferred = + write_some(stream, sr, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return bytes_transferred; +} + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields> +std::size_t +write_some( + SyncWriteStream& stream, + serializer<isRequest, Body, Fields>& sr, + error_code& ec) +{ + static_assert(is_sync_write_stream<SyncWriteStream>::value, + "SyncWriteStream requirements not met"); + static_assert(is_body<Body>::value, + "Body requirements not met"); + static_assert(is_body_writer<Body>::value, + "BodyWriter requirements not met"); + return detail::write_some(stream, sr, ec); +} + +template< + class AsyncWriteStream, + bool isRequest, class Body, class Fields, + class WriteHandler> +BOOST_ASIO_INITFN_RESULT_TYPE( + WriteHandler, void(error_code, std::size_t)) +async_write_some( + AsyncWriteStream& stream, + serializer<isRequest, Body, Fields>& sr, + WriteHandler&& handler) +{ + static_assert(is_async_write_stream< + AsyncWriteStream>::value, + "AsyncWriteStream requirements not met"); + static_assert(is_body<Body>::value, + "Body requirements not met"); + static_assert(is_body_writer<Body>::value, + "BodyWriter requirements not met"); + return detail::async_write_some(stream, sr, + std::forward<WriteHandler>(handler)); +} + +//------------------------------------------------------------------------------ + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields> +std::size_t +write_header(SyncWriteStream& stream, + serializer<isRequest, Body, Fields>& sr) +{ + static_assert(is_sync_write_stream<SyncWriteStream>::value, + "SyncWriteStream requirements not met"); + static_assert(is_body<Body>::value, + "Body requirements not met"); + static_assert(is_body_writer<Body>::value, + "BodyWriter requirements not met"); + error_code ec; + auto const bytes_transferred = + write_header(stream, sr, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return bytes_transferred; +} + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields> +std::size_t +write_header( + SyncWriteStream& stream, + serializer<isRequest, Body, Fields>& sr, + error_code& ec) +{ + static_assert(is_sync_write_stream<SyncWriteStream>::value, + "SyncWriteStream requirements not met"); + static_assert(is_body<Body>::value, + "Body requirements not met"); + static_assert(is_body_writer<Body>::value, + "BodyWriter requirements not met"); + sr.split(true); + std::size_t bytes_transferred = 0; + if(! sr.is_header_done()) + { + detail::write_lambda<SyncWriteStream> f{stream}; + do + { + sr.next(ec, f); + bytes_transferred += f.bytes_transferred; + if(ec) + return bytes_transferred; + BOOST_ASSERT(f.invoked); + sr.consume(f.bytes_transferred); + } + while(! sr.is_header_done()); + } + else + { + ec.assign(0, ec.category()); + } + return bytes_transferred; +} + +template< + class AsyncWriteStream, + bool isRequest, class Body, class Fields, + class WriteHandler> +BOOST_ASIO_INITFN_RESULT_TYPE( + WriteHandler, void(error_code, std::size_t)) +async_write_header( + AsyncWriteStream& stream, + serializer<isRequest, Body, Fields>& sr, + WriteHandler&& handler) +{ + static_assert(is_async_write_stream< + AsyncWriteStream>::value, + "AsyncWriteStream requirements not met"); + static_assert(is_body<Body>::value, + "Body requirements not met"); + static_assert(is_body_writer<Body>::value, + "BodyWriter requirements not met"); + sr.split(true); + boost::asio::async_completion< + WriteHandler, + void(error_code, std::size_t)> init{handler}; + detail::write_op< + AsyncWriteStream, + BOOST_ASIO_HANDLER_TYPE(WriteHandler, + void(error_code, std::size_t)), + detail::serializer_is_header_done, + isRequest, Body, Fields>{ + init.completion_handler, stream, sr}(); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields> +std::size_t +write( + SyncWriteStream& stream, + serializer<isRequest, Body, Fields>& sr) +{ + static_assert(is_sync_write_stream<SyncWriteStream>::value, + "SyncWriteStream requirements not met"); + error_code ec; + auto const bytes_transferred = + write(stream, sr, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return bytes_transferred; +} + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields> +std::size_t +write( + SyncWriteStream& stream, + serializer<isRequest, Body, Fields>& sr, + error_code& ec) +{ + static_assert(is_sync_write_stream<SyncWriteStream>::value, + "SyncWriteStream requirements not met"); + std::size_t bytes_transferred = 0; + sr.split(false); + for(;;) + { + bytes_transferred += + write_some(stream, sr, ec); + if(ec) + return bytes_transferred; + if(sr.is_done()) + break; + } + return bytes_transferred; +} + +template< + class AsyncWriteStream, + bool isRequest, class Body, class Fields, + class WriteHandler> +BOOST_ASIO_INITFN_RESULT_TYPE( + WriteHandler, void(error_code, std::size_t)) +async_write( + AsyncWriteStream& stream, + serializer<isRequest, Body, Fields>& sr, + WriteHandler&& handler) +{ + static_assert(is_async_write_stream< + AsyncWriteStream>::value, + "AsyncWriteStream requirements not met"); + static_assert(is_body<Body>::value, + "Body requirements not met"); + static_assert(is_body_writer<Body>::value, + "BodyWriter requirements not met"); + sr.split(false); + boost::asio::async_completion< + WriteHandler, + void(error_code, std::size_t)> init{handler}; + detail::write_op< + AsyncWriteStream, + BOOST_ASIO_HANDLER_TYPE(WriteHandler, + void(error_code, std::size_t)), + detail::serializer_is_done, + isRequest, Body, Fields>{ + init.completion_handler, stream, sr}(); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields> +std::size_t +write( + SyncWriteStream& stream, + message<isRequest, Body, Fields> const& msg) +{ + static_assert(is_sync_write_stream<SyncWriteStream>::value, + "SyncWriteStream requirements not met"); + static_assert(is_body<Body>::value, + "Body requirements not met"); + static_assert(is_body_writer<Body>::value, + "BodyWriter requirements not met"); + error_code ec; + auto const bytes_transferred = + write(stream, msg, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return bytes_transferred; +} + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields> +std::size_t +write( + SyncWriteStream& stream, + message<isRequest, Body, Fields> const& msg, + error_code& ec) +{ + static_assert(is_sync_write_stream<SyncWriteStream>::value, + "SyncWriteStream requirements not met"); + static_assert(is_body<Body>::value, + "Body requirements not met"); + static_assert(is_body_writer<Body>::value, + "BodyWriter requirements not met"); + serializer<isRequest, Body, Fields> sr{msg}; + return write(stream, sr, ec); +} + +template< + class AsyncWriteStream, + bool isRequest, class Body, class Fields, + class WriteHandler> +BOOST_ASIO_INITFN_RESULT_TYPE( + WriteHandler, void(error_code, std::size_t)) +async_write( + AsyncWriteStream& stream, + message<isRequest, Body, Fields>& msg, + WriteHandler&& handler) +{ + static_assert( + is_async_write_stream<AsyncWriteStream>::value, + "AsyncWriteStream requirements not met"); + static_assert(is_body<Body>::value, + "Body requirements not met"); + static_assert(is_body_writer<Body>::value, + "BodyWriter requirements not met"); + boost::asio::async_completion< + WriteHandler, + void(error_code, std::size_t)> init{handler}; + detail::write_msg_op< + AsyncWriteStream, + BOOST_ASIO_HANDLER_TYPE(WriteHandler, + void(error_code, std::size_t)), + isRequest, Body, Fields>{ + init.completion_handler, stream, msg}(); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +namespace detail { + +template<class Serializer> +class write_ostream_lambda +{ + std::ostream& os_; + Serializer& sr_; + +public: + write_ostream_lambda(std::ostream& os, + Serializer& sr) + : os_(os) + , sr_(sr) + { + } + + template<class ConstBufferSequence> + void + operator()(error_code& ec, + ConstBufferSequence const& buffers) const + { + ec.assign(0, ec.category()); + if(os_.fail()) + return; + std::size_t bytes_transferred = 0; + for(auto b : buffers_range(buffers)) + { + os_.write(reinterpret_cast<char const*>( + b.data()), b.size()); + if(os_.fail()) + return; + bytes_transferred += b.size(); + } + sr_.consume(bytes_transferred); + } +}; + +} // detail + +template<class Fields> +std::ostream& +operator<<(std::ostream& os, + header<true, Fields> const& h) +{ + typename Fields::writer fr{ + h, h.version(), h.method()}; + return os << buffers(fr.get()); +} + +template<class Fields> +std::ostream& +operator<<(std::ostream& os, + header<false, Fields> const& h) +{ + typename Fields::writer fr{ + h, h.version(), h.result_int()}; + return os << buffers(fr.get()); +} + +template<bool isRequest, class Body, class Fields> +std::ostream& +operator<<(std::ostream& os, + message<isRequest, Body, Fields> const& msg) +{ + static_assert(is_body<Body>::value, + "Body requirements not met"); + static_assert(is_body_writer<Body>::value, + "BodyWriter requirements not met"); + serializer<isRequest, Body, Fields> sr{msg}; + error_code ec; + detail::write_ostream_lambda<decltype(sr)> f{os, sr}; + do + { + sr.next(ec, f); + if(os.fail()) + break; + if(ec) + { + os.setstate(std::ios::failbit); + break; + } + } + while(! sr.is_done()); + return os; +} + +} // http +} // beast +} // boost + +#endif |