2 // Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
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)
8 #ifndef BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP
9 #define BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP
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>
25 // permessage-deflate offer parameters
27 // "context takeover" means:
28 // preserve sliding window across messages
34 // 0 = absent, or 8..15
35 int server_max_window_bits;
37 // -1 = present, 0 = absent, or 8..15
38 int client_max_window_bits;
40 // `true` if server_no_context_takeover offered
41 bool server_no_context_takeover;
43 // `true` if client_no_context_takeover offered
44 bool client_no_context_takeover;
47 template<class = void>
49 parse_bits(boost::string_ref const& s)
55 if(s[0] < '1' || s[0] > '9')
60 if(c < '0' || c > '9')
62 i = 10 * i + (c - '0');
67 // Parse permessage-deflate request fields
69 template<class Fields>
71 pmd_read(pmd_offer& offer, Fields const& fields)
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;
79 using beast::detail::ci_equal;
81 fields["Sec-WebSocket-Extensions"]};
82 for(auto const& ext : list)
84 if(ci_equal(ext.first, "permessage-deflate"))
86 for(auto const& param : ext.second)
88 if(ci_equal(param.first,
89 "server_max_window_bits"))
91 if(offer.server_max_window_bits != 0)
93 // The negotiation offer contains multiple
94 // extension parameters with the same name.
96 return; // MUST decline
98 if(param.second.empty())
100 // The negotiation offer extension
101 // parameter is missing the value.
103 return; // MUST decline
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)
110 // The negotiation offer contains an
111 // extension parameter with an invalid value.
113 return; // MUST decline
116 else if(ci_equal(param.first,
117 "client_max_window_bits"))
119 if(offer.client_max_window_bits != 0)
121 // The negotiation offer contains multiple
122 // extension parameters with the same name.
124 return; // MUST decline
126 if(! param.second.empty())
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)
133 // The negotiation offer contains an
134 // extension parameter with an invalid value.
136 return; // MUST decline
141 offer.client_max_window_bits = -1;
144 else if(ci_equal(param.first,
145 "server_no_context_takeover"))
147 if(offer.server_no_context_takeover)
149 // The negotiation offer contains multiple
150 // extension parameters with the same name.
152 return; // MUST decline
154 if(! param.second.empty())
156 // The negotiation offer contains an
157 // extension parameter with an invalid value.
159 return; // MUST decline
161 offer.server_no_context_takeover = true;
163 else if(ci_equal(param.first,
164 "client_no_context_takeover"))
166 if(offer.client_no_context_takeover)
168 // The negotiation offer contains multiple
169 // extension parameters with the same name.
171 return; // MUST decline
173 if(! param.second.empty())
175 // The negotiation offer contains an
176 // extension parameter with an invalid value.
178 return; // MUST decline
180 offer.client_no_context_takeover = true;
184 // The negotiation offer contains an extension
185 // parameter not defined for use in an offer.
187 return; // MUST decline
196 // Set permessage-deflate fields for a client offer
198 template<class Fields>
200 pmd_write(Fields& fields, pmd_offer const& offer)
203 s = "permessage-deflate";
204 if(offer.server_max_window_bits != 0)
206 if(offer.server_max_window_bits != -1)
208 s += "; server_max_window_bits=";
210 offer.server_max_window_bits);
214 s += "; server_max_window_bits";
217 if(offer.client_max_window_bits != 0)
219 if(offer.client_max_window_bits != -1)
221 s += "; client_max_window_bits=";
223 offer.client_max_window_bits);
227 s += "; client_max_window_bits";
230 if(offer.server_no_context_takeover)
232 s += "; server_no_context_takeover";
234 if(offer.client_no_context_takeover)
236 s += "; client_no_context_takeover";
238 fields.replace("Sec-WebSocket-Extensions", s);
241 // Negotiate a permessage-deflate client offer
243 template<class Fields>
248 pmd_offer const& offer,
249 permessage_deflate const& o)
251 if(! (offer.accept && o.server_enable))
253 config.accept = false;
256 config.accept = true;
258 std::string s = "permessage-deflate";
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";
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";
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);
277 config.server_max_window_bits =
278 o.server_max_window_bits;
279 if(config.server_max_window_bits < 15)
281 // ZLib's deflateInit silently treats 8 as
282 // 9 due to a bug, so prevent 8 from being used.
284 if(config.server_max_window_bits < 9)
285 config.server_max_window_bits = 9;
287 s += "; server_max_window_bits=";
289 config.server_max_window_bits);
292 switch(offer.client_max_window_bits)
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)
300 s += "; client_max_window_bits=";
302 config.client_max_window_bits);
307 /* extension parameter is absent.
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.
314 if(o.client_max_window_bits == 15)
315 config.client_max_window_bits = 15;
317 config.accept = false;
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=";
327 config.client_max_window_bits);
331 fields.replace("Sec-WebSocket-Extensions", s);
334 // Normalize the server's response
338 pmd_normalize(pmd_offer& offer)
342 if( offer.server_max_window_bits == 0)
343 offer.server_max_window_bits = 15;
345 if( offer.client_max_window_bits == 0 ||
346 offer.client_max_window_bits == -1)
347 offer.client_max_window_bits = 15;
351 //--------------------------------------------------------------------
353 // Decompress into a DynamicBuffer
355 template<class InflateStream, class DynamicBuffer>
359 DynamicBuffer& dynabuf,
360 boost::asio::const_buffer const& in,
363 using boost::asio::buffer_cast;
364 using boost::asio::buffer_size;
366 zs.avail_in = buffer_size(in);
367 zs.next_in = buffer_cast<void const*>(in);
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);
379 if( ec == zlib::error::need_buffers ||
380 ec == zlib::error::end_of_stream)
390 // Compress a buffer sequence
391 // Returns: `true` if more calls are needed
393 template<class DeflateStream, class ConstBufferSequence>
397 boost::asio::mutable_buffer& out,
398 consuming_buffers<ConstBufferSequence>& cb,
402 using boost::asio::buffer;
403 using boost::asio::buffer_cast;
404 using boost::asio::buffer_size;
405 BOOST_ASSERT(buffer_size(out) >= 6);
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)
413 zs.avail_in = buffer_size(in);
416 zs.next_in = buffer_cast<void const*>(in);
417 zo.write(zs, zlib::Flush::none, ec);
420 if(ec != zlib::error::need_buffers)
422 BOOST_ASSERT(zs.avail_out == 0);
423 BOOST_ASSERT(zs.total_out == buffer_size(out));
427 if(zs.avail_out == 0)
429 BOOST_ASSERT(zs.total_out == buffer_size(out));
432 BOOST_ASSERT(zs.avail_in == 0);
434 cb.consume(zs.total_in);
435 if(zs.avail_out > 0 && fin)
437 auto const remain = buffer_size(cb);
440 // Inspired by Mark Adler
441 // https://github.com/madler/zlib/issues/149
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)
451 if(zs.avail_out >= 6)
453 zo.write(zs, zlib::Flush::full, ec);
455 // remove flush marker
458 buffer_cast<void*>(out), zs.total_out);
464 buffer_cast<void*>(out), zs.total_out);