// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/websockets/websocket_stream.h" #include #include #include #include #include "base/compiler_specific.h" #include "base/memory/scoped_vector.h" #include "base/metrics/histogram.h" #include "base/metrics/histogram_samples.h" #include "base/metrics/statistics_recorder.h" #include "base/run_loop.h" #include "base/strings/stringprintf.h" #include "base/timer/mock_timer.h" #include "base/timer/timer.h" #include "net/base/net_errors.h" #include "net/base/test_data_directory.h" #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" #include "net/socket/client_socket_handle.h" #include "net/socket/socket_test_util.h" #include "net/test/cert_test_util.h" #include "net/url_request/url_request_test_util.h" #include "net/websockets/websocket_basic_handshake_stream.h" #include "net/websockets/websocket_frame.h" #include "net/websockets/websocket_handshake_request_info.h" #include "net/websockets/websocket_handshake_response_info.h" #include "net/websockets/websocket_handshake_stream_create_helper.h" #include "net/websockets/websocket_test_util.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" #include "url/origin.h" namespace net { namespace { typedef std::pair HeaderKeyValuePair; std::vector ToVector(const HttpRequestHeaders& headers) { HttpRequestHeaders::Iterator it(headers); std::vector result; while (it.GetNext()) result.push_back(HeaderKeyValuePair(it.name(), it.value())); return result; } std::vector ToVector(const HttpResponseHeaders& headers) { void* iter = NULL; std::string name, value; std::vector result; while (headers.EnumerateHeaderLines(&iter, &name, &value)) result.push_back(HeaderKeyValuePair(name, value)); return result; } // Simple builder for a DeterministicSocketData object to save repetitive code. // It always sets the connect data to MockConnect(SYNCHRONOUS, OK), so it cannot // be used in tests where the connect fails. In practice, those tests never have // any read/write data and so can't benefit from it anyway. The arrays are not // copied. It is up to the caller to ensure they stay in scope until the test // ends. template scoped_ptr BuildSocketData( MockRead (&reads)[reads_count], MockWrite (&writes)[writes_count]) { scoped_ptr socket_data( new DeterministicSocketData(reads, reads_count, writes, writes_count)); socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); socket_data->SetStop(reads_count + writes_count); return socket_data.Pass(); } // Builder for a DeterministicSocketData that expects nothing. This does not // set the connect data, so the calling code must do that explicitly. scoped_ptr BuildNullSocketData() { return make_scoped_ptr(new DeterministicSocketData(NULL, 0, NULL, 0)); } class MockWeakTimer : public base::MockTimer, public base::SupportsWeakPtr { public: MockWeakTimer(bool retain_user_task, bool is_repeating) : MockTimer(retain_user_task, is_repeating) {} }; // A sub-class of WebSocketHandshakeStreamCreateHelper which always sets a // deterministic key to use in the WebSocket handshake. class DeterministicKeyWebSocketHandshakeStreamCreateHelper : public WebSocketHandshakeStreamCreateHelper { public: DeterministicKeyWebSocketHandshakeStreamCreateHelper( WebSocketStream::ConnectDelegate* connect_delegate, const std::vector& requested_subprotocols) : WebSocketHandshakeStreamCreateHelper(connect_delegate, requested_subprotocols) {} void OnStreamCreated(WebSocketBasicHandshakeStream* stream) override { stream->SetWebSocketKeyForTesting("dGhlIHNhbXBsZSBub25jZQ=="); } }; class WebSocketStreamCreateTest : public ::testing::Test { public: WebSocketStreamCreateTest() : has_failed_(false), ssl_fatal_(false) {} void CreateAndConnectCustomResponse( const std::string& socket_url, const std::string& socket_path, const std::vector& sub_protocols, const std::string& origin, const std::string& extra_request_headers, const std::string& response_body, scoped_ptr timer = scoped_ptr()) { url_request_context_host_.SetExpectations( WebSocketStandardRequest(socket_path, origin, extra_request_headers), response_body); CreateAndConnectStream(socket_url, sub_protocols, origin, timer.Pass()); } // |extra_request_headers| and |extra_response_headers| must end in "\r\n" or // errors like "Unable to perform synchronous IO while stopped" will occur. void CreateAndConnectStandard(const std::string& socket_url, const std::string& socket_path, const std::vector& sub_protocols, const std::string& origin, const std::string& extra_request_headers, const std::string& extra_response_headers, scoped_ptr timer = scoped_ptr()) { CreateAndConnectCustomResponse( socket_url, socket_path, sub_protocols, origin, extra_request_headers, WebSocketStandardResponse(extra_response_headers), timer.Pass()); } void CreateAndConnectRawExpectations( const std::string& socket_url, const std::vector& sub_protocols, const std::string& origin, scoped_ptr socket_data, scoped_ptr timer = scoped_ptr()) { AddRawExpectations(socket_data.Pass()); CreateAndConnectStream(socket_url, sub_protocols, origin, timer.Pass()); } // Add additional raw expectations for sockets created before the final one. void AddRawExpectations(scoped_ptr socket_data) { url_request_context_host_.AddRawExpectations(socket_data.Pass()); } // A wrapper for CreateAndConnectStreamForTesting that knows about our default // parameters. void CreateAndConnectStream(const std::string& socket_url, const std::vector& sub_protocols, const std::string& origin, scoped_ptr timer) { for (size_t i = 0; i < ssl_data_.size(); ++i) { scoped_ptr ssl_data(ssl_data_[i]); ssl_data_[i] = NULL; url_request_context_host_.AddSSLSocketDataProvider(ssl_data.Pass()); } ssl_data_.clear(); scoped_ptr connect_delegate( new TestConnectDelegate(this)); WebSocketStream::ConnectDelegate* delegate = connect_delegate.get(); scoped_ptr create_helper( new DeterministicKeyWebSocketHandshakeStreamCreateHelper( delegate, sub_protocols)); stream_request_ = ::net::CreateAndConnectStreamForTesting( GURL(socket_url), create_helper.Pass(), url::Origin(origin), url_request_context_host_.GetURLRequestContext(), BoundNetLog(), connect_delegate.Pass(), timer ? timer.Pass() : scoped_ptr( new base::Timer(false, false))); } static void RunUntilIdle() { base::RunLoop().RunUntilIdle(); } // A simple function to make the tests more readable. Creates an empty vector. static std::vector NoSubProtocols() { return std::vector(); } const std::string& failure_message() const { return failure_message_; } bool has_failed() const { return has_failed_; } class TestConnectDelegate : public WebSocketStream::ConnectDelegate { public: explicit TestConnectDelegate(WebSocketStreamCreateTest* owner) : owner_(owner) {} void OnSuccess(scoped_ptr stream) override { stream.swap(owner_->stream_); } void OnFailure(const std::string& message) override { owner_->has_failed_ = true; owner_->failure_message_ = message; } void OnStartOpeningHandshake( scoped_ptr request) override { // Can be called multiple times (in the case of HTTP auth). Last call // wins. owner_->request_info_ = request.Pass(); } void OnFinishOpeningHandshake( scoped_ptr response) override { if (owner_->response_info_) ADD_FAILURE(); owner_->response_info_ = response.Pass(); } void OnSSLCertificateError( scoped_ptr ssl_error_callbacks, const SSLInfo& ssl_info, bool fatal) override { owner_->ssl_error_callbacks_ = ssl_error_callbacks.Pass(); owner_->ssl_info_ = ssl_info; owner_->ssl_fatal_ = fatal; } private: WebSocketStreamCreateTest* owner_; }; WebSocketTestURLRequestContextHost url_request_context_host_; scoped_ptr stream_request_; // Only set if the connection succeeded. scoped_ptr stream_; // Only set if the connection failed. std::string failure_message_; bool has_failed_; scoped_ptr request_info_; scoped_ptr response_info_; scoped_ptr ssl_error_callbacks_; SSLInfo ssl_info_; bool ssl_fatal_; ScopedVector ssl_data_; }; // There are enough tests of the Sec-WebSocket-Extensions header that they // deserve their own test fixture. class WebSocketStreamCreateExtensionTest : public WebSocketStreamCreateTest { public: // Performs a standard connect, with the value of the Sec-WebSocket-Extensions // header in the response set to |extensions_header_value|. Runs the event // loop to allow the connect to complete. void CreateAndConnectWithExtensions( const std::string& extensions_header_value) { CreateAndConnectStandard( "ws://localhost/testing_path", "/testing_path", NoSubProtocols(), "http://localhost", "", "Sec-WebSocket-Extensions: " + extensions_header_value + "\r\n"); RunUntilIdle(); } }; // Common code to construct expectations for authentication tests that receive // the auth challenge on one connection and then create a second connection to // send the authenticated request on. class CommonAuthTestHelper { public: CommonAuthTestHelper() : reads1_(), writes1_(), reads2_(), writes2_() {} scoped_ptr BuildSocketData1( const std::string& response) { request1_ = WebSocketStandardRequest("/", "http://localhost", ""); writes1_[0] = MockWrite(SYNCHRONOUS, 0, request1_.c_str()); response1_ = response; reads1_[0] = MockRead(SYNCHRONOUS, 1, response1_.c_str()); reads1_[1] = MockRead(SYNCHRONOUS, OK, 2); // Close connection return BuildSocketData(reads1_, writes1_); } scoped_ptr BuildSocketData2( const std::string& request, const std::string& response) { request2_ = request; response2_ = response; writes2_[0] = MockWrite(SYNCHRONOUS, 0, request2_.c_str()); reads2_[0] = MockRead(SYNCHRONOUS, 1, response2_.c_str()); return BuildSocketData(reads2_, writes2_); } private: // These need to be object-scoped since they have to remain valid until all // socket operations in the test are complete. std::string request1_; std::string request2_; std::string response1_; std::string response2_; MockRead reads1_[2]; MockWrite writes1_[1]; MockRead reads2_[1]; MockWrite writes2_[1]; DISALLOW_COPY_AND_ASSIGN(CommonAuthTestHelper); }; // Data and methods for BasicAuth tests. class WebSocketStreamCreateBasicAuthTest : public WebSocketStreamCreateTest { protected: void CreateAndConnectAuthHandshake(const std::string& url, const std::string& base64_user_pass, const std::string& response2) { AddRawExpectations(helper_.BuildSocketData1(kUnauthorizedResponse)); static const char request2format[] = "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Pragma: no-cache\r\n" "Cache-Control: no-cache\r\n" "Authorization: Basic %s\r\n" "Upgrade: websocket\r\n" "Origin: http://localhost\r\n" "Sec-WebSocket-Version: 13\r\n" "User-Agent:\r\n" "Accept-Encoding: gzip, deflate\r\n" "Accept-Language: en-us,fr\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Extensions: permessage-deflate; " "client_max_window_bits\r\n" "\r\n"; const std::string request = base::StringPrintf(request2format, base64_user_pass.c_str()); CreateAndConnectRawExpectations( url, NoSubProtocols(), "http://localhost", helper_.BuildSocketData2(request, response2)); } static const char kUnauthorizedResponse[]; CommonAuthTestHelper helper_; }; class WebSocketStreamCreateDigestAuthTest : public WebSocketStreamCreateTest { protected: static const char kUnauthorizedResponse[]; static const char kAuthorizedRequest[]; CommonAuthTestHelper helper_; }; const char WebSocketStreamCreateBasicAuthTest::kUnauthorizedResponse[] = "HTTP/1.1 401 Unauthorized\r\n" "Content-Length: 0\r\n" "WWW-Authenticate: Basic realm=\"camelot\"\r\n" "\r\n"; // These negotiation values are borrowed from // http_auth_handler_digest_unittest.cc. Feel free to come up with new ones if // you are bored. Only the weakest (no qop) variants of Digest authentication // can be tested by this method, because the others involve random input. const char WebSocketStreamCreateDigestAuthTest::kUnauthorizedResponse[] = "HTTP/1.1 401 Unauthorized\r\n" "Content-Length: 0\r\n" "WWW-Authenticate: Digest realm=\"Oblivion\", nonce=\"nonce-value\"\r\n" "\r\n"; const char WebSocketStreamCreateDigestAuthTest::kAuthorizedRequest[] = "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Pragma: no-cache\r\n" "Cache-Control: no-cache\r\n" "Authorization: Digest username=\"FooBar\", realm=\"Oblivion\", " "nonce=\"nonce-value\", uri=\"/\", " "response=\"f72ff54ebde2f928860f806ec04acd1b\"\r\n" "Upgrade: websocket\r\n" "Origin: http://localhost\r\n" "Sec-WebSocket-Version: 13\r\n" "User-Agent:\r\n" "Accept-Encoding: gzip, deflate\r\n" "Accept-Language: en-us,fr\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Extensions: permessage-deflate; " "client_max_window_bits\r\n" "\r\n"; class WebSocketStreamCreateUMATest : public ::testing::Test { public: // This enum should match with the enum in Delegate in websocket_stream.cc. enum HandshakeResult { INCOMPLETE, CONNECTED, FAILED, NUM_HANDSHAKE_RESULT_TYPES, }; class StreamCreation : public WebSocketStreamCreateTest { void TestBody() override {} }; scoped_ptr GetSamples(const std::string& name) { base::HistogramBase* histogram = base::StatisticsRecorder::FindHistogram(name); return histogram ? histogram->SnapshotSamples() : scoped_ptr(); } }; // Confirm that the basic case works as expected. TEST_F(WebSocketStreamCreateTest, SimpleSuccess) { CreateAndConnectStandard( "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", ""); EXPECT_FALSE(request_info_); EXPECT_FALSE(response_info_); RunUntilIdle(); EXPECT_FALSE(has_failed()); EXPECT_TRUE(stream_); EXPECT_TRUE(request_info_); EXPECT_TRUE(response_info_); } TEST_F(WebSocketStreamCreateTest, HandshakeInfo) { static const char kResponse[] = "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" "foo: bar, baz\r\n" "hoge: fuga\r\n" "hoge: piyo\r\n" "\r\n"; CreateAndConnectCustomResponse( "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", kResponse); EXPECT_FALSE(request_info_); EXPECT_FALSE(response_info_); RunUntilIdle(); EXPECT_TRUE(stream_); ASSERT_TRUE(request_info_); ASSERT_TRUE(response_info_); std::vector request_headers = ToVector(request_info_->headers); // We examine the contents of request_info_ and response_info_ // mainly only in this test case. EXPECT_EQ(GURL("ws://localhost/"), request_info_->url); EXPECT_EQ(GURL("ws://localhost/"), response_info_->url); EXPECT_EQ(101, response_info_->status_code); EXPECT_EQ("Switching Protocols", response_info_->status_text); ASSERT_EQ(12u, request_headers.size()); EXPECT_EQ(HeaderKeyValuePair("Host", "localhost"), request_headers[0]); EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), request_headers[1]); EXPECT_EQ(HeaderKeyValuePair("Pragma", "no-cache"), request_headers[2]); EXPECT_EQ(HeaderKeyValuePair("Cache-Control", "no-cache"), request_headers[3]); EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), request_headers[4]); EXPECT_EQ(HeaderKeyValuePair("Origin", "http://localhost"), request_headers[5]); EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Version", "13"), request_headers[6]); EXPECT_EQ(HeaderKeyValuePair("User-Agent", ""), request_headers[7]); EXPECT_EQ(HeaderKeyValuePair("Accept-Encoding", "gzip, deflate"), request_headers[8]); EXPECT_EQ(HeaderKeyValuePair("Accept-Language", "en-us,fr"), request_headers[9]); EXPECT_EQ("Sec-WebSocket-Key", request_headers[10].first); EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Extensions", "permessage-deflate; client_max_window_bits"), request_headers[11]); std::vector response_headers = ToVector(*response_info_->headers.get()); ASSERT_EQ(6u, response_headers.size()); // Sort the headers for ease of verification. std::sort(response_headers.begin(), response_headers.end()); EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), response_headers[0]); EXPECT_EQ("Sec-WebSocket-Accept", response_headers[1].first); EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), response_headers[2]); EXPECT_EQ(HeaderKeyValuePair("foo", "bar, baz"), response_headers[3]); EXPECT_EQ(HeaderKeyValuePair("hoge", "fuga"), response_headers[4]); EXPECT_EQ(HeaderKeyValuePair("hoge", "piyo"), response_headers[5]); } // Confirm that the stream isn't established until the message loop runs. TEST_F(WebSocketStreamCreateTest, NeedsToRunLoop) { CreateAndConnectStandard( "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", ""); EXPECT_FALSE(has_failed()); EXPECT_FALSE(stream_); } // Check the path is used. TEST_F(WebSocketStreamCreateTest, PathIsUsed) { CreateAndConnectStandard("ws://localhost/testing_path", "/testing_path", NoSubProtocols(), "http://localhost", "", ""); RunUntilIdle(); EXPECT_FALSE(has_failed()); EXPECT_TRUE(stream_); } // Check that the origin is used. TEST_F(WebSocketStreamCreateTest, OriginIsUsed) { CreateAndConnectStandard("ws://localhost/testing_path", "/testing_path", NoSubProtocols(), "http://google.com", "", ""); RunUntilIdle(); EXPECT_FALSE(has_failed()); EXPECT_TRUE(stream_); } // Check that sub-protocols are sent and parsed. TEST_F(WebSocketStreamCreateTest, SubProtocolIsUsed) { std::vector 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: chatv20.chromium.org\r\n"); RunUntilIdle(); EXPECT_TRUE(stream_); EXPECT_FALSE(has_failed()); EXPECT_EQ("chatv20.chromium.org", stream_->GetSubProtocol()); } // Unsolicited sub-protocols are rejected. TEST_F(WebSocketStreamCreateTest, UnsolicitedSubProtocol) { CreateAndConnectStandard("ws://localhost/testing_path", "/testing_path", NoSubProtocols(), "http://google.com", "", "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n"); RunUntilIdle(); EXPECT_FALSE(stream_); 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 sub_protocols; sub_protocols.push_back("chat.example.com"); CreateAndConnectStandard("ws://localhost/testing_path", "/testing_path", sub_protocols, "http://localhost", "Sec-WebSocket-Protocol: chat.example.com\r\n", ""); RunUntilIdle(); EXPECT_FALSE(stream_); 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. TEST_F(WebSocketStreamCreateTest, MultipleSubProtocolsInResponse) { std::vector 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: chatv11.chromium.org, " "chatv20.chromium.org\r\n"); RunUntilIdle(); EXPECT_FALSE(stream_); 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 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()); } // permessage-deflate extension basic success case. TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateSuccess) { CreateAndConnectWithExtensions("permessage-deflate"); EXPECT_TRUE(stream_); EXPECT_FALSE(has_failed()); } // permessage-deflate extensions success with all parameters. TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateParamsSuccess) { CreateAndConnectWithExtensions( "permessage-deflate; client_no_context_takeover; " "server_max_window_bits=11; client_max_window_bits=13; " "server_no_context_takeover"); EXPECT_TRUE(stream_); EXPECT_FALSE(has_failed()); } // Verify that incoming messages are actually decompressed with // permessage-deflate enabled. TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateInflates) { CreateAndConnectCustomResponse( "ws://localhost/testing_path", "/testing_path", NoSubProtocols(), "http://localhost", "", WebSocketStandardResponse( "Sec-WebSocket-Extensions: permessage-deflate\r\n") + std::string( "\xc1\x07" // WebSocket header (FIN + RSV1, Text payload 7 bytes) "\xf2\x48\xcd\xc9\xc9\x07\x00", // "Hello" DEFLATE compressed 9)); RunUntilIdle(); ASSERT_TRUE(stream_); ScopedVector frames; CompletionCallback callback; ASSERT_EQ(OK, stream_->ReadFrames(&frames, callback)); ASSERT_EQ(1U, frames.size()); ASSERT_EQ(5U, frames[0]->header.payload_length); EXPECT_EQ("Hello", std::string(frames[0]->data->data(), 5)); } // Unknown extension in the response is rejected TEST_F(WebSocketStreamCreateExtensionTest, UnknownExtension) { CreateAndConnectWithExtensions("x-unknown-extension"); EXPECT_FALSE(stream_); EXPECT_TRUE(has_failed()); EXPECT_EQ("Error during WebSocket handshake: " "Found an unsupported extension 'x-unknown-extension' " "in 'Sec-WebSocket-Extensions' header", failure_message()); } // Malformed extensions are rejected (this file does not cover all possible // parse failures, as the parser is covered thoroughly by its own unit tests). TEST_F(WebSocketStreamCreateExtensionTest, MalformedExtension) { CreateAndConnectWithExtensions(";"); EXPECT_FALSE(stream_); EXPECT_TRUE(has_failed()); EXPECT_EQ( "Error during WebSocket handshake: 'Sec-WebSocket-Extensions' header " "value is rejected by the parser: ;", failure_message()); } // The permessage-deflate extension may only be specified once. TEST_F(WebSocketStreamCreateExtensionTest, OnlyOnePerMessageDeflateAllowed) { CreateAndConnectWithExtensions( "permessage-deflate, permessage-deflate; client_max_window_bits=10"); EXPECT_FALSE(stream_); EXPECT_TRUE(has_failed()); EXPECT_EQ( "Error during WebSocket handshake: " "Received duplicate permessage-deflate response", failure_message()); } // permessage-deflate parameters may not be duplicated. TEST_F(WebSocketStreamCreateExtensionTest, NoDuplicateParameters) { CreateAndConnectWithExtensions( "permessage-deflate; client_no_context_takeover; " "client_no_context_takeover"); EXPECT_FALSE(stream_); EXPECT_TRUE(has_failed()); EXPECT_EQ( "Error during WebSocket handshake: Error in permessage-deflate: " "Received duplicate permessage-deflate extension parameter " "client_no_context_takeover", failure_message()); } // permessage-deflate parameters must start with "client_" or "server_" TEST_F(WebSocketStreamCreateExtensionTest, BadParameterPrefix) { CreateAndConnectWithExtensions( "permessage-deflate; absurd_no_context_takeover"); EXPECT_FALSE(stream_); EXPECT_TRUE(has_failed()); EXPECT_EQ( "Error during WebSocket handshake: Error in permessage-deflate: " "Received an unexpected permessage-deflate extension parameter", failure_message()); } // permessage-deflate parameters must be either *_no_context_takeover or // *_max_window_bits TEST_F(WebSocketStreamCreateExtensionTest, BadParameterSuffix) { CreateAndConnectWithExtensions( "permessage-deflate; client_max_content_bits=5"); EXPECT_FALSE(stream_); EXPECT_TRUE(has_failed()); EXPECT_EQ( "Error during WebSocket handshake: Error in permessage-deflate: " "Received an unexpected permessage-deflate extension parameter", failure_message()); } // *_no_context_takeover parameters must not have an argument TEST_F(WebSocketStreamCreateExtensionTest, BadParameterValue) { CreateAndConnectWithExtensions( "permessage-deflate; client_no_context_takeover=true"); EXPECT_FALSE(stream_); EXPECT_TRUE(has_failed()); EXPECT_EQ( "Error during WebSocket handshake: Error in permessage-deflate: " "Received invalid client_no_context_takeover parameter", failure_message()); } // *_max_window_bits must have an argument TEST_F(WebSocketStreamCreateExtensionTest, NoMaxWindowBitsArgument) { CreateAndConnectWithExtensions("permessage-deflate; client_max_window_bits"); EXPECT_FALSE(stream_); EXPECT_TRUE(has_failed()); EXPECT_EQ( "Error during WebSocket handshake: Error in permessage-deflate: " "client_max_window_bits must have value", failure_message()); } // *_max_window_bits must be an integer TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueInteger) { CreateAndConnectWithExtensions( "permessage-deflate; server_max_window_bits=banana"); EXPECT_FALSE(stream_); EXPECT_TRUE(has_failed()); EXPECT_EQ( "Error during WebSocket handshake: Error in permessage-deflate: " "Received invalid server_max_window_bits parameter", failure_message()); } // *_max_window_bits must be >= 8 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueTooSmall) { CreateAndConnectWithExtensions( "permessage-deflate; server_max_window_bits=7"); EXPECT_FALSE(stream_); EXPECT_TRUE(has_failed()); EXPECT_EQ( "Error during WebSocket handshake: Error in permessage-deflate: " "Received invalid server_max_window_bits parameter", failure_message()); } // *_max_window_bits must be <= 15 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueTooBig) { CreateAndConnectWithExtensions( "permessage-deflate; client_max_window_bits=16"); EXPECT_FALSE(stream_); EXPECT_TRUE(has_failed()); EXPECT_EQ( "Error during WebSocket handshake: Error in permessage-deflate: " "Received invalid client_max_window_bits parameter", failure_message()); } // *_max_window_bits must not start with 0 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueStartsWithZero) { CreateAndConnectWithExtensions( "permessage-deflate; client_max_window_bits=08"); EXPECT_FALSE(stream_); EXPECT_TRUE(has_failed()); EXPECT_EQ( "Error during WebSocket handshake: Error in permessage-deflate: " "Received invalid client_max_window_bits parameter", failure_message()); } // *_max_window_bits must not start with + TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueStartsWithPlus) { CreateAndConnectWithExtensions( "permessage-deflate; server_max_window_bits=+9"); EXPECT_FALSE(stream_); EXPECT_TRUE(has_failed()); EXPECT_EQ( "Error during WebSocket handshake: Error in permessage-deflate: " "Received invalid server_max_window_bits parameter", failure_message()); } // TODO(ricea): Check that WebSocketDeflateStream is initialised with the // arguments from the server. This is difficult because the data written to the // socket is randomly masked. // Additional Sec-WebSocket-Accept headers should be rejected. TEST_F(WebSocketStreamCreateTest, DoubleAccept) { CreateAndConnectStandard( "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"); RunUntilIdle(); EXPECT_FALSE(stream_); 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. TEST_F(WebSocketStreamCreateTest, InvalidStatusCode) { static const char kInvalidStatusCodeResponse[] = "HTTP/1.1 200 OK\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" "\r\n"; CreateAndConnectCustomResponse("ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", kInvalidStatusCodeResponse); RunUntilIdle(); EXPECT_TRUE(has_failed()); EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 200", failure_message()); } // Redirects are not followed (according to the WHATWG WebSocket API, which // overrides RFC6455 for browser applications). TEST_F(WebSocketStreamCreateTest, RedirectsRejected) { static const char kRedirectResponse[] = "HTTP/1.1 302 Moved Temporarily\r\n" "Content-Type: text/html\r\n" "Content-Length: 34\r\n" "Connection: keep-alive\r\n" "Location: ws://localhost/other\r\n" "\r\n" "Moved

