Enable more split compilation in websocket and http

Signed-off-by: Damian Jarek <damian.jarek93@gmail.com>
This commit is contained in:
Damian Jarek
2019-03-10 20:56:40 +01:00
committed by Vinnie Falco
parent d2041c0322
commit dc52df351a
11 changed files with 947 additions and 776 deletions

View File

@@ -86,12 +86,12 @@ private:
increment(); increment();
} }
template<class = void> BOOST_BEAST_DECL
static static
std::string std::string
unquote(string_view sr); unquote(string_view sr);
template<class = void> BOOST_BEAST_DECL
void void
increment(); increment();
}; };
@@ -132,46 +132,6 @@ cend() const ->
return const_iterator{s_.end(), s_.end()}; return const_iterator{s_.end(), s_.end()};
} }
template<class>
std::string
param_list::const_iterator::
unquote(string_view sr)
{
std::string s;
s.reserve(sr.size());
auto it = sr.begin() + 1;
auto end = sr.end() - 1;
while(it != end)
{
if(*it == '\\')
++it;
s.push_back(*it);
++it;
}
return s;
}
template<class>
void
param_list::const_iterator::
increment()
{
s_.clear();
pi_.increment();
if(pi_.empty())
{
pi_.it = pi_.last;
pi_.first = pi_.last;
}
else if(! pi_.v.second.empty() &&
pi_.v.second.front() == '"')
{
s_ = unquote(pi_.v.second);
pi_.v.second = string_view{
s_.data(), s_.size()};
}
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
class ext_list::const_iterator class ext_list::const_iterator
@@ -243,7 +203,7 @@ private:
increment(); increment();
} }
template<class = void> BOOST_BEAST_DECL
void void
increment(); increment();
}; };
@@ -305,74 +265,6 @@ exists(T const& s)
return find(s) != end(); return find(s) != end();
} }
template<class>
void
ext_list::const_iterator::
increment()
{
/*
ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] )
ext = token param-list
param-list = *( OWS ";" OWS param )
param = token OWS "=" OWS ( token / quoted-string )
chunked;a=b;i=j;gzip;windowBits=12
x,y
,,,,,chameleon
*/
auto const err =
[&]
{
it_ = last_;
first_ = last_;
};
auto need_comma = it_ != first_;
v_.first = {};
first_ = it_;
for(;;)
{
detail::skip_ows(it_, last_);
if(it_ == last_)
return err();
auto const c = *it_;
if(detail::is_token_char(c))
{
if(need_comma)
return err();
auto const p0 = it_;
for(;;)
{
++it_;
if(it_ == last_)
break;
if(! detail::is_token_char(*it_))
break;
}
v_.first = string_view{&*p0,
static_cast<std::size_t>(it_ - p0)};
if (it_ == last_)
return;
detail::param_iter pi;
pi.it = it_;
pi.first = it_;
pi.last = last_;
for(;;)
{
pi.increment();
if(pi.empty())
break;
}
v_.second = param_list{string_view{&*it_,
static_cast<std::size_t>(pi.it - it_)}};
it_ = pi.it;
return;
}
if(c != ',')
return err();
need_comma = false;
++it_;
}
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -445,7 +337,7 @@ private:
increment(); increment();
} }
template<class = void> BOOST_BEAST_DECL
void void
increment(); increment();
}; };
@@ -486,53 +378,6 @@ cend() const ->
return const_iterator{s_.end(), s_.end()}; return const_iterator{s_.end(), s_.end()};
} }
template<class>
void
token_list::const_iterator::
increment()
{
/*
token-list = *( "," OWS ) token *( OWS "," [ OWS ext ] )
*/
auto const err =
[&]
{
it_ = last_;
first_ = last_;
};
auto need_comma = it_ != first_;
v_ = {};
first_ = it_;
for(;;)
{
detail::skip_ows(it_, last_);
if(it_ == last_)
return err();
auto const c = *it_;
if(detail::is_token_char(c))
{
if(need_comma)
return err();
auto const p0 = it_;
for(;;)
{
++it_;
if(it_ == last_)
break;
if(! detail::is_token_char(*it_))
break;
}
v_ = string_view{&*p0,
static_cast<std::size_t>(it_ - p0)};
return;
}
if(c != ',')
return err();
need_comma = false;
++it_;
}
}
template<class T> template<class T>
bool bool
token_list:: token_list::
@@ -570,5 +415,9 @@ validate_list(detail::basic_parsed_list<
} // beast } // beast
} // boost } // boost
#ifdef BOOST_BEAST_HEADER_ONLY
#include <boost/beast/http/impl/rfc7230.ipp>
#endif
#endif #endif

View File

