From ed3d3d0ecbe91e36dd2a1aa4702fd58f35fe5eb0 Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Mon, 25 Aug 2025 09:36:29 +0000 Subject: [PATCH] parser is move constructible and move assignable --- include/boost/http_proto/detail/managed.hpp | 54 + include/boost/http_proto/impl/parser.hpp | 37 +- include/boost/http_proto/parser.hpp | 118 +- include/boost/http_proto/request_parser.hpp | 50 +- include/boost/http_proto/response_parser.hpp | 50 +- include/boost/http_proto/serializer.hpp | 4 +- src/detail/workspace.cpp | 49 +- src/parser.cpp | 2255 ++++++++++-------- src/serializer.cpp | 59 +- test/unit/parser.cpp | 58 +- test/unit/request_parser.cpp | 19 +- test/unit/response_parser.cpp | 17 + test/unit/serializer.cpp | 10 + 13 files changed, 1560 insertions(+), 1220 deletions(-) create mode 100644 include/boost/http_proto/detail/managed.hpp diff --git a/include/boost/http_proto/detail/managed.hpp b/include/boost/http_proto/detail/managed.hpp new file mode 100644 index 00000000..3bbd01d3 --- /dev/null +++ b/include/boost/http_proto/detail/managed.hpp @@ -0,0 +1,54 @@ +// +// Copyright (c) 2025 Mohammad Nejati +// +// 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/cppalliance/http_proto +// + +#ifndef BOOST_HTTP_PROTO_DETAIL_MANAGED_HPP +#define BOOST_HTTP_PROTO_DETAIL_MANAGED_HPP + +namespace boost { +namespace http_proto { +namespace detail { + +template +class managed +{ + T value_ = Default; + +public: + managed() = default; + + managed(T s) noexcept + : value_(s) + { + } + + managed(managed&& other) noexcept + : value_(other.value_) + { + other.value_ = Default; + } + + managed& + operator=(managed&& other) noexcept + { + value_ = other.value_; + other.value_ = Default; + return *this; + } + + operator T() const noexcept + { + return value_; + } +}; + +} // detail +} // http_proto +} // boost + +#endif diff --git a/include/boost/http_proto/impl/parser.hpp b/include/boost/http_proto/impl/parser.hpp index fc76000f..51f79a7f 100644 --- a/include/boost/http_proto/impl/parser.hpp +++ b/include/boost/http_proto/impl/parser.hpp @@ -21,8 +21,6 @@ namespace boost { namespace http_proto { -//------------------------------------------------ - template typename std::enable_if< ! detail::is_reference_wrapper< @@ -44,21 +42,20 @@ set_body( buffers::is_dynamic_buffer::value, "Type requirements not met."); - // body must not be set already - if(how_ != how::in_place) + // body must not already be set + if(is_body_set()) detail::throw_logic_error(); // headers must be complete if(! got_header()) detail::throw_logic_error(); - auto& dyn = ws_.emplace< + auto& dyn = ws().emplace< buffers::any_dynamic_buffer_impl::type, buffers_N>>(std::forward(eb)); - eb_ = &dyn; - how_ = how::elastic; - on_set_body(); + + set_body_impl(dyn); } template @@ -72,24 +69,21 @@ set_body( buffers::is_dynamic_buffer::value, "Type requirements not met."); - // body must not be set already - if(how_ != how::in_place) + // body must not already be set + if(is_body_set()) detail::throw_logic_error(); // headers must be complete if(! got_header()) detail::throw_logic_error(); - auto& dyn = ws_.emplace< + auto& dyn = ws().emplace< buffers::any_dynamic_buffer_impl::type&, buffers_N>>(eb); - eb_ = &dyn; - how_ = how::elastic; - on_set_body(); -} -//------------------------------------------------ + set_body_impl(dyn); +} template< class Sink, @@ -99,19 +93,18 @@ Sink& parser:: set_body(Args&&... args) { - // body must not be set already - if(how_ != how::in_place) + // body must not already be set + if(is_body_set()) detail::throw_logic_error(); // headers must be complete if(! got_header()) detail::throw_logic_error(); - auto& s = ws_.emplace( + auto& s = ws().emplace( std::forward(args)...); - sink_ = &s; - how_ = how::sink; - on_set_body(); + + set_body_impl(s); return s; } diff --git a/include/boost/http_proto/parser.hpp b/include/boost/http_proto/parser.hpp index 81ba94a9..af613dfe 100644 --- a/include/boost/http_proto/parser.hpp +++ b/include/boost/http_proto/parser.hpp @@ -15,18 +15,14 @@ #include #include #include -#include #include #include #include -#include -#include #include #include #include #include -#include #include #include @@ -37,10 +33,6 @@ namespace http_proto { // Forward declaration class request_parser; class response_parser; -namespace detail { -class parser_service; -class filter; -} // detail /** A parser for HTTP/1 messages. @@ -92,30 +84,6 @@ class parser using const_buffers_type = buffers::const_buffer_span; - //-------------------------------------------- - // - // Special Members - // - //-------------------------------------------- - - /** Constructor (deleted) - */ - // TODO - parser(parser&&) = delete; - - /** Assignment (deleted) - */ - // TODO - parser& operator=(parser&&) = delete; - - /** Destructor. - - Any views or buffers obtained from this - parser become invalid. - */ - BOOST_HTTP_PROTO_DECL - ~parser(); - //-------------------------------------------- // // Observers @@ -643,6 +611,7 @@ class parser private: friend class request_parser; friend class response_parser; + class impl; BOOST_HTTP_PROTO_DECL parser( @@ -650,80 +619,39 @@ class parser detail::kind); BOOST_HTTP_PROTO_DECL - void - start_impl(bool); + parser(parser&& other) noexcept; BOOST_HTTP_PROTO_DECL - void - on_set_body() noexcept; + parser& operator=(parser&& other) noexcept; - std::size_t - apply_filter( - system::error_code&, - std::size_t, - bool); + BOOST_HTTP_PROTO_DECL + ~parser(); + + BOOST_HTTP_PROTO_DECL + void + start_impl(bool); detail::header const* safe_get_header() const; + BOOST_HTTP_PROTO_DECL + detail::workspace& + ws() noexcept; + + BOOST_HTTP_PROTO_DECL bool - is_plain() const noexcept; + is_body_set() const noexcept; - std::uint64_t - body_limit_remain() const noexcept; + BOOST_HTTP_PROTO_DECL + void + set_body_impl(buffers::any_dynamic_buffer&) noexcept; - static constexpr unsigned buffers_N = 8; + BOOST_HTTP_PROTO_DECL + void + set_body_impl(sink&) noexcept; - enum class state - { - reset, - start, - header, - header_done, - body, - set_body, - complete_in_place, - complete - }; - - enum class how - { - in_place, - sink, - elastic, - }; - - const rts::context& ctx_; - detail::parser_service& svc_; - - detail::workspace ws_; - detail::header h_; - std::uint64_t body_limit_; - std::uint64_t body_total_; - std::uint64_t payload_remain_; - std::uint64_t chunk_remain_; - std::size_t body_avail_; - std::size_t nprepare_; - - buffers::flat_buffer fb_; - buffers::circular_buffer cb0_; - buffers::circular_buffer cb1_; - - buffers::mutable_buffer_pair mbp_; - buffers::const_buffer_pair cbp_; - - detail::filter* filter_; - buffers::any_dynamic_buffer* eb_; - sink* sink_; - - state st_; - how how_; - bool got_header_; - bool got_eof_; - bool head_response_; - bool needs_chunk_close_; - bool trailer_headers_; - bool chunked_body_ended; + static constexpr unsigned buffers_N = 8; + impl* impl_; }; //------------------------------------------------ diff --git a/include/boost/http_proto/request_parser.hpp b/include/boost/http_proto/request_parser.hpp index ff8296c0..1eb031ae 100644 --- a/include/boost/http_proto/request_parser.hpp +++ b/include/boost/http_proto/request_parser.hpp @@ -86,7 +86,55 @@ class request_parser explicit request_parser(const rts::context& ctx); - /// @copydoc parser + /** Constructor. + + The states of `other` are transferred + to the newly constructed object, + including the allocated buffer. + After construction, the only valid + operations on the moved-from object + are destruction and assignment. + + Buffer sequences previously obtained + using @ref prepare or @ref pull_body + remain valid. + + @par Complexity + Constant. + + @param other The parser to move from. + */ + request_parser( + request_parser&& other) noexcept = default; + + /** Assignment. + + The states of `other` are transferred to + `this`, including the allocated buffer. + The previous states of `this` are + destroyed. After assignment, the only + valid operations on the moved-from object + are destruction and assignment. + + Buffer sequences previously obtained + using @ref prepare or @ref pull_body + remain valid. + + @par Complexity + Constant. + + @return A reference to this object. + + @param other The parser to assign from. + */ + request_parser& operator=( + request_parser&& other) noexcept = default; + + /** Destructor. + + Any views or buffers obtained from this + parser become invalid. + */ ~request_parser() = default; /** Return a read-only view to the parsed request headers. diff --git a/include/boost/http_proto/response_parser.hpp b/include/boost/http_proto/response_parser.hpp index db228362..d2cc4e46 100644 --- a/include/boost/http_proto/response_parser.hpp +++ b/include/boost/http_proto/response_parser.hpp @@ -86,7 +86,55 @@ class response_parser explicit response_parser(const rts::context& ctx); - /// @copydoc parser::~parser + /** Constructor. + + The states of `other` are transferred + to the newly constructed object, + including the allocated buffer. + After construction, the only valid + operations on the moved-from object + are destruction and assignemt. + + Buffer sequences previously obtained + using @ref prepare or @ref pull_body + remain valid. + + @par Complexity + Constant. + + @param other The parser to move from. + */ + response_parser( + response_parser&& other) noexcept = default; + + /** Assignment. + + The states of `other` are transferred to + `this`, including the allocated buffer. + The previous states of `this` are + destroyed. After assignment, the only + valid operations on the moved-from object + are destruction and assignemt. + + Buffer sequences previously obtained + using @ref prepare or @ref pull_body + remain valid. + + @par Complexity + Constant. + + @return A reference to this object. + + @param other The parser to assign from. + */ + response_parser& operator=( + response_parser&& other) noexcept = default; + + /** Destructor. + + Any views or buffers obtained from this + parser become invalid. + */ ~response_parser() = default; /** Prepare for the next message on the stream. diff --git a/include/boost/http_proto/serializer.hpp b/include/boost/http_proto/serializer.hpp index 25a95f18..083232e5 100644 --- a/include/boost/http_proto/serializer.hpp +++ b/include/boost/http_proto/serializer.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -653,7 +654,8 @@ class serializer detail::array_of_const_buffers prepped_; buffers::const_buffer tmp_; - state state_ = state::start; + detail::managed< + state, state::start> state_; style style_ = style::empty; uint8_t chunk_header_len_ = 0; bool more_input_ = false; diff --git a/src/detail/workspace.cpp b/src/detail/workspace.cpp index fd898bc7..4515b8f5 100644 --- a/src/detail/workspace.cpp +++ b/src/detail/workspace.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace boost { @@ -24,11 +25,8 @@ any:: workspace:: ~workspace() { - if(begin_) - { - clear(); - delete[] begin_; - } + clear(); + delete[] begin_; } workspace:: @@ -45,17 +43,12 @@ workspace( workspace:: workspace( workspace&& other) noexcept - : begin_(other.begin_) - , front_(other.front_) - , head_(other.head_) - , back_(other.back_) - , end_(other.end_) + : begin_(boost::exchange(other.begin_, nullptr)) + , front_(boost::exchange(other.front_, nullptr)) + , head_(boost::exchange(other.head_, nullptr)) + , back_(boost::exchange(other.back_, nullptr)) + , end_(boost::exchange(other.end_, nullptr)) { - other.begin_ = nullptr; - other.front_ = nullptr; - other.head_ = nullptr; - other.back_ = nullptr; - other.end_ = nullptr; } workspace& @@ -63,17 +56,16 @@ workspace:: operator=( workspace&& other) noexcept { - if(begin_) + if(this != &other) { - clear(); delete[] begin_; - begin_ = nullptr; + + begin_ = boost::exchange(other.begin_, nullptr); + front_ = boost::exchange(other.front_, nullptr); + head_ = boost::exchange(other.head_, nullptr); + back_ = boost::exchange(other.back_, nullptr); + end_ = boost::exchange(other.end_, nullptr); } - std::swap(begin_, other.begin_); - std::swap(front_, other.front_); - std::swap(head_, other.head_); - std::swap(back_, other.back_); - std::swap(end_, other.end_); return *this; } @@ -156,9 +148,9 @@ workspace:: reserve_back( std::size_t n) { - // can't reserve after acquire - if(head_ != end_) - detail::throw_logic_error(); + // // can't reserve after acquire + // if(head_ != end_) + // detail::throw_logic_error(); // can't reserve twice if(back_ != end_) @@ -185,7 +177,6 @@ bump_down( BOOST_ASSERT(align > 0); BOOST_ASSERT( (align & (align - 1)) == 0); - BOOST_ASSERT(front_); auto ip0 = reinterpret_cast< std::uintptr_t>(front_); @@ -197,7 +188,7 @@ bump_down( // for your workload. Increase the // buffer size. if(size > ip - ip0) - detail::throw_bad_alloc(); + detail::throw_length_error(); ip -= size; ip &= ~(align - 1); @@ -207,7 +198,7 @@ bump_down( // for your workload. Increase the // buffer size. if(ip < ip0) - detail::throw_bad_alloc(); + detail::throw_length_error(); return reinterpret_cast< unsigned char*>(ip); diff --git a/src/parser.cpp b/src/parser.cpp index 201acdfd..2930507e 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -16,16 +16,19 @@ #include "src/detail/zlib_filter_base.hpp" #include +#include #include +#include #include +#include #include #include -#include #include #include #include #include #include +#include #include namespace boost { @@ -115,6 +118,7 @@ Buffer Usage */ namespace { + class chained_sequence { char const* pos_; @@ -407,10 +411,6 @@ class brotli_filter } }; -} // namespace - -namespace detail { - class parser_service : public rts::service { @@ -499,7 +499,8 @@ class parser_service cfg.min_buffer; } }; -} //detail + +} // namespace //------------------------------------------------ @@ -508,805 +509,946 @@ install_parser_service( rts::context& ctx, parser::config_base const& cfg) { - ctx.make_service< - detail::parser_service>(cfg); + ctx.make_service(cfg); } //------------------------------------------------ -// -// Special Members -// -//------------------------------------------------ - -parser:: -parser(const rts::context& ctx, detail::kind k) - : ctx_(ctx) - , svc_(ctx.get_service()) - , ws_(svc_.space_needed) - , h_(detail::empty{ k }) - , st_(state::reset) - , got_header_(false) -{ -} - -parser:: -~parser() -{ -} - -//-------------------------------------------- -// -// Observers -// -//-------------------------------------------- - -bool -parser::got_header() const noexcept -{ - return got_header_; -} -bool -parser::is_complete() const noexcept +class parser::impl { - return st_ >= state::complete_in_place; -} - -//------------------------------------------------ -// -// Modifiers -// -//------------------------------------------------ + enum class state + { + reset, + start, + header, + header_done, + body, + set_body, + complete_in_place, + complete + }; + + enum class style + { + in_place, + sink, + elastic, + }; + + const rts::context& ctx_; + parser_service& svc_; + + detail::workspace ws_; + detail::header h_; + std::uint64_t body_limit_; + std::uint64_t body_total_; + std::uint64_t payload_remain_; + std::uint64_t chunk_remain_; + std::size_t body_avail_; + std::size_t nprepare_; + + buffers::flat_buffer fb_; + buffers::circular_buffer cb0_; + buffers::circular_buffer cb1_; + + buffers::mutable_buffer_pair mbp_; + buffers::const_buffer_pair cbp_; + + detail::filter* filter_; + buffers::any_dynamic_buffer* eb_; + sink* sink_; + + state state_; + style style_; + bool got_header_; + bool got_eof_; + bool head_response_; + bool needs_chunk_close_; + bool trailer_headers_; + bool chunked_body_ended; -void -parser:: -reset() noexcept -{ - ws_.clear(); - st_ = state::start; - got_header_ = false; - got_eof_ = false; -} +public: + impl(const rts::context& ctx, detail::kind k) + : ctx_(ctx) + , svc_(ctx.get_service()) + , ws_(svc_.space_needed) + , h_(detail::empty{ k }) + , state_(state::reset) + , got_header_(false) + { + } -void -parser::start() -{ - start_impl(false); -} + bool + got_header() const noexcept + { + return got_header_; + } -void -parser:: -start_impl( - bool head_response) -{ - std::size_t leftover = 0; - switch(st_) + bool + is_complete() const noexcept { - default: - case state::reset: - // reset must be called first - detail::throw_logic_error(); - - case state::start: - // reset required on eof - if(got_eof_) - detail::throw_logic_error(); - break; + return state_ >= state::complete_in_place; + } - case state::header: - if(fb_.size() == 0) - { - // start() called twice + detail::header const* + safe_get_header() const + { + // headers must be received + if(! got_header_) detail::throw_logic_error(); - } - BOOST_FALLTHROUGH; - case state::header_done: - case state::body: - case state::set_body: - // current message is incomplete - detail::throw_logic_error(); - - case state::complete_in_place: - // remove available body. - if(is_plain()) - cb0_.consume(body_avail_); - BOOST_FALLTHROUGH; + return &h_; + } - case state::complete: + bool + is_body_set() const noexcept { - // move leftovers to front + return style_ != style::in_place; + } + void + reset() noexcept + { ws_.clear(); - leftover = cb0_.size(); - - auto* dest = reinterpret_cast(ws_.data()); - auto cbp = cb0_.data(); - auto* a = static_cast(cbp[0].data()); - auto* b = static_cast(cbp[1].data()); - auto an = cbp[0].size(); - auto bn = cbp[1].size(); - - if(bn == 0) - { - std::memmove(dest, a, an); - } - else - { - // if `a` can fit between `dest` and `b`, shift `b` to the left - // and copy `a` to its position. if `a` fits perfectly, the - // shift will be of size 0. - // if `a` requires more space, shift `b` to the right and - // copy `a` to its position. this process may require multiple - // iterations and should be done chunk by chunk to prevent `b` - // from overlapping with `a`. - do - { - // clamp right shifts to prevent overlap with `a` - auto* bp = (std::min)(dest + an, const_cast(a) - bn); - b = static_cast(std::memmove(bp, b, bn)); - - // a chunk or all of `a` based on available space - auto chunk_a = static_cast(b - dest); - std::memcpy(dest, a, chunk_a); // never overlap - an -= chunk_a; - dest += chunk_a; - a += chunk_a; - } while(an); - } - - break; - } + state_ = state::start; + got_header_ = false; + got_eof_ = false; } - ws_.clear(); + void + start( + bool head_response) + { + std::size_t leftover = 0; + switch(state_) + { + default: + case state::reset: + // reset must be called first + detail::throw_logic_error(); - fb_ = { - ws_.data(), - svc_.cfg.headers.max_size + svc_.cfg.min_buffer, - leftover }; + case state::start: + // reset required on eof + if(got_eof_) + detail::throw_logic_error(); + break; - BOOST_ASSERT( - fb_.capacity() == svc_.max_overread() - leftover); + case state::header: + if(fb_.size() == 0) + { + // start() called twice + detail::throw_logic_error(); + } + BOOST_FALLTHROUGH; - BOOST_ASSERT( - head_response == false || - h_.kind == detail::kind::response); + case state::header_done: + case state::body: + case state::set_body: + // current message is incomplete + detail::throw_logic_error(); - h_ = detail::header(detail::empty{h_.kind}); - h_.buf = reinterpret_cast(ws_.data()); - h_.cbuf = h_.buf; - h_.cap = ws_.size(); + case state::complete_in_place: + // remove available body. + if(is_plain()) + cb0_.consume(body_avail_); + BOOST_FALLTHROUGH; - st_ = state::header; - how_ = how::in_place; + case state::complete: + { + // move leftovers to front - // reset to the configured default - body_limit_ = svc_.cfg.body_limit; + ws_.clear(); + leftover = cb0_.size(); - body_total_ = 0; - payload_remain_ = 0; - chunk_remain_ = 0; - body_avail_ = 0; - nprepare_ = 0; + auto* dest = reinterpret_cast(ws_.data()); + auto cbp = cb0_.data(); + auto* a = static_cast(cbp[0].data()); + auto* b = static_cast(cbp[1].data()); + auto an = cbp[0].size(); + auto bn = cbp[1].size(); - filter_ = nullptr; - eb_ = nullptr; - sink_ = nullptr; + if(bn == 0) + { + std::memmove(dest, a, an); + } + else + { + // if `a` can fit between `dest` and `b`, shift `b` to the left + // and copy `a` to its position. if `a` fits perfectly, the + // shift will be of size 0. + // if `a` requires more space, shift `b` to the right and + // copy `a` to its position. this process may require multiple + // iterations and should be done chunk by chunk to prevent `b` + // from overlapping with `a`. + do + { + // clamp right shifts to prevent overlap with `a` + auto* bp = (std::min)(dest + an, const_cast(a) - bn); + b = static_cast(std::memmove(bp, b, bn)); + + // a chunk or all of `a` based on available space + auto chunk_a = static_cast(b - dest); + std::memcpy(dest, a, chunk_a); // never overlap + an -= chunk_a; + dest += chunk_a; + a += chunk_a; + } while(an); + } - got_header_ = false; - head_response_ = head_response; - needs_chunk_close_ = false; - trailer_headers_ = false; - chunked_body_ended = false; -} + break; + } + } -auto -parser:: -prepare() -> - mutable_buffers_type -{ - nprepare_ = 0; + ws_.clear(); - switch(st_) - { - default: - case state::reset: - // reset must be called first - detail::throw_logic_error(); + fb_ = { + ws_.data(), + svc_.cfg.headers.max_size + svc_.cfg.min_buffer, + leftover }; - case state::start: - // start must be called first - detail::throw_logic_error(); + BOOST_ASSERT( + fb_.capacity() == svc_.max_overread() - leftover); - case state::header: - { BOOST_ASSERT( - h_.size < svc_.cfg.headers.max_size); - std::size_t n = fb_.capacity() - fb_.size(); - BOOST_ASSERT(n <= svc_.max_overread()); - n = clamp(n, svc_.cfg.max_prepare); - mbp_[0] = fb_.prepare(n); - nprepare_ = n; - return mutable_buffers_type(&mbp_[0], 1); + head_response == false || + h_.kind == detail::kind::response); + + h_ = detail::header(detail::empty{h_.kind}); + h_.buf = reinterpret_cast(ws_.data()); + h_.cbuf = h_.buf; + h_.cap = ws_.size(); + + state_ = state::header; + style_ = style::in_place; + + // reset to the configured default + body_limit_ = svc_.cfg.body_limit; + + body_total_ = 0; + payload_remain_ = 0; + chunk_remain_ = 0; + body_avail_ = 0; + nprepare_ = 0; + + filter_ = nullptr; + eb_ = nullptr; + sink_ = nullptr; + + got_header_ = false; + head_response_ = head_response; + needs_chunk_close_ = false; + trailer_headers_ = false; + chunked_body_ended = false; } - case state::header_done: - // forgot to call parse() - detail::throw_logic_error(); - - case state::body: + auto + prepare() -> + mutable_buffers_type { - if(got_eof_) + nprepare_ = 0; + + switch(state_) { - // forgot to call parse() + default: + case state::reset: + // reset must be called first detail::throw_logic_error(); - } - if(! is_plain()) + case state::start: + // start must be called first + detail::throw_logic_error(); + + case state::header: { - // buffered payload - std::size_t n = cb0_.capacity(); + BOOST_ASSERT( + h_.size < svc_.cfg.headers.max_size); + std::size_t n = fb_.capacity() - fb_.size(); + BOOST_ASSERT(n <= svc_.max_overread()); n = clamp(n, svc_.cfg.max_prepare); + mbp_[0] = fb_.prepare(n); nprepare_ = n; - mbp_ = cb0_.prepare(n); - return mutable_buffers_type(mbp_); + return mutable_buffers_type(&mbp_[0], 1); } - else + + case state::header_done: + // forgot to call parse() + detail::throw_logic_error(); + + case state::body: { - switch(how_) + if(got_eof_) { - default: - case how::in_place: - case how::sink: + // forgot to call parse() + detail::throw_logic_error(); + } + + if(! is_plain()) { + // buffered payload std::size_t n = cb0_.capacity(); n = clamp(n, svc_.cfg.max_prepare); - - if(h_.md.payload == payload::size) - { - if(n > payload_remain_) - { - std::size_t overread = - n - static_cast(payload_remain_); - if(overread > svc_.max_overread()) - n = static_cast(payload_remain_) + - svc_.max_overread(); - } - } - else - { - BOOST_ASSERT( - h_.md.payload == payload::to_eof); - // No more messages can be pipelined, so - // limit the output buffer to the remaining - // body limit plus one byte to detect - // exhaustion. - std::uint64_t r = body_limit_remain(); - if(r != std::uint64_t(-1)) - r += 1; - n = clamp(r, n); - } - nprepare_ = n; mbp_ = cb0_.prepare(n); return mutable_buffers_type(mbp_); } - case how::elastic: + else { - BOOST_ASSERT(cb0_.size() == 0); - BOOST_ASSERT(body_avail_ == 0); + switch(style_) + { + default: + case style::in_place: + case style::sink: + { + std::size_t n = cb0_.capacity(); + n = clamp(n, svc_.cfg.max_prepare); - std::size_t n = svc_.cfg.min_buffer; + if(h_.md.payload == payload::size) + { + if(n > payload_remain_) + { + std::size_t overread = + n - static_cast(payload_remain_); + if(overread > svc_.max_overread()) + n = static_cast(payload_remain_) + + svc_.max_overread(); + } + } + else + { + BOOST_ASSERT( + h_.md.payload == payload::to_eof); + // No more messages can be pipelined, so + // limit the output buffer to the remaining + // body limit plus one byte to detect + // exhaustion. + std::uint64_t r = body_limit_remain(); + if(r != std::uint64_t(-1)) + r += 1; + n = clamp(r, n); + } - if(h_.md.payload == payload::size) - { - // Overreads are not allowed, or - // else the caller will see extra - // unrelated data. - n = clamp(payload_remain_, n); + nprepare_ = n; + mbp_ = cb0_.prepare(n); + return mutable_buffers_type(mbp_); } - else + case style::elastic: { - BOOST_ASSERT( - h_.md.payload == payload::to_eof); - // No more messages can be pipelined, so - // limit the output buffer to the remaining - // body limit plus one byte to detect - // exhaustion. - std::uint64_t r = body_limit_remain(); - if(r != std::uint64_t(-1)) - r += 1; - n = clamp(r, n); - n = clamp(n, eb_->max_size() - eb_->size()); - // fill capacity first to avoid an allocation - std::size_t avail = - eb_->capacity() - eb_->size(); - if(avail != 0) - n = clamp(n, avail); + BOOST_ASSERT(cb0_.size() == 0); + BOOST_ASSERT(body_avail_ == 0); + + std::size_t n = svc_.cfg.min_buffer; - if(n == 0) + if(h_.md.payload == payload::size) { - // dynamic buffer is full - // attempt a 1 byte read so - // we can detect overflow - nprepare_ = 1; - mbp_ = cb0_.prepare(1); - return mutable_buffers_type(mbp_); + // Overreads are not allowed, or + // else the caller will see extra + // unrelated data. + n = clamp(payload_remain_, n); + } + else + { + BOOST_ASSERT( + h_.md.payload == payload::to_eof); + // No more messages can be pipelined, so + // limit the output buffer to the remaining + // body limit plus one byte to detect + // exhaustion. + std::uint64_t r = body_limit_remain(); + if(r != std::uint64_t(-1)) + r += 1; + n = clamp(r, n); + n = clamp(n, eb_->max_size() - eb_->size()); + // fill capacity first to avoid an allocation + std::size_t avail = + eb_->capacity() - eb_->size(); + if(avail != 0) + n = clamp(n, avail); + + if(n == 0) + { + // dynamic buffer is full + // attempt a 1 byte read so + // we can detect overflow + nprepare_ = 1; + mbp_ = cb0_.prepare(1); + return mutable_buffers_type(mbp_); + } } - } - n = clamp(n, svc_.cfg.max_prepare); - BOOST_ASSERT(n != 0); - nprepare_ = n; - return eb_->prepare(n); - } + n = clamp(n, svc_.cfg.max_prepare); + BOOST_ASSERT(n != 0); + nprepare_ = n; + return eb_->prepare(n); + } + } } } - } - - case state::set_body: - // forgot to call parse() - detail::throw_logic_error(); - - case state::complete_in_place: - case state::complete: - // already complete - detail::throw_logic_error(); - } -} -void -parser:: -commit( - std::size_t n) -{ - switch(st_) - { - default: - case state::reset: - { - // reset must be called first - detail::throw_logic_error(); - } + case state::set_body: + // forgot to call parse() + detail::throw_logic_error(); - case state::start: - { - // forgot to call start() - detail::throw_logic_error(); + case state::complete_in_place: + case state::complete: + // already complete + detail::throw_logic_error(); + } } - case state::header: + void + commit( + std::size_t n) { - if(n > nprepare_) + switch(state_) { - // n can't be greater than size of - // the buffers returned by prepare() - detail::throw_invalid_argument(); + default: + case state::reset: + { + // reset must be called first + detail::throw_logic_error(); } - if(got_eof_) + case state::start: { - // can't commit after EOF + // forgot to call start() detail::throw_logic_error(); } - nprepare_ = 0; // invalidate - fb_.commit(n); - break; - } + case state::header: + { + if(n > nprepare_) + { + // n can't be greater than size of + // the buffers returned by prepare() + detail::throw_invalid_argument(); + } - case state::header_done: - { - // forgot to call parse() - detail::throw_logic_error(); - } + if(got_eof_) + { + // can't commit after EOF + detail::throw_logic_error(); + } - case state::body: - { - if(n > nprepare_) - { - // n can't be greater than size of - // the buffers returned by prepare() - detail::throw_invalid_argument(); + nprepare_ = 0; // invalidate + fb_.commit(n); + break; } - if(got_eof_) + case state::header_done: { - // can't commit after EOF + // forgot to call parse() detail::throw_logic_error(); } - - nprepare_ = 0; // invalidate - if(is_plain() && how_ == how::elastic) + + case state::body: { - if(eb_->max_size() == eb_->size()) + if(n > nprepare_) { - // borrowed 1 byte from - // cb0_ in prepare() - BOOST_ASSERT(n <= 1); - cb0_.commit(n); + // n can't be greater than size of + // the buffers returned by prepare() + detail::throw_invalid_argument(); + } + + if(got_eof_) + { + // can't commit after EOF + detail::throw_logic_error(); + } + + nprepare_ = 0; // invalidate + if(is_plain() && style_ == style::elastic) + { + if(eb_->max_size() == eb_->size()) + { + // borrowed 1 byte from + // cb0_ in prepare() + BOOST_ASSERT(n <= 1); + cb0_.commit(n); + } + else + { + eb_->commit(n); + payload_remain_ -= n; + body_total_ += n; + } } else { - eb_->commit(n); - payload_remain_ -= n; - body_total_ += n; + cb0_.commit(n); } + break; } - else + + case state::set_body: { - cb0_.commit(n); + // forgot to call parse() + detail::throw_logic_error(); } - break; - } - case state::set_body: - { - // forgot to call parse() - detail::throw_logic_error(); + case state::complete_in_place: + case state::complete: + { + // already complete + detail::throw_logic_error(); + } + } } - case state::complete_in_place: - case state::complete: + void + commit_eof() { - // already complete - detail::throw_logic_error(); - } - } -} + nprepare_ = 0; // invalidate -void -parser:: -commit_eof() -{ - nprepare_ = 0; // invalidate + switch(state_) + { + default: + case state::reset: + // reset must be called first + detail::throw_logic_error(); - switch(st_) - { - default: - case state::reset: - // reset must be called first - detail::throw_logic_error(); - - case state::start: - // forgot to call start() - detail::throw_logic_error(); - - case state::header: - got_eof_ = true; - break; - - case state::header_done: - // forgot to call parse() - detail::throw_logic_error(); - - case state::body: - got_eof_ = true; - break; - - case state::set_body: - // forgot to call parse() - detail::throw_logic_error(); - - case state::complete_in_place: - case state::complete: - // can't commit eof when complete - detail::throw_logic_error(); - } -} + case state::start: + // forgot to call start() + detail::throw_logic_error(); -void -parser:: -parse( - system::error_code& ec) -{ - ec = {}; - switch(st_) - { - default: - case state::reset: - // reset must be called first - detail::throw_logic_error(); + case state::header: + got_eof_ = true; + break; - case state::start: - // start must be called first - detail::throw_logic_error(); + case state::header_done: + // forgot to call parse() + detail::throw_logic_error(); - case state::header: - { - BOOST_ASSERT(h_.buf == static_cast< - void const*>(ws_.data())); - BOOST_ASSERT(h_.cbuf == static_cast< - void const*>(ws_.data())); + case state::body: + got_eof_ = true; + break; - h_.parse(fb_.size(), svc_.cfg.headers, ec); + case state::set_body: + // forgot to call parse() + detail::throw_logic_error(); - if(ec == condition::need_more_input) - { - if(! got_eof_) - { - // headers incomplete - return; - } + case state::complete_in_place: + case state::complete: + // can't commit eof when complete + detail::throw_logic_error(); + } + } - if(fb_.size() == 0) - { - // stream closed cleanly - st_ = state::reset; - ec = BOOST_HTTP_PROTO_ERR( - error::end_of_stream); - return; - } - - // stream closed with a - // partial message received - st_ = state::reset; - ec = BOOST_HTTP_PROTO_ERR( - error::incomplete); - return; - } - else if(ec.failed()) + void + parse( + system::error_code& ec) + { + ec = {}; + switch(state_) { - // other error, - // - // VFALCO map this to a bad - // request or bad response error? - // - st_ = state::reset; // unrecoverable - return; - } - - got_header_ = true; + default: + case state::reset: + // reset must be called first + detail::throw_logic_error(); - // reserve headers + table - ws_.reserve_front(h_.size); - ws_.reserve_back(h_.table_space()); + case state::start: + // start must be called first + detail::throw_logic_error(); - // no payload - if(h_.md.payload == payload::none || - head_response_) + case state::header: { - // octets of the next message - auto overread = fb_.size() - h_.size; - cb0_ = { ws_.data(), overread, overread }; - ws_.reserve_front(overread); - st_ = state::complete_in_place; - return; - } + BOOST_ASSERT(h_.buf == static_cast< + void const*>(ws_.data())); + BOOST_ASSERT(h_.cbuf == static_cast< + void const*>(ws_.data())); - st_ = state::header_done; - break; - } + h_.parse(fb_.size(), svc_.cfg.headers, ec); - case state::header_done: - { - // metadata error - if(h_.md.payload == payload::error) - { - // VFALCO This needs looking at - ec = BOOST_HTTP_PROTO_ERR( - error::bad_payload); - st_ = state::reset; // unrecoverable - return; - } - - // overread currently includes any and all octets that - // extend beyond the current end of the header - // this can include associated body octets for the - // current message or octets of the next message in the - // stream, e.g. pipelining is being used - auto const overread = fb_.size() - h_.size; - BOOST_ASSERT(overread <= svc_.max_overread()); + if(ec == condition::need_more_input) + { + if(! got_eof_) + { + // headers incomplete + return; + } - auto cap = fb_.capacity() + overread + - svc_.cfg.min_buffer; + if(fb_.size() == 0) + { + // stream closed cleanly + state_ = state::reset; + ec = BOOST_HTTP_PROTO_ERR( + error::end_of_stream); + return; + } - // reserve body buffers first, as the decoder - // must be installed after them. - auto const p = ws_.reserve_front(cap); + // stream closed with a + // partial message received + state_ = state::reset; + ec = BOOST_HTTP_PROTO_ERR( + error::incomplete); + return; + } + else if(ec.failed()) + { + // other error, + // + // VFALCO map this to a bad + // request or bad response error? + // + state_ = state::reset; // unrecoverable + return; + } - switch(h_.md.content_encoding.coding) - { - case content_coding::deflate: - if(!svc_.cfg.apply_deflate_decoder) - goto no_filter; - filter_ = &ws_.emplace( - ctx_, ws_, svc_.cfg.zlib_window_bits); - break; + got_header_ = true; - case content_coding::gzip: - if(!svc_.cfg.apply_gzip_decoder) - goto no_filter; - filter_ = &ws_.emplace( - ctx_, ws_, svc_.cfg.zlib_window_bits + 16); - break; + // reserve headers + table + ws_.reserve_front(h_.size); + ws_.reserve_back(h_.table_space()); - case content_coding::br: - if(!svc_.cfg.apply_brotli_decoder) - goto no_filter; - filter_ = &ws_.emplace( - ctx_, ws_); - break; + // no payload + if(h_.md.payload == payload::none || + head_response_) + { + // octets of the next message + auto overread = fb_.size() - h_.size; + cb0_ = { ws_.data(), overread, overread }; + ws_.reserve_front(overread); + state_ = state::complete_in_place; + return; + } - no_filter: - default: - cap += svc_.max_codec; - ws_.reserve_front(svc_.max_codec); + state_ = state::header_done; break; } - if(is_plain() || how_ == how::elastic) - { - cb0_ = { p, cap, overread }; - cb1_ = {}; - } - else - { - // buffered payload - std::size_t n0 = (overread > svc_.cfg.min_buffer) - ? overread - : svc_.cfg.min_buffer; - std::size_t n1 = svc_.cfg.min_buffer; - - cb0_ = { p , n0, overread }; - cb1_ = { p + n0 , n1 }; - } - - if(h_.md.payload == payload::size) + case state::header_done: { - if(!filter_ && - body_limit_ < h_.md.payload_size) + // metadata error + if(h_.md.payload == payload::error) { + // VFALCO This needs looking at ec = BOOST_HTTP_PROTO_ERR( - error::body_too_large); - st_ = state::reset; + error::bad_payload); + state_ = state::reset; // unrecoverable return; } - payload_remain_ = h_.md.payload_size; - } - st_ = state::body; - BOOST_FALLTHROUGH; - } + // overread currently includes any and all octets that + // extend beyond the current end of the header + // this can include associated body octets for the + // current message or octets of the next message in the + // stream, e.g. pipelining is being used + auto const overread = fb_.size() - h_.size; + BOOST_ASSERT(overread <= svc_.max_overread()); - case state::body: - { - do_body: - BOOST_ASSERT(st_ == state::body); - BOOST_ASSERT(h_.md.payload != payload::none); - BOOST_ASSERT(h_.md.payload != payload::error); + auto cap = fb_.capacity() + overread + + svc_.cfg.min_buffer; - auto set_state_to_complete = [&]() - { - if(how_ == how::in_place) + // reserve body buffers first, as the decoder + // must be installed after them. + auto const p = ws_.reserve_front(cap); + + switch(h_.md.content_encoding.coding) { - st_ = state::complete_in_place; - return; + case content_coding::deflate: + if(!svc_.cfg.apply_deflate_decoder) + goto no_filter; + filter_ = &ws_.emplace( + ctx_, ws_, svc_.cfg.zlib_window_bits); + break; + + case content_coding::gzip: + if(!svc_.cfg.apply_gzip_decoder) + goto no_filter; + filter_ = &ws_.emplace( + ctx_, ws_, svc_.cfg.zlib_window_bits + 16); + break; + + case content_coding::br: + if(!svc_.cfg.apply_brotli_decoder) + goto no_filter; + filter_ = &ws_.emplace( + ctx_, ws_); + break; + + no_filter: + default: + cap += svc_.max_codec; + ws_.reserve_front(svc_.max_codec); + break; } - st_ = state::complete; - }; - if(h_.md.payload == payload::chunked) + if(is_plain() || style_ == style::elastic) + { + cb0_ = { p, cap, overread }; + cb1_ = {}; + } + else + { + // buffered payload + std::size_t n0 = (overread > svc_.cfg.min_buffer) + ? overread + : svc_.cfg.min_buffer; + std::size_t n1 = svc_.cfg.min_buffer; + + cb0_ = { p , n0, overread }; + cb1_ = { p + n0 , n1 }; + } + + if(h_.md.payload == payload::size) + { + if(!filter_ && + body_limit_ < h_.md.payload_size) + { + ec = BOOST_HTTP_PROTO_ERR( + error::body_too_large); + state_ = state::reset; + return; + } + payload_remain_ = h_.md.payload_size; + } + + state_ = state::body; + BOOST_FALLTHROUGH; + } + + case state::body: { - for(;;) + do_body: + BOOST_ASSERT(state_ == state::body); + BOOST_ASSERT(h_.md.payload != payload::none); + BOOST_ASSERT(h_.md.payload != payload::error); + + auto set_state_to_complete = [&]() { - if(chunk_remain_ == 0 - && !chunked_body_ended) + if(style_ == style::in_place) { - auto cs = chained_sequence(cb0_.data()); - auto check_ec = [&]() + state_ = state::complete_in_place; + return; + } + state_ = state::complete; + }; + + if(h_.md.payload == payload::chunked) + { + for(;;) + { + if(chunk_remain_ == 0 + && !chunked_body_ended) { - if(ec == condition::need_more_input && got_eof_) + auto cs = chained_sequence(cb0_.data()); + auto check_ec = [&]() + { + if(ec == condition::need_more_input && got_eof_) + { + ec = BOOST_HTTP_PROTO_ERR(error::incomplete); + state_ = state::reset; + } + }; + + if(needs_chunk_close_) { - ec = BOOST_HTTP_PROTO_ERR(error::incomplete); - st_ = state::reset; + parse_eol(cs, ec); + if(ec) + { + check_ec(); + return; + } } - }; - - if(needs_chunk_close_) - { - parse_eol(cs, ec); + else if(trailer_headers_) + { + skip_trailer_headers(cs, ec); + if(ec) + { + check_ec(); + return; + } + cb0_.consume(cb0_.size() - cs.size()); + chunked_body_ended = true; + continue; + } + + auto chunk_size = parse_hex(cs, ec); if(ec) { check_ec(); return; } - } - else if(trailer_headers_) - { - skip_trailer_headers(cs, ec); + + // skip chunk extensions + find_eol(cs, ec); if(ec) { check_ec(); return; } + cb0_.consume(cb0_.size() - cs.size()); - chunked_body_ended = true; - continue; + chunk_remain_ = chunk_size; + + needs_chunk_close_ = true; + if(chunk_remain_ == 0) + { + needs_chunk_close_ = false; + trailer_headers_ = true; + continue; + } } - - auto chunk_size = parse_hex(cs, ec); - if(ec) + + if(cb0_.size() == 0 && !chunked_body_ended) { - check_ec(); + if(got_eof_) + { + ec = BOOST_HTTP_PROTO_ERR( + error::incomplete); + state_ = state::reset; + return; + } + + ec = BOOST_HTTP_PROTO_ERR( + error::need_data); return; } - // skip chunk extensions - find_eol(cs, ec); - if(ec) + if(filter_) { - check_ec(); - return; + chunk_remain_ -= apply_filter( + ec, + clamp(chunk_remain_, cb0_.size()), + !chunked_body_ended); + + if(ec || chunked_body_ended) + return; } + else + { + const std::size_t chunk_avail = + clamp(chunk_remain_, cb0_.size()); + const auto chunk = + buffers::prefix(cb0_.data(), chunk_avail); - cb0_.consume(cb0_.size() - cs.size()); - chunk_remain_ = chunk_size; + if(body_limit_remain() < chunk_avail) + { + ec = BOOST_HTTP_PROTO_ERR( + error::body_too_large); + state_ = state::reset; + return; + } - needs_chunk_close_ = true; - if(chunk_remain_ == 0) - { - needs_chunk_close_ = false; - trailer_headers_ = true; - continue; + switch(style_) + { + case style::in_place: + { + auto copied = buffers::copy( + cb1_.prepare(cb1_.capacity()), + chunk); + chunk_remain_ -= copied; + body_avail_ += copied; + body_total_ += copied; + cb0_.consume(copied); + cb1_.commit(copied); + if(cb1_.capacity() == 0 + && !chunked_body_ended) + { + ec = BOOST_HTTP_PROTO_ERR( + error::in_place_overflow); + return; + } + break; + } + case style::sink: + { + auto sink_rs = sink_->write( + chunk, !chunked_body_ended); + chunk_remain_ -= sink_rs.bytes; + body_total_ += sink_rs.bytes; + cb0_.consume(sink_rs.bytes); + if(sink_rs.ec.failed()) + { + body_avail_ += + chunk_avail - sink_rs.bytes; + ec = sink_rs.ec; + state_ = state::reset; + return; + } + break; + } + case style::elastic: + { + if(eb_->max_size() - eb_->size() + < chunk_avail) + { + ec = BOOST_HTTP_PROTO_ERR( + error::buffer_overflow); + state_ = state::reset; + return; + } + buffers::copy( + eb_->prepare(chunk_avail), + chunk); + chunk_remain_ -= chunk_avail; + body_total_ += chunk_avail; + cb0_.consume(chunk_avail); + eb_->commit(chunk_avail); + break; + } + } + + if(chunked_body_ended) + { + set_state_to_complete(); + return; + } } } + } + else + { + // non-chunked payload - if(cb0_.size() == 0 && !chunked_body_ended) + const std::size_t payload_avail = [&]() { - if(got_eof_) - { - ec = BOOST_HTTP_PROTO_ERR( - error::incomplete); - st_ = state::reset; - return; - } - - ec = BOOST_HTTP_PROTO_ERR( - error::need_data); - return; - } + auto ret = cb0_.size(); + if(!filter_) + ret -= body_avail_; + if(h_.md.payload == payload::size) + return clamp(payload_remain_, ret); + // payload::eof + return ret; + }(); + + const bool is_complete = [&]() + { + if(h_.md.payload == payload::size) + return payload_avail == payload_remain_; + // payload::eof + return got_eof_; + }(); if(filter_) { - chunk_remain_ -= apply_filter( - ec, - clamp(chunk_remain_, cb0_.size()), - !chunked_body_ended); - - if(ec || chunked_body_ended) + payload_remain_ -= apply_filter( + ec, payload_avail, !is_complete); + if(ec || is_complete) return; } else { - const std::size_t chunk_avail = - clamp(chunk_remain_, cb0_.size()); - const auto chunk = - buffers::prefix(cb0_.data(), chunk_avail); + // plain body - if(body_limit_remain() < chunk_avail) + if(h_.md.payload == payload::to_eof) { - ec = BOOST_HTTP_PROTO_ERR( - error::body_too_large); - st_ = state::reset; - return; + if(body_limit_remain() < payload_avail) + { + ec = BOOST_HTTP_PROTO_ERR( + error::body_too_large); + state_ = state::reset; + return; + } } - switch(how_) + switch(style_) { - case how::in_place: + case style::in_place: { - auto copied = buffers::copy( - cb1_.prepare(cb1_.capacity()), - chunk); - chunk_remain_ -= copied; - body_avail_ += copied; - body_total_ += copied; - cb0_.consume(copied); - cb1_.commit(copied); - if(cb1_.capacity() == 0 - && !chunked_body_ended) + payload_remain_ -= payload_avail; + body_avail_ += payload_avail; + body_total_ += payload_avail; + if(cb0_.capacity() == 0 && !is_complete) { ec = BOOST_HTTP_PROTO_ERR( error::in_place_overflow); @@ -1314,244 +1456,497 @@ parse( } break; } - case how::sink: + case style::sink: { + payload_remain_ -= payload_avail; + body_total_ += payload_avail; auto sink_rs = sink_->write( - chunk, !chunked_body_ended); - chunk_remain_ -= sink_rs.bytes; - body_total_ += sink_rs.bytes; + buffers::prefix( + cb0_.data(), + payload_avail), + !is_complete); cb0_.consume(sink_rs.bytes); if(sink_rs.ec.failed()) { body_avail_ += - chunk_avail - sink_rs.bytes; + payload_avail - sink_rs.bytes; ec = sink_rs.ec; - st_ = state::reset; + state_ = state::reset; return; } break; } - case how::elastic: + case style::elastic: { - if(eb_->max_size() - eb_->size() - < chunk_avail) + // payload_remain_ and body_total_ + // are already updated in commit() + + // cb0_ contains data + if(payload_avail != 0) { - ec = BOOST_HTTP_PROTO_ERR( - error::buffer_overflow); - st_ = state::reset; - return; + if(eb_->max_size() - eb_->size() + < payload_avail) + { + ec = BOOST_HTTP_PROTO_ERR( + error::buffer_overflow); + state_ = state::reset; + return; + } + // only happens when an elastic body + // is attached in header_done state + buffers::copy( + eb_->prepare(payload_avail), + cb0_.data()); + cb0_.consume(payload_avail); + eb_->commit(payload_avail); + payload_remain_ -= payload_avail; + body_total_ += payload_avail; } - buffers::copy( - eb_->prepare(chunk_avail), - chunk); - chunk_remain_ -= chunk_avail; - body_total_ += chunk_avail; - cb0_.consume(chunk_avail); - eb_->commit(chunk_avail); break; } } - if(chunked_body_ended) + if(is_complete) { set_state_to_complete(); return; } } + + if(h_.md.payload == payload::size && got_eof_) + { + ec = BOOST_HTTP_PROTO_ERR( + error::incomplete); + state_ = state::reset; + return; + } + + ec = BOOST_HTTP_PROTO_ERR( + error::need_data); + return; } + + break; } - else - { - // non-chunked payload - const std::size_t payload_avail = [&]() - { - auto ret = cb0_.size(); - if(!filter_) - ret -= body_avail_; - if(h_.md.payload == payload::size) - return clamp(payload_remain_, ret); - // payload::eof - return ret; - }(); + case state::set_body: + case state::complete_in_place: + { + auto& body_buf = is_plain() ? cb0_ : cb1_; - const bool is_complete = [&]() + switch(style_) { - if(h_.md.payload == payload::size) - return payload_avail == payload_remain_; - // payload::eof - return got_eof_; - }(); - - if(filter_) + case style::in_place: + return; // no-op + case style::sink: { - payload_remain_ -= apply_filter( - ec, payload_avail, !is_complete); - if(ec || is_complete) + auto rs = sink_->write( + buffers::prefix( + body_buf.data(), + body_avail_), + state_ == state::set_body); + body_buf.consume(rs.bytes); + body_avail_ -= rs.bytes; + if(rs.ec.failed()) + { + ec = rs.ec; + state_ = state::reset; return; + } + break; } - else + case style::elastic: { - // plain body - - if(h_.md.payload == payload::to_eof) + if(eb_->max_size() - eb_->size() + < body_avail_) { - if(body_limit_remain() < payload_avail) - { - ec = BOOST_HTTP_PROTO_ERR( - error::body_too_large); - st_ = state::reset; - return; - } + ec = BOOST_HTTP_PROTO_ERR( + error::buffer_overflow); + return; } + buffers::copy( + eb_->prepare(body_avail_), + body_buf.data()); + body_buf.consume(body_avail_); + eb_->commit(body_avail_); + body_avail_ = 0; + // TODO: expand cb0_ when possible? + break; + } + } - switch(how_) - { - case how::in_place: + if(state_ == state::set_body) + { + state_ = state::body; + goto do_body; + } + + state_ = state::complete; + break; + } + + case state::complete: + break; + } + } + + auto + pull_body() -> + const_buffers_type + { + switch(state_) + { + case state::header_done: + return {}; + case state::body: + case state::complete_in_place: + cbp_ = buffers::prefix( + (is_plain() ? cb0_ : cb1_).data(), + body_avail_); + return const_buffers_type(cbp_); + default: + detail::throw_logic_error(); + } + } + + void + consume_body(std::size_t n) + { + switch(state_) + { + case state::header_done: + return; + case state::body: + case state::complete_in_place: + n = clamp(n, body_avail_); + (is_plain() ? cb0_ : cb1_).consume(n); + body_avail_ -= n; + return; + default: + detail::throw_logic_error(); + } + } + + core::string_view + body() const + { + // Precondition violation + if(state_ != state::complete_in_place) + detail::throw_logic_error(); + + // Precondition violation + if(body_avail_ != body_total_) + detail::throw_logic_error(); + + auto cbp = (is_plain() ? cb0_ : cb1_).data(); + BOOST_ASSERT(cbp[1].size() == 0); + BOOST_ASSERT(cbp[0].size() == body_avail_); + return core::string_view( + static_cast(cbp[0].data()), + body_avail_); + } + + void + set_body_limit(std::uint64_t n) + { + switch(state_) + { + case state::header: + case state::header_done: + body_limit_ = n; + break; + case state::complete_in_place: + // only allowed for empty bodies + if(body_total_ == 0) + break; + BOOST_FALLTHROUGH; + default: + // set body_limit before parsing the body + detail::throw_logic_error(); + } + } + + void + set_body( + buffers::any_dynamic_buffer& eb) noexcept + { + eb_ = &eb; + style_ = style::elastic; + nprepare_ = 0; // invalidate + if(state_ == state::body) + state_ = state::set_body; + } + + void + set_body(sink& s) noexcept + { + sink_ = &s; + style_ = style::sink; + nprepare_ = 0; // invalidate + if(state_ == state::body) + state_ = state::set_body; + } + + detail::workspace& + ws() noexcept + { + return ws_; + } + +private: + bool + is_plain() const noexcept + { + return ! filter_ && + h_.md.payload != payload::chunked; + } + + std::uint64_t + body_limit_remain() const noexcept + { + return body_limit_ - body_total_; + } + + std::size_t + apply_filter( + system::error_code& ec, + std::size_t payload_avail, + bool more) + { + std::size_t p0 = payload_avail; + for(;;) + { + if(payload_avail == 0 && more) + break; + + auto f_rs = [&](){ + BOOST_ASSERT(filter_ != nullptr); + if(style_ == style::elastic) { - payload_remain_ -= payload_avail; - body_avail_ += payload_avail; - body_total_ += payload_avail; - if(cb0_.capacity() == 0 && !is_complete) - { - ec = BOOST_HTTP_PROTO_ERR( - error::in_place_overflow); - return; - } - break; + std::size_t n = clamp(body_limit_remain()); + n = clamp(n, svc_.cfg.min_buffer); + n = clamp(n, eb_->max_size() - eb_->size()); + + // fill capacity first to avoid + // an allocation + std::size_t avail = + eb_->capacity() - eb_->size(); + if(avail != 0) + n = clamp(n, avail); + + return filter_->process( + eb_->prepare(n), + buffers::prefix( + cb0_.data(), + payload_avail), + more); } - case how::sink: + else // in-place and sink { - payload_remain_ -= payload_avail; - body_total_ += payload_avail; - auto sink_rs = sink_->write( + std::size_t n = clamp(body_limit_remain()); + n = clamp(n, cb1_.capacity()); + + return filter_->process( + buffers::mutable_buffer_span{ cb1_.prepare(n) }, buffers::prefix( cb0_.data(), payload_avail), - !is_complete); - cb0_.consume(sink_rs.bytes); - if(sink_rs.ec.failed()) - { - body_avail_ += - payload_avail - sink_rs.bytes; - ec = sink_rs.ec; - st_ = state::reset; - return; - } - break; + more); } - case how::elastic: - { - // payload_remain_ and body_total_ - // are already updated in commit() + }(); - // cb0_ contains data - if(payload_avail != 0) - { - if(eb_->max_size() - eb_->size() - < payload_avail) - { - ec = BOOST_HTTP_PROTO_ERR( - error::buffer_overflow); - st_ = state::reset; - return; - } - // only happens when an elastic body - // is attached in header_done state - buffers::copy( - eb_->prepare(payload_avail), - cb0_.data()); - cb0_.consume(payload_avail); - eb_->commit(payload_avail); - payload_remain_ -= payload_avail; - body_total_ += payload_avail; - } - break; + cb0_.consume(f_rs.in_bytes); + payload_avail -= f_rs.in_bytes; + body_total_ += f_rs.out_bytes; + + switch(style_) + { + case style::in_place: + { + cb1_.commit(f_rs.out_bytes); + body_avail_ += f_rs.out_bytes; + if(cb1_.capacity() == 0 && + !f_rs.finished && f_rs.in_bytes == 0) + { + ec = BOOST_HTTP_PROTO_ERR( + error::in_place_overflow); + goto done; } + break; + } + case style::sink: + { + cb1_.commit(f_rs.out_bytes); + auto sink_rs = sink_->write( + cb1_.data(), !f_rs.finished || more); + cb1_.consume(sink_rs.bytes); + if(sink_rs.ec.failed()) + { + ec = sink_rs.ec; + state_ = state::reset; + goto done; } - - if(is_complete) + break; + } + case style::elastic: + { + eb_->commit(f_rs.out_bytes); + if(eb_->max_size() - eb_->size() == 0 && + !f_rs.finished && f_rs.in_bytes == 0) { - set_state_to_complete(); - return; + ec = BOOST_HTTP_PROTO_ERR( + error::buffer_overflow); + state_ = state::reset; + goto done; } + break; + } } - if(h_.md.payload == payload::size && got_eof_) + if(f_rs.ec.failed()) + { + ec = f_rs.ec; + state_ = state::reset; + break; + } + + if(body_limit_remain() == 0 && + !f_rs.finished && f_rs.in_bytes == 0) { ec = BOOST_HTTP_PROTO_ERR( - error::incomplete); - st_ = state::reset; - return; + error::body_too_large); + state_ = state::reset; + break; } - ec = BOOST_HTTP_PROTO_ERR( - error::need_data); - return; + if(f_rs.finished) + { + if(!more) + { + state_ = (style_ == style::in_place) + ? state::complete_in_place + : state::complete; + } + break; + } } - break; + done: + return p0 - payload_avail; } +}; - case state::set_body: - case state::complete_in_place: - { - auto& body_buf = is_plain() ? cb0_ : cb1_; +//------------------------------------------------ +// +// Special Members +// +//------------------------------------------------ - switch(how_) - { - case how::in_place: - return; // no-op - case how::sink: - { - auto rs = sink_->write( - buffers::prefix( - body_buf.data(), - body_avail_), - st_ == state::set_body); - body_buf.consume(rs.bytes); - body_avail_ -= rs.bytes; - if(rs.ec.failed()) - { - ec = rs.ec; - st_ = state::reset; - return; - } - break; - } - case how::elastic: - { - if(eb_->max_size() - eb_->size() - < body_avail_) - { - ec = BOOST_HTTP_PROTO_ERR( - error::buffer_overflow); - return; - } - buffers::copy( - eb_->prepare(body_avail_), - body_buf.data()); - body_buf.consume(body_avail_); - eb_->commit(body_avail_); - body_avail_ = 0; - // TODO: expand cb0_ when possible? - break; - } - } +parser:: +parser(const rts::context& ctx, detail::kind k) + : impl_(new impl(ctx, k)) +{ + // TODO: use a single allocation for + // impl and workspace buffer. +} - if(st_ == state::set_body) - { - st_ = state::body; - goto do_body; - } +parser:: +parser(parser&& other) noexcept + : impl_(other.impl_) +{ + other.impl_ = nullptr; +} - st_ = state::complete; - break; - } +parser& +parser:: +operator=(parser&&other) noexcept +{ + delete impl_; + impl_ = other.impl_; + other.impl_ = nullptr; + return *this; +} - case state::complete: - break; - } +parser:: +~parser() +{ + delete impl_; +} + +//-------------------------------------------- +// +// Observers +// +//-------------------------------------------- + +bool +parser::got_header() const noexcept +{ + BOOST_ASSERT(impl_); + return impl_->got_header(); +} + +bool +parser::is_complete() const noexcept +{ + BOOST_ASSERT(impl_); + return impl_->is_complete(); +} + +//------------------------------------------------ +// +// Modifiers +// +//------------------------------------------------ + +void +parser:: +reset() noexcept +{ + BOOST_ASSERT(impl_); + impl_->reset(); +} + +void +parser::start() +{ + BOOST_ASSERT(impl_); + impl_->start(false); +} + +auto +parser:: +prepare() -> + mutable_buffers_type +{ + BOOST_ASSERT(impl_); + return impl_->prepare(); +} + +void +parser:: +commit( + std::size_t n) +{ + BOOST_ASSERT(impl_); + impl_->commit(n); +} + +void +parser:: +commit_eof() +{ + BOOST_ASSERT(impl_); + impl_->commit_eof(); +} + +void +parser:: +parse( + system::error_code& ec) +{ + BOOST_ASSERT(impl_); + impl_->parse(ec); } auto @@ -1559,58 +1954,24 @@ parser:: pull_body() -> const_buffers_type { - switch(st_) - { - case state::header_done: - return {}; - case state::body: - case state::complete_in_place: - cbp_ = buffers::prefix( - (is_plain() ? cb0_ : cb1_).data(), - body_avail_); - return const_buffers_type(cbp_); - default: - detail::throw_logic_error(); - } + BOOST_ASSERT(impl_); + return impl_->pull_body(); } void parser:: consume_body(std::size_t n) { - switch(st_) - { - case state::header_done: - return; - case state::body: - case state::complete_in_place: - n = clamp(n, body_avail_); - (is_plain() ? cb0_ : cb1_).consume(n); - body_avail_ -= n; - return; - default: - detail::throw_logic_error(); - } + BOOST_ASSERT(impl_); + impl_->consume_body(n); } core::string_view parser:: body() const { - // Precondition violation - if(st_ != state::complete_in_place) - detail::throw_logic_error(); - - // Precondition violation - if(body_avail_ != body_total_) - detail::throw_logic_error(); - - auto cbp = (is_plain() ? cb0_ : cb1_).data(); - BOOST_ASSERT(cbp[1].size() == 0); - BOOST_ASSERT(cbp[0].size() == body_avail_); - return core::string_view( - static_cast(cbp[0].data()), - body_avail_); + BOOST_ASSERT(impl_); + return impl_->body(); } core::string_view @@ -1625,21 +1986,8 @@ void parser:: set_body_limit(std::uint64_t n) { - switch(st_) - { - case state::header: - case state::header_done: - body_limit_ = n; - break; - case state::complete_in_place: - // only allowed for empty bodies - if(body_total_ == 0) - break; - BOOST_FALLTHROUGH; - default: - // set body_limit before parsing the body - detail::throw_logic_error(); - } + BOOST_ASSERT(impl_); + impl_->set_body_limit(n); } //------------------------------------------------ @@ -1650,172 +1998,51 @@ set_body_limit(std::uint64_t n) void parser:: -on_set_body() noexcept +start_impl(bool head_response) { - BOOST_ASSERT( - st_ == state::header_done || - st_ == state::body || - st_ == state::complete_in_place); - - nprepare_ = 0; // invalidate - - if(st_ == state::body) - st_ = state::set_body; + BOOST_ASSERT(impl_); + impl_->start(head_response); } -std::size_t +detail::header const* parser:: -apply_filter( - system::error_code& ec, - std::size_t payload_avail, - bool more) +safe_get_header() const { - std::size_t p0 = payload_avail; - for(;;) - { - if(payload_avail == 0 && more) - break; - - auto f_rs = [&](){ - BOOST_ASSERT(filter_ != nullptr); - if(how_ == how::elastic) - { - std::size_t n = clamp(body_limit_remain()); - n = clamp(n, svc_.cfg.min_buffer); - n = clamp(n, eb_->max_size() - eb_->size()); - - // fill capacity first to avoid - // an allocation - std::size_t avail = - eb_->capacity() - eb_->size(); - if(avail != 0) - n = clamp(n, avail); - - return filter_->process( - eb_->prepare(n), - buffers::prefix( - cb0_.data(), - payload_avail), - more); - } - else // in-place and sink - { - std::size_t n = clamp(body_limit_remain()); - n = clamp(n, cb1_.capacity()); - - return filter_->process( - buffers::mutable_buffer_span{ cb1_.prepare(n) }, - buffers::prefix( - cb0_.data(), - payload_avail), - more); - } - }(); - - cb0_.consume(f_rs.in_bytes); - payload_avail -= f_rs.in_bytes; - body_total_ += f_rs.out_bytes; - - switch(how_) - { - case how::in_place: - { - cb1_.commit(f_rs.out_bytes); - body_avail_ += f_rs.out_bytes; - if(cb1_.capacity() == 0 && - !f_rs.finished && f_rs.in_bytes == 0) - { - ec = BOOST_HTTP_PROTO_ERR( - error::in_place_overflow); - goto done; - } - break; - } - case how::sink: - { - cb1_.commit(f_rs.out_bytes); - auto sink_rs = sink_->write( - cb1_.data(), !f_rs.finished || more); - cb1_.consume(sink_rs.bytes); - if(sink_rs.ec.failed()) - { - ec = sink_rs.ec; - st_ = state::reset; - goto done; - } - break; - } - case how::elastic: - { - eb_->commit(f_rs.out_bytes); - if(eb_->max_size() - eb_->size() == 0 && - !f_rs.finished && f_rs.in_bytes == 0) - { - ec = BOOST_HTTP_PROTO_ERR( - error::buffer_overflow); - st_ = state::reset; - goto done; - } - break; - } - } - - if(f_rs.ec.failed()) - { - ec = f_rs.ec; - st_ = state::reset; - break; - } - - if(body_limit_remain() == 0 && - !f_rs.finished && f_rs.in_bytes == 0) - { - ec = BOOST_HTTP_PROTO_ERR( - error::body_too_large); - st_ = state::reset; - break; - } - - if(f_rs.finished) - { - if(!more) - { - st_ = (how_ == how::in_place) - ? state::complete_in_place - : state::complete; - } - break; - } - } - -done: - return p0 - payload_avail; + BOOST_ASSERT(impl_); + return impl_->safe_get_header(); } -detail::header const* +detail::workspace& parser:: -safe_get_header() const +ws() noexcept { - // headers must be received - if(! got_header_) - detail::throw_logic_error(); - - return &h_; + BOOST_ASSERT(impl_); + return impl_->ws(); } bool parser:: -is_plain() const noexcept +is_body_set() const noexcept { - return ! filter_ && - h_.md.payload != payload::chunked; + BOOST_ASSERT(impl_); + return impl_->is_body_set(); } -std::uint64_t +void +parser:: +set_body_impl( + buffers::any_dynamic_buffer& eb) noexcept +{ + BOOST_ASSERT(impl_); + impl_->set_body(eb); +} + +void parser:: -body_limit_remain() const noexcept +set_body_impl(sink& s) noexcept { - return body_limit_ - body_total_; + BOOST_ASSERT(impl_); + impl_->set_body(s); } } // http_proto diff --git a/src/serializer.cpp b/src/serializer.cpp index 1ae378a8..4dc2c1be 100644 --- a/src/serializer.cpp +++ b/src/serializer.cpp @@ -287,63 +287,16 @@ serializer:: { } +// TODO: use an indirection for stream +// interface so it stays valid after move. serializer:: -serializer( - serializer&& other) noexcept - : ctx_(other.ctx_) - , svc_(other.svc_) - , ws_(std::move(other.ws_)) - , filter_(other.filter_) - , cbs_gen_(other.cbs_gen_) - , source_(other.source_) - , out_(other.out_) - , in_(other.in_) - , prepped_(other.prepped_) - , tmp_(other.tmp_) - , state_(other.state_) - , style_(other.style_) - , chunk_header_len_(other.chunk_header_len_) - , more_input_(other.more_input_) - , is_chunked_(other.is_chunked_) - , needs_exp100_continue_(other.needs_exp100_continue_) - , filter_done_(other.filter_done_) -{ - // TODO: make state a class type and default - // move ctor and assignment. - - // TODO: use an indirection for stream - // interface so it stays valid after move. - - other.state_ = state::start; -} +serializer(serializer&&) noexcept = default; +// TODO: use an indirection for stream +// interface so it stays valid after move. serializer& serializer:: -operator=( - serializer&& other) noexcept -{ - ctx_ = other.ctx_; - svc_ = other.svc_; - ws_ = std::move(other.ws_); - filter_ = other.filter_; - cbs_gen_ = other.cbs_gen_; - source_ = other.source_; - out_ = other.out_; - in_ = other.in_; - prepped_ = other.prepped_; - tmp_ = other.tmp_; - state_ = other.state_; - style_ = other.style_; - chunk_header_len_ = other.chunk_header_len_; - more_input_ = other.more_input_; - is_chunked_ = other.is_chunked_; - needs_exp100_continue_ = other.needs_exp100_continue_; - filter_done_ = other.filter_done_; - - other.state_ = state::start; - - return *this; -} +operator=(serializer&&) noexcept = default; serializer:: serializer(const rts::context& ctx) diff --git a/test/unit/parser.cpp b/test/unit/parser.cpp index 27cb9283..e823beef 100644 --- a/test/unit/parser.cpp +++ b/test/unit/parser.cpp @@ -336,16 +336,68 @@ struct parser_test void testSpecial() { - // ~parser + // parser(parser&&) + { + rts::context ctx; + install_parser_service(ctx, {}); + + core::string_view header = + "POST / HTTP/1.1\r\n" + "Content-Length: 3\r\n" + "\r\n"; + core::string_view body = "123"; + pieces in = { header, body }; + request_parser pr1(ctx); + pr1.reset(); + pr1.start(); + system::error_code ec; + read_header(pr1, in, ec); + BOOST_TEST_NOT(ec.failed()); + + request_parser pr2(std::move(pr1)); + + BOOST_TEST_EQ(pr2.get().buffer(), header); + read(pr2, in, ec); + BOOST_TEST_NOT(ec.failed()); + BOOST_TEST_EQ(pr2.body(), body); + } + + // parser& operator=(parser&&) { - request_parser pr(ctx_); + rts::context ctx; + install_parser_service(ctx, {}); + + core::string_view header = + "POST / HTTP/1.1\r\n" + "Content-Length: 3\r\n" + "\r\n"; + core::string_view body = "123"; + pieces in = { header, body }; + + request_parser pr1(ctx); + request_parser pr2(ctx); + + pr1.reset(); + pr1.start(); + system::error_code ec; + read_header(pr1, in, ec); + + pr2 = std::move(pr1); + + BOOST_TEST_EQ(pr2.get().buffer(), header); + read(pr2, in, ec); + BOOST_TEST_NOT(ec.failed()); + BOOST_TEST_EQ(pr2.body(), body); } + // ~parser + { + request_parser pr(ctx_); + } { response_parser pr(ctx_); } - } void diff --git a/test/unit/request_parser.cpp b/test/unit/request_parser.cpp index e04d8e02..3979cc83 100644 --- a/test/unit/request_parser.cpp +++ b/test/unit/request_parser.cpp @@ -200,13 +200,30 @@ struct request_parser_test void testSpecial() { - // request_parser() + // request_parser(rts::context&) { rts::context ctx; request_parser::config cfg; install_parser_service(ctx, cfg); request_parser pr(ctx); } + + // request_parser(request_parser&&) + { + rts::context ctx; + install_parser_service(ctx, {}); + request_parser pr1(ctx); + request_parser pr2(std::move(pr1)); + } + + // request_parser& operator=(request_parser&&) + { + rts::context ctx; + install_parser_service(ctx, {}); + request_parser pr1(ctx); + request_parser pr2(ctx); + pr2 = std::move(pr1); + } } //-------------------------------------------- diff --git a/test/unit/response_parser.cpp b/test/unit/response_parser.cpp index 4162bc73..dbf922d5 100644 --- a/test/unit/response_parser.cpp +++ b/test/unit/response_parser.cpp @@ -30,6 +30,23 @@ class response_parser_test install_parser_service(ctx, cfg); response_parser pr(ctx); } + + // response_parser(response_parser&&) + { + rts::context ctx; + install_parser_service(ctx, {}); + response_parser pr1(ctx); + response_parser pr2(std::move(pr1)); + } + + // response_parser& operator=(response_parser&&) + { + rts::context ctx; + install_parser_service(ctx, {}); + response_parser pr1(ctx); + response_parser pr2(ctx); + pr2 = std::move(pr1); + } } void diff --git a/test/unit/serializer.cpp b/test/unit/serializer.cpp index 3cf9a96c..8e3021b3 100644 --- a/test/unit/serializer.cpp +++ b/test/unit/serializer.cpp @@ -239,7 +239,12 @@ struct serializer_test } serializer sr2(std::move(sr1)); + + // valid moved-from state BOOST_TEST(sr1.is_done()); + BOOST_TEST_THROWS( + sr1.start(res), + std::length_error); // consume the reset from sr2 { @@ -273,7 +278,12 @@ struct serializer_test serializer sr2(ctx); sr2 = std::move(sr1); + + // valid moved-from state BOOST_TEST(sr1.is_done()); + BOOST_TEST_THROWS( + sr1.start(res), + std::length_error); // consume the reset from sr2 {