From dc52df351a474837c2d4fa98e850a78eab75a9c5 Mon Sep 17 00:00:00 2001 From: Damian Jarek Date: Sun, 10 Mar 2019 20:56:40 +0100 Subject: [PATCH] Enable more split compilation in websocket and http Signed-off-by: Damian Jarek --- include/boost/beast/http/impl/rfc7230.hpp | 167 +-------- include/boost/beast/http/impl/rfc7230.ipp | 176 ++++++++++ include/boost/beast/src.hpp | 4 + .../boost/beast/websocket/detail/hybi13.hpp | 46 +-- .../boost/beast/websocket/detail/hybi13.ipp | 67 ++++ .../beast/websocket/detail/pmd_extension.hpp | 291 ++------------- .../beast/websocket/detail/pmd_extension.ipp | 310 ++++++++++++++++ .../beast/websocket/detail/utf8_checker.hpp | 326 +---------------- .../beast/websocket/detail/utf8_checker.ipp | 331 ++++++++++++++++++ include/boost/beast/websocket/impl/read.hpp | 1 + include/boost/beast/websocket/stream.hpp | 4 +- 11 files changed, 947 insertions(+), 776 deletions(-) create mode 100644 include/boost/beast/http/impl/rfc7230.ipp create mode 100644 include/boost/beast/websocket/detail/hybi13.ipp create mode 100644 include/boost/beast/websocket/detail/pmd_extension.ipp create mode 100644 include/boost/beast/websocket/detail/utf8_checker.ipp diff --git a/include/boost/beast/http/impl/rfc7230.hpp b/include/boost/beast/http/impl/rfc7230.hpp index 0556eeae..11e424b4 100644 --- a/include/boost/beast/http/impl/rfc7230.hpp +++ b/include/boost/beast/http/impl/rfc7230.hpp @@ -86,12 +86,12 @@ private: increment(); } - template + BOOST_BEAST_DECL static std::string unquote(string_view sr); - template + BOOST_BEAST_DECL void increment(); }; @@ -132,46 +132,6 @@ cend() const -> return const_iterator{s_.end(), s_.end()}; } -template -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 -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 @@ -243,7 +203,7 @@ private: increment(); } - template + BOOST_BEAST_DECL void increment(); }; @@ -305,74 +265,6 @@ exists(T const& s) return find(s) != end(); } -template -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(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(pi.it - it_)}}; - it_ = pi.it; - return; - } - if(c != ',') - return err(); - need_comma = false; - ++it_; - } -} //------------------------------------------------------------------------------ @@ -445,7 +337,7 @@ private: increment(); } - template + BOOST_BEAST_DECL void increment(); }; @@ -486,53 +378,6 @@ cend() const -> return const_iterator{s_.end(), s_.end()}; } -template -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(it_ - p0)}; - return; - } - if(c != ',') - return err(); - need_comma = false; - ++it_; - } -} - template bool token_list:: @@ -570,5 +415,9 @@ validate_list(detail::basic_parsed_list< } // beast } // boost +#ifdef BOOST_BEAST_HEADER_ONLY +#include +#endif + #endif diff --git a/include/boost/beast/http/impl/rfc7230.ipp b/include/boost/beast/http/impl/rfc7230.ipp new file mode 100644 index 00000000..cf60cefa --- /dev/null +++ b/include/boost/beast/http/impl/rfc7230.ipp @@ -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 + +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(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(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(it_ - p0)}; + return; + } + if(c != ',') + return err(); + need_comma = false; + ++it_; + } +} + +} // http +} // beast +} // boost + +#endif // BOOST_BEAST_HTTP_IMPL_RFC7230_IPP diff --git a/include/boost/beast/src.hpp b/include/boost/beast/src.hpp index 732d3f7b..5d8f94a3 100644 --- a/include/boost/beast/src.hpp +++ b/include/boost/beast/src.hpp @@ -43,11 +43,15 @@ the program, with the macro BOOST_BEAST_SPLIT_COMPILATION defined. #include #include #include +#include #include #include +#include +#include #include #include +#include #include #include diff --git a/include/boost/beast/websocket/detail/hybi13.hpp b/include/boost/beast/websocket/detail/hybi13.hpp index 4a7e9585..7aeae2a3 100644 --- a/include/boost/beast/websocket/detail/hybi13.hpp +++ b/include/boost/beast/websocket/detail/hybi13.hpp @@ -13,13 +13,7 @@ #include #include #include -#include -#include -#include -#include #include -#include -#include namespace boost { namespace beast { @@ -32,47 +26,23 @@ using sec_ws_key_type = static_string< using sec_ws_accept_type = static_string< beast::detail::base64::encoded_size(20)>; -inline +BOOST_BEAST_DECL 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)); -} +make_sec_ws_key(sec_ws_key_type& key); -template +BOOST_BEAST_DECL 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 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))); -} + string_view key); } // detail } // websocket } // beast } // boost +#if BOOST_BEAST_HEADER_ONLY +#include +#endif + #endif diff --git a/include/boost/beast/websocket/detail/hybi13.ipp b/include/boost/beast/websocket/detail/hybi13.ipp new file mode 100644 index 00000000..b0ed5775 --- /dev/null +++ b/include/boost/beast/websocket/detail/hybi13.ipp @@ -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 +#include +#include + +#include +#include +#include + +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 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 diff --git a/include/boost/beast/websocket/detail/pmd_extension.hpp b/include/boost/beast/websocket/detail/pmd_extension.hpp index e6fa43c5..cdad99ba 100644 --- a/include/boost/beast/websocket/detail/pmd_extension.hpp +++ b/include/boost/beast/websocket/detail/pmd_extension.hpp @@ -11,13 +11,8 @@ #define BOOST_BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP #include -#include -#include -#include -#include #include #include -#include #include #include @@ -48,28 +43,24 @@ struct pmd_offer bool client_no_context_takeover; }; -template +BOOST_BEAST_DECL 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(i); -} +parse_bits(string_view s); + +BOOST_BEAST_DECL +void +pmd_read_impl(pmd_offer& offer, http::ext_list const& list); + +BOOST_BEAST_DECL +static_string<512> +pmd_write_impl(pmd_offer const& offer); + +BOOST_BEAST_DECL +static_string<512> +pmd_negotiate_impl( + pmd_offer& config, + pmd_offer const& offer, + permessage_deflate const& o); // Parse permessage-deflate request fields // @@ -78,126 +69,9 @@ void pmd_read(pmd_offer& offer, http::basic_fields 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{ fields["Sec-WebSocket-Extensions"]}; - 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; - } - } + detail::pmd_read_impl(offer, list); } // Set permessage-deflate fields for a client offer @@ -207,42 +81,7 @@ void pmd_write(http::basic_fields& fields, pmd_offer const& offer) { - static_string<512> s; - 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"; - } + auto s = detail::pmd_write_impl(offer); fields.set(http::field::sec_websocket_extensions, s); } @@ -263,102 +102,24 @@ pmd_negotiate( } config.accept = true; - 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; - } + auto s = detail::pmd_negotiate_impl(config, offer, o); if(config.accept) fields.set(http::field::sec_websocket_extensions, s); } // Normalize the server's response // -inline +BOOST_BEAST_DECL 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; - } -} +pmd_normalize(pmd_offer& offer); } // detail } // websocket } // beast } // boost +#if BOOST_BEAST_HEADER_ONLY +#include +#endif + #endif diff --git a/include/boost/beast/websocket/detail/pmd_extension.ipp b/include/boost/beast/websocket/detail/pmd_extension.ipp new file mode 100644 index 00000000..790998e8 --- /dev/null +++ b/include/boost/beast/websocket/detail/pmd_extension.ipp @@ -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 + +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(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 diff --git a/include/boost/beast/websocket/detail/utf8_checker.hpp b/include/boost/beast/websocket/detail/utf8_checker.hpp index a8de98ac..c997cfb6 100644 --- a/include/boost/beast/websocket/detail/utf8_checker.hpp +++ b/include/boost/beast/websocket/detail/utf8_checker.hpp @@ -12,8 +12,7 @@ #include #include -#include -#include + #include namespace boost { @@ -27,8 +26,7 @@ namespace detail { valid. The write function may be called incrementally with segmented UTF8 sequences. The finish function determines if all processed text is valid. */ -template -class utf8_checker_t +class utf8_checker { std::size_t need_ = 0; // chars we need to finish the code point std::uint8_t* p_ = cp_; // current position in temp buffer @@ -37,11 +35,13 @@ class utf8_checker_t public: /** Prepare to process text as valid utf8 */ + BOOST_BEAST_DECL void reset(); /** Check that all processed text is valid utf8 */ + BOOST_BEAST_DECL bool finish(); @@ -49,6 +49,7 @@ public: @return `true` if the text is valid utf8 or false otherwise. */ + BOOST_BEAST_DECL bool write(std::uint8_t const* in, std::size_t size); @@ -61,29 +62,10 @@ public: write(ConstBufferSequence const& bs); }; -template -void -utf8_checker_t<_>:: -reset() -{ - need_ = 0; - p_ = cp_; -} -template -bool -utf8_checker_t<_>:: -finish() -{ - auto const success = need_ == 0; - reset(); - return success; -} - -template template bool -utf8_checker_t<_>:: +utf8_checker:: write(ConstBufferSequence const& buffers) { static_assert( @@ -97,300 +79,18 @@ write(ConstBufferSequence const& buffers) return true; } -template + +BOOST_BEAST_DECL bool -utf8_checker_t<_>:: -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( - ((reinterpret_cast(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(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 -bool -check_utf8(char const* p, std::size_t n) -{ - utf8_checker c; - if(! c.write(reinterpret_cast(p), n)) - return false; - return c.finish(); -} +check_utf8(char const* p, std::size_t n); } // detail } // websocket } // beast } // boost +#if BOOST_BEAST_HEADER_ONLY +#include +#endif + #endif diff --git a/include/boost/beast/websocket/detail/utf8_checker.ipp b/include/boost/beast/websocket/detail/utf8_checker.ipp new file mode 100644 index 00000000..64a29345 --- /dev/null +++ b/include/boost/beast/websocket/detail/utf8_checker.ipp @@ -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 + +#include + +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( + ((reinterpret_cast(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(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(p), n)) + return false; + return c.finish(); +} + +} // detail +} // websocket +} // beast +} // boost + +#endif // BOOST_BEAST_WEBSOCKET_DETAIL_UTF8_CHECKER_IPP diff --git a/include/boost/beast/websocket/impl/read.hpp b/include/boost/beast/websocket/impl/read.hpp index 65ab6cc3..8be18e59 100644 --- a/include/boost/beast/websocket/impl/read.hpp +++ b/include/boost/beast/websocket/impl/read.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/include/boost/beast/websocket/stream.hpp b/include/boost/beast/websocket/stream.hpp index 77c57791..67ee357e 100644 --- a/include/boost/beast/websocket/stream.hpp +++ b/include/boost/beast/websocket/stream.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,7 @@ #include #include #include +#include namespace boost { namespace beast { @@ -857,7 +859,7 @@ public: immediately or not, the handler will not be invoked from within this function. Invocation of the handler will be performed in a manner equivalent to using `net::post`. - + @par Example @code ws.async_handshake("localhost", "/",