@@ -0,0 +1,176 @@
//
// Copyright (c) 2016-2019 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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_HTTP_IMPL_RFC7230_IPP
#define BOOST_BEAST_HTTP_IMPL_RFC7230_IPP
#include <boost/beast/http/rfc7230.hpp>
namespace boost {
namespace beast {
namespace http {
std::string
param_list::const_iterator::
unquote(string_view sr)
{
std::string s;
s.reserve(sr.size());
auto it = sr.begin() + 1;
auto end = sr.end() - 1;
while(it != end)
{
if(*it == '\\')
++it;
s.push_back(*it);
++it;
}
return s;
}
void
param_list::const_iterator::
increment()
{
s_.clear();
pi_.increment();
if(pi_.empty())
{
pi_.it = pi_.last;
pi_.first = pi_.last;
}
else if(! pi_.v.second.empty() &&
pi_.v.second.front() == '"')
{
s_ = unquote(pi_.v.second);
pi_.v.second = string_view{
s_.data(), s_.size()};
}
}
void
ext_list::const_iterator::
increment()
{
/*
ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] )
ext = token param-list
param-list = *( OWS ";" OWS param )
param = token OWS "=" OWS ( token / quoted-string )
chunked;a=b;i=j;gzip;windowBits=12
x,y
,,,,,chameleon
*/
auto const err =
[&]
{
it_ = last_;
first_ = last_;
};
auto need_comma = it_ != first_;
v_.first = {};
first_ = it_;
for(;;)
{
detail::skip_ows(it_, last_);
if(it_ == last_)
return err();
auto const c = *it_;
if(detail::is_token_char(c))
{
if(need_comma)
return err();
auto const p0 = it_;
for(;;)
{
++it_;
if(it_ == last_)
break;
if(! detail::is_token_char(*it_))
break;
}
v_.first = string_view{&*p0,
static_cast<std::size_t>(it_ - p0)};
if (it_ == last_)
return;
detail::param_iter pi;
pi.it = it_;
pi.first = it_;
pi.last = last_;
for(;;)
{
pi.increment();
if(pi.empty())
break;
}
v_.second = param_list{string_view{&*it_,
static_cast<std::size_t>(pi.it - it_)}};
it_ = pi.it;
return;
}
if(c != ',')
return err();
need_comma = false;
++it_;
}
}
void
token_list::const_iterator::
increment()
{
/*
token-list = *( "," OWS ) token *( OWS "," [ OWS ext ] )
*/
auto const err =
[&]
{
it_ = last_;
first_ = last_;
};
auto need_comma = it_ != first_;
v_ = {};
first_ = it_;
for(;;)
{
detail::skip_ows(it_, last_);
if(it_ == last_)
return err();
auto const c = *it_;
if(detail::is_token_char(c))
{
if(need_comma)
return err();
auto const p0 = it_;
for(;;)
{
++it_;
if(it_ == last_)
break;
if(! detail::is_token_char(*it_))
break;
}
v_ = string_view{&*p0,
static_cast<std::size_t>(it_ - p0)};
return;
}
if(c != ',')
return err();
need_comma = false;
++it_;
}
}
} // http
} // beast
} // boost
#endif // BOOST_BEAST_HTTP_IMPL_RFC7230_IPP

View File

@@ -43,11 +43,15 @@ the program, with the macro BOOST_BEAST_SPLIT_COMPILATION defined.
#include <boost/beast/http/impl/basic_parser.ipp> #include <boost/beast/http/impl/basic_parser.ipp>
#include <boost/beast/http/impl/error.ipp> #include <boost/beast/http/impl/error.ipp>
#include <boost/beast/http/impl/field.ipp> #include <boost/beast/http/impl/field.ipp>
#include <boost/beast/http/impl/rfc7230.ipp>
#include <boost/beast/http/impl/status.ipp> #include <boost/beast/http/impl/status.ipp>
#include <boost/beast/http/impl/verb.ipp> #include <boost/beast/http/impl/verb.ipp>
#include <boost/beast/websocket/detail/hybi13.ipp>
#include <boost/beast/websocket/detail/pmd_extension.ipp>
#include <boost/beast/websocket/detail/prng.ipp> #include <boost/beast/websocket/detail/prng.ipp>
#include <boost/beast/websocket/detail/service.ipp> #include <boost/beast/websocket/detail/service.ipp>
#include <boost/beast/websocket/detail/utf8_checker.ipp>
#include <boost/beast/websocket/impl/error.ipp> #include <boost/beast/websocket/impl/error.ipp>
#include <boost/beast/zlib/detail/deflate_stream.ipp> #include <boost/beast/zlib/detail/deflate_stream.ipp>

View File

@@ -13,13 +13,7 @@
#include <boost/beast/core/static_string.hpp> #include <boost/beast/core/static_string.hpp>
#include <boost/beast/core/string.hpp> #include <boost/beast/core/string.hpp>
#include <boost/beast/core/detail/base64.hpp> #include <boost/beast/core/detail/base64.hpp>
#include <boost/beast/core/detail/sha1.hpp>
#include <boost/beast/websocket/detail/prng.hpp>
#include <boost/assert.hpp>
#include <array>
#include <cstdint> #include <cstdint>
#include <string>
#include <type_traits>
namespace boost { namespace boost {
namespace beast { namespace beast {
@@ -32,47 +26,23 @@ using sec_ws_key_type = static_string<
using sec_ws_accept_type = static_string< using sec_ws_accept_type = static_string<
beast::detail::base64::encoded_size(20)>; beast::detail::base64::encoded_size(20)>;
inline BOOST_BEAST_DECL
void void
make_sec_ws_key(sec_ws_key_type& key) make_sec_ws_key(sec_ws_key_type& key);
{
auto g = make_prng(true);
char a[16];
for(int i = 0; i < 16; i += 4)
{
auto const v = g();
a[i ] = v & 0xff;
a[i+1] = (v >> 8) & 0xff;
a[i+2] = (v >> 16) & 0xff;
a[i+3] = (v >> 24) & 0xff;
}
key.resize(key.max_size());
key.resize(beast::detail::base64::encode(
key.data(), &a[0], 16));
}
template<class = void> BOOST_BEAST_DECL
void void
make_sec_ws_accept( make_sec_ws_accept(
sec_ws_accept_type& accept, sec_ws_accept_type& accept,
string_view key) string_view key);
{
BOOST_ASSERT(key.size() <= sec_ws_key_type::max_size_n);
static_string<sec_ws_key_type::max_size_n + 36> m(key);
m.append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
beast::detail::sha1_context ctx;
beast::detail::init(ctx);
beast::detail::update(ctx, m.data(), m.size());
char digest[beast::detail::sha1_context::digest_size];
beast::detail::finish(ctx, &digest[0]);
accept.resize(accept.max_size());
accept.resize(beast::detail::base64::encode(
accept.data(), &digest[0], sizeof(digest)));
}
} // detail } // detail
} // websocket } // websocket
} // beast } // beast
} // boost } // boost
#if BOOST_BEAST_HEADER_ONLY
#include <boost/beast/websocket/detail/hybi13.ipp>
#endif
#endif #endif

View File

