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 committed 2017-07-20 08:15:31 -07:00
1 parent cc73d883be
commit 4b71aa67e0
27 files changed
+1851 -269

No files matched your search

+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,