Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1286)

Side by Side Diff: net/server/web_socket_encoder.cc

Issue 769423005: Support WebSocket per-message deflate extension in http server. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fixed comments by yhirano@ Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « net/server/web_socket_encoder.h ('k') | net/server/web_socket_encoder_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/server/web_socket_encoder.h"
6
7 #include "base/logging.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/strings/stringprintf.h"
10 #include "net/base/io_buffer.h"
11 #include "net/websockets/websocket_extension_parser.h"
12
13 namespace net {
14
15 const char WebSocketEncoder::kClientExtensions[] =
16 "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits";
17
18 namespace {
19
20 const int kInflaterChunkSize = 16 * 1024;
21
22 // Constants for hybi-10 frame format.
23
24 typedef int OpCode;
25
26 const OpCode kOpCodeContinuation = 0x0;
27 const OpCode kOpCodeText = 0x1;
28 const OpCode kOpCodeBinary = 0x2;
29 const OpCode kOpCodeClose = 0x8;
30 const OpCode kOpCodePing = 0x9;
31 const OpCode kOpCodePong = 0xA;
32
33 const unsigned char kFinalBit = 0x80;
34 const unsigned char kReserved1Bit = 0x40;
35 const unsigned char kReserved2Bit = 0x20;
36 const unsigned char kReserved3Bit = 0x10;
37 const unsigned char kOpCodeMask = 0xF;
38 const unsigned char kMaskBit = 0x80;
39 const unsigned char kPayloadLengthMask = 0x7F;
40
41 const size_t kMaxSingleBytePayloadLength = 125;
42 const size_t kTwoBytePayloadLengthField = 126;
43 const size_t kEightBytePayloadLengthField = 127;
44 const size_t kMaskingKeyWidthInBytes = 4;
45
46 WebSocket::ParseResult DecodeFrameHybi17(const base::StringPiece& frame,
47 bool client_frame,
48 int* bytes_consumed,
49 std::string* output,
50 bool* compressed) {
51 size_t data_length = frame.length();
52 if (data_length < 2)
53 return WebSocket::FRAME_INCOMPLETE;
54
55 const char* buffer_begin = const_cast<char*>(frame.data());
56 const char* p = buffer_begin;
57 const char* buffer_end = p + data_length;
58
59 unsigned char first_byte = *p++;
60 unsigned char second_byte = *p++;
61
62 bool final = (first_byte & kFinalBit) != 0;
63 bool reserved1 = (first_byte & kReserved1Bit) != 0;
64 bool reserved2 = (first_byte & kReserved2Bit) != 0;
65 bool reserved3 = (first_byte & kReserved3Bit) != 0;
66 int op_code = first_byte & kOpCodeMask;
67 bool masked = (second_byte & kMaskBit) != 0;
68 *compressed = reserved1;
69 if (!final || reserved2 || reserved3)
70 return WebSocket::FRAME_ERROR; // Only compression extension is supported.
71
72 bool closed = false;
73 switch (op_code) {
74 case kOpCodeClose:
75 closed = true;
76 break;
77 case kOpCodeText:
78 break;
79 case kOpCodeBinary: // We don't support binary frames yet.
80 case kOpCodeContinuation: // We don't support binary frames yet.
81 case kOpCodePing: // We don't support binary frames yet.
82 case kOpCodePong: // We don't support binary frames yet.
83 default:
84 return WebSocket::FRAME_ERROR;
85 }
86
87 if (client_frame && !masked) // In Hybi-17 spec client MUST mask his frame.
88 return WebSocket::FRAME_ERROR;
89
90 uint64 payload_length64 = second_byte & kPayloadLengthMask;
91 if (payload_length64 > kMaxSingleBytePayloadLength) {
92 int extended_payload_length_size;
93 if (payload_length64 == kTwoBytePayloadLengthField)
94 extended_payload_length_size = 2;
95 else {
96 DCHECK(payload_length64 == kEightBytePayloadLengthField);
97 extended_payload_length_size = 8;
98 }
99 if (buffer_end - p < extended_payload_length_size)
100 return WebSocket::FRAME_INCOMPLETE;
101 payload_length64 = 0;
102 for (int i = 0; i < extended_payload_length_size; ++i) {
103 payload_length64 <<= 8;
104 payload_length64 |= static_cast<unsigned char>(*p++);
105 }
106 }
107
108 size_t actual_masking_key_length = masked ? kMaskingKeyWidthInBytes : 0;
109 static const uint64 max_payload_length = 0x7FFFFFFFFFFFFFFFull;
110 static size_t max_length = std::numeric_limits<size_t>::max();
111 if (payload_length64 > max_payload_length ||
112 payload_length64 + actual_masking_key_length > max_length) {
113 // WebSocket frame length too large.
114 return WebSocket::FRAME_ERROR;
115 }
116 size_t payload_length = static_cast<size_t>(payload_length64);
117
118 size_t total_length = actual_masking_key_length + payload_length;
119 if (static_cast<size_t>(buffer_end - p) < total_length)
120 return WebSocket::FRAME_INCOMPLETE;
121
122 if (masked) {
123 output->resize(payload_length);
124 const char* masking_key = p;
125 char* payload = const_cast<char*>(p + kMaskingKeyWidthInBytes);
126 for (size_t i = 0; i < payload_length; ++i) // Unmask the payload.
127 (*output)[i] = payload[i] ^ masking_key[i % kMaskingKeyWidthInBytes];
128 } else {
129 output->assign(p, p + payload_length);
130 }
131
132 size_t pos = p + actual_masking_key_length + payload_length - buffer_begin;
133 *bytes_consumed = pos;
134 return closed ? WebSocket::FRAME_CLOSE : WebSocket::FRAME_OK;
135 }
136
137 void EncodeFrameHybi17(const std::string& message,
138 int masking_key,
139 bool compressed,
140 std::string* output) {
141 std::vector<char> frame;
142 OpCode op_code = kOpCodeText;
143 size_t data_length = message.length();
144
145 int reserved1 = compressed ? kReserved1Bit : 0;
146 frame.push_back(kFinalBit | op_code | reserved1);
147 char mask_key_bit = masking_key != 0 ? kMaskBit : 0;
148 if (data_length <= kMaxSingleBytePayloadLength)
149 frame.push_back(data_length | mask_key_bit);
150 else if (data_length <= 0xFFFF) {
151 frame.push_back(kTwoBytePayloadLengthField | mask_key_bit);
152 frame.push_back((data_length & 0xFF00) >> 8);
153 frame.push_back(data_length & 0xFF);
154 } else {
155 frame.push_back(kEightBytePayloadLengthField | mask_key_bit);
156 char extended_payload_length[8];
157 size_t remaining = data_length;
158 // Fill the length into extended_payload_length in the network byte order.
159 for (int i = 0; i < 8; ++i) {
160 extended_payload_length[7 - i] = remaining & 0xFF;
161 remaining >>= 8;
162 }
163 frame.insert(frame.end(), extended_payload_length,
164 extended_payload_length + 8);
165 DCHECK(!remaining);
166 }
167
168 const char* data = const_cast<char*>(message.data());
169 if (masking_key != 0) {
170 const char* mask_bytes = reinterpret_cast<char*>(&masking_key);
171 frame.insert(frame.end(), mask_bytes, mask_bytes + 4);
172 for (size_t i = 0; i < data_length; ++i) // Mask the payload.
173 frame.push_back(data[i] ^ mask_bytes[i % kMaskingKeyWidthInBytes]);
174 } else {
175 frame.insert(frame.end(), data, data + data_length);
176 }
177 *output = std::string(&frame[0], frame.size());
178 }
179
180 } // anonymous namespace
181
182 // static
183 WebSocketEncoder* WebSocketEncoder::CreateServer(
184 const std::string& request_extensions,
185 std::string* response_extensions) {
186 bool deflate;
187 int client_window_bits;
188 int server_window_bits;
189 bool client_no_context_takeover;
190 bool server_no_context_takeover;
191 ParseExtensions(request_extensions, &deflate, &client_window_bits,
192 &server_window_bits, &client_no_context_takeover,
193 &server_no_context_takeover);
194
195 if (deflate) {
196 *response_extensions = base::StringPrintf(
197 "permessage-deflate; server_max_window_bits=%d; "
198 "client_max_window_bits=%d%s%s",
199 server_window_bits, client_window_bits,
200 client_no_context_takeover ? "client_no_context_takeover" : "",
yhirano 2014/12/09 11:11:23 You don't need to send back client_no_context_take
dgozman 2014/12/10 10:34:10 Done.
201 server_no_context_takeover ? "server_no_context_takeover" : "");
yhirano 2014/12/09 11:11:23 ';' is needed.
dgozman 2014/12/10 10:34:10 Oops. Fixed.
202 return new WebSocketEncoder(true /* is_server */, server_window_bits,
203 client_window_bits, server_no_context_takeover);
204 } else {
205 *response_extensions = std::string();
206 return new WebSocketEncoder(true /* is_server */);
207 }
208 }
209
210 // static
211 WebSocketEncoder* WebSocketEncoder::CreateClient(
212 const std::string& response_extensions) {
213 bool deflate;
214 int client_window_bits;
215 int server_window_bits;
216 bool client_no_context_takeover;
217 bool server_no_context_takeover;
218 ParseExtensions(response_extensions, &deflate, &client_window_bits,
219 &server_window_bits, &client_no_context_takeover,
220 &server_no_context_takeover);
221
222 if (deflate) {
223 return new WebSocketEncoder(false /* is_server */, client_window_bits,
224 server_window_bits, client_no_context_takeover);
225 } else {
226 return new WebSocketEncoder(false /* is_server */);
227 }
228 }
229
230 // static
231 void WebSocketEncoder::ParseExtensions(const std::string& extensions,
232 bool* deflate,
233 int* client_window_bits,
234 int* server_window_bits,
235 bool* client_no_context_takeover,
236 bool* server_no_context_takeover) {
237 *deflate = false;
238 *client_window_bits = 15;
239 *server_window_bits = 15;
240 *client_no_context_takeover = false;
241 *server_no_context_takeover = false;
242
243 if (extensions.empty())
244 return;
245
246 // TODO(dgozman): split extensions header if another extension is introduced.
247 WebSocketExtensionParser parser;
248 parser.Parse(extensions);
249 if (parser.has_error())
250 return;
251 if (parser.extension().name() != "permessage-deflate")
252 return;
253
254 const std::vector<WebSocketExtension::Parameter>& parameters =
255 parser.extension().parameters();
256 for (const auto& param : parameters) {
257 const std::string& name = param.name();
258 if (name == "client_max_window_bits" && param.HasValue()) {
259 int bits = 0;
260 if (base::StringToInt(param.value(), &bits) && bits >= 8 && bits <= 15)
261 *client_window_bits = bits;
262 }
263 if (name == "server_max_window_bits" && param.HasValue()) {
264 int bits = 0;
265 if (base::StringToInt(param.value(), &bits) && bits >= 8 && bits <= 15)
266 *server_window_bits = bits;
267 }
268 if (name == "client_no_context_takeover")
269 *client_no_context_takeover = true;
270 if (name == "server_no_context_takeover")
271 *server_no_context_takeover = true;
272 }
273 *deflate = true;
274 }
275
276 WebSocketEncoder::WebSocketEncoder(bool is_server) : is_server_(is_server) {
277 }
278
279 WebSocketEncoder::WebSocketEncoder(bool is_server,
280 int deflate_bits,
281 int inflate_bits,
282 bool no_context_takeover)
283 : is_server_(is_server) {
284 deflater_.reset(new WebSocketDeflater(
285 no_context_takeover ? WebSocketDeflater::DO_NOT_TAKE_OVER_CONTEXT
286 : WebSocketDeflater::TAKE_OVER_CONTEXT));
287 inflater_.reset(
288 new WebSocketInflater(kInflaterChunkSize, kInflaterChunkSize));
289
290 if (!deflater_->Initialize(deflate_bits) ||
291 !inflater_->Initialize(inflate_bits)) {
292 // Disable deflate support.
293 deflater_.reset();
294 inflater_.reset();
295 }
296 }
297
298 WebSocketEncoder::~WebSocketEncoder() {
299 }
300
301 WebSocket::ParseResult WebSocketEncoder::DecodeFrame(
302 const base::StringPiece& frame,
303 int* bytes_consumed,
304 std::string* output) {
305 bool compressed;
306 WebSocket::ParseResult result =
307 DecodeFrameHybi17(frame, is_server_, bytes_consumed, output, &compressed);
308 if (result == WebSocket::FRAME_OK && compressed) {
309 if (!Inflate(output))
310 result = WebSocket::FRAME_ERROR;
311 }
312 return result;
313 }
314
315 void WebSocketEncoder::EncodeFrame(const std::string& frame,
316 int masking_key,
317 std::string* output) {
318 std::string compressed;
319 if (Deflate(frame, &compressed))
320 EncodeFrameHybi17(compressed, masking_key, true, output);
321 else
322 EncodeFrameHybi17(frame, masking_key, false, output);
323 }
324
325 bool WebSocketEncoder::Inflate(std::string* message) {
326 if (!inflater_)
327 return false;
328 if (!inflater_->AddBytes(message->data(), message->length()))
329 return false;
330 if (!inflater_->Finish())
331 return false;
332
333 std::vector<char> output;
334 while (inflater_->CurrentOutputSize() > 0) {
335 scoped_refptr<IOBufferWithSize> chunk =
336 inflater_->GetOutput(inflater_->CurrentOutputSize());
337 if (!chunk.get())
338 return false;
339 output.insert(output.end(), chunk->data(), chunk->data() + chunk->size());
340 }
341
342 *message =
343 output.size() ? std::string(&output[0], output.size()) : std::string();
344 return true;
345 }
346
347 bool WebSocketEncoder::Deflate(const std::string& message,
348 std::string* output) {
349 if (!deflater_)
350 return false;
351 if (!deflater_->AddBytes(message.data(), message.length())) {
352 deflater_->Finish();
353 return false;
354 }
355 if (!deflater_->Finish())
356 return false;
357 scoped_refptr<IOBufferWithSize> buffer =
358 deflater_->GetOutput(deflater_->CurrentOutputSize());
359 if (!buffer.get())
360 return false;
361 *output = std::string(buffer->data(), buffer->size());
362 return true;
363 }
364
365 } // namespace net
OLDNEW
« no previous file with comments | « net/server/web_socket_encoder.h ('k') | net/server/web_socket_encoder_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698