summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-27 03:21:03 +0000
committerricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-27 03:21:03 +0000
commitea56b98bb5ec43de0f6cbff96f0b918ebb669492 (patch)
tree3b39814aec999b06e97029943875b4d3e4ac4a06 /net
parent2897491c3e677eca5ebc0d1af10762ff06f1e130 (diff)
downloadchromium_src-ea56b98bb5ec43de0f6cbff96f0b918ebb669492.zip
chromium_src-ea56b98bb5ec43de0f6cbff96f0b918ebb669492.tar.gz
chromium_src-ea56b98bb5ec43de0f6cbff96f0b918ebb669492.tar.bz2
Previously, FailChannel() called OnDropChannel(). This resulted in the
wrong status code being reported to Javascript and an inability to log failures to the console. Make FailChannel() call OnFailChannel(). In addition, add console messages to match the Blink-side implementation as closely as possible. Relax the incoming Close frame code validation to match existing Chrome behaviour. Also remove "Internal Error" Close reason payload text that the Blink implementation doesn't send. Add tests for the new console messages. BUG=326899 TEST=net_unittests --gtest_filter=WebSocketChannel* Review URL: https://codereview.chromium.org/135043004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@247189 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/websockets/websocket_channel.cc119
-rw-r--r--net/websockets/websocket_channel.h41
-rw-r--r--net/websockets/websocket_channel_test.cc206
3 files changed, 247 insertions, 119 deletions
diff --git a/net/websockets/websocket_channel.cc b/net/websockets/websocket_channel.cc
index 11ac6ce..2e63a26 100644
--- a/net/websockets/websocket_channel.cc
+++ b/net/websockets/websocket_channel.cc
@@ -13,6 +13,7 @@
#include "base/message_loop/message_loop.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "net/base/big_endian.h"
#include "net/base/io_buffer.h"
@@ -278,9 +279,9 @@ void WebSocketChannel::SendFrame(bool fin,
return;
}
if (data.size() > base::checked_cast<size_t>(current_send_quota_)) {
- AllowUnused(FailChannel(SEND_GOING_AWAY,
- kWebSocketMuxErrorSendQuotaViolation,
- "Send quota exceeded"));
+ // TODO(ricea): Kill renderer.
+ AllowUnused(
+ FailChannel("Send quota exceeded", kWebSocketErrorGoingAway, ""));
// |this| has been deleted.
return;
}
@@ -329,8 +330,7 @@ void WebSocketChannel::StartClosingHandshake(uint16 code,
// errata 3227 to RFC6455. If the renderer is sending us an invalid code or
// reason it must be malfunctioning in some way, and based on that we
// interpret this as an internal error.
- AllowUnused(
- SendClose(kWebSocketErrorInternalServerError, "Internal Error"));
+ AllowUnused(SendClose(kWebSocketErrorInternalServerError, ""));
// |this| may have been deleted.
return;
}
@@ -545,7 +545,10 @@ ChannelState WebSocketChannel::OnReadDone(bool synchronous, int result) {
return CHANNEL_ALIVE;
case ERR_WS_PROTOCOL_ERROR:
- return FailChannel(SEND_REAL_ERROR,
+ // This could be kWebSocketErrorProtocolError (specifically, non-minimal
+ // encoding of payload length) or kWebSocketErrorMessageTooBig, or an
+ // extension-specific error.
+ return FailChannel("Invalid frame header",
kWebSocketErrorProtocolError,
"WebSocket Protocol Error");
@@ -569,16 +572,20 @@ ChannelState WebSocketChannel::ProcessFrame(scoped_ptr<WebSocketFrame> frame) {
if (frame->header.masked) {
// RFC6455 Section 5.1 "A client MUST close a connection if it detects a
// masked frame."
- return FailChannel(SEND_REAL_ERROR,
- kWebSocketErrorProtocolError,
- "Masked frame from server");
+ return FailChannel(
+ "A server must not mask any frames that it sends to the "
+ "client.",
+ kWebSocketErrorProtocolError,
+ "Masked frame from server");
}
const WebSocketFrameHeader::OpCode opcode = frame->header.opcode;
if (WebSocketFrameHeader::IsKnownControlOpCode(opcode) &&
!frame->header.final) {
- return FailChannel(SEND_REAL_ERROR,
- kWebSocketErrorProtocolError,
- "Control message with FIN bit unset received");
+ return FailChannel(
+ base::StringPrintf("Received fragmented control frame: opcode = %d",
+ opcode),
+ kWebSocketErrorProtocolError,
+ "Control message with FIN bit unset received");
}
// Respond to the frame appropriately to its type.
@@ -620,11 +627,9 @@ ChannelState WebSocketChannel::HandleFrame(
frame_name = "Unknown frame type";
break;
}
- // SEND_REAL_ERROR makes no difference here, as FailChannel() won't send
- // another Close frame.
- return FailChannel(SEND_REAL_ERROR,
- kWebSocketErrorProtocolError,
- frame_name + " received after close");
+ // FailChannel() won't send another Close frame.
+ return FailChannel(
+ frame_name + " received after close", kWebSocketErrorProtocolError, "");
}
switch (opcode) {
case WebSocketFrameHeader::kOpCodeText: // fall-thru
@@ -664,7 +669,10 @@ ChannelState WebSocketChannel::HandleFrame(
case WebSocketFrameHeader::kOpCodeClose: {
uint16 code = kWebSocketNormalClosure;
std::string reason;
- ParseClose(data_buffer, size, &code, &reason);
+ std::string message;
+ if (!ParseClose(data_buffer, size, &code, &reason, &message)) {
+ return FailChannel(message, code, reason);
+ }
// TODO(ricea): Find a way to safely log the message from the close
// message (escape control codes and so on).
VLOG(1) << "Got Close with code " << code;
@@ -698,7 +706,9 @@ ChannelState WebSocketChannel::HandleFrame(
default:
return FailChannel(
- SEND_REAL_ERROR, kWebSocketErrorProtocolError, "Unknown opcode");
+ base::StringPrintf("Unrecognized frame opcode: %d", opcode),
+ kWebSocketErrorProtocolError,
+ "Unknown opcode");
}
}
@@ -730,7 +740,7 @@ ChannelState WebSocketChannel::SendIOBuffer(
return WriteFrames();
}
-ChannelState WebSocketChannel::FailChannel(ExposeError expose,
+ChannelState WebSocketChannel::FailChannel(const std::string& message,
uint16 code,
const std::string& reason) {
DCHECK_NE(FRESHLY_CONSTRUCTED, state_);
@@ -738,13 +748,7 @@ ChannelState WebSocketChannel::FailChannel(ExposeError expose,
DCHECK_NE(CLOSED, state_);
// TODO(ricea): Logging.
if (state_ == CONNECTED) {
- uint16 send_code = kWebSocketErrorGoingAway;
- std::string send_reason = "Internal Error";
- if (expose == SEND_REAL_ERROR) {
- send_code = code;
- send_reason = reason;
- }
- if (SendClose(send_code, send_reason) == // Sets state_ to SEND_CLOSED
+ if (SendClose(code, reason) == // Sets state_ to SEND_CLOSED
CHANNEL_DELETED)
return CHANNEL_DELETED;
}
@@ -754,7 +758,7 @@ ChannelState WebSocketChannel::FailChannel(ExposeError expose,
stream_->Close();
state_ = CLOSED;
- return DoDropChannel(code, reason);
+ return event_interface_->OnFailChannel(message);
}
ChannelState WebSocketChannel::SendClose(uint16 code,
@@ -792,40 +796,59 @@ ChannelState WebSocketChannel::SendClose(uint16 code,
return CHANNEL_ALIVE;
}
-void WebSocketChannel::ParseClose(const scoped_refptr<IOBuffer>& buffer,
+bool WebSocketChannel::ParseClose(const scoped_refptr<IOBuffer>& buffer,
size_t size,
uint16* code,
- std::string* reason) {
+ std::string* reason,
+ std::string* message) {
+ bool parsed_ok = true;
reason->clear();
if (size < kWebSocketCloseCodeLength) {
*code = kWebSocketErrorNoStatusReceived;
if (size != 0) {
- VLOG(1) << "Close frame with payload size " << size << " received "
- << "(the first byte is " << std::hex
- << static_cast<int>(buffer->data()[0]) << ")";
+ DVLOG(1) << "Close frame with payload size " << size << " received "
+ << "(the first byte is " << std::hex
+ << static_cast<int>(buffer->data()[0]) << ")";
+ parsed_ok = false;
+ *code = kWebSocketErrorProtocolError;
+ *message =
+ "Received a broken close frame containing an invalid size body.";
}
- return;
+ return parsed_ok;
}
const char* data = buffer->data();
uint16 unchecked_code = 0;
ReadBigEndian(data, &unchecked_code);
COMPILE_ASSERT(sizeof(unchecked_code) == kWebSocketCloseCodeLength,
they_should_both_be_two_bytes);
- if (unchecked_code >= static_cast<uint16>(kWebSocketNormalClosure) &&
- unchecked_code <=
- static_cast<uint16>(kWebSocketErrorPrivateReservedMax)) {
- *code = unchecked_code;
- } else {
- VLOG(1) << "Close frame contained code outside of the valid range: "
- << unchecked_code;
- *code = kWebSocketErrorAbnormalClosure;
- }
- std::string text(data + kWebSocketCloseCodeLength, data + size);
- // IsStringUTF8() blocks surrogate pairs and non-characters, so it is strictly
- // stronger than required by RFC3629.
- if (IsStringUTF8(text)) {
- reason->swap(text);
+ switch (unchecked_code) {
+ case kWebSocketErrorNoStatusReceived:
+ case kWebSocketErrorAbnormalClosure:
+ case kWebSocketErrorTlsHandshake:
+ *code = kWebSocketErrorProtocolError;
+ *message =
+ "Received a broken close frame containing a reserved status code.";
+ parsed_ok = false;
+ break;
+
+ default:
+ *code = unchecked_code;
+ break;
+ }
+ if (parsed_ok) {
+ std::string text(data + kWebSocketCloseCodeLength, data + size);
+ // IsStringUTF8() blocks surrogate pairs and non-characters, so it is
+ // strictly stronger than required by RFC3629.
+ if (IsStringUTF8(text)) {
+ reason->swap(text);
+ } else {
+ *code = kWebSocketErrorProtocolError;
+ *reason = "Invalid UTF-8 in Close frame";
+ *message = "Received a broken close frame containing invalid UTF-8.";
+ parsed_ok = false;
+ }
}
+ return parsed_ok;
}
ChannelState WebSocketChannel::DoDropChannel(uint16 code,
diff --git a/net/websockets/websocket_channel.h b/net/websockets/websocket_channel.h
index 22d7911..64cf91d 100644
--- a/net/websockets/websocket_channel.h
+++ b/net/websockets/websocket_channel.h
@@ -138,14 +138,6 @@ class NET_EXPORT WebSocketChannel {
// has been closed; or the connection is failed.
};
- // When failing a channel, sometimes it is inappropriate to expose the real
- // reason for failing to the remote server. This enum is used by FailChannel()
- // to select between sending the real status or a "Going Away" status.
- enum ExposeError {
- SEND_REAL_ERROR,
- SEND_GOING_AWAY,
- };
-
// Implementation of WebSocketStream::ConnectDelegate for
// WebSocketChannel. WebSocketChannel does not inherit from
// WebSocketStream::ConnectDelegate directly to avoid cluttering the public
@@ -218,14 +210,15 @@ class NET_EXPORT WebSocketChannel {
size_t size) WARN_UNUSED_RESULT;
// Performs the "Fail the WebSocket Connection" operation as defined in
- // RFC6455. The supplied code and reason are sent back to the renderer in an
- // OnDropChannel message. If state_ is CONNECTED then a Close message is sent
- // to the remote host. If |expose| is SEND_REAL_ERROR then the remote host is
- // given the same status code passed to the renderer; otherwise it is sent a
- // fixed "Going Away" code. Closes the stream_ and sets state_ to CLOSED.
- // FailChannel() always returns CHANNEL_DELETED. It is not valid to access any
- // member variables or methods after calling FailChannel().
- ChannelState FailChannel(ExposeError expose,
+ // RFC6455. A NotifyFailure message is sent to the renderer with |message|.
+ // The renderer will log the message to the console but not expose it to
+ // Javascript. Javascript will see a Close code of AbnormalClosure (1006) with
+ // an empty reason string. If state_ is CONNECTED then a Close message is sent
+ // to the remote host containing the supplied |code| and |reason|. If the
+ // stream is open, closes it and sets state_ to CLOSED. FailChannel() always
+ // returns CHANNEL_DELETED. It is not valid to access any member variables or
+ // methods after calling FailChannel().
+ ChannelState FailChannel(const std::string& message,
uint16 code,
const std::string& reason) WARN_UNUSED_RESULT;
@@ -236,15 +229,17 @@ class NET_EXPORT WebSocketChannel {
ChannelState SendClose(uint16 code,
const std::string& reason) WARN_UNUSED_RESULT;
- // Parses a Close frame. If no status code is supplied, then |code| is set to
- // 1005 (No status code) with empty |reason|. If the supplied code is
- // outside the valid range, then 1002 (Protocol error) is set instead. If the
- // reason text is not valid UTF-8, then |reason| is set to an empty string
- // instead.
- void ParseClose(const scoped_refptr<IOBuffer>& buffer,
+ // Parses a Close frame payload. If no status code is supplied, then |code| is
+ // set to 1005 (No status code) with empty |reason|. If the reason text is not
+ // valid UTF-8, then |reason| is set to an empty string. If the payload size
+ // is 1, or the supplied code is not permitted to be sent over the network,
+ // then false is returned and |message| is set to an appropriate console
+ // message.
+ bool ParseClose(const scoped_refptr<IOBuffer>& buffer,
size_t size,
uint16* code,
- std::string* reason);
+ std::string* reason,
+ std::string* message);
// Drop this channel.
// If there are pending opening handshake notifications, notify them
diff --git a/net/websockets/websocket_channel_test.cc b/net/websockets/websocket_channel_test.cc
index 896d972..f470899c6 100644
--- a/net/websockets/websocket_channel_test.cc
+++ b/net/websockets/websocket_channel_test.cc
@@ -39,6 +39,7 @@
#define WEBSOCKET_CLOSE_CODE_AS_STRING_NORMAL_CLOSURE "\x03\xe8"
#define WEBSOCKET_CLOSE_CODE_AS_STRING_GOING_AWAY "\x03\xe9"
#define WEBSOCKET_CLOSE_CODE_AS_STRING_PROTOCOL_ERROR "\x03\xea"
+#define WEBSOCKET_CLOSE_CODE_AS_STRING_ABNORMAL_CLOSURE "\x03\xee"
#define WEBSOCKET_CLOSE_CODE_AS_STRING_SERVER_ERROR "\x03\xf3"
namespace net {
@@ -243,15 +244,9 @@ class FakeWebSocketStream : public WebSocketStream {
// To make the static initialisers easier to read, we use enums rather than
// bools.
-enum IsFinal {
- NOT_FINAL_FRAME,
- FINAL_FRAME
-};
+enum IsFinal { NOT_FINAL_FRAME, FINAL_FRAME };
-enum IsMasked {
- NOT_MASKED,
- MASKED
-};
+enum IsMasked { NOT_MASKED, MASKED };
// This is used to initialise a WebSocketFrame but is statically initialisable.
struct InitFrame {
@@ -425,10 +420,7 @@ ACTION_P(InvokeClosureReturnDeleted, closure) {
// A FakeWebSocketStream whose ReadFrames() function returns data.
class ReadableFakeWebSocketStream : public FakeWebSocketStream {
public:
- enum IsSync {
- SYNC,
- ASYNC
- };
+ enum IsSync { SYNC, ASYNC };
// After constructing the object, call PrepareReadFrames() once for each
// time you wish it to return from the test.
@@ -758,10 +750,7 @@ class WebSocketChannelTest : public ::testing::Test {
// A struct containing the data that will be used to connect the channel.
// Grouped for readability.
struct ConnectData {
- ConnectData() :
- socket_url("ws://ws/"),
- origin("http://ws/")
- {}
+ ConnectData() : socket_url("ws://ws/"), origin("http://ws/") {}
// URLRequestContext object.
URLRequestContext url_request_context;
@@ -898,6 +887,8 @@ class WebSocketChannelEventInterfaceTest : public WebSocketChannelTest {
.WillByDefault(Return(CHANNEL_DELETED));
ON_CALL(*event_interface_, OnDropChannel(_, _))
.WillByDefault(Return(CHANNEL_DELETED));
+ ON_CALL(*event_interface_, OnFailChannel(_))
+ .WillByDefault(Return(CHANNEL_DELETED));
}
virtual ~WebSocketChannelEventInterfaceTest() {
@@ -1117,7 +1108,7 @@ TEST_F(WebSocketChannelDeletingTest, OnNotifyFinishOpeningHandshakeError) {
TEST_F(WebSocketChannelDeletingTest, FailChannelInSendFrame) {
set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
- deleting_ = EVENT_ON_DROP_CHANNEL;
+ deleting_ = EVENT_ON_FAIL_CHANNEL;
CreateChannelAndConnectSuccessfully();
ASSERT_TRUE(channel_);
channel_->SendFrame(true,
@@ -1132,7 +1123,7 @@ TEST_F(WebSocketChannelDeletingTest, FailChannelInOnReadDone) {
stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC,
ERR_WS_PROTOCOL_ERROR);
set_stream(stream.Pass());
- deleting_ = EVENT_ON_DROP_CHANNEL;
+ deleting_ = EVENT_ON_FAIL_CHANNEL;
CreateChannelAndConnectSuccessfully();
ASSERT_TRUE(channel_);
base::MessageLoop::current()->RunUntilIdle();
@@ -1146,7 +1137,7 @@ TEST_F(WebSocketChannelDeletingTest, FailChannelDueToMaskedFrame) {
{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "HELLO"}};
stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames);
set_stream(stream.Pass());
- deleting_ = EVENT_ON_DROP_CHANNEL;
+ deleting_ = EVENT_ON_FAIL_CHANNEL;
CreateChannelAndConnectSuccessfully();
EXPECT_EQ(NULL, channel_.get());
@@ -1159,7 +1150,7 @@ TEST_F(WebSocketChannelDeletingTest, FailChannelDueToBadControlFrame) {
{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, NOT_MASKED, ""}};
stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames);
set_stream(stream.Pass());
- deleting_ = EVENT_ON_DROP_CHANNEL;
+ deleting_ = EVENT_ON_FAIL_CHANNEL;
CreateChannelAndConnectSuccessfully();
EXPECT_EQ(NULL, channel_.get());
@@ -1173,7 +1164,7 @@ TEST_F(WebSocketChannelDeletingTest, FailChannelDueToBadControlFrameNull) {
{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, NOT_MASKED, NULL}};
stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames);
set_stream(stream.Pass());
- deleting_ = EVENT_ON_DROP_CHANNEL;
+ deleting_ = EVENT_ON_FAIL_CHANNEL;
CreateChannelAndConnectSuccessfully();
EXPECT_EQ(NULL, channel_.get());
@@ -1183,12 +1174,12 @@ TEST_F(WebSocketChannelDeletingTest, FailChannelDueToPongAfterClose) {
scoped_ptr<ReadableFakeWebSocketStream> stream(
new ReadableFakeWebSocketStream);
static const InitFrame frames[] = {
- {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED,
- CLOSE_DATA(NORMAL_CLOSURE, "Success")},
- {FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, NOT_MASKED, ""}};
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED,
+ CLOSE_DATA(NORMAL_CLOSURE, "Success")},
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, NOT_MASKED, ""}};
stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames);
set_stream(stream.Pass());
- deleting_ = EVENT_ON_DROP_CHANNEL;
+ deleting_ = EVENT_ON_FAIL_CHANNEL;
CreateChannelAndConnectSuccessfully();
EXPECT_EQ(NULL, channel_.get());
@@ -1198,12 +1189,12 @@ TEST_F(WebSocketChannelDeletingTest, FailChannelDueToPongAfterCloseNull) {
scoped_ptr<ReadableFakeWebSocketStream> stream(
new ReadableFakeWebSocketStream);
static const InitFrame frames[] = {
- {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED,
- CLOSE_DATA(NORMAL_CLOSURE, "Success")},
- {FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, NOT_MASKED, NULL}};
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED,
+ CLOSE_DATA(NORMAL_CLOSURE, "Success")},
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, NOT_MASKED, NULL}};
stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames);
set_stream(stream.Pass());
- deleting_ = EVENT_ON_DROP_CHANNEL;
+ deleting_ = EVENT_ON_FAIL_CHANNEL;
CreateChannelAndConnectSuccessfully();
EXPECT_EQ(NULL, channel_.get());
@@ -1215,7 +1206,7 @@ TEST_F(WebSocketChannelDeletingTest, FailChannelDueToUnknownOpCode) {
static const InitFrame frames[] = {{FINAL_FRAME, 0x7, NOT_MASKED, ""}};
stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames);
set_stream(stream.Pass());
- deleting_ = EVENT_ON_DROP_CHANNEL;
+ deleting_ = EVENT_ON_FAIL_CHANNEL;
CreateChannelAndConnectSuccessfully();
EXPECT_EQ(NULL, channel_.get());
@@ -1227,7 +1218,21 @@ TEST_F(WebSocketChannelDeletingTest, FailChannelDueToUnknownOpCodeNull) {
static const InitFrame frames[] = {{FINAL_FRAME, 0x7, NOT_MASKED, NULL}};
stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames);
set_stream(stream.Pass());
- deleting_ = EVENT_ON_DROP_CHANNEL;
+ deleting_ = EVENT_ON_FAIL_CHANNEL;
+
+ CreateChannelAndConnectSuccessfully();
+ EXPECT_EQ(NULL, channel_.get());
+}
+
+TEST_F(WebSocketChannelDeletingTest, FailChannelDueInvalidCloseReason) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ NOT_MASKED, CLOSE_DATA(NORMAL_CLOSURE, "\xFF")}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames);
+ set_stream(stream.Pass());
+ deleting_ = EVENT_ON_FAIL_CHANNEL;
CreateChannelAndConnectSuccessfully();
EXPECT_EQ(NULL, channel_.get());
@@ -1480,7 +1485,7 @@ TEST_F(WebSocketChannelEventInterfaceTest, MultiFrameControlMessageIsRejected) {
EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
EXPECT_CALL(*event_interface_, OnFlowControl(_));
EXPECT_CALL(*event_interface_,
- OnDropChannel(kWebSocketErrorProtocolError, _));
+ OnFailChannel("Received fragmented control frame: opcode = 9"));
}
CreateChannelAndConnectSuccessfully();
@@ -1538,8 +1543,10 @@ TEST_F(WebSocketChannelEventInterfaceTest, MaskedFramesAreRejected) {
InSequence s;
EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
EXPECT_CALL(*event_interface_, OnFlowControl(_));
- EXPECT_CALL(*event_interface_,
- OnDropChannel(kWebSocketErrorProtocolError, _));
+ EXPECT_CALL(
+ *event_interface_,
+ OnFailChannel(
+ "A server must not mask any frames that it sends to the client."));
}
CreateChannelAndConnectSuccessfully();
@@ -1560,7 +1567,7 @@ TEST_F(WebSocketChannelEventInterfaceTest, UnknownOpCodeIsRejected) {
EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
EXPECT_CALL(*event_interface_, OnFlowControl(_));
EXPECT_CALL(*event_interface_,
- OnDropChannel(kWebSocketErrorProtocolError, _));
+ OnFailChannel("Unrecognized frame opcode: 4"));
}
CreateChannelAndConnectSuccessfully();
@@ -1635,8 +1642,10 @@ TEST_F(WebSocketChannelEventInterfaceTest, FrameAfterInvalidFrame) {
InSequence s;
EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
EXPECT_CALL(*event_interface_, OnFlowControl(_));
- EXPECT_CALL(*event_interface_,
- OnDropChannel(kWebSocketErrorProtocolError, _));
+ EXPECT_CALL(
+ *event_interface_,
+ OnFailChannel(
+ "A server must not mask any frames that it sends to the client."));
}
CreateChannelAndConnectSuccessfully();
@@ -1719,8 +1728,7 @@ TEST_F(WebSocketChannelEventInterfaceTest, WriteOverQuotaIsRejected) {
InSequence s;
EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
EXPECT_CALL(*event_interface_, OnFlowControl(kDefaultInitialQuota));
- EXPECT_CALL(*event_interface_,
- OnDropChannel(kWebSocketMuxErrorSendQuotaViolation, _));
+ EXPECT_CALL(*event_interface_, OnFailChannel("Send quota exceeded"));
}
CreateChannelAndConnectSuccessfully();
@@ -1824,8 +1832,8 @@ TEST_F(WebSocketChannelEventInterfaceTest,
CreateChannelAndConnectSuccessfully();
}
-// If ReadFrames() returns ERR_WS_PROTOCOL_ERROR, then
-// kWebSocketErrorProtocolError must be sent to the renderer.
+// If ReadFrames() returns ERR_WS_PROTOCOL_ERROR, then the connection must be
+// failed.
TEST_F(WebSocketChannelEventInterfaceTest, SyncProtocolErrorGivesStatus1002) {
scoped_ptr<ReadableFakeWebSocketStream> stream(
new ReadableFakeWebSocketStream);
@@ -1835,8 +1843,7 @@ TEST_F(WebSocketChannelEventInterfaceTest, SyncProtocolErrorGivesStatus1002) {
EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
EXPECT_CALL(*event_interface_, OnFlowControl(_));
- EXPECT_CALL(*event_interface_,
- OnDropChannel(kWebSocketErrorProtocolError, _));
+ EXPECT_CALL(*event_interface_, OnFailChannel("Invalid frame header"));
CreateChannelAndConnectSuccessfully();
}
@@ -1851,8 +1858,7 @@ TEST_F(WebSocketChannelEventInterfaceTest, AsyncProtocolErrorGivesStatus1002) {
EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
EXPECT_CALL(*event_interface_, OnFlowControl(_));
- EXPECT_CALL(*event_interface_,
- OnDropChannel(kWebSocketErrorProtocolError, _));
+ EXPECT_CALL(*event_interface_, OnFailChannel("Invalid frame header"));
CreateChannelAndConnectSuccessfully();
base::MessageLoop::current()->RunUntilIdle();
@@ -1930,6 +1936,89 @@ TEST_F(WebSocketChannelEventInterfaceTest, FailJustAfterHandshake) {
base::MessageLoop::current()->RunUntilIdle();
}
+// Any frame after close is invalid. This test uses a Text frame. See also
+// test "PingAfterCloseIfRejected".
+TEST_F(WebSocketChannelEventInterfaceTest, DataAfterCloseIsRejected) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED,
+ CLOSE_DATA(NORMAL_CLOSURE, "OK")},
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "Payload"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames);
+ set_stream(stream.Pass());
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnClosingHandshake());
+ EXPECT_CALL(*event_interface_,
+ OnFailChannel("Data frame received after close"));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// A Close frame with a one-byte payload elicits a specific console error
+// message.
+TEST_F(WebSocketChannelEventInterfaceTest, OneByteClosePayloadMessage) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, "\x03"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames);
+ set_stream(stream.Pass());
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnFailChannel(
+ "Received a broken close frame containing an invalid size body."));
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// A Close frame with a reserved status code also elicits a specific console
+// error message.
+TEST_F(WebSocketChannelEventInterfaceTest, ClosePayloadReservedStatusMessage) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ NOT_MASKED, CLOSE_DATA(ABNORMAL_CLOSURE, "Not valid on wire")}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames);
+ set_stream(stream.Pass());
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnFailChannel(
+ "Received a broken close frame containing a reserved status code."));
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// A Close frame with invalid UTF-8 also elicits a specific console error
+// message.
+TEST_F(WebSocketChannelEventInterfaceTest, ClosePayloadInvalidReason) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ NOT_MASKED, CLOSE_DATA(NORMAL_CLOSURE, "\xFF")}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames);
+ set_stream(stream.Pass());
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnFailChannel(
+ "Received a broken close frame containing invalid UTF-8."));
+
+ CreateChannelAndConnectSuccessfully();
+}
+
// The closing handshake times out and sends an OnDropChannel event if no
// response to the client Close message is received.
TEST_F(WebSocketChannelEventInterfaceTest,
@@ -2095,7 +2184,7 @@ TEST_F(WebSocketChannelStreamTest, CloseOnlySentOnce) {
TEST_F(WebSocketChannelStreamTest, InvalidCloseStatusCodeNotSent) {
static const InitFrame expected[] = {
{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
- MASKED, CLOSE_DATA(SERVER_ERROR, "Internal Error")}};
+ MASKED, CLOSE_DATA(SERVER_ERROR, "")}};
EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
@@ -2112,7 +2201,7 @@ TEST_F(WebSocketChannelStreamTest, InvalidCloseStatusCodeNotSent) {
TEST_F(WebSocketChannelStreamTest, LongCloseReasonNotSent) {
static const InitFrame expected[] = {
{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
- MASKED, CLOSE_DATA(SERVER_ERROR, "Internal Error")}};
+ MASKED, CLOSE_DATA(SERVER_ERROR, "")}};
EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
@@ -2159,6 +2248,27 @@ TEST_F(WebSocketChannelStreamTest, Code1005IsNotEchoedNull) {
CreateChannelAndConnectSuccessfully();
}
+// Receiving an invalid UTF-8 payload in a Close frame causes us to fail the
+// connection.
+TEST_F(WebSocketChannelStreamTest, CloseFrameInvalidUtf8) {
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ NOT_MASKED, CLOSE_DATA(NORMAL_CLOSURE, "\xFF")}};
+ static const InitFrame expected[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ MASKED, CLOSE_DATA(PROTOCOL_ERROR, "Invalid UTF-8 in Close frame")}};
+
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(ReturnFrames(&frames))
+ .WillRepeatedly(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(*mock_stream_, Close());
+
+ CreateChannelAndConnectSuccessfully();
+}
+
// RFC6455 5.5.2 "Upon receipt of a Ping frame, an endpoint MUST send a Pong
// frame in response"
// 5.5.3 "A Pong frame sent in response to a Ping frame must have identical
@@ -2314,7 +2424,7 @@ TEST_F(WebSocketChannelStreamTest, WaitingMessagesAreBatched) {
TEST_F(WebSocketChannelStreamTest, MuxErrorIsNotSentToStream) {
static const InitFrame expected[] = {
{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
- MASKED, CLOSE_DATA(GOING_AWAY, "Internal Error")}};
+ MASKED, CLOSE_DATA(GOING_AWAY, "")}};
EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _))