1 // 2 // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 // 4 // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 // 7 // Official repository: https://github.com/boostorg/beast 8 // 9 10 #ifndef BOOST_BEAST_WEBSOCKET_DETAIL_IMPL_BASE_HPP 11 #define BOOST_BEAST_WEBSOCKET_DETAIL_IMPL_BASE_HPP 12 13 #include <boost/beast/websocket/option.hpp> 14 #include <boost/beast/websocket/detail/frame.hpp> 15 #include <boost/beast/websocket/detail/pmd_extension.hpp> 16 #include <boost/beast/core/buffer_traits.hpp> 17 #include <boost/beast/core/role.hpp> 18 #include <boost/beast/http/empty_body.hpp> 19 #include <boost/beast/http/message.hpp> 20 #include <boost/beast/http/string_body.hpp> 21 #include <boost/beast/zlib/deflate_stream.hpp> 22 #include <boost/beast/zlib/inflate_stream.hpp> 23 #include <boost/beast/core/buffers_suffix.hpp> 24 #include <boost/beast/core/error.hpp> 25 #include <boost/beast/core/detail/clamp.hpp> 26 #include <boost/asio/buffer.hpp> 27 #include <cstdint> 28 #include <memory> 29 #include <stdexcept> 30 31 namespace boost { 32 namespace beast { 33 namespace websocket { 34 namespace detail { 35 36 //------------------------------------------------------------------------------ 37 38 template<bool deflateSupported> 39 struct impl_base; 40 41 template<> 42 struct impl_base<true> 43 { 44 // State information for the permessage-deflate extension 45 struct pmd_type 46 { 47 // `true` if current read message is compressed 48 bool rd_set = false; 49 50 zlib::deflate_stream zo; 51 zlib::inflate_stream zi; 52 }; 53 54 std::unique_ptr<pmd_type> pmd_; // pmd settings or nullptr 55 permessage_deflate pmd_opts_; // local pmd options 56 detail::pmd_offer pmd_config_; // offer (client) or negotiation (server) 57 58 // return `true` if current message is deflated 59 bool rd_deflatedboost::beast::websocket::detail::impl_base60 rd_deflated() const 61 { 62 return pmd_ && pmd_->rd_set; 63 } 64 65 // set whether current message is deflated 66 // returns `false` on protocol violation 67 bool rd_deflatedboost::beast::websocket::detail::impl_base68 rd_deflated(bool rsv1) 69 { 70 if(pmd_) 71 { 72 pmd_->rd_set = rsv1; 73 return true; 74 } 75 return ! rsv1; // pmd not negotiated 76 } 77 78 // Compress a buffer sequence 79 // Returns: `true` if more calls are needed 80 // 81 template<class ConstBufferSequence> 82 bool deflateboost::beast::websocket::detail::impl_base83 deflate( 84 net::mutable_buffer& out, 85 buffers_suffix<ConstBufferSequence>& cb, 86 bool fin, 87 std::size_t& total_in, 88 error_code& ec) 89 { 90 BOOST_ASSERT(out.size() >= 6); 91 auto& zo = this->pmd_->zo; 92 zlib::z_params zs; 93 zs.avail_in = 0; 94 zs.next_in = nullptr; 95 zs.avail_out = out.size(); 96 zs.next_out = out.data(); 97 for(auto in : beast::buffers_range_ref(cb)) 98 { 99 zs.avail_in = in.size(); 100 if(zs.avail_in == 0) 101 continue; 102 zs.next_in = in.data(); 103 zo.write(zs, zlib::Flush::none, ec); 104 if(ec) 105 { 106 if(ec != zlib::error::need_buffers) 107 return false; 108 BOOST_ASSERT(zs.avail_out == 0); 109 BOOST_ASSERT(zs.total_out == out.size()); 110 ec = {}; 111 break; 112 } 113 if(zs.avail_out == 0) 114 { 115 BOOST_ASSERT(zs.total_out == out.size()); 116 break; 117 } 118 BOOST_ASSERT(zs.avail_in == 0); 119 } 120 total_in = zs.total_in; 121 cb.consume(zs.total_in); 122 if(zs.avail_out > 0 && fin) 123 { 124 auto const remain = buffer_bytes(cb); 125 if(remain == 0) 126 { 127 // Inspired by Mark Adler 128 // https://github.com/madler/zlib/issues/149 129 // 130 // VFALCO We could do this flush twice depending 131 // on how much space is in the output. 132 zo.write(zs, zlib::Flush::block, ec); 133 BOOST_ASSERT(! ec || ec == zlib::error::need_buffers); 134 if(ec == zlib::error::need_buffers) 135 ec = {}; 136 if(ec) 137 return false; 138 if(zs.avail_out >= 6) 139 { 140 zo.write(zs, zlib::Flush::full, ec); 141 BOOST_ASSERT(! ec); 142 // remove flush marker 143 zs.total_out -= 4; 144 out = net::buffer(out.data(), zs.total_out); 145 return false; 146 } 147 } 148 } 149 ec = {}; 150 out = net::buffer(out.data(), zs.total_out); 151 return true; 152 } 153 154 void do_context_takeover_writeboost::beast::websocket::detail::impl_base155 do_context_takeover_write(role_type role) 156 { 157 if((role == role_type::client && 158 this->pmd_config_.client_no_context_takeover) || 159 (role == role_type::server && 160 this->pmd_config_.server_no_context_takeover)) 161 { 162 this->pmd_->zo.reset(); 163 } 164 } 165 166 void inflateboost::beast::websocket::detail::impl_base167 inflate( 168 zlib::z_params& zs, 169 zlib::Flush flush, 170 error_code& ec) 171 { 172 pmd_->zi.write(zs, flush, ec); 173 } 174 175 void do_context_takeover_readboost::beast::websocket::detail::impl_base176 do_context_takeover_read(role_type role) 177 { 178 if((role == role_type::client && 179 pmd_config_.server_no_context_takeover) || 180 (role == role_type::server && 181 pmd_config_.client_no_context_takeover)) 182 { 183 pmd_->zi.clear(); 184 } 185 } 186 187 template<class Body, class Allocator> 188 void 189 build_response_pmd( 190 http::response<http::string_body>& res, 191 http::request<Body, 192 http::basic_fields<Allocator>> const& req); 193 194 void on_response_pmdboost::beast::websocket::detail::impl_base195 on_response_pmd( 196 http::response<http::string_body> const& res) 197 { 198 detail::pmd_offer offer; 199 detail::pmd_read(offer, res); 200 // VFALCO see if offer satisfies pmd_config_, 201 // return an error if not. 202 pmd_config_ = offer; // overwrite for now 203 } 204 205 template<class Allocator> 206 void do_pmd_configboost::beast::websocket::detail::impl_base207 do_pmd_config( 208 http::basic_fields<Allocator> const& h) 209 { 210 detail::pmd_read(pmd_config_, h); 211 } 212 213 void set_option_pmdboost::beast::websocket::detail::impl_base214 set_option_pmd(permessage_deflate const& o) 215 { 216 if( o.server_max_window_bits > 15 || 217 o.server_max_window_bits < 9) 218 BOOST_THROW_EXCEPTION(std::invalid_argument{ 219 "invalid server_max_window_bits"}); 220 if( o.client_max_window_bits > 15 || 221 o.client_max_window_bits < 9) 222 BOOST_THROW_EXCEPTION(std::invalid_argument{ 223 "invalid client_max_window_bits"}); 224 if( o.compLevel < 0 || 225 o.compLevel > 9) 226 BOOST_THROW_EXCEPTION(std::invalid_argument{ 227 "invalid compLevel"}); 228 if( o.memLevel < 1 || 229 o.memLevel > 9) 230 BOOST_THROW_EXCEPTION(std::invalid_argument{ 231 "invalid memLevel"}); 232 pmd_opts_ = o; 233 } 234 235 void get_option_pmdboost::beast::websocket::detail::impl_base236 get_option_pmd(permessage_deflate& o) 237 { 238 o = pmd_opts_; 239 } 240 241 242 void build_request_pmdboost::beast::websocket::detail::impl_base243 build_request_pmd(http::request<http::empty_body>& req) 244 { 245 if(pmd_opts_.client_enable) 246 { 247 detail::pmd_offer config; 248 config.accept = true; 249 config.server_max_window_bits = 250 pmd_opts_.server_max_window_bits; 251 config.client_max_window_bits = 252 pmd_opts_.client_max_window_bits; 253 config.server_no_context_takeover = 254 pmd_opts_.server_no_context_takeover; 255 config.client_no_context_takeover = 256 pmd_opts_.client_no_context_takeover; 257 detail::pmd_write(req, config); 258 } 259 } 260 261 void open_pmdboost::beast::websocket::detail::impl_base262 open_pmd(role_type role) 263 { 264 if(((role == role_type::client && 265 pmd_opts_.client_enable) || 266 (role == role_type::server && 267 pmd_opts_.server_enable)) && 268 pmd_config_.accept) 269 { 270 detail::pmd_normalize(pmd_config_); 271 pmd_.reset(::new pmd_type); 272 if(role == role_type::client) 273 { 274 pmd_->zi.reset( 275 pmd_config_.server_max_window_bits); 276 pmd_->zo.reset( 277 pmd_opts_.compLevel, 278 pmd_config_.client_max_window_bits, 279 pmd_opts_.memLevel, 280 zlib::Strategy::normal); 281 } 282 else 283 { 284 pmd_->zi.reset( 285 pmd_config_.client_max_window_bits); 286 pmd_->zo.reset( 287 pmd_opts_.compLevel, 288 pmd_config_.server_max_window_bits, 289 pmd_opts_.memLevel, 290 zlib::Strategy::normal); 291 } 292 } 293 } 294 close_pmdboost::beast::websocket::detail::impl_base295 void close_pmd() 296 { 297 pmd_.reset(); 298 } 299 pmd_enabledboost::beast::websocket::detail::impl_base300 bool pmd_enabled() const 301 { 302 return pmd_ != nullptr; 303 } 304 305 std::size_t read_size_hint_pmdboost::beast::websocket::detail::impl_base306 read_size_hint_pmd( 307 std::size_t initial_size, 308 bool rd_done, 309 std::uint64_t rd_remain, 310 detail::frame_header const& rd_fh) const 311 { 312 using beast::detail::clamp; 313 std::size_t result; 314 BOOST_ASSERT(initial_size > 0); 315 if(! pmd_ || (! rd_done && ! pmd_->rd_set)) 316 { 317 // current message is uncompressed 318 319 if(rd_done) 320 { 321 // first message frame 322 result = initial_size; 323 goto done; 324 } 325 else if(rd_fh.fin) 326 { 327 // last message frame 328 BOOST_ASSERT(rd_remain > 0); 329 result = clamp(rd_remain); 330 goto done; 331 } 332 } 333 result = (std::max)( 334 initial_size, clamp(rd_remain)); 335 done: 336 BOOST_ASSERT(result != 0); 337 return result; 338 } 339 }; 340 341 //------------------------------------------------------------------------------ 342 343 template<> 344 struct impl_base<false> 345 { 346 // These stubs are for avoiding linking in the zlib 347 // code when permessage-deflate is not enabled. 348 349 bool rd_deflatedboost::beast::websocket::detail::impl_base350 rd_deflated() const 351 { 352 return false; 353 } 354 355 bool rd_deflatedboost::beast::websocket::detail::impl_base356 rd_deflated(bool rsv1) 357 { 358 return ! rsv1; 359 } 360 361 template<class ConstBufferSequence> 362 bool deflateboost::beast::websocket::detail::impl_base363 deflate( 364 net::mutable_buffer&, 365 buffers_suffix<ConstBufferSequence>&, 366 bool, 367 std::size_t&, 368 error_code&) 369 { 370 return false; 371 } 372 373 void do_context_takeover_writeboost::beast::websocket::detail::impl_base374 do_context_takeover_write(role_type) 375 { 376 } 377 378 void inflateboost::beast::websocket::detail::impl_base379 inflate( 380 zlib::z_params&, 381 zlib::Flush, 382 error_code&) 383 { 384 } 385 386 void do_context_takeover_readboost::beast::websocket::detail::impl_base387 do_context_takeover_read(role_type) 388 { 389 } 390 391 template<class Body, class Allocator> 392 void 393 build_response_pmd( 394 http::response<http::string_body>&, 395 http::request<Body, 396 http::basic_fields<Allocator>> const&); 397 398 void on_response_pmdboost::beast::websocket::detail::impl_base399 on_response_pmd( 400 http::response<http::string_body> const&) 401 { 402 } 403 404 template<class Allocator> 405 void do_pmd_configboost::beast::websocket::detail::impl_base406 do_pmd_config(http::basic_fields<Allocator> const&) 407 { 408 } 409 410 void set_option_pmdboost::beast::websocket::detail::impl_base411 set_option_pmd(permessage_deflate const& o) 412 { 413 if(o.client_enable || o.server_enable) 414 { 415 // Can't enable permessage-deflate 416 // when deflateSupported == false. 417 // 418 BOOST_THROW_EXCEPTION(std::invalid_argument{ 419 "deflateSupported == false"}); 420 } 421 } 422 423 void get_option_pmdboost::beast::websocket::detail::impl_base424 get_option_pmd(permessage_deflate& o) 425 { 426 o = {}; 427 o.client_enable = false; 428 o.server_enable = false; 429 } 430 431 void build_request_pmdboost::beast::websocket::detail::impl_base432 build_request_pmd( 433 http::request<http::empty_body>&) 434 { 435 } 436 open_pmdboost::beast::websocket::detail::impl_base437 void open_pmd(role_type) 438 { 439 } 440 close_pmdboost::beast::websocket::detail::impl_base441 void close_pmd() 442 { 443 } 444 pmd_enabledboost::beast::websocket::detail::impl_base445 bool pmd_enabled() const 446 { 447 return false; 448 } 449 450 std::size_t read_size_hint_pmdboost::beast::websocket::detail::impl_base451 read_size_hint_pmd( 452 std::size_t initial_size, 453 bool rd_done, 454 std::uint64_t rd_remain, 455 frame_header const& rd_fh) const 456 { 457 using beast::detail::clamp; 458 std::size_t result; 459 BOOST_ASSERT(initial_size > 0); 460 // compression is not supported 461 if(rd_done) 462 { 463 // first message frame 464 result = initial_size; 465 } 466 else if(rd_fh.fin) 467 { 468 // last message frame 469 BOOST_ASSERT(rd_remain > 0); 470 result = clamp(rd_remain); 471 } 472 else 473 { 474 result = (std::max)( 475 initial_size, clamp(rd_remain)); 476 } 477 BOOST_ASSERT(result != 0); 478 return result; 479 } 480 }; 481 482 } // detail 483 } // websocket 484 } // beast 485 } // boost 486 487 #endif 488