Large WebSocket Upgrade response no longer overflows

fix #1460
This commit is contained in:
Vinnie Falco
2019-02-20 16:03:09 -08:00
parent 9c83a9c6e7
commit c681241d70
3 changed files with 191 additions and 16 deletions

View File

@ -56,7 +56,7 @@
* All asynchronous operations use Asio's
[@boost:/doc/html/boost_asio/reference/async_initiate.html `async_initiate`]
for efficient integration with Coroutines TS.
* New [@boost:/doc/html/beast/example/websocket/server/chat-multi websocket-chat-multi]
* New [@../../example/websocket/server/chat-multi websocket-chat-multi]
multi-threaded websocket chat server with a JavaScript browser client.
* New [link beast.using_io.asio_refresher Networking Refresher] explains basic concepts.
* OpenSSL is required to build tests and examples
@ -262,6 +262,8 @@ Enlarged scope
* ([issue 1418]) `test::stream`
maintains a handler work guard
* ([issue 1460]) Large WebSocket Upgrade response no longer overflows
* `buffers_cat`
correctly skips empty buffers when iterated

View File

@ -17,6 +17,7 @@
#include <boost/beast/http/read.hpp>
#include <boost/beast/http/write.hpp>
#include <boost/beast/core/async_op_base.hpp>
#include <boost/beast/core/flat_buffer.hpp>
#include <boost/beast/core/stream_traits.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/assert.hpp>
@ -43,7 +44,10 @@ class stream<NextLayer, deflateSupported>::handshake_op
// VFALCO This really should be two separate
// composed operations, to save on memory
http::request<http::empty_body> req;
response_type res;
http::response_parser<
typename response_type::body_type> p;
flat_buffer fb;
bool overflow;
};
boost::weak_ptr<impl_type> wp_;
@ -100,16 +104,52 @@ public:
// read HTTP response
BOOST_ASIO_CORO_YIELD
http::async_read(impl.stream,
impl.rd_buf, d_.res,
impl.rd_buf, d_.p,
std::move(*this));
if(ec == http::error::buffer_overflow)
{
// If the response overflows the internal
// read buffer, switch to a dynamically
// allocated flat buffer.
d_.fb.commit(net::buffer_copy(
d_.fb.prepare(impl.rd_buf.size()),
impl.rd_buf.data()));
impl.rd_buf.clear();
BOOST_ASIO_CORO_YIELD
http::async_read(impl.stream,
d_.fb, d_.p, std::move(*this));
if(! ec)
{
// Copy any leftovers back into the read
// buffer, since this represents websocket
// frame data.
if(d_.fb.size() <= impl.rd_buf.capacity())
{
impl.rd_buf.commit(net::buffer_copy(
impl.rd_buf.prepare(d_.fb.size()),
d_.fb.data()));
}
else
{
ec = http::error::buffer_overflow;
}
}
// Do this before the upcall
d_.fb.clear();
}
if(impl.check_stop_now(ec))
goto upcall;
// success
impl.reset_idle();
impl.on_response(d_.res, key_, ec);
impl.on_response(d_.p.get(), key_, ec);
if(res_p_)
swap(d_.res, *res_p_);
swap(d_.p.get(), *res_p_);
upcall:
this->invoke(cont ,ec);
@ -130,24 +170,62 @@ do_handshake(
RequestDecorator const& decorator,
error_code& ec)
{
response_type res;
impl_->change_status(status::handshake);
impl_->reset();
auto& impl = *impl_;
impl.change_status(status::handshake);
impl.reset();
detail::sec_ws_key_type key;
{
auto const req = impl_->build_request(
auto const req = impl.build_request(
key, host, target, decorator);
this->impl_->do_pmd_config(req);
http::write(impl_->stream, req, ec);
impl.do_pmd_config(req);
http::write(impl.stream, req, ec);
}
if(ec)
if(impl.check_stop_now(ec))
return;
http::read(next_layer(), impl_->rd_buf, res, ec);
if(ec)
http::response_parser<
typename response_type::body_type> p;
http::read(next_layer(), impl.rd_buf, p, ec);
if(ec == http::error::buffer_overflow)
{
// If the response overflows the internal
// read buffer, switch to a dynamically
// allocated flat buffer.
flat_buffer fb;
fb.commit(net::buffer_copy(
fb.prepare(impl.rd_buf.size()),
impl.rd_buf.data()));
impl.rd_buf.clear();
http::read(next_layer(), fb, p, ec);;
if(! ec)
{
// Copy any leftovers back into the read
// buffer, since this represents websocket
// frame data.
if(fb.size() <= impl.rd_buf.capacity())
{
impl.rd_buf.commit(net::buffer_copy(
impl.rd_buf.prepare(fb.size()),
fb.data()));
}
else
{
ec = http::error::buffer_overflow;
}
}
}
if(impl.check_stop_now(ec))
return;
impl_->on_response(res, key, ec);
impl.on_response(p.get(), key, ec);
if(impl.check_stop_now(ec))
return;
if(res_p)
*res_p = std::move(res);
*res_p = p.release();
}
//------------------------------------------------------------------------------

View File

@ -17,6 +17,7 @@
#include <boost/asio/io_context.hpp>
#include <boost/asio/strand.hpp>
#include <thread>
namespace boost {
namespace beast {
@ -609,6 +610,99 @@ public:
}
}
// https://github.com/boostorg/beast/issues/1460
void
testIssue1460()
{
net::io_context ioc;
auto const make_big = [](response_type& res)
{
res.insert("Date", "Mon, 18 Feb 2019 12:48:36 GMT");
res.insert("Set-Cookie",
"__cfduid=de1e209833e7f05aaa1044c6d448994761550494116; "
"expires=Tue, 18-Feb-20 12:48:36 GMT; path=/; domain=.cryptofacilities.com; HttpOnly; Secure");
res.insert("Feature-Policy",
"accelerometer 'none'; ambient-light-sensor 'none'; "
"animations 'none'; autoplay 'none'; camera 'none'; document-write 'none'; "
"encrypted-media 'none'; geolocation 'none'; gyroscope 'none'; legacy-image-formats 'none'; "
"magnetometer 'none'; max-downscaling-image 'none'; microphone 'none'; midi 'none'; "
"payment 'none'; picture-in-picture 'none'; unsized-media 'none'; usb 'none'; vr 'none'");
res.insert("Referrer-Policy", "origin");
res.insert("Strict-Transport-Security", "max-age=15552000; includeSubDomains; preload");
res.insert("X-Content-Type-Options", "nosniff");
res.insert("Content-Security-Policy",
"default-src 'none'; manifest-src 'self'; object-src 'self'; "
"child-src 'self' https://www.google.com; "
"font-src 'self' https://use.typekit.net https://maxcdn.bootstrapcdn.com https://fonts.gstatic.com data:; "
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://ajax.cloudflare.com https://use.typekit.net "
"https://www.googletagmanager.com https://www.google-analytics.com https://www.google.com https://www.googleadservices.com "
"https://googleads.g.doubleclick.net https://www.gstatic.com; connect-src 'self' wss://*.cryptofacilities.com/ws/v1 wss://*.cryptofacilities.com/ws/indices "
"https://uat.cryptofacilities.com https://uat.cf0.io wss://*.cf0.io https://www.googletagmanager.com https://www.google-analytics.com https://www.google.com "
"https://fonts.googleapis.com https://google-analytics.com https://use.typekit.net https://p.typekit.net https://fonts.gstatic.com https://www.gstatic.com "
"https://chart.googleapis.com; worker-src 'self'; img-src 'self' https://chart.googleapis.com https://p.typekit.net https://www.google.co.uk https://www.google.com "
"https://www.google-analytics.com https://stats.g.doubleclick.net data:; style-src 'self' 'unsafe-inline' https://use.typekit.net https://p.typekit.net "
"https://fonts.googleapis.com https://maxcdn.bootstrapcdn.com");
res.insert("X-Frame-Options", "SAMEORIGIN");
res.insert("X-Xss-Protection", "1; mode=block");
res.insert("Expect-CT", "max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"");
res.insert("Server", "cloudflare");
res.insert("CF-RAY", "4ab09be1a9d0cb06-ARN");
res.insert("Bulk",
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************"
"****************************************************************************************************");
};
{
stream<test::stream> ws1(ioc);
stream<test::stream> ws2(ioc);
test::connect(ws1.next_layer(), ws2.next_layer());
ws2.set_option(stream_base::decorator(make_big));
error_code ec;
ws2.async_accept(test::success_handler());
std::thread t(
[&ioc]
{
ioc.run();
ioc.restart();
});
ws1.handshake("test", "/", ec);
BEAST_EXPECTS(! ec, ec.message());
t.join();
}
{
stream<test::stream> ws1(ioc);
stream<test::stream> ws2(ioc);
test::connect(ws1.next_layer(), ws2.next_layer());
ws2.set_option(stream_base::decorator(make_big));
error_code ec;
ws2.async_accept(test::success_handler());
ws1.async_handshake("test", "/", test::success_handler());
ioc.run();
ioc.restart();
}
}
void
run() override
{
@ -618,6 +712,7 @@ public:
testExtNegotiate();
testMoveOnly();
testAsync();
testIssue1460();
}
};