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/location.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/run_loop.h"
19 #include "base/single_thread_task_runner.h"
20 #include "base/strings/string_piece.h"
21 #include "base/thread_task_runner_handle.h"
22 #include "net/base/auth.h"
23 #include "net/base/network_delegate.h"
24 #include "net/base/test_data_directory.h"
25 #include "net/proxy/proxy_service.h"
26 #include "net/test/spawned_test_server/spawned_test_server.h"
27 #include "net/url_request/url_request_test_util.h"
28 #include "net/websockets/websocket_channel.h"
29 #include "net/websockets/websocket_event_interface.h"
30 #include "testing/gtest/include/gtest/gtest.h"
31 #include "url/origin.h"
37 static const char kEchoServer
[] = "echo-with-no-extension";
39 // Simplify changing URL schemes.
40 GURL
ReplaceUrlScheme(const GURL
& in_url
, const base::StringPiece
& scheme
) {
41 GURL::Replacements replacements
;
42 replacements
.SetSchemeStr(scheme
);
43 return in_url
.ReplaceComponents(replacements
);
46 // An implementation of WebSocketEventInterface that waits for and records the
47 // results of the connect.
48 class ConnectTestingEventInterface
: public WebSocketEventInterface
{
50 ConnectTestingEventInterface();
52 void WaitForResponse();
54 bool failed() const { return failed_
; }
56 // Only set if the handshake failed, otherwise empty.
57 std::string
failure_message() const;
59 std::string
selected_subprotocol() const;
61 std::string
extensions() const;
63 // Implementation of WebSocketEventInterface.
64 ChannelState
OnAddChannelResponse(const std::string
& selected_subprotocol
,
65 const std::string
& extensions
) override
;
67 ChannelState
OnDataFrame(bool fin
,
68 WebSocketMessageType type
,
69 const std::vector
<char>& data
) override
;
71 ChannelState
OnFlowControl(int64 quota
) override
;
73 ChannelState
OnClosingHandshake() override
;
75 ChannelState
OnDropChannel(bool was_clean
,
77 const std::string
& reason
) override
;
79 ChannelState
OnFailChannel(const std::string
& message
) override
;
81 ChannelState
OnStartOpeningHandshake(
82 scoped_ptr
<WebSocketHandshakeRequestInfo
> request
) override
;
84 ChannelState
OnFinishOpeningHandshake(
85 scoped_ptr
<WebSocketHandshakeResponseInfo
> response
) override
;
87 ChannelState
OnSSLCertificateError(
88 scoped_ptr
<SSLErrorCallbacks
> ssl_error_callbacks
,
90 const SSLInfo
& ssl_info
,
94 void QuitNestedEventLoop();
96 // failed_ is true if the handshake failed (ie. OnFailChannel was called).
98 std::string selected_subprotocol_
;
99 std::string extensions_
;
100 std::string failure_message_
;
101 base::RunLoop run_loop_
;
103 DISALLOW_COPY_AND_ASSIGN(ConnectTestingEventInterface
);
106 ConnectTestingEventInterface::ConnectTestingEventInterface() : failed_(false) {
109 void ConnectTestingEventInterface::WaitForResponse() {
113 std::string
ConnectTestingEventInterface::failure_message() const {
114 return failure_message_
;
117 std::string
ConnectTestingEventInterface::selected_subprotocol() const {
118 return selected_subprotocol_
;
121 std::string
ConnectTestingEventInterface::extensions() const {
125 // Make the function definitions below less verbose.
126 typedef ConnectTestingEventInterface::ChannelState ChannelState
;
128 ChannelState
ConnectTestingEventInterface::OnAddChannelResponse(
129 const std::string
& selected_subprotocol
,
130 const std::string
& extensions
) {
131 selected_subprotocol_
= selected_subprotocol
;
132 extensions_
= extensions
;
133 QuitNestedEventLoop();
134 return CHANNEL_ALIVE
;
137 ChannelState
ConnectTestingEventInterface::OnDataFrame(
139 WebSocketMessageType type
,
140 const std::vector
<char>& data
) {
141 return CHANNEL_ALIVE
;
144 ChannelState
ConnectTestingEventInterface::OnFlowControl(int64 quota
) {
145 return CHANNEL_ALIVE
;
148 ChannelState
ConnectTestingEventInterface::OnClosingHandshake() {
149 return CHANNEL_ALIVE
;
152 ChannelState
ConnectTestingEventInterface::OnDropChannel(
155 const std::string
& reason
) {
156 return CHANNEL_DELETED
;
159 ChannelState
ConnectTestingEventInterface::OnFailChannel(
160 const std::string
& message
) {
162 failure_message_
= message
;
163 QuitNestedEventLoop();
164 return CHANNEL_DELETED
;
167 ChannelState
ConnectTestingEventInterface::OnStartOpeningHandshake(
168 scoped_ptr
<WebSocketHandshakeRequestInfo
> request
) {
169 return CHANNEL_ALIVE
;
172 ChannelState
ConnectTestingEventInterface::OnFinishOpeningHandshake(
173 scoped_ptr
<WebSocketHandshakeResponseInfo
> response
) {
174 return CHANNEL_ALIVE
;
177 ChannelState
ConnectTestingEventInterface::OnSSLCertificateError(
178 scoped_ptr
<SSLErrorCallbacks
> ssl_error_callbacks
,
180 const SSLInfo
& ssl_info
,
182 base::ThreadTaskRunnerHandle::Get()->PostTask(
183 FROM_HERE
, base::Bind(&SSLErrorCallbacks::CancelSSLRequest
,
184 base::Owned(ssl_error_callbacks
.release()),
185 ERR_SSL_PROTOCOL_ERROR
, &ssl_info
));
186 return CHANNEL_ALIVE
;
189 void ConnectTestingEventInterface::QuitNestedEventLoop() {
193 // A subclass of TestNetworkDelegate that additionally implements the
194 // OnResolveProxy callback and records the information passed to it.
195 class TestNetworkDelegateWithProxyInfo
: public TestNetworkDelegate
{
197 TestNetworkDelegateWithProxyInfo() {}
199 struct ResolvedProxyInfo
{
201 ProxyInfo proxy_info
;
204 const ResolvedProxyInfo
& resolved_proxy_info() const {
205 return resolved_proxy_info_
;
209 void OnResolveProxy(const GURL
& url
,
211 const ProxyService
& proxy_service
,
212 ProxyInfo
* result
) override
{
213 resolved_proxy_info_
.url
= url
;
214 resolved_proxy_info_
.proxy_info
= *result
;
218 ResolvedProxyInfo resolved_proxy_info_
;
220 DISALLOW_COPY_AND_ASSIGN(TestNetworkDelegateWithProxyInfo
);
223 class WebSocketEndToEndTest
: public ::testing::Test
{
225 WebSocketEndToEndTest()
226 : event_interface_(),
227 network_delegate_(new TestNetworkDelegateWithProxyInfo
),
230 initialised_context_(false) {}
232 // Initialise the URLRequestContext. Normally done automatically by
233 // ConnectAndWait(). This method is for the use of tests that need the
234 // URLRequestContext initialised before calling ConnectAndWait().
235 void InitialiseContext() {
236 context_
.set_network_delegate(network_delegate_
.get());
238 initialised_context_
= true;
241 // Send the connect request to |socket_url| and wait for a response. Returns
242 // true if the handshake succeeded.
243 bool ConnectAndWait(const GURL
& socket_url
) {
244 if (!initialised_context_
) {
247 url::Origin
origin(GURL("http://localhost"));
248 event_interface_
= new ConnectTestingEventInterface
;
250 new WebSocketChannel(make_scoped_ptr(event_interface_
), &context_
));
251 channel_
->SendAddChannelRequest(GURL(socket_url
), sub_protocols_
, origin
);
252 event_interface_
->WaitForResponse();
253 return !event_interface_
->failed();
256 ConnectTestingEventInterface
* event_interface_
; // owned by channel_
257 scoped_ptr
<TestNetworkDelegateWithProxyInfo
> network_delegate_
;
258 TestURLRequestContext context_
;
259 scoped_ptr
<WebSocketChannel
> channel_
;
260 std::vector
<std::string
> sub_protocols_
;
261 bool initialised_context_
;
264 // None of these tests work on Android.
265 // TODO(ricea): Make these tests work on Android. See crbug.com/441711.
266 #if defined(OS_ANDROID)
267 #define DISABLED_ON_ANDROID(test) DISABLED_##test
269 #define DISABLED_ON_ANDROID(test) test
272 // Basic test of connectivity. If this test fails, nothing else can be expected
274 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(BasicSmokeTest
)) {
275 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
276 SpawnedTestServer::kLocalhost
,
277 GetWebSocketTestDataDirectory());
278 ASSERT_TRUE(ws_server
.Start());
279 EXPECT_TRUE(ConnectAndWait(ws_server
.GetURL(kEchoServer
)));
282 // Test for issue crbug.com/433695 "Unencrypted WebSocket connection via
283 // authenticated proxy times out"
284 // TODO(ricea): Enable this when the issue is fixed.
285 TEST_F(WebSocketEndToEndTest
, DISABLED_HttpsProxyUnauthedFails
) {
286 SpawnedTestServer
proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY
,
287 SpawnedTestServer::kLocalhost
,
289 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
290 SpawnedTestServer::kLocalhost
,
291 GetWebSocketTestDataDirectory());
292 ASSERT_TRUE(proxy_server
.StartInBackground());
293 ASSERT_TRUE(ws_server
.StartInBackground());
294 ASSERT_TRUE(proxy_server
.BlockUntilStarted());
295 ASSERT_TRUE(ws_server
.BlockUntilStarted());
296 std::string proxy_config
=
297 "https=" + proxy_server
.host_port_pair().ToString();
298 scoped_ptr
<ProxyService
> proxy_service(
299 ProxyService::CreateFixed(proxy_config
));
300 ASSERT_TRUE(proxy_service
);
301 context_
.set_proxy_service(proxy_service
.get());
302 EXPECT_FALSE(ConnectAndWait(ws_server
.GetURL(kEchoServer
)));
303 EXPECT_EQ("Proxy authentication failed", event_interface_
->failure_message());
306 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(HttpsWssProxyUnauthedFails
)) {
307 SpawnedTestServer
proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY
,
308 SpawnedTestServer::kLocalhost
,
310 SpawnedTestServer
wss_server(SpawnedTestServer::TYPE_WSS
,
311 SpawnedTestServer::kLocalhost
,
312 GetWebSocketTestDataDirectory());
313 ASSERT_TRUE(proxy_server
.StartInBackground());
314 ASSERT_TRUE(wss_server
.StartInBackground());
315 ASSERT_TRUE(proxy_server
.BlockUntilStarted());
316 ASSERT_TRUE(wss_server
.BlockUntilStarted());
317 std::string proxy_config
=
318 "https=" + proxy_server
.host_port_pair().ToString();
319 scoped_ptr
<ProxyService
> proxy_service(
320 ProxyService::CreateFixed(proxy_config
));
321 ASSERT_TRUE(proxy_service
);
322 context_
.set_proxy_service(proxy_service
.get());
323 EXPECT_FALSE(ConnectAndWait(wss_server
.GetURL(kEchoServer
)));
324 EXPECT_EQ("Proxy authentication failed", event_interface_
->failure_message());
327 // Regression test for crbug/426736 "WebSocket connections not using configured
328 // system HTTPS Proxy".
329 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(HttpsProxyUsed
)) {
330 SpawnedTestServer
proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY
,
331 SpawnedTestServer::kLocalhost
,
333 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
334 SpawnedTestServer::kLocalhost
,
335 GetWebSocketTestDataDirectory());
336 ASSERT_TRUE(proxy_server
.StartInBackground());
337 ASSERT_TRUE(ws_server
.StartInBackground());
338 ASSERT_TRUE(proxy_server
.BlockUntilStarted());
339 ASSERT_TRUE(ws_server
.BlockUntilStarted());
340 std::string proxy_config
= "https=" +
341 proxy_server
.host_port_pair().ToString() + ";" +
342 "http=" + proxy_server
.host_port_pair().ToString();
343 scoped_ptr
<ProxyService
> proxy_service(
344 ProxyService::CreateFixed(proxy_config
));
345 context_
.set_proxy_service(proxy_service
.get());
348 // The test server doesn't have an unauthenticated proxy mode. WebSockets
349 // cannot provide auth information that isn't already cached, so it's
350 // necessary to preflight an HTTP request to authenticate against the proxy.
351 // It doesn't matter what the URL is, as long as it is an HTTP navigation.
353 ReplaceUrlScheme(ws_server
.GetURL("connect_check.html"), "http");
354 TestDelegate delegate
;
355 delegate
.set_credentials(
356 AuthCredentials(base::ASCIIToUTF16("foo"), base::ASCIIToUTF16("bar")));
358 scoped_ptr
<URLRequest
> request(
359 context_
.CreateRequest(http_page
, DEFAULT_PRIORITY
, &delegate
));
361 // TestDelegate exits the message loop when the request completes by
363 base::RunLoop().Run();
364 EXPECT_TRUE(delegate
.auth_required_called());
367 GURL ws_url
= ws_server
.GetURL(kEchoServer
);
368 EXPECT_TRUE(ConnectAndWait(ws_url
));
369 const TestNetworkDelegateWithProxyInfo::ResolvedProxyInfo
& info
=
370 network_delegate_
->resolved_proxy_info();
371 EXPECT_EQ(ws_url
, info
.url
);
372 EXPECT_TRUE(info
.proxy_info
.is_http());
375 // This is a regression test for crbug.com/408061 Crash in
376 // net::WebSocketBasicHandshakeStream::Upgrade.
377 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(TruncatedResponse
)) {
378 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
379 SpawnedTestServer::kLocalhost
,
380 GetWebSocketTestDataDirectory());
381 ASSERT_TRUE(ws_server
.Start());
384 GURL ws_url
= ws_server
.GetURL("truncated-headers");
385 EXPECT_FALSE(ConnectAndWait(ws_url
));
388 // Regression test for crbug.com/455215 "HSTS not applied to WebSocket"
389 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(HstsHttpsToWebSocket
)) {
390 SpawnedTestServer::SSLOptions
ssl_options(
391 SpawnedTestServer::SSLOptions::CERT_COMMON_NAME_IS_DOMAIN
);
392 SpawnedTestServer
https_server(
393 SpawnedTestServer::TYPE_HTTPS
, ssl_options
,
394 base::FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
395 SpawnedTestServer
wss_server(SpawnedTestServer::TYPE_WSS
, ssl_options
,
396 GetWebSocketTestDataDirectory());
398 ASSERT_TRUE(https_server
.StartInBackground());
399 ASSERT_TRUE(wss_server
.StartInBackground());
400 ASSERT_TRUE(https_server
.BlockUntilStarted());
401 ASSERT_TRUE(wss_server
.BlockUntilStarted());
403 // Set HSTS via https:
404 TestDelegate delegate
;
405 GURL https_page
= https_server
.GetURL("files/hsts-headers.html");
406 scoped_ptr
<URLRequest
> request(
407 context_
.CreateRequest(https_page
, DEFAULT_PRIORITY
, &delegate
));
409 // TestDelegate exits the message loop when the request completes.
410 base::RunLoop().Run();
411 EXPECT_TRUE(request
->status().is_success());
413 // Check HSTS with ws:
414 // Change the scheme from wss: to ws: to verify that it is switched back.
415 GURL ws_url
= ReplaceUrlScheme(wss_server
.GetURL(kEchoServer
), "ws");
416 EXPECT_TRUE(ConnectAndWait(ws_url
));
419 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(HstsWebSocketToHttps
)) {
420 SpawnedTestServer::SSLOptions
ssl_options(
421 SpawnedTestServer::SSLOptions::CERT_COMMON_NAME_IS_DOMAIN
);
422 SpawnedTestServer
https_server(
423 SpawnedTestServer::TYPE_HTTPS
, ssl_options
,
424 base::FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
425 SpawnedTestServer
wss_server(SpawnedTestServer::TYPE_WSS
, ssl_options
,
426 GetWebSocketTestDataDirectory());
427 ASSERT_TRUE(https_server
.StartInBackground());
428 ASSERT_TRUE(wss_server
.StartInBackground());
429 ASSERT_TRUE(https_server
.BlockUntilStarted());
430 ASSERT_TRUE(wss_server
.BlockUntilStarted());
433 GURL wss_url
= wss_server
.GetURL("set-hsts");
434 EXPECT_TRUE(ConnectAndWait(wss_url
));
437 TestDelegate delegate
;
439 ReplaceUrlScheme(https_server
.GetURL("files/simple.html"), "http");
440 scoped_ptr
<URLRequest
> request(
441 context_
.CreateRequest(http_page
, DEFAULT_PRIORITY
, &delegate
));
443 // TestDelegate exits the message loop when the request completes.
444 base::RunLoop().Run();
445 EXPECT_TRUE(request
->status().is_success());
446 EXPECT_TRUE(request
->url().SchemeIs("https"));
449 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(HstsWebSocketToWebSocket
)) {
450 SpawnedTestServer::SSLOptions
ssl_options(
451 SpawnedTestServer::SSLOptions::CERT_COMMON_NAME_IS_DOMAIN
);
452 SpawnedTestServer
wss_server(SpawnedTestServer::TYPE_WSS
, ssl_options
,
453 GetWebSocketTestDataDirectory());
454 ASSERT_TRUE(wss_server
.Start());
457 GURL wss_url
= wss_server
.GetURL("set-hsts");
458 EXPECT_TRUE(ConnectAndWait(wss_url
));
461 GURL ws_url
= ReplaceUrlScheme(wss_server
.GetURL(kEchoServer
), "ws");
462 EXPECT_TRUE(ConnectAndWait(ws_url
));
465 // Regression test for crbug.com/180504 "WebSocket handshake fails when HTTP
466 // headers have trailing LWS".
467 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(TrailingWhitespace
)) {
468 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
469 SpawnedTestServer::kLocalhost
,
470 GetWebSocketTestDataDirectory());
471 ASSERT_TRUE(ws_server
.Start());
473 GURL ws_url
= ws_server
.GetURL("trailing-whitespace");
474 sub_protocols_
.push_back("sip");
475 EXPECT_TRUE(ConnectAndWait(ws_url
));
476 EXPECT_EQ("sip", event_interface_
->selected_subprotocol());
479 // This is a regression test for crbug.com/169448 "WebSockets should support
480 // header continuations"
481 // TODO(ricea): HTTP continuation headers have been deprecated by RFC7230. If
482 // support for continuation headers is removed from Chrome, then this test will
483 // break and should be removed.
484 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(HeaderContinuations
)) {
485 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
486 SpawnedTestServer::kLocalhost
,
487 GetWebSocketTestDataDirectory());
488 ASSERT_TRUE(ws_server
.Start());
490 GURL ws_url
= ws_server
.GetURL("header-continuation");
492 EXPECT_TRUE(ConnectAndWait(ws_url
));
493 EXPECT_EQ("permessage-deflate; server_max_window_bits=10",
494 event_interface_
->extensions());