// // Copyright (c) 2013-2017 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_WEBSOCKET_IMPL_STREAM_IPP #define BEAST_WEBSOCKET_IMPL_STREAM_IPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace beast { namespace websocket { template template stream:: stream(Args&&... args) : stream_(std::forward(args)...) { } template void stream:: set_option(permessage_deflate const& o) { if( o.server_max_window_bits > 15 || o.server_max_window_bits < 9) throw std::invalid_argument{ "invalid server_max_window_bits"}; if( o.client_max_window_bits > 15 || o.client_max_window_bits < 9) throw std::invalid_argument{ "invalid client_max_window_bits"}; if( o.compLevel < 0 || o.compLevel > 9) throw std::invalid_argument{ "invalid compLevel"}; if( o.memLevel < 1 || o.memLevel > 9) throw std::invalid_argument{ "invalid memLevel"}; pmd_opts_ = o; } //------------------------------------------------------------------------------ template void stream:: reset() { failed_ = false; rd_.cont = false; wr_close_ = false; wr_.cont = false; wr_block_ = nullptr; // should be nullptr on close anyway ping_data_ = nullptr; // should be nullptr on close anyway stream_.buffer().consume( stream_.buffer().size()); } template template void stream:: do_accept( Decorator const& decorator, error_code& ec) { http::header_parser p; auto const bytes_used = http::read_some( next_layer(), stream_.buffer(), p, ec); if(ec) return; BOOST_ASSERT(p.got_header()); stream_.buffer().consume(bytes_used); do_accept(p.get(), decorator, ec); } template template void stream:: do_accept(http::header const& req, Decorator const& decorator, error_code& ec) { auto const res = build_response(req, decorator); http::write(stream_, res, ec); if(ec) return; if(res.status != 101) { ec = error::handshake_failed; // VFALCO TODO Respect keep alive setting, perform // teardown if Connection: close. return; } pmd_read(pmd_config_, req.fields); open(detail::role_type::server); } template template void stream:: do_handshake(response_type* res_p, boost::string_ref const& host, boost::string_ref const& resource, RequestDecorator const& decorator, error_code& ec) { response_type res; reset(); std::string key; { auto const req = build_request( key, host, resource, decorator); pmd_read(pmd_config_, req.fields); http::write(stream_, req, ec); } if(ec) return; http::read(next_layer(), stream_.buffer(), res, ec); if(ec) return; do_response(res, key, ec); if(res_p) swap(res, *res_p); } template template request_type stream:: build_request(std::string& key, boost::string_ref const& host, boost::string_ref const& resource, Decorator const& decorator) { request_type req; req.url = { resource.data(), resource.size() }; req.version = 11; req.method = "GET"; req.fields.insert("Host", host); req.fields.insert("Upgrade", "websocket"); req.fields.insert("Connection", "upgrade"); key = detail::make_sec_ws_key(maskgen_); req.fields.insert("Sec-WebSocket-Key", key); req.fields.insert("Sec-WebSocket-Version", "13"); if(pmd_opts_.client_enable) { detail::pmd_offer config; config.accept = true; config.server_max_window_bits = pmd_opts_.server_max_window_bits; config.client_max_window_bits = pmd_opts_.client_max_window_bits; config.server_no_context_takeover = pmd_opts_.server_no_context_takeover; config.client_no_context_takeover = pmd_opts_.client_no_context_takeover; detail::pmd_write( req.fields, config); } decorator(req); if(! req.fields.exists("User-Agent")) req.fields.insert("User-Agent", std::string("Beast/") + BEAST_VERSION_STRING); return req; } template template response_type stream:: build_response(request_type const& req, Decorator const& decorator) { auto const decorate = [&decorator](response_type& res) { decorator(res); if(! res.fields.exists("Server")) res.fields.insert("Server", std::string("Beast/") + BEAST_VERSION_STRING); }; auto err = [&](std::string const& text) { response_type res; res.status = 400; res.reason = http::reason_string(res.status); res.version = req.version; res.body = text; prepare(res, (is_keep_alive(req) && keep_alive_) ? http::connection::keep_alive : http::connection::close); decorate(res); return res; }; if(req.version < 11) return err("HTTP version 1.1 required"); if(req.method != "GET") return err("Wrong method"); if(! is_upgrade(req)) return err("Expected Upgrade request"); if(! req.fields.exists("Host")) return err("Missing Host"); if(! req.fields.exists("Sec-WebSocket-Key")) return err("Missing Sec-WebSocket-Key"); if(! http::token_list{req.fields["Upgrade"]}.exists("websocket")) return err("Missing websocket Upgrade token"); { auto const version = req.fields["Sec-WebSocket-Version"]; if(version.empty()) return err("Missing Sec-WebSocket-Version"); if(version != "13") { response_type res; res.status = 426; res.reason = http::reason_string(res.status); res.version = req.version; res.fields.insert("Sec-WebSocket-Version", "13"); prepare(res, (is_keep_alive(req) && keep_alive_) ? http::connection::keep_alive : http::connection::close); decorate(res); return res; } } response_type res; { detail::pmd_offer offer; detail::pmd_offer unused; pmd_read(offer, req.fields); pmd_negotiate( res.fields, unused, offer, pmd_opts_); } res.status = 101; res.reason = http::reason_string(res.status); res.version = req.version; res.fields.insert("Upgrade", "websocket"); res.fields.insert("Connection", "upgrade"); { auto const key = req.fields["Sec-WebSocket-Key"]; res.fields.insert("Sec-WebSocket-Accept", detail::make_sec_ws_accept(key)); } decorate(res); return res; } template void stream:: do_response(http::response_header const& res, boost::string_ref const& key, error_code& ec) { // VFALCO Review these error codes auto fail = [&]{ ec = error::response_failed; }; if(res.version < 11) return fail(); if(res.status != 101) return fail(); if(! is_upgrade(res)) return fail(); if(! http::token_list{res.fields["Upgrade"]}.exists("websocket")) return fail(); if(! res.fields.exists("Sec-WebSocket-Accept")) return fail(); if(res.fields["Sec-WebSocket-Accept"] != detail::make_sec_ws_accept(key)) return fail(); detail::pmd_offer offer; pmd_read(offer, res.fields); // VFALCO see if offer satisfies pmd_config_, // return an error if not. pmd_config_ = offer; // overwrite for now open(detail::role_type::client); } } // websocket } // beast #endif