1 | /* |
2 | * IXWebSocketPerMessageDeflateOptions.cpp |
3 | * Author: Benjamin Sergeant |
4 | * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. |
5 | */ |
6 | |
7 | #include "IXWebSocketPerMessageDeflateOptions.h" |
8 | |
9 | #include <algorithm> |
10 | #include <cctype> |
11 | #include <sstream> |
12 | |
13 | namespace ix |
14 | { |
15 | /// Default values as defined in the RFC |
16 | const uint8_t WebSocketPerMessageDeflateOptions::kDefaultServerMaxWindowBits = 15; |
17 | static const uint8_t minServerMaxWindowBits = 8; |
18 | static const uint8_t maxServerMaxWindowBits = 15; |
19 | |
20 | const uint8_t WebSocketPerMessageDeflateOptions::kDefaultClientMaxWindowBits = 15; |
21 | static const uint8_t minClientMaxWindowBits = 8; |
22 | static const uint8_t maxClientMaxWindowBits = 15; |
23 | |
24 | WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions( |
25 | bool enabled, |
26 | bool clientNoContextTakeover, |
27 | bool serverNoContextTakeover, |
28 | uint8_t clientMaxWindowBits, |
29 | uint8_t serverMaxWindowBits) |
30 | { |
31 | _enabled = enabled; |
32 | _clientNoContextTakeover = clientNoContextTakeover; |
33 | _serverNoContextTakeover = serverNoContextTakeover; |
34 | _clientMaxWindowBits = clientMaxWindowBits; |
35 | _serverMaxWindowBits = serverMaxWindowBits; |
36 | |
37 | sanitizeClientMaxWindowBits(); |
38 | } |
39 | |
40 | // |
41 | // Four extension parameters are defined for "permessage-deflate" to |
42 | // help endpoints manage per-connection resource usage. |
43 | // |
44 | // - "server_no_context_takeover" |
45 | // - "client_no_context_takeover" |
46 | // - "server_max_window_bits" |
47 | // - "client_max_window_bits" |
48 | // |
49 | // Server response could look like that: |
50 | // |
51 | // Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover; |
52 | // server_no_context_takeover |
53 | // |
54 | WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(std::string extension) |
55 | { |
56 | extension = removeSpaces(extension); |
57 | |
58 | _enabled = false; |
59 | _clientNoContextTakeover = false; |
60 | _serverNoContextTakeover = false; |
61 | _clientMaxWindowBits = kDefaultClientMaxWindowBits; |
62 | _serverMaxWindowBits = kDefaultServerMaxWindowBits; |
63 | |
64 | #ifdef IXWEBSOCKET_USE_ZLIB |
65 | // Split by ; |
66 | std::string token; |
67 | std::stringstream tokenStream(extension); |
68 | |
69 | while (std::getline(tokenStream, token, ';')) |
70 | { |
71 | if (token == "permessage-deflate" ) |
72 | { |
73 | _enabled = true; |
74 | } |
75 | |
76 | if (token == "server_no_context_takeover" ) |
77 | { |
78 | _serverNoContextTakeover = true; |
79 | } |
80 | |
81 | if (token == "client_no_context_takeover" ) |
82 | { |
83 | _clientNoContextTakeover = true; |
84 | } |
85 | |
86 | if (startsWith(token, "server_max_window_bits=" )) |
87 | { |
88 | uint8_t x = strtol(token.substr(token.find_last_of("=" ) + 1).c_str(), nullptr, 10); |
89 | |
90 | // Sanitize values to be in the proper range [8, 15] in |
91 | // case a server would give us bogus values |
92 | _serverMaxWindowBits = |
93 | std::min(maxServerMaxWindowBits, std::max(x, minServerMaxWindowBits)); |
94 | } |
95 | |
96 | if (startsWith(token, "client_max_window_bits=" )) |
97 | { |
98 | uint8_t x = strtol(token.substr(token.find_last_of("=" ) + 1).c_str(), nullptr, 10); |
99 | |
100 | // Sanitize values to be in the proper range [8, 15] in |
101 | // case a server would give us bogus values |
102 | _clientMaxWindowBits = |
103 | std::min(maxClientMaxWindowBits, std::max(x, minClientMaxWindowBits)); |
104 | |
105 | sanitizeClientMaxWindowBits(); |
106 | } |
107 | } |
108 | #endif |
109 | } |
110 | |
111 | void WebSocketPerMessageDeflateOptions::sanitizeClientMaxWindowBits() |
112 | { |
113 | // zlib/deflate has a bug with windowsbits == 8, so we silently upgrade it to 9 |
114 | // See https://bugs.chromium.org/p/chromium/issues/detail?id=691074 |
115 | if (_clientMaxWindowBits == 8) |
116 | { |
117 | _clientMaxWindowBits = 9; |
118 | } |
119 | } |
120 | |
121 | std::string WebSocketPerMessageDeflateOptions::() |
122 | { |
123 | #ifdef IXWEBSOCKET_USE_ZLIB |
124 | std::stringstream ss; |
125 | ss << "Sec-WebSocket-Extensions: permessage-deflate" ; |
126 | |
127 | if (_clientNoContextTakeover) ss << "; client_no_context_takeover" ; |
128 | if (_serverNoContextTakeover) ss << "; server_no_context_takeover" ; |
129 | |
130 | ss << "; server_max_window_bits=" << static_cast<int>(_serverMaxWindowBits); |
131 | ss << "; client_max_window_bits=" << static_cast<int>(_clientMaxWindowBits); |
132 | |
133 | ss << "\r\n" ; |
134 | |
135 | return ss.str(); |
136 | #else |
137 | return std::string(); |
138 | #endif |
139 | } |
140 | |
141 | bool WebSocketPerMessageDeflateOptions::enabled() const |
142 | { |
143 | #ifdef IXWEBSOCKET_USE_ZLIB |
144 | return _enabled; |
145 | #else |
146 | return false; |
147 | #endif |
148 | } |
149 | |
150 | bool WebSocketPerMessageDeflateOptions::getClientNoContextTakeover() const |
151 | { |
152 | return _clientNoContextTakeover; |
153 | } |
154 | |
155 | bool WebSocketPerMessageDeflateOptions::getServerNoContextTakeover() const |
156 | { |
157 | return _serverNoContextTakeover; |
158 | } |
159 | |
160 | uint8_t WebSocketPerMessageDeflateOptions::getClientMaxWindowBits() const |
161 | { |
162 | return _clientMaxWindowBits; |
163 | } |
164 | |
165 | uint8_t WebSocketPerMessageDeflateOptions::getServerMaxWindowBits() const |
166 | { |
167 | return _serverMaxWindowBits; |
168 | } |
169 | |
170 | bool WebSocketPerMessageDeflateOptions::startsWith(const std::string& str, |
171 | const std::string& start) |
172 | { |
173 | return str.compare(0, start.length(), start) == 0; |
174 | } |
175 | |
176 | std::string WebSocketPerMessageDeflateOptions::removeSpaces(const std::string& str) |
177 | { |
178 | std::string out(str); |
179 | out.erase( |
180 | std::remove_if(out.begin(), out.end(), [](unsigned char x) { return std::isspace(x); }), |
181 | out.end()); |
182 | |
183 | return out; |
184 | } |
185 | } // namespace ix |
186 | |