// // Copyright (c) 2016-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) // // Official repository: https://github.com/boostorg/beast // #ifndef BOOST_BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP #define BOOST_BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP #include #include #include #include #include #include #include #include #include #include namespace boost { namespace beast { namespace websocket { namespace detail { // permessage-deflate offer parameters // // "context takeover" means: // preserve sliding window across messages // struct pmd_offer { bool accept; // 0 = absent, or 8..15 int server_max_window_bits; // -1 = present, 0 = absent, or 8..15 int client_max_window_bits; // `true` if server_no_context_takeover offered bool server_no_context_takeover; // `true` if client_no_context_takeover offered bool client_no_context_takeover; }; template 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 // template 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; } } } // Set permessage-deflate fields for a client offer // template 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"; } fields.set(http::field::sec_websocket_extensions, s); } // Negotiate a permessage-deflate client offer // template void pmd_negotiate( http::basic_fields& fields, pmd_offer& config, pmd_offer const& offer, permessage_deflate const& o) { if(! (offer.accept && o.server_enable)) { config.accept = false; return; } 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; } if(config.accept) fields.set(http::field::sec_websocket_extensions, s); } // Normalize the server's response // inline 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