//
// Copyright (c) 2019 Vinnie Falco ([email protected])
// Copyright (c) 2024 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
//
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "src/detail/brotli_filter_base.hpp"
#include "src/detail/buffer_utils.hpp"
#include "src/detail/zlib_filter_base.hpp"
#include
namespace boost {
namespace http {
/*
Principles for fixed-size buffer design
axiom 1:
To read data you must have a buffer.
axiom 2:
The size of the HTTP header is not
known in advance.
conclusion 3:
A single I/O can produce a complete
HTTP header and additional payload
data.
conclusion 4:
A single I/O can produce multiple
complete HTTP headers, complete
payloads, and a partial header or
payload.
axiom 5:
A process is in one of two states:
1. at or below capacity
2. above capacity
axiom 6:
A program which can allocate an
unbounded number of resources can
go above capacity.
conclusion 7:
A program can guarantee never going
above capacity if all resources are
provisioned at program startup.
corollary 8:
`parser` and `serializer` should each
allocate a single buffer of calculated
size, and never resize it.
axiom #:
A parser and a serializer are always
used in pairs.
Buffer Usage
| | begin
| H | p | | f | read headers
| H | p | | T | f | set T body
| H | p | | C | T | f | make codec C
| H | p | b | C | T | f | decode p into b
| H | p | b | C | T | f | read/parse loop
| H | | T | f | destroy codec
| H | | T | f | finished
H headers
C codec
T body
f table
p partial payload
b body data
"payload" is the bytes coming in from
the stream.
"body" is the logical body, after transfer
encoding is removed. This can be the
same as the payload.
A "plain payload" is when the payload and
body are identical (no transfer encodings).
A "buffered payload" is any payload which is
not plain. A second buffer is required
for reading.
"overread" is additional data received past
the end of the headers when reading headers,
or additional data received past the end of
the message payload.
*/
namespace {
class chained_sequence
{
char const* pos_;
char const* end_;
char const* begin_b_;
char const* end_b_;
public:
chained_sequence(capy::const_buffer_pair const& cbp)
: pos_(static_cast(cbp[0].data()))
, end_(pos_ + cbp[0].size())
, begin_b_(static_cast(cbp[1].data()))
, end_b_(begin_b_ + cbp[1].size())
{
}
char const*
next() noexcept
{
++pos_;
// most frequently taken branch
if(pos_ < end_)
return pos_;
// bring the second range
if(begin_b_ != end_b_)
{
pos_ = begin_b_;
end_ = end_b_;
begin_b_ = end_b_;
return pos_;
}
// undo the increament
pos_ = end_;
return nullptr;
}
bool
is_empty() const noexcept
{
return pos_ == end_;
}
char
value() const noexcept
{
return *pos_;
}
std::size_t
size() const noexcept
{
return (end_ - pos_) + (end_b_ - begin_b_);
}
};
std::uint64_t
parse_hex(
chained_sequence& cs,
system::error_code& ec) noexcept
{
std::uint64_t v = 0;
std::size_t init_size = cs.size();
while(!cs.is_empty())
{
auto n = grammar::hexdig_value(cs.value());
if(n < 0)
{
if(init_size == cs.size())
{
ec = BOOST_HTTP_ERR(
error::bad_payload);
return 0;
}
return v;
}
// at least 4 significant bits are free
if(v > (std::numeric_limits<:uint64_t>::max)() >> 4)
{
ec = BOOST_HTTP_ERR(
error::bad_payload);
return 0;
}
v = (v << 4) | static_cast<:uint64_t>(n);
cs.next();
}
ec = BOOST_HTTP_ERR(
error::need_data);
return 0;
}
void
find_eol(
chained_sequence& cs,
system::error_code& ec) noexcept
{
while(!cs.is_empty())
{
if(cs.value() == '\r')
{
if(!cs.next())
break;
if(cs.value() != '\n')
{
ec = BOOST_HTTP_ERR(
error::bad_payload);
return;
}
cs.next();
return;
}
cs.next();
}
ec = BOOST_HTTP_ERR(
error::need_data);
}
void
parse_eol(
chained_sequence& cs,
system::error_code& ec) noexcept
{
if(cs.size() >= 2)
{
// we are sure size is at least 2
if(cs.value() == '\r' && *cs.next() == '\n')
{
cs.next();
return;
}
ec = BOOST_HTTP_ERR(
error::bad_payload);
return;
}
ec = BOOST_HTTP_ERR(
error::need_data);
}
void
skip_trailer_headers(
chained_sequence& cs,
system::error_code& ec) noexcept
{
while(!cs.is_empty())
{
if(cs.value() == '\r')
{
if(!cs.next())
break;
if(cs.value() != '\n')
{
ec = BOOST_HTTP_ERR(
error::bad_payload);
return;
}
cs.next();
return;
}
// skip to the end of field
find_eol(cs, ec);
if(ec)
return;
}
ec = BOOST_HTTP_ERR(
error::need_data);
}
template
std::size_t
clamp(
UInt x,
std::size_t limit = (std::numeric_limits<
std::size_t>::max)()) noexcept
{
if(x >= limit)
return limit;
return static_cast<:size_t>(x);
}
class zlib_filter
: public detail::zlib_filter_base
{
http::zlib::inflate_service& svc_;
public:
zlib_filter(
http::zlib::inflate_service& svc,
int window_bits)
: svc_(svc)
{
system::error_code ec = static_cast<:zlib::error>(
svc_.init2(strm_, window_bits));
if(ec != http::zlib::error::ok)
detail::throw_system_error(ec);
}
private:
virtual
results
do_process(
capy::mutable_buffer out,
capy::const_buffer in,
bool more) noexcept override
{
strm_.next_out = static_cast(out.data());
strm_.avail_out = saturate_cast(out.size());
strm_.next_in = static_cast(const_cast(in.data()));
strm_.avail_in = saturate_cast(in.size());
auto rs = static_cast<:zlib::error>(
svc_.inflate(
strm_,
more ? http::zlib::no_flush : http::zlib::finish));
results rv;
rv.out_bytes = saturate_cast(out.size()) - strm_.avail_out;
rv.in_bytes = saturate_cast(in.size()) - strm_.avail_in;
rv.finished = (rs == http::zlib::error::stream_end);
if(rs < http::zlib::error::ok && rs != http::zlib::error::buf_err)
rv.ec = rs;
return rv;
}
};
class brotli_filter
: public detail::brotli_filter_base
{
http::brotli::decode_service& svc_;
http::brotli::decoder_state* state_;
public:
brotli_filter(http::brotli::decode_service& svc)
: svc_(svc)
{
state_ = svc_.create_instance(nullptr, nullptr, nullptr);
if(!state_)
detail::throw_bad_alloc();
}
~brotli_filter()
{
svc_.destroy_instance(state_);
}
private:
virtual
results
do_process(
capy::mutable_buffer out,
capy::const_buffer in,
bool more) noexcept override
{
auto* next_in = reinterpret_cast(in.data());
auto available_in = in.size();
auto* next_out = reinterpret_cast<:uint8_t>(out.data());
auto available_out = out.size();
auto rs = svc_.decompress_stream(
state_,
&available_in,
&next_in,
&available_out,
&next_out,
nullptr);
results rv;
rv.in_bytes = in.size() - available_in;
rv.out_bytes = out.size() - available_out;
rv.finished = svc_.is_finished(state_);
if(!more && rs == http::brotli::decoder_result::needs_more_input)
rv.ec = BOOST_HTTP_ERR(error::bad_payload);
if(rs == http::brotli::decoder_result::error)
rv.ec = BOOST_HTTP_ERR(
svc_.get_error_code(state_));
return rv;
}
};
} // namespace
//------------------------------------------------
class parser::impl
{
enum class state
{
reset,
start,
header,
header_done,
body,
complete,
};
std::shared_ptr cfg_;
detail::workspace ws_;
static_request m_;
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_;
capy::flat_dynamic_buffer fb_;
capy::circular_dynamic_buffer cb0_;
capy::circular_dynamic_buffer cb1_;
capy::mutable_buffer_pair mbp_;
capy::const_buffer_pair cbp_;
std::unique_ptr<:filter> filter_;
state state_;
bool got_header_;
bool got_eof_;
bool head_response_;
bool needs_chunk_close_;
bool trailer_headers_;
bool chunked_body_ended;
public:
impl(std::shared_ptr cfg, detail::kind k)
: cfg_(std::move(cfg))
, ws_(cfg_->space_needed)
, m_(ws_.data(), ws_.size())
, state_(state::reset)
, got_header_(false)
{
m_.h_ = detail::header(detail::empty{ k });
}
bool
got_header() const noexcept
{
return got_header_;
}
bool
is_complete() const noexcept
{
return state_ == state::complete;
}
static_request const&
safe_get_request() const
{
// headers must be received
if(! got_header_)
detail::throw_logic_error();
return m_;
}
static_response const&
safe_get_response() const
{
// headers must be received
if(! got_header_)
detail::throw_logic_error();
// TODO: use a union
return reinterpret_cast(m_);
}
void
reset() noexcept
{
ws_.clear();
state_ = state::start;
got_header_ = false;
got_eof_ = false;
}
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();
case state::start:
// reset required on eof
if(got_eof_)
detail::throw_logic_error();
break;
case state::header:
if(fb_.size() == 0)
{
// start() called twice
detail::throw_logic_error();
}
BOOST_FALLTHROUGH;
case state::header_done:
case state::body:
// current message is incomplete
detail::throw_logic_error();
case state::complete:
{
// remove available body.
if(is_plain())
cb0_.consume(body_avail_);
// move leftovers to front
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<:size_t>(b - dest);
std::memcpy(dest, a, chunk_a); // never overlap
an -= chunk_a;
dest += chunk_a;
a += chunk_a;
} while(an);
}
break;
}
}
ws_.clear();
fb_ = {
ws_.data(),
cfg_->headers.max_size + cfg_->min_buffer,
leftover };
BOOST_ASSERT(
fb_.capacity() == cfg_->max_overread() - leftover);
BOOST_ASSERT(
head_response == false ||
m_.h_.kind == detail::kind::response);
m_.h_ = detail::header(detail::empty{m_.h_.kind});
m_.h_.buf = reinterpret_cast(ws_.data());
m_.h_.cbuf = m_.h_.buf;
m_.h_.cap = ws_.size();
state_ = state::header;
// reset to the configured default
body_limit_ = cfg_->body_limit;
body_total_ = 0;
payload_remain_ = 0;
chunk_remain_ = 0;
body_avail_ = 0;
nprepare_ = 0;
filter_.reset();
got_header_ = false;
head_response_ = head_response;
needs_chunk_close_ = false;
trailer_headers_ = false;
chunked_body_ended = false;
}
auto
prepare() ->
mutable_buffers_type
{
nprepare_ = 0;
switch(state_)
{
default:
case state::reset:
// reset must be called first
detail::throw_logic_error();
case state::start:
// start must be called first
detail::throw_logic_error();
case state::header:
{
BOOST_ASSERT(
m_.h_.size < cfg_->headers.max_size);
std::size_t n = fb_.capacity();
BOOST_ASSERT(n <= cfg_->max_overread());
n = clamp(n, cfg_->max_prepare);
mbp_[0] = fb_.prepare(n);
nprepare_ = n;
return mutable_buffers_type(&mbp_[0], 1);
}
case state::header_done:
// forgot to call parse()
detail::throw_logic_error();
case state::body:
{
if(got_eof_)
{
// forgot to call parse()
detail::throw_logic_error();
}
if(! is_plain())
{
// buffered payload
std::size_t n = cb0_.capacity();
n = clamp(n, cfg_->max_prepare);
nprepare_ = n;
mbp_ = cb0_.prepare(n);
return detail::make_span(mbp_);
}
else
{
// plain payload
std::size_t n = cb0_.capacity();
n = clamp(n, cfg_->max_prepare);
if(m_.payload() == payload::size)
{
if(n > payload_remain_)
{
std::size_t overread =
n - static_cast<:size_t>(payload_remain_);
if(overread > cfg_->max_overread())
n = static_cast<:size_t>(payload_remain_) +
cfg_->max_overread();
}
}
else
{
BOOST_ASSERT(
m_.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 detail::make_span(mbp_);
}
}
case state::complete:
// already complete
detail::throw_logic_error();
}
}
void
commit(
std::size_t n)
{
switch(state_)
{
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:
{
if(n > nprepare_)
{
// 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
fb_.commit(n);
break;
}
case state::header_done:
{
// forgot to call parse()
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();
}
if(got_eof_)
{
// can't commit after EOF
detail::throw_logic_error();
}
nprepare_ = 0; // invalidate
cb0_.commit(n);
break;
}
case state::complete:
{
// already complete
detail::throw_logic_error();
}
}
}
void
commit_eof()
{
nprepare_ = 0; // invalidate
switch(state_)
{
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::complete:
// can't commit eof when complete
detail::throw_logic_error();
}
}
void
parse(
system::error_code& ec)
{
ec = {};
switch(state_)
{
default:
case state::reset:
// reset must be called first
detail::throw_logic_error();
case state::start:
// start must be called first
detail::throw_logic_error();
case state::header:
{
BOOST_ASSERT(m_.h_.buf == static_cast<
void const*>(ws_.data()));
BOOST_ASSERT(m_.h_.cbuf == static_cast<
void const*>(ws_.data()));
m_.h_.parse(fb_.size(), cfg_->headers, ec);
if(ec == condition::need_more_input)
{
if(! got_eof_)
{
// headers incomplete
return;
}
if(fb_.size() == 0)
{
// stream closed cleanly
state_ = state::reset;
ec = BOOST_HTTP_ERR(
error::end_of_stream);
return;
}
// stream closed with a
// partial message received
state_ = state::reset;
ec = BOOST_HTTP_ERR(
error::incomplete);
return;
}
else if(ec)
{
// other error,
//
// VFALCO map this to a bad
// request or bad response error?
//
state_ = state::reset; // unrecoverable
return;
}
got_header_ = true;
// reserve headers + table
ws_.reserve_front(m_.h_.size);
ws_.reserve_back(m_.h_.table_space());
// no payload
if(m_.payload() == payload::none ||
head_response_)
{
// octets of the next message
auto overread = fb_.size() - m_.h_.size;
cb0_ = { ws_.data(), overread, overread };
ws_.reserve_front(overread);
state_ = state::complete;
return;
}
state_ = state::header_done;
break;
}
case state::header_done:
{
// metadata error
if(m_.payload() == payload::error)
{
// VFALCO This needs looking at
ec = BOOST_HTTP_ERR(
error::bad_payload);
state_ = 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() - m_.h_.size;
BOOST_ASSERT(overread <= cfg_->max_overread());
auto cap = fb_.capacity() + overread +
cfg_->min_buffer;
// reserve body buffers first, as the decoder
// must be installed after them.
auto const p = ws_.reserve_front(cap);
// Content-Encoding
switch(m_.metadata().content_encoding.coding)
{
case content_coding::deflate:
if(!cfg_->apply_deflate_decoder)
goto no_filter;
if(auto* svc = capy::get_system_context().find_service<:zlib::inflate_service>())
{
filter_.reset(new zlib_filter(
*svc,
cfg_->zlib_window_bits));
}
break;
case content_coding::gzip:
if(!cfg_->apply_gzip_decoder)
goto no_filter;
if(auto* svc = capy::get_system_context().find_service<:zlib::inflate_service>())
{
filter_.reset(new zlib_filter(
*svc,
cfg_->zlib_window_bits + 16));
}
break;
case content_coding::br:
if(!cfg_->apply_brotli_decoder)
goto no_filter;
if(auto* svc = capy::get_system_context().find_service<:brotli::decode_service>())
{
filter_.reset(new brotli_filter(*svc));
}
break;
no_filter:
default:
break;
}
if(is_plain())
{
cb0_ = { p, cap, overread };
cb1_ = {};
}
else
{
// buffered payload
std::size_t n0 = (overread > cfg_->min_buffer)
? overread
: cfg_->min_buffer;
std::size_t n1 = cfg_->min_buffer;
cb0_ = { p , n0, overread };
cb1_ = { p + n0 , n1 };
}
if(m_.payload() == payload::size)
{
if(!filter_ &&
body_limit_ < m_.payload_size())
{
ec = BOOST_HTTP_ERR(
error::body_too_large);
state_ = state::reset;
return;
}
payload_remain_ = m_.payload_size();
}
state_ = state::body;
BOOST_FALLTHROUGH;
}
case state::body:
{
BOOST_ASSERT(state_ == state::body);
BOOST_ASSERT(m_.payload() != payload::none);
BOOST_ASSERT(m_.payload() != payload::error);
auto set_state_to_complete = [&]()
{
state_ = state::complete;
};
if(m_.payload() == payload::chunked)
{
for(;;)
{
if(chunk_remain_ == 0
&& !chunked_body_ended)
{
auto cs = chained_sequence(cb0_.data());
auto check_ec = [&]()
{
if(ec == condition::need_more_input && got_eof_)
{
ec = BOOST_HTTP_ERR(error::incomplete);
state_ = state::reset;
}
};
if(needs_chunk_close_)
{
parse_eol(cs, ec);
if(ec)
{
check_ec();
return;
}
}
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;
}
// skip chunk extensions
find_eol(cs, ec);
if(ec)
{
check_ec();
return;
}
cb0_.consume(cb0_.size() - cs.size());
chunk_remain_ = chunk_size;
needs_chunk_close_ = true;
if(chunk_remain_ == 0)
{
needs_chunk_close_ = false;
trailer_headers_ = true;
continue;
}
}
if(cb0_.size() == 0 && !chunked_body_ended)
{
if(got_eof_)
{
ec = BOOST_HTTP_ERR(
error::incomplete);
state_ = state::reset;
return;
}
ec = BOOST_HTTP_ERR(
error::need_data);
return;
}
if(filter_)
{
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 =
capy::prefix(cb0_.data(), chunk_avail);
if(body_limit_remain() < chunk_avail)
{
ec = BOOST_HTTP_ERR(
error::body_too_large);
state_ = state::reset;
return;
}
// in_place style
auto copied = capy::buffer_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_ERR(
error::in_place_overflow);
return;
}
if(chunked_body_ended)
{
set_state_to_complete();
return;
}
}
}
}
else
{
// non-chunked payload
const std::size_t payload_avail = [&]()
{
auto ret = cb0_.size();
if(!filter_)
ret -= body_avail_;
if(m_.payload() == payload::size)
return clamp(payload_remain_, ret);
// payload::eof
return ret;
}();
const bool is_complete = [&]()
{
if(m_.payload() == payload::size)
return payload_avail == payload_remain_;
// payload::eof
return got_eof_;
}();
if(filter_)
{
payload_remain_ -= apply_filter(
ec, payload_avail, !is_complete);
if(ec || is_complete)
return;
}
else
{
// plain body
if(m_.payload() == payload::to_eof)
{
if(body_limit_remain() < payload_avail)
{
ec = BOOST_HTTP_ERR(
error::body_too_large);
state_ = state::reset;
return;
}
}
// in_place style
payload_remain_ -= payload_avail;
body_avail_ += payload_avail;
body_total_ += payload_avail;
if(cb0_.capacity() == 0 && !is_complete)
{
ec = BOOST_HTTP_ERR(
error::in_place_overflow);
return;
}
if(is_complete)
{
set_state_to_complete();
return;
}
}
if(m_.payload() == payload::size && got_eof_)
{
ec = BOOST_HTTP_ERR(
error::incomplete);
state_ = state::reset;
return;
}
ec = BOOST_HTTP_ERR(
error::need_data);
return;
}
break;
}
case state::complete:
break;
}
}
auto
pull_body() ->
const_buffers_type
{
switch(state_)
{
case state::header_done:
return {};
case state::body:
case state::complete:
cbp_ = capy::prefix(
(is_plain() ? cb0_ : cb1_).data(),
body_avail_);
return detail::make_span(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:
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)
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:
// only allowed for empty bodies
if(body_total_ == 0)
break;
BOOST_FALLTHROUGH;
default:
// set body_limit before parsing the body
detail::throw_logic_error();
}
}
private:
bool
is_plain() const noexcept
{
return ! filter_ &&
m_.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);
std::size_t n = clamp(body_limit_remain());
n = clamp(n, cb1_.capacity());
return filter_->process(
detail::make_span(cb1_.prepare(n)),
capy::prefix(cb0_.data(), payload_avail),
more);
}();
cb0_.consume(f_rs.in_bytes);
payload_avail -= f_rs.in_bytes;
body_total_ += f_rs.out_bytes;
// in_place style
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_ERR(
error::in_place_overflow);
goto done;
}
if(f_rs.ec)
{
ec = f_rs.ec;
state_ = state::reset;
break;
}
if(body_limit_remain() == 0 &&
!f_rs.finished && f_rs.in_bytes == 0)
{
ec = BOOST_HTTP_ERR(
error::body_too_large);
state_ = state::reset;
break;
}
if(f_rs.finished)
{
if(!more)
state_ = state::complete;
break;
}
}
done:
return p0 - payload_avail;
}
};
//------------------------------------------------
//
// Special Members
//
//------------------------------------------------
parser::
~parser()
{
delete impl_;
}
parser::
parser() noexcept
: impl_(nullptr)
{
}
parser::
parser(parser&& other) noexcept
: impl_(other.impl_)
{
other.impl_ = nullptr;
}
parser::
parser(
std::shared_ptr cfg,
detail::kind k)
: impl_(new impl(std::move(cfg), k))
{
// TODO: use a single allocation for
// impl and workspace buffer.
}
void
parser::
assign(parser&& other) noexcept
{
if(this == &other)
return;
delete impl_;
impl_ = other.impl_;
other.impl_ = nullptr;
}
//--------------------------------------------
//
// 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
parser::
pull_body() ->
const_buffers_type
{
BOOST_ASSERT(impl_);
return impl_->pull_body();
}
void
parser::
consume_body(std::size_t n)
{
BOOST_ASSERT(impl_);
impl_->consume_body(n);
}
core::string_view
parser::
body() const
{
BOOST_ASSERT(impl_);
return impl_->body();
}
core::string_view
parser::
release_buffered_data() noexcept
{
// TODO
return {};
}
void
parser::
set_body_limit(std::uint64_t n)
{
BOOST_ASSERT(impl_);
impl_->set_body_limit(n);
}
//------------------------------------------------
//
// Implementation
//
//------------------------------------------------
void
parser::
start_impl(bool head_response)
{
BOOST_ASSERT(impl_);
impl_->start(head_response);
}
static_request const&
parser::
safe_get_request() const
{
BOOST_ASSERT(impl_);
return impl_->safe_get_request();
}
static_response const&
parser::
safe_get_response() const
{
BOOST_ASSERT(impl_);
return impl_->safe_get_response();
}
} // http
} // boost