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