diff options
author | yhirano@chromium.org <yhirano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-09 10:38:04 +0000 |
---|---|---|
committer | yhirano@chromium.org <yhirano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-09 10:38:04 +0000 |
commit | 968682039b8f5c4f98a17be7a85afa623938a35d (patch) | |
tree | e0a847d169f0bbf04ee2fea5914f1953ab7d2702 /net | |
parent | a44d2ea810e1509891e68f678598a6d2b81004fe (diff) | |
download | chromium_src-968682039b8f5c4f98a17be7a85afa623938a35d.zip chromium_src-968682039b8f5c4f98a17be7a85afa623938a35d.tar.gz chromium_src-968682039b8f5c4f98a17be7a85afa623938a35d.tar.bz2 |
Fail WebSocket channel when handshake fails.
Call WebSocketMsg_NotifyFailure instead of
WebSocketMsg_AddChannelResponse(true, ...) when the opening handshake fails.
BUG=310405
R=ricea@chromium.org
Review URL: https://codereview.chromium.org/105833003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@243835 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/http/http_stream_factory_impl_unittest.cc | 4 | ||||
-rw-r--r-- | net/url_request/url_request_http_job_unittest.cc | 4 | ||||
-rw-r--r-- | net/websockets/websocket_basic_handshake_stream.cc | 225 | ||||
-rw-r--r-- | net/websockets/websocket_basic_handshake_stream.h | 4 | ||||
-rw-r--r-- | net/websockets/websocket_channel.cc | 8 | ||||
-rw-r--r-- | net/websockets/websocket_channel.h | 2 | ||||
-rw-r--r-- | net/websockets/websocket_channel_test.cc | 25 | ||||
-rw-r--r-- | net/websockets/websocket_event_interface.h | 10 | ||||
-rw-r--r-- | net/websockets/websocket_handshake_stream_base.h | 6 | ||||
-rw-r--r-- | net/websockets/websocket_stream.cc | 20 | ||||
-rw-r--r-- | net/websockets/websocket_stream.h | 7 | ||||
-rw-r--r-- | net/websockets/websocket_stream_test.cc | 166 |
12 files changed, 393 insertions, 88 deletions
diff --git a/net/http/http_stream_factory_impl_unittest.cc b/net/http/http_stream_factory_impl_unittest.cc index e3169b6..c326b8a 100644 --- a/net/http/http_stream_factory_impl_unittest.cc +++ b/net/http/http_stream_factory_impl_unittest.cc @@ -117,6 +117,10 @@ class MockWebSocketHandshakeStream : public WebSocketHandshakeStreamBase { return scoped_ptr<WebSocketStream>(); } + virtual std::string GetFailureMessage() const OVERRIDE { + return std::string(); + } + private: const StreamType type_; }; diff --git a/net/url_request/url_request_http_job_unittest.cc b/net/url_request/url_request_http_job_unittest.cc index 9637917..e7f0bf7e 100644 --- a/net/url_request/url_request_http_job_unittest.cc +++ b/net/url_request/url_request_http_job_unittest.cc @@ -257,6 +257,10 @@ class FakeWebSocketHandshakeStream : public WebSocketHandshakeStreamBase { return scoped_ptr<WebSocketStream>(); } + virtual std::string GetFailureMessage() const OVERRIDE { + return std::string(); + } + private: bool initialize_stream_was_called_; }; diff --git a/net/websockets/websocket_basic_handshake_stream.cc b/net/websockets/websocket_basic_handshake_stream.cc index 73a1045..e30cf9e 100644 --- a/net/websockets/websocket_basic_handshake_stream.cc +++ b/net/websockets/websocket_basic_handshake_stream.cc @@ -6,6 +6,7 @@ #include <algorithm> #include <iterator> +#include <string> #include "base/base64.h" #include "base/basictypes.h" @@ -13,6 +14,7 @@ #include "base/containers/hash_tables.h" #include "base/stl_util.h" #include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" #include "crypto/random.h" #include "net/http/http_request_headers.h" #include "net/http/http_request_info.h" @@ -22,6 +24,7 @@ #include "net/http/http_stream_parser.h" #include "net/socket/client_socket_handle.h" #include "net/websockets/websocket_basic_stream.h" +#include "net/websockets/websocket_extension_parser.h" #include "net/websockets/websocket_handshake_constants.h" #include "net/websockets/websocket_handshake_handler.h" #include "net/websockets/websocket_stream.h" @@ -29,6 +32,23 @@ namespace net { namespace { +enum GetHeaderResult { + GET_HEADER_OK, + GET_HEADER_MISSING, + GET_HEADER_MULTIPLE, +}; + +std::string MissingHeaderMessage(const std::string& header_name) { + return std::string("'") + header_name + "' header is missing"; +} + +std::string MultipleHeaderValuesMessage(const std::string& header_name) { + return + std::string("'") + + header_name + + "' header must not appear more than once in a response"; +} + std::string GenerateHandshakeChallenge() { std::string raw_challenge(websockets::kRawChallengeLength, '\0'); crypto::RandBytes(string_as_array(&raw_challenge), raw_challenge.length()); @@ -45,57 +65,162 @@ void AddVectorHeaderIfNonEmpty(const char* name, headers->SetHeader(name, JoinString(value, ", ")); } -// If |case_sensitive| is false, then |value| must be in lower-case. -bool ValidateSingleTokenHeader( - const scoped_refptr<HttpResponseHeaders>& headers, - const base::StringPiece& name, - const std::string& value, - bool case_sensitive) { +GetHeaderResult GetSingleHeaderValue(const HttpResponseHeaders* headers, + const base::StringPiece& name, + std::string* value) { void* state = NULL; - std::string token; - int tokens = 0; - bool has_value = false; - while (headers->EnumerateHeader(&state, name, &token)) { - if (++tokens > 1) - return false; - has_value = case_sensitive ? value == token - : LowerCaseEqualsASCII(token, value.c_str()); + size_t num_values = 0; + std::string temp_value; + while (headers->EnumerateHeader(&state, name, &temp_value)) { + if (++num_values > 1) + return GET_HEADER_MULTIPLE; + *value = temp_value; + } + return num_values > 0 ? GET_HEADER_OK : GET_HEADER_MISSING; +} + +bool ValidateHeaderHasSingleValue(GetHeaderResult result, + const std::string& header_name, + std::string* failure_message) { + if (result == GET_HEADER_MISSING) { + *failure_message = MissingHeaderMessage(header_name); + return false; + } + if (result == GET_HEADER_MULTIPLE) { + *failure_message = MultipleHeaderValuesMessage(header_name); + return false; + } + DCHECK_EQ(result, GET_HEADER_OK); + return true; +} + +bool ValidateUpgrade(const HttpResponseHeaders* headers, + std::string* failure_message) { + std::string value; + GetHeaderResult result = + GetSingleHeaderValue(headers, websockets::kUpgrade, &value); + if (!ValidateHeaderHasSingleValue(result, + websockets::kUpgrade, + failure_message)) { + return false; + } + + if (!LowerCaseEqualsASCII(value, websockets::kWebSocketLowercase)) { + *failure_message = + "'Upgrade' header value is not 'WebSocket': " + value; + return false; + } + return true; +} + +bool ValidateSecWebSocketAccept(const HttpResponseHeaders* headers, + const std::string& expected, + std::string* failure_message) { + std::string actual; + GetHeaderResult result = + GetSingleHeaderValue(headers, websockets::kSecWebSocketAccept, &actual); + if (!ValidateHeaderHasSingleValue(result, + websockets::kSecWebSocketAccept, + failure_message)) { + return false; + } + + if (expected != actual) { + *failure_message = "Incorrect 'Sec-WebSocket-Accept' header value"; + return false; + } + return true; +} + +bool ValidateConnection(const HttpResponseHeaders* headers, + std::string* failure_message) { + // Connection header is permitted to contain other tokens. + if (!headers->HasHeader(HttpRequestHeaders::kConnection)) { + *failure_message = MissingHeaderMessage(HttpRequestHeaders::kConnection); + return false; } - return has_value; + if (!headers->HasHeaderValue(HttpRequestHeaders::kConnection, + websockets::kUpgrade)) { + *failure_message = "'Connection' header value must contain 'Upgrade'"; + return false; + } + return true; } bool ValidateSubProtocol( - const scoped_refptr<HttpResponseHeaders>& headers, + const HttpResponseHeaders* headers, const std::vector<std::string>& requested_sub_protocols, - std::string* sub_protocol) { + std::string* sub_protocol, + std::string* failure_message) { void* state = NULL; - std::string token; + std::string value; base::hash_set<std::string> requested_set(requested_sub_protocols.begin(), requested_sub_protocols.end()); - int accepted = 0; - while (headers->EnumerateHeader( - &state, websockets::kSecWebSocketProtocol, &token)) { - if (requested_set.count(token) == 0) - return false; + int count = 0; + bool has_multiple_protocols = false; + bool has_invalid_protocol = false; + + while (!has_invalid_protocol || !has_multiple_protocols) { + std::string temp_value; + if (!headers->EnumerateHeader( + &state, websockets::kSecWebSocketProtocol, &temp_value)) + break; + value = temp_value; + if (requested_set.count(value) == 0) + has_invalid_protocol = true; + if (++count > 1) + has_multiple_protocols = true; + } - *sub_protocol = token; - // The server is only allowed to accept one protocol. - if (++accepted > 1) - return false; + if (has_multiple_protocols) { + *failure_message = + MultipleHeaderValuesMessage(websockets::kSecWebSocketProtocol); + return false; + } else if (count > 0 && requested_sub_protocols.size() == 0) { + *failure_message = + std::string("Response must not include 'Sec-WebSocket-Protocol' " + "header if not present in request: ") + + value; + return false; + } else if (has_invalid_protocol) { + *failure_message = + "'Sec-WebSocket-Protocol' header value '" + + value + + "' in response does not match any of sent values"; + return false; + } else if (requested_sub_protocols.size() > 0 && count == 0) { + *failure_message = + "Sent non-empty 'Sec-WebSocket-Protocol' header " + "but no response was received"; + return false; } - // If the browser requested > 0 protocols, the server is required to accept - // one. - return requested_set.empty() || accepted == 1; + *sub_protocol = value; + return true; } -bool ValidateExtensions(const scoped_refptr<HttpResponseHeaders>& headers, +bool ValidateExtensions(const HttpResponseHeaders* headers, const std::vector<std::string>& requested_extensions, - std::string* extensions) { + std::string* extensions, + std::string* failure_message) { void* state = NULL; - std::string token; + std::string value; while (headers->EnumerateHeader( - &state, websockets::kSecWebSocketExtensions, &token)) { + &state, websockets::kSecWebSocketExtensions, &value)) { + WebSocketExtensionParser parser; + parser.Parse(value); + if (parser.has_error()) { + // TODO(yhirano) Set appropriate failure message. + *failure_message = + "'Sec-WebSocket-Extensions' header value is " + "rejected by the parser: " + + value; + return false; + } // TODO(ricea): Accept permessage-deflate with valid parameters. + *failure_message = + "Found an unsupported extension '" + + parser.extension().name() + + "' in 'Sec-WebSocket-Extensions' header"; return false; } return true; @@ -267,6 +392,10 @@ void WebSocketBasicHandshakeStream::SetWebSocketKeyForTesting( handshake_challenge_for_testing_.reset(new std::string(key)); } +std::string WebSocketBasicHandshakeStream::GetFailureMessage() const { + return failure_message_; +} + void WebSocketBasicHandshakeStream::ReadResponseHeadersCallback( const CompletionCallback& callback, int result) { @@ -292,26 +421,30 @@ int WebSocketBasicHandshakeStream::ValidateResponse() { // Other status codes are potentially risky (see the warnings in the // WHATWG WebSocket API spec) and so are dropped by default. default: + failure_message_ = base::StringPrintf("Unexpected status code: %d", + headers->response_code()); return ERR_INVALID_RESPONSE; } } int WebSocketBasicHandshakeStream::ValidateUpgradeResponse( const scoped_refptr<HttpResponseHeaders>& headers) { - if (ValidateSingleTokenHeader(headers, - websockets::kUpgrade, - websockets::kWebSocketLowercase, - false) && - ValidateSingleTokenHeader(headers, - websockets::kSecWebSocketAccept, - handshake_challenge_response_, - true) && - headers->HasHeaderValue(HttpRequestHeaders::kConnection, - websockets::kUpgrade) && - ValidateSubProtocol(headers, requested_sub_protocols_, &sub_protocol_) && - ValidateExtensions(headers, requested_extensions_, &extensions_)) { + if (ValidateUpgrade(headers.get(), &failure_message_) && + ValidateSecWebSocketAccept(headers.get(), + handshake_challenge_response_, + &failure_message_) && + ValidateConnection(headers.get(), &failure_message_) && + ValidateSubProtocol(headers.get(), + requested_sub_protocols_, + &sub_protocol_, + &failure_message_) && + ValidateExtensions(headers.get(), + requested_extensions_, + &extensions_, + &failure_message_)) { return OK; } + failure_message_ = "Error during WebSocket handshake: " + failure_message_; return ERR_INVALID_RESPONSE; } diff --git a/net/websockets/websocket_basic_handshake_stream.h b/net/websockets/websocket_basic_handshake_stream.h index 2e5b628..8f3cdf5 100644 --- a/net/websockets/websocket_basic_handshake_stream.h +++ b/net/websockets/websocket_basic_handshake_stream.h @@ -72,6 +72,8 @@ class NET_EXPORT_PRIVATE WebSocketBasicHandshakeStream // For tests only. void SetWebSocketKeyForTesting(const std::string& key); + virtual std::string GetFailureMessage() const OVERRIDE; + private: // A wrapper for the ReadResponseHeaders callback that checks whether or not // the connection has been accepted. @@ -114,6 +116,8 @@ class NET_EXPORT_PRIVATE WebSocketBasicHandshakeStream // The extension(s) selected by the server. std::string extensions_; + std::string failure_message_; + DISALLOW_COPY_AND_ASSIGN(WebSocketBasicHandshakeStream); }; diff --git a/net/websockets/websocket_channel.cc b/net/websockets/websocket_channel.cc index 61a47b7..34c58b3 100644 --- a/net/websockets/websocket_channel.cc +++ b/net/websockets/websocket_channel.cc @@ -112,8 +112,8 @@ class WebSocketChannel::ConnectDelegate // |this| may have been deleted. } - virtual void OnFailure(uint16 websocket_error) OVERRIDE { - creator_->OnConnectFailure(websocket_error); + virtual void OnFailure(const std::string& message) OVERRIDE { + creator_->OnConnectFailure(message); // |this| has been deleted. } @@ -312,11 +312,11 @@ void WebSocketChannel::OnConnectSuccess(scoped_ptr<WebSocketStream> stream) { // |this| may have been deleted. } -void WebSocketChannel::OnConnectFailure(uint16 websocket_error) { +void WebSocketChannel::OnConnectFailure(const std::string& message) { DCHECK_EQ(CONNECTING, state_); state_ = CLOSED; stream_request_.reset(); - AllowUnused(event_interface_->OnAddChannelResponse(true, "")); + AllowUnused(event_interface_->OnFailChannel(message)); // |this| has been deleted. } diff --git a/net/websockets/websocket_channel.h b/net/websockets/websocket_channel.h index 61f191a..721f71c 100644 --- a/net/websockets/websocket_channel.h +++ b/net/websockets/websocket_channel.h @@ -153,7 +153,7 @@ class NET_EXPORT WebSocketChannel { // Failure callback from WebSocketStream::CreateAndConnectStream(). Reports // failure to the event interface. May delete |this|. - void OnConnectFailure(uint16 websocket_error); + void OnConnectFailure(const std::string& message); // Returns true if state_ is SEND_CLOSED, CLOSE_WAIT or CLOSED. bool InClosingState() const; diff --git a/net/websockets/websocket_channel_test.cc b/net/websockets/websocket_channel_test.cc index 1bd75db..2e20f30 100644 --- a/net/websockets/websocket_channel_test.cc +++ b/net/websockets/websocket_channel_test.cc @@ -142,8 +142,9 @@ class MockWebSocketEventInterface : public WebSocketEventInterface { ChannelState(bool, WebSocketMessageType, const std::vector<char>&)); // NOLINT - MOCK_METHOD1(OnFlowControl, ChannelState(int64)); // NOLINT + MOCK_METHOD1(OnFlowControl, ChannelState(int64)); // NOLINT MOCK_METHOD0(OnClosingHandshake, ChannelState(void)); // NOLINT + MOCK_METHOD1(OnFailChannel, ChannelState(const std::string&)); // NOLINT MOCK_METHOD2(OnDropChannel, ChannelState(uint16, const std::string&)); // NOLINT }; @@ -165,6 +166,9 @@ class FakeWebSocketEventInterface : public WebSocketEventInterface { return CHANNEL_ALIVE; } virtual ChannelState OnClosingHandshake() OVERRIDE { return CHANNEL_ALIVE; } + virtual ChannelState OnFailChannel(const std::string& message) OVERRIDE { + return CHANNEL_DELETED; + } virtual ChannelState OnDropChannel(uint16 code, const std::string& reason) OVERRIDE { return CHANNEL_DELETED; @@ -761,7 +765,8 @@ enum EventInterfaceCall { EVENT_ON_DATA_FRAME = 0x2, EVENT_ON_FLOW_CONTROL = 0x4, EVENT_ON_CLOSING_HANDSHAKE = 0x8, - EVENT_ON_DROP_CHANNEL = 0x10, + EVENT_ON_FAIL_CHANNEL = 0x10, + EVENT_ON_DROP_CHANNEL = 0x20, }; class WebSocketChannelDeletingTest : public WebSocketChannelTest { @@ -780,6 +785,7 @@ class WebSocketChannelDeletingTest : public WebSocketChannelTest { : deleting_(EVENT_ON_ADD_CHANNEL_RESPONSE | EVENT_ON_DATA_FRAME | EVENT_ON_FLOW_CONTROL | EVENT_ON_CLOSING_HANDSHAKE | + EVENT_ON_FAIL_CHANNEL | EVENT_ON_DROP_CHANNEL) {} // Create a ChannelDeletingFakeWebSocketEventInterface. Defined out-of-line to // avoid circular dependency. @@ -820,6 +826,10 @@ class ChannelDeletingFakeWebSocketEventInterface return fixture_->DeleteIfDeleting(EVENT_ON_CLOSING_HANDSHAKE); } + virtual ChannelState OnFailChannel(const std::string& message) OVERRIDE { + return fixture_->DeleteIfDeleting(EVENT_ON_FAIL_CHANNEL); + } + virtual ChannelState OnDropChannel(uint16 code, const std::string& reason) OVERRIDE { return fixture_->DeleteIfDeleting(EVENT_ON_DROP_CHANNEL); @@ -915,8 +925,7 @@ TEST_F(WebSocketChannelTest, SendFlowControlDuringHandshakeOkay) { TEST_F(WebSocketChannelDeletingTest, OnAddChannelResponseFail) { CreateChannelAndConnect(); EXPECT_TRUE(channel_); - connect_data_.creator.connect_delegate->OnFailure( - kWebSocketErrorNoStatusReceived); + connect_data_.creator.connect_delegate->OnFailure("bye"); EXPECT_EQ(NULL, channel_.get()); } @@ -964,7 +973,7 @@ TEST_F(WebSocketChannelDeletingTest, OnFlowControlAfterConnect) { TEST_F(WebSocketChannelDeletingTest, OnFlowControlAfterSend) { set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream)); // Avoid deleting the channel yet. - deleting_ = EVENT_ON_DROP_CHANNEL; + deleting_ = EVENT_ON_FAIL_CHANNEL | EVENT_ON_DROP_CHANNEL; CreateChannelAndConnectSuccessfully(); ASSERT_TRUE(channel_); deleting_ = EVENT_ON_FLOW_CONTROL; @@ -1156,13 +1165,11 @@ TEST_F(WebSocketChannelEventInterfaceTest, ConnectSuccessReported) { } TEST_F(WebSocketChannelEventInterfaceTest, ConnectFailureReported) { - // true means failure. - EXPECT_CALL(*event_interface_, OnAddChannelResponse(true, "")); + EXPECT_CALL(*event_interface_, OnFailChannel("hello")); CreateChannelAndConnect(); - connect_data_.creator.connect_delegate->OnFailure( - kWebSocketErrorNoStatusReceived); + connect_data_.creator.connect_delegate->OnFailure("hello"); } TEST_F(WebSocketChannelEventInterfaceTest, NonWebSocketSchemeRejected) { diff --git a/net/websockets/websocket_event_interface.h b/net/websockets/websocket_event_interface.h index baba88c..9ca96f6 100644 --- a/net/websockets/websocket_event_interface.h +++ b/net/websockets/websocket_event_interface.h @@ -71,6 +71,16 @@ class NET_EXPORT WebSocketEventInterface { virtual ChannelState OnDropChannel(uint16 code, const std::string& reason) WARN_UNUSED_RESULT = 0; + // Called when the browser fails the channel, as specified in the spec. + // + // The channel should not be used again after OnFailChannel() has been + // called. + // + // This method returns a ChannelState for consistency, but all implementations + // must delete the Channel and return CHANNEL_DELETED. + virtual ChannelState OnFailChannel(const std::string& message) + WARN_UNUSED_RESULT = 0; + protected: WebSocketEventInterface() {} diff --git a/net/websockets/websocket_handshake_stream_base.h b/net/websockets/websocket_handshake_stream_base.h index 71d8321..b172a55 100644 --- a/net/websockets/websocket_handshake_stream_base.h +++ b/net/websockets/websocket_handshake_stream_base.h @@ -9,6 +9,8 @@ // Since net/http can be built without linking net/websockets code, // this file must not introduce any link-time dependencies on websockets. +#include <string> + #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" @@ -64,6 +66,10 @@ class NET_EXPORT WebSocketHandshakeStreamBase : public HttpStreamBase { // been called. virtual scoped_ptr<WebSocketStream> Upgrade() = 0; + // Returns a string describing the connection failure information. + // Returns an empty string if there is no failure. + virtual std::string GetFailureMessage() const = 0; + protected: // As with the destructor, this must be inline. WebSocketHandshakeStreamBase() {} diff --git a/net/websockets/websocket_stream.cc b/net/websockets/websocket_stream.cc index e81c24e..450ce32 100644 --- a/net/websockets/websocket_stream.cc +++ b/net/websockets/websocket_stream.cc @@ -70,7 +70,25 @@ class StreamRequestImpl : public WebSocketStreamRequest { } void ReportFailure() { - connect_delegate_->OnFailure(kWebSocketErrorAbnormalClosure); + std::string failure_message; + if (create_helper_->stream()) { + failure_message = create_helper_->stream()->GetFailureMessage(); + } else { + switch (url_request_.status().status()) { + case URLRequestStatus::SUCCESS: + case URLRequestStatus::IO_PENDING: + break; + case URLRequestStatus::CANCELED: + failure_message = "WebSocket opening handshake was canceled"; + break; + case URLRequestStatus::FAILED: + failure_message = + std::string("Error in connection establishment: ") + + ErrorToString(url_request_.status().error()); + break; + } + } + connect_delegate_->OnFailure(failure_message); } private: diff --git a/net/websockets/websocket_stream.h b/net/websockets/websocket_stream.h index c08f8dc..78180fd 100644 --- a/net/websockets/websocket_stream.h +++ b/net/websockets/websocket_stream.h @@ -57,10 +57,9 @@ class NET_EXPORT_PRIVATE WebSocketStream { // WebSocketStream. virtual void OnSuccess(scoped_ptr<WebSocketStream> stream) = 0; - // Called on failure to connect. The parameter is either one of the values - // defined in net::WebSocketError, or an error defined by some WebSocket - // extension protocol that we implement. - virtual void OnFailure(unsigned short websocket_error) = 0; + // Called on failure to connect. + // |message| contains defails of the failure. + virtual void OnFailure(const std::string& message) = 0; }; // Create and connect a WebSocketStream of an appropriate type. The actual diff --git a/net/websockets/websocket_stream_test.cc b/net/websockets/websocket_stream_test.cc index 3e11a95..f48dcbe 100644 --- a/net/websockets/websocket_stream_test.cc +++ b/net/websockets/websocket_stream_test.cc @@ -45,7 +45,7 @@ class DeterministicKeyWebSocketHandshakeStreamCreateHelper class WebSocketStreamCreateTest : public ::testing::Test { protected: - WebSocketStreamCreateTest() : websocket_error_(0) {} + WebSocketStreamCreateTest(): has_failed_(false) {} void CreateAndConnectCustomResponse( const std::string& socket_url, @@ -110,18 +110,21 @@ class WebSocketStreamCreateTest : public ::testing::Test { return std::vector<std::string>(); } - uint16 error() const { return websocket_error_; } + const std::string& failure_message() const { return failure_message_; } + bool has_failed() const { return has_failed_; } class TestConnectDelegate : public WebSocketStream::ConnectDelegate { public: - TestConnectDelegate(WebSocketStreamCreateTest* owner) : owner_(owner) {} + explicit TestConnectDelegate(WebSocketStreamCreateTest* owner) + : owner_(owner) {} virtual void OnSuccess(scoped_ptr<WebSocketStream> stream) OVERRIDE { stream.swap(owner_->stream_); } - virtual void OnFailure(uint16 websocket_error) OVERRIDE { - owner_->websocket_error_ = websocket_error; + virtual void OnFailure(const std::string& message) OVERRIDE { + owner_->has_failed_ = true; + owner_->failure_message_ = message; } private: @@ -132,8 +135,9 @@ class WebSocketStreamCreateTest : public ::testing::Test { scoped_ptr<WebSocketStreamRequest> stream_request_; // Only set if the connection succeeded. scoped_ptr<WebSocketStream> stream_; - // Only set if the connection failed. 0 otherwise. - uint16 websocket_error_; + // Only set if the connection failed. + std::string failure_message_; + bool has_failed_; }; // Confirm that the basic case works as expected. @@ -141,6 +145,7 @@ TEST_F(WebSocketStreamCreateTest, SimpleSuccess) { CreateAndConnectStandard( "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", ""); RunUntilIdle(); + EXPECT_FALSE(has_failed()); EXPECT_TRUE(stream_); } @@ -148,6 +153,7 @@ TEST_F(WebSocketStreamCreateTest, SimpleSuccess) { TEST_F(WebSocketStreamCreateTest, NeedsToRunLoop) { CreateAndConnectStandard( "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", ""); + EXPECT_FALSE(has_failed()); EXPECT_FALSE(stream_); } @@ -160,6 +166,7 @@ TEST_F(WebSocketStreamCreateTest, PathIsUsed) { "", ""); RunUntilIdle(); + EXPECT_FALSE(has_failed()); EXPECT_TRUE(stream_); } @@ -172,6 +179,7 @@ TEST_F(WebSocketStreamCreateTest, OriginIsUsed) { "", ""); RunUntilIdle(); + EXPECT_FALSE(has_failed()); EXPECT_TRUE(stream_); } @@ -189,6 +197,7 @@ TEST_F(WebSocketStreamCreateTest, SubProtocolIsUsed) { "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n"); RunUntilIdle(); EXPECT_TRUE(stream_); + EXPECT_FALSE(has_failed()); EXPECT_EQ("chatv20.chromium.org", stream_->GetSubProtocol()); } @@ -202,20 +211,30 @@ TEST_F(WebSocketStreamCreateTest, UnsolicitedSubProtocol) { "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n"); RunUntilIdle(); EXPECT_FALSE(stream_); - EXPECT_EQ(1006, error()); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Error during WebSocket handshake: " + "Response must not include 'Sec-WebSocket-Protocol' header " + "if not present in request: chatv20.chromium.org", + failure_message()); } // Missing sub-protocol response is rejected. TEST_F(WebSocketStreamCreateTest, UnacceptedSubProtocol) { + std::vector<std::string> sub_protocols; + sub_protocols.push_back("chat.example.com"); CreateAndConnectStandard("ws://localhost/testing_path", "/testing_path", - std::vector<std::string>(1, "chat.example.com"), + sub_protocols, "http://localhost/", "Sec-WebSocket-Protocol: chat.example.com\r\n", ""); RunUntilIdle(); EXPECT_FALSE(stream_); - EXPECT_EQ(1006, error()); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Error during WebSocket handshake: " + "Sent non-empty 'Sec-WebSocket-Protocol' header " + "but no response was received", + failure_message()); } // Only one sub-protocol can be accepted. @@ -233,7 +252,32 @@ TEST_F(WebSocketStreamCreateTest, MultipleSubProtocolsInResponse) { "chatv20.chromium.org\r\n"); RunUntilIdle(); EXPECT_FALSE(stream_); - EXPECT_EQ(1006, error()); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Error during WebSocket handshake: " + "'Sec-WebSocket-Protocol' header must not appear " + "more than once in a response", + failure_message()); +} + +// Unmatched sub-protocol should be rejected. +TEST_F(WebSocketStreamCreateTest, UnmatchedSubProtocolInResponse) { + std::vector<std::string> sub_protocols; + sub_protocols.push_back("chatv11.chromium.org"); + sub_protocols.push_back("chatv20.chromium.org"); + CreateAndConnectStandard("ws://localhost/testing_path", + "/testing_path", + sub_protocols, + "http://google.com/", + "Sec-WebSocket-Protocol: chatv11.chromium.org, " + "chatv20.chromium.org\r\n", + "Sec-WebSocket-Protocol: chatv21.chromium.org\r\n"); + RunUntilIdle(); + EXPECT_FALSE(stream_); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Error during WebSocket handshake: " + "'Sec-WebSocket-Protocol' header value 'chatv21.chromium.org' " + "in response does not match any of sent values", + failure_message()); } // Unknown extension in the response is rejected @@ -246,7 +290,11 @@ TEST_F(WebSocketStreamCreateTest, UnknownExtension) { "Sec-WebSocket-Extensions: x-unknown-extension\r\n"); RunUntilIdle(); EXPECT_FALSE(stream_); - EXPECT_EQ(1006, error()); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Error during WebSocket handshake: " + "Found an unsupported extension 'x-unknown-extension' " + "in 'Sec-WebSocket-Extensions' header", + failure_message()); } // Additional Sec-WebSocket-Accept headers should be rejected. @@ -260,7 +308,11 @@ TEST_F(WebSocketStreamCreateTest, DoubleAccept) { "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"); RunUntilIdle(); EXPECT_FALSE(stream_); - EXPECT_EQ(1006, error()); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Error during WebSocket handshake: " + "'Sec-WebSocket-Accept' header must not appear " + "more than once in a response", + failure_message()); } // Response code 200 must be rejected. @@ -278,7 +330,8 @@ TEST_F(WebSocketStreamCreateTest, InvalidStatusCode) { "", kInvalidStatusCodeResponse); RunUntilIdle(); - EXPECT_EQ(1006, error()); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Unexpected status code: 200", failure_message()); } // Redirects are not followed (according to the WHATWG WebSocket API, which @@ -299,7 +352,8 @@ TEST_F(WebSocketStreamCreateTest, RedirectsRejected) { "", kRedirectResponse); RunUntilIdle(); - EXPECT_EQ(1006, error()); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Unexpected status code: 302", failure_message()); } // Malformed responses should be rejected. HttpStreamParser will accept just @@ -321,7 +375,8 @@ TEST_F(WebSocketStreamCreateTest, MalformedResponse) { "", kMalformedResponse); RunUntilIdle(); - EXPECT_EQ(1006, error()); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Unexpected status code: 200", failure_message()); } // Upgrade header must be present. @@ -338,7 +393,9 @@ TEST_F(WebSocketStreamCreateTest, MissingUpgradeHeader) { "", kMissingUpgradeResponse); RunUntilIdle(); - EXPECT_EQ(1006, error()); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Error during WebSocket handshake: 'Upgrade' header is missing", + failure_message()); } // There must only be one upgrade header. @@ -350,7 +407,31 @@ TEST_F(WebSocketStreamCreateTest, DoubleUpgradeHeader) { "http://localhost/", "", "Upgrade: HTTP/2.0\r\n"); RunUntilIdle(); - EXPECT_EQ(1006, error()); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Error during WebSocket handshake: " + "'Upgrade' header must not appear more than once in a response", + failure_message()); +} + +// There must only be one correct upgrade header. +TEST_F(WebSocketStreamCreateTest, IncorrectUpgradeHeader) { + static const char kMissingUpgradeResponse[] = + "HTTP/1.1 101 Switching Protocols\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" + "Upgrade: hogefuga\r\n" + "\r\n"; + CreateAndConnectCustomResponse("ws://localhost/", + "/", + NoSubProtocols(), + "http://localhost/", + "", + kMissingUpgradeResponse); + RunUntilIdle(); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Error during WebSocket handshake: " + "'Upgrade' header value is not 'WebSocket': hogefuga", + failure_message()); } // Connection header must be present. @@ -367,7 +448,31 @@ TEST_F(WebSocketStreamCreateTest, MissingConnectionHeader) { "", kMissingConnectionResponse); RunUntilIdle(); - EXPECT_EQ(1006, error()); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Error during WebSocket handshake: " + "'Connection' header is missing", + failure_message()); +} + +// Connection header must contain "Upgrade". +TEST_F(WebSocketStreamCreateTest, IncorrectConnectionHeader) { + static const char kMissingConnectionResponse[] = + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" + "Connection: hogefuga\r\n" + "\r\n"; + CreateAndConnectCustomResponse("ws://localhost/", + "/", + NoSubProtocols(), + "http://localhost/", + "", + kMissingConnectionResponse); + RunUntilIdle(); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Error during WebSocket handshake: " + "'Connection' header value must contain 'Upgrade'", + failure_message()); } // Connection header is permitted to contain other tokens. @@ -385,6 +490,7 @@ TEST_F(WebSocketStreamCreateTest, AdditionalTokenInConnectionHeader) { "", kAdditionalConnectionTokenResponse); RunUntilIdle(); + EXPECT_FALSE(has_failed()); EXPECT_TRUE(stream_); } @@ -402,7 +508,10 @@ TEST_F(WebSocketStreamCreateTest, MissingSecWebSocketAccept) { "", kMissingAcceptResponse); RunUntilIdle(); - EXPECT_EQ(1006, error()); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Error during WebSocket handshake: " + "'Sec-WebSocket-Accept' header is missing", + failure_message()); } // Sec-WebSocket-Accept header must match the key that was sent. @@ -420,7 +529,10 @@ TEST_F(WebSocketStreamCreateTest, WrongSecWebSocketAccept) { "", kIncorrectAcceptResponse); RunUntilIdle(); - EXPECT_EQ(1006, error()); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Error during WebSocket handshake: " + "Incorrect 'Sec-WebSocket-Accept' header value", + failure_message()); } // Cancellation works. @@ -429,6 +541,7 @@ TEST_F(WebSocketStreamCreateTest, Cancellation) { "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", ""); stream_request_.reset(); RunUntilIdle(); + EXPECT_FALSE(has_failed()); EXPECT_FALSE(stream_); } @@ -441,7 +554,9 @@ TEST_F(WebSocketStreamCreateTest, ConnectionFailure) { CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), "http://localhost/", socket_data.Pass()); RunUntilIdle(); - EXPECT_EQ(1006, error()); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED", + failure_message()); } // Connect timeout must look just like any other failure. @@ -453,7 +568,9 @@ TEST_F(WebSocketStreamCreateTest, ConnectionTimeout) { CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), "http://localhost/", socket_data.Pass()); RunUntilIdle(); - EXPECT_EQ(1006, error()); + EXPECT_TRUE(has_failed()); + EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_TIMED_OUT", + failure_message()); } // Cancellation during connect works. @@ -467,6 +584,7 @@ TEST_F(WebSocketStreamCreateTest, CancellationDuringConnect) { socket_data.Pass()); stream_request_.reset(); RunUntilIdle(); + EXPECT_FALSE(has_failed()); EXPECT_FALSE(stream_); } @@ -487,6 +605,7 @@ TEST_F(WebSocketStreamCreateTest, CancellationDuringWrite) { socket_data->Run(); stream_request_.reset(); RunUntilIdle(); + EXPECT_FALSE(has_failed()); EXPECT_FALSE(stream_); } @@ -508,6 +627,7 @@ TEST_F(WebSocketStreamCreateTest, CancellationDuringRead) { socket_data->Run(); stream_request_.reset(); RunUntilIdle(); + EXPECT_FALSE(has_failed()); EXPECT_FALSE(stream_); } |