|
Lines 48-64
a/Source/WebCore/websockets/WebSocketChannel.cpp_sec1
|
| 48 |
#include "WebSocketChannelClient.h" |
48 |
#include "WebSocketChannelClient.h" |
| 49 |
#include "WebSocketHandshake.h" |
49 |
#include "WebSocketHandshake.h" |
| 50 |
|
50 |
|
| 51 |
#include <wtf/text/CString.h> |
51 |
#include <stdint.h> |
| 52 |
#include <wtf/text/WTFString.h> |
52 |
#include <wtf/CryptographicallyRandomNumber.h> |
| 53 |
#include <wtf/text/StringHash.h> |
|
|
| 54 |
#include <wtf/Deque.h> |
53 |
#include <wtf/Deque.h> |
| 55 |
#include <wtf/FastMalloc.h> |
54 |
#include <wtf/FastMalloc.h> |
| 56 |
#include <wtf/HashMap.h> |
55 |
#include <wtf/HashMap.h> |
|
|
56 |
#include <wtf/text/CString.h> |
| 57 |
#include <wtf/text/StringHash.h> |
| 58 |
#include <wtf/text/WTFString.h> |
| 57 |
|
59 |
|
| 58 |
namespace WebCore { |
60 |
namespace WebCore { |
| 59 |
|
61 |
|
| 60 |
const double TCPMaximumSegmentLifetime = 2 * 60.0; |
62 |
const double TCPMaximumSegmentLifetime = 2 * 60.0; |
| 61 |
|
63 |
|
|
|
64 |
// Constants for hybi-10 frame format. |
| 65 |
const unsigned char finalBit = 0x80; |
| 66 |
const unsigned char reserved1Bit = 0x40; |
| 67 |
const unsigned char reserved2Bit = 0x20; |
| 68 |
const unsigned char reserved3Bit = 0x10; |
| 69 |
const unsigned char opCodeMask = 0xF; |
| 70 |
const unsigned char maskBit = 0x80; |
| 71 |
const unsigned char payloadLengthMask = 0x7F; |
| 72 |
const size_t maxPayloadLengthWithoutExtendedLengthField = 125; |
| 73 |
const size_t payloadLengthWithTwoByteExtendedLengthField = 126; |
| 74 |
const size_t payloadLengthWithEightByteExtendedLengthField = 127; |
| 75 |
const size_t maskingKeyWidthInBytes = 4; |
| 76 |
|
| 77 |
const WebSocketChannel::OpCode WebSocketChannel::OpCodeContinuation = 0x0; |
| 78 |
const WebSocketChannel::OpCode WebSocketChannel::OpCodeText = 0x1; |
| 79 |
const WebSocketChannel::OpCode WebSocketChannel::OpCodeBinary = 0x2; |
| 80 |
const WebSocketChannel::OpCode WebSocketChannel::OpCodeClose = 0x8; |
| 81 |
const WebSocketChannel::OpCode WebSocketChannel::OpCodePing = 0x9; |
| 82 |
const WebSocketChannel::OpCode WebSocketChannel::OpCodePong = 0xA; |
| 83 |
|
| 62 |
WebSocketChannel::WebSocketChannel(ScriptExecutionContext* context, WebSocketChannelClient* client, const KURL& url, const String& protocol) |
84 |
WebSocketChannel::WebSocketChannel(ScriptExecutionContext* context, WebSocketChannelClient* client, const KURL& url, const String& protocol) |
| 63 |
: m_context(context) |
85 |
: m_context(context) |
| 64 |
, m_client(client) |
86 |
, m_client(client) |
|
Lines 74-79
WebSocketChannel::WebSocketChannel(ScriptExecutionContext* context, WebSocketCha
a/Source/WebCore/websockets/WebSocketChannel.cpp_sec2
|
| 74 |
, m_unhandledBufferedAmount(0) |
96 |
, m_unhandledBufferedAmount(0) |
| 75 |
, m_identifier(0) |
97 |
, m_identifier(0) |
| 76 |
, m_useHixie76Protocol(true) |
98 |
, m_useHixie76Protocol(true) |
|
|
99 |
, m_hasContinuousFrame(false) |
| 77 |
{ |
100 |
{ |
| 78 |
ASSERT(m_context->isDocument()); |
101 |
ASSERT(m_context->isDocument()); |
| 79 |
Document* document = static_cast<Document*>(m_context); |
102 |
Document* document = static_cast<Document*>(m_context); |
|
Lines 102-118
void WebSocketChannel::connect()
a/Source/WebCore/websockets/WebSocketChannel.cpp_sec3
|
| 102 |
m_handle = SocketStreamHandle::create(m_handshake->url(), this); |
125 |
m_handle = SocketStreamHandle::create(m_handshake->url(), this); |
| 103 |
} |
126 |
} |
| 104 |
|
127 |
|
| 105 |
bool WebSocketChannel::send(const String& msg) |
128 |
bool WebSocketChannel::send(const String& message) |
| 106 |
{ |
129 |
{ |
| 107 |
LOG(Network, "WebSocketChannel %p send %s", this, msg.utf8().data()); |
130 |
LOG(Network, "WebSocketChannel %p send %s", this, message.utf8().data()); |
| 108 |
ASSERT(m_handle); |
131 |
CString utf8 = message.utf8(); |
| 109 |
ASSERT(!m_suspended); |
132 |
if (m_useHixie76Protocol) |
| 110 |
Vector<char> buf; |
133 |
return sendFrameHixie76(utf8.data(), utf8.length()); |
| 111 |
buf.append('\0'); // frame type |
134 |
return sendFrame(OpCodeText, utf8.data(), utf8.length()); |
| 112 |
CString utf8 = msg.utf8(); |
|
|
| 113 |
buf.append(utf8.data(), utf8.length()); |
| 114 |
buf.append('\xff'); // frame end |
| 115 |
return m_handle->send(buf.data(), buf.size()); |
| 116 |
} |
135 |
} |
| 117 |
|
136 |
|
| 118 |
unsigned long WebSocketChannel::bufferedAmount() const |
137 |
unsigned long WebSocketChannel::bufferedAmount() const |
|
Lines 140-145
void WebSocketChannel::fail(const String& reason)
a/Source/WebCore/websockets/WebSocketChannel.cpp_sec4
|
| 140 |
ASSERT(!m_suspended); |
159 |
ASSERT(!m_suspended); |
| 141 |
if (m_context) |
160 |
if (m_context) |
| 142 |
m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, reason, 0, m_handshake->clientOrigin(), 0); |
161 |
m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, reason, 0, m_handshake->clientOrigin(), 0); |
|
|
162 |
if (!m_useHixie76Protocol) { |
| 163 |
// Hybi-10 specification explicitly states we must not continue to handle incoming data |
| 164 |
// once the WebSocket connection is failed (section 7.1.7). |
| 165 |
// FIXME: Should we do this in hixie-76 too? |
| 166 |
m_shouldDiscardReceivedData = true; |
| 167 |
if (m_buffer) |
| 168 |
skipBuffer(m_bufferSize); // Save memory. |
| 169 |
m_hasContinuousFrame = false; |
| 170 |
m_continuousFrameData.clear(); |
| 171 |
} |
| 143 |
if (m_handle && !m_closed) |
172 |
if (m_handle && !m_closed) |
| 144 |
m_handle->disconnect(); // Will call didClose(). |
173 |
m_handle->disconnect(); // Will call didClose(). |
| 145 |
} |
174 |
} |
|
Lines 345-350
bool WebSocketChannel::processBuffer()
a/Source/WebCore/websockets/WebSocketChannel.cpp_sec5
|
| 345 |
if (m_handshake->mode() != WebSocketHandshake::Connected) |
374 |
if (m_handshake->mode() != WebSocketHandshake::Connected) |
| 346 |
return false; |
375 |
return false; |
| 347 |
|
376 |
|
|
|
377 |
if (m_useHixie76Protocol) |
| 378 |
return processFrameHixie76(); |
| 379 |
|
| 380 |
return processFrame(); |
| 381 |
} |
| 382 |
|
| 383 |
void WebSocketChannel::resumeTimerFired(Timer<WebSocketChannel>* timer) |
| 384 |
{ |
| 385 |
ASSERT_UNUSED(timer, timer == &m_resumeTimer); |
| 386 |
|
| 387 |
RefPtr<WebSocketChannel> protect(this); // The client can close the channel, potentially removing the last reference. |
| 388 |
while (!m_suspended && m_client && m_buffer) |
| 389 |
if (!processBuffer()) |
| 390 |
break; |
| 391 |
if (!m_suspended && m_client && m_closed && m_handle) |
| 392 |
didClose(m_handle.get()); |
| 393 |
} |
| 394 |
|
| 395 |
void WebSocketChannel::startClosingHandshake() |
| 396 |
{ |
| 397 |
LOG(Network, "WebSocketChannel %p closing %d %d", this, m_closing, m_receivedClosingHandshake); |
| 398 |
if (m_closing) |
| 399 |
return; |
| 400 |
ASSERT(m_handle); |
| 401 |
bool sentSuccessfully; |
| 402 |
if (m_useHixie76Protocol) { |
| 403 |
Vector<char> buf; |
| 404 |
buf.append('\xff'); |
| 405 |
buf.append('\0'); |
| 406 |
sentSuccessfully = m_handle->send(buf.data(), buf.size()); |
| 407 |
} else |
| 408 |
sentSuccessfully = sendFrame(OpCodeClose, "", 0); // FIXME: Send status code and reason message. |
| 409 |
|
| 410 |
if (!sentSuccessfully) { |
| 411 |
m_handle->disconnect(); |
| 412 |
return; |
| 413 |
} |
| 414 |
m_closing = true; |
| 415 |
if (m_client) |
| 416 |
m_client->didStartClosingHandshake(); |
| 417 |
} |
| 418 |
|
| 419 |
void WebSocketChannel::closingTimerFired(Timer<WebSocketChannel>* timer) |
| 420 |
{ |
| 421 |
LOG(Network, "WebSocketChannel %p closing timer", this); |
| 422 |
ASSERT_UNUSED(timer, &m_closingTimer == timer); |
| 423 |
if (m_handle) |
| 424 |
m_handle->disconnect(); |
| 425 |
} |
| 426 |
|
| 427 |
WebSocketChannel::ParseFrameResult WebSocketChannel::parseFrame(FrameData& frame) |
| 428 |
{ |
| 429 |
const char* p = m_buffer; |
| 430 |
const char* bufferEnd = m_buffer + m_bufferSize; |
| 431 |
|
| 432 |
if (m_bufferSize < 2) |
| 433 |
return FrameIncomplete; |
| 434 |
|
| 435 |
unsigned char firstByte = *p++; |
| 436 |
unsigned char secondByte = *p++; |
| 437 |
|
| 438 |
bool final = firstByte & finalBit; |
| 439 |
bool reserved1 = firstByte & reserved1Bit; |
| 440 |
bool reserved2 = firstByte & reserved2Bit; |
| 441 |
bool reserved3 = firstByte & reserved3Bit; |
| 442 |
OpCode opCode = firstByte & opCodeMask; |
| 443 |
|
| 444 |
bool masked = secondByte & maskBit; |
| 445 |
uint64_t payloadLength64 = secondByte & payloadLengthMask; |
| 446 |
if (payloadLength64 > maxPayloadLengthWithoutExtendedLengthField) { |
| 447 |
int extendedPayloadLengthSize; |
| 448 |
if (payloadLength64 == payloadLengthWithTwoByteExtendedLengthField) |
| 449 |
extendedPayloadLengthSize = 2; |
| 450 |
else { |
| 451 |
ASSERT(payloadLength64 == payloadLengthWithEightByteExtendedLengthField); |
| 452 |
extendedPayloadLengthSize = 8; |
| 453 |
} |
| 454 |
if (bufferEnd - p < extendedPayloadLengthSize) |
| 455 |
return FrameIncomplete; |
| 456 |
payloadLength64 = 0; |
| 457 |
for (int i = 0; i < extendedPayloadLengthSize; ++i) { |
| 458 |
payloadLength64 <<= 8; |
| 459 |
payloadLength64 |= static_cast<unsigned char>(*p++); |
| 460 |
} |
| 461 |
} |
| 462 |
|
| 463 |
static const uint64_t maxPayloadLength = UINT64_C(0x7FFFFFFFFFFFFFFF); |
| 464 |
size_t maskingKeyLength = masked ? maskingKeyWidthInBytes : 0; |
| 465 |
if (payloadLength64 > maxPayloadLength || payloadLength64 + maskingKeyLength > std::numeric_limits<size_t>::max()) { |
| 466 |
fail("WebSocket frame length too large: " + String::number(payloadLength64) + " bytes"); |
| 467 |
return FrameError; |
| 468 |
} |
| 469 |
size_t payloadLength = static_cast<size_t>(payloadLength64); |
| 470 |
|
| 471 |
if (static_cast<size_t>(bufferEnd - p) < maskingKeyLength + payloadLength) |
| 472 |
return FrameIncomplete; |
| 473 |
|
| 474 |
if (masked) { |
| 475 |
const char* maskingKey = p; |
| 476 |
char* payload = const_cast<char*>(p + maskingKeyWidthInBytes); |
| 477 |
for (size_t i = 0; i < payloadLength; ++i) |
| 478 |
payload[i] ^= maskingKey[i % maskingKeyWidthInBytes]; // Unmask the payload. |
| 479 |
} |
| 480 |
|
| 481 |
frame.opCode = opCode; |
| 482 |
frame.final = final; |
| 483 |
frame.reserved1 = reserved1; |
| 484 |
frame.reserved2 = reserved2; |
| 485 |
frame.reserved3 = reserved3; |
| 486 |
frame.masked = masked; |
| 487 |
frame.payload = p + maskingKeyLength; |
| 488 |
frame.payloadLength = payloadLength; |
| 489 |
frame.frameEnd = p + maskingKeyLength + payloadLength; |
| 490 |
return FrameOK; |
| 491 |
} |
| 492 |
|
| 493 |
bool WebSocketChannel::processFrame() |
| 494 |
{ |
| 495 |
ASSERT(m_buffer); |
| 496 |
|
| 497 |
FrameData frame; |
| 498 |
if (parseFrame(frame) != FrameOK) |
| 499 |
return false; |
| 500 |
|
| 501 |
// Validate the frame data. |
| 502 |
if (isReservedOpCode(frame.opCode)) { |
| 503 |
fail("Unrecognized frame opcode: " + String::number(frame.opCode)); |
| 504 |
return false; |
| 505 |
} |
| 506 |
|
| 507 |
if (frame.reserved1 || frame.reserved2 || frame.reserved3) { |
| 508 |
fail("One or more reserved bits are on: reserved1 = " + String::number(frame.reserved1) + ", reserved2 = " + String::number(frame.reserved2) + ", reserved3 = " + String::number(frame.reserved3)); |
| 509 |
return false; |
| 510 |
} |
| 511 |
|
| 512 |
// All control frames must not be fragmented. |
| 513 |
if (isControlOpCode(frame.opCode) && !frame.final) { |
| 514 |
fail("Received fragmented control frame: opcode = " + String::number(frame.opCode)); |
| 515 |
return false; |
| 516 |
} |
| 517 |
|
| 518 |
// All control frames must have a payload of 125 bytes or less, which means the frame must not contain |
| 519 |
// the "extended payload length" field. |
| 520 |
if (isControlOpCode(frame.opCode) && frame.payloadLength > maxPayloadLengthWithoutExtendedLengthField) { |
| 521 |
fail("Received control frame having too long payload: " + String::number(frame.payloadLength) + " bytes"); |
| 522 |
return false; |
| 523 |
} |
| 524 |
|
| 525 |
// A new data frame is received before the previous continuous frame finishes. |
| 526 |
// Note that control frames are allowed to come in the middle of continuous frames. |
| 527 |
if (m_hasContinuousFrame && frame.opCode != OpCodeContinuation && !isControlOpCode(frame.opCode)) { |
| 528 |
fail("Received new data frame but previous continuous frame is unfinished."); |
| 529 |
return false; |
| 530 |
} |
| 531 |
|
| 532 |
switch (frame.opCode) { |
| 533 |
case OpCodeContinuation: |
| 534 |
// Throw away content of a binary message because binary messages are not supported yet. |
| 535 |
if (m_continuousFrameOpCode == OpCodeText) |
| 536 |
m_continuousFrameData.append(frame.payload, frame.payloadLength); |
| 537 |
skipBuffer(frame.frameEnd - m_buffer); |
| 538 |
if (frame.final) { |
| 539 |
// onmessage handler may eventually call the other methods of this channel, |
| 540 |
// so we should pretend that we have finished to read this frame and |
| 541 |
// make sure that the member variables are in a consistent state before |
| 542 |
// the handler is invoked. |
| 543 |
// Vector<char>::swap() is used here to clear m_continuousFrameData. |
| 544 |
Vector<char> continuousFrameData; |
| 545 |
m_continuousFrameData.swap(continuousFrameData); |
| 546 |
m_hasContinuousFrame = false; |
| 547 |
if (m_continuousFrameOpCode == OpCodeText) { |
| 548 |
String message = String::fromUTF8(continuousFrameData.data(), continuousFrameData.size()); |
| 549 |
if (message.isNull()) |
| 550 |
fail("Could not decode a text frame as UTF-8."); |
| 551 |
else |
| 552 |
m_client->didReceiveMessage(message); |
| 553 |
} else if (m_continuousFrameOpCode == OpCodeBinary) { |
| 554 |
ASSERT(m_continuousFrameData.isEmpty()); |
| 555 |
fail("Received a binary frame which is not supported yet."); |
| 556 |
} |
| 557 |
} |
| 558 |
break; |
| 559 |
|
| 560 |
case OpCodeText: |
| 561 |
if (frame.final) { |
| 562 |
String message = String::fromUTF8(frame.payload, frame.payloadLength); |
| 563 |
skipBuffer(frame.frameEnd - m_buffer); |
| 564 |
if (message.isNull()) |
| 565 |
fail("Could not decode a text frame as UTF-8."); |
| 566 |
else |
| 567 |
m_client->didReceiveMessage(message); |
| 568 |
} else { |
| 569 |
m_hasContinuousFrame = true; |
| 570 |
m_continuousFrameOpCode = OpCodeText; |
| 571 |
ASSERT(m_continuousFrameData.isEmpty()); |
| 572 |
m_continuousFrameData.append(frame.payload, frame.payloadLength); |
| 573 |
skipBuffer(frame.frameEnd - m_buffer); |
| 574 |
} |
| 575 |
break; |
| 576 |
|
| 577 |
case OpCodeBinary: |
| 578 |
if (frame.final) |
| 579 |
fail("Received a binary frame which is not supported yet."); |
| 580 |
else { |
| 581 |
m_hasContinuousFrame = true; |
| 582 |
m_continuousFrameOpCode = OpCodeBinary; |
| 583 |
ASSERT(m_continuousFrameData.isEmpty()); |
| 584 |
// Do not store data of a binary message to m_continuousFrameData to save memory. |
| 585 |
skipBuffer(frame.frameEnd - m_buffer); |
| 586 |
} |
| 587 |
break; |
| 588 |
|
| 589 |
case OpCodeClose: |
| 590 |
// FIXME: Handle payload. |
| 591 |
skipBuffer(frame.frameEnd - m_buffer); |
| 592 |
m_receivedClosingHandshake = true; |
| 593 |
startClosingHandshake(); |
| 594 |
if (m_closing) |
| 595 |
m_handle->close(); // Close after sending a close frame. |
| 596 |
break; |
| 597 |
|
| 598 |
case OpCodePing: { |
| 599 |
bool result = sendFrame(OpCodePong, frame.payload, frame.payloadLength); |
| 600 |
skipBuffer(frame.frameEnd - m_buffer); |
| 601 |
if (!result) |
| 602 |
fail("Failed to send a pong frame."); |
| 603 |
break; |
| 604 |
} |
| 605 |
|
| 606 |
case OpCodePong: |
| 607 |
// A server may send a pong in response to our ping, or an unsolicited pong which is not associated with |
| 608 |
// any specific ping. Either way, there's nothing to do on receipt of pong. |
| 609 |
skipBuffer(frame.frameEnd - m_buffer); |
| 610 |
break; |
| 611 |
|
| 612 |
default: |
| 613 |
ASSERT_NOT_REACHED(); |
| 614 |
skipBuffer(frame.frameEnd - m_buffer); |
| 615 |
break; |
| 616 |
} |
| 617 |
|
| 618 |
return m_buffer; |
| 619 |
} |
| 620 |
|
| 621 |
bool WebSocketChannel::processFrameHixie76() |
| 622 |
{ |
| 348 |
const char* nextFrame = m_buffer; |
623 |
const char* nextFrame = m_buffer; |
| 349 |
const char* p = m_buffer; |
624 |
const char* p = m_buffer; |
| 350 |
const char* end = p + m_bufferSize; |
625 |
const char* end = p + m_bufferSize; |
|
Lines 427-468
bool WebSocketChannel::processBuffer()
a/Source/WebCore/websockets/WebSocketChannel.cpp_sec6
|
| 427 |
return false; |
702 |
return false; |
| 428 |
} |
703 |
} |
| 429 |
|
704 |
|
| 430 |
void WebSocketChannel::resumeTimerFired(Timer<WebSocketChannel>* timer) |
705 |
bool WebSocketChannel::sendFrame(OpCode opCode, const char* data, size_t dataLength) |
| 431 |
{ |
706 |
{ |
| 432 |
ASSERT_UNUSED(timer, timer == &m_resumeTimer); |
707 |
ASSERT(m_handle); |
|
|
708 |
ASSERT(!m_suspended); |
| 433 |
|
709 |
|
| 434 |
RefPtr<WebSocketChannel> protect(this); // The client can close the channel, potentially removing the last reference. |
710 |
Vector<char> frame; |
| 435 |
while (!m_suspended && m_client && m_buffer) |
711 |
ASSERT(!(opCode & ~opCodeMask)); // Checks whether "opCode" fits in the range of opCodes. |
| 436 |
if (!processBuffer()) |
712 |
frame.append(finalBit | opCode); |
| 437 |
break; |
713 |
if (dataLength <= maxPayloadLengthWithoutExtendedLengthField) |
| 438 |
if (!m_suspended && m_client && m_closed && m_handle) |
714 |
frame.append(maskBit | dataLength); |
| 439 |
didClose(m_handle.get()); |
715 |
else if (dataLength <= 0xFFFF) { |
|
|
716 |
frame.append(maskBit | payloadLengthWithTwoByteExtendedLengthField); |
| 717 |
frame.append((dataLength & 0xFF00) >> 8); |
| 718 |
frame.append(dataLength & 0xFF); |
| 719 |
} else { |
| 720 |
frame.append(maskBit | payloadLengthWithEightByteExtendedLengthField); |
| 721 |
char extendedPayloadLength[8]; |
| 722 |
size_t remaining = dataLength; |
| 723 |
// Fill the length into extendedPayloadLength in the network byte order. |
| 724 |
for (int i = 0; i < 8; ++i) { |
| 725 |
extendedPayloadLength[7 - i] = remaining & 0xFF; |
| 726 |
remaining >>= 8; |
| 727 |
} |
| 728 |
ASSERT(!remaining); |
| 729 |
frame.append(extendedPayloadLength, 8); |
| 730 |
} |
| 731 |
|
| 732 |
// Mask the frame. |
| 733 |
size_t maskingKeyStart = frame.size(); |
| 734 |
frame.grow(frame.size() + maskingKeyWidthInBytes); // Add placeholder for masking key. Will be overwritten. |
| 735 |
size_t payloadStart = frame.size(); |
| 736 |
frame.append(data, dataLength); |
| 737 |
|
| 738 |
cryptographicallyRandomValues(frame.data() + maskingKeyStart, maskingKeyWidthInBytes); |
| 739 |
for (size_t i = 0; i < dataLength; ++i) |
| 740 |
frame[payloadStart + i] ^= frame[maskingKeyStart + i % maskingKeyWidthInBytes]; |
| 741 |
|
| 742 |
return m_handle->send(frame.data(), frame.size()); |
| 440 |
} |
743 |
} |
| 441 |
|
744 |
|
| 442 |
void WebSocketChannel::startClosingHandshake() |
745 |
bool WebSocketChannel::sendFrameHixie76(const char* data, size_t dataLength) |
| 443 |
{ |
746 |
{ |
| 444 |
LOG(Network, "WebSocketChannel %p closing %d %d", this, m_closing, m_receivedClosingHandshake); |
|
|
| 445 |
if (m_closing) |
| 446 |
return; |
| 447 |
ASSERT(m_handle); |
747 |
ASSERT(m_handle); |
| 448 |
Vector<char> buf; |
748 |
ASSERT(!m_suspended); |
| 449 |
buf.append('\xff'); |
|
|
| 450 |
buf.append('\0'); |
| 451 |
if (!m_handle->send(buf.data(), buf.size())) { |
| 452 |
m_handle->disconnect(); |
| 453 |
return; |
| 454 |
} |
| 455 |
m_closing = true; |
| 456 |
if (m_client) |
| 457 |
m_client->didStartClosingHandshake(); |
| 458 |
} |
| 459 |
|
749 |
|
| 460 |
void WebSocketChannel::closingTimerFired(Timer<WebSocketChannel>* timer) |
750 |
Vector<char> frame; |
| 461 |
{ |
751 |
frame.append('\0'); // Frame type. |
| 462 |
LOG(Network, "WebSocketChannel %p closing timer", this); |
752 |
frame.append(data, dataLength); |
| 463 |
ASSERT_UNUSED(timer, &m_closingTimer == timer); |
753 |
frame.append('\xff'); // Frame end. |
| 464 |
if (m_handle) |
754 |
return m_handle->send(frame.data(), frame.size()); |
| 465 |
m_handle->disconnect(); |
|
|
| 466 |
} |
755 |
} |
| 467 |
|
756 |
|
| 468 |
} // namespace WebCore |
757 |
} // namespace WebCore |