Parser concept, fixes:

A new concept Parser is introduced with routines to read from a stream
into the parser. This solves a problem with the old read interface where
messages must be default constructible and move assignable.

Parser fixes:

* Fix detect invalid reason-phrase octets
* Fix write_eof to set the 'complete' state on success
* Fix consider parse complete if eof received on empty body

WebSocket:

* Increase coverage
This commit is contained in:
Vinnie Falco
2016-04-30 10:29:39 -04:00
parent b62d1b2164
commit 4f1b9089b8
28 changed files with 1536 additions and 701 deletions

View File

@@ -30,7 +30,8 @@ WebSocket:
* optimized versions of key/masking, choose prepared_key size * optimized versions of key/masking, choose prepared_key size
* invokable unit test * invokable unit test
* Don't try to read requests into empty_body * Don't try to read requests into empty_body
* Give callers control over the http request/response used during handshake
* Investigate poor autobahn results in Debug builds
HTTP: HTTP:
* Define Parser concept in HTTP * Define Parser concept in HTTP
@@ -43,6 +44,7 @@ HTTP:
* More fine grained parser errors * More fine grained parser errors
* HTTP parser size limit with test (configurable?) * HTTP parser size limit with test (configurable?)
* HTTP parser trailers with test * HTTP parser trailers with test
* Decode chunk encoding parameters
* URL parser, strong URL character checking in HTTP parser * URL parser, strong URL character checking in HTTP parser
* Update for rfc7230 * Update for rfc7230
* Consider rename to MessageBody concept * Consider rename to MessageBody concept

View File

@@ -40,11 +40,11 @@
[section:intro Introduction] [section:intro Introduction]
Beast is a cross-platform C++ library built on Boost, containing two modules Beast is a cross-platform C++ library built on Boost.Asio and Boost, containing
implementing widely used network protocols. Beast.HTTP offers a universal two modules implementing widely used network protocols. Beast.HTTP offers a
model for describing, sending, and receiving HTTP messages while Beast.WebSocket universal model for describing, sending, and receiving HTTP messages while
provides a complete implementation of the WebSocket protocol. Their design Beast.WebSocket provides a complete implementation of the WebSocket protocol.
achieves these goals: Their design achieves these goals:
* [*Symmetry.] Interfaces are role-agnostic; the same interfaces can be * [*Symmetry.] Interfaces are role-agnostic; the same interfaces can be
used to build clients, servers, or both. used to build clients, servers, or both.
@@ -60,7 +60,7 @@ strategies; important decisions such as buffer or thread management are
left to users of the library. left to users of the library.
* [*Performance.] The implementation performs competitively, making it a * [*Performance.] The implementation performs competitively, making it a
realistic choice for building a high performance network server. realistic choice for building high performance network servers.
* [*Scalability.] Development of network applications that scale to thousands * [*Scalability.] Development of network applications that scale to thousands
of concurrent connections is possible with the implementation. of concurrent connections is possible with the implementation.
@@ -168,12 +168,16 @@ int main()
[section:credits Credits] [section:credits Credits]
Boost.Asio is the inspiration behind which all of the interfaces and
implementation strategies are built. Some parts of the documentation are
written to closely resemble the wording and presentation of Boost.Asio
documentation. Credit goes to Christopher Kohloff for the wonderful
Asio library and the ideas upon which Beast is built.
Beast would not be possible without the considerable time and patience Beast would not be possible without the considerable time and patience
contributed by David Schwartz, Edward Hennis, Howard Hinnant, Miguel Portilla, contributed by David Schwartz, Edward Hennis, Howard Hinnant, Miguel Portilla,
Nikolaos Bougalis, Scott Determan, Scott Schurr, and Ripple Labs for Nikolaos Bougalis, Scott Determan, Scott Schurr, and Ripple Labs for
supporting its development. Thanks also to Christopher Kohloff, whose Asio supporting its development.
C++ library is the inspiration behind which much of the design and
documentation is based.
[endsect] [endsect]

View File

@@ -12,7 +12,7 @@ their associated operations including synchronous and asynchronous reading and
writing of messages in the HTTP/1 wire format using Boost.Asio. writing of messages in the HTTP/1 wire format using Boost.Asio.
The HTTP protocol is described fully in The HTTP protocol is described fully in
[@https://tools.ietf.org/html/rfc2616 rfc2616] [@https://tools.ietf.org/html/rfc7230 rfc7230]

View File

@@ -23,7 +23,7 @@ namespace unit_test {
/** @{ */ /** @{ */
template <class = void> template <class = void>
void void
print (results const& r, beast::detail::abstract_ostream& stream) print (results const& r, abstract_ostream& stream)
{ {
for (auto const& s : r) for (auto const& s : r)
{ {

View File

@@ -855,8 +855,11 @@ std::size_t
read_size_helper(basic_streambuf< read_size_helper(basic_streambuf<
Allocator> const& streambuf, std::size_t max_size) Allocator> const& streambuf, std::size_t max_size)
{ {
return std::min<std::size_t>(max_size, auto const avail = streambuf.prepare_size();
std::max<std::size_t>(512, streambuf.prepare_size())); if(avail == 0)
return std::min(max_size,
std::max<std::size_t>(512, streambuf.alloc_size_));
return std::min(max_size, avail);
} }
template<class Alloc, class T> template<class Alloc, class T>

View File

@@ -36,11 +36,26 @@ enum values
}; };
} // parse_flag } // parse_flag
/** Base class for parsing HTTP/1 requests and responses. /** A parser for decoding HTTP/1 wire format messages.
During parsing, callbacks will be made to the derived class This parser is designed to efficiently parse messages in the
if those members are present (detected through SFINAE). The HTTP/1 wire format. It allocates no memory and uses minimal
signatures which can be present in the derived class are:<br> state. It will handle chunked encoding and it understands the
semantics of the Connection and Content-Length header fields.
The interface uses CRTP (Curiously Recurring Template Pattern).
To use this class, derive from basic_parser. When bytes are
presented, the implementation will make a series of zero or
more calls to derived class members functions (referred to as
"callbacks" from here on) matching a specific signature.
Callbacks are detected through SFINAE. The derived class may
implement as few or as many of the members as needed.
These are the signatures of the callbacks:<br>
@li `void on_start(error_code&)`
Called when the first valid octet of a new message is received
@li `void on_method(boost::string_ref const&, error_code&)` @li `void on_method(boost::string_ref const&, error_code&)`
@@ -106,6 +121,9 @@ enum values
If a callback sets an error, parsing stops at the current octet If a callback sets an error, parsing stops at the current octet
and the error is returned to the caller. and the error is returned to the caller.
@tparam isRequest A `bool` indicating whether the parser will be
presented with request or response message.
*/ */
template<bool isRequest, class Derived> template<bool isRequest, class Derived>
class basic_parser_v1 class basic_parser_v1
@@ -188,7 +206,8 @@ private:
s_chunk_data_done, s_chunk_data_done,
s_complete, s_complete,
s_restart s_restart,
s_closed_complete
}; };
enum field_state : std::uint8_t enum field_state : std::uint8_t
@@ -341,7 +360,7 @@ public:
bool bool
complete() const complete() const
{ {
return s_ == s_restart; return s_ == s_restart || s_ == s_closed_complete;
} }
/** Write a sequence of buffers to the parser. /** Write a sequence of buffers to the parser.
@@ -411,6 +430,24 @@ private:
bool bool
needs_eof(std::false_type) const; needs_eof(std::false_type) const;
template<class C>
class has_on_start_t
{
template<class T, class R =
decltype(std::declval<T>().on_start(
std::declval<error_code&>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_start =
std::integral_constant<bool, has_on_start_t<C>::value>;
template<class C> template<class C>
class has_on_method_t class has_on_method_t
{ {
@@ -596,6 +633,20 @@ private:
using has_on_complete = using has_on_complete =
std::integral_constant<bool, has_on_complete_t<C>::value>; std::integral_constant<bool, has_on_complete_t<C>::value>;
void call_on_start(error_code& ec, std::true_type)
{
impl().on_start(ec);
}
void call_on_start(error_code& ec, std::false_type)
{
}
void call_on_start(error_code& ec)
{
call_on_start(ec, has_on_start<Derived>{});
}
void call_on_method(error_code& ec, void call_on_method(error_code& ec,
boost::string_ref const& s, std::true_type) boost::string_ref const& s, std::true_type)
{ {

View File

@@ -132,7 +132,7 @@ to_value_char(char c)
} }
inline inline
std::uint8_t std::int8_t
unhex(char c) unhex(char c)
{ {
static std::array<std::int8_t, 256> constexpr tab = {{ static std::array<std::int8_t, 256> constexpr tab = {{

View File

@@ -9,6 +9,7 @@
#define BEAST_HTTP_IMPL_BASIC_PARSER_V1_IPP #define BEAST_HTTP_IMPL_BASIC_PARSER_V1_IPP
#include <beast/core/buffer_concepts.hpp> #include <beast/core/buffer_concepts.hpp>
#include <cassert>
namespace beast { namespace beast {
namespace http { namespace http {
@@ -88,6 +89,11 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
s_ = s_closed; s_ = s_closed;
return used(); return used();
}; };
auto errc = [&]
{
s_ = s_closed;
return used();
};
auto piece = [&] auto piece = [&]
{ {
return boost::string_ref{ return boost::string_ref{
@@ -113,6 +119,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
switch(s_) switch(s_)
{ {
case s_closed: case s_closed:
case s_closed_complete:
return err(parse_error::connection_closed); return err(parse_error::connection_closed);
break; break;
@@ -126,6 +133,9 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
case s_req_method_start: case s_req_method_start:
if(! is_token(ch)) if(! is_token(ch))
return err(parse_error::bad_method); return err(parse_error::bad_method);
call_on_start(ec);
if(ec)
return errc();
cb_ = &self::call_on_method; cb_ = &self::call_on_method;
s_ = s_req_method; s_ = s_req_method;
break; break;
@@ -134,7 +144,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
if(! is_token(ch)) if(! is_token(ch))
{ {
if(cb(nullptr)) if(cb(nullptr))
return used(); return errc();
s_ = s_req_space_before_url; s_ = s_req_space_before_url;
goto redo; goto redo;
} }
@@ -147,21 +157,23 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
break; break;
case s_req_url_start: case s_req_url_start:
{
if(ch == ' ') if(ch == ' ')
return err(parse_error::bad_uri); return err(parse_error::bad_uri);
// VFALCO TODO Better checking for valid URL characters // VFALCO TODO Better checking for valid URL characters
if(! is_text(ch)) if(! is_text(ch))
return err(parse_error::bad_uri); return err(parse_error::bad_uri);
if(cb(&self::call_on_uri)) assert(! cb_);
return used(); cb(&self::call_on_uri);
s_ = s_req_url; s_ = s_req_url;
break; break;
}
case s_req_url: case s_req_url:
if(ch == ' ') if(ch == ' ')
{ {
if(cb(nullptr)) if(cb(nullptr))
return used(); return errc();
s_ = s_req_http_start; s_ = s_req_http_start;
break; break;
} }
@@ -245,7 +257,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
return err(parse_error::bad_crlf); return err(parse_error::bad_crlf);
call_on_request(ec); call_on_request(ec);
if(ec) if(ec)
return used(); return errc();
s_ = s_header_field_start; s_ = s_header_field_start;
break; break;
@@ -257,7 +269,14 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
content_length_ = no_content_length; content_length_ = no_content_length;
switch(ch) switch(ch)
{ {
case 'H': s_ = s_res_H; break; case 'H':
call_on_start(ec);
if(ec)
return errc();
s_ = s_res_H;
break;
// VFALCO NOTE this allows whitespace at the beginning,
// need to check rfc7230
case '\r': case '\r':
case '\n': case '\n':
break; break;
@@ -365,13 +384,16 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
s_ = s_res_line_almost_done; s_ = s_res_line_almost_done;
break; break;
} }
// VFALCO Is this up to spec?
if(ch == '\n') if(ch == '\n')
{ {
s_ = s_header_field_start; s_ = s_header_field_start;
break; break;
} }
if(! is_text(ch))
return err(parse_error::bad_status);
if(cb(&self::call_on_reason)) if(cb(&self::call_on_reason))
return used(); return errc();
pos_ = 0; pos_ = 0;
s_ = s_res_status; s_ = s_res_status;
break; break;
@@ -380,17 +402,19 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
if(ch == '\r') if(ch == '\r')
{ {
if(cb(nullptr)) if(cb(nullptr))
return used(); return errc();
s_ = s_res_line_almost_done; s_ = s_res_line_almost_done;
break; break;
} }
if(ch == '\n') if(ch == '\n')
{ {
if(cb(nullptr)) if(cb(nullptr))
return used(); return errc();
s_ = s_header_field_start; s_ = s_header_field_start;
break; break;
} }
if(! is_text(ch))
return err(parse_error::bad_status);
break; break;
case s_res_line_almost_done: case s_res_line_almost_done:
@@ -402,7 +426,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
case s_res_line_done: case s_res_line_done:
call_on_response(ec); call_on_response(ec);
if(ec) if(ec)
return used(); return errc();
s_ = s_header_field_start; s_ = s_header_field_start;
goto redo; goto redo;
@@ -431,8 +455,8 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
fs_ = h_general; fs_ = h_general;
break; break;
} }
if(cb(&self::call_on_field)) assert(! cb_);
return used(); cb(&self::call_on_field);
s_ = s_header_field; s_ = s_header_field;
break; break;
} }
@@ -529,7 +553,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
if(ch == ':') if(ch == ':')
{ {
if(cb(nullptr)) if(cb(nullptr))
return used(); return errc();
s_ = s_header_value_start; s_ = s_header_value_start;
break; break;
} }
@@ -579,7 +603,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
} }
call_on_value(ec, boost::string_ref{"", 0}); call_on_value(ec, boost::string_ref{"", 0});
if(ec) if(ec)
return used(); return errc();
s_ = s_header_field_start; s_ = s_header_field_start;
goto redo; goto redo;
@@ -629,7 +653,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
} }
pos_ = 0; pos_ = 0;
if(cb(&self::call_on_value)) if(cb(&self::call_on_value))
return used(); return errc();
s_ = s_header_value_text; s_ = s_header_value_text;
break; break;
} }
@@ -641,7 +665,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
if(ch == '\r') if(ch == '\r')
{ {
if(cb(nullptr)) if(cb(nullptr))
return used(); return errc();
s_ = s_header_value_discard_lWs; s_ = s_header_value_discard_lWs;
break; break;
} }
@@ -775,9 +799,9 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
return err(parse_error::bad_value); return err(parse_error::bad_value);
call_on_value(ec, boost::string_ref(" ", 1)); call_on_value(ec, boost::string_ref(" ", 1));
if(ec) if(ec)
return used(); return errc();
if(cb(&self::call_on_value)) if(cb(&self::call_on_value))
return used(); return errc();
s_ = s_header_value_text; s_ = s_header_value_text;
break; break;
@@ -811,7 +835,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
return err(parse_error::bad_crlf); return err(parse_error::bad_crlf);
if(flags_ & parse_flag::trailing) if(flags_ & parse_flag::trailing)
{ {
//if(cb(&self::call_on_chunk_complete)) return used(); //if(cb(&self::call_on_chunk_complete)) return errc();
s_ = s_complete; s_ = s_complete;
goto redo; goto redo;
} }
@@ -821,7 +845,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
(parse_flag::upgrade | parse_flag::connection_upgrade)) /*|| method == "connect"*/; (parse_flag::upgrade | parse_flag::connection_upgrade)) /*|| method == "connect"*/;
auto const maybe_skip = call_on_headers(ec); auto const maybe_skip = call_on_headers(ec);
if(ec) if(ec)
return used(); return errc();
switch(maybe_skip) switch(maybe_skip)
{ {
case 0: break; case 0: break;
@@ -839,7 +863,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
assert(! cb_); assert(! cb_);
call_on_headers(ec); call_on_headers(ec);
if(ec) if(ec)
return used(); return errc();
bool const hasBody = bool const hasBody =
(flags_ & parse_flag::chunked) || (content_length_ > 0 && (flags_ & parse_flag::chunked) || (content_length_ > 0 &&
content_length_ != no_content_length); content_length_ != no_content_length);
@@ -878,8 +902,8 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
} }
case s_body_identity0: case s_body_identity0:
if(cb(&self::call_on_body)) assert(! cb_);
return used(); cb(&self::call_on_body);
s_ = s_body_identity; s_ = s_body_identity;
goto redo; // VFALCO fall through? goto redo; // VFALCO fall through?
@@ -903,8 +927,8 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
} }
case s_body_identity_eof0: case s_body_identity_eof0:
if(cb(&self::call_on_body)) assert(! cb_);
return used(); cb(&self::call_on_body);
s_ = s_body_identity_eof; s_ = s_body_identity_eof;
goto redo; // VFALCO fall through? goto redo; // VFALCO fall through?
@@ -963,13 +987,13 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
s_ = s_header_field_start; s_ = s_header_field_start;
break; break;
} }
//call_chunk_header(ec); if(ec) return used(); //call_chunk_header(ec); if(ec) return errc();
s_ = s_chunk_data_start; s_ = s_chunk_data_start;
break; break;
case s_chunk_data_start: case s_chunk_data_start:
if(cb(&self::call_on_body)) assert(! cb_);
return used(); cb(&self::call_on_body);
s_ = s_chunk_data; s_ = s_chunk_data;
goto redo; // VFALCO fall through? goto redo; // VFALCO fall through?
@@ -991,7 +1015,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
if(ch != '\r') if(ch != '\r')
return err(parse_error::bad_crlf); return err(parse_error::bad_crlf);
if(cb(nullptr)) if(cb(nullptr))
return used(); return errc();
s_ = s_chunk_data_done; s_ = s_chunk_data_done;
break; break;
@@ -1005,10 +1029,10 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
case s_complete: case s_complete:
++p; ++p;
if(cb(nullptr)) if(cb(nullptr))
return used(); return errc();
call_on_complete(ec); call_on_complete(ec);
if(ec) if(ec)
return used(); return errc();
s_ = s_restart; s_ = s_restart;
return used(); return used();
@@ -1024,7 +1048,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
{ {
(this->*cb_)(ec, piece()); (this->*cb_)(ec, piece());
if(ec) if(ec)
return used(); return errc();
} }
return used(); return used();
} }
@@ -1036,17 +1060,31 @@ write_eof(error_code& ec)
{ {
switch(s_) switch(s_)
{ {
case s_restart:
s_ = s_closed_complete;
break;
case s_closed:
case s_closed_complete:
break;
case s_body_identity_eof0:
case s_body_identity_eof: case s_body_identity_eof:
cb_ = nullptr; cb_ = nullptr;
call_on_complete(ec); call_on_complete(ec);
if(ec) if(ec)
return; {
return; s_ = s_closed;
default:
break; break;
} }
ec = parse_error::short_read; s_ = s_closed_complete;
break;
default:
s_ = s_closed; s_ = s_closed;
ec = parse_error::short_read;
break;
}
} }
template<bool isRequest, class Derived> template<bool isRequest, class Derived>

