]>
Commit | Line | Data |
---|---|---|
92f5a8d4 TL |
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 | |
60 | 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 | |
68 | 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 | |
83 | 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 | |
155 | 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 | |
167 | 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 | |
176 | 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 | |
195 | 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 | |
207 | do_pmd_config( | |
208 | http::basic_fields<Allocator> const& h) | |
209 | { | |
210 | detail::pmd_read(pmd_config_, h); | |
211 | } | |
212 | ||
213 | void | |
214 | 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 | |
236 | get_option_pmd(permessage_deflate& o) | |
237 | { | |
238 | o = pmd_opts_; | |
239 | } | |
240 | ||
241 | ||
242 | void | |
243 | 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 | |
262 | 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 | ||
295 | void close_pmd() | |
296 | { | |
297 | pmd_.reset(); | |
298 | } | |
299 | ||
300 | bool pmd_enabled() const | |
301 | { | |
302 | return pmd_ != nullptr; | |
303 | } | |
304 | ||
305 | std::size_t | |
306 | 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 | |
350 | rd_deflated() const | |
351 | { | |
352 | return false; | |
353 | } | |
354 | ||
355 | bool | |
356 | rd_deflated(bool rsv1) | |
357 | { | |
358 | return ! rsv1; | |
359 | } | |
360 | ||
361 | template<class ConstBufferSequence> | |
362 | bool | |
363 | 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 | |
374 | do_context_takeover_write(role_type) | |
375 | { | |
376 | } | |
377 | ||
378 | void | |
379 | inflate( | |
380 | zlib::z_params&, | |
381 | zlib::Flush, | |
382 | error_code&) | |
383 | { | |
384 | } | |
385 | ||
386 | void | |
387 | 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 | |
399 | on_response_pmd( | |
400 | http::response<http::string_body> const&) | |
401 | { | |
402 | } | |
403 | ||
404 | template<class Allocator> | |
405 | void | |
406 | do_pmd_config(http::basic_fields<Allocator> const&) | |
407 | { | |
408 | } | |
409 | ||
410 | void | |
411 | 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 | |
424 | get_option_pmd(permessage_deflate& o) | |
425 | { | |
426 | o = {}; | |
427 | o.client_enable = false; | |
428 | o.server_enable = false; | |
429 | } | |
430 | ||
431 | void | |
432 | build_request_pmd( | |
433 | http::request<http::empty_body>&) | |
434 | { | |
435 | } | |
436 | ||
437 | void open_pmd(role_type) | |
438 | { | |
439 | } | |
440 | ||
441 | void close_pmd() | |
442 | { | |
443 | } | |
444 | ||
445 | bool pmd_enabled() const | |
446 | { | |
447 | return false; | |
448 | } | |
449 | ||
450 | std::size_t | |
451 | 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 |