forked from boostorg/beast
Refactor chunked parsing (API Change):
* parser now has a callback feature for intercepting chunk headers and chunk bodies * The names for basic_parser derived class callbacks have been refactored * basic_parser offers an additional callback for distinguishing chunk body data. Actions Required: * Adjust signatures for required members of user-defined subclasses of basic_parser * Use the new basic_parser chunk callbacks for accessing chunk extensions and chunk bodies.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// Copyright (c) 2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_DETAIL_VARINT_HPP
|
||||
#define BEAST_DETAIL_VARINT_HPP
|
||||
|
||||
#include <boost/static_assert.hpp>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
|
||||
namespace beast {
|
||||
namespace detail {
|
||||
|
||||
// https://developers.google.com/protocol-buffers/docs/encoding#varints
|
||||
|
||||
inline
|
||||
std::size_t
|
||||
varint_size(std::size_t value)
|
||||
{
|
||||
std::size_t n = 1;
|
||||
while(value > 127)
|
||||
{
|
||||
++n;
|
||||
value /= 128;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
template<class FwdIt>
|
||||
std::size_t
|
||||
varint_read(FwdIt& first)
|
||||
{
|
||||
using value_type = typename
|
||||
std::iterator_traits<FwdIt>::value_type;
|
||||
BOOST_STATIC_ASSERT(
|
||||
std::is_integral<value_type>::value &&
|
||||
sizeof(value_type) == 1);
|
||||
std::size_t value = 0;
|
||||
std::size_t factor = 1;
|
||||
while((*first & 0x80) != 0)
|
||||
{
|
||||
value += (*first++ & 0x7f) * factor;
|
||||
factor *= 128;
|
||||
}
|
||||
value += *first++ * factor;
|
||||
return value;
|
||||
}
|
||||
|
||||
template<class FwdIt>
|
||||
void
|
||||
varint_write(FwdIt& first, std::size_t value)
|
||||
{
|
||||
using value_type = typename
|
||||
std::iterator_traits<FwdIt>::value_type;
|
||||
BOOST_STATIC_ASSERT(
|
||||
std::is_integral<value_type>::value &&
|
||||
sizeof(value_type) == 1);
|
||||
while(value > 127)
|
||||
{
|
||||
*first++ = static_cast<value_type>(
|
||||
0x80 | value);
|
||||
value /= 128;
|
||||
}
|
||||
*first++ = static_cast<value_type>(value);
|
||||
}
|
||||
|
||||
} // detail
|
||||
} // beast
|
||||
|
||||
#endif
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <boost/assert.hpp>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace beast {
|
||||
@@ -136,16 +137,16 @@ public:
|
||||
using is_request =
|
||||
std::integral_constant<bool, isRequest>;
|
||||
|
||||
/// Copy constructor (disallowed)
|
||||
/// Destructor
|
||||
~basic_parser();
|
||||
|
||||
/// Constructor
|
||||
basic_parser(basic_parser const&) = delete;
|
||||
|
||||
/// Copy assignment (disallowed)
|
||||
/// Constructor
|
||||
basic_parser& operator=(basic_parser const&) = delete;
|
||||
|
||||
/// Destructor
|
||||
~basic_parser() = default;
|
||||
|
||||
/// Default constructor
|
||||
/// Constructor
|
||||
basic_parser();
|
||||
|
||||
/** Move constructor
|
||||
|
||||
@@ -558,8 +558,9 @@ public:
|
||||
|
||||
/** A set of chunk extensions
|
||||
|
||||
This container stores a set of chunk extensions suited
|
||||
for use with @ref chunk_header and @ref chunk_body.
|
||||
This container stores a set of chunk extensions suited for use with
|
||||
@ref chunk_header and @ref chunk_body. The container may be iterated
|
||||
to access the extensions in their structured form.
|
||||
|
||||
Meets the requirements of ChunkExtensions
|
||||
*/
|
||||
@@ -569,7 +570,27 @@ class basic_chunk_extensions
|
||||
std::basic_string<char,
|
||||
std::char_traits<char>, Allocator> s_;
|
||||
|
||||
std::basic_string<char,
|
||||
std::char_traits<char>, Allocator> range_;
|
||||
|
||||
template<class FwdIt>
|
||||
FwdIt
|
||||
do_parse(FwdIt it, FwdIt last, error_code& ec);
|
||||
|
||||
void
|
||||
do_insert(string_view name, string_view value);
|
||||
|
||||
public:
|
||||
/** The type of value when iterating.
|
||||
|
||||
The first element of the pair is the name, and the second
|
||||
element is the value which may be empty. The value is
|
||||
stored in its raw representation, without quotes or escapes.
|
||||
*/
|
||||
using value_type = std::pair<string_view, string_view>;
|
||||
|
||||
class const_iterator;
|
||||
|
||||
/// Constructor
|
||||
basic_chunk_extensions() = default;
|
||||
|
||||
@@ -600,6 +621,13 @@ public:
|
||||
s_.clear();
|
||||
}
|
||||
|
||||
/** Parse a set of chunk extensions
|
||||
|
||||
Any previous extensions will be cleared
|
||||
*/
|
||||
void
|
||||
parse(string_view s, error_code& ec);
|
||||
|
||||
/** Insert an extension name with an empty value
|
||||
|
||||
@param name The name of the extension
|
||||
@@ -623,6 +651,12 @@ public:
|
||||
{
|
||||
return s_;
|
||||
}
|
||||
|
||||
const_iterator
|
||||
begin() const;
|
||||
|
||||
const_iterator
|
||||
end() const;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -158,6 +158,7 @@ protected:
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
static
|
||||
std::pair<char const*, bool>
|
||||
find_fast(
|
||||
char const* buf,
|
||||
@@ -205,7 +206,6 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
// VFALCO Can SIMD help this?
|
||||
static
|
||||
char const*
|
||||
find_eom(char const* p, char const* last)
|
||||
@@ -242,6 +242,7 @@ protected:
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
static
|
||||
char const*
|
||||
parse_token_to_eol(
|
||||
char const* p,
|
||||
@@ -317,6 +318,7 @@ protected:
|
||||
}
|
||||
|
||||
template<class Iter, class Unsigned>
|
||||
static
|
||||
bool
|
||||
parse_hex(Iter& it, Unsigned& v)
|
||||
{
|
||||
@@ -361,7 +363,7 @@ protected:
|
||||
ec = error::need_more;
|
||||
return;
|
||||
}
|
||||
if(! detail::is_tchar(*it))
|
||||
if(! detail::is_token_char(*it))
|
||||
break;
|
||||
}
|
||||
if(it + 1 > last)
|
||||
@@ -709,6 +711,178 @@ protected:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
parse_chunk_extensions(
|
||||
char const*& it,
|
||||
char const* last,
|
||||
error_code& ec)
|
||||
{
|
||||
/*
|
||||
chunk-ext = *( BWS ";" BWS chunk-ext-name [ BWS "=" BWS chunk-ext-val ] )
|
||||
BWS = *( SP / HTAB ) ; "Bad White Space"
|
||||
chunk-ext-name = token
|
||||
chunk-ext-val = token / quoted-string
|
||||
token = 1*tchar
|
||||
quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
|
||||
qdtext = HTAB / SP / "!" / %x23-5B ; '#'-'[' / %x5D-7E ; ']'-'~' / obs-text
|
||||
quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
|
||||
obs-text = %x80-FF
|
||||
|
||||
https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4667
|
||||
*/
|
||||
loop:
|
||||
if(it == last)
|
||||
{
|
||||
ec = error::need_more;
|
||||
return;
|
||||
}
|
||||
if(*it != ' ' && *it != '\t' && *it != ';')
|
||||
return;
|
||||
// BWS
|
||||
if(*it == ' ' || *it == '\t')
|
||||
{
|
||||
for(;;)
|
||||
{
|
||||
++it;
|
||||
if(it == last)
|
||||
{
|
||||
ec = error::need_more;
|
||||
return;
|
||||
}
|
||||
if(*it != ' ' && *it != '\t')
|
||||
break;
|
||||
}
|
||||
}
|
||||
// ';'
|
||||
if(*it != ';')
|
||||
{
|
||||
ec = error::bad_chunk_extension;
|
||||
return;
|
||||
}
|
||||
semi:
|
||||
++it; // skip ';'
|
||||
// BWS
|
||||
for(;;)
|
||||
{
|
||||
if(it == last)
|
||||
{
|
||||
ec = error::need_more;
|
||||
return;
|
||||
}
|
||||
if(*it != ' ' && *it != '\t')
|
||||
break;
|
||||
++it;
|
||||
}
|
||||
// chunk-ext-name
|
||||
if(! detail::is_token_char(*it))
|
||||
{
|
||||
ec = error::bad_chunk_extension;
|
||||
return;
|
||||
}
|
||||
for(;;)
|
||||
{
|
||||
++it;
|
||||
if(it == last)
|
||||
{
|
||||
ec = error::need_more;
|
||||
return;
|
||||
}
|
||||
if(! detail::is_token_char(*it))
|
||||
break;
|
||||
}
|
||||
// BWS [ ";" / "=" ]
|
||||
{
|
||||
bool bws;
|
||||
if(*it == ' ' || *it == '\t')
|
||||
{
|
||||
for(;;)
|
||||
{
|
||||
++it;
|
||||
if(it == last)
|
||||
{
|
||||
ec = error::need_more;
|
||||
return;
|
||||
}
|
||||
if(*it != ' ' && *it != '\t')
|
||||
break;
|
||||
}
|
||||
bws = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bws = false;
|
||||
}
|
||||
if(*it == ';')
|
||||
goto semi;
|
||||
if(*it != '=')
|
||||
{
|
||||
if(bws)
|
||||
ec = error::bad_chunk_extension;
|
||||
return;
|
||||
}
|
||||
++it; // skip '='
|
||||
}
|
||||
// BWS
|
||||
for(;;)
|
||||
{
|
||||
if(it == last)
|
||||
{
|
||||
ec = error::need_more;
|
||||
return;
|
||||
}
|
||||
if(*it != ' ' && *it != '\t')
|
||||
break;
|
||||
++it;
|
||||
}
|
||||
// chunk-ext-val
|
||||
if(*it != '"')
|
||||
{
|
||||
// token
|
||||
if(! detail::is_token_char(*it))
|
||||
{
|
||||
ec = error::bad_chunk_extension;
|
||||
return;
|
||||
}
|
||||
for(;;)
|
||||
{
|
||||
++it;
|
||||
if(it == last)
|
||||
{
|
||||
ec = error::need_more;
|
||||
return;
|
||||
}
|
||||
if(! detail::is_token_char(*it))
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// quoted-string
|
||||
for(;;)
|
||||
{
|
||||
++it;
|
||||
if(it == last)
|
||||
{
|
||||
ec = error::need_more;
|
||||
return;
|
||||
}
|
||||
if(*it == '"')
|
||||
break;
|
||||
if(*it == '\\')
|
||||
{
|
||||
++it;
|
||||
if(it == last)
|
||||
{
|
||||
ec = error::need_more;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
++it;
|
||||
}
|
||||
goto loop;
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
@@ -78,7 +78,7 @@ is_text(char c)
|
||||
|
||||
inline
|
||||
char
|
||||
is_tchar(char c)
|
||||
is_token_char(char c)
|
||||
{
|
||||
/*
|
||||
tchar = "!" | "#" | "$" | "%" | "&" |
|
||||
@@ -287,7 +287,7 @@ template<class FwdIt>
|
||||
void
|
||||
skip_token(FwdIt& it, FwdIt const& last)
|
||||
{
|
||||
while(it != last && is_tchar(*it))
|
||||
while(it != last && is_token_char(*it))
|
||||
++it;
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@ increment()
|
||||
if(it == last)
|
||||
return err();
|
||||
// param
|
||||
if(! detail::is_tchar(*it))
|
||||
if(! detail::is_token_char(*it))
|
||||
return err();
|
||||
auto const p0 = it;
|
||||
skip_token(++it, last);
|
||||
@@ -406,7 +406,7 @@ increment()
|
||||
else
|
||||
{
|
||||
// token
|
||||
if(! detail::is_tchar(*it))
|
||||
if(! detail::is_token_char(*it))
|
||||
return err();
|
||||
auto const p2 = it;
|
||||
skip_token(++it, last);
|
||||
@@ -436,7 +436,7 @@ struct opt_token_list_policy
|
||||
return true;
|
||||
}
|
||||
auto const c = *it;
|
||||
if(detail::is_tchar(c))
|
||||
if(detail::is_token_char(c))
|
||||
{
|
||||
if(need_comma)
|
||||
return false;
|
||||
@@ -446,7 +446,7 @@ struct opt_token_list_policy
|
||||
++it;
|
||||
if(it == s.end())
|
||||
break;
|
||||
if(! detail::is_tchar(*it))
|
||||
if(! detail::is_token_char(*it))
|
||||
break;
|
||||
}
|
||||
v = string_view{&*p0,
|
||||
|
||||
@@ -72,6 +72,10 @@ enum class error
|
||||
*/
|
||||
need_buffer,
|
||||
|
||||
/** The end of a chunk was reached
|
||||
*/
|
||||
end_of_chunk,
|
||||
|
||||
/** Buffer maximum exceeded.
|
||||
|
||||
This error is returned when reading HTTP content
|
||||
@@ -138,6 +142,9 @@ enum class error
|
||||
/// The chunk syntax is invalid.
|
||||
bad_chunk,
|
||||
|
||||
/// The chunk extension is invalid.
|
||||
bad_chunk_extension,
|
||||
|
||||
/// An obs-fold exceeded an internal limit.
|
||||
bad_obs_fold
|
||||
};
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
template<bool isRequest, class Derived>
|
||||
basic_parser<isRequest, Derived>::
|
||||
~basic_parser()
|
||||
{
|
||||
}
|
||||
|
||||
template<bool isRequest, class Derived>
|
||||
basic_parser<isRequest, Derived>::
|
||||
basic_parser()
|
||||
@@ -215,7 +221,7 @@ loop:
|
||||
|
||||
case state::body0:
|
||||
BOOST_ASSERT(! skip_);
|
||||
impl().on_body(content_length(), ec);
|
||||
impl().on_body_init_impl(content_length(), ec);
|
||||
if(ec)
|
||||
goto done;
|
||||
state_ = state::body;
|
||||
@@ -230,7 +236,7 @@ loop:
|
||||
|
||||
case state::body_to_eof0:
|
||||
BOOST_ASSERT(! skip_);
|
||||
impl().on_body(content_length(), ec);
|
||||
impl().on_body_init_impl(content_length(), ec);
|
||||
if(ec)
|
||||
goto done;
|
||||
state_ = state::body_to_eof;
|
||||
@@ -244,7 +250,7 @@ loop:
|
||||
break;
|
||||
|
||||
case state::chunk_header0:
|
||||
impl().on_body(content_length(), ec);
|
||||
impl().on_body_init_impl(content_length(), ec);
|
||||
if(ec)
|
||||
goto done;
|
||||
state_ = state::chunk_header;
|
||||
@@ -297,7 +303,7 @@ put_eof(error_code& ec)
|
||||
ec.assign(0, ec.category());
|
||||
return;
|
||||
}
|
||||
impl().on_complete(ec);
|
||||
impl().on_finish_impl(ec);
|
||||
if(ec)
|
||||
return;
|
||||
state_ = state::complete;
|
||||
@@ -401,7 +407,7 @@ parse_start_line(
|
||||
if(version >= 11)
|
||||
f_ |= flagHTTP11;
|
||||
|
||||
impl().on_request(string_to_verb(method),
|
||||
impl().on_request_impl(string_to_verb(method),
|
||||
method, target, version, ec);
|
||||
if(ec)
|
||||
return;
|
||||
@@ -460,7 +466,7 @@ parse_start_line(
|
||||
if(version >= 11)
|
||||
f_ |= flagHTTP11;
|
||||
|
||||
impl().on_response(
|
||||
impl().on_response_impl(
|
||||
status_, reason, version, ec);
|
||||
if(ec)
|
||||
return;
|
||||
@@ -501,7 +507,7 @@ parse_fields(char const*& in,
|
||||
do_field(f, value, ec);
|
||||
if(ec)
|
||||
return;
|
||||
impl().on_field(f, name, value, ec);
|
||||
impl().on_field_impl(f, name, value, ec);
|
||||
if(ec)
|
||||
return;
|
||||
in = p;
|
||||
@@ -544,12 +550,12 @@ finish_header(error_code& ec, std::true_type)
|
||||
state_ = state::complete;
|
||||
}
|
||||
|
||||
impl().on_header(ec);
|
||||
impl().on_header_impl(ec);
|
||||
if(ec)
|
||||
return;
|
||||
if(state_ == state::complete)
|
||||
{
|
||||
impl().on_complete(ec);
|
||||
impl().on_finish_impl(ec);
|
||||
if(ec)
|
||||
return;
|
||||
}
|
||||
@@ -597,12 +603,12 @@ finish_header(error_code& ec, std::false_type)
|
||||
state_ = state::body_to_eof0;
|
||||
}
|
||||
|
||||
impl().on_header(ec);
|
||||
impl().on_header_impl(ec);
|
||||
if(ec)
|
||||
return;
|
||||
if(state_ == state::complete)
|
||||
{
|
||||
impl().on_complete(ec);
|
||||
impl().on_finish_impl(ec);
|
||||
if(ec)
|
||||
return;
|
||||
}
|
||||
@@ -615,7 +621,7 @@ basic_parser<isRequest, Derived>::
|
||||
parse_body(char const*& p,
|
||||
std::size_t n, error_code& ec)
|
||||
{
|
||||
n = impl().on_data(string_view{p,
|
||||
n = impl().on_body_impl(string_view{p,
|
||||
beast::detail::clamp(len_, n)}, ec);
|
||||
p += n;
|
||||
len_ -= n;
|
||||
@@ -623,7 +629,7 @@ parse_body(char const*& p,
|
||||
return;
|
||||
if(len_ > 0)
|
||||
return;
|
||||
impl().on_complete(ec);
|
||||
impl().on_finish_impl(ec);
|
||||
if(ec)
|
||||
return;
|
||||
state_ = state::complete;
|
||||
@@ -642,7 +648,7 @@ parse_body_to_eof(char const*& p,
|
||||
return;
|
||||
}
|
||||
body_limit_ = body_limit_ - n;
|
||||
n = impl().on_data(string_view{p, n}, ec);
|
||||
n = impl().on_body_impl(string_view{p, n}, ec);
|
||||
p += n;
|
||||
if(ec)
|
||||
return;
|
||||
@@ -702,40 +708,34 @@ parse_chunk_header(char const*& p0,
|
||||
skip_ = static_cast<
|
||||
std::size_t>(eol - 2 - p0);
|
||||
|
||||
std::uint64_t v;
|
||||
if(! parse_hex(p, v))
|
||||
std::uint64_t size;
|
||||
if(! parse_hex(p, size))
|
||||
{
|
||||
ec = error::bad_chunk;
|
||||
return;
|
||||
}
|
||||
if(v != 0)
|
||||
if(size != 0)
|
||||
{
|
||||
if(v > body_limit_)
|
||||
if(size > body_limit_)
|
||||
{
|
||||
ec = error::body_limit;
|
||||
return;
|
||||
}
|
||||
body_limit_ -= v;
|
||||
if(*p == ';')
|
||||
body_limit_ -= size;
|
||||
auto const start = p;
|
||||
parse_chunk_extensions(p, pend, ec);
|
||||
if(ec)
|
||||
return;
|
||||
if(p != eol -2 )
|
||||
{
|
||||
// VFALCO TODO Validate extension
|
||||
impl().on_chunk(v, make_string(
|
||||
p, eol - 2), ec);
|
||||
if(ec)
|
||||
return;
|
||||
}
|
||||
else if(p == eol - 2)
|
||||
{
|
||||
impl().on_chunk(v, {}, ec);
|
||||
if(ec)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
ec = error::bad_chunk;
|
||||
ec = error::bad_chunk_extension;
|
||||
return;
|
||||
}
|
||||
len_ = v;
|
||||
auto const ext = make_string(start, p);
|
||||
impl().on_chunk_header_impl(size, ext, ec);
|
||||
if(ec)
|
||||
return;
|
||||
len_ = size;
|
||||
skip_ = 2;
|
||||
p0 = eol;
|
||||
f_ |= flagExpectCRLF;
|
||||
@@ -750,8 +750,8 @@ parse_chunk_header(char const*& p0,
|
||||
BOOST_ASSERT(n >= 5);
|
||||
if(f_ & flagExpectCRLF)
|
||||
BOOST_VERIFY(parse_crlf(p));
|
||||
std::uint64_t v;
|
||||
BOOST_VERIFY(parse_hex(p, v));
|
||||
std::uint64_t size;
|
||||
BOOST_VERIFY(parse_hex(p, size));
|
||||
eol = find_eol(p, pend, ec);
|
||||
BOOST_ASSERT(! ec);
|
||||
}
|
||||
@@ -765,14 +765,19 @@ parse_chunk_header(char const*& p0,
|
||||
return;
|
||||
}
|
||||
|
||||
if(*p == ';')
|
||||
auto const start = p;
|
||||
parse_chunk_extensions(p, pend, ec);
|
||||
if(ec)
|
||||
return;
|
||||
if(p != eol - 2)
|
||||
{
|
||||
// VFALCO TODO Validate extension
|
||||
impl().on_chunk(0, make_string(
|
||||
p, eol - 2), ec);
|
||||
if(ec)
|
||||
return;
|
||||
ec = error::bad_chunk_extension;
|
||||
return;
|
||||
}
|
||||
auto const ext = make_string(start, p);
|
||||
impl().on_chunk_header_impl(0, ext, ec);
|
||||
if(ec)
|
||||
return;
|
||||
p = eol;
|
||||
parse_fields(p, eom, ec);
|
||||
if(ec)
|
||||
@@ -780,7 +785,7 @@ parse_chunk_header(char const*& p0,
|
||||
BOOST_ASSERT(p == eom);
|
||||
p0 = eom;
|
||||
|
||||
impl().on_complete(ec);
|
||||
impl().on_finish_impl(ec);
|
||||
if(ec)
|
||||
return;
|
||||
state_ = state::complete;
|
||||
@@ -793,15 +798,13 @@ basic_parser<isRequest, Derived>::
|
||||
parse_chunk_body(char const*& p,
|
||||
std::size_t n, error_code& ec)
|
||||
{
|
||||
n = impl().on_data(string_view{p,
|
||||
beast::detail::clamp(len_, n)}, ec);
|
||||
n = impl().on_chunk_body_impl(
|
||||
len_, string_view{p,
|
||||
beast::detail::clamp(len_, n)}, ec);
|
||||
p += n;
|
||||
len_ -= n;
|
||||
if(ec)
|
||||
return;
|
||||
if(len_ > 0)
|
||||
return;
|
||||
state_ = state::chunk_header;
|
||||
if(len_ == 0)
|
||||
state_ = state::chunk_header;
|
||||
}
|
||||
|
||||
template<bool isRequest, class Derived>
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
#ifndef BEAST_HTTP_IMPL_CHUNK_ENCODE_IPP
|
||||
#define BEAST_HTTP_IMPL_CHUNK_ENCODE_IPP
|
||||
|
||||
#include <beast/core/detail/varint.hpp>
|
||||
#include <beast/http/error.hpp>
|
||||
#include <beast/http/detail/rfc7230.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
@@ -210,23 +213,350 @@ chunk_last(
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class Allocator>
|
||||
class basic_chunk_extensions<Allocator>::const_iterator
|
||||
{
|
||||
friend class basic_chunk_extensions;
|
||||
|
||||
using iter_type = char const*;
|
||||
|
||||
iter_type it_;
|
||||
typename basic_chunk_extensions::value_type value_;
|
||||
|
||||
explicit
|
||||
const_iterator(iter_type it)
|
||||
: it_(it)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
increment();
|
||||
|
||||
public:
|
||||
using value_type = typename
|
||||
basic_chunk_extensions::value_type;
|
||||
using pointer = value_type const*;
|
||||
using reference = value_type const&;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using iterator_category =
|
||||
std::forward_iterator_tag;
|
||||
|
||||
const_iterator() = default;
|
||||
const_iterator(const_iterator&& other) = default;
|
||||
const_iterator(const_iterator const& other) = default;
|
||||
const_iterator& operator=(const_iterator&& other) = default;
|
||||
const_iterator& operator=(const_iterator const& other) = default;
|
||||
|
||||
bool
|
||||
operator==(const_iterator const& other) const
|
||||
{
|
||||
return it_ == other.it_;
|
||||
}
|
||||
|
||||
bool
|
||||
operator!=(const_iterator const& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
reference
|
||||
operator*();
|
||||
|
||||
pointer
|
||||
operator->()
|
||||
{
|
||||
return &(*this);
|
||||
}
|
||||
|
||||
const_iterator&
|
||||
operator++()
|
||||
{
|
||||
increment();
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_iterator
|
||||
operator++(int)
|
||||
{
|
||||
auto temp = *this;
|
||||
increment();
|
||||
return temp;
|
||||
}
|
||||
};
|
||||
|
||||
template<class Allocator>
|
||||
void
|
||||
basic_chunk_extensions<Allocator>::
|
||||
insert(string_view name)
|
||||
const_iterator::
|
||||
increment()
|
||||
{
|
||||
using beast::detail::varint_read;
|
||||
auto n = varint_read(it_);
|
||||
it_ += n;
|
||||
n = varint_read(it_);
|
||||
it_ += n;
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
auto
|
||||
basic_chunk_extensions<Allocator>::
|
||||
const_iterator::
|
||||
operator*() ->
|
||||
reference
|
||||
{
|
||||
using beast::detail::varint_read;
|
||||
auto it = it_;
|
||||
auto n = varint_read(it);
|
||||
value_.first = string_view{it, n};
|
||||
it += n;
|
||||
n = varint_read(it);
|
||||
value_.second = string_view{it, n};
|
||||
return value_;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class Allocator>
|
||||
template<class FwdIt>
|
||||
FwdIt
|
||||
basic_chunk_extensions<Allocator>::
|
||||
do_parse(FwdIt it, FwdIt last, error_code& ec)
|
||||
{
|
||||
/*
|
||||
chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
|
||||
chunk-ext = *( BWS ";" BWS chunk-ext-name [ BWS "=" BWS chunk-ext-val ] )
|
||||
BWS = *( SP / HTAB ) ; "Bad White Space"
|
||||
chunk-ext-name = token
|
||||
chunk-ext-val = token / quoted-string
|
||||
token = 1*tchar
|
||||
quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
|
||||
qdtext = HTAB / SP / "!" / %x23-5B ; '#'-'[' / %x5D-7E ; ']'-'~' / obs-text
|
||||
quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
|
||||
obs-text = %x80-FF
|
||||
|
||||
https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4667
|
||||
*/
|
||||
s_.reserve(1 + name.size());
|
||||
s_.push_back(';');
|
||||
s_.append(name.data(), name.size());
|
||||
using beast::detail::varint_size;
|
||||
using beast::detail::varint_write;
|
||||
using CharT = char;
|
||||
using Traits = std::char_traits<CharT>;
|
||||
range_.reserve(static_cast<std::size_t>(
|
||||
std::distance(it, last) * 1.2));
|
||||
range_.resize(0);
|
||||
auto const emit_string =
|
||||
[this](FwdIt from, FwdIt to)
|
||||
{
|
||||
auto const len =
|
||||
std::distance(from, to);
|
||||
auto const offset = range_.size();
|
||||
range_.resize(
|
||||
offset +
|
||||
varint_size(len) +
|
||||
len);
|
||||
auto dest = &range_[offset];
|
||||
varint_write(dest, len);
|
||||
Traits::copy(dest, from, len);
|
||||
};
|
||||
auto const emit_string_plus_empty =
|
||||
[this](FwdIt from, FwdIt to)
|
||||
{
|
||||
auto const len =
|
||||
std::distance(from, to);
|
||||
auto const offset = range_.size();
|
||||
range_.resize(
|
||||
offset +
|
||||
varint_size(len) +
|
||||
len +
|
||||
varint_size(0));
|
||||
auto dest = &range_[offset];
|
||||
varint_write(dest, len);
|
||||
Traits::copy(dest, from, len);
|
||||
dest += len;
|
||||
varint_write(dest, 0);
|
||||
};
|
||||
auto const emit_empty_string =
|
||||
[this]
|
||||
{
|
||||
auto const offset = range_.size();
|
||||
range_.resize(offset + varint_size(0));
|
||||
auto dest = &range_[offset];
|
||||
varint_write(dest, 0);
|
||||
};
|
||||
loop:
|
||||
if(it == last)
|
||||
{
|
||||
ec.assign(0, ec.category());
|
||||
return it;
|
||||
}
|
||||
// BWS
|
||||
if(*it == ' ' || *it == '\t')
|
||||
{
|
||||
for(;;)
|
||||
{
|
||||
++it;
|
||||
if(it == last)
|
||||
{
|
||||
ec = error::bad_chunk_extension;
|
||||
return it;
|
||||
}
|
||||
if(*it != ' ' && *it != '\t')
|
||||
break;
|
||||
}
|
||||
}
|
||||
// ';'
|
||||
if(*it != ';')
|
||||
{
|
||||
ec = error::bad_chunk_extension;
|
||||
return it;
|
||||
}
|
||||
semi:
|
||||
++it; // skip ';'
|
||||
// BWS
|
||||
for(;;)
|
||||
{
|
||||
if(it == last)
|
||||
{
|
||||
ec = error::bad_chunk_extension;
|
||||
return it;
|
||||
}
|
||||
if(*it != ' ' && *it != '\t')
|
||||
break;
|
||||
++it;
|
||||
}
|
||||
// chunk-ext-name
|
||||
{
|
||||
if(! detail::is_token_char(*it))
|
||||
{
|
||||
ec = error::bad_chunk_extension;
|
||||
return it;
|
||||
}
|
||||
auto const first = it;
|
||||
for(;;)
|
||||
{
|
||||
++it;
|
||||
if(it == last)
|
||||
{
|
||||
emit_string_plus_empty(first, it);
|
||||
return it;
|
||||
}
|
||||
if(! detail::is_token_char(*it))
|
||||
break;
|
||||
}
|
||||
emit_string(first, it);
|
||||
}
|
||||
// BWS [ ";" / "=" ]
|
||||
for(;;)
|
||||
{
|
||||
if(*it != ' ' && *it != '\t')
|
||||
break;
|
||||
++it;
|
||||
if(it == last)
|
||||
{
|
||||
ec = error::bad_chunk_extension;
|
||||
return it;
|
||||
}
|
||||
}
|
||||
if(*it == ';')
|
||||
{
|
||||
emit_empty_string();
|
||||
goto semi;
|
||||
}
|
||||
if(*it != '=')
|
||||
{
|
||||
ec = error::bad_chunk_extension;
|
||||
return it;
|
||||
}
|
||||
++it; // skip '='
|
||||
// BWS
|
||||
for(;;)
|
||||
{
|
||||
if(it == last)
|
||||
{
|
||||
ec = error::bad_chunk_extension;
|
||||
return it;
|
||||
}
|
||||
if(*it != ' ' && *it != '\t')
|
||||
break;
|
||||
++it;
|
||||
}
|
||||
// chunk-ext-val
|
||||
if(*it != '"')
|
||||
{
|
||||
// token
|
||||
if(! detail::is_token_char(*it))
|
||||
{
|
||||
ec = error::bad_chunk_extension;
|
||||
return it;
|
||||
}
|
||||
auto const first = it;
|
||||
for(;;)
|
||||
{
|
||||
++it;
|
||||
if(it == last)
|
||||
break;
|
||||
if(! detail::is_token_char(*it))
|
||||
break;
|
||||
}
|
||||
emit_string(first, it);
|
||||
if(it == last)
|
||||
return it;
|
||||
}
|
||||
else
|
||||
{
|
||||
// quoted-string
|
||||
auto const first = ++it; // skip DQUOTE
|
||||
// first pass, count chars
|
||||
std::size_t len = 0;
|
||||
for(;;)
|
||||
{
|
||||
if(it == last)
|
||||
{
|
||||
ec = error::bad_chunk_extension;
|
||||
return it;
|
||||
}
|
||||
if(*it == '"')
|
||||
break;
|
||||
if(*it == '\\')
|
||||
{
|
||||
++it;
|
||||
if(it == last)
|
||||
{
|
||||
ec = error::bad_chunk_extension;
|
||||
return it;
|
||||
}
|
||||
}
|
||||
++len;
|
||||
++it;
|
||||
}
|
||||
// now build the string
|
||||
auto const offset = range_.size();
|
||||
range_.resize(
|
||||
offset +
|
||||
varint_size(len) +
|
||||
len);
|
||||
auto dest = &range_[offset];
|
||||
varint_write(dest, len);
|
||||
it = first;
|
||||
for(;;)
|
||||
{
|
||||
BOOST_ASSERT(it != last);
|
||||
if(*it == '"')
|
||||
break;
|
||||
if(*it == '\\')
|
||||
{
|
||||
++it;
|
||||
BOOST_ASSERT(it != last);
|
||||
}
|
||||
Traits::assign(*dest++, *it++);
|
||||
}
|
||||
++it; // skip DQUOTE
|
||||
}
|
||||
goto loop;
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
void
|
||||
basic_chunk_extensions<Allocator>::
|
||||
insert(string_view name, string_view value)
|
||||
do_insert(string_view name, string_view value)
|
||||
{
|
||||
/*
|
||||
chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
|
||||
@@ -238,10 +568,18 @@ insert(string_view name, string_view value)
|
||||
quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
|
||||
obs-text = %x80-FF
|
||||
*/
|
||||
if(value.empty())
|
||||
{
|
||||
s_.reserve(1 + name.size());
|
||||
s_.push_back(';');
|
||||
s_.append(name.data(), name.size());
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_token = true;
|
||||
for(auto const c : value)
|
||||
{
|
||||
if(! detail::is_tchar(c))
|
||||
if(! detail::is_token_char(c))
|
||||
{
|
||||
is_token = false;
|
||||
break;
|
||||
@@ -249,32 +587,114 @@ insert(string_view name, string_view value)
|
||||
}
|
||||
if(is_token)
|
||||
{
|
||||
// token
|
||||
s_.reserve(1 + name.size() + 1 + value.size());
|
||||
s_.push_back(';');
|
||||
s_.append(name.data(), name.size());
|
||||
s_.push_back('=');
|
||||
s_.append(value.data(), value.size());
|
||||
return;
|
||||
}
|
||||
|
||||
// quoted-string
|
||||
|
||||
s_.reserve(
|
||||
1 + name.size() + 1 +
|
||||
1 + value.size() + 20 + 1);
|
||||
s_.push_back(';');
|
||||
s_.append(name.data(), name.size());
|
||||
s_.append("=\"", 2);
|
||||
for(auto const c : value)
|
||||
else
|
||||
{
|
||||
if(c == '\\')
|
||||
s_.append(R"(\\)", 2);
|
||||
else if(c == '\"')
|
||||
s_.append(R"(\")", 2);
|
||||
else
|
||||
s_.push_back(c);
|
||||
// quoted-string
|
||||
s_.reserve(
|
||||
1 + name.size() + 1 +
|
||||
1 + value.size() + 20 + 1);
|
||||
s_.push_back(';');
|
||||
s_.append(name.data(), name.size());
|
||||
s_.append("=\"", 2);
|
||||
for(auto const c : value)
|
||||
{
|
||||
if(c == '\\')
|
||||
s_.append(R"(\\)", 2);
|
||||
else if(c == '\"')
|
||||
s_.append(R"(\")", 2);
|
||||
else
|
||||
s_.push_back(c);
|
||||
}
|
||||
s_.push_back('"');
|
||||
}
|
||||
s_.push_back('"');
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
void
|
||||
basic_chunk_extensions<Allocator>::
|
||||
parse(string_view s, error_code& ec)
|
||||
{
|
||||
do_parse(s.data(), s.data() + s.size(), ec);
|
||||
if(! ec)
|
||||
{
|
||||
s_.clear();
|
||||
for(auto const& v : *this)
|
||||
do_insert(v.first, v.second);
|
||||
}
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
void
|
||||
basic_chunk_extensions<Allocator>::
|
||||
insert(string_view name)
|
||||
{
|
||||
do_insert(name, {});
|
||||
|
||||
using beast::detail::varint_size;
|
||||
using beast::detail::varint_write;
|
||||
auto const offset = range_.size();
|
||||
range_.resize(
|
||||
offset +
|
||||
varint_size(name.size()) +
|
||||
name.size() +
|
||||
varint_size(0));
|
||||
auto dest = &range_[offset];
|
||||
varint_write(dest, name.size());
|
||||
std::memcpy(dest, name.data(), name.size());
|
||||
dest += name.size();
|
||||
varint_write(dest, 0);
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
void
|
||||
basic_chunk_extensions<Allocator>::
|
||||
insert(string_view name, string_view value)
|
||||
{
|
||||
do_insert(name, value);
|
||||
|
||||
using beast::detail::varint_size;
|
||||
using beast::detail::varint_write;
|
||||
auto const offset = range_.size();
|
||||
range_.resize(
|
||||
offset +
|
||||
varint_size(name.size()) +
|
||||
name.size() +
|
||||
varint_size(value.size()) +
|
||||
value.size());
|
||||
auto dest = &range_[offset];
|
||||
varint_write(dest, name.size());
|
||||
std::memcpy(dest, name.data(), name.size());
|
||||
dest += name.size();
|
||||
varint_write(dest, value.size());
|
||||
std::memcpy(dest, value.data(), value.size());
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
inline
|
||||
auto
|
||||
basic_chunk_extensions<Allocator>::
|
||||
begin() const ->
|
||||
const_iterator
|
||||
{
|
||||
return const_iterator{range_.data()};
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
inline
|
||||
auto
|
||||
basic_chunk_extensions<Allocator>::
|
||||
end() const ->
|
||||
const_iterator
|
||||
{
|
||||
return const_iterator{
|
||||
range_.data() + range_.size()};
|
||||
}
|
||||
|
||||
} // http
|
||||
|
||||
@@ -43,6 +43,7 @@ public:
|
||||
case error::need_more: return "need more";
|
||||
case error::unexpected_body: return "unexpected body";
|
||||
case error::need_buffer: return "need buffer";
|
||||
case error::end_of_chunk: return "end of chunk";
|
||||
case error::buffer_overflow: return "buffer overflow";
|
||||
case error::header_limit: return "header limit exceeded";
|
||||
case error::body_limit: return "body limit exceeded";
|
||||
@@ -58,6 +59,7 @@ public:
|
||||
case error::bad_content_length: return "bad Content-Length";
|
||||
case error::bad_transfer_encoding: return "bad Transfer-Encoding";
|
||||
case error::bad_chunk: return "bad chunk";
|
||||
case error::bad_chunk_extension: return "bad chunk extension";
|
||||
case error::bad_obs_fold: return "bad obs-fold";
|
||||
|
||||
default:
|
||||
|
||||
@@ -14,6 +14,16 @@
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
template<bool isRequest, class Body, class Allocator>
|
||||
parser<isRequest, Body, Allocator>::
|
||||
~parser()
|
||||
{
|
||||
if(cb_h_)
|
||||
cb_h_->~cb_h_t();
|
||||
if(cb_b_)
|
||||
cb_b_->~cb_b_t();
|
||||
}
|
||||
|
||||
template<bool isRequest, class Body, class Allocator>
|
||||
parser<isRequest, Body, Allocator>::
|
||||
parser()
|
||||
@@ -21,6 +31,22 @@ parser()
|
||||
{
|
||||
}
|
||||
|
||||
template<bool isRequest, class Body, class Allocator>
|
||||
parser<isRequest, Body, Allocator>::
|
||||
parser(parser&& other)
|
||||
: base_type(std::move(other))
|
||||
, m_(other.m_)
|
||||
, wr_(other.wr_)
|
||||
, wr_inited_(other.wr_inited_)
|
||||
{
|
||||
if(other.cb_h_)
|
||||
cb_h_ = other.cb_h_->move(
|
||||
&cb_h_buf_);
|
||||
if(other.cb_b_)
|
||||
cb_b_ = other.cb_h_->move(
|
||||
&cb_b_buf_);
|
||||
}
|
||||
|
||||
template<bool isRequest, class Body, class Allocator>
|
||||
template<class Arg1, class... ArgN, class>
|
||||
parser<isRequest, Body, Allocator>::
|
||||
@@ -34,17 +60,53 @@ parser(Arg1&& arg1, ArgN&&... argn)
|
||||
template<bool isRequest, class Body, class Allocator>
|
||||
template<class OtherBody, class... Args, class>
|
||||
parser<isRequest, Body, Allocator>::
|
||||
parser(parser<isRequest, OtherBody, Allocator>&& p,
|
||||
parser(parser<isRequest, OtherBody, Allocator>&& other,
|
||||
Args&&... args)
|
||||
: base_type(std::move(p))
|
||||
, m_(p.release(), std::forward<Args>(args)...)
|
||||
: base_type(std::move(other))
|
||||
, m_(other.release(), std::forward<Args>(args)...)
|
||||
, wr_(m_)
|
||||
{
|
||||
if(wr_inited_)
|
||||
if(other.wr_inited_)
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument{
|
||||
"moved-from parser has a body"});
|
||||
}
|
||||
|
||||
template<bool isRequest, class Body, class Allocator>
|
||||
template<class Callback>
|
||||
void
|
||||
parser<isRequest, Body, Allocator>::
|
||||
on_chunk_header(Callback& cb)
|
||||
{
|
||||
// Callback may not be constant, caller is responsible for
|
||||
// managing the lifetime of the callback. Copies are not made.
|
||||
BOOST_STATIC_ASSERT(! std::is_const<Callback>::value);
|
||||
|
||||
// Can't set the callback after receiving any chunk data!
|
||||
BOOST_ASSERT(! wr_inited_);
|
||||
|
||||
if(cb_h_)
|
||||
cb_h_->~cb_h_t();
|
||||
cb_h_ = new(&cb_h_buf_) cb_h_t_impl<Callback>(cb);
|
||||
}
|
||||
|
||||
template<bool isRequest, class Body, class Allocator>
|
||||
template<class Callback>
|
||||
void
|
||||
parser<isRequest, Body, Allocator>::
|
||||
on_chunk_body(Callback& cb)
|
||||
{
|
||||
// Callback may not be constant, caller is responsible for
|
||||
// managing the lifetime of the callback. Copies are not made.
|
||||
BOOST_STATIC_ASSERT(! std::is_const<Callback>::value);
|
||||
|
||||
// Can't set the callback after receiving any chunk data!
|
||||
BOOST_ASSERT(! wr_inited_);
|
||||
|
||||
if(cb_b_)
|
||||
cb_b_->~cb_b_t();
|
||||
cb_b_ = new(&cb_b_buf_) cb_b_t_impl<Callback>(cb);
|
||||
}
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
|
||||
@@ -332,7 +332,7 @@ increment()
|
||||
if(it_ == last_)
|
||||
return err();
|
||||
auto const c = *it_;
|
||||
if(detail::is_tchar(c))
|
||||
if(detail::is_token_char(c))
|
||||
{
|
||||
if(need_comma)
|
||||
return err();
|
||||
@@ -342,7 +342,7 @@ increment()
|
||||
++it_;
|
||||
if(it_ == last_)
|
||||
break;
|
||||
if(! detail::is_tchar(*it_))
|
||||
if(! detail::is_token_char(*it_))
|
||||
break;
|
||||
}
|
||||
v_.first = string_view{&*p0,
|
||||
@@ -504,7 +504,7 @@ increment()
|
||||
if(it_ == last_)
|
||||
return err();
|
||||
auto const c = *it_;
|
||||
if(detail::is_tchar(c))
|
||||
if(detail::is_token_char(c))
|
||||
{
|
||||
if(need_comma)
|
||||
return err();
|
||||
@@ -514,7 +514,7 @@ increment()
|
||||
++it_;
|
||||
if(it_ == last_)
|
||||
break;
|
||||
if(! detail::is_tchar(*it_))
|
||||
if(! detail::is_token_char(*it_))
|
||||
break;
|
||||
}
|
||||
v_ = string_view{&*p0,
|
||||
|
||||
+217
-40
@@ -14,7 +14,6 @@
|
||||
#include <beast/http/type_traits.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/throw_exception.hpp>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
@@ -56,34 +55,134 @@ class parser
|
||||
template<bool, class, class>
|
||||
friend class parser;
|
||||
|
||||
struct cb_h_exemplar
|
||||
{
|
||||
void
|
||||
operator()(std::uint64_t, string_view, error_code&);
|
||||
};
|
||||
|
||||
struct cb_h_t
|
||||
{
|
||||
virtual ~cb_h_t() = default;
|
||||
virtual cb_h_t* move(void* dest) = 0;
|
||||
virtual void operator()(
|
||||
std::uint64_t size,
|
||||
string_view extensions,
|
||||
error_code& ec) = 0;
|
||||
};
|
||||
|
||||
template<class Callback>
|
||||
struct cb_h_t_impl : cb_h_t
|
||||
{
|
||||
Callback& cb_;
|
||||
|
||||
explicit
|
||||
cb_h_t_impl(Callback& cb)
|
||||
: cb_(cb)
|
||||
{
|
||||
}
|
||||
|
||||
cb_h_t*
|
||||
move(void* dest) override
|
||||
{
|
||||
new(dest) cb_h_t_impl<
|
||||
Callback>(std::move(*this));
|
||||
return this;
|
||||
}
|
||||
|
||||
void
|
||||
operator()(
|
||||
std::uint64_t size,
|
||||
string_view extensions,
|
||||
error_code& ec) override
|
||||
{
|
||||
cb_(size, extensions, ec);
|
||||
}
|
||||
};
|
||||
|
||||
struct cb_b_exemplar
|
||||
{
|
||||
std::size_t
|
||||
operator()(std::uint64_t, string_view, error_code&);
|
||||
};
|
||||
|
||||
struct cb_b_t
|
||||
{
|
||||
virtual ~cb_b_t() = default;
|
||||
virtual cb_b_t* move(void* dest) = 0;
|
||||
virtual std::size_t operator()(
|
||||
std::uint64_t remain,
|
||||
string_view body,
|
||||
error_code& ec) = 0;
|
||||
};
|
||||
|
||||
template<class Callback>
|
||||
struct cb_b_t_impl : cb_b_t
|
||||
{
|
||||
Callback& cb_;
|
||||
|
||||
explicit
|
||||
cb_b_t_impl(Callback& cb)
|
||||
: cb_(cb)
|
||||
{
|
||||
}
|
||||
|
||||
cb_b_t*
|
||||
move(void* dest) override
|
||||
{
|
||||
new(dest) cb_b_t_impl<
|
||||
Callback>(std::move(*this));
|
||||
return this;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
operator()(
|
||||
std::uint64_t remain,
|
||||
string_view body,
|
||||
error_code& ec) override
|
||||
{
|
||||
return cb_(remain, body, ec);
|
||||
}
|
||||
};
|
||||
|
||||
using base_type = basic_parser<isRequest,
|
||||
parser<isRequest, Body, Allocator>>;
|
||||
|
||||
message<isRequest, Body, basic_fields<Allocator>> m_;
|
||||
typename Body::writer wr_;
|
||||
std::function<void(parser&, error_code&)> cb_;
|
||||
bool wr_inited_ = false;
|
||||
|
||||
cb_h_t* cb_h_ = nullptr;
|
||||
typename std::aligned_storage<
|
||||
sizeof(cb_h_t_impl<cb_h_exemplar>)>::type cb_h_buf_;
|
||||
|
||||
cb_b_t* cb_b_ = nullptr;
|
||||
typename std::aligned_storage<
|
||||
sizeof(cb_b_t_impl<cb_b_exemplar>)>::type cb_b_buf_;
|
||||
|
||||
public:
|
||||
/// The type of message returned by the parser
|
||||
using value_type =
|
||||
message<isRequest, Body, basic_fields<Allocator>>;
|
||||
|
||||
/// Destructor
|
||||
~parser();
|
||||
|
||||
/// Constructor
|
||||
parser();
|
||||
|
||||
/// Copy constructor (disallowed)
|
||||
/// Constructor
|
||||
parser(parser const&) = delete;
|
||||
|
||||
/// Copy assignment (disallowed)
|
||||
/// Assignment
|
||||
parser& operator=(parser const&) = delete;
|
||||
|
||||
/** Move constructor.
|
||||
/** Constructor
|
||||
|
||||
After the move, the only valid operation
|
||||
on the moved-from object is destruction.
|
||||
*/
|
||||
parser(parser&& other) = default;
|
||||
parser(parser&& other);
|
||||
|
||||
/** Constructor
|
||||
|
||||
@@ -189,34 +288,92 @@ public:
|
||||
return std::move(m_);
|
||||
}
|
||||
|
||||
/** Set the on_header callback.
|
||||
/** Set a callback to be invoked on each chunk header.
|
||||
|
||||
When the callback is set, it is called after the parser
|
||||
receives a complete header. The function must be invocable with
|
||||
this signature:
|
||||
The callback will be invoked once for every chunk in the message
|
||||
payload, as well as once for the last chunk. The invocation
|
||||
happens after the chunk header is available but before any body
|
||||
octets have been parsed.
|
||||
|
||||
The extensions are provided in raw, validated form, use
|
||||
@ref chunk_extensions::parse to parse the extensions into a
|
||||
structured container for easier access.
|
||||
The implementation type-erases the callback without requiring
|
||||
a dynamic allocation. For this reason, the callback object is
|
||||
passed by a non-constant reference.
|
||||
|
||||
@par Example
|
||||
@code
|
||||
void callback(
|
||||
parser<isRequest, Body, Fields>& p, // `*this`
|
||||
error_code& ec) // Set to the error, if any
|
||||
auto callback =
|
||||
[](std::uint64_t size, string_view extensions, error_code& ec)
|
||||
{
|
||||
//...
|
||||
};
|
||||
parser.on_chunk_header(callback);
|
||||
@endcode
|
||||
The callback will ensure that `!ec` is `true` if there was
|
||||
no error or set to the appropriate error code if there was one.
|
||||
|
||||
The callback may not call @ref put or @ref put_eof, or
|
||||
else the behavior is undefined.
|
||||
@param cb The function to set, which must be invocable with
|
||||
this equivalent signature:
|
||||
@code
|
||||
void
|
||||
on_chunk_header(
|
||||
std::uint64_t size, // Size of the chunk, zero for the last chunk
|
||||
string_view extensions, // The chunk-extensions in raw form
|
||||
error_code& ec); // May be set by the callback to indicate an error
|
||||
@endcode
|
||||
*/
|
||||
template<class Callback>
|
||||
void
|
||||
on_header(std::function<void(parser&, error_code&)> cb)
|
||||
{
|
||||
cb_ = std::move(cb);
|
||||
}
|
||||
on_chunk_header(Callback& cb);
|
||||
|
||||
/** Set a callback to be invoked on chunk body data
|
||||
|
||||
The provided function object will be invoked one or more times
|
||||
to provide buffers corresponding to the chunk body for the current
|
||||
chunk. The callback receives the number of octets remaining in this
|
||||
chunk body including the octets in the buffer provided.
|
||||
|
||||
The callback must return the number of octets actually consumed.
|
||||
Any octets not consumed will be presented again in a subsequent
|
||||
invocation of the callback.
|
||||
The implementation type-erases the callback without requiring
|
||||
a dynamic allocation. For this reason, the callback object is
|
||||
passed by a non-constant reference.
|
||||
|
||||
@par Example
|
||||
@code
|
||||
auto callback =
|
||||
[](std::uint64_t remain, string_view body, error_code& ec)
|
||||
{
|
||||
//...
|
||||
};
|
||||
parser.on_chunk_body(callback);
|
||||
@endcode
|
||||
|
||||
@param cb The function to set, which must be invocable with
|
||||
this equivalent signature:
|
||||
@code
|
||||
std::size_t
|
||||
on_chunk_header(
|
||||
std::uint64_t remain, // Octets remaining in this chunk, includes `body`
|
||||
string_view body, // A buffer holding some or all of the remainder of the chunk body
|
||||
error_code& ec); // May be set by the callback to indicate an error
|
||||
@endcode
|
||||
*/
|
||||
template<class Callback>
|
||||
void
|
||||
on_chunk_body(Callback& cb);
|
||||
|
||||
private:
|
||||
friend class basic_parser<isRequest, parser>;
|
||||
|
||||
void
|
||||
on_request(verb method, string_view method_str,
|
||||
string_view target, int version, error_code& ec)
|
||||
on_request_impl(
|
||||
verb method,
|
||||
string_view method_str,
|
||||
string_view target,
|
||||
int version,
|
||||
error_code& ec)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -235,9 +392,11 @@ private:
|
||||
}
|
||||
|
||||
void
|
||||
on_response(int code,
|
||||
on_response_impl(
|
||||
int code,
|
||||
string_view reason,
|
||||
int version, error_code& ec)
|
||||
int version,
|
||||
error_code& ec)
|
||||
{
|
||||
m_.result(code);
|
||||
m_.version = version;
|
||||
@@ -253,8 +412,11 @@ private:
|
||||
}
|
||||
|
||||
void
|
||||
on_field(field name, string_view name_string,
|
||||
string_view value, error_code& ec)
|
||||
on_field_impl(
|
||||
field name,
|
||||
string_view name_string,
|
||||
string_view value,
|
||||
error_code& ec)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -268,39 +430,54 @@ private:
|
||||
}
|
||||
|
||||
void
|
||||
on_header(error_code& ec)
|
||||
on_header_impl(error_code& ec)
|
||||
{
|
||||
if(cb_)
|
||||
cb_(*this, ec);
|
||||
else
|
||||
ec.assign(0, ec.category());
|
||||
ec.assign(0, ec.category());
|
||||
}
|
||||
|
||||
void
|
||||
on_body(boost::optional<
|
||||
std::uint64_t> const& content_length,
|
||||
error_code& ec)
|
||||
on_body_init_impl(
|
||||
boost::optional<std::uint64_t> const& content_length,
|
||||
error_code& ec)
|
||||
{
|
||||
wr_.init(content_length, ec);
|
||||
wr_inited_ = true;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
on_data(string_view s, error_code& ec)
|
||||
on_body_impl(
|
||||
string_view body,
|
||||
error_code& ec)
|
||||
{
|
||||
return wr_.put(boost::asio::buffer(
|
||||
s.data(), s.size()), ec);
|
||||
body.data(), body.size()), ec);
|
||||
}
|
||||
|
||||
void
|
||||
on_chunk(std::uint64_t,
|
||||
string_view, error_code& ec)
|
||||
on_chunk_header_impl(
|
||||
std::uint64_t size,
|
||||
string_view extensions,
|
||||
error_code& ec)
|
||||
{
|
||||
if(cb_h_)
|
||||
return (*cb_h_)(size, extensions, ec);
|
||||
ec.assign(0, ec.category());
|
||||
}
|
||||
|
||||
std::size_t
|
||||
on_chunk_body_impl(
|
||||
std::uint64_t remain,
|
||||
string_view body,
|
||||
error_code& ec)
|
||||
{
|
||||
if(cb_b_)
|
||||
return (*cb_b_)(remain, body, ec);
|
||||
return wr_.put(boost::asio::buffer(
|
||||
body.data(), body.size()), ec);
|
||||
}
|
||||
|
||||
void
|
||||
on_complete(error_code& ec)
|
||||
on_finish_impl(error_code& ec)
|
||||
{
|
||||
wr_.finish(ec);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user