View File

@@ -9,6 +9,7 @@
#define BEAST_HTTP_IMPL_READ_IPP_HPP #define BEAST_HTTP_IMPL_READ_IPP_HPP
#include <beast/http/parser_v1.hpp> #include <beast/http/parser_v1.hpp>
#include <beast/http/type_check.hpp>
#include <beast/core/bind_handler.hpp> #include <beast/core/bind_handler.hpp>
#include <beast/core/handler_alloc.hpp> #include <beast/core/handler_alloc.hpp>
#include <beast/core/stream_concepts.hpp> #include <beast/core/stream_concepts.hpp>
@@ -19,6 +20,185 @@ namespace http {
namespace detail { namespace detail {
template<class Stream,
class Streambuf, class Parser, class Handler>
class parse_op
{
using alloc_type =
handler_alloc<char, Handler>;
struct data
{
Stream& s;
Streambuf& sb;
Parser& p;
Handler h;
bool started = false;
bool cont;
int state = 0;
template<class DeducedHandler>
data(DeducedHandler&& h_, Stream& s_,
Streambuf& sb_, Parser& p_)
: s(s_)
, sb(sb_)
, p(p_)
, h(std::forward<DeducedHandler>(h_))
, cont(boost_asio_handler_cont_helpers::
is_continuation(h))
{
}
};
std::shared_ptr<data> d_;
public:
parse_op(parse_op&&) = default;
parse_op(parse_op const&) = default;
template<class DeducedHandler, class... Args>
parse_op(DeducedHandler&& h, Stream& s, Args&&... args)
: d_(std::allocate_shared<data>(alloc_type{h},
std::forward<DeducedHandler>(h), s,
std::forward<Args>(args)...))
{
(*this)(error_code{}, 0, false);
}
void
operator()(error_code ec,
std::size_t bytes_transferred, bool again = true);
friend
void* asio_handler_allocate(
std::size_t size, parse_op* op)
{
return boost_asio_handler_alloc_helpers::
allocate(size, op->d_->h);
}
friend
void asio_handler_deallocate(
void* p, std::size_t size, parse_op* op)
{
return boost_asio_handler_alloc_helpers::
deallocate(p, size, op->d_->h);
}
friend
bool asio_handler_is_continuation(parse_op* op)
{
return op->d_->cont;
}
template <class Function>
friend
void asio_handler_invoke(Function&& f, parse_op* op)
{
return boost_asio_handler_invoke_helpers::
invoke(f, op->d_->h);
}
};
template<class Stream,
class Streambuf, class Parser, class Handler>
void
parse_op<Stream, Streambuf, Parser, Handler>::
operator()(error_code ec, std::size_t bytes_transferred, bool again)
{
auto& d = *d_;
d.cont = d.cont || again;
while(d.state != 99)
{
switch(d.state)
{
case 0:
{
auto const used =
d.p.write(d.sb.data(), ec);
if(ec)
{
// call handler
d.state = 99;
d.s.get_io_service().post(
bind_handler(std::move(*this), ec, 0));
return;
}
if(used > 0)
d.started = true;
d.sb.consume(used);
if(d.p.complete())
{
// call handler
d.state = 99;
d.s.get_io_service().post(
bind_handler(std::move(*this), ec, 0));
return;
}
d.state = 1;
break;
}
case 1:
// read
d.state = 2;
d.s.async_read_some(d.sb.prepare(
read_size_helper(d.sb, 65536)),
std::move(*this));
return;
// got data
case 2:
{
if(ec == boost::asio::error::eof)
{
if(! d.started)
{
// call handler
d.state = 99;
break;
}
// Caller will see eof on next read.
ec = {};
d.p.write_eof(ec);
assert(ec || d.p.complete());
// call handler
d.state = 99;
break;
}
if(ec)
{
// call handler
d.state = 99;
break;
}
d.sb.commit(bytes_transferred);
auto const used = d.p.write(d.sb.data(), ec);
if(ec)
{
// call handler
d.state = 99;
break;
}
if(used > 0)
d.started = true;
d.sb.consume(used);
if(d.p.complete())
{
// call handler
d.state = 99;
break;
}
d.state = 1;
break;
}
}
}
d.h(ec);
}
//------------------------------------------------------------------------------
template<class Stream, class Streambuf, template<class Stream, class Streambuf,
bool isRequest, class Body, class Headers, bool isRequest, class Body, class Headers,
class Handler> class Handler>
@@ -69,12 +249,11 @@ public:
std::forward<DeducedHandler>(h), s, std::forward<DeducedHandler>(h), s,
std::forward<Args>(args)...)) std::forward<Args>(args)...))
{ {
(*this)(error_code{}, 0, false); (*this)(error_code{}, false);
} }
void void
operator()(error_code ec, operator()(error_code ec, bool again = true);
std::size_t bytes_transferred, bool again = true);
friend friend
void* asio_handler_allocate( void* asio_handler_allocate(
@@ -112,98 +291,25 @@ template<class Stream, class Streambuf,
class Handler> class Handler>
void void
read_op<Stream, Streambuf, isRequest, Body, Headers, Handler>:: read_op<Stream, Streambuf, isRequest, Body, Headers, Handler>::
operator()(error_code ec, std::size_t bytes_transferred, bool again) operator()(error_code ec, bool again)
{ {
auto& d = *d_; auto& d = *d_;
d.cont = d.cont || again; d.cont = d.cont || again;
while(d.state != 99) while(! ec && d.state != 99)
{ {
switch(d.state) switch(d.state)
{ {
case 0: case 0:
{
auto const used =
d.p.write(d.sb.data(), ec);
if(ec)
{
// call handler
d.state = 99;
d.s.get_io_service().post(
bind_handler(std::move(*this), ec, 0));
return;
}
if(used > 0)
d.started = true;
d.sb.consume(used);
if(d.p.complete())
{
// call handler
d.state = 99;
d.m = d.p.release();
d.s.get_io_service().post(
bind_handler(std::move(*this), ec, 0));
return;
}
d.state = 1; d.state = 1;
break; async_parse(d.s, d.sb, d.p, std::move(*this));
} return;
case 1: case 1:
// read
d.state = 2;
d.s.async_read_some(d.sb.prepare(
read_size_helper(d.sb, 65536)),
std::move(*this));
return;
// got data
case 2:
{
if(ec == boost::asio::error::eof)
{
if(! d.started)
{
// call handler
d.state = 99;
break;
}
// Caller will see eof on next read.
ec = {};
d.p.write_eof(ec);
if(! ec)
{
assert(d.p.complete());
d.m = d.p.release();
}
// call handler
d.state = 99;
break;
}
if(ec)
{
// call handler
d.state = 99;
break;
}
d.sb.commit(bytes_transferred);
d.sb.consume(d.p.write(d.sb.data(), ec));
if(ec)
{
// call handler
d.state = 99;
break;
}
if(d.p.complete())
{
// call handler // call handler
d.state = 99; d.state = 99;
d.m = d.p.release(); d.m = d.p.release();
break; break;
} }
d.state = 1;
break;
}
}
} }
d.h(ec); d.h(ec);
} }
@@ -212,12 +318,91 @@ operator()(error_code ec, std::size_t bytes_transferred, bool again)
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
template<class SyncReadStream, class Streambuf, class Parser>
void
parse(SyncReadStream& stream,
Streambuf& streambuf, Parser& parser)
{
error_code ec;
parse(stream, streambuf, parser, ec);
if(ec)
throw boost::system::system_error{ec};
}
template<class SyncReadStream, class Streambuf, class Parser>
void
parse(SyncReadStream& stream, Streambuf& streambuf,
Parser& parser, error_code& ec)
{
static_assert(is_SyncReadStream<SyncReadStream>::value,
"SyncReadStream requirements not met");
static_assert(is_Streambuf<Streambuf>::value,
"Streambuf requirements not met");
static_assert(is_Parser<Parser>::value,
"Parser requirements not met");
bool started = false;
for(;;)
{
auto used =
parser.write(streambuf.data(), ec);
if(ec)
return;
streambuf.consume(used);
if(used > 0)
started = true;
if(parser.complete())
break;
streambuf.commit(stream.read_some(
streambuf.prepare(read_size_helper(
streambuf, 65536)), ec));
if(ec && ec != boost::asio::error::eof)
return;
if(ec == boost::asio::error::eof)
{
if(! started)
return;
// Caller will see eof on next read.
ec = {};
parser.write_eof(ec);
if(ec)
return;
assert(parser.complete());
break;
}
}
}
template<class AsyncReadStream,
class Streambuf, class Parser, class ReadHandler>
typename async_completion<
ReadHandler, void(error_code)>::result_type
async_parse(AsyncReadStream& stream,
Streambuf& streambuf, Parser& parser, ReadHandler&& handler)
{
static_assert(is_AsyncReadStream<AsyncReadStream>::value,
"AsyncReadStream requirements not met");
static_assert(is_Streambuf<Streambuf>::value,
"Streambuf requirements not met");
static_assert(is_Parser<Parser>::value,
"Parser requirements not met");
beast::async_completion<ReadHandler,
void(error_code)> completion(handler);
detail::parse_op<AsyncReadStream, Streambuf,
Parser, decltype(completion.handler)>{
completion.handler, stream, streambuf, parser};
return completion.result.get();
}
template<class SyncReadStream, class Streambuf, template<class SyncReadStream, class Streambuf,
bool isRequest, class Body, class Headers> bool isRequest, class Body, class Headers>
void void
read(SyncReadStream& stream, Streambuf& streambuf, read(SyncReadStream& stream, Streambuf& streambuf,
message_v1<isRequest, Body, Headers>& msg) message_v1<isRequest, Body, Headers>& msg)
{ {
static_assert(is_SyncReadStream<SyncReadStream>::value,
"SyncReadStream requirements not met");
static_assert(is_Streambuf<Streambuf>::value,
"Streambuf requirements not met");
error_code ec; error_code ec;
read(stream, streambuf, msg, ec); read(stream, streambuf, msg, ec);
if(ec) if(ec)
@@ -236,40 +421,11 @@ read(SyncReadStream& stream, Streambuf& streambuf,
static_assert(is_Streambuf<Streambuf>::value, static_assert(is_Streambuf<Streambuf>::value,
"Streambuf requirements not met"); "Streambuf requirements not met");
parser_v1<isRequest, Body, Headers> p; parser_v1<isRequest, Body, Headers> p;
bool started = false; parse(stream, streambuf, p, ec);
for(;;)
{
auto used =
p.write(streambuf.data(), ec);
if(ec)
return;
streambuf.consume(used);
if(used > 0)
started = true;
if(p.complete())
{
m = p.release();
break;
}
streambuf.commit(stream.read_some(
streambuf.prepare(read_size_helper(
streambuf, 65536)), ec));
if(ec && ec != boost::asio::error::eof)
return;
if(ec == boost::asio::error::eof)
{
if(! started)
return;
// Caller will see eof on next read.
ec = {};
p.write_eof(ec);
if(ec) if(ec)
return; return;
assert(p.complete()); assert(p.complete());
m = p.release(); m = p.release();
break;
}
}
} }
template<class AsyncReadStream, class Streambuf, template<class AsyncReadStream, class Streambuf,

View File

@@ -11,7 +11,6 @@
#include <beast/http/basic_parser_v1.hpp> #include <beast/http/basic_parser_v1.hpp>
#include <beast/http/message_v1.hpp> #include <beast/http/message_v1.hpp>
#include <beast/core/error.hpp> #include <beast/core/error.hpp>
#include <boost/optional.hpp>
#include <functional> #include <functional>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
@@ -39,6 +38,8 @@ struct parser_response
This class uses the basic HTTP/1 wire format parser to convert This class uses the basic HTTP/1 wire format parser to convert
a series of octets into a `message_v1`. a series of octets into a `message_v1`.
@note A new instance of the parser is required for each message.
*/ */
template<bool isRequest, class Body, class Headers> template<bool isRequest, class Body, class Headers>
class parser_v1 class parser_v1
@@ -47,9 +48,12 @@ class parser_v1
, private std::conditional<isRequest, , private std::conditional<isRequest,
detail::parser_request, detail::parser_response>::type detail::parser_request, detail::parser_response>::type
{ {
public:
/// The type of message this parser produces.
using message_type = using message_type =
message_v1<isRequest, Body, Headers>; message_v1<isRequest, Body, Headers>;
private:
std::string field_; std::string field_;
std::string value_; std::string value_;
message_type m_; message_type m_;
@@ -57,15 +61,55 @@ class parser_v1
public: public:
parser_v1(parser_v1&&) = default; parser_v1(parser_v1&&) = default;
parser_v1(parser_v1 const&) = delete;
parser_v1& operator=(parser_v1&&) = delete;
parser_v1& operator=(parser_v1 const&) = delete;
parser_v1() /** Construct the parser.
: r_(m_)
@param args A list of arguments forwarded to the message constructor.
*/
template<class... Args>
explicit
parser_v1(Args&&... args)
: m_(std::forward<Args>(args)...)
, r_(m_)
{ {
} }
/** Returns the parsed message.
Only valid if `complete()` would return `true`.
*/
message_type const&
get() const
{
return m_;
}
/** Returns the parsed message.
Only valid if `complete()` would return `true`.
*/
message_type&
get()
{
return m_;
}
/** Returns the parsed message.
Ownership is transferred to the caller.
Only valid if `complete()` would return `true`.
Requires:
`message<isRequest, Body, Headers>` is MoveConstructible
*/
message_type message_type
release() release()
{ {
static_assert(std::is_move_constructible<decltype(m_)>::value,
"MoveConstructible requirements not met");
return std::move(m_); return std::move(m_);
} }
@@ -84,6 +128,10 @@ private:
} }
} }
void on_start(error_code&)
{
}
void on_method(boost::string_ref const& s, error_code&) void on_method(boost::string_ref const& s, error_code&)
{ {
this->method_.append(s.data(), s.size()); this->method_.append(s.data(), s.size());

View File

@@ -17,6 +17,127 @@
namespace beast { namespace beast {
namespace http { namespace http {
/** Parse a HTTP/1 message from a stream.
This function synchronously reads from a stream and passes
data to the specified parser. The call will block until one
of the following conditions are met:
@li A complete message is read in.
@li An error occurs in the stream or parser.
This function is implemented in terms of one or more calls
to the stream's `read_some` function. The implementation may
read additional octets that lie past the end of the message
being parsed. This additional data is stored in the stream
buffer, which may be used in subsequent calls.
@param stream The stream from which the data is to be read.
The type must support the @b `SyncReadStream` concept.
@param streambuf A `Streambuf` holding additional bytes
read by the implementation from the stream. This is both
an input and an output parameter; on entry, any data in the
stream buffer's input sequence will be given to the parser
first.
@param parser An object meeting the requirements of Parser
which will receive the data.
@throws boost::system::system_error on failure.
*/
template<class SyncReadStream, class Streambuf, class Parser>
void
parse(SyncReadStream& stream,
Streambuf& streambuf, Parser& parser);
/** Parse a HTTP/1 message from a stream.
This function synchronously reads from a stream and passes
data to the specified parser. The call will block until one
of the following conditions are met:
@li A complete message is read in.
@li An error occurs in the stream or parser.
This function is implemented in terms of one or more calls
to the stream's `read_some` function. The implementation may
read additional octets that lie past the end of the message
being parsed. This additional data is stored in the stream
buffer, which may be used in subsequent calls.
@param stream The stream from which the data is to be read.
The type must support the @b `SyncReadStream` concept.
@param streambuf A `Streambuf` holding additional bytes
read by the implementation from the stream. This is both
an input and an output parameter; on entry, any data in the
stream buffer's input sequence will be given to the parser
first.
@param parser An object meeting the requirements of `Parser`
which will receive the data.
@param ec Set to the error, if any occurred.
*/
template<class SyncReadStream, class Streambuf, class Parser>
void
parse(SyncReadStream& stream,
Streambuf& streambuf, Parser& parser, error_code& ec);
/** Start an asynchronous operation to parse a HTTP/1 message from a stream.
This function is used to asynchronously read from a stream and
pass the data to the specified parser. The function call always
returns immediately. The asynchronous operation will continue
until one of the following conditions is true:
@li A complete message is read in.
@li An error occurs in the stream or parser.
This operation is implemented in terms of one or more calls to
the next layer's `async_read_some` function, and is known as a
<em>composed operation</em>. The program must ensure that the
stream performs no other operations until this operation completes.
@param stream The stream from which the data is to be read.
The type must support the @b `AsyncReadStream` concept.
@param streambuf A `Streambuf` holding additional bytes
read by the implementation from the stream. This is both
an input and an output parameter; on entry, any data in the
stream buffer's input sequence will be given to the parser
first.
@param parser An object meeting the requirements of `Parser`
which will receive the data. This object must remain valid
until the completion handler is invoked.
@param handler The handler to be called when the request completes.
Copies will be made of the handler as required. The equivalent
function signature of the handler must be:
@code void handler(
error_code const& error // result of operation
); @endcode
Regardless of whether the asynchronous operation completes
immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a
manner equivalent to using `boost::asio::io_service::post`.
*/
template<class AsyncReadStream,
class Streambuf, class Parser, class ReadHandler>
#if GENERATING_DOCS
void_or_deduced
#else
typename async_completion<
ReadHandler, void(error_code)>::result_type
#endif
async_parse(AsyncReadStream& stream, Streambuf& streambuf,
Parser& parser, ReadHandler&& handler);
/** Read a HTTP/1 message from a stream. /** Read a HTTP/1 message from a stream.
This function is used to synchronously read a message from This function is used to synchronously read a message from
@@ -25,18 +146,22 @@ namespace http {
@li A complete message is read in. @li A complete message is read in.
@li An error occurs on the stream. @li An error occurs in the stream or parser.
This function is implemented in terms of one or more calls to the This function is implemented in terms of one or more calls
stream's `read_some` function. to the stream's `read_some` function. The implementation may
read additional octets that lie past the end of the message
being parsed. This additional data is stored in the stream
buffer, which may be used in subsequent calls.
@param stream The stream to which the data is to be written. @param stream The stream from which the data is to be read.
The type must support the @b `SyncReadStream` concept. The type must support the @b `SyncReadStream` concept.
@param streambuf An object meeting the @b `Streambuf` type requirements @param streambuf A `Streambuf` holding additional bytes
used to hold unread bytes. The implementation may read past the end of read by the implementation from the stream. This is both
the message. The extra bytes are stored here, to be presented in a an input and an output parameter; on entry, any data in the
subsequent call to @ref read. stream buffer's input sequence will be given to the parser
first.
@param msg An object used to store the message. Any @param msg An object used to store the message. Any
contents will be overwritten. contents will be overwritten.
@@ -57,21 +182,25 @@ read(SyncReadStream& stream, Streambuf& streambuf,
@li A complete message is read in. @li A complete message is read in.
@li An error occurs on the stream. @li An error occurs in the stream or parser.
This function is implemented in terms of one or more calls to the This function is implemented in terms of one or more calls
stream's `read_some` function. to the stream's `read_some` function. The implementation may
read additional octets that lie past the end of the message
being parsed. This additional data is stored in the stream
buffer, which may be used in subsequent calls.
@param stream The stream to which the data is to be written. @param stream The stream from which the data is to be read.
The type must support the @b `SyncReadStream` concept. The type must support the @b `SyncReadStream` concept.
@param streambuf An object meeting the @b `Streambuf` type requirements @param streambuf A `Streambuf` holding additional bytes
used to hold unread bytes. The implementation may read past the end of read by the implementation from the stream. This is both
the message. The extra bytes are stored here, to be presented in a an input and an output parameter; on entry, any data in the
subsequent call to @ref read. stream buffer's input sequence will be given to the parser
first.
@param msg An object used to store the message. Any contents @param msg An object used to store the message. Any
will be overwritten. contents will be overwritten.
@param ec Set to the error, if any occurred. @param ec Set to the error, if any occurred.
*/ */
@@ -90,7 +219,7 @@ read(SyncReadStream& stream, Streambuf& streambuf,
@li A complete message is read in. @li A complete message is read in.
@li An error occurs on the stream. @li An error occurs in the stream or parser.
This operation is implemented in terms of one or more calls to the This operation is implemented in terms of one or more calls to the
next layer's `async_read_some` function, and is known as a next layer's `async_read_some` function, and is known as a
@@ -100,10 +229,11 @@ read(SyncReadStream& stream, Streambuf& streambuf,
@param stream The stream to read the message from. @param stream The stream to read the message from.
The type must support the @b `AsyncReadStream` concept. The type must support the @b `AsyncReadStream` concept.
@param streambuf A Streambuf used to hold unread bytes. The @param streambuf A `Streambuf` holding additional bytes
implementation may read past the end of the message. The extra read by the implementation from the stream. This is both
bytes are stored here, to be presented in a subsequent call to an input and an output parameter; on entry, any data in the
@ref async_read. stream buffer's input sequence will be given to the parser
first.
@param msg An object used to store the message. Any contents @param msg An object used to store the message. Any contents
will be overwritten. will be overwritten.

View File

@@ -40,11 +40,9 @@ class is_Parser
static std::false_type check2(...); static std::false_type check2(...);
using type2 = decltype(check2<T>(0)); using type2 = decltype(check2<T>(0));
template<class U, class R = template<class U, class R = decltype(
std::is_convertible<decltype( std::declval<U>().write_eof(std::declval<error_code&>()),
std::declval<U>().write_eof( std::true_type{})>
std::declval<error_code&>())),
std::size_t>>
static R check3(int); static R check3(int);
template<class> template<class>
static std::false_type check3(...); static std::false_type check3(...);

View File

@@ -75,9 +75,9 @@ unit-test websocket-tests :
websocket/rfc6455.cpp websocket/rfc6455.cpp
websocket/stream.cpp websocket/stream.cpp
websocket/teardown.cpp websocket/teardown.cpp
websocket/utf8_checker.cpp
websocket/detail/frame.cpp websocket/detail/frame.cpp
websocket/detail/mask.cpp websocket/detail/mask.cpp
websocket/detail/utf8_checker.cpp
; ;
exe websocket-echo : exe websocket-echo :

View File

@@ -7,6 +7,7 @@ GroupSources(test/core "/")
add_executable (core-tests add_executable (core-tests
${BEAST_INCLUDES} ${BEAST_INCLUDES}
../../extras/beast/unit_test/main.cpp ../../extras/beast/unit_test/main.cpp
buffer_test.hpp
async_completion.cpp async_completion.cpp
basic_streambuf.cpp basic_streambuf.cpp
bind_handler.cpp bind_handler.cpp

View File

@@ -8,6 +8,7 @@
// Test that header file is self-contained. // Test that header file is self-contained.
#include <beast/core/basic_streambuf.hpp> #include <beast/core/basic_streambuf.hpp>
#include "buffer_test.hpp"
#include <beast/core/streambuf.hpp> #include <beast/core/streambuf.hpp>
#include <beast/core/to_string.hpp> #include <beast/core/to_string.hpp>
#include <beast/unit_test/suite.hpp> #include <beast/unit_test/suite.hpp>
@@ -148,6 +149,24 @@ public:
return to_string(sb1.data()) == to_string(sb2.data()); return to_string(sb1.data()) == to_string(sb2.data());
} }
template<class ConstBufferSequence>
void
expect_size(std::size_t n, ConstBufferSequence const& buffers)
{
expect(test::size_pre(buffers) == n);
expect(test::size_post(buffers) == n);
expect(test::size_rev_pre(buffers) == n);
expect(test::size_rev_post(buffers) == n);
}
template<class U, class V>
static
void
self_assign(U& u, V&& v)
{
u = std::forward<V>(v);
}
void testSpecialMembers() void testSpecialMembers()
{ {
using boost::asio::buffer; using boost::asio::buffer;
@@ -177,19 +196,31 @@ public:
{ {
streambuf sb2(std::move(sb)); streambuf sb2(std::move(sb));
expect(to_string(sb2.data()) == s); expect(to_string(sb2.data()) == s);
expect(buffer_size(sb.data()) == 0); expect_size(0, sb.data());
sb = std::move(sb2); sb = std::move(sb2);
expect(to_string(sb.data()) == s); expect(to_string(sb.data()) == s);
expect(buffer_size(sb2.data()) == 0); expect_size(0, sb2.data());
} }
sb = sb; self_assign(sb, sb);
sb = std::move(sb); expect(to_string(sb.data()) == s);
self_assign(sb, std::move(sb));
expect(to_string(sb.data()) == s);
} }
}}} }}}
try
{
streambuf sb0(0);
fail();
}
catch(std::exception const&)
{
pass();
}
} }
void testAllocator() void testAllocator()
{ {
// VFALCO This needs work
{ {
using alloc_type = using alloc_type =
test_allocator<char, false, false, false, false>; test_allocator<char, false, false, false, false>;
@@ -206,7 +237,6 @@ public:
sb_type sb2(sb); sb_type sb2(sb);
expect(sb2.get_allocator().id() == 2); expect(sb2.get_allocator().id() == 2);
sb_type sb3(sb, alloc_type{}); sb_type sb3(sb, alloc_type{});
//expect(sb3.get_allocator().id() == 3);
} }
} }
@@ -223,21 +253,9 @@ public:
{ {
streambuf sb(2); streambuf sb(2);
sb.prepare(2); sb.prepare(2);
{ expect(test::buffer_count(sb.prepare(5)) == 2);
auto const bs = sb.prepare(5); expect(test::buffer_count(sb.prepare(8)) == 3);
expect(std::distance( expect(test::buffer_count(sb.prepare(4)) == 2);
bs.begin(), bs.end()) == 2);
}
{
auto const bs = sb.prepare(8);
expect(std::distance(
bs.begin(), bs.end()) == 3);
}
{
auto const bs = sb.prepare(4);
expect(std::distance(
bs.begin(), bs.end()) == 2);
}
} }
} }
@@ -248,10 +266,21 @@ public:
sb.prepare(2); sb.prepare(2);
sb.prepare(5); sb.prepare(5);
sb.commit(1); sb.commit(1);
expect(buffer_size(sb.data()) == 1); expect_size(1, sb.data());
} }
void testStreambuf() void testConsume()
{
using boost::asio::buffer_size;
streambuf sb(1);
expect_size(5, sb.prepare(5));
sb.commit(3);
expect_size(3, sb.data());
sb.consume(1);
expect_size(2, sb.data());
}
void testMatrix()
{ {
using boost::asio::buffer; using boost::asio::buffer;
using boost::asio::buffer_cast; using boost::asio::buffer_cast;
@@ -354,41 +383,11 @@ public:
sb.commit(1); sb.commit(1);
sb.prepare(2); sb.prepare(2);
sb.commit(2); sb.commit(2);
expect(buffer_size(sb.data()) == 3); expect_size(3, sb.data());
sb.prepare(1); sb.prepare(1);
expect(buffer_size(sb.prepare(3)) == 3); expect_size(3, sb.prepare(3));
expect(read_size_helper(sb, 3) == 3);
sb.commit(2); sb.commit(2);
try expect(test::buffer_count(sb.data()) == 4);
{
streambuf sb0(0);
fail();
}
catch(std::exception const&)
{
pass();
}
std::size_t n;
n = 0;
for(auto it = sb.data().begin();
it != sb.data().end(); it++)
++n;
expect(n == 4);
n = 0;
for(auto it = sb.data().begin();
it != sb.data().end(); ++it)
++n;
expect(n == 4);
n = 0;
for(auto it = sb.data().end();
it != sb.data().begin(); it--)
++n;
expect(n == 4);
n = 0;
for(auto it = sb.data().end();
it != sb.data().begin(); --it)
++n;
expect(n == 4);
} }
void testOutputStream() void testOutputStream()
@@ -398,15 +397,63 @@ public:
expect(to_string(sb.data()) == "x"); expect(to_string(sb.data()) == "x");
} }
void testReadSizeHelper()
{
using boost::asio::buffer_size;
{
streambuf sb(10);
expect(read_size_helper(sb, 0) == 0);
expect(read_size_helper(sb, 1) == 1);
expect(read_size_helper(sb, 10) == 10);
expect(read_size_helper(sb, 20) == 20);
expect(read_size_helper(sb, 1000) == 512);
sb.prepare(3);
sb.commit(3);
expect(read_size_helper(sb, 10) == 7);
expect(read_size_helper(sb, 1000) == 7);
}
{
streambuf sb(1000);
expect(read_size_helper(sb, 0) == 0);
expect(read_size_helper(sb, 1) == 1);
expect(read_size_helper(sb, 1000) == 1000);
expect(read_size_helper(sb, 2000) == 1000);
sb.prepare(3);
expect(read_size_helper(sb, 0) == 0);
expect(read_size_helper(sb, 1) == 1);
expect(read_size_helper(sb, 1000) == 1000);
expect(read_size_helper(sb, 2000) == 1000);
sb.commit(3);
expect(read_size_helper(sb, 0) == 0);
expect(read_size_helper(sb, 1) == 1);
expect(read_size_helper(sb, 1000) == 997);
expect(read_size_helper(sb, 2000) == 997);
sb.consume(2);
expect(read_size_helper(sb, 0) == 0);
expect(read_size_helper(sb, 1) == 1);
expect(read_size_helper(sb, 1000) == 997);
expect(read_size_helper(sb, 2000) == 997);
}
{
streambuf sb(2);
expect(test::buffer_count(sb.prepare(2)) == 1);
expect(test::buffer_count(sb.prepare(3)) == 2);
expect(buffer_size(sb.prepare(5)) == 5);
expect(read_size_helper(sb, 10) == 6);
}
}
void run() override void run() override
{ {
testSpecialMembers(); testSpecialMembers();
testAllocator(); testAllocator();
testPrepare(); testPrepare();
testCommit(); testCommit();
testStreambuf(); testConsume();
testMatrix();
testIterators(); testIterators();
testOutputStream(); testOutputStream();
testReadSizeHelper();
} }
}; };

