]> git.proxmox.com Git - ceph.git/blob - ceph/src/Beast/include/beast/websocket/detail/pmd_extension.hpp
bump version to 12.2.2-pve1
[ceph.git] / ceph / src / Beast / include / beast / websocket / detail / pmd_extension.hpp
1 //
2 // Copyright (c) 2013-2017 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
8 #ifndef BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP
9 #define BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP
10
11 #include <beast/core/error.hpp>
12 #include <beast/core/consuming_buffers.hpp>
13 #include <beast/core/detail/ci_char_traits.hpp>
14 #include <beast/zlib/deflate_stream.hpp>
15 #include <beast/zlib/inflate_stream.hpp>
16 #include <beast/websocket/option.hpp>
17 #include <beast/http/rfc7230.hpp>
18 #include <boost/asio/buffer.hpp>
19 #include <utility>
20
21 namespace beast {
22 namespace websocket {
23 namespace detail {
24
25 // permessage-deflate offer parameters
26 //
27 // "context takeover" means:
28 // preserve sliding window across messages
29 //
30 struct pmd_offer
31 {
32 bool accept;
33
34 // 0 = absent, or 8..15
35 int server_max_window_bits;
36
37 // -1 = present, 0 = absent, or 8..15
38 int client_max_window_bits;
39
40 // `true` if server_no_context_takeover offered
41 bool server_no_context_takeover;
42
43 // `true` if client_no_context_takeover offered
44 bool client_no_context_takeover;
45 };
46
47 template<class = void>
48 int
49 parse_bits(boost::string_ref const& s)
50 {
51 if(s.size() == 0)
52 return -1;
53 if(s.size() > 2)
54 return -1;
55 if(s[0] < '1' || s[0] > '9')
56 return -1;
57 int i = 0;
58 for(auto c : s)
59 {
60 if(c < '0' || c > '9')
61 return -1;
62 i = 10 * i + (c - '0');
63 }
64 return i;
65 }
66
67 // Parse permessage-deflate request fields
68 //
69 template<class Fields>
70 void
71 pmd_read(pmd_offer& offer, Fields const& fields)
72 {
73 offer.accept = false;
74 offer.server_max_window_bits= 0;
75 offer.client_max_window_bits = 0;
76 offer.server_no_context_takeover = false;
77 offer.client_no_context_takeover = false;
78
79 using beast::detail::ci_equal;
80 http::ext_list list{
81 fields["Sec-WebSocket-Extensions"]};
82 for(auto const& ext : list)
83 {
84 if(ci_equal(ext.first, "permessage-deflate"))
85 {
86 for(auto const& param : ext.second)
87 {
88 if(ci_equal(param.first,
89 "server_max_window_bits"))
90 {
91 if(offer.server_max_window_bits != 0)
92 {
93 // The negotiation offer contains multiple
94 // extension parameters with the same name.
95 //
96 return; // MUST decline
97 }
98 if(param.second.empty())
99 {
100 // The negotiation offer extension
101 // parameter is missing the value.
102 //
103 return; // MUST decline
104 }
105 offer.server_max_window_bits =
106 parse_bits(param.second);
107 if( offer.server_max_window_bits < 8 ||
108 offer.server_max_window_bits > 15)
109 {
110 // The negotiation offer contains an
111 // extension parameter with an invalid value.
112 //
113 return; // MUST decline
114 }
115 }
116 else if(ci_equal(param.first,
117 "client_max_window_bits"))
118 {
119 if(offer.client_max_window_bits != 0)
120 {
121 // The negotiation offer contains multiple
122 // extension parameters with the same name.
123 //
124 return; // MUST decline
125 }
126 if(! param.second.empty())
127 {
128 offer.client_max_window_bits =
129 parse_bits(param.second);
130 if( offer.client_max_window_bits < 8 ||
131 offer.client_max_window_bits > 15)
132 {
133 // The negotiation offer contains an
134 // extension parameter with an invalid value.
135 //
136 return; // MUST decline
137 }
138 }
139 else
140 {
141 offer.client_max_window_bits = -1;
142 }
143 }
144 else if(ci_equal(param.first,
145 "server_no_context_takeover"))
146 {
147 if(offer.server_no_context_takeover)
148 {
149 // The negotiation offer contains multiple
150 // extension parameters with the same name.
151 //
152 return; // MUST decline
153 }
154 if(! param.second.empty())
155 {
156 // The negotiation offer contains an
157 // extension parameter with an invalid value.
158 //
159 return; // MUST decline
160 }
161 offer.server_no_context_takeover = true;
162 }
163 else if(ci_equal(param.first,
164 "client_no_context_takeover"))
165 {
166 if(offer.client_no_context_takeover)
167 {
168 // The negotiation offer contains multiple
169 // extension parameters with the same name.
170 //
171 return; // MUST decline
172 }
173 if(! param.second.empty())
174 {
175 // The negotiation offer contains an
176 // extension parameter with an invalid value.
177 //
178 return; // MUST decline
179 }
180 offer.client_no_context_takeover = true;
181 }
182 else
183 {
184 // The negotiation offer contains an extension
185 // parameter not defined for use in an offer.
186 //
187 return; // MUST decline
188 }
189 }
190 offer.accept = true;
191 return;
192 }
193 }
194 }
195
196 // Set permessage-deflate fields for a client offer
197 //
198 template<class Fields>
199 void
200 pmd_write(Fields& fields, pmd_offer const& offer)
201 {
202 std::string s;
203 s = "permessage-deflate";
204 if(offer.server_max_window_bits != 0)
205 {
206 if(offer.server_max_window_bits != -1)
207 {
208 s += "; server_max_window_bits=";
209 s += std::to_string(
210 offer.server_max_window_bits);
211 }
212 else
213 {
214 s += "; server_max_window_bits";
215 }
216 }
217 if(offer.client_max_window_bits != 0)
218 {
219 if(offer.client_max_window_bits != -1)
220 {
221 s += "; client_max_window_bits=";
222 s += std::to_string(
223 offer.client_max_window_bits);
224 }
225 else
226 {
227 s += "; client_max_window_bits";
228 }
229 }
230 if(offer.server_no_context_takeover)
231 {
232 s += "; server_no_context_takeover";
233 }
234 if(offer.client_no_context_takeover)
235 {
236 s += "; client_no_context_takeover";
237 }
238 fields.replace("Sec-WebSocket-Extensions", s);
239 }
240
241 // Negotiate a permessage-deflate client offer
242 //
243 template<class Fields>
244 void
245 pmd_negotiate(
246 Fields& fields,
247 pmd_offer& config,
248 pmd_offer const& offer,
249 permessage_deflate const& o)
250 {
251 if(! (offer.accept && o.server_enable))
252 {
253 config.accept = false;
254 return;
255 }
256 config.accept = true;
257
258 std::string s = "permessage-deflate";
259
260 config.server_no_context_takeover =
261 offer.server_no_context_takeover ||
262 o.server_no_context_takeover;
263 if(config.server_no_context_takeover)
264 s += "; server_no_context_takeover";
265
266 config.client_no_context_takeover =
267 o.client_no_context_takeover ||
268 offer.client_no_context_takeover;
269 if(config.client_no_context_takeover)
270 s += "; client_no_context_takeover";
271
272 if(offer.server_max_window_bits != 0)
273 config.server_max_window_bits = std::min(
274 offer.server_max_window_bits,
275 o.server_max_window_bits);
276 else
277 config.server_max_window_bits =
278 o.server_max_window_bits;
279 if(config.server_max_window_bits < 15)
280 {
281 // ZLib's deflateInit silently treats 8 as
282 // 9 due to a bug, so prevent 8 from being used.
283 //
284 if(config.server_max_window_bits < 9)
285 config.server_max_window_bits = 9;
286
287 s += "; server_max_window_bits=";
288 s += std::to_string(
289 config.server_max_window_bits);
290 }
291
292 switch(offer.client_max_window_bits)
293 {
294 case -1:
295 // extension parameter is present with no value
296 config.client_max_window_bits =
297 o.client_max_window_bits;
298 if(config.client_max_window_bits < 15)
299 {
300 s += "; client_max_window_bits=";
301 s += std::to_string(
302 config.client_max_window_bits);
303 }
304 break;
305
306 case 0:
307 /* extension parameter is absent.
308
309 If a received extension negotiation offer doesn't have the
310 "client_max_window_bits" extension parameter, the corresponding
311 extension negotiation response to the offer MUST NOT include the
312 "client_max_window_bits" extension parameter.
313 */
314 if(o.client_max_window_bits == 15)
315 config.client_max_window_bits = 15;
316 else
317 config.accept = false;
318 break;
319
320 default:
321 // extension parameter has value in [8..15]
322 config.client_max_window_bits = std::min(
323 o.client_max_window_bits,
324 offer.client_max_window_bits);
325 s += "; client_max_window_bits=";
326 s += std::to_string(
327 config.client_max_window_bits);
328 break;
329 }
330 if(config.accept)
331 fields.replace("Sec-WebSocket-Extensions", s);
332 }
333
334 // Normalize the server's response
335 //
336 inline
337 void
338 pmd_normalize(pmd_offer& offer)
339 {
340 if(offer.accept)
341 {
342 if( offer.server_max_window_bits == 0)
343 offer.server_max_window_bits = 15;
344
345 if( offer.client_max_window_bits == 0 ||
346 offer.client_max_window_bits == -1)
347 offer.client_max_window_bits = 15;
348 }
349 }
350
351 //--------------------------------------------------------------------
352
353 // Decompress into a DynamicBuffer
354 //
355 template<class InflateStream, class DynamicBuffer>
356 void
357 inflate(
358 InflateStream& zi,
359 DynamicBuffer& dynabuf,
360 boost::asio::const_buffer const& in,
361 error_code& ec)
362 {
363 using boost::asio::buffer_cast;
364 using boost::asio::buffer_size;
365 zlib::z_params zs;
366 zs.avail_in = buffer_size(in);
367 zs.next_in = buffer_cast<void const*>(in);
368 for(;;)
369 {
370 // VFALCO we could be smarter about the size
371 auto const bs = dynabuf.prepare(
372 read_size_helper(dynabuf, 65536));
373 auto const out = *bs.begin();
374 zs.avail_out = buffer_size(out);
375 zs.next_out = buffer_cast<void*>(out);
376 zi.write(zs, zlib::Flush::sync, ec);
377 dynabuf.commit(zs.total_out);
378 zs.total_out = 0;
379 if( ec == zlib::error::need_buffers ||
380 ec == zlib::error::end_of_stream)
381 {
382 ec = {};
383 break;
384 }
385 if(ec)
386 return;
387 }
388 }
389
390 // Compress a buffer sequence
391 // Returns: `true` if more calls are needed
392 //
393 template<class DeflateStream, class ConstBufferSequence>
394 bool
395 deflate(
396 DeflateStream& zo,
397 boost::asio::mutable_buffer& out,
398 consuming_buffers<ConstBufferSequence>& cb,
399 bool fin,
400 error_code& ec)
401 {
402 using boost::asio::buffer;
403 using boost::asio::buffer_cast;
404 using boost::asio::buffer_size;
405 BOOST_ASSERT(buffer_size(out) >= 6);
406 zlib::z_params zs;
407 zs.avail_in = 0;
408 zs.next_in = nullptr;
409 zs.avail_out = buffer_size(out);
410 zs.next_out = buffer_cast<void*>(out);
411 for(auto const& in : cb)
412 {
413 zs.avail_in = buffer_size(in);
414 if(zs.avail_in == 0)
415 continue;
416 zs.next_in = buffer_cast<void const*>(in);
417 zo.write(zs, zlib::Flush::none, ec);
418 if(ec)
419 {
420 if(ec != zlib::error::need_buffers)
421 return false;
422 BOOST_ASSERT(zs.avail_out == 0);
423 BOOST_ASSERT(zs.total_out == buffer_size(out));
424 ec = {};
425 break;
426 }
427 if(zs.avail_out == 0)
428 {
429 BOOST_ASSERT(zs.total_out == buffer_size(out));
430 break;
431 }
432 BOOST_ASSERT(zs.avail_in == 0);
433 }
434 cb.consume(zs.total_in);
435 if(zs.avail_out > 0 && fin)
436 {
437 auto const remain = buffer_size(cb);
438 if(remain == 0)
439 {
440 // Inspired by Mark Adler
441 // https://github.com/madler/zlib/issues/149
442 //
443 // VFALCO We could do this flush twice depending
444 // on how much space is in the output.
445 zo.write(zs, zlib::Flush::block, ec);
446 BOOST_ASSERT(! ec || ec == zlib::error::need_buffers);
447 if(ec == zlib::error::need_buffers)
448 ec = {};
449 if(ec)
450 return false;
451 if(zs.avail_out >= 6)
452 {
453 zo.write(zs, zlib::Flush::full, ec);
454 BOOST_ASSERT(! ec);
455 // remove flush marker
456 zs.total_out -= 4;
457 out = buffer(
458 buffer_cast<void*>(out), zs.total_out);
459 return false;
460 }
461 }
462 }
463 out = buffer(
464 buffer_cast<void*>(out), zs.total_out);
465 return true;
466 }
467
468 } // detail
469 } // websocket
470 } // beast
471
472 #endif