@@ -0,0 +1,67 @@
//
// Copyright (c) 2016-2019 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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_WEBSOCKET_DETAIL_HYBI13_IPP
#define BOOST_BEAST_WEBSOCKET_DETAIL_HYBI13_IPP
#include <boost/beast/websocket/detail/hybi13.hpp>
#include <boost/beast/core/detail/sha1.hpp>
#include <boost/beast/websocket/detail/prng.hpp>
#include <boost/assert.hpp>
#include <cstdint>
#include <string>
namespace boost {
namespace beast {
namespace websocket {
namespace detail {
void
make_sec_ws_key(sec_ws_key_type& key)
{
auto g = make_prng(true);
char a[16];
for(int i = 0; i < 16; i += 4)
{
auto const v = g();
a[i ] = v & 0xff;
a[i+1] = (v >> 8) & 0xff;
a[i+2] = (v >> 16) & 0xff;
a[i+3] = (v >> 24) & 0xff;
}
key.resize(key.max_size());
key.resize(beast::detail::base64::encode(
key.data(), &a[0], 16));
}
void
make_sec_ws_accept(
sec_ws_accept_type& accept,
string_view key)
{
BOOST_ASSERT(key.size() <= sec_ws_key_type::max_size_n);
static_string<sec_ws_key_type::max_size_n + 36> m(key);
m.append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
beast::detail::sha1_context ctx;
beast::detail::init(ctx);
beast::detail::update(ctx, m.data(), m.size());
char digest[beast::detail::sha1_context::digest_size];
beast::detail::finish(ctx, &digest[0]);
accept.resize(accept.max_size());
accept.resize(beast::detail::base64::encode(
accept.data(), &digest[0], sizeof(digest)));
}
} // detail
} // websocket
} // beast
} // boost
#endif // BOOST_BEAST_WEBSOCKET_DETAIL_HYBI13_IPP

View File

@@ -11,13 +11,8 @@
#define BOOST_BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP #define BOOST_BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP
#include <boost/beast/core/error.hpp> #include <boost/beast/core/error.hpp>
#include <boost/beast/core/buffers_suffix.hpp>
#include <boost/beast/core/read_size.hpp>
#include <boost/beast/zlib/deflate_stream.hpp>
#include <boost/beast/zlib/inflate_stream.hpp>
#include <boost/beast/websocket/option.hpp> #include <boost/beast/websocket/option.hpp>
#include <boost/beast/http/rfc7230.hpp> #include <boost/beast/http/rfc7230.hpp>
#include <boost/asio/buffer.hpp>
#include <utility> #include <utility>
#include <type_traits> #include <type_traits>
@@ -48,28 +43,24 @@ struct pmd_offer
bool client_no_context_takeover; bool client_no_context_takeover;
}; };
template<class = void> BOOST_BEAST_DECL
int int
parse_bits(string_view s) parse_bits(string_view s);
{
if(s.size() == 0) BOOST_BEAST_DECL
return -1; void
if(s.size() > 2) pmd_read_impl(pmd_offer& offer, http::ext_list const& list);
return -1;
if(s[0] < '1' || s[0] > '9') BOOST_BEAST_DECL
return -1; static_string<512>
unsigned i = 0; pmd_write_impl(pmd_offer const& offer);
for(auto c : s)
{ BOOST_BEAST_DECL
if(c < '0' || c > '9') static_string<512>
return -1; pmd_negotiate_impl(
auto const i0 = i; pmd_offer& config,
i = 10 * i + (c - '0'); pmd_offer const& offer,
if(i < i0) permessage_deflate const& o);
return -1;
}
return static_cast<int>(i);
}
// Parse permessage-deflate request fields // Parse permessage-deflate request fields
// //
@@ -78,126 +69,9 @@ void
pmd_read(pmd_offer& offer, pmd_read(pmd_offer& offer,
http::basic_fields<Allocator> const& fields) http::basic_fields<Allocator> const& fields)
{ {
offer.accept = false;
offer.server_max_window_bits= 0;
offer.client_max_window_bits = 0;
offer.server_no_context_takeover = false;
offer.client_no_context_takeover = false;
http::ext_list list{ http::ext_list list{
fields["Sec-WebSocket-Extensions"]}; fields["Sec-WebSocket-Extensions"]};
for(auto const& ext : list) detail::pmd_read_impl(offer, list);
{
if(iequals(ext.first, "permessage-deflate"))
{
for(auto const& param : ext.second)
{
if(iequals(param.first,
"server_max_window_bits"))
{
if(offer.server_max_window_bits != 0)
{
// The negotiation offer contains multiple
// extension parameters with the same name.
//
return; // MUST decline
}
if(param.second.empty())
{
// The negotiation offer extension
// parameter is missing the value.
//
return; // MUST decline
}
offer.server_max_window_bits =
parse_bits(param.second);
if( offer.server_max_window_bits < 8 ||
offer.server_max_window_bits > 15)
{
// The negotiation offer contains an
// extension parameter with an invalid value.
//
return; // MUST decline
}
}
else if(iequals(param.first,
"client_max_window_bits"))
{
if(offer.client_max_window_bits != 0)
{
// The negotiation offer contains multiple
// extension parameters with the same name.
//
return; // MUST decline
}
if(! param.second.empty())
{
offer.client_max_window_bits =
parse_bits(param.second);
if( offer.client_max_window_bits < 8 ||
offer.client_max_window_bits > 15)
{
// The negotiation offer contains an
// extension parameter with an invalid value.
//
return; // MUST decline
}
}
else
{
offer.client_max_window_bits = -1;
}
}
else if(iequals(param.first,
"server_no_context_takeover"))
{
if(offer.server_no_context_takeover)
{
// The negotiation offer contains multiple
// extension parameters with the same name.
//
return; // MUST decline
}
if(! param.second.empty())
{
// The negotiation offer contains an
// extension parameter with an invalid value.
//
return; // MUST decline
}
offer.server_no_context_takeover = true;
}
else if(iequals(param.first,
"client_no_context_takeover"))
{
if(offer.client_no_context_takeover)
{
// The negotiation offer contains multiple
// extension parameters with the same name.
//
return; // MUST decline
}
if(! param.second.empty())
{
// The negotiation offer contains an
// extension parameter with an invalid value.
//
return; // MUST decline
}
offer.client_no_context_takeover = true;
}
else
{
// The negotiation offer contains an extension
// parameter not defined for use in an offer.
//
return; // MUST decline
}
}
offer.accept = true;
return;
}
}
} }
// Set permessage-deflate fields for a client offer // Set permessage-deflate fields for a client offer
@@ -207,42 +81,7 @@ void
pmd_write(http::basic_fields<Allocator>& fields, pmd_write(http::basic_fields<Allocator>& fields,
pmd_offer const& offer) pmd_offer const& offer)
{ {
static_string<512> s; auto s = detail::pmd_write_impl(offer);
s = "permessage-deflate";
if(offer.server_max_window_bits != 0)
{
if(offer.server_max_window_bits != -1)
{
s += "; server_max_window_bits=";
s += to_static_string(
offer.server_max_window_bits);
}
else
{
s += "; server_max_window_bits";
}
}
if(offer.client_max_window_bits != 0)
{
if(offer.client_max_window_bits != -1)
{
s += "; client_max_window_bits=";
s += to_static_string(
offer.client_max_window_bits);
}
else
{
s += "; client_max_window_bits";
}
}
if(offer.server_no_context_takeover)
{
s += "; server_no_context_takeover";
}
if(offer.client_no_context_takeover)
{
s += "; client_no_context_takeover";
}
fields.set(http::field::sec_websocket_extensions, s); fields.set(http::field::sec_websocket_extensions, s);
} }
@@ -263,102 +102,24 @@ pmd_negotiate(
} }
config.accept = true; config.accept = true;
static_string<512> s = "permessage-deflate"; auto s = detail::pmd_negotiate_impl(config, offer, o);
config.server_no_context_takeover =
offer.server_no_context_takeover ||
o.server_no_context_takeover;
if(config.server_no_context_takeover)
s += "; server_no_context_takeover";
config.client_no_context_takeover =
o.client_no_context_takeover ||
offer.client_no_context_takeover;
if(config.client_no_context_takeover)
s += "; client_no_context_takeover";
if(offer.server_max_window_bits != 0)
config.server_max_window_bits = (std::min)(
offer.server_max_window_bits,
o.server_max_window_bits);
else
config.server_max_window_bits =
o.server_max_window_bits;
if(config.server_max_window_bits < 15)
{
// ZLib's deflateInit silently treats 8 as
// 9 due to a bug, so prevent 8 from being used.
//
if(config.server_max_window_bits < 9)
config.server_max_window_bits = 9;
s += "; server_max_window_bits=";
s += to_static_string(
config.server_max_window_bits);
}
switch(offer.client_max_window_bits)
{
case -1:
// extension parameter is present with no value
config.client_max_window_bits =
o.client_max_window_bits;
if(config.client_max_window_bits < 15)
{
s += "; client_max_window_bits=";
s += to_static_string(
config.client_max_window_bits);
}
break;
case 0:
/* extension parameter is absent.
If a received extension negotiation offer doesn't have the
"client_max_window_bits" extension parameter, the corresponding
extension negotiation response to the offer MUST NOT include the
"client_max_window_bits" extension parameter.
*/
if(o.client_max_window_bits == 15)
config.client_max_window_bits = 15;
else
config.accept = false;
break;
default:
// extension parameter has value in [8..15]
config.client_max_window_bits = (std::min)(
o.client_max_window_bits,
offer.client_max_window_bits);
s += "; client_max_window_bits=";
s += to_static_string(
config.client_max_window_bits);
break;
}
if(config.accept) if(config.accept)
fields.set(http::field::sec_websocket_extensions, s); fields.set(http::field::sec_websocket_extensions, s);
} }
// Normalize the server's response // Normalize the server's response
// //
inline BOOST_BEAST_DECL
void void
pmd_normalize(pmd_offer& offer) pmd_normalize(pmd_offer& offer);
{
if(offer.accept)
{
if( offer.server_max_window_bits == 0)
offer.server_max_window_bits = 15;
if( offer.client_max_window_bits == 0 ||
offer.client_max_window_bits == -1)
offer.client_max_window_bits = 15;
}
}
} // detail } // detail
} // websocket } // websocket
} // beast } // beast
} // boost } // boost
#if BOOST_BEAST_HEADER_ONLY
#include <boost/beast/websocket/detail/pmd_extension.ipp>
#endif
#endif #endif