View File

@@ -116,7 +116,8 @@ public:
try try
{ {
expect((buffer_size(*bs.end()) == 0, false)); buffer_size(*bs.end());
fail();
} }
catch(std::exception const&) catch(std::exception const&)
{ {

89
test/core/buffer_test.hpp Normal file
View File

@@ -0,0 +1,89 @@
//
// Copyright (c) 2013-2016 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_TEST_BUFFER_TEST_HPP
#define BEAST_TEST_BUFFER_TEST_HPP
#include <beast/core/buffer_concepts.hpp>
#include <boost/asio/buffer.hpp>
#include <algorithm>
#include <type_traits>
namespace beast {
namespace test {
template<class ConstBufferSequence>
typename std::enable_if<
is_ConstBufferSequence<ConstBufferSequence>::value,
std::size_t>::type
buffer_count(ConstBufferSequence const& buffers)
{
return std::distance(buffers.begin(), buffers.end());
}
template<class ConstBufferSequence>
typename std::enable_if<
is_ConstBufferSequence<ConstBufferSequence>::value,
std::size_t>::type
size_pre(ConstBufferSequence const& buffers)
{
std::size_t n = 0;
for(auto it = buffers.begin(); it != buffers.end(); ++it)
{
typename ConstBufferSequence::const_iterator it0(std::move(it));
typename ConstBufferSequence::const_iterator it1(it0);
typename ConstBufferSequence::const_iterator it2;
it2 = it1;
n += boost::asio::buffer_size(*it2);
it = std::move(it2);
}
return n;
}
template<class ConstBufferSequence>
typename std::enable_if<
is_ConstBufferSequence<ConstBufferSequence>::value,
std::size_t>::type
size_post(ConstBufferSequence const& buffers)
{
std::size_t n = 0;
for(auto it = buffers.begin(); it != buffers.end(); it++)
n += boost::asio::buffer_size(*it);
return n;
}
template<class ConstBufferSequence>
typename std::enable_if<
is_ConstBufferSequence<ConstBufferSequence>::value,
std::size_t>::type
size_rev_pre(ConstBufferSequence const& buffers)
{
std::size_t n = 0;
for(auto it = buffers.end(); it != buffers.begin();)
n += boost::asio::buffer_size(*--it);
return n;
}
template<class ConstBufferSequence>
typename std::enable_if<
is_ConstBufferSequence<ConstBufferSequence>::value,
std::size_t>::type
size_rev_post(ConstBufferSequence const& buffers)
{
std::size_t n = 0;
for(auto it = buffers.end(); it != buffers.begin();)
{
it--;
n += boost::asio::buffer_size(*it);
}
return n;
}
} // test
} // beast
#endif

View File

@@ -8,6 +8,8 @@
// Test that header file is self-contained. // Test that header file is self-contained.
#include <beast/core/consuming_buffers.hpp> #include <beast/core/consuming_buffers.hpp>
#include "buffer_test.hpp"
#include <beast/core/to_string.hpp>
#include <beast/unit_test/suite.hpp> #include <beast/unit_test/suite.hpp>
#include <boost/asio/buffer.hpp> #include <boost/asio/buffer.hpp>
#include <string> #include <string>
@@ -17,22 +19,25 @@ namespace beast {
class consuming_buffers_test : public beast::unit_test::suite class consuming_buffers_test : public beast::unit_test::suite
{ {
public: public:
template<class ConstBufferSequence> template<class Buffers1, class Buffers2>
static static
std::string bool
to_string(ConstBufferSequence const& bs) eq(Buffers1 const& lhs, Buffers2 const& rhs)
{ {
using boost::asio::buffer_cast; return to_string(lhs) == to_string(rhs);
using boost::asio::buffer_size;
std::string s;
s.reserve(buffer_size(bs));
for(auto const& b : bs)
s.append(buffer_cast<char const*>(b),
buffer_size(b));
return s;
} }
void testBuffers() template<class ConstBufferSequence>
void
expect_size(std::size_t n, ConstBufferSequence const& buffers)
{
expect(test::size_pre(buffers) == n);
expect(test::size_post(buffers) == n);
expect(test::size_rev_pre(buffers) == n);
expect(test::size_rev_post(buffers) == n);
}
void testMatrix()
{ {
using boost::asio::buffer; using boost::asio::buffer;
using boost::asio::const_buffer; using boost::asio::const_buffer;
@@ -54,16 +59,23 @@ public:
const_buffer{&buf[i+j], k}}}; const_buffer{&buf[i+j], k}}};
consuming_buffers<decltype(bs)> cb(bs); consuming_buffers<decltype(bs)> cb(bs);
expect(to_string(cb) == s); expect(to_string(cb) == s);
expect_size(s.size(), cb);
cb.consume(0); cb.consume(0);
expect(eq(cb, consumed_buffers(bs, 0)));
expect(to_string(cb) == s); expect(to_string(cb) == s);
expect_size(s.size(), cb);
cb.consume(x); cb.consume(x);
expect(to_string(cb) == s.substr(x)); expect(to_string(cb) == s.substr(x));
expect(eq(cb, consumed_buffers(bs, x)));
cb.consume(y); cb.consume(y);
expect(to_string(cb) == s.substr(x+y)); expect(to_string(cb) == s.substr(x+y));
expect(eq(cb, consumed_buffers(bs, x+y)));
cb.consume(z); cb.consume(z);
expect(to_string(cb) == ""); expect(to_string(cb) == "");
expect(eq(cb, consumed_buffers(bs, x+y+z)));
cb.consume(1); cb.consume(1);
expect(to_string(cb) == ""); expect(to_string(cb) == "");
expect(eq(cb, consumed_buffers(bs, x+y+z)));
} }
}}}} }}}}
} }
@@ -94,7 +106,7 @@ public:
void run() override void run() override
{ {
testBuffers(); testMatrix();
testNullBuffers(); testNullBuffers();
testIterator(); testIterator();
} }

