]>
Commit | Line | Data |
---|---|---|
7c673cae | 1 | // |
b32b8144 | 2 | // Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) |
7c673cae FG |
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 | // | |
b32b8144 FG |
7 | // Official repository: https://github.com/boostorg/beast |
8 | // | |
7c673cae | 9 | |
b32b8144 FG |
10 | #ifndef BOOST_BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP |
11 | #define BOOST_BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP | |
7c673cae | 12 | |
b32b8144 FG |
13 | #include <boost/beast/core/error.hpp> |
14 | #include <boost/beast/core/buffers_suffix.hpp> | |
15 | #include <boost/beast/core/read_size.hpp> | |
16 | #include <boost/beast/zlib/deflate_stream.hpp> | |
17 | #include <boost/beast/zlib/inflate_stream.hpp> | |
18 | #include <boost/beast/websocket/option.hpp> | |
19 | #include <boost/beast/http/rfc7230.hpp> | |
7c673cae FG |
20 | #include <boost/asio/buffer.hpp> |
21 | #include <utility> | |
11fdf7f2 | 22 | #include <type_traits> |
7c673cae | 23 | |
b32b8144 | 24 | namespace boost { |
7c673cae FG |
25 | namespace beast { |
26 | namespace websocket { | |
27 | namespace detail { | |
28 | ||
29 | // permessage-deflate offer parameters | |
30 | // | |
31 | // "context takeover" means: | |
32 | // preserve sliding window across messages | |
33 | // | |
34 | struct pmd_offer | |
35 | { | |
36 | bool accept; | |
37 | ||
38 | // 0 = absent, or 8..15 | |
39 | int server_max_window_bits; | |
40 | ||
41 | // -1 = present, 0 = absent, or 8..15 | |
42 | int client_max_window_bits; | |
43 | ||
44 | // `true` if server_no_context_takeover offered | |
45 | bool server_no_context_takeover; | |
46 | ||
47 | // `true` if client_no_context_takeover offered | |
48 | bool client_no_context_takeover; | |
49 | }; | |
50 | ||
51 | template<class = void> | |
52 | int | |
b32b8144 | 53 | parse_bits(string_view s) |
7c673cae FG |
54 | { |
55 | if(s.size() == 0) | |
56 | return -1; | |
57 | if(s.size() > 2) | |
58 | return -1; | |
59 | if(s[0] < '1' || s[0] > '9') | |
60 | return -1; | |
b32b8144 | 61 | unsigned i = 0; |
7c673cae FG |
62 | for(auto c : s) |
63 | { | |
64 | if(c < '0' || c > '9') | |
65 | return -1; | |
b32b8144 | 66 | auto const i0 = i; |
7c673cae | 67 | i = 10 * i + (c - '0'); |
b32b8144 FG |
68 | if(i < i0) |
69 | return -1; | |
7c673cae | 70 | } |
b32b8144 | 71 | return static_cast<int>(i); |
7c673cae FG |
72 | } |
73 | ||
74 | // Parse permessage-deflate request fields | |
75 | // | |
b32b8144 | 76 | template<class Allocator> |
7c673cae | 77 | void |
b32b8144 FG |
78 | pmd_read(pmd_offer& offer, |
79 | http::basic_fields<Allocator> const& fields) | |
7c673cae FG |
80 | { |
81 | offer.accept = false; | |
82 | offer.server_max_window_bits= 0; | |
83 | offer.client_max_window_bits = 0; | |
84 | offer.server_no_context_takeover = false; | |
85 | offer.client_no_context_takeover = false; | |
86 | ||
7c673cae FG |
87 | http::ext_list list{ |
88 | fields["Sec-WebSocket-Extensions"]}; | |
89 | for(auto const& ext : list) | |
90 | { | |
b32b8144 | 91 | if(iequals(ext.first, "permessage-deflate")) |
7c673cae FG |
92 | { |
93 | for(auto const& param : ext.second) | |
94 | { | |
b32b8144 | 95 | if(iequals(param.first, |
7c673cae FG |
96 | "server_max_window_bits")) |
97 | { | |
98 | if(offer.server_max_window_bits != 0) | |
99 | { | |
100 | // The negotiation offer contains multiple | |
101 | // extension parameters with the same name. | |
102 | // | |
103 | return; // MUST decline | |
104 | } | |
105 | if(param.second.empty()) | |
106 | { | |
107 | // The negotiation offer extension | |
108 | // parameter is missing the value. | |
109 | // | |
110 | return; // MUST decline | |
111 | } | |
112 | offer.server_max_window_bits = | |
113 | parse_bits(param.second); | |
114 | if( offer.server_max_window_bits < 8 || | |
115 | offer.server_max_window_bits > 15) | |
116 | { | |
117 | // The negotiation offer contains an | |
118 | // extension parameter with an invalid value. | |
119 | // | |
120 | return; // MUST decline | |
121 | } | |
122 | } | |
b32b8144 | 123 | else if(iequals(param.first, |
7c673cae FG |
124 | "client_max_window_bits")) |
125 | { | |
126 | if(offer.client_max_window_bits != 0) | |
127 | { | |
128 | // The negotiation offer contains multiple | |
129 | // extension parameters with the same name. | |
130 | // | |
131 | return; // MUST decline | |
132 | } | |
133 | if(! param.second.empty()) | |
134 | { | |
135 | offer.client_max_window_bits = | |
136 | parse_bits(param.second); | |
137 | if( offer.client_max_window_bits < 8 || | |
138 | offer.client_max_window_bits > 15) | |
139 | { | |
140 | // The negotiation offer contains an | |
141 | // extension parameter with an invalid value. | |
142 | // | |
143 | return; // MUST decline | |
144 | } | |
145 | } | |
146 | else | |
147 | { | |
148 | offer.client_max_window_bits = -1; | |
149 | } | |
150 | } | |
b32b8144 | 151 | else if(iequals(param.first, |
7c673cae FG |
152 | "server_no_context_takeover")) |
153 | { | |
154 | if(offer.server_no_context_takeover) | |
155 | { | |
156 | // The negotiation offer contains multiple | |
157 | // extension parameters with the same name. | |
158 | // | |
159 | return; // MUST decline | |
160 | } | |
161 | if(! param.second.empty()) | |
162 | { | |
163 | // The negotiation offer contains an | |
164 | // extension parameter with an invalid value. | |
165 | // | |
166 | return; // MUST decline | |
167 | } | |
168 | offer.server_no_context_takeover = true; | |
169 | } | |
b32b8144 | 170 | else if(iequals(param.first, |
7c673cae FG |
171 | "client_no_context_takeover")) |
172 | { | |
173 | if(offer.client_no_context_takeover) | |
174 | { | |
175 | // The negotiation offer contains multiple | |
176 | // extension parameters with the same name. | |
177 | // | |
178 | return; // MUST decline | |
179 | } | |
180 | if(! param.second.empty()) | |
181 | { | |
182 | // The negotiation offer contains an | |
183 | // extension parameter with an invalid value. | |
184 | // | |
185 | return; // MUST decline | |
186 | } | |
187 | offer.client_no_context_takeover = true; | |
188 | } | |
189 | else | |
190 | { | |
191 | // The negotiation offer contains an extension | |
192 | // parameter not defined for use in an offer. | |
193 | // | |
194 | return; // MUST decline | |
195 | } | |
196 | } | |
197 | offer.accept = true; | |
198 | return; | |
199 | } | |
200 | } | |
201 | } | |
202 | ||
203 | // Set permessage-deflate fields for a client offer | |
204 | // | |
b32b8144 | 205 | template<class Allocator> |
7c673cae | 206 | void |
b32b8144 FG |
207 | pmd_write(http::basic_fields<Allocator>& fields, |
208 | pmd_offer const& offer) | |
7c673cae | 209 | { |
b32b8144 | 210 | static_string<512> s; |
7c673cae FG |
211 | s = "permessage-deflate"; |
212 | if(offer.server_max_window_bits != 0) | |
213 | { | |
214 | if(offer.server_max_window_bits != -1) | |
215 | { | |
216 | s += "; server_max_window_bits="; | |
b32b8144 | 217 | s += to_static_string( |
7c673cae FG |
218 | offer.server_max_window_bits); |
219 | } | |
220 | else | |
221 | { | |
222 | s += "; server_max_window_bits"; | |
223 | } | |
224 | } | |
225 | if(offer.client_max_window_bits != 0) | |
226 | { | |
227 | if(offer.client_max_window_bits != -1) | |
228 | { | |
229 | s += "; client_max_window_bits="; | |
b32b8144 | 230 | s += to_static_string( |
7c673cae FG |
231 | offer.client_max_window_bits); |
232 | } | |
233 | else | |
234 | { | |
235 | s += "; client_max_window_bits"; | |
236 | } | |
237 | } | |
238 | if(offer.server_no_context_takeover) | |
239 | { | |
240 | s += "; server_no_context_takeover"; | |
241 | } | |
242 | if(offer.client_no_context_takeover) | |
243 | { | |
244 | s += "; client_no_context_takeover"; | |
245 | } | |
b32b8144 | 246 | fields.set(http::field::sec_websocket_extensions, s); |
7c673cae FG |
247 | } |
248 | ||
249 | // Negotiate a permessage-deflate client offer | |
250 | // | |
b32b8144 | 251 | template<class Allocator> |
7c673cae FG |
252 | void |
253 | pmd_negotiate( | |
b32b8144 | 254 | http::basic_fields<Allocator>& fields, |
7c673cae FG |
255 | pmd_offer& config, |
256 | pmd_offer const& offer, | |
257 | permessage_deflate const& o) | |
258 | { | |
259 | if(! (offer.accept && o.server_enable)) | |
260 | { | |
261 | config.accept = false; | |
262 | return; | |
263 | } | |
264 | config.accept = true; | |
265 | ||
b32b8144 | 266 | static_string<512> s = "permessage-deflate"; |
7c673cae FG |
267 | |
268 | config.server_no_context_takeover = | |
269 | offer.server_no_context_takeover || | |
270 | o.server_no_context_takeover; | |
271 | if(config.server_no_context_takeover) | |
272 | s += "; server_no_context_takeover"; | |
273 | ||
274 | config.client_no_context_takeover = | |
275 | o.client_no_context_takeover || | |
276 | offer.client_no_context_takeover; | |
277 | if(config.client_no_context_takeover) | |
278 | s += "; client_no_context_takeover"; | |
279 | ||
280 | if(offer.server_max_window_bits != 0) | |
b32b8144 | 281 | config.server_max_window_bits = (std::min)( |
7c673cae FG |
282 | offer.server_max_window_bits, |
283 | o.server_max_window_bits); | |
284 | else | |
285 | config.server_max_window_bits = | |
286 | o.server_max_window_bits; | |
287 | if(config.server_max_window_bits < 15) | |
288 | { | |
289 | // ZLib's deflateInit silently treats 8 as | |
290 | // 9 due to a bug, so prevent 8 from being used. | |
291 | // | |
292 | if(config.server_max_window_bits < 9) | |
293 | config.server_max_window_bits = 9; | |
294 | ||
295 | s += "; server_max_window_bits="; | |
b32b8144 | 296 | s += to_static_string( |
7c673cae FG |
297 | config.server_max_window_bits); |
298 | } | |
299 | ||
300 | switch(offer.client_max_window_bits) | |
301 | { | |
302 | case -1: | |
303 | // extension parameter is present with no value | |
304 | config.client_max_window_bits = | |
305 | o.client_max_window_bits; | |
306 | if(config.client_max_window_bits < 15) | |
307 | { | |
308 | s += "; client_max_window_bits="; | |
b32b8144 | 309 | s += to_static_string( |
7c673cae FG |
310 | config.client_max_window_bits); |
311 | } | |
312 | break; | |
313 | ||
314 | case 0: | |
315 | /* extension parameter is absent. | |
316 | ||
317 | If a received extension negotiation offer doesn't have the | |
318 | "client_max_window_bits" extension parameter, the corresponding | |
319 | extension negotiation response to the offer MUST NOT include the | |
320 | "client_max_window_bits" extension parameter. | |
321 | */ | |
322 | if(o.client_max_window_bits == 15) | |
323 | config.client_max_window_bits = 15; | |
324 | else | |
325 | config.accept = false; | |
326 | break; | |
327 | ||
328 | default: | |
329 | // extension parameter has value in [8..15] | |
b32b8144 | 330 | config.client_max_window_bits = (std::min)( |
7c673cae FG |
331 | o.client_max_window_bits, |
332 | offer.client_max_window_bits); | |
333 | s += "; client_max_window_bits="; | |
b32b8144 | 334 | s += to_static_string( |
7c673cae FG |
335 | config.client_max_window_bits); |
336 | break; | |
337 | } | |
338 | if(config.accept) | |
b32b8144 | 339 | fields.set(http::field::sec_websocket_extensions, s); |
7c673cae FG |
340 | } |
341 | ||
342 | // Normalize the server's response | |
343 | // | |
344 | inline | |
345 | void | |
346 | pmd_normalize(pmd_offer& offer) | |
347 | { | |
348 | if(offer.accept) | |
349 | { | |
350 | if( offer.server_max_window_bits == 0) | |
351 | offer.server_max_window_bits = 15; | |
352 | ||
353 | if( offer.client_max_window_bits == 0 || | |
354 | offer.client_max_window_bits == -1) | |
355 | offer.client_max_window_bits = 15; | |
356 | } | |
357 | } | |
358 | ||
7c673cae FG |
359 | } // detail |
360 | } // websocket | |
361 | } // beast | |
b32b8144 | 362 | } // boost |
7c673cae FG |
363 | |
364 | #endif |