summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-29 07:42:29 +0000
committerricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-29 07:42:29 +0000
commite69c1cd33dee41835499a5130c3431a059b4ac7d (patch)
tree45a0bd23c30d455d99c45909d05329b18800d6b7 /net
parente0cc8e2ac33e2943920e8b0f0184cce62c01fd5a (diff)
downloadchromium_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/README5
-rw-r--r--net/data/websocket/connect_to.html34
-rw-r--r--net/http/http_network_transaction.cc12
-rw-r--r--net/test/spawned_test_server/base_test_server.cc11
-rw-r--r--net/test/spawned_test_server/base_test_server.h9
-rwxr-xr-xnet/tools/testserver/testserver.py6
-rw-r--r--net/websockets/websocket_basic_handshake_stream.cc17
-rw-r--r--net/websockets/websocket_stream.cc39
-rw-r--r--net/websockets/websocket_stream.h12
-rw-r--r--net/websockets/websocket_stream_test.cc249
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));