diff options
author | ricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-29 07:42:29 +0000 |
---|---|---|
committer | ricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-29 07:42:29 +0000 |
commit | e69c1cd33dee41835499a5130c3431a059b4ac7d (patch) | |
tree | 45a0bd23c30d455d99c45909d05329b18800d6b7 /net | |
parent | e0cc8e2ac33e2943920e8b0f0184cce62c01fd5a (diff) | |
download | chromium_src-e69c1cd33dee41835499a5130c3431a059b4ac7d.zip chromium_src-e69c1cd33dee41835499a5130c3431a059b4ac7d.tar.gz chromium_src-e69c1cd33dee41835499a5130c3431a059b4ac7d.tar.bz2 |
Map WebSocket URL schemes to HTTP URL schemes for auth purposes.
This permits WebSocket connections to inherit credentials from HTTP pages, and
matches the behaviour of other browsers.
Design doc: https://docs.google.com/a/chromium.org/document/d/129rLtf5x3hvhP5rayLiSxnEjOXS8Z7EnLJgBL4CdwjI/edit
Also consider any 401 or 407 results that reach the WebSocketStream
URLRequest::Delegate to be unrecoverable errors.
Also ensure that the response headers are reported back to the renderer
when the developer tools are open and a 401 error happens.
BUG=123862
Review URL: https://codereview.chromium.org/336263005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@286108 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/data/websocket/README | 5 | ||||
-rw-r--r-- | net/data/websocket/connect_to.html | 34 | ||||
-rw-r--r-- | net/http/http_network_transaction.cc | 12 | ||||
-rw-r--r-- | net/test/spawned_test_server/base_test_server.cc | 11 | ||||
-rw-r--r-- | net/test/spawned_test_server/base_test_server.h | 9 | ||||
-rwxr-xr-x | net/tools/testserver/testserver.py | 6 | ||||
-rw-r--r-- | net/websockets/websocket_basic_handshake_stream.cc | 17 | ||||
-rw-r--r-- | net/websockets/websocket_stream.cc | 39 | ||||
-rw-r--r-- | net/websockets/websocket_stream.h | 12 | ||||
-rw-r--r-- | net/websockets/websocket_stream_test.cc | 249 |
10 files changed, 355 insertions, 39 deletions
diff --git a/net/data/websocket/README b/net/data/websocket/README index 9282a38..d31d184 100644 --- a/net/data/websocket/README +++ b/net/data/websocket/README @@ -19,6 +19,11 @@ Multiple tests may share one resource, or URI handler. content::TitleWatcher. Used by WorkerTest.WebSocketSharedWorker. +- connect_to.html : A page which makes a connection to the WebSocket server + specified in the "url" parameter, + eg. connect_to.html?url=ws://localhost/echo Sets the title to "PASS" if + connection succeeds and "FAIL" otherwise. + - counted_connection.html : A page that creates a WebSocket connection to count-connection_wsh. This file does NOT close the established connection. diff --git a/net/data/websocket/connect_to.html b/net/data/websocket/connect_to.html new file mode 100644 index 0000000..05c653f --- /dev/null +++ b/net/data/websocket/connect_to.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html> +<head> +<title>test ws connection</title> +<script type="text/javascript"> + +var href = window.location.href; +var queryBegin = href.indexOf('?url='); +if (queryBegin == -1) { + console.log("Failed to find ?url= in URL"); + document.title = 'FAIL'; + throw "FAILURE"; +} +var url = href.slice(queryBegin + 5); + +// Do connection test. +var ws = new WebSocket(url); + +ws.onopen = function() +{ + // Set document title to 'PASS'. The test observer catches this title changes + // to know the result. + document.title = 'PASS'; +} + +ws.onclose = function() +{ + // Set document title to 'FAIL'. + document.title = 'FAIL'; +} + +</script> +</head> +</html> diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc index f4df073..8ee9069 100644 --- a/net/http/http_network_transaction.cc +++ b/net/http/http_network_transaction.cc @@ -61,6 +61,7 @@ #include "net/ssl/ssl_cert_request_info.h" #include "net/ssl/ssl_connection_status_flags.h" #include "url/gurl.h" +#include "url/url_canon.h" #if defined(SPDY_PROXY_AUTH_ORIGIN) #include <algorithm> @@ -1545,6 +1546,17 @@ GURL HttpNetworkTransaction::AuthURL(HttpAuth::Target target) const { proxy_info_.proxy_server().host_port_pair().ToString()); } case HttpAuth::AUTH_SERVER: + if (ForWebSocketHandshake()) { + const GURL& url = request_->url; + url::Replacements<char> ws_to_http; + if (url.SchemeIs("ws")) { + ws_to_http.SetScheme("http", url::Component(0, 4)); + } else { + DCHECK(url.SchemeIs("wss")); + ws_to_http.SetScheme("https", url::Component(0, 5)); + } + return url.ReplaceComponents(ws_to_http); + } return request_->url; default: return GURL(); diff --git a/net/test/spawned_test_server/base_test_server.cc b/net/test/spawned_test_server/base_test_server.cc index c0fc6c5..26608c6 100644 --- a/net/test/spawned_test_server/base_test_server.cc +++ b/net/test/spawned_test_server/base_test_server.cc @@ -165,7 +165,8 @@ const char BaseTestServer::kLocalhost[] = "127.0.0.1"; BaseTestServer::BaseTestServer(Type type, const std::string& host) : type_(type), started_(false), - log_to_console_(false) { + log_to_console_(false), + ws_basic_auth_(false) { Init(host); } @@ -173,7 +174,8 @@ BaseTestServer::BaseTestServer(Type type, const SSLOptions& ssl_options) : ssl_options_(ssl_options), type_(type), started_(false), - log_to_console_(false) { + log_to_console_(false), + ws_basic_auth_(false) { DCHECK(UsingSSL(type)); Init(GetHostname(type, ssl_options)); } @@ -384,6 +386,11 @@ bool BaseTestServer::GenerateArguments(base::DictionaryValue* arguments) const { if (VLOG_IS_ON(1) || log_to_console_) arguments->Set("log-to-console", base::Value::CreateNullValue()); + if (ws_basic_auth_) { + DCHECK(type_ == TYPE_WS || type_ == TYPE_WSS); + arguments->Set("ws-basic-auth", base::Value::CreateNullValue()); + } + if (UsingSSL(type_)) { // Check the certificate arguments of the HTTPS server. base::FilePath certificate_path(certificates_dir_); diff --git a/net/test/spawned_test_server/base_test_server.h b/net/test/spawned_test_server/base_test_server.h index a5e3287..6dd67db 100644 --- a/net/test/spawned_test_server/base_test_server.h +++ b/net/test/spawned_test_server/base_test_server.h @@ -242,6 +242,12 @@ class BaseTestServer { type == BaseTestServer::TYPE_WSS; } + // Enable HTTP basic authentication. Currently this only works for TYPE_WS and + // TYPE_WSS. + void set_websocket_basic_auth(bool ws_basic_auth) { + ws_basic_auth_ = ws_basic_auth; + } + protected: virtual ~BaseTestServer(); Type type() const { return type_; } @@ -308,6 +314,9 @@ class BaseTestServer { // Enables logging of the server to the console. bool log_to_console_; + // Is WebSocket basic HTTP authentication enabled? + bool ws_basic_auth_; + scoped_ptr<ScopedPortException> allowed_port_; DISALLOW_COPY_AND_ASSIGN(BaseTestServer); diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py index 0ff4e69..60b7b28 100755 --- a/net/tools/testserver/testserver.py +++ b/net/tools/testserver/testserver.py @@ -103,6 +103,7 @@ class WebSocketOptions: self.tls_client_ca = None self.tls_module = 'ssl' self.use_basic_auth = False + self.basic_auth_credential = 'Basic ' + base64.b64encode('test:test') class RecordingSSLSessionCache(object): @@ -2025,6 +2026,7 @@ class ServerRunner(testserver_base.TestServerRunner): print 'WebSocket server started on %s://%s:%d...' % \ (scheme, host, server.server_port) server_data['port'] = server.server_port + websocket_options.use_basic_auth = self.options.ws_basic_auth elif self.options.server_type == SERVER_TCP_ECHO: # Used for generating the key (randomly) that encodes the "echo request" # message. @@ -2206,6 +2208,10 @@ class ServerRunner(testserver_base.TestServerRunner): 'support for exactly one protocol, http/1.1') self.option_parser.add_option('--file-root-url', default='/files/', help='Specify a root URL for files served.') + # TODO(ricea): Generalize this to support basic auth for HTTP too. + self.option_parser.add_option('--ws-basic-auth', action='store_true', + dest='ws_basic_auth', + help='Enable basic-auth for WebSocket') if __name__ == '__main__': diff --git a/net/websockets/websocket_basic_handshake_stream.cc b/net/websockets/websocket_basic_handshake_stream.cc index f61885b..2eee942 100644 --- a/net/websockets/websocket_basic_handshake_stream.cc +++ b/net/websockets/websocket_basic_handshake_stream.cc @@ -539,20 +539,11 @@ void WebSocketBasicHandshakeStream::ReadResponseHeadersCallback( } void WebSocketBasicHandshakeStream::OnFinishOpeningHandshake() { - DCHECK(connect_delegate_); DCHECK(http_response_info_); - scoped_refptr<HttpResponseHeaders> headers = http_response_info_->headers; - // If the headers are too large, HttpStreamParser will just not parse them at - // all. - if (headers) { - scoped_ptr<WebSocketHandshakeResponseInfo> response( - new WebSocketHandshakeResponseInfo(url_, - headers->response_code(), - headers->GetStatusText(), - headers, - http_response_info_->response_time)); - connect_delegate_->OnFinishOpeningHandshake(response.Pass()); - } + WebSocketDispatchOnFinishOpeningHandshake(connect_delegate_, + url_, + http_response_info_->headers, + http_response_info_->response_time); } int WebSocketBasicHandshakeStream::ValidateResponse(int rv) { diff --git a/net/websockets/websocket_stream.cc b/net/websockets/websocket_stream.cc index 36b0ad4..b6a2359 100644 --- a/net/websockets/websocket_stream.cc +++ b/net/websockets/websocket_stream.cc @@ -10,6 +10,7 @@ #include "base/metrics/sparse_histogram.h" #include "net/base/load_flags.h" #include "net/http/http_request_headers.h" +#include "net/http/http_response_headers.h" #include "net/http/http_status_code.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_context.h" @@ -133,7 +134,18 @@ class StreamRequestImpl : public WebSocketStreamRequest { break; } } - connect_delegate_->OnFailure(failure_message_); + ReportFailureWithMessage(failure_message_); + } + + void ReportFailureWithMessage(const std::string& failure_message) { + connect_delegate_->OnFailure(failure_message); + } + + void OnFinishOpeningHandshake() { + WebSocketDispatchOnFinishOpeningHandshake(connect_delegate(), + url_request_.url(), + url_request_.response_headers(), + url_request_.response_time()); } WebSocketStream::ConnectDelegate* connect_delegate() const { @@ -198,7 +210,16 @@ void Delegate::OnResponseStarted(URLRequest* request) { return; case HTTP_UNAUTHORIZED: + result_ = FAILED; + owner_->OnFinishOpeningHandshake(); + owner_->ReportFailureWithMessage( + "HTTP Authentication failed; no valid credentials available"); + return; + case HTTP_PROXY_AUTHENTICATION_REQUIRED: + result_ = FAILED; + owner_->OnFinishOpeningHandshake(); + owner_->ReportFailureWithMessage("Proxy authentication failed"); return; default: @@ -285,4 +306,20 @@ scoped_ptr<WebSocketStreamRequest> CreateAndConnectStreamForTesting( return request.PassAs<WebSocketStreamRequest>(); } +void WebSocketDispatchOnFinishOpeningHandshake( + WebSocketStream::ConnectDelegate* connect_delegate, + const GURL& url, + const scoped_refptr<HttpResponseHeaders>& headers, + base::Time response_time) { + DCHECK(connect_delegate); + if (headers) { + connect_delegate->OnFinishOpeningHandshake(make_scoped_ptr( + new WebSocketHandshakeResponseInfo(url, + headers->response_code(), + headers->GetStatusText(), + headers, + response_time))); + } +} + } // namespace net diff --git a/net/websockets/websocket_stream.h b/net/websockets/websocket_stream.h index 09f11b2..46dc5262 100644 --- a/net/websockets/websocket_stream.h +++ b/net/websockets/websocket_stream.h @@ -10,8 +10,10 @@ #include "base/basictypes.h" #include "base/callback_forward.h" +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" +#include "base/time/time.h" #include "net/base/completion_callback.h" #include "net/base/net_export.h" #include "net/websockets/websocket_event_interface.h" @@ -198,6 +200,16 @@ class NET_EXPORT_PRIVATE WebSocketStream { DISALLOW_COPY_AND_ASSIGN(WebSocketStream); }; +// A helper function used in the implementation of CreateAndConnectStream() and +// WebSocketBasicHandshakeStream. It creates a WebSocketHandshakeResponseInfo +// object and dispatches it to the OnFinishOpeningHandshake() method of the +// supplied |connect_delegate|. +void WebSocketDispatchOnFinishOpeningHandshake( + WebSocketStream::ConnectDelegate* connect_delegate, + const GURL& gurl, + const scoped_refptr<HttpResponseHeaders>& headers, + base::Time response_time); + } // namespace net #endif // NET_WEBSOCKETS_WEBSOCKET_STREAM_H_ diff --git a/net/websockets/websocket_stream_test.cc b/net/websockets/websocket_stream_test.cc index b7229f9..653e367b 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)); |