1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // End-to-end tests for WebSocket.
7 // A python server is (re)started for each test, which is moderately
8 // inefficient. However, it makes these tests a good fit for scenarios which
9 // require special server configurations.
13 #include "base/bind.h"
14 #include "base/bind_helpers.h"
15 #include "base/callback.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/message_loop/message_loop.h"
18 #include "base/run_loop.h"
19 #include "net/base/auth.h"
20 #include "net/base/network_delegate.h"
21 #include "net/base/test_data_directory.h"
22 #include "net/proxy/proxy_service.h"
23 #include "net/test/spawned_test_server/spawned_test_server.h"
24 #include "net/url_request/url_request_test_util.h"
25 #include "net/websockets/websocket_channel.h"
26 #include "net/websockets/websocket_event_interface.h"
27 #include "testing/gtest/include/gtest/gtest.h"
28 #include "url/origin.h"
34 static const char kEchoServer
[] = "echo-with-no-extension";
36 // An implementation of WebSocketEventInterface that waits for and records the
37 // results of the connect.
38 class ConnectTestingEventInterface
: public WebSocketEventInterface
{
40 ConnectTestingEventInterface();
42 void WaitForResponse();
44 bool failed() const { return failed_
; }
46 // Only set if the handshake failed, otherwise empty.
47 std::string
failure_message() const;
49 std::string
selected_subprotocol() const;
51 std::string
extensions() const;
53 // Implementation of WebSocketEventInterface.
54 ChannelState
OnAddChannelResponse(bool fail
,
55 const std::string
& selected_subprotocol
,
56 const std::string
& extensions
) override
;
58 ChannelState
OnDataFrame(bool fin
,
59 WebSocketMessageType type
,
60 const std::vector
<char>& data
) override
;
62 ChannelState
OnFlowControl(int64 quota
) override
;
64 ChannelState
OnClosingHandshake() override
;
66 ChannelState
OnDropChannel(bool was_clean
,
68 const std::string
& reason
) override
;
70 ChannelState
OnFailChannel(const std::string
& message
) override
;
72 ChannelState
OnStartOpeningHandshake(
73 scoped_ptr
<WebSocketHandshakeRequestInfo
> request
) override
;
75 ChannelState
OnFinishOpeningHandshake(
76 scoped_ptr
<WebSocketHandshakeResponseInfo
> response
) override
;
78 ChannelState
OnSSLCertificateError(
79 scoped_ptr
<SSLErrorCallbacks
> ssl_error_callbacks
,
81 const SSLInfo
& ssl_info
,
85 void QuitNestedEventLoop();
87 // failed_ is true if the handshake failed (ie. OnFailChannel was called).
89 std::string selected_subprotocol_
;
90 std::string extensions_
;
91 std::string failure_message_
;
92 base::RunLoop run_loop_
;
94 DISALLOW_COPY_AND_ASSIGN(ConnectTestingEventInterface
);
97 ConnectTestingEventInterface::ConnectTestingEventInterface() : failed_(true) {
100 void ConnectTestingEventInterface::WaitForResponse() {
104 std::string
ConnectTestingEventInterface::failure_message() const {
105 return failure_message_
;
108 std::string
ConnectTestingEventInterface::selected_subprotocol() const {
109 return selected_subprotocol_
;
112 std::string
ConnectTestingEventInterface::extensions() const {
116 // Make the function definitions below less verbose.
117 typedef ConnectTestingEventInterface::ChannelState ChannelState
;
119 ChannelState
ConnectTestingEventInterface::OnAddChannelResponse(
121 const std::string
& selected_subprotocol
,
122 const std::string
& extensions
) {
124 selected_subprotocol_
= selected_subprotocol
;
125 extensions_
= extensions
;
126 QuitNestedEventLoop();
127 return fail
? CHANNEL_DELETED
: CHANNEL_ALIVE
;
130 ChannelState
ConnectTestingEventInterface::OnDataFrame(
132 WebSocketMessageType type
,
133 const std::vector
<char>& data
) {
134 return CHANNEL_ALIVE
;
137 ChannelState
ConnectTestingEventInterface::OnFlowControl(int64 quota
) {
138 return CHANNEL_ALIVE
;
141 ChannelState
ConnectTestingEventInterface::OnClosingHandshake() {
142 return CHANNEL_ALIVE
;
145 ChannelState
ConnectTestingEventInterface::OnDropChannel(
148 const std::string
& reason
) {
149 return CHANNEL_DELETED
;
152 ChannelState
ConnectTestingEventInterface::OnFailChannel(
153 const std::string
& message
) {
155 failure_message_
= message
;
156 QuitNestedEventLoop();
157 return CHANNEL_DELETED
;
160 ChannelState
ConnectTestingEventInterface::OnStartOpeningHandshake(
161 scoped_ptr
<WebSocketHandshakeRequestInfo
> request
) {
162 return CHANNEL_ALIVE
;
165 ChannelState
ConnectTestingEventInterface::OnFinishOpeningHandshake(
166 scoped_ptr
<WebSocketHandshakeResponseInfo
> response
) {
167 return CHANNEL_ALIVE
;
170 ChannelState
ConnectTestingEventInterface::OnSSLCertificateError(
171 scoped_ptr
<SSLErrorCallbacks
> ssl_error_callbacks
,
173 const SSLInfo
& ssl_info
,
175 base::MessageLoop::current()->PostTask(
176 FROM_HERE
, base::Bind(&SSLErrorCallbacks::CancelSSLRequest
,
177 base::Owned(ssl_error_callbacks
.release()),
178 ERR_SSL_PROTOCOL_ERROR
, &ssl_info
));
179 return CHANNEL_ALIVE
;
182 void ConnectTestingEventInterface::QuitNestedEventLoop() {
186 // A subclass of TestNetworkDelegate that additionally implements the
187 // OnResolveProxy callback and records the information passed to it.
188 class TestNetworkDelegateWithProxyInfo
: public TestNetworkDelegate
{
190 TestNetworkDelegateWithProxyInfo() {}
192 struct ResolvedProxyInfo
{
194 ProxyInfo proxy_info
;
197 const ResolvedProxyInfo
& resolved_proxy_info() const {
198 return resolved_proxy_info_
;
202 void OnResolveProxy(const GURL
& url
,
204 const ProxyService
& proxy_service
,
205 ProxyInfo
* result
) override
{
206 resolved_proxy_info_
.url
= url
;
207 resolved_proxy_info_
.proxy_info
= *result
;
211 ResolvedProxyInfo resolved_proxy_info_
;
213 DISALLOW_COPY_AND_ASSIGN(TestNetworkDelegateWithProxyInfo
);
216 class WebSocketEndToEndTest
: public ::testing::Test
{
218 WebSocketEndToEndTest()
219 : event_interface_(new ConnectTestingEventInterface
),
220 network_delegate_(new TestNetworkDelegateWithProxyInfo
),
222 channel_(make_scoped_ptr(event_interface_
), &context_
),
223 initialised_context_(false) {}
225 // Initialise the URLRequestContext. Normally done automatically by
226 // ConnectAndWait(). This method is for the use of tests that need the
227 // URLRequestContext initialised before calling ConnectAndWait().
228 void InitialiseContext() {
229 context_
.set_network_delegate(network_delegate_
.get());
231 initialised_context_
= true;
234 // Send the connect request to |socket_url| and wait for a response. Returns
235 // true if the handshake succeeded.
236 bool ConnectAndWait(const GURL
& socket_url
) {
237 if (!initialised_context_
) {
240 std::vector
<std::string
> sub_protocols
;
241 url::Origin
origin("http://localhost");
242 channel_
.SendAddChannelRequest(GURL(socket_url
), sub_protocols
, origin
);
243 event_interface_
->WaitForResponse();
244 return !event_interface_
->failed();
247 ConnectTestingEventInterface
* event_interface_
; // owned by channel_
248 scoped_ptr
<TestNetworkDelegateWithProxyInfo
> network_delegate_
;
249 TestURLRequestContext context_
;
250 WebSocketChannel channel_
;
251 bool initialised_context_
;
254 // None of these tests work on Android.
255 // TODO(ricea): Make these tests work on Android. See crbug.com/441711.
256 #if defined(OS_ANDROID)
257 #define DISABLED_ON_ANDROID(test) DISABLED_##test
259 #define DISABLED_ON_ANDROID(test) test
262 // Basic test of connectivity. If this test fails, nothing else can be expected
264 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(BasicSmokeTest
)) {
265 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
266 SpawnedTestServer::kLocalhost
,
267 GetWebSocketTestDataDirectory());
268 ASSERT_TRUE(ws_server
.Start());
269 EXPECT_TRUE(ConnectAndWait(ws_server
.GetURL(kEchoServer
)));
272 // Test for issue crbug.com/433695 "Unencrypted WebSocket connection via
273 // authenticated proxy times out"
274 // TODO(ricea): Enable this when the issue is fixed.
275 TEST_F(WebSocketEndToEndTest
, DISABLED_HttpsProxyUnauthedFails
) {
276 SpawnedTestServer
proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY
,
277 SpawnedTestServer::kLocalhost
,
279 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
280 SpawnedTestServer::kLocalhost
,
281 GetWebSocketTestDataDirectory());
282 ASSERT_TRUE(proxy_server
.StartInBackground());
283 ASSERT_TRUE(ws_server
.StartInBackground());
284 ASSERT_TRUE(proxy_server
.BlockUntilStarted());
285 ASSERT_TRUE(ws_server
.BlockUntilStarted());
286 std::string proxy_config
=
287 "https=" + proxy_server
.host_port_pair().ToString();
288 scoped_ptr
<ProxyService
> proxy_service(
289 ProxyService::CreateFixed(proxy_config
));
290 ASSERT_TRUE(proxy_service
);
291 context_
.set_proxy_service(proxy_service
.get());
292 EXPECT_FALSE(ConnectAndWait(ws_server
.GetURL(kEchoServer
)));
293 EXPECT_EQ("Proxy authentication failed", event_interface_
->failure_message());
296 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(HttpsWssProxyUnauthedFails
)) {
297 SpawnedTestServer
proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY
,
298 SpawnedTestServer::kLocalhost
,
300 SpawnedTestServer
wss_server(SpawnedTestServer::TYPE_WSS
,
301 SpawnedTestServer::kLocalhost
,
302 GetWebSocketTestDataDirectory());
303 ASSERT_TRUE(proxy_server
.StartInBackground());
304 ASSERT_TRUE(wss_server
.StartInBackground());
305 ASSERT_TRUE(proxy_server
.BlockUntilStarted());
306 ASSERT_TRUE(wss_server
.BlockUntilStarted());
307 std::string proxy_config
=
308 "https=" + proxy_server
.host_port_pair().ToString();
309 scoped_ptr
<ProxyService
> proxy_service(
310 ProxyService::CreateFixed(proxy_config
));
311 ASSERT_TRUE(proxy_service
);
312 context_
.set_proxy_service(proxy_service
.get());
313 EXPECT_FALSE(ConnectAndWait(wss_server
.GetURL(kEchoServer
)));
314 EXPECT_EQ("Proxy authentication failed", event_interface_
->failure_message());
317 // Regression test for crbug/426736 "WebSocket connections not using configured
318 // system HTTPS Proxy".
319 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(HttpsProxyUsed
)) {
320 SpawnedTestServer
proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY
,
321 SpawnedTestServer::kLocalhost
,
323 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
324 SpawnedTestServer::kLocalhost
,
325 GetWebSocketTestDataDirectory());
326 ASSERT_TRUE(proxy_server
.StartInBackground());
327 ASSERT_TRUE(ws_server
.StartInBackground());
328 ASSERT_TRUE(proxy_server
.BlockUntilStarted());
329 ASSERT_TRUE(ws_server
.BlockUntilStarted());
330 std::string proxy_config
= "https=" +
331 proxy_server
.host_port_pair().ToString() + ";" +
332 "http=" + proxy_server
.host_port_pair().ToString();
333 scoped_ptr
<ProxyService
> proxy_service(
334 ProxyService::CreateFixed(proxy_config
));
335 context_
.set_proxy_service(proxy_service
.get());
338 // The test server doesn't have an unauthenticated proxy mode. WebSockets
339 // cannot provide auth information that isn't already cached, so it's
340 // necessary to preflight an HTTP request to authenticate against the proxy.
341 GURL::Replacements replacements
;
342 replacements
.SetSchemeStr("http");
343 // It doesn't matter what the URL is, as long as it is an HTTP navigation.
345 ws_server
.GetURL("connect_check.html").ReplaceComponents(replacements
);
346 TestDelegate delegate
;
347 delegate
.set_credentials(
348 AuthCredentials(base::ASCIIToUTF16("foo"), base::ASCIIToUTF16("bar")));
350 scoped_ptr
<URLRequest
> request(
351 context_
.CreateRequest(http_page
, DEFAULT_PRIORITY
, &delegate
, NULL
));
353 // TestDelegate exits the message loop when the request completes by
355 base::RunLoop().Run();
356 EXPECT_TRUE(delegate
.auth_required_called());
359 GURL ws_url
= ws_server
.GetURL(kEchoServer
);
360 EXPECT_TRUE(ConnectAndWait(ws_url
));
361 const TestNetworkDelegateWithProxyInfo::ResolvedProxyInfo
& info
=
362 network_delegate_
->resolved_proxy_info();
363 EXPECT_EQ(ws_url
, info
.url
);
364 EXPECT_TRUE(info
.proxy_info
.is_http());
367 // This is a regression test for crbug.com/408061 Crash in
368 // net::WebSocketBasicHandshakeStream::Upgrade.
369 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(TruncatedResponse
)) {
370 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
371 SpawnedTestServer::kLocalhost
,
372 GetWebSocketTestDataDirectory());
373 ASSERT_TRUE(ws_server
.Start());
376 GURL ws_url
= ws_server
.GetURL("truncated-headers");
377 EXPECT_FALSE(ConnectAndWait(ws_url
));