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

Side by Side Diff: net/websockets/websocket_channel.cc

Issue 12764006: WebSocketChannel implementation (Closed) Base URL: http://git.chromium.org/chromium/src.git@web_socket_dispatcher
Patch Set: Code refactoring and cleanup. Created 7 years, 5 months 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
OLDNEW
(Empty)
1 // Copyright 2013 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/websockets/websocket_channel.h"
6
7 #include <algorithm>
8
9 #include "base/basictypes.h" // for size_t
10 #include "base/bind.h"
11 #include "base/safe_numerics.h"
12 #include "base/strings/string_util.h"
13 #include "net/base/big_endian.h"
14 #include "net/base/io_buffer.h"
15 #include "net/base/net_log.h"
16 #include "net/websockets/websocket_errors.h"
17 #include "net/websockets/websocket_event_interface.h"
18 #include "net/websockets/websocket_frame.h"
19 #include "net/websockets/websocket_mux.h"
20 #include "net/websockets/websocket_stream.h"
21
22 namespace net {
23
24 namespace {
25
26 const int kDefaultSendQuotaLowWaterMark = 1 << 16;
27 const int kDefaultSendQuotaHighWaterMark = 1 << 17;
28 const size_t kWebSocketCloseCodeLength = 2;
29
30 // Concatenate the data from two IOBufferWithSize objects into a single one.
31 IOBufferWithSize* ConcatenateIOBuffers(
32 const scoped_refptr<IOBufferWithSize>& part1,
33 const scoped_refptr<IOBufferWithSize>& part2) {
34 int newsize = part1->size() + part2->size();
35 IOBufferWithSize* newbuffer = new IOBufferWithSize(newsize);
36 std::copy(part1->data(), part1->data() + part1->size(), newbuffer->data());
37 std::copy(part2->data(),
38 part2->data() + part2->size(),
39 newbuffer->data() + part1->size());
40 return newbuffer;
41 }
42
43 } // namespace
44
45 struct WebSocketChannel::SendBuffer {
46 SendBuffer() : total_bytes(0) {}
47 ScopedVector<WebSocketFrameChunk> frames;
48 size_t total_bytes;
49 };
50
51 // Implementation of WebSocketStream::ConnectDelegate that simply forwards the
52 // calls on to the WebSocketChannel that created it.
53 class WebSocketChannel::ConnectDelegate
54 : public WebSocketStream::ConnectDelegate {
55 public:
56 explicit ConnectDelegate(WebSocketChannel* creator) : creator_(creator) {}
57
58 virtual void OnSuccess(scoped_ptr<WebSocketStream> stream) OVERRIDE {
59 creator_->OnConnectSuccess(stream.Pass());
60 }
61
62 virtual void OnFailure(uint16 websocket_error) OVERRIDE {
63 creator_->OnConnectFailure(websocket_error);
64 }
65
66 private:
67 // A pointer to the WebSocketChannel that created us. We do not need to worry
68 // about this pointer being stale, because deleting WebSocketChannel cancels
69 // the connect process, deleting this object and preventing its callbacks from
70 // being called.
71 WebSocketChannel* const creator_;
72
73 DISALLOW_COPY_AND_ASSIGN(ConnectDelegate);
74 };
75
76 WebSocketChannel::WebSocketChannel(
77 const GURL& socket_url,
78 scoped_ptr<WebSocketEventInterface> event_interface)
79 : socket_url_(socket_url),
80 event_interface_(event_interface.Pass()),
81 send_quota_low_water_mark_(kDefaultSendQuotaLowWaterMark),
82 send_quota_high_water_mark_(kDefaultSendQuotaHighWaterMark),
83 current_send_quota_(0),
84 state_(FRESHLY_CONSTRUCTED) {}
85
86 WebSocketChannel::~WebSocketChannel() {
87 // The stream may hold a pointer to read_frame_chunks_, and so it needs to be
88 // destroyed first.
89 stream_.reset();
90 }
91
92 void WebSocketChannel::SendAddChannelRequest(
93 const std::vector<std::string>& requested_subprotocols,
94 const GURL& origin,
95 URLRequestContext* url_request_context) {
96 // Delegate to the tested version.
97 SendAddChannelRequestWithFactory(
98 requested_subprotocols,
99 origin,
100 url_request_context,
101 base::Bind(&WebSocketStream::CreateAndConnectStream));
102 }
103
104 void WebSocketChannel::SendAddChannelRequestWithFactory(
105 const std::vector<std::string>& requested_subprotocols,
106 const GURL& origin,
107 URLRequestContext* url_request_context,
108 base::Callback<scoped_ptr<WebSocketStreamRequest>(
109 const GURL&,
110 const std::vector<std::string>&,
111 const GURL&,
112 URLRequestContext*,
113 const BoundNetLog&,
114 scoped_ptr<WebSocketStream::ConnectDelegate>)> factory) {
115 DCHECK_EQ(FRESHLY_CONSTRUCTED, state_);
116 scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate(
117 new WebSocketChannel::ConnectDelegate(this));
118 stream_request_ = factory.Run(socket_url_,
119 requested_subprotocols,
120 origin,
121 url_request_context,
122 BoundNetLog(),
123 connect_delegate.Pass());
124 state_ = CONNECTING;
125 }
126
127 void WebSocketChannel::OnConnectSuccess(scoped_ptr<WebSocketStream> stream) {
128 DCHECK(stream);
129 DCHECK_EQ(CONNECTING, state_);
130 stream_ = stream.Pass();
131 state_ = CONNECTED;
132 event_interface_->OnAddChannelResponse(false, stream_->GetSubProtocol());
133
134 // TODO(ricea): Get flow control information from the WebSocketStream once we
135 // have a multiplexing WebSocketStream.
136 current_send_quota_ = send_quota_high_water_mark_;
137 event_interface_->OnFlowControl(send_quota_high_water_mark_);
138
139 // We don't need this any more.
140 stream_request_.reset();
141 ReadFrames();
142 }
143
144 void WebSocketChannel::OnConnectFailure(uint16 websocket_error) {
145 DCHECK_EQ(CONNECTING, state_);
146 state_ = CLOSED;
147 stream_request_.reset();
148 event_interface_->OnAddChannelResponse(true, "");
149 }
150
151 void WebSocketChannel::SendFrame(bool fin,
152 WebSocketFrameHeader::OpCode op_code,
153 const std::vector<char>& data) {
154 if (data.size() > INT_MAX) {
155 NOTREACHED() << "Frame size sanity check failed";
156 return;
157 }
158 if (stream_ == NULL) {
159 LOG(DFATAL) << "Got SendFrame without a connection established; "
160 << "misbehaving renderer? fin=" << fin << " op_code=" << op_code
161 << " data.size()=" << data.size();
162 return;
163 }
164 if (state_ == SEND_CLOSED || state_ == RECV_CLOSED || state_ == CLOSED) {
165 VLOG(1) << "SendFrame called in state " << state_
166 << ". This may be a bug, or a harmless race.";
167 return;
168 }
169 if (state_ != CONNECTED) {
170 NOTREACHED() << "SendFrame() called in state " << state_;
171 return;
172 }
173 if (data.size() > base::checked_numeric_cast<size_t>(current_send_quota_)) {
174 FailChannel(SEND_INTERNAL_ERROR,
175 kWebSocketMuxErrorSendQuotaViolation,
176 "Send quota exceeded");
177 return;
178 }
179 if (!WebSocketFrameHeader::IsKnownDataOpCode(op_code)) {
180 LOG(DFATAL) << "Got SendFrame with bogus op_code " << op_code
181 << "; misbehaving renderer? fin=" << fin
182 << " data.size()=" << data.size();
183 return;
184 }
185 current_send_quota_ -= data.size();
186 // TODO(ricea): If current_send_quota_ has dropped below
187 // send_quota_low_water_mark_, we may want to consider increasing the "low
188 // water mark" and "high water mark", but only if we think we are not
189 // saturating the link to the WebSocket server.
190 // TODO(ricea): For kOpCodeText, do UTF-8 validation?
191 scoped_refptr<IOBufferWithSize> buffer(new IOBufferWithSize(data.size()));
192 std::copy(data.begin(), data.end(), buffer->data());
193 SendIOBufferWithSize(fin, op_code, buffer);
194 }
195
196 void WebSocketChannel::SendIOBufferWithSize(
197 bool fin,
198 WebSocketFrameHeader::OpCode op_code,
199 const scoped_refptr<IOBufferWithSize>& buffer) {
200 DCHECK(state_ == CONNECTED || state_ == RECV_CLOSED);
201 DCHECK(stream_);
202 scoped_ptr<WebSocketFrameHeader> header(new WebSocketFrameHeader(op_code));
203 header->final = fin;
204 header->masked = true;
205 header->payload_length = buffer->size();
206 scoped_ptr<WebSocketFrameChunk> chunk(new WebSocketFrameChunk());
207 chunk->header = header.Pass();
208 chunk->final_chunk = true;
209 chunk->data = buffer;
210 if (data_being_sent_) {
211 // Either the link to the WebSocket server is saturated, or we are simply
212 // processing a batch of messages.
213 // TODO(ricea): We need to keep some statistics to work out which situation
214 // we are in and adjust quota appropriately.
215 if (!data_to_send_next_) {
216 data_to_send_next_.reset(new SendBuffer);
217 }
218 data_to_send_next_->frames.push_back(chunk.release());
219 data_to_send_next_->total_bytes += buffer->size();
220 } else {
221 data_being_sent_.reset(new SendBuffer);
222 data_being_sent_->frames.push_back(chunk.release());
223 data_being_sent_->total_bytes += buffer->size();
224 WriteFrames();
225 }
226 }
227
228 void WebSocketChannel::WriteFrames() {
229 int result = OK;
230 do {
231 // This use of base::Unretained is safe because we own the WebSocketStream
232 // and destroying it cancels all callbacks.
233 result = stream_->WriteFrames(
234 &(data_being_sent_->frames),
235 base::Bind(
236 &WebSocketChannel::OnWriteDone, base::Unretained(this), false));
237 if (result != ERR_IO_PENDING) {
238 OnWriteDone(true, result);
239 }
240 } while (result == OK && data_being_sent_);
241 }
242
243 void WebSocketChannel::OnWriteDone(bool synchronous, int result) {
244 DCHECK(state_ != FRESHLY_CONSTRUCTED && state_ != CONNECTING);
245 DCHECK_NE(ERR_IO_PENDING, result);
246 DCHECK(data_being_sent_);
247 switch (result) {
248 case OK:
249 if (data_to_send_next_) {
250 data_being_sent_ = data_to_send_next_.Pass();
251 if (!synchronous) {
252 WriteFrames();
253 }
254 } else {
255 data_being_sent_.reset();
256 if (current_send_quota_ < send_quota_low_water_mark_) {
257 // TODO(ricea): Increase low_water_mark and high_water_mark if
258 // throughput is high, reduce them if throughput is low. Low water
259 // mark needs to be >= the bandwidth delay product *of the IPC
260 // channel*. Because factors like context-switch time, thread wake-up
261 // time, and bus speed come into play it is complex and probably needs
262 // to be determined empirically.
263 DCHECK_LE(send_quota_low_water_mark_, send_quota_high_water_mark_);
264 // TODO(ricea): Truncate quota by the quota specified by the remote
265 // server, if the protocol in use supports quota.
266 int fresh_quota = send_quota_high_water_mark_ - current_send_quota_;
267 current_send_quota_ += fresh_quota;
268 event_interface_->OnFlowControl(fresh_quota);
269 }
270 }
271 break;
272
273 // If a recoverable error condition existed, it would go here.
274
275 default:
276 DCHECK_LT(result, 0)
277 << "WriteFrames() should only return OK or ERR_ codes";
278 stream_->Close();
279 state_ = CLOSED;
280 event_interface_->OnDropChannel(kWebSocketErrorAbnormalClosure,
281 "Abnormal Closure");
282 break;
283 }
284 }
285
286 void WebSocketChannel::ReadFrames() {
287 int result = OK;
288 do {
289 // This use of base::Unretained is safe because we own the WebSocketStream,
290 // and any pending reads will be cancelled when it is destroyed.
291 result = stream_->ReadFrames(
292 &read_frame_chunks_,
293 base::Bind(
294 &WebSocketChannel::OnReadDone, base::Unretained(this), false));
295 if (result != ERR_IO_PENDING) {
296 OnReadDone(true, result);
297 }
298 } while (result == OK);
299 }
300
301 void WebSocketChannel::OnReadDone(bool synchronous, int result) {
302 DCHECK(state_ != FRESHLY_CONSTRUCTED && state_ != CONNECTING);
303 DCHECK_NE(ERR_IO_PENDING, result);
304 switch (result) {
305 case OK:
306 // ReadFrames() must use ERR_CONNECTION_CLOSED for a closed connection
307 // with no data read, not an empty response.
308 DCHECK(!read_frame_chunks_.empty())
309 << "ReadFrames() returned OK, but nothing was read.";
310 for (size_t i = 0; i < read_frame_chunks_.size(); ++i) {
311 scoped_ptr<WebSocketFrameChunk> chunk(read_frame_chunks_[i]);
312 read_frame_chunks_[i] = NULL;
313 ProcessFrameChunk(chunk.Pass());
314 }
315 read_frame_chunks_.clear();
316 // We need to always keep a call to ReadFrames pending.
317 if (!synchronous) {
318 ReadFrames();
319 }
320 return;
321
322 default: {
323 DCHECK_LT(result, 0)
324 << "ReadFrames() should only return OK or ERR_ codes";
325 stream_->Close();
326 State old_state = state_;
327 state_ = CLOSED;
328 if (old_state != RECV_CLOSED && old_state != CLOSED) {
329 event_interface_->OnDropChannel(kWebSocketErrorAbnormalClosure,
330 "Abnormal Closure");
331 }
332 return;
333 }
334 }
335 }
336
337 void WebSocketChannel::ProcessFrameChunk(
338 scoped_ptr<WebSocketFrameChunk> chunk) {
339 bool is_first_chunk = false;
340 if (chunk->header) {
341 DCHECK(current_frame_header_ == NULL)
342 << "Received the header for a new frame without notification that "
343 << "the previous frame was complete.";
344 is_first_chunk = true;
345 current_frame_header_.swap(chunk->header);
346 if (current_frame_header_->masked) {
347 // RFC6455 Section 5.1 "A client MUST close a connection if it detects a
348 // masked frame."
349 FailChannel(SEND_REAL_ERROR,
350 kWebSocketErrorProtocolError,
351 "Masked frame from server");
352 return;
353 }
354 }
355 if (!current_frame_header_) {
356 // If we rejected the previous chunk as invalid, then we will have reset
357 // current_frame_header_ to avoid using it. More chunks of the invalid frame
358 // may still arrive, so this is not necessarily a bug on our side. However,
359 // if this happens when state_ is CONNECTED, it is a definitely a bug.
tyoshino (SeeGerritForStatus) 2013/07/02 16:34:19 remove first a?
Adam Rice 2013/07/03 07:24:22 Sorry. Done.
360 DCHECK(state_ != CONNECTED) << "Unexpected header-less frame received "
361 << "(final_chunk = " << chunk->final_chunk
362 << ", data size = " << chunk->data->size()
363 << ")";
364 return;
365 }
366 scoped_refptr<IOBufferWithSize> data_buffer;
367 data_buffer.swap(chunk->data);
368 const bool is_final_chunk = chunk->final_chunk;
369 chunk.reset();
370 WebSocketFrameHeader::OpCode opcode = current_frame_header_->opcode;
371 if (WebSocketFrameHeader::IsKnownControlOpCode(opcode)) {
372 if (is_final_chunk) {
373 if (incomplete_control_frame_body_) {
374 VLOG(2) << "Rejoining a split control frame, opcode " << opcode;
375 data_buffer =
376 ConcatenateIOBuffers(incomplete_control_frame_body_, data_buffer);
377 incomplete_control_frame_body_ = NULL;
378 }
379 } else {
380 // TODO(ricea): Enforce a maximum size of 125 bytes on the control frames
381 // we accept.
382 VLOG(2) << "Encountered a split control frame, opcode " << opcode;
383 if (incomplete_control_frame_body_) {
384 // The really horrid case. We need to create a new IOBufferWithSize
385 // combining the new one and the old one. This should virtually never
386 // happen.
387 // TODO(ricea): This algorithm is O(N^2). Use a fixed 127-byte buffer
388 // instead.
389 VLOG(3) << "Hit the really horrid case";
390 incomplete_control_frame_body_ =
391 ConcatenateIOBuffers(incomplete_control_frame_body_, data_buffer);
392 } else {
393 // The merely horrid case. Store the IOBufferWithSize to use when the
394 // rest of the control frame arrives.
395 incomplete_control_frame_body_.swap(data_buffer);
396 }
397 return;
398 }
399 }
400
401 HandleFrame(opcode, is_first_chunk, is_final_chunk, data_buffer);
402
403 if (is_final_chunk) {
404 // Make sure we do not apply this frame header to any future chunks.
405 current_frame_header_.reset();
406 }
407 }
408
409 void WebSocketChannel::HandleFrame(
410 const WebSocketFrameHeader::OpCode opcode,
411 const bool is_first_chunk,
412 const bool is_final_chunk,
413 const scoped_refptr<IOBufferWithSize>& data_buffer) {
414 if (state_ == RECV_CLOSED) {
415 std::string frame_name;
416 switch (opcode) {
417 case WebSocketFrameHeader::kOpCodeText: // fall-thru
418 case WebSocketFrameHeader::kOpCodeBinary: // fall-thru
419 case WebSocketFrameHeader::kOpCodeContinuation:
420 frame_name = "Data frame";
421 break;
422
423 case WebSocketFrameHeader::kOpCodePing:
424 frame_name = "Ping";
425 break;
426
427 case WebSocketFrameHeader::kOpCodePong:
428 frame_name = "Pong";
429 break;
430
431 case WebSocketFrameHeader::kOpCodeClose:
432 frame_name = "Close";
433 break;
434
435 default:
436 frame_name = "Unknown frame type";
437 break;
438 }
439 FailChannel(SEND_REAL_ERROR,
440 kWebSocketErrorProtocolError,
441 frame_name + " received after close");
442 return;
443 }
444 switch (opcode) {
445 case WebSocketFrameHeader::kOpCodeText: // fall-thru
446 case WebSocketFrameHeader::kOpCodeBinary: // fall-thru
447 case WebSocketFrameHeader::kOpCodeContinuation:
448 if (state_ == CONNECTED) {
449 const bool final = is_final_chunk && current_frame_header_->final;
450 // TODO(ricea): Can this copy be eliminated?
451 const char* const data_begin = data_buffer->data();
452 const char* const data_end = data_begin + data_buffer->size();
453 const std::vector<char> data(data_begin, data_end);
454 // TODO(ricea): Handle the (improbable) case when ReadFrames returns far
455 // more data at once than we want to send in a single IPC (in which case
456 // we need to buffer the data and return to the event loop with a
457 // callback to send the rest in 32K chunks).
458
459 // Send the received frame to the renderer process.
460 event_interface_->OnDataFrame(
461 final,
462 is_first_chunk ? opcode : WebSocketFrameHeader::kOpCodeContinuation,
463 data);
464 } else {
465 VLOG(3) << "Ignored data packet received in state " << state_;
466 }
467 return;
468
469 case WebSocketFrameHeader::kOpCodePing:
470 VLOG(1) << "Got Ping of size " << data_buffer->size();
471 if (state_ == CONNECTED) {
472 SendIOBufferWithSize(
473 true, WebSocketFrameHeader::kOpCodePong, data_buffer);
474 } else {
475 VLOG(3) << "Ignored ping in state " << state_;
476 }
477 return;
478
479 case WebSocketFrameHeader::kOpCodePong:
480 VLOG(1) << "Got Pong of size " << data_buffer->size();
481 // We do not need to do anything with pong messages.
482 return;
483
484 case WebSocketFrameHeader::kOpCodeClose: {
485 uint16 code = kWebSocketNormalClosure;
486 std::string reason;
487 ParseClose(data_buffer, &code, &reason);
488 // TODO(ricea): Find a way to safely log the message from the close
489 // message (escape control codes and so on).
490 VLOG(1) << "Got Close with code " << code;
491 switch (state_) {
492 case CONNECTED:
493 state_ = RECV_CLOSED;
494 SendClose(code, reason); // Sets state_ to CLOSED
495 event_interface_->OnDropChannel(code, reason);
496 break;
497
498 case SEND_CLOSED:
499 state_ = CLOSED;
500 event_interface_->OnDropChannel(code, reason);
501 break;
502
503 default:
504 LOG(DFATAL) << "Got Close in unexpected state " << state_;
505 break;
506 }
507 return;
508 }
509
510 default:
511 FailChannel(
512 SEND_REAL_ERROR, kWebSocketErrorProtocolError, "Unknown opcode");
513 return;
514 }
515 }
516
517 void WebSocketChannel::SendFlowControl(int64 quota) {
518 DCHECK_EQ(CONNECTED, state_);
519 // TODO(ricea): Add interface to WebSocketStream and implement.
520 // stream_->SendFlowControl(quota);
521 }
522
523 void WebSocketChannel::SendDropChannel(uint16 code,
524 const std::string& reason) {
525 if (state_ == SEND_CLOSED || state_ == CLOSED) {
526 VLOG(1) << "SendDropChannel called in state " << state_
527 << ". This may be a bug, or a harmless race.";
528 return;
529 }
530 if (state_ != CONNECTED) {
531 NOTREACHED() << "SendDropChannel() called in state " << state_;
532 return;
533 }
534 // TODO(ricea): Validate |code|? Check that |reason| is valid UTF-8?
535 // TODO(ricea): There should be a timeout for the closing handshake.
536 SendClose(code, reason); // Sets state_ to SEND_CLOSED
537 }
538
539 void WebSocketChannel::FailChannel(ExposeError expose,
540 uint16 code,
541 const std::string& reason) {
542 DCHECK(state_ != FRESHLY_CONSTRUCTED && state_ != CONNECTING);
543 // TODO(ricea): Logging.
544 State old_state = state_;
545 if (state_ == CONNECTED) {
546 uint16 send_code = kWebSocketErrorGoingAway;
547 std::string send_reason = "Internal Error";
548 if (expose == SEND_REAL_ERROR) {
549 send_code = code;
550 send_reason = reason;
551 }
552 SendClose(send_code, send_reason); // Sets state_ to SEND_CLOSED
553 }
554 // This method is mostly called in response to an invalid frame, in
555 // which case we should not re-use the header.
556 current_frame_header_.reset();
557 if (old_state != RECV_CLOSED && old_state != CLOSED) {
558 event_interface_->OnDropChannel(code, reason);
559 }
560 }
561
562 void WebSocketChannel::SendClose(uint16 code,
563 const std::string& reason) {
564 DCHECK(state_ == CONNECTED || state_ == RECV_CLOSED);
565 uint64 payload_length = kWebSocketCloseCodeLength + reason.length();
566 scoped_refptr<IOBufferWithSize> body = new IOBufferWithSize(payload_length);
567 WriteBigEndian(body->data(), code);
568 COMPILE_ASSERT(sizeof(code) == kWebSocketCloseCodeLength,
569 they_should_both_be_two);
570 std::copy(
571 reason.begin(), reason.end(), body->data() + kWebSocketCloseCodeLength);
572 SendIOBufferWithSize(true, WebSocketFrameHeader::kOpCodeClose, body);
573 state_ = state_ == CONNECTED ? SEND_CLOSED : CLOSED;
574 }
575
576 void WebSocketChannel::ParseClose(const scoped_refptr<IOBufferWithSize>& buffer,
577 uint16* code,
578 std::string* reason) {
579 const char* data = buffer->data();
580 size_t size = base::checked_numeric_cast<size_t>(buffer->size());
581 reason->clear();
582 if (size < kWebSocketCloseCodeLength) {
583 *code = kWebSocketErrorNoStatusReceived;
584 if (size != 0) {
585 VLOG(1) << "Close frame with payload size " << size << " received "
586 << "(the first byte is " << std::hex << static_cast<int>(data[0])
587 << ")";
588 return;
589 }
590 return;
591 }
592 uint16 unchecked_code = 0;
593 ReadBigEndian(data, &unchecked_code);
594 COMPILE_ASSERT(sizeof(unchecked_code) == kWebSocketCloseCodeLength,
595 they_should_both_be_two_bytes);
596 if (unchecked_code >= static_cast<uint16>(kWebSocketNormalClosure) &&
597 unchecked_code <=
598 static_cast<uint16>(kWebSocketErrorPrivateReservedMax)) {
599 *code = unchecked_code;
600 } else {
601 VLOG(1) << "Close frame contained code outside of the valid range: "
602 << unchecked_code;
603 *code = kWebSocketErrorProtocolError;
604 }
605 std::string text(data + kWebSocketCloseCodeLength, data + size);
606 // TODO(ricea): Is this check strict enough? In particular, check the
607 // "Security Considerations" from RFC3629.
608 if (IsStringUTF8(text)) {
609 using std::swap;
610 swap(*reason, text);
611 }
612 }
613
614 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698