// // 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 // // Test that header file is self-contained. #include #include #include #include #include "test.hpp" namespace boost { namespace beast { namespace websocket { class accept_test : public unit_test::suite //: public websocket_test_suite { public: class res_decorator { bool& b_; public: res_decorator(res_decorator const&) = default; explicit res_decorator(bool& b) : b_(b) { } void operator()(response_type&) const { this->b_ = true; } }; template static net::const_buffer sbuf(const char (&s)[N]) { return net::const_buffer(&s[0], N-1); } static void fail_loop( std::function&)> f, std::chrono::steady_clock::duration amount = std::chrono::seconds(5)) { using clock_type = std::chrono::steady_clock; auto const expires_at = clock_type::now() + amount; net::io_context ioc; for(std::size_t n = 0;;++n) { test::fail_count fc(n); try { stream ws(ioc, fc); auto tr = connect(ws.next_layer()); f(ws); break; } catch(system_error const& se) { // VFALCO Commented this out after the short // read change, because it converts test_failure // into http::partial_message // boost::ignore_unused(se); #if 0 if(! BEAST_EXPECTS( se.code() == test::error::test_failure, se.code().message())) throw; #endif if(! BEAST_EXPECTS( clock_type::now() < expires_at, "a test timeout occurred")) break; } } } template void testMatrix(Api api) { net::io_context ioc; // request in stream fail_loop([&](stream& ws) { ws.next_layer().append( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n"); ws.next_layer().read_size(20); api.accept(ws); }); // request in stream, decorator fail_loop([&](stream& ws) { ws.next_layer().append( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n"); ws.next_layer().read_size(20); bool called = false; api.accept_ex(ws, res_decorator{called}); BEAST_EXPECT(called); }); // request in buffers fail_loop([&](stream& ws) { api.accept(ws, sbuf( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" )); }); // request in buffers, decorator fail_loop([&](stream& ws) { bool called = false; api.accept_ex(ws, sbuf( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n"), res_decorator{called}); BEAST_EXPECT(called); }); // request in buffers and stream fail_loop([&](stream& ws) { ws.next_layer().append( "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n"); ws.next_layer().read_size(16); api.accept(ws, sbuf( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" )); // VFALCO validate contents of ws.next_layer().str? }); // request in buffers and stream, decorator fail_loop([&](stream& ws) { ws.next_layer().append( "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n"); ws.next_layer().read_size(16); bool called = false; api.accept_ex(ws, sbuf( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n"), res_decorator{called}); BEAST_EXPECT(called); }); // request in message { request_type req; req.method(http::verb::get); req.target("/"); req.version(11); req.insert(http::field::host, "localhost"); req.insert(http::field::upgrade, "websocket"); req.insert(http::field::connection, "upgrade"); req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); req.insert(http::field::sec_websocket_version, "13"); fail_loop([&](stream& ws) { api.accept(ws, req); }); } // request in message, decorator { request_type req; req.method(http::verb::get); req.target("/"); req.version(11); req.insert(http::field::host, "localhost"); req.insert(http::field::upgrade, "websocket"); req.insert(http::field::connection, "upgrade"); req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); req.insert(http::field::sec_websocket_version, "13"); fail_loop([&](stream& ws) { bool called = false; api.accept_ex(ws, req, res_decorator{called}); BEAST_EXPECT(called); }); } // request in message, close frame in stream { request_type req; req.method(http::verb::get); req.target("/"); req.version(11); req.insert(http::field::host, "localhost"); req.insert(http::field::upgrade, "websocket"); req.insert(http::field::connection, "upgrade"); req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); req.insert(http::field::sec_websocket_version, "13"); fail_loop([&](stream& ws) { ws.next_layer().append("\x88\x82\xff\xff\xff\xff\xfc\x17"); api.accept(ws, req); try { static_buffer<1> b; api.read(ws, b); fail("success", __FILE__, __LINE__); } catch(system_error const& e) { if(e.code() != websocket::error::closed) throw; } }); } // failed handshake (missing Sec-WebSocket-Key) fail_loop([&](stream& ws) { ws.next_layer().append( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n"); ws.next_layer().read_size(20); try { api.accept(ws); BEAST_FAIL(); } catch(system_error const& e) { if( e.code() != websocket::error::no_sec_key && e.code() != net::error::eof) throw; } }); } template void testOversized(Api const& api) { net::io_context ioc; auto const big = [] { std::string s; s += "X1: " + std::string(2000, '*') + "\r\n"; return s; }(); // request in stream { stream ws{ioc, "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" + big + "\r\n"}; auto tr = connect(ws.next_layer()); try { api.accept(ws); BEAST_FAIL(); } catch(system_error const& se) { // VFALCO Its the http error category... BEAST_EXPECTS( se.code() == http::error::buffer_overflow, se.code().message()); } } // request in stream, decorator { stream ws{ioc, "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" + big + "\r\n"}; auto tr = connect(ws.next_layer()); try { bool called = false; api.accept_ex(ws, res_decorator{called}); BEAST_FAIL(); } catch(system_error const& se) { // VFALCO Its the http error category... BEAST_EXPECTS( se.code() == http::error::buffer_overflow, se.code().message()); } } // request in buffers { stream ws{ioc}; auto tr = connect(ws.next_layer()); try { api.accept(ws, net::buffer( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" + big + "\r\n" )); BEAST_FAIL(); } catch(system_error const& se) { BEAST_EXPECTS( se.code() == error::buffer_overflow, se.code().message()); } } // request in buffers, decorator { stream ws{ioc}; auto tr = connect(ws.next_layer()); try { bool called = false; api.accept_ex(ws, net::buffer( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" + big + "\r\n"), res_decorator{called}); BEAST_FAIL(); } catch(system_error const& se) { BEAST_EXPECTS( se.code() == error::buffer_overflow, se.code().message()); } } // request in buffers and stream { stream ws{ioc, "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" + big + "\r\n"}; auto tr = connect(ws.next_layer()); try { api.accept(ws, websocket_test_suite::sbuf( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" )); BEAST_FAIL(); } catch(system_error const& se) { BEAST_EXPECTS( se.code() == http::error::buffer_overflow, se.code().message()); } } // request in buffers and stream, decorator { stream ws{ioc, "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" + big + "\r\n"}; auto tr = connect(ws.next_layer()); try { bool called = false; api.accept_ex(ws, websocket_test_suite::sbuf( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n"), res_decorator{called}); BEAST_FAIL(); } catch(system_error const& se) { BEAST_EXPECTS( se.code() == http::error::buffer_overflow, se.code().message()); } } } void testInvalidInputs() { net::io_context ioc; auto const check = [&](error_code ev, string_view s) { for(int i = 0; i < 3; ++i) { std::size_t n; switch(i) { default: case 0: n = 1; break; case 1: n = s.size() / 2; break; case 2: n = s.size() - 1; break; } stream ws(ioc); auto tr = connect(ws.next_layer()); ws.next_layer().append( s.substr(n, s.size() - n)); tr.close(); try { ws.accept(net::buffer(s.data(), n)); BEAST_EXPECTS(! ev, ev.message()); } catch(system_error const& se) { BEAST_EXPECTS(se.code() == ev, se.what()); } } }; // bad version check(error::bad_http_version, "GET / HTTP/1.0\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: keep-alive,upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // bad method check(error::bad_method, "POST / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: keep-alive,upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // no Host check(error::no_host, "GET / HTTP/1.1\r\n" "Upgrade: WebSocket\r\n" "Connection: keep-alive,upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // no Connection check(error::no_connection, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // no Connection upgrade check(error::no_connection_upgrade, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: keep-alive\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // no Upgrade check(error::no_upgrade, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // no Upgrade websocket check(error::no_upgrade_websocket, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: HTTP/2\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // no Sec-WebSocket-Key check(error::no_sec_key, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: keep-alive,upgrade\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // bad Sec-WebSocket-Key check(error::bad_sec_key, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQdGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // no Sec-WebSocket-Version check(error::no_sec_version, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: keep-alive,upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "\r\n" ); // bad Sec-WebSocket-Version check(error::bad_sec_version, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: keep-alive,upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 1\r\n" "\r\n" ); // bad Sec-WebSocket-Version check(error::bad_sec_version, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 12\r\n" "\r\n" ); // valid request check({}, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); } void testEndOfStream() { net::io_context ioc; { stream ws(ioc); auto tr = connect(ws.next_layer()); tr.close(); try { test_sync_api api; api.accept(ws, net::const_buffer{}); BEAST_FAIL(); } catch(system_error const& se) { BEAST_EXPECTS( se.code() == error::closed, se.code().message()); } } { stream ws(ioc); auto tr = connect(ws.next_layer()); tr.close(); try { test_async_api api; api.accept(ws, net::const_buffer{}); BEAST_FAIL(); } catch(system_error const& se) { BEAST_EXPECTS( se.code() == error::closed, se.code().message()); } } } void testAsync() { using tcp = net::ip::tcp; net::io_context ioc; // success, no timeout { stream ws1(ioc); stream ws2(ioc); test::connect(ws1.next_layer(), ws2.next_layer()); ws1.async_handshake("test", "/", test::success_handler()); ws2.async_accept(test::success_handler()); test::run_for(ioc, std::chrono::seconds(1)); } { stream ws1(ioc); stream ws2(ioc); test::connect(ws1.next_layer(), ws2.next_layer()); ws1.async_handshake("test", "/", test::success_handler()); ws2.async_accept(test::success_handler()); test::run_for(ioc, std::chrono::seconds(1)); } // success, timeout enabled { stream ws1(ioc); stream ws2(ioc); test::connect(ws1.next_layer(), ws2.next_layer()); ws1.set_option(stream_base::timeout{ std::chrono::milliseconds(50), stream_base::none(), false}); ws1.async_accept(test::success_handler()); ws2.async_handshake("test", "/", test::success_handler()); test::run_for(ioc, std::chrono::seconds(1)); } { stream ws1(ioc); stream ws2(ioc); test::connect(ws1.next_layer(), ws2.next_layer()); ws1.set_option(stream_base::timeout{ std::chrono::milliseconds(50), stream_base::none(), false}); ws1.async_accept(test::success_handler()); ws2.async_handshake("test", "/", test::success_handler()); test::run_for(ioc, std::chrono::seconds(1)); } // timeout { stream ws1(ioc); stream ws2(ioc); test::connect(ws1.next_layer(), ws2.next_layer()); ws1.set_option(stream_base::timeout{ std::chrono::milliseconds(50), stream_base::none(), false}); ws1.async_accept(test::fail_handler(beast::error::timeout)); test::run_for(ioc, std::chrono::seconds(1)); } { stream ws1(ioc); stream ws2(ioc); test::connect(ws1.next_layer(), ws2.next_layer()); ws1.set_option(stream_base::timeout{ std::chrono::milliseconds(50), stream_base::none(), false}); ws1.async_accept(test::fail_handler(beast::error::timeout)); test::run_for(ioc, std::chrono::seconds(1)); } // abandoned operation { { stream ws1(ioc); ws1.async_accept(test::fail_handler( net::error::operation_aborted)); } test::run(ioc); } { { stream ws1(ioc); string_view s = "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n"; error_code ec; http::request_parser p; p.put(net::const_buffer(s.data(), s.size()), ec); ws1.async_accept(p.get(), test::fail_handler( net::error::operation_aborted)); } test::run(ioc); } } void run() override { testMatrix(test_sync_api{}); testMatrix(test_async_api{}); testOversized(test_sync_api{}); testOversized(test_async_api{}); testInvalidInputs(); testEndOfStream(); testAsync(); } }; BEAST_DEFINE_TESTSUITE(beast,websocket,accept); } // websocket } // beast } // boost