Moved

"; CreateAndConnectCustomResponse("ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", kRedirectResponse); RunUntilIdle(); EXPECT_TRUE(has_failed()); EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 302", failure_message()); } // Malformed responses should be rejected. HttpStreamParser will accept just // about any garbage in the middle of the headers. To make it give up, the junk // has to be at the start of the response. Even then, it just gets treated as an // HTTP/0.9 response. TEST_F(WebSocketStreamCreateTest, MalformedResponse) { static const char kMalformedResponse[] = "220 mx.google.com ESMTP\r\n" "HTTP/1.1 101 OK\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" "\r\n"; CreateAndConnectCustomResponse("ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", kMalformedResponse); RunUntilIdle(); EXPECT_TRUE(has_failed()); EXPECT_EQ("Error during WebSocket handshake: Invalid status line", failure_message()); } // Upgrade header must be present. TEST_F(WebSocketStreamCreateTest, MissingUpgradeHeader) { static const char kMissingUpgradeResponse[] = "HTTP/1.1 101 Switching Protocols\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" "\r\n"; CreateAndConnectCustomResponse("ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", kMissingUpgradeResponse); RunUntilIdle(); EXPECT_TRUE(has_failed()); EXPECT_EQ("Error during WebSocket handshake: 'Upgrade' header is missing", failure_message()); } // There must only be one upgrade header. TEST_F(WebSocketStreamCreateTest, DoubleUpgradeHeader) { CreateAndConnectStandard( "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "Upgrade: HTTP/2.0\r\n"); RunUntilIdle(); 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. TEST_F(WebSocketStreamCreateTest, MissingConnectionHeader) { static const char kMissingConnectionResponse[] = "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" "\r\n"; CreateAndConnectCustomResponse("ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", kMissingConnectionResponse); RunUntilIdle(); 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. TEST_F(WebSocketStreamCreateTest, AdditionalTokenInConnectionHeader) { static const char kAdditionalConnectionTokenResponse[] = "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade, Keep-Alive\r\n" "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" "\r\n"; CreateAndConnectCustomResponse("ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", kAdditionalConnectionTokenResponse); RunUntilIdle(); EXPECT_FALSE(has_failed()); EXPECT_TRUE(stream_); } // Sec-WebSocket-Accept header must be present. TEST_F(WebSocketStreamCreateTest, MissingSecWebSocketAccept) { static const char kMissingAcceptResponse[] = "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "\r\n"; CreateAndConnectCustomResponse("ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", kMissingAcceptResponse); RunUntilIdle(); 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. TEST_F(WebSocketStreamCreateTest, WrongSecWebSocketAccept) { static const char kIncorrectAcceptResponse[] = "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Accept: x/byyPZ2tOFvJCGkkugcKvqhhPk=\r\n" "\r\n"; CreateAndConnectCustomResponse("ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", kIncorrectAcceptResponse); RunUntilIdle(); EXPECT_TRUE(has_failed()); EXPECT_EQ("Error during WebSocket handshake: " "Incorrect 'Sec-WebSocket-Accept' header value", failure_message()); } // Cancellation works. TEST_F(WebSocketStreamCreateTest, Cancellation) { CreateAndConnectStandard( "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", ""); stream_request_.reset(); RunUntilIdle(); EXPECT_FALSE(has_failed()); EXPECT_FALSE(stream_); EXPECT_FALSE(request_info_); EXPECT_FALSE(response_info_); } // Connect failure must look just like negotiation failure. TEST_F(WebSocketStreamCreateTest, ConnectionFailure) { scoped_ptr socket_data(BuildNullSocketData()); socket_data->set_connect_data( MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED)); CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), "http://localhost", socket_data.Pass()); RunUntilIdle(); EXPECT_TRUE(has_failed()); EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED", failure_message()); EXPECT_FALSE(request_info_); EXPECT_FALSE(response_info_); } // Connect timeout must look just like any other failure. TEST_F(WebSocketStreamCreateTest, ConnectionTimeout) { scoped_ptr socket_data(BuildNullSocketData()); socket_data->set_connect_data( MockConnect(ASYNC, ERR_CONNECTION_TIMED_OUT)); CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), "http://localhost", socket_data.Pass()); RunUntilIdle(); EXPECT_TRUE(has_failed()); EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_TIMED_OUT", failure_message()); } // The server doesn't respond to the opening handshake. TEST_F(WebSocketStreamCreateTest, HandshakeTimeout) { scoped_ptr socket_data(BuildNullSocketData()); socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING)); scoped_ptr timer(new MockWeakTimer(false, false)); base::WeakPtr weak_timer = timer->AsWeakPtr(); CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), "http://localhost", socket_data.Pass(), timer.Pass()); EXPECT_FALSE(has_failed()); ASSERT_TRUE(weak_timer.get()); EXPECT_TRUE(weak_timer->IsRunning()); weak_timer->Fire(); RunUntilIdle(); EXPECT_TRUE(has_failed()); EXPECT_EQ("WebSocket opening handshake timed out", failure_message()); ASSERT_TRUE(weak_timer.get()); EXPECT_FALSE(weak_timer->IsRunning()); } // When the connection establishes the timer should be stopped. TEST_F(WebSocketStreamCreateTest, HandshakeTimerOnSuccess) { scoped_ptr timer(new MockWeakTimer(false, false)); base::WeakPtr weak_timer = timer->AsWeakPtr(); CreateAndConnectStandard("ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "", timer.Pass()); ASSERT_TRUE(weak_timer); EXPECT_TRUE(weak_timer->IsRunning()); RunUntilIdle(); EXPECT_FALSE(has_failed()); EXPECT_TRUE(stream_); ASSERT_TRUE(weak_timer); EXPECT_FALSE(weak_timer->IsRunning()); } // When the connection fails the timer should be stopped. TEST_F(WebSocketStreamCreateTest, HandshakeTimerOnFailure) { scoped_ptr socket_data(BuildNullSocketData()); socket_data->set_connect_data( MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED)); scoped_ptr timer(new MockWeakTimer(false, false)); base::WeakPtr weak_timer = timer->AsWeakPtr(); CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), "http://localhost", socket_data.Pass(), timer.Pass()); ASSERT_TRUE(weak_timer.get()); EXPECT_TRUE(weak_timer->IsRunning()); RunUntilIdle(); EXPECT_TRUE(has_failed()); EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED", failure_message()); ASSERT_TRUE(weak_timer.get()); EXPECT_FALSE(weak_timer->IsRunning()); } // Cancellation during connect works. TEST_F(WebSocketStreamCreateTest, CancellationDuringConnect) { scoped_ptr socket_data(BuildNullSocketData()); socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING)); CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), "http://localhost", socket_data.Pass()); stream_request_.reset(); RunUntilIdle(); EXPECT_FALSE(has_failed()); EXPECT_FALSE(stream_); } // Cancellation during write of the request headers works. TEST_F(WebSocketStreamCreateTest, CancellationDuringWrite) { // We seem to need at least two operations in order to use SetStop(). MockWrite writes[] = {MockWrite(ASYNC, 0, "GET / HTTP/"), MockWrite(ASYNC, 1, "1.1\r\n")}; // We keep a copy of the pointer so that we can call RunFor() on it later. DeterministicSocketData* socket_data( new DeterministicSocketData(NULL, 0, writes, arraysize(writes))); socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); socket_data->SetStop(1); CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), "http://localhost", make_scoped_ptr(socket_data)); socket_data->Run(); stream_request_.reset(); RunUntilIdle(); EXPECT_FALSE(has_failed()); EXPECT_FALSE(stream_); EXPECT_TRUE(request_info_); EXPECT_FALSE(response_info_); } // Cancellation during read of the response headers works. TEST_F(WebSocketStreamCreateTest, CancellationDuringRead) { std::string request = WebSocketStandardRequest("/", "http://localhost", ""); MockWrite writes[] = {MockWrite(ASYNC, 0, request.c_str())}; MockRead reads[] = { MockRead(ASYNC, 1, "HTTP/1.1 101 Switching Protocols\r\nUpgr"), }; scoped_ptr socket_data( BuildSocketData(reads, writes)); socket_data->SetStop(1); DeterministicSocketData* socket_data_raw_ptr = socket_data.get(); CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), "http://localhost", socket_data.Pass()); socket_data_raw_ptr->Run(); stream_request_.reset(); RunUntilIdle(); EXPECT_FALSE(has_failed()); EXPECT_FALSE(stream_); EXPECT_TRUE(request_info_); EXPECT_FALSE(response_info_); } // Over-size response headers (> 256KB) should not cause a crash. This is a // regression test for crbug.com/339456. It is based on the layout test // "cookie-flood.html". TEST_F(WebSocketStreamCreateTest, VeryLargeResponseHeaders) { std::string set_cookie_headers; set_cookie_headers.reserve(45 * 10000); for (int i = 0; i < 10000; ++i) { set_cookie_headers += base::StringPrintf("Set-Cookie: WK-websocket-test-flood-%d=1\r\n", i); } CreateAndConnectStandard("ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", set_cookie_headers); RunUntilIdle(); EXPECT_TRUE(has_failed()); EXPECT_FALSE(response_info_); } // If the remote host closes the connection without sending headers, we should // log the console message "Connection closed before receiving a handshake // response". TEST_F(WebSocketStreamCreateTest, NoResponse) { std::string request = WebSocketStandardRequest("/", "http://localhost", ""); MockWrite writes[] = {MockWrite(ASYNC, request.data(), request.size(), 0)}; MockRead reads[] = {MockRead(ASYNC, 0, 1)}; scoped_ptr socket_data( BuildSocketData(reads, writes)); DeterministicSocketData* socket_data_raw_ptr = socket_data.get(); CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), "http://localhost", socket_data.Pass()); socket_data_raw_ptr->RunFor(2); EXPECT_TRUE(has_failed()); EXPECT_FALSE(stream_); EXPECT_FALSE(response_info_); EXPECT_EQ("Connection closed before receiving a handshake response", failure_message()); } TEST_F(WebSocketStreamCreateTest, SelfSignedCertificateFailure) { ssl_data_.push_back( new SSLSocketDataProvider(ASYNC, ERR_CERT_AUTHORITY_INVALID)); ssl_data_[0]->cert = ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der"); ASSERT_TRUE(ssl_data_[0]->cert.get()); scoped_ptr raw_socket_data(BuildNullSocketData()); CreateAndConnectRawExpectations("wss://localhost/", NoSubProtocols(), "http://localhost", raw_socket_data.Pass()); RunUntilIdle(); EXPECT_FALSE(has_failed()); ASSERT_TRUE(ssl_error_callbacks_); ssl_error_callbacks_->CancelSSLRequest(ERR_CERT_AUTHORITY_INVALID, &ssl_info_); RunUntilIdle(); EXPECT_TRUE(has_failed()); } TEST_F(WebSocketStreamCreateTest, SelfSignedCertificateSuccess) { scoped_ptr ssl_data( new SSLSocketDataProvider(ASYNC, ERR_CERT_AUTHORITY_INVALID)); ssl_data->cert = ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der"); ASSERT_TRUE(ssl_data->cert.get()); ssl_data_.push_back(ssl_data.release()); ssl_data.reset(new SSLSocketDataProvider(ASYNC, OK)); ssl_data_.push_back(ssl_data.release()); url_request_context_host_.AddRawExpectations(BuildNullSocketData()); CreateAndConnectStandard( "wss://localhost/", "/", NoSubProtocols(), "http://localhost", "", ""); RunUntilIdle(); ASSERT_TRUE(ssl_error_callbacks_); ssl_error_callbacks_->ContinueSSLRequest(); RunUntilIdle(); EXPECT_FALSE(has_failed()); EXPECT_TRUE(stream_); } // If the server requests authorisation, but we have no credentials, the // connection should fail cleanly. TEST_F(WebSocketStreamCreateBasicAuthTest, FailureNoCredentials) { CreateAndConnectCustomResponse("ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", kUnauthorizedResponse); RunUntilIdle(); EXPECT_TRUE(has_failed()); EXPECT_EQ("HTTP Authentication failed; no valid credentials available", failure_message()); EXPECT_TRUE(response_info_); } TEST_F(WebSocketStreamCreateBasicAuthTest, SuccessPasswordInUrl) { CreateAndConnectAuthHandshake("ws://foo:bar@localhost/", "Zm9vOmJhcg==", WebSocketStandardResponse(std::string())); RunUntilIdle(); EXPECT_FALSE(has_failed()); EXPECT_TRUE(stream_); ASSERT_TRUE(response_info_); EXPECT_EQ(101, response_info_->status_code); } TEST_F(WebSocketStreamCreateBasicAuthTest, FailureIncorrectPasswordInUrl) { CreateAndConnectAuthHandshake( "ws://foo:baz@localhost/", "Zm9vOmJheg==", kUnauthorizedResponse); RunUntilIdle(); EXPECT_TRUE(has_failed()); EXPECT_TRUE(response_info_); } // Digest auth has the same connection semantics as Basic auth, so we can // generally assume that whatever works for Basic auth will also work for // Digest. There's just one test here, to confirm that it works at all. TEST_F(WebSocketStreamCreateDigestAuthTest, DigestPasswordInUrl) { AddRawExpectations(helper_.BuildSocketData1(kUnauthorizedResponse)); CreateAndConnectRawExpectations( "ws://FooBar:pass@localhost/", NoSubProtocols(), "http://localhost", helper_.BuildSocketData2(kAuthorizedRequest, WebSocketStandardResponse(std::string()))); RunUntilIdle(); EXPECT_FALSE(has_failed()); EXPECT_TRUE(stream_); ASSERT_TRUE(response_info_); EXPECT_EQ(101, response_info_->status_code); } TEST_F(WebSocketStreamCreateUMATest, Incomplete) { const std::string name("Net.WebSocket.HandshakeResult"); scoped_ptr original(GetSamples(name)); { StreamCreation creation; creation.CreateAndConnectStandard("ws://localhost/", "/", creation.NoSubProtocols(), "http://localhost", "", ""); } scoped_ptr samples(GetSamples(name)); ASSERT_TRUE(samples); if (original) { samples->Subtract(*original); // Cancel the original values. } EXPECT_EQ(1, samples->GetCount(INCOMPLETE)); EXPECT_EQ(0, samples->GetCount(CONNECTED)); EXPECT_EQ(0, samples->GetCount(FAILED)); } TEST_F(WebSocketStreamCreateUMATest, Connected) { const std::string name("Net.WebSocket.HandshakeResult"); scoped_ptr original(GetSamples(name)); { StreamCreation creation; creation.CreateAndConnectStandard("ws://localhost/", "/", creation.NoSubProtocols(), "http://localhost", "", ""); creation.RunUntilIdle(); } scoped_ptr samples(GetSamples(name)); ASSERT_TRUE(samples); if (original) { samples->Subtract(*original); // Cancel the original values. } EXPECT_EQ(0, samples->GetCount(INCOMPLETE)); EXPECT_EQ(1, samples->GetCount(CONNECTED)); EXPECT_EQ(0, samples->GetCount(FAILED)); } TEST_F(WebSocketStreamCreateUMATest, Failed) { const std::string name("Net.WebSocket.HandshakeResult"); scoped_ptr original(GetSamples(name)); { StreamCreation creation; static const char kInvalidStatusCodeResponse[] = "HTTP/1.1 200 OK\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" "\r\n"; creation.CreateAndConnectCustomResponse("ws://localhost/", "/", creation.NoSubProtocols(), "http://localhost", "", kInvalidStatusCodeResponse); creation.RunUntilIdle(); } scoped_ptr samples(GetSamples(name)); ASSERT_TRUE(samples); if (original) { samples->Subtract(*original); // Cancel the original values. } EXPECT_EQ(1, samples->GetCount(INCOMPLETE)); EXPECT_EQ(0, samples->GetCount(CONNECTED)); EXPECT_EQ(0, samples->GetCount(FAILED)); } } // namespace } // namespace net