diff --git a/doc/qbk/release_notes.qbk b/doc/qbk/release_notes.qbk index 989f4631..451eedb5 100644 --- a/doc/qbk/release_notes.qbk +++ b/doc/qbk/release_notes.qbk @@ -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 diff --git a/include/boost/beast/websocket/impl/handshake.hpp b/include/boost/beast/websocket/impl/handshake.hpp index 19431afc..806445b0 100644 --- a/include/boost/beast/websocket/impl/handshake.hpp +++ b/include/boost/beast/websocket/impl/handshake.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -43,7 +44,10 @@ class stream::handshake_op // VFALCO This really should be two separate // composed operations, to save on memory http::request req; - response_type res; + http::response_parser< + typename response_type::body_type> p; + flat_buffer fb; + bool overflow; }; boost::weak_ptr 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(); } //------------------------------------------------------------------------------ diff --git a/test/beast/websocket/handshake.cpp b/test/beast/websocket/handshake.cpp index 0c38b851..e3eb16a3 100644 --- a/test/beast/websocket/handshake.cpp +++ b/test/beast/websocket/handshake.cpp @@ -17,6 +17,7 @@ #include #include +#include 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 ws1(ioc); + 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 ws1(ioc); + 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(); } };