Index: net/websockets/websocket_stream_test.cc |
diff --git a/net/websockets/websocket_stream_test.cc b/net/websockets/websocket_stream_test.cc |
index 0a6b99be4d60f68404d97f5cda56d7af7f20ee8b..faf06476a80e7c1db939a714bd883d3d7b57956f 100644 |
--- a/net/websockets/websocket_stream_test.cc |
+++ b/net/websockets/websocket_stream_test.cc |
@@ -55,6 +55,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 |
@@ -118,10 +141,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, |
@@ -174,8 +202,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( |
@@ -233,6 +261,128 @@ 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: |
+ 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; |
Johnny
2014/07/08 20:27:40
Trailing underscores?
Adam Rice
2014/07/09 06:35:01
Okay, that's weird. In my brain this was a struct,
|
+ std::string request2; |
+ std::string response1; |
+ std::string response2; |
+ MockRead reads1[2]; |
+ MockWrite writes1[1]; |
+ MockRead reads2[1]; |
+ MockWrite writes2[1]; |
+}; |
+ |
+// 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. |
@@ -925,8 +1075,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(), |
@@ -941,8 +1090,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(), |
@@ -955,8 +1103,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(), |
@@ -998,15 +1145,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()); |
Johnny
2014/07/08 20:27:40
Why is ownership of |socket_data| passed to WebSoc
Adam Rice
2014/07/09 06:35:01
Short explanation: WebSocketDeterministicMockClien
Johnny
2014/07/09 16:16:17
SGTM.
|
+ socket_data_raw_ptr->Run(); |
stream_request_.reset(); |
RunUntilIdle(); |
EXPECT_FALSE(has_failed()); |
@@ -1039,14 +1186,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_); |
@@ -1060,8 +1207,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", |
@@ -1084,8 +1230,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(); |
@@ -1096,6 +1241,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)); |