Index: net/websockets/websocket_stream_test.cc |
diff --git a/net/websockets/websocket_stream_test.cc b/net/websockets/websocket_stream_test.cc |
index b7229f93188a43543b4871d98f5f418de70503ff..653e367b36ac96a074e479fcf74314c2e044b6f8 100644 |
--- a/net/websockets/websocket_stream_test.cc |
+++ b/net/websockets/websocket_stream_test.cc |
@@ -56,6 +56,29 @@ std::vector<HeaderKeyValuePair> ToVector(const HttpResponseHeaders& headers) { |
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 <size_t reads_count, size_t writes_count> |
+scoped_ptr<DeterministicSocketData> BuildSocketData( |
+ MockRead (&reads)[reads_count], |
+ MockWrite (&writes)[writes_count]) { |
+ scoped_ptr<DeterministicSocketData> 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<DeterministicSocketData> BuildNullSocketData() { |
+ return make_scoped_ptr(new DeterministicSocketData(NULL, 0, NULL, 0)); |
+} |
+ |
// A sub-class of WebSocketHandshakeStreamCreateHelper which always sets a |
// deterministic key to use in the WebSocket handshake. |
class DeterministicKeyWebSocketHandshakeStreamCreateHelper |
@@ -111,10 +134,15 @@ class WebSocketStreamCreateTest : public ::testing::Test { |
const std::vector<std::string>& sub_protocols, |
const std::string& origin, |
scoped_ptr<DeterministicSocketData> socket_data) { |
- url_request_context_host_.AddRawExpectations(socket_data.Pass()); |
+ AddRawExpectations(socket_data.Pass()); |
CreateAndConnectStream(socket_url, sub_protocols, origin); |
} |
+ // Add additional raw expectations for sockets created before the final one. |
+ void AddRawExpectations(scoped_ptr<DeterministicSocketData> 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, |
@@ -167,8 +195,8 @@ class WebSocketStreamCreateTest : public ::testing::Test { |
virtual void OnStartOpeningHandshake( |
scoped_ptr<WebSocketHandshakeRequestInfo> request) OVERRIDE { |
- if (owner_->request_info_) |
- ADD_FAILURE(); |
+ // Can be called multiple times (in the case of HTTP auth). Last call |
+ // wins. |
owner_->request_info_ = request.Pass(); |
} |
virtual void OnFinishOpeningHandshake( |
@@ -226,6 +254,132 @@ class WebSocketStreamCreateExtensionTest : public WebSocketStreamCreateTest { |
} |
}; |
+// 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<DeterministicSocketData> 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<DeterministicSocketData> 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. |
@@ -918,8 +1072,7 @@ TEST_F(WebSocketStreamCreateTest, Cancellation) { |
// Connect failure must look just like negotiation failure. |
TEST_F(WebSocketStreamCreateTest, ConnectionFailure) { |
- scoped_ptr<DeterministicSocketData> socket_data( |
- new DeterministicSocketData(NULL, 0, NULL, 0)); |
+ scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData()); |
socket_data->set_connect_data( |
MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED)); |
CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), |
@@ -934,8 +1087,7 @@ TEST_F(WebSocketStreamCreateTest, ConnectionFailure) { |
// Connect timeout must look just like any other failure. |
TEST_F(WebSocketStreamCreateTest, ConnectionTimeout) { |
- scoped_ptr<DeterministicSocketData> socket_data( |
- new DeterministicSocketData(NULL, 0, NULL, 0)); |
+ scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData()); |
socket_data->set_connect_data( |
MockConnect(ASYNC, ERR_CONNECTION_TIMED_OUT)); |
CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), |
@@ -948,8 +1100,7 @@ TEST_F(WebSocketStreamCreateTest, ConnectionTimeout) { |
// Cancellation during connect works. |
TEST_F(WebSocketStreamCreateTest, CancellationDuringConnect) { |
- scoped_ptr<DeterministicSocketData> socket_data( |
- new DeterministicSocketData(NULL, 0, NULL, 0)); |
+ scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData()); |
socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING)); |
CreateAndConnectRawExpectations("ws://localhost/", |
NoSubProtocols(), |
@@ -991,15 +1142,15 @@ TEST_F(WebSocketStreamCreateTest, CancellationDuringRead) { |
MockRead reads[] = { |
MockRead(ASYNC, 1, "HTTP/1.1 101 Switching Protocols\r\nUpgr"), |
}; |
- DeterministicSocketData* socket_data(new DeterministicSocketData( |
- reads, arraysize(reads), writes, arraysize(writes))); |
- socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
+ scoped_ptr<DeterministicSocketData> socket_data( |
+ BuildSocketData(reads, writes)); |
socket_data->SetStop(1); |
+ DeterministicSocketData* socket_data_raw_ptr = socket_data.get(); |
CreateAndConnectRawExpectations("ws://localhost/", |
NoSubProtocols(), |
"http://localhost", |
- make_scoped_ptr(socket_data)); |
- socket_data->Run(); |
+ socket_data.Pass()); |
+ socket_data_raw_ptr->Run(); |
stream_request_.reset(); |
RunUntilIdle(); |
EXPECT_FALSE(has_failed()); |
@@ -1032,14 +1183,14 @@ 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)}; |
- DeterministicSocketData* socket_data(new DeterministicSocketData( |
- reads, arraysize(reads), writes, arraysize(writes))); |
- socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
+ scoped_ptr<DeterministicSocketData> socket_data( |
+ BuildSocketData(reads, writes)); |
+ DeterministicSocketData* socket_data_raw_ptr = socket_data.get(); |
CreateAndConnectRawExpectations("ws://localhost/", |
NoSubProtocols(), |
"http://localhost", |
- make_scoped_ptr(socket_data)); |
- socket_data->RunFor(2); |
+ socket_data.Pass()); |
+ socket_data_raw_ptr->RunFor(2); |
EXPECT_TRUE(has_failed()); |
EXPECT_FALSE(stream_); |
EXPECT_FALSE(response_info_); |
@@ -1053,8 +1204,7 @@ TEST_F(WebSocketStreamCreateTest, SelfSignedCertificateFailure) { |
ssl_data_[0]->cert = |
ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der"); |
ASSERT_TRUE(ssl_data_[0]->cert); |
- scoped_ptr<DeterministicSocketData> raw_socket_data( |
- new DeterministicSocketData(NULL, 0, NULL, 0)); |
+ scoped_ptr<DeterministicSocketData> raw_socket_data(BuildNullSocketData()); |
CreateAndConnectRawExpectations("wss://localhost/", |
NoSubProtocols(), |
"http://localhost", |
@@ -1077,8 +1227,7 @@ TEST_F(WebSocketStreamCreateTest, SelfSignedCertificateSuccess) { |
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( |
- make_scoped_ptr(new DeterministicSocketData(NULL, 0, NULL, 0))); |
+ url_request_context_host_.AddRawExpectations(BuildNullSocketData()); |
CreateAndConnectStandard( |
"wss://localhost/", "/", NoSubProtocols(), "http://localhost", "", ""); |
RunUntilIdle(); |
@@ -1089,6 +1238,60 @@ TEST_F(WebSocketStreamCreateTest, SelfSignedCertificateSuccess) { |
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<base::HistogramSamples> original(GetSamples(name)); |