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:
Vinnie Falco
2017-07-20 08:15:31 -07:00
parent cc73d883be
commit 4b71aa67e0
27 changed files with 1851 additions and 269 deletions
+75
View File
@@ -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
+7 -6
View File
@@ -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
+36 -2
View File
@@ -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;
};
//------------------------------------------------------------------------------
+176 -2
View File
@@ -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
+6 -6
View File
@@ -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,
+7
View File
@@ -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
};
+55 -52
View File
@@ -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>
+445 -25
View File
@@ -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
+2
View File
@@ -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:
+66 -4
View File
@@ -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
+4 -4
View File
@@ -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
View File
@@ -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);
}