View File

@@ -0,0 +1,310 @@
//
// Copyright (c) 2016-2019 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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_IPP
#define BOOST_BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_IPP
#include <boost/beast/websocket/detail/pmd_extension.hpp>
namespace boost {
namespace beast {
namespace websocket {
namespace detail {
int
parse_bits(string_view s)
{
if(s.size() == 0)
return -1;
if(s.size() > 2)
return -1;
if(s[0] < '1' || s[0] > '9')
return -1;
unsigned i = 0;
for(auto c : s)
{
if(c < '0' || c > '9')
return -1;
auto const i0 = i;
i = 10 * i + (c - '0');
if(i < i0)
return -1;
}
return static_cast<int>(i);
}
// Parse permessage-deflate request fields
//
void
pmd_read_impl(pmd_offer& offer, http::ext_list const& list)
{
offer.accept = false;
offer.server_max_window_bits= 0;
offer.client_max_window_bits = 0;
offer.server_no_context_takeover = false;
offer.client_no_context_takeover = false;
for(auto const& ext : list)
{
if(iequals(ext.first, "permessage-deflate"))
{
for(auto const& param : ext.second)
{
if(iequals(param.first,
"server_max_window_bits"))
{
if(offer.server_max_window_bits != 0)
{
// The negotiation offer contains multiple
// extension parameters with the same name.
//
return; // MUST decline
}
if(param.second.empty())
{
// The negotiation offer extension
// parameter is missing the value.
//
return; // MUST decline
}
offer.server_max_window_bits =
parse_bits(param.second);
if( offer.server_max_window_bits < 8 ||
offer.server_max_window_bits > 15)
{
// The negotiation offer contains an
// extension parameter with an invalid value.
//
return; // MUST decline
}
}
else if(iequals(param.first,
"client_max_window_bits"))
{
if(offer.client_max_window_bits != 0)
{
// The negotiation offer contains multiple
// extension parameters with the same name.
//
return; // MUST decline
}
if(! param.second.empty())
{
offer.client_max_window_bits =
parse_bits(param.second);
if( offer.client_max_window_bits < 8 ||
offer.client_max_window_bits > 15)
{
// The negotiation offer contains an
// extension parameter with an invalid value.
//
return; // MUST decline
}
}
else
{
offer.client_max_window_bits = -1;
}
}
else if(iequals(param.first,
"server_no_context_takeover"))
{
if(offer.server_no_context_takeover)
{
// The negotiation offer contains multiple
// extension parameters with the same name.
//
return; // MUST decline
}
if(! param.second.empty())
{
// The negotiation offer contains an
// extension parameter with an invalid value.
//
return; // MUST decline
}
offer.server_no_context_takeover = true;
}
else if(iequals(param.first,
"client_no_context_takeover"))
{
if(offer.client_no_context_takeover)
{
// The negotiation offer contains multiple
// extension parameters with the same name.
//
return; // MUST decline
}
if(! param.second.empty())
{
// The negotiation offer contains an
// extension parameter with an invalid value.
//
return; // MUST decline
}
offer.client_no_context_takeover = true;
}
else
{
// The negotiation offer contains an extension
// parameter not defined for use in an offer.
//
return; // MUST decline
}
}
offer.accept = true;
return;
}
}
}
static_string<512>
pmd_write_impl(pmd_offer const& offer)
{
static_string<512> s = "permessage-deflate";
if(offer.server_max_window_bits != 0)
{
if(offer.server_max_window_bits != -1)
{
s += "; server_max_window_bits=";
s += to_static_string(
offer.server_max_window_bits);
}
else
{
s += "; server_max_window_bits";
}
}
if(offer.client_max_window_bits != 0)
{
if(offer.client_max_window_bits != -1)
{
s += "; client_max_window_bits=";
s += to_static_string(
offer.client_max_window_bits);
}
else
{
s += "; client_max_window_bits";
}
}
if(offer.server_no_context_takeover)
{
s += "; server_no_context_takeover";
}
if(offer.client_no_context_takeover)
{
s += "; client_no_context_takeover";
}
return s;
}
static_string<512>
pmd_negotiate_impl(
pmd_offer& config,
pmd_offer const& offer,
permessage_deflate const& o)
{
static_string<512> s = "permessage-deflate";
config.server_no_context_takeover =
offer.server_no_context_takeover ||
o.server_no_context_takeover;
if(config.server_no_context_takeover)
s += "; server_no_context_takeover";
config.client_no_context_takeover =
o.client_no_context_takeover ||
offer.client_no_context_takeover;
if(config.client_no_context_takeover)
s += "; client_no_context_takeover";
if(offer.server_max_window_bits != 0)
config.server_max_window_bits = (std::min)(
offer.server_max_window_bits,
o.server_max_window_bits);
else
config.server_max_window_bits =
o.server_max_window_bits;
if(config.server_max_window_bits < 15)
{
// ZLib's deflateInit silently treats 8 as
// 9 due to a bug, so prevent 8 from being used.
//
if(config.server_max_window_bits < 9)
config.server_max_window_bits = 9;
s += "; server_max_window_bits=";
s += to_static_string(
config.server_max_window_bits);
}
switch(offer.client_max_window_bits)
{
case -1:
// extension parameter is present with no value
config.client_max_window_bits =
o.client_max_window_bits;
if(config.client_max_window_bits < 15)
{
s += "; client_max_window_bits=";
s += to_static_string(
config.client_max_window_bits);
}
break;
case 0:
/* extension parameter is absent.
If a received extension negotiation offer doesn't have the
"client_max_window_bits" extension parameter, the corresponding
extension negotiation response to the offer MUST NOT include the
"client_max_window_bits" extension parameter.
*/
if(o.client_max_window_bits == 15)
config.client_max_window_bits = 15;
else
config.accept = false;
break;
default:
// extension parameter has value in [8..15]
config.client_max_window_bits = (std::min)(
o.client_max_window_bits,
offer.client_max_window_bits);
s += "; client_max_window_bits=";
s += to_static_string(
config.client_max_window_bits);
break;
}
return s;
}
void
pmd_normalize(pmd_offer& offer)
{
if(offer.accept)
{
if( offer.server_max_window_bits == 0)
offer.server_max_window_bits = 15;
if( offer.client_max_window_bits == 0 ||
offer.client_max_window_bits == -1)
offer.client_max_window_bits = 15;
}
}
} // detail
} // websocket
} // beast
} // boost
#endif // BOOST_BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_IPP

