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