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
13namespace 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::generateHeader()
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