View File

@@ -12,8 +12,7 @@
#include <boost/beast/core/buffers_range.hpp> #include <boost/beast/core/buffers_range.hpp>
#include <boost/asio/buffer.hpp> #include <boost/asio/buffer.hpp>
#include <boost/assert.hpp>
#include <algorithm>
#include <cstdint> #include <cstdint>
namespace boost { namespace boost {
@@ -27,8 +26,7 @@ namespace detail {
valid. The write function may be called incrementally with segmented UTF8 valid. The write function may be called incrementally with segmented UTF8
sequences. The finish function determines if all processed text is valid. sequences. The finish function determines if all processed text is valid.
*/ */
template<class = void> class utf8_checker
class utf8_checker_t
{ {
std::size_t need_ = 0; // chars we need to finish the code point std::size_t need_ = 0; // chars we need to finish the code point
std::uint8_t* p_ = cp_; // current position in temp buffer std::uint8_t* p_ = cp_; // current position in temp buffer
@@ -37,11 +35,13 @@ class utf8_checker_t
public: public:
/** Prepare to process text as valid utf8 /** Prepare to process text as valid utf8
*/ */
BOOST_BEAST_DECL
void void
reset(); reset();
/** Check that all processed text is valid utf8 /** Check that all processed text is valid utf8
*/ */
BOOST_BEAST_DECL
bool bool
finish(); finish();
@@ -49,6 +49,7 @@ public:
@return `true` if the text is valid utf8 or false otherwise. @return `true` if the text is valid utf8 or false otherwise.
*/ */
BOOST_BEAST_DECL
bool bool
write(std::uint8_t const* in, std::size_t size); write(std::uint8_t const* in, std::size_t size);
@@ -61,29 +62,10 @@ public:
write(ConstBufferSequence const& bs); write(ConstBufferSequence const& bs);
}; };
template<class _>
void
utf8_checker_t<_>::
reset()
{
need_ = 0;
p_ = cp_;
}
template<class _>
bool
utf8_checker_t<_>::
finish()
{
auto const success = need_ == 0;
reset();
return success;
}
template<class _>
template<class ConstBufferSequence> template<class ConstBufferSequence>
bool bool
utf8_checker_t<_>:: utf8_checker::
write(ConstBufferSequence const& buffers) write(ConstBufferSequence const& buffers)
{ {
static_assert( static_assert(
@@ -97,300 +79,18 @@ write(ConstBufferSequence const& buffers)
return true; return true;
} }
template<class _>
BOOST_BEAST_DECL
bool bool
utf8_checker_t<_>:: check_utf8(char const* p, std::size_t n);
write(std::uint8_t const* in, std::size_t size)
{
auto const valid =
[](std::uint8_t const*& p)
{
if(p[0] < 128)
{
++p;
return true;
}
if((p[0] & 0xe0) == 0xc0)
{
if( (p[1] & 0xc0) != 0x80 ||
(p[0] & 0x1e) == 0) // overlong
return false;
p += 2;
return true;
}
if((p[0] & 0xf0) == 0xe0)
{
if( (p[1] & 0xc0) != 0x80
|| (p[2] & 0xc0) != 0x80
|| (p[0] == 0xe0 && (p[1] & 0x20) == 0) // overlong
|| (p[0] == 0xed && (p[1] & 0x20) == 0x20) // surrogate
//|| (p[0] == 0xef && p[1] == 0xbf && (p[2] & 0xfe) == 0xbe) // U+FFFE or U+FFFF
)
return false;
p += 3;
return true;
}
if((p[0] & 0xf8) == 0xf0)
{
if( (p[0] & 0x07) >= 0x05 // invalid F5...FF characters
|| (p[1] & 0xc0) != 0x80
|| (p[2] & 0xc0) != 0x80
|| (p[3] & 0xc0) != 0x80
|| (p[0] == 0xf0 && (p[1] & 0x30) == 0) // overlong
|| (p[0] == 0xf4 && p[1] > 0x8f) || p[0] > 0xf4 // > U+10FFFF
)
return false;
p += 4;
return true;
}
return false;
};
auto const fail_fast =
[&]()
{
if(cp_[0] < 128)
{
return false;
}
const auto& p = cp_; // alias, only to keep this code similar to valid() above
const auto known_only = p_ - cp_;
if (known_only == 1)
{
if((p[0] & 0xe0) == 0xc0)
{
return ((p[0] & 0x1e) == 0); // overlong
}
if((p[0] & 0xf0) == 0xe0)
{
return false;
}
if((p[0] & 0xf8) == 0xf0)
{
return ((p[0] & 0x07) >= 0x05); // invalid F5...FF characters
}
}
else if (known_only == 2)
{
if((p[0] & 0xe0) == 0xc0)
{
return ((p[1] & 0xc0) != 0x80 ||
(p[0] & 0x1e) == 0); // overlong
}
if((p[0] & 0xf0) == 0xe0)
{
return ( (p[1] & 0xc0) != 0x80
|| (p[0] == 0xe0 && (p[1] & 0x20) == 0) // overlong
|| (p[0] == 0xed && (p[1] & 0x20) == 0x20)); // surrogate
}
if((p[0] & 0xf8) == 0xf0)
{
return ( (p[0] & 0x07) >= 0x05 // invalid F5...FF characters
|| (p[1] & 0xc0) != 0x80
|| (p[0] == 0xf0 && (p[1] & 0x30) == 0) // overlong
|| (p[0] == 0xf4 && p[1] > 0x8f) || p[0] > 0xf4); // > U+10FFFF
}
}
else if (known_only == 3)
{
if((p[0] & 0xe0) == 0xc0)
{
return ( (p[1] & 0xc0) != 0x80
|| (p[0] & 0x1e) == 0); // overlong
}
if((p[0] & 0xf0) == 0xe0)
{
return ( (p[1] & 0xc0) != 0x80
|| (p[2] & 0xc0) != 0x80
|| (p[0] == 0xe0 && (p[1] & 0x20) == 0) // overlong
|| (p[0] == 0xed && (p[1] & 0x20) == 0x20)); // surrogate
//|| (p[0] == 0xef && p[1] == 0xbf && (p[2] & 0xfe) == 0xbe) // U+FFFE or U+FFFF
}
if((p[0] & 0xf8) == 0xf0)
{
return ( (p[0] & 0x07) >= 0x05 // invalid F5...FF characters
|| (p[1] & 0xc0) != 0x80
|| (p[2] & 0xc0) != 0x80
|| (p[0] == 0xf0 && (p[1] & 0x30) == 0) // overlong
|| (p[0] == 0xf4 && p[1] > 0x8f) || p[0] > 0xf4); // > U+10FFFF
}
}
return true;
};
auto const needed =
[](std::uint8_t const v)
{
if(v < 128)
return 1;
if(v < 192)
return 0;
if(v < 224)
return 2;
if(v < 240)
return 3;
if(v < 248)
return 4;
return 0;
};
auto const end = in + size;
// Finish up any incomplete code point
if(need_ > 0)
{
// Calculate what we have
auto n = (std::min)(size, need_);
size -= n;
need_ -= n;
// Add characters to the code point
while(n--)
*p_++ = *in++;
BOOST_ASSERT(p_ <= cp_ + 4);
// Still incomplete?
if(need_ > 0)
{
// Incomplete code point
BOOST_ASSERT(in == end);
// Do partial validation on the incomplete
// code point, this is called "Fail fast"
// in Autobahn|Testsuite parlance.
return ! fail_fast();
}
// Complete code point, validate it
std::uint8_t const* p = &cp_[0];
if(! valid(p))
return false;
p_ = cp_;
}
if(size <= sizeof(std::size_t))
goto slow;
// Align `in` to sizeof(std::size_t) boundary
{
auto const in0 = in;
auto last = reinterpret_cast<std::uint8_t const*>(
((reinterpret_cast<std::uintptr_t>(in) + sizeof(std::size_t) - 1) /
sizeof(std::size_t)) * sizeof(std::size_t));
// Check one character at a time for low-ASCII
while(in < last)
{
if(*in & 0x80)
{
// Not low-ASCII so switch to slow loop
size = size - (in - in0);
goto slow;
}
++in;
}
size = size - (in - in0);
}
// Fast loop: Process 4 or 8 low-ASCII characters at a time
{
auto const in0 = in;
auto last = in + size - 7;
auto constexpr mask = static_cast<
std::size_t>(0x8080808080808080 & ~std::size_t{0});
while(in < last)
{
#if 0
std::size_t temp;
std::memcpy(&temp, in, sizeof(temp));
if((temp & mask) != 0)
#else
// Technically UB but works on all known platforms
if((*reinterpret_cast<std::size_t const*>(in) & mask) != 0)
#endif
{
size = size - (in - in0);
goto slow;
}
in += sizeof(std::size_t);
}
// There's at least one more full code point left
last += 4;
while(in < last)
if(! valid(in))
return false;
goto tail;
}
slow:
// Slow loop: Full validation on one code point at a time
{
auto last = in + size - 3;
while(in < last)
if(! valid(in))
return false;
}
tail:
// Handle the remaining bytes. The last
// characters could split a code point so
// we save the partial code point for later.
//
// On entry to the loop, `in` points to the
// beginning of a code point.
//
for(;;)
{
// Number of chars left
auto n = end - in;
if(! n)
break;
// Chars we need to finish this code point
auto const need = needed(*in);
if(need == 0)
return false;
if(need <= n)
{
// Check a whole code point
if(! valid(in))
return false;
}
else
{
// Calculate how many chars we need
// to finish this partial code point
need_ = need - n;
// Save the partial code point
while(n--)
*p_++ = *in++;
BOOST_ASSERT(in == end);
BOOST_ASSERT(p_ <= cp_ + 4);
// Do partial validation on the incomplete
// code point, this is called "Fail fast"
// in Autobahn|Testsuite parlance.
return ! fail_fast();
}
}
return true;
}
using utf8_checker = utf8_checker_t<>;
template<class = void>
bool
check_utf8(char const* p, std::size_t n)
{
utf8_checker c;
if(! c.write(reinterpret_cast<const uint8_t*>(p), n))
return false;
return c.finish();
}
} // detail } // detail
} // websocket } // websocket
} // beast } // beast
} // boost } // boost
#if BOOST_BEAST_HEADER_ONLY
#include <boost/beast/websocket/detail/utf8_checker.ipp>
#endif
#endif #endif

View File

@@ -0,0 +1,331 @@
//
// Copyright (c) 2016-2019 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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_WEBSOCKET_DETAIL_UTF8_CHECKER_IPP
#define BOOST_BEAST_WEBSOCKET_DETAIL_UTF8_CHECKER_IPP
#include <boost/beast/websocket/detail/utf8_checker.hpp>
#include <boost/assert.hpp>
namespace boost {
namespace beast {
namespace websocket {
namespace detail {
void
utf8_checker::
reset()
{
need_ = 0;
p_ = cp_;
}
bool
utf8_checker::
finish()
{
auto const success = need_ == 0;
reset();
return success;
}
bool
utf8_checker::
write(std::uint8_t const* in, std::size_t size)
{
auto const valid =
[](std::uint8_t const*& p)
{
if(p[0] < 128)
{
++p;
return true;
}
if((p[0] & 0xe0) == 0xc0)
{
if( (p[1] & 0xc0) != 0x80 ||
(p[0] & 0x1e) == 0) // overlong
return false;
p += 2;
return true;
}
if((p[0] & 0xf0) == 0xe0)
{
if( (p[1] & 0xc0) != 0x80
|| (p[2] & 0xc0) != 0x80
|| (p[0] == 0xe0 && (p[1] & 0x20) == 0) // overlong
|| (p[0] == 0xed && (p[1] & 0x20) == 0x20) // surrogate
//|| (p[0] == 0xef && p[1] == 0xbf && (p[2] & 0xfe) == 0xbe) // U+FFFE or U+FFFF
)
return false;
p += 3;
return true;
}
if((p[0] & 0xf8) == 0xf0)
{
if( (p[0] & 0x07) >= 0x05 // invalid F5...FF characters
|| (p[1] & 0xc0) != 0x80
|| (p[2] & 0xc0) != 0x80
|| (p[3] & 0xc0) != 0x80
|| (p[0] == 0xf0 && (p[1] & 0x30) == 0) // overlong
|| (p[0] == 0xf4 && p[1] > 0x8f) || p[0] > 0xf4 // > U+10FFFF
)
return false;
p += 4;
return true;
}
return false;
};
auto const fail_fast =
[&]()
{
if(cp_[0] < 128)
{
return false;
}
const auto& p = cp_; // alias, only to keep this code similar to valid() above
const auto known_only = p_ - cp_;
if (known_only == 1)
{
if((p[0] & 0xe0) == 0xc0)
{
return ((p[0] & 0x1e) == 0); // overlong
}
if((p[0] & 0xf0) == 0xe0)
{
return false;
}
if((p[0] & 0xf8) == 0xf0)
{
return ((p[0] & 0x07) >= 0x05); // invalid F5...FF characters
}
}
else if (known_only == 2)
{
if((p[0] & 0xe0) == 0xc0)
{
return ((p[1] & 0xc0) != 0x80 ||
(p[0] & 0x1e) == 0); // overlong
}
if((p[0] & 0xf0) == 0xe0)
{
return ( (p[1] & 0xc0) != 0x80
|| (p[0] == 0xe0 && (p[1] & 0x20) == 0) // overlong
|| (p[0] == 0xed && (p[1] & 0x20) == 0x20)); // surrogate
}
if((p[0] & 0xf8) == 0xf0)
{
return ( (p[0] & 0x07) >= 0x05 // invalid F5...FF characters
|| (p[1] & 0xc0) != 0x80
|| (p[0] == 0xf0 && (p[1] & 0x30) == 0) // overlong
|| (p[0] == 0xf4 && p[1] > 0x8f) || p[0] > 0xf4); // > U+10FFFF
}
}
else if (known_only == 3)
{
if((p[0] & 0xe0) == 0xc0)
{
return ( (p[1] & 0xc0) != 0x80
|| (p[0] & 0x1e) == 0); // overlong
}
if((p[0] & 0xf0) == 0xe0)
{
return ( (p[1] & 0xc0) != 0x80
|| (p[2] & 0xc0) != 0x80
|| (p[0] == 0xe0 && (p[1] & 0x20) == 0) // overlong
|| (p[0] == 0xed && (p[1] & 0x20) == 0x20)); // surrogate
//|| (p[0] == 0xef && p[1] == 0xbf && (p[2] & 0xfe) == 0xbe) // U+FFFE or U+FFFF
}
if((p[0] & 0xf8) == 0xf0)
{
return ( (p[0] & 0x07) >= 0x05 // invalid F5...FF characters
|| (p[1] & 0xc0) != 0x80
|| (p[2] & 0xc0) != 0x80
|| (p[0] == 0xf0 && (p[1] & 0x30) == 0) // overlong
|| (p[0] == 0xf4 && p[1] > 0x8f) || p[0] > 0xf4); // > U+10FFFF
}
}
return true;
};
auto const needed =
[](std::uint8_t const v)
{
if(v < 128)
return 1;
if(v < 192)
return 0;
if(v < 224)
return 2;
if(v < 240)
return 3;
if(v < 248)
return 4;
return 0;
};
auto const end = in + size;
// Finish up any incomplete code point
if(need_ > 0)
{
// Calculate what we have
auto n = (std::min)(size, need_);
size -= n;
need_ -= n;
// Add characters to the code point
while(n--)
*p_++ = *in++;
BOOST_ASSERT(p_ <= cp_ + 4);
// Still incomplete?
if(need_ > 0)
{
// Incomplete code point
BOOST_ASSERT(in == end);
// Do partial validation on the incomplete
// code point, this is called "Fail fast"
// in Autobahn|Testsuite parlance.
return ! fail_fast();
}
// Complete code point, validate it
std::uint8_t const* p = &cp_[0];
if(! valid(p))
return false;
p_ = cp_;
}
if(size <= sizeof(std::size_t))
goto slow;
// Align `in` to sizeof(std::size_t) boundary
{
auto const in0 = in;
auto last = reinterpret_cast<std::uint8_t const*>(
((reinterpret_cast<std::uintptr_t>(in) + sizeof(std::size_t) - 1) /
sizeof(std::size_t)) * sizeof(std::size_t));
// Check one character at a time for low-ASCII
while(in < last)
{
if(*in & 0x80)
{
// Not low-ASCII so switch to slow loop
size = size - (in - in0);
goto slow;
}
++in;
}
size = size - (in - in0);
}
// Fast loop: Process 4 or 8 low-ASCII characters at a time
{
auto const in0 = in;
auto last = in + size - 7;
auto constexpr mask = static_cast<
std::size_t>(0x8080808080808080 & ~std::size_t{0});
while(in < last)
{
#if 0
std::size_t temp;
std::memcpy(&temp, in, sizeof(temp));
if((temp & mask) != 0)
#else
// Technically UB but works on all known platforms
if((*reinterpret_cast<std::size_t const*>(in) & mask) != 0)
#endif
{
size = size - (in - in0);
goto slow;
}
in += sizeof(std::size_t);
}
// There's at least one more full code point left
last += 4;
while(in < last)
if(! valid(in))
return false;
goto tail;
}
slow:
// Slow loop: Full validation on one code point at a time
{
auto last = in + size - 3;
while(in < last)
if(! valid(in))
return false;
}
tail:
// Handle the remaining bytes. The last
// characters could split a code point so
// we save the partial code point for later.
//
// On entry to the loop, `in` points to the
// beginning of a code point.
//
for(;;)
{
// Number of chars left
auto n = end - in;
if(! n)
break;
// Chars we need to finish this code point
auto const need = needed(*in);
if(need == 0)
return false;
if(need <= n)
{
// Check a whole code point
if(! valid(in))
return false;
}
else
{
// Calculate how many chars we need
// to finish this partial code point
need_ = need - n;
// Save the partial code point
while(n--)
*p_++ = *in++;
BOOST_ASSERT(in == end);
BOOST_ASSERT(p_ <= cp_ + 4);
// Do partial validation on the incomplete
// code point, this is called "Fail fast"
// in Autobahn|Testsuite parlance.
return ! fail_fast();
}
}
return true;
}
bool
check_utf8(char const* p, std::size_t n)
{
utf8_checker c;
if(! c.write(reinterpret_cast<const uint8_t*>(p), n))
return false;
return c.finish();
}
} // detail
} // websocket
} // beast
} // boost
#endif // BOOST_BEAST_WEBSOCKET_DETAIL_UTF8_CHECKER_IPP

View File

@@ -19,6 +19,7 @@
#include <boost/beast/core/buffers_prefix.hpp> #include <boost/beast/core/buffers_prefix.hpp>
#include <boost/beast/core/buffers_suffix.hpp> #include <boost/beast/core/buffers_suffix.hpp>
#include <boost/beast/core/flat_static_buffer.hpp> #include <boost/beast/core/flat_static_buffer.hpp>
#include <boost/beast/core/read_size.hpp>
#include <boost/beast/core/stream_traits.hpp> #include <boost/beast/core/stream_traits.hpp>
#include <boost/beast/core/detail/bind_continuation.hpp> #include <boost/beast/core/detail/bind_continuation.hpp>
#include <boost/beast/core/detail/buffer.hpp> #include <boost/beast/core/detail/buffer.hpp>

View File

@@ -19,6 +19,7 @@
#include <boost/beast/websocket/detail/hybi13.hpp> #include <boost/beast/websocket/detail/hybi13.hpp>
#include <boost/beast/websocket/detail/impl_base.hpp> #include <boost/beast/websocket/detail/impl_base.hpp>
#include <boost/beast/websocket/detail/pmd_extension.hpp> #include <boost/beast/websocket/detail/pmd_extension.hpp>
#include <boost/beast/websocket/detail/prng.hpp>
#include <boost/beast/core/role.hpp> #include <boost/beast/core/role.hpp>
#include <boost/beast/core/stream_traits.hpp> #include <boost/beast/core/stream_traits.hpp>
#include <boost/beast/core/string.hpp> #include <boost/beast/core/string.hpp>
@@ -33,6 +34,7 @@
#include <limits> #include <limits>
#include <memory> #include <memory>
#include <type_traits> #include <type_traits>
#include <random>
namespace boost { namespace boost {
namespace beast { namespace beast {
@@ -857,7 +859,7 @@ public:
immediately or not, the handler will not be invoked from within immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. manner equivalent to using `net::post`.
@par Example @par Example
@code @code
ws.async_handshake("localhost", "/", ws.async_handshake("localhost", "/",