View File

@@ -33,20 +33,20 @@ public:
return s; return s;
} }
void testBuffers() template<class BufferType>
void testMatrix()
{ {
using boost::asio::buffer_size; using boost::asio::buffer_size;
using boost::asio::const_buffer; std::string s = "Hello, world";
std::string const s = "Hello, world";
expect(s.size() == 12); expect(s.size() == 12);
for(std::size_t x = 1; x < 4; ++x) { for(std::size_t x = 1; x < 4; ++x) {
for(std::size_t y = 1; y < 4; ++y) { for(std::size_t y = 1; y < 4; ++y) {
std::size_t z = s.size() - (x + y); std::size_t z = s.size() - (x + y);
{ {
std::array<const_buffer, 3> bs{{ std::array<BufferType, 3> bs{{
const_buffer{&s[0], x}, BufferType{&s[0], x},
const_buffer{&s[x], y}, BufferType{&s[x], y},
const_buffer{&s[x+y], z}}}; BufferType{&s[x+y], z}}};
for(std::size_t i = 0; i <= s.size() + 1; ++i) for(std::size_t i = 0; i <= s.size() + 1; ++i)
{ {
auto pb = prepare_buffers(i, bs); auto pb = prepare_buffers(i, bs);
@@ -104,7 +104,8 @@ public:
void run() override void run() override
{ {
testBuffers(); testMatrix<boost::asio::const_buffer>();
testMatrix<boost::asio::mutable_buffer>();
testNullBuffers(); testNullBuffers();
testIterator(); testIterator();
} }

View File

@@ -30,6 +30,14 @@ public:
h.insert(std::to_string(i), i); h.insert(std::to_string(i), i);
} }
template<class U, class V>
static
void
self_assign(U& u, V&& v)
{
u = std::forward<V>(v);
}
void testHeaders() void testHeaders()
{ {
bh h1; bh h1;
@@ -47,12 +55,23 @@ public:
bh h3(std::move(h1)); bh h3(std::move(h1));
expect(h3.size() == 2); expect(h3.size() == 2);
expect(h1.size() == 0); expect(h1.size() == 0);
h2 = std::move(h2); self_assign(h3, std::move(h3));
expect(h3.size() == 2);
expect(h2.erase("Not-Present") == 0);
}
void testRFC2616()
{
bh h;
h.insert("a", "x");
h.insert("a", "y");
expect(h["a"] == "x,y");
} }
void run() override void run() override
{ {
testHeaders(); testHeaders();
testRFC2616();
} }
}; };

View File

@@ -49,6 +49,7 @@ public:
cb_req_checker, cb_res_checker>::type cb_req_checker, cb_res_checker>::type
{ {
bool start = false;
bool field = false; bool field = false;
bool value = false; bool value = false;
bool headers = false; bool headers = false;
@@ -58,6 +59,10 @@ public:
private: private:
friend class basic_parser_v1<isRequest, cb_checker<isRequest>>; friend class basic_parser_v1<isRequest, cb_checker<isRequest>>;
void on_start(error_code&)
{
this->start = true;
}
void on_method(boost::string_ref const&, error_code&) void on_method(boost::string_ref const&, error_code&)
{ {
this->method = true; this->method = true;
@@ -101,68 +106,6 @@ public:
} }
}; };
template<bool isRequest>
struct cb_fail
: public basic_parser_v1<isRequest, cb_fail<isRequest>>
{
std::size_t n_;
void fail(error_code& ec)
{
if(n_ > 0)
--n_;
if(! n_)
ec = boost::system::errc::make_error_code(
boost::system::errc::invalid_argument);
}
private:
friend class basic_parser_v1<isRequest, cb_checker<isRequest>>;
void on_method(boost::string_ref const&, error_code& ec)
{
fail(ec);
}
void on_uri(boost::string_ref const&, error_code& ec)
{
fail(ec);
}
void on_reason(boost::string_ref const&, error_code& ec)
{
fail(ec);
}
void on_request(error_code& ec)
{
fail(ec);
}
void on_response(error_code& ec)
{
fail(ec);
}
void on_field(boost::string_ref const&, error_code& ec)
{
fail(ec);
}
void on_value(boost::string_ref const&, error_code& ec)
{
fail(ec);
}
int on_headers(error_code& ec)
{
fail(ec);
return 0;
}
void on_body(boost::string_ref const&, error_code& ec)
{
fail(ec);
}
void on_complete(error_code& ec)
{
fail(ec);
}
};
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
static static
@@ -238,99 +181,6 @@ public:
} }
}; };
void
testFail()
{
using boost::asio::buffer;
{
std::string const s =
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*";
static std::size_t constexpr limit = 100;
std::size_t n = 1;
for(; n < limit; ++n)
{
error_code ec;
basic_parser_v1<true, cb_fail<true>> p;
p.write(buffer(s), ec);
if(! ec)
break;
}
expect(n < limit);
}
{
std::string const s =
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*";
static std::size_t constexpr limit = 100;
std::size_t n = 1;
for(; n < limit; ++n)
{
error_code ec;
basic_parser_v1<false, cb_fail<false>> p;
p.write(buffer(s), ec);
if(! ec)
break;
}
expect(n < limit);
}
}
void
testCallbacks()
{
using boost::asio::buffer;
{
cb_checker<true> p;
error_code ec;
std::string const s =
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*";
p.write(buffer(s), ec);
if( expect(! ec))
{
expect(p.method);
expect(p.uri);
expect(p.request);
expect(p.field);
expect(p.value);
expect(p.headers);
expect(p.body);
expect(p.complete);
}
}
{
cb_checker<false> p;
error_code ec;
std::string const s =
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*";
p.write(buffer(s), ec);
if( expect(! ec))
{
expect(p.reason);
expect(p.response);
expect(p.field);
expect(p.value);
expect(p.headers);
expect(p.body);
expect(p.complete);
}
}
}
// Parse the entire input buffer as a valid message, // Parse the entire input buffer as a valid message,
// then parse in two pieces of all possible lengths. // then parse in two pieces of all possible lengths.
// //
@@ -339,13 +189,22 @@ public:
parse(boost::string_ref const& m, F&& f) parse(boost::string_ref const& m, F&& f)
{ {
using boost::asio::buffer; using boost::asio::buffer;
for(;;)
{ {
error_code ec; error_code ec;
Parser p; Parser p;
p.write(buffer(m.data(), m.size()), ec); p.write(buffer(m.data(), m.size()), ec);
if(! expect(! ec, ec.message()))
break;
if(p.needs_eof())
{
p.write_eof(ec);
if(! expect(! ec, ec.message()))
break;
}
if(expect(p.complete())) if(expect(p.complete()))
if(expect(! ec, ec.message()))
f(p); f(p);
break;
} }
for(std::size_t i = 1; i < m.size() - 1; ++i) for(std::size_t i = 1; i < m.size() - 1; ++i)
{ {
@@ -354,18 +213,21 @@ public:
p.write(buffer(&m[0], i), ec); p.write(buffer(&m[0], i), ec);
if(! expect(! ec, ec.message())) if(! expect(! ec, ec.message()))
continue; continue;
if(p.complete()) if(! p.complete())
{
f(p);
}
else
{ {
p.write(buffer(&m[i], m.size() - i), ec); p.write(buffer(&m[i], m.size() - i), ec);
if(! expect(! ec, ec.message())) if(! expect(! ec, ec.message()))
continue; continue;
expect(p.complete());
f(p);
} }
if(! p.complete() && p.needs_eof())
{
p.write_eof(ec);
if(! expect(! ec, ec.message()))
continue;
}
if(! expect(p.complete()))
continue;
f(p);
} }
} }
@@ -403,8 +265,6 @@ public:
} }
} }
//--------------------------------------------------------------------------
// Parse a valid message with expected version // Parse a valid message with expected version
// //
template<bool isRequest> template<bool isRequest>
@@ -432,6 +292,60 @@ public:
}); });
} }
//--------------------------------------------------------------------------
// Check all callbacks invoked
void
testCallbacks()
{
using boost::asio::buffer;
{
cb_checker<true> p;
error_code ec;
std::string const s =
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*";
p.write(buffer(s), ec);
if(expect(! ec))
{
expect(p.start);
expect(p.method);
expect(p.uri);
expect(p.request);
expect(p.field);
expect(p.value);
expect(p.headers);
expect(p.body);
expect(p.complete);
}
}
{
cb_checker<false> p;
error_code ec;
std::string const s =
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*";
p.write(buffer(s), ec);
if(expect(! ec))
{
expect(p.start);
expect(p.reason);
expect(p.response);
expect(p.field);
expect(p.value);
expect(p.headers);
expect(p.body);
expect(p.complete);
}
}
}
void void
testVersion() testVersion()
{ {
@@ -446,10 +360,21 @@ public:
parse_ev<true>("GET / HTTP/0.1000\r\n\r\n", parse_error::bad_version); parse_ev<true>("GET / HTTP/0.1000\r\n\r\n", parse_error::bad_version);
parse_ev<true>("GET / HTTP/99999999999999999999.0\r\n\r\n", parse_error::bad_version); parse_ev<true>("GET / HTTP/99999999999999999999.0\r\n\r\n", parse_error::bad_version);
parse_ev<true>("GET / HTTP/0.99999999999999999999\r\n\r\n", parse_error::bad_version); parse_ev<true>("GET / HTTP/0.99999999999999999999\r\n\r\n", parse_error::bad_version);
version <false>("HTTP/0.0 200 OK\r\n\r\n", 0, 0);
version <false>("HTTP/0.1 200 OK\r\n\r\n", 0, 1);
version <false>("HTTP/0.9 200 OK\r\n\r\n", 0, 9);
version <false>("HTTP/1.0 200 OK\r\n\r\n", 1, 0);
version <false>("HTTP/1.1 200 OK\r\n\r\n", 1, 1);
version <false>("HTTP/9.9 200 OK\r\n\r\n", 9, 9);
version <false>("HTTP/999.999 200 OK\r\n\r\n", 999, 999);
parse_ev<false>("HTTP/1000.0 200 OK\r\n\r\n", parse_error::bad_version);
parse_ev<false>("HTTP/0.1000 200 OK\r\n\r\n", parse_error::bad_version);
parse_ev<false>("HTTP/99999999999999999999.0 200 OK\r\n\r\n", parse_error::bad_version);
parse_ev<false>("HTTP/0.99999999999999999999 200 OK\r\n\r\n", parse_error::bad_version);
} }
void void testConnection(std::string const& token,
testConnection(std::string const& token,
std::uint8_t flag) std::uint8_t flag)
{ {
checkf("GET / HTTP/1.1\r\nConnection:" + token + "\r\n\r\n", flag); checkf("GET / HTTP/1.1\r\nConnection:" + token + "\r\n\r\n", flag);
@@ -472,8 +397,7 @@ public:
checkf("GET / HTTP/1.1\r\nConnection: X," + token + "\t\r\n\r\n", flag); checkf("GET / HTTP/1.1\r\nConnection: X," + token + "\t\r\n\r\n", flag);
} }
void void testContentLength()
testContentLength()
{ {
std::size_t const length = 0; std::size_t const length = 0;
std::string const length_s = std::string const length_s =
@@ -493,8 +417,7 @@ public:
checkf("GET / HTTP/1.1\r\nContent-Length:\t\r\n" "\t"+ length_s + "\r\n\r\n", parse_flag::contentlength); checkf("GET / HTTP/1.1\r\nContent-Length:\t\r\n" "\t"+ length_s + "\r\n\r\n", parse_flag::contentlength);
} }
void void testTransferEncoding()
testTransferEncoding()
{ {
checkf("GET / HTTP/1.1\r\nTransfer-Encoding:chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); checkf("GET / HTTP/1.1\r\nTransfer-Encoding:chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked);
checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked);
@@ -510,17 +433,15 @@ public:
checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\t\r\n" "\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked ); checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\t\r\n" "\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked );
} }
void void testFlags()
testFlags()
{ {
testConnection("keep-alive", testConnection("keep-alive", parse_flag::connection_keep_alive);
parse_flag::connection_keep_alive); testConnection("close", parse_flag::connection_close);
testConnection("upgrade", parse_flag::connection_upgrade);
testConnection("close", checkf("GET / HTTP/1.1\r\nConnection: close, win\r\n\r\n", parse_flag::connection_close);
parse_flag::connection_close); checkf("GET / HTTP/1.1\r\nConnection: keep-alive, win\r\n\r\n", parse_flag::connection_keep_alive);
checkf("GET / HTTP/1.1\r\nConnection: upgrade, win\r\n\r\n", parse_flag::connection_upgrade);
testConnection("upgrade",
parse_flag::connection_upgrade);
testContentLength(); testContentLength();
@@ -537,11 +458,46 @@ public:
"GET / HTTP/1.1\r\n" "GET / HTTP/1.1\r\n"
"Transfer-Encoding:chunked\r\n" "Transfer-Encoding:chunked\r\n"
"Content-Length: 0\r\n" "Content-Length: 0\r\n"
"Proxy-Connection: close\r\n"
"\r\n", parse_error::illegal_content_length); "\r\n", parse_error::illegal_content_length);
} }
void void testHeaders()
testUpgrade() {
parse<null_parser<true>>(
"GET / HTTP/1.0\r\n"
"Conniving: yes\r\n"
"Content-Lengthening: yes\r\n"
"Transfer-Encoding: deflate\r\n"
"Connection: sweep\r\n"
"\r\n",
[](null_parser<true> const&)
{
});
parse_ev<true>(
"GET / HTTP/1.0\r\n"
"Content-Length: 1\r\n"
"Content-Length: 2\r\n"
"\r\n",
parse_error::bad_content_length);
parse_ev<true>(
"GET / HTTP/1.0\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"fffffffffffffffff\r\n"
"0\r\n\r\n",
parse_error::bad_content_length);
parse_ev<true>("GET / HTTP/1.0\r\nContent-Length: 1e9\r\n\r\n",
parse_error::bad_content_length);
parse_ev<true>("GET / HTTP/1.0\r\nContent-Length: 99999999999999999999999\r\n\r\n",
parse_error::bad_content_length);
}
void testUpgrade()
{ {
using boost::asio::buffer; using boost::asio::buffer;
null_parser<true> p; null_parser<true> p;
@@ -576,11 +532,10 @@ public:
void testInvalidMatrix() void testInvalidMatrix()
{ {
using boost::asio::buffer; using boost::asio::buffer;
static std::size_t constexpr limit = 200;
std::string s; std::string s;
std::size_t n; std::size_t n;
for(n = 0; n < limit; ++n) for(n = 0;; ++n)
{ {
// Create a request and set one octet to an invalid char // Create a request and set one octet to an invalid char
s = s =
@@ -590,11 +545,8 @@ public:
"Content-Length: 00\r\n" "Content-Length: 00\r\n"
"\r\n"; "\r\n";
auto const len = s.size(); auto const len = s.size();
if(n >= s.size()) if(n < len)
{ {
pass();
break;
}
s[n] = 0; s[n] = 0;
for(std::size_t m = 1; m < len - 1; ++m) for(std::size_t m = 1; m < len - 1; ++m)
{ {
@@ -610,29 +562,32 @@ public:
expect(ec); expect(ec);
} }
} }
expect(n < limit); else
{
null_parser<true> p;
error_code ec;
p.write(buffer(s.data(), s.size()), ec);
expect(! ec, ec.message());
break;
}
}
for(n = 0; n < limit; ++n) for(n = 0;; ++n)
{ {
// Create a response and set one octet to an invalid char // Create a response and set one octet to an invalid char
s = s =
"HTTP/1.1 200 OK\r\n" "HTTP/1.1 200 OK\r\n"
"Server: test\r\n" "Server: test\r\n"
"Transer-Encoding: chunked\r\n" "Transfer-Encoding: chunked\r\n"
"\r\n" "\r\n"
"10\r\n"
"****************\r\n"
"0\r\n\r\n"; "0\r\n\r\n";
auto const len = s.size(); auto const len = s.size();
if(n >= s.size()) if(n < len)
{ {
pass();
break;
}
s[n] = 0; s[n] = 0;
for(std::size_t m = 1; m < len - 1; ++m) for(std::size_t m = 1; m < len - 1; ++m)
{ {
null_parser<true> p; null_parser<false> p;
error_code ec; error_code ec;
p.write(buffer(s.data(), m), ec); p.write(buffer(s.data(), m), ec);
if(ec) if(ec)
@@ -644,7 +599,15 @@ public:
expect(ec); expect(ec);
} }
} }
expect(n < limit); else
{
null_parser<false> p;
error_code ec;
p.write(buffer(s.data(), s.size()), ec);
expect(! ec, ec.message());
break;
}
}
} }
void void
@@ -754,24 +717,30 @@ public:
expect(p.body == body); expect(p.body == body);
}; };
}; };
parse<test_parser<true>>( parse<test_parser<true>>(
"GET / HTTP/1.1\r\nContent-Length: 1\r\n\r\n123", match("1")); "GET / HTTP/1.1\r\nContent-Length: 1\r\n\r\n123", match("1"));
parse<test_parser<true>>( parse<test_parser<true>>(
"GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\n123", match("123")); "GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\n123", match("123"));
parse<test_parser<true>>( parse<test_parser<true>>(
"GET / HTTP/1.1\r\nContent-Length: 0\r\n\r\n", match("")); "GET / HTTP/1.1\r\nContent-Length: 0\r\n\r\n", match(""));
parse<test_parser<true>>( parse<test_parser<true>>(
"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
"1\r\n" "1\r\n"
"a\r\n" "a\r\n"
"0\r\n" "0\r\n"
"\r\n", match("a")); "\r\n", match("a"));
parse<test_parser<true>>( parse<test_parser<true>>(
"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
"2\r\n" "2\r\n"
"ab\r\n" "ab\r\n"
"0\r\n" "0\r\n"
"\r\n", match("ab")); "\r\n", match("ab"));
parse<test_parser<true>>( parse<test_parser<true>>(
"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
"2\r\n" "2\r\n"
@@ -780,6 +749,7 @@ public:
"c\r\n" "c\r\n"
"0\r\n" "0\r\n"
"\r\n", match("abc")); "\r\n", match("abc"));
parse<test_parser<true>>( parse<test_parser<true>>(
"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
"10\r\n" "10\r\n"
@@ -790,10 +760,10 @@ public:
void run() override void run() override
{ {
testFail();
testCallbacks(); testCallbacks();
testVersion(); testVersion();
testFlags(); testFlags();
testHeaders();
testUpgrade(); testUpgrade();
testBad(); testBad();
testInvalidMatrix(); testInvalidMatrix();

View File

@@ -9,126 +9,34 @@
#include <beast/http/message_v1.hpp> #include <beast/http/message_v1.hpp>
#include <beast/unit_test/suite.hpp> #include <beast/unit_test/suite.hpp>
#include <beast/unit_test/thread.hpp> #include <beast/http/empty_body.hpp>
#include <beast/core/placeholders.hpp>
#include <beast/core/streambuf.hpp>
#include <beast/http.hpp>
#include <boost/asio.hpp>
namespace beast { namespace beast {
namespace http { namespace http {
class sync_echo_http_server
{
public:
using endpoint_type = boost::asio::ip::tcp::endpoint;
using address_type = boost::asio::ip::address;
using socket_type = boost::asio::ip::tcp::socket;
private:
unit_test::suite& suite_;
boost::asio::io_service ios_;
socket_type sock_;
boost::asio::ip::tcp::acceptor acceptor_;
unit_test::thread thread_;
public:
sync_echo_http_server(
endpoint_type ep, unit_test::suite& suite)
: suite_(suite)
, sock_(ios_)
, acceptor_(ios_)
{
error_code ec;
acceptor_.open(ep.protocol(), ec);
maybe_throw(ec, "open");
acceptor_.bind(ep, ec);
maybe_throw(ec, "bind");
acceptor_.listen(
boost::asio::socket_base::max_connections, ec);
maybe_throw(ec, "listen");
acceptor_.async_accept(sock_,
std::bind(&sync_echo_http_server::on_accept, this,
beast::asio::placeholders::error));
thread_ = unit_test::thread(suite_,
[&]
{
ios_.run();
});
}
~sync_echo_http_server()
{
error_code ec;
ios_.dispatch(
[&]{ acceptor_.close(ec); });
thread_.join();
}
private:
void
fail(error_code ec, std::string what)
{
suite_.log <<
what << ": " << ec.message();
}
void
maybe_throw(error_code ec, std::string what)
{
if(ec &&
ec != boost::asio::error::operation_aborted)
{
fail(ec, what);
throw ec;
}
}
void
on_accept(error_code ec)
{
if(ec == boost::asio::error::operation_aborted)
return;
maybe_throw(ec, "accept");
std::thread{&sync_echo_http_server::do_client, this,
std::move(sock_), boost::asio::io_service::work{
sock_.get_io_service()}}.detach();
acceptor_.async_accept(sock_,
std::bind(&sync_echo_http_server::on_accept, this,
beast::asio::placeholders::error));
}
void
do_client(socket_type sock, boost::asio::io_service::work)
{
error_code ec;
streambuf rb;
for(;;)
{
request_v1<string_body> req;
read(sock, rb, req, ec);
if(ec)
break;
response_v1<string_body> resp;
resp.status = 100;
resp.reason = "OK";
resp.version = req.version;
resp.body = "Completed successfully.";
write(sock, resp, ec);
if(ec)
break;
}
}
};
class message_v1_test : public beast::unit_test::suite class message_v1_test : public beast::unit_test::suite
{ {
public: public:
using endpoint_type = boost::asio::ip::tcp::endpoint; void testFreeFunctions()
using address_type = boost::asio::ip::address; {
using socket_type = boost::asio::ip::tcp::socket; {
request_v1<empty_body> m;
m.method = "GET";
m.url = "/";
m.version = 11;
m.headers.insert("Upgrade", "test");
expect(! is_upgrade(m));
void testFunctions() prepare(m, connection::upgrade);
expect(is_upgrade(m));
expect(m.headers["Connection"] == "upgrade");
m.version = 10;
expect(! is_upgrade(m));
}
}
void testPrepare()
{ {
request_v1<empty_body> m; request_v1<empty_body> m;
m.version = 10; m.version = 10;
@@ -164,45 +72,10 @@ public:
} }
} }
void
syncEcho(endpoint_type ep)
{
boost::asio::io_service ios;
socket_type sock(ios);
sock.connect(ep);
streambuf rb;
{
request_v1<string_body> req;
req.method = "GET";
req.url = "/";
req.version = 11;
req.body = "Beast.HTTP";
req.headers.replace("Host",
ep.address().to_string() + ":" +
std::to_string(ep.port()));
write(sock, req);
}
{
response_v1<string_body> m;
read(sock, rb, m);
}
}
void
testAsio()
{
endpoint_type ep{
address_type::from_string("127.0.0.1"), 6000};
sync_echo_http_server s(ep, *this);
syncEcho(ep);
}
void run() override void run() override
{ {
testFunctions(); testFreeFunctions();
testAsio(); testPrepare();
pass();
} }
}; };

View File

@@ -8,6 +8,7 @@
// Test that header file is self-contained. // Test that header file is self-contained.
#include <beast/http/read.hpp> #include <beast/http/read.hpp>
#include <beast/http/headers.hpp>
#include <beast/http/streambuf_body.hpp> #include <beast/http/streambuf_body.hpp>
#include <beast/test/fail_stream.hpp> #include <beast/test/fail_stream.hpp>
#include <beast/test/string_stream.hpp> #include <beast/test/string_stream.hpp>
@@ -23,6 +24,227 @@ class read_test
, public test::enable_yield_to , public test::enable_yield_to
{ {
public: public:
template<bool isRequest>
class fail_parser
: public basic_parser_v1<isRequest, fail_parser<isRequest>>
{
test::fail_counter& fc_;
public:
template<class... Args>
explicit
fail_parser(test::fail_counter& fc, Args&&... args)
: fc_(fc)
{
}
void on_start(error_code& ec)
{
fc_.fail(ec);
}
void on_method(boost::string_ref const&, error_code& ec)
{
fc_.fail(ec);
}
void on_uri(boost::string_ref const&, error_code& ec)
{
fc_.fail(ec);
}
void on_reason(boost::string_ref const&, error_code& ec)
{
fc_.fail(ec);
}
void on_request(error_code& ec)
{
fc_.fail(ec);
}
void on_response(error_code& ec)
{
fc_.fail(ec);
}
void on_field(boost::string_ref const&, error_code& ec)
{
fc_.fail(ec);
}
void on_value(boost::string_ref const&, error_code& ec)
{
fc_.fail(ec);
}
int on_headers(error_code& ec)
{
fc_.fail(ec);
return 0;
}
void on_body(boost::string_ref const&, error_code& ec)
{
fc_.fail(ec);
}
void on_complete(error_code& ec)
{
fc_.fail(ec);
}
};
template<bool isRequest>
void failMatrix(const char* s, yield_context do_yield)
{
using boost::asio::buffer;
using boost::asio::buffer_copy;
static std::size_t constexpr limit = 100;
std::size_t n;
auto const len = strlen(s);
for(n = 0; n < limit; ++n)
{
streambuf sb;
sb.commit(buffer_copy(
sb.prepare(len), buffer(s, len)));
test::fail_counter fc(n);
test::fail_stream<
test::string_stream> fs{fc, ios_, ""};
fail_parser<isRequest> p(fc);
error_code ec;
parse(fs, sb, p, ec);
if(! ec)
break;
}
expect(n < limit);
for(n = 0; n < limit; ++n)
{
static std::size_t constexpr pre = 10;
streambuf sb;
sb.commit(buffer_copy(
sb.prepare(pre), buffer(s, pre)));
test::fail_counter fc(n);
test::fail_stream<test::string_stream> fs{
fc, ios_, std::string{s + pre, len - pre}};
fail_parser<isRequest> p(fc);
error_code ec;
parse(fs, sb, p, ec);
if(! ec)
break;
}
expect(n < limit);
for(n = 0; n < limit; ++n)
{
streambuf sb;
sb.commit(buffer_copy(
sb.prepare(len), buffer(s, len)));
test::fail_counter fc(n);
test::fail_stream<
test::string_stream> fs{fc, ios_, ""};
fail_parser<isRequest> p(fc);
error_code ec;
async_parse(fs, sb, p, do_yield[ec]);
if(! ec)
break;
}
expect(n < limit);
for(n = 0; n < limit; ++n)
{
static std::size_t constexpr pre = 10;
streambuf sb;
sb.commit(buffer_copy(
sb.prepare(pre), buffer(s, pre)));
test::fail_counter fc(n);
test::fail_stream<test::string_stream> fs{
fc, ios_, std::string{s + pre, len - pre}};
fail_parser<isRequest> p(fc);
error_code ec;
async_parse(fs, sb, p, do_yield[ec]);
if(! ec)
break;
}
expect(n < limit);
}
void testThrow()
{
try
{
streambuf sb;
test::string_stream ss(ios_, "GET / X");
parser_v1<true, streambuf_body, headers> p;
parse(ss, sb, p);
fail();
}
catch(std::exception const&)
{
pass();
}
}
void testFailures(yield_context do_yield)
{
char const* req[] = {
"GET / HTTP/1.0\r\n"
"Host: localhost\r\n"
"User-Agent: test\r\n"
"Empty:\r\n"
"\r\n"
,
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"User-Agent: test\r\n"
"Content-Length: 2\r\n"
"\r\n"
"**"
,
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"User-Agent: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"10\r\n"
"****************\r\n"
"0\r\n\r\n"
,
nullptr
};
char const* res[] = {
"HTTP/1.0 200 OK\r\n"
"Server: test\r\n"
"\r\n"
,
"HTTP/1.0 200 OK\r\n"
"Server: test\r\n"
"\r\n"
"***"
,
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 3\r\n"
"\r\n"
"***"
,
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"10\r\n"
"****************\r\n"
"0\r\n\r\n"
,
nullptr
};
for(std::size_t i = 0; req[i]; ++i)
failMatrix<true>(req[i], do_yield);
for(std::size_t i = 0; res[i]; ++i)
failMatrix<false>(res[i], do_yield);
}
void testRead(yield_context do_yield) void testRead(yield_context do_yield)
{ {
static std::size_t constexpr limit = 100; static std::size_t constexpr limit = 100;
@@ -30,7 +252,6 @@ public:
for(n = 0; n < limit; ++n) for(n = 0; n < limit; ++n)
{ {
streambuf sb;
test::fail_stream<test::string_stream> fs(n, ios_, test::fail_stream<test::string_stream> fs(n, ios_,
"GET / HTTP/1.1\r\n" "GET / HTTP/1.1\r\n"
"Host: localhost\r\n" "Host: localhost\r\n"
@@ -41,6 +262,7 @@ public:
request_v1<streambuf_body> m; request_v1<streambuf_body> m;
try try
{ {
streambuf sb;
read(fs, sb, m); read(fs, sb, m);
break; break;
} }
@@ -52,7 +274,6 @@ public:
for(n = 0; n < limit; ++n) for(n = 0; n < limit; ++n)
{ {
streambuf sb;
test::fail_stream<test::string_stream> fs(n, ios_, test::fail_stream<test::string_stream> fs(n, ios_,
"GET / HTTP/1.1\r\n" "GET / HTTP/1.1\r\n"
"Host: localhost\r\n" "Host: localhost\r\n"
@@ -62,6 +283,7 @@ public:
); );
request_v1<streambuf_body> m; request_v1<streambuf_body> m;
error_code ec; error_code ec;
streambuf sb;
read(fs, sb, m, ec); read(fs, sb, m, ec);
if(! ec) if(! ec)
break; break;
@@ -70,7 +292,6 @@ public:
for(n = 0; n < limit; ++n) for(n = 0; n < limit; ++n)
{ {
streambuf sb;
test::fail_stream<test::string_stream> fs(n, ios_, test::fail_stream<test::string_stream> fs(n, ios_,
"GET / HTTP/1.1\r\n" "GET / HTTP/1.1\r\n"
"Host: localhost\r\n" "Host: localhost\r\n"
@@ -80,6 +301,7 @@ public:
); );
request_v1<streambuf_body> m; request_v1<streambuf_body> m;
error_code ec; error_code ec;
streambuf sb;
async_read(fs, sb, m, do_yield[ec]); async_read(fs, sb, m, do_yield[ec]);
if(! ec) if(! ec)
break; break;
@@ -87,10 +309,38 @@ public:
expect(n < limit); expect(n < limit);
} }
void testEof(yield_context do_yield)
{
{
streambuf sb;
test::string_stream ss(ios_, "");
parser_v1<true, streambuf_body, headers> p;
error_code ec;
parse(ss, sb, p, ec);
expect(ec == boost::asio::error::eof);
}
{
streambuf sb;
test::string_stream ss(ios_, "");
parser_v1<true, streambuf_body, headers> p;
error_code ec;
async_parse(ss, sb, p, do_yield[ec]);
expect(ec == boost::asio::error::eof);
}
}
void run() override void run() override
{ {
testThrow();
yield_to(std::bind(&read_test::testFailures,
this, std::placeholders::_1));
yield_to(std::bind(&read_test::testRead, yield_to(std::bind(&read_test::testRead,
this, std::placeholders::_1)); this, std::placeholders::_1));
yield_to(std::bind(&read_test::testEof,
this, std::placeholders::_1));
} }
}; };

View File

@@ -7,3 +7,38 @@
// Test that header file is self-contained. // Test that header file is self-contained.
#include <beast/http/streambuf_body.hpp> #include <beast/http/streambuf_body.hpp>
#include <beast/core/to_string.hpp>
#include <beast/http/headers.hpp>
#include <beast/http/parser_v1.hpp>
#include <beast/http/read.hpp>
#include <beast/test/string_stream.hpp>
#include <beast/unit_test/suite.hpp>
namespace beast {
namespace http {
class streambuf_body_test : public beast::unit_test::suite
{
boost::asio::io_service ios_;
public:
void run() override
{
test::string_stream ss(ios_,
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 3\r\n"
"\r\n"
"xyz");
parser_v1<false, streambuf_body, headers> p;
streambuf sb;
parse(ss, sb, p);
expect(to_string(p.get().body.data()) == "xyz");
}
};
BEAST_DEFINE_TESTSUITE(streambuf_body,http,beast);
} // http
} // beast

View File

@@ -14,9 +14,9 @@ add_executable (websocket-tests
rfc6455.cpp rfc6455.cpp
stream.cpp stream.cpp
teardown.cpp teardown.cpp
utf8_checker.cpp
detail/frame.cpp detail/frame.cpp
detail/mask.cpp detail/mask.cpp
detail/utf8_checker.cpp
) )
if (NOT WIN32) if (NOT WIN32)

View File

@@ -32,7 +32,7 @@ operator==(frame_header const& lhs, frame_header const& rhs)
class frame_test : public beast::unit_test::suite class frame_test : public beast::unit_test::suite
{ {
public: public:
void testValidOpcode() void testCloseCodes()
{ {
expect(! is_valid(0)); expect(! is_valid(0));
expect(! is_valid(1)); expect(! is_valid(1));
@@ -43,6 +43,8 @@ public:
expect(! is_valid(1016)); expect(! is_valid(1016));
expect(! is_valid(2000)); expect(! is_valid(2000));
expect(! is_valid(2999)); expect(! is_valid(2999));
expect(is_valid(1000));
expect(is_valid(1002));
expect(is_valid(3000)); expect(is_valid(3000));
expect(is_valid(4000)); expect(is_valid(4000));
expect(is_valid(5000)); expect(is_valid(5000));
@@ -220,7 +222,7 @@ public:
void run() override void run() override
{ {
testValidOpcode(); testCloseCodes();
testFrameHeader(); testFrameHeader();
testBadFrameHeaders(); testBadFrameHeaders();
pass(); pass();

View File

@@ -8,12 +8,14 @@
// Test that header file is self-contained. // Test that header file is self-contained.
#include <beast/websocket/detail/utf8_checker.hpp> #include <beast/websocket/detail/utf8_checker.hpp>
#include <beast/core/consuming_buffers.hpp>
#include <beast/core/streambuf.hpp> #include <beast/core/streambuf.hpp>
#include <beast/unit_test/suite.hpp> #include <beast/unit_test/suite.hpp>
#include <array> #include <array>
namespace beast { namespace beast {
namespace websocket { namespace websocket {
namespace detail {
class utf8_checker_test : public beast::unit_test::suite class utf8_checker_test : public beast::unit_test::suite
{ {
@@ -21,7 +23,7 @@ public:
void void
testOneByteSequence() testOneByteSequence()
{ {
detail::utf8_checker utf8; utf8_checker utf8;
std::array<std::uint8_t, 256> const buf = std::array<std::uint8_t, 256> const buf =
([]() ([]()
{ {
@@ -50,7 +52,7 @@ public:
void void
testTwoByteSequence() testTwoByteSequence()
{ {
detail::utf8_checker utf8; utf8_checker utf8;
std::uint8_t buf[2]; std::uint8_t buf[2];
for(auto i = 194; i <= 223; ++i) for(auto i = 194; i <= 223; ++i)
{ {
@@ -84,7 +86,7 @@ public:
void void
testThreeByteSequence() testThreeByteSequence()
{ {
detail::utf8_checker utf8; utf8_checker utf8;
std::uint8_t buf[3]; std::uint8_t buf[3];
for (auto i = 224; i <= 239; ++i) for (auto i = 224; i <= 239; ++i)
{ {
@@ -140,7 +142,8 @@ public:
void void
testFourByteSequence() testFourByteSequence()
{ {
detail::utf8_checker utf8; using boost::asio::const_buffers_1;
utf8_checker utf8;
std::uint8_t buf[4]; std::uint8_t buf[4];
for (auto i = 240; i <= 244; ++i) for (auto i = 240; i <= 244; ++i)
{ {
@@ -163,7 +166,7 @@ public:
{ {
// Fourth byte valid range 128-191 // Fourth byte valid range 128-191
buf[3] = static_cast<std::uint8_t>(n); buf[3] = static_cast<std::uint8_t>(n);
expect(utf8.write(buf, 4)); expect(utf8.write(const_buffers_1{buf, 4}));
expect(utf8.finish()); expect(utf8.finish());
} }
@@ -171,7 +174,7 @@ public:
{ {
// Fourth byte invalid range 0-127 // Fourth byte invalid range 0-127
buf[3] = static_cast<std::uint8_t>(n); buf[3] = static_cast<std::uint8_t>(n);
expect(! utf8.write(buf, 4)); expect(! utf8.write(const_buffers_1{buf, 4}));
} }
for (auto n = 192; n <= 255; ++n) for (auto n = 192; n <= 255; ++n)
@@ -217,12 +220,14 @@ public:
testWithStreamBuffer() testWithStreamBuffer()
{ {
using namespace boost::asio; using namespace boost::asio;
{
// Valid UTF8 encoded text // Valid UTF8 encoded text
std::vector<std::vector<std::uint8_t>> const data{ std::vector<std::vector<std::uint8_t>> const data{{
{0x48,0x65,0x69,0x7A,0xC3,0xB6,0x6C,0x72,0xC3,0xBC,0x63,0x6B, 0x48,0x65,0x69,0x7A,0xC3,0xB6,0x6C,0x72,0xC3,0xBC,0x63,0x6B,
0x73,0x74,0x6F,0xC3,0x9F,0x61,0x62,0x64,0xC3,0xA4,0x6D,0x70, 0x73,0x74,0x6F,0xC3,0x9F,0x61,0x62,0x64,0xC3,0xA4,0x6D,0x70,
0x66,0x75,0x6E,0x67}, 0x66,0x75,0x6E,0x67
{0xCE,0x93,0xCE,0xB1,0xCE,0xB6,0xCE,0xAD,0xCE,0xB5,0xCF,0x82, }, {
0xCE,0x93,0xCE,0xB1,0xCE,0xB6,0xCE,0xAD,0xCE,0xB5,0xCF,0x82,
0x20,0xCE,0xBA,0xCE,0xB1,0xE1,0xBD,0xB6,0x20,0xCE,0xBC,0xCF, 0x20,0xCE,0xBA,0xCE,0xB1,0xE1,0xBD,0xB6,0x20,0xCE,0xBC,0xCF,
0x85,0xCF,0x81,0xCF,0x84,0xCE,0xB9,0xE1,0xBD,0xB2,0xCF,0x82, 0x85,0xCF,0x81,0xCF,0x84,0xCE,0xB9,0xE1,0xBD,0xB2,0xCF,0x82,
0x20,0xCE,0xB4,0xE1,0xBD,0xB2,0xCE,0xBD,0x20,0xCE,0xB8,0xE1, 0x20,0xCE,0xB4,0xE1,0xBD,0xB2,0xCE,0xBD,0x20,0xCE,0xB8,0xE1,
@@ -230,23 +235,34 @@ public:
0x80,0xCE,0xB9,0xE1,0xBD,0xB0,0x20,0xCF,0x83,0xCF,0x84,0xE1, 0x80,0xCE,0xB9,0xE1,0xBD,0xB0,0x20,0xCF,0x83,0xCF,0x84,0xE1,
0xBD,0xB8,0x20,0xCF,0x87,0xCF,0x81,0xCF,0x85,0xCF,0x83,0xCE, 0xBD,0xB8,0x20,0xCF,0x87,0xCF,0x81,0xCF,0x85,0xCF,0x83,0xCE,
0xB1,0xCF,0x86,0xE1,0xBD,0xB6,0x20,0xCE,0xBE,0xCE,0xAD,0xCF, 0xB1,0xCF,0x86,0xE1,0xBD,0xB6,0x20,0xCE,0xBE,0xCE,0xAD,0xCF,
0x86,0xCF,0x89,0xCF,0x84,0xCE,0xBF}, 0x86,0xCF,0x89,0xCF,0x84,0xCE,0xBF
{0xC3,0x81,0x72,0x76,0xC3,0xAD,0x7A,0x74,0xC5,0xB1,0x72,0xC5, }, {
0xC3,0x81,0x72,0x76,0xC3,0xAD,0x7A,0x74,0xC5,0xB1,0x72,0xC5,
0x91,0x20,0x74,0xC3,0xBC,0x6B,0xC3,0xB6,0x72,0x66,0xC3,0xBA, 0x91,0x20,0x74,0xC3,0xBC,0x6B,0xC3,0xB6,0x72,0x66,0xC3,0xBA,
0x72,0xC3,0xB3,0x67,0xC3,0xA9,0x70} 0x72,0xC3,0xB3,0x67,0xC3,0xA9,0x70
}
}; };
detail::utf8_checker utf8; utf8_checker utf8;
for(auto const& s : data) for(auto const& s : data)
{ {
beast::streambuf sb( static std::size_t constexpr size = 8;
s.size() / 4); // Force split across blocks std::size_t n = s.size();
sb.commit(buffer_copy( auto cb = consumed_buffers(
sb.prepare(s.size()), boost::asio::const_buffers_1(
const_buffer(s.data(), s.size()))); s.data(), n), 0);
streambuf sb(size);
while(n)
{
auto const amount = std::min(n, size);
sb.commit(buffer_copy(sb.prepare(amount), cb));
cb.consume(amount);
n -= amount;
}
expect(utf8.write(sb.data())); expect(utf8.write(sb.data()));
expect(utf8.finish()); expect(utf8.finish());
} }
} }
}
void run() override void run() override
{ {
@@ -260,5 +276,6 @@ public:
BEAST_DEFINE_TESTSUITE(utf8_checker,websocket,beast); BEAST_DEFINE_TESTSUITE(utf8_checker,websocket,beast);
} // detail
} // websocket } // websocket
} // beast } // beast

View File

@@ -477,6 +477,88 @@ public:
} }
} }
struct con
{
stream<socket_type> ws;
explicit
con(endpoint_type const& ep, boost::asio::io_service& ios)
: ws(ios)
{
ws.next_layer().connect(ep);
ws.handshake("localhost", "/");
}
};
template<std::size_t N>
class cbuf_helper
{
std::array<std::uint8_t, N> v_;
boost::asio::const_buffer cb_;
public:
using value_type = decltype(cb_);
using const_iterator = value_type const*;
template<class... Vn>
explicit
cbuf_helper(Vn... vn)
: v_({{ static_cast<std::uint8_t>(vn)... }})
, cb_(v_.data(), v_.size())
{
}
const_iterator
begin() const
{
return &cb_;
}
const_iterator
end() const
{
return begin()+1;
}
};
template<class... Vn>
cbuf_helper<sizeof...(Vn)>
cbuf(Vn... vn)
{
return cbuf_helper<sizeof...(Vn)>(vn...);
}
void testClose(endpoint_type const& ep, yield_context do_yield)
{
using boost::asio::buffer;
{
// payload length 1
con c(ep, ios_);
boost::asio::write(c.ws.next_layer(),
cbuf(0x88, 0x81, 0xff, 0xff, 0xff, 0xff, 0x00));
}
{
// invalid close code 1005
con c(ep, ios_);
boost::asio::write(c.ws.next_layer(),
cbuf(0x88, 0x82, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x12));
}
{
// invalid utf8
con c(ep, ios_);
boost::asio::write(c.ws.next_layer(),
cbuf(0x88, 0x86, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x15,
0x0f, 0xd7, 0x73, 0x43));
}
{
// good utf8
con c(ep, ios_);
boost::asio::write(c.ws.next_layer(),
cbuf(0x88, 0x86, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x15,
'u', 't', 'f', '8'));
}
}
void testWriteFrame(endpoint_type const& ep) void testWriteFrame(endpoint_type const& ep)
{ {
for(;;) for(;;)
@@ -528,6 +610,9 @@ public:
yield_to(std::bind(&stream_test::testMask, yield_to(std::bind(&stream_test::testMask,
this, ep, std::placeholders::_1)); this, ep, std::placeholders::_1));
yield_to(std::bind(&stream_test::testClose,
this, ep, std::placeholders::_1));
testWriteFrame(ep); testWriteFrame(ep);
} }
{ {
@@ -542,6 +627,9 @@ public:
yield_to(std::bind(&stream_test::testMask, yield_to(std::bind(&stream_test::testMask,
this, ep, std::placeholders::_1)); this, ep, std::placeholders::_1));
yield_to(std::bind(&stream_test::testClose,
this, ep, std::placeholders::_1));
} }
pass(); pass();