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 "base/strings/string_piece.h"
20 #include "net/base/auth.h"
21 #include "net/base/network_delegate.h"
22 #include "net/base/test_data_directory.h"
23 #include "net/proxy/proxy_service.h"
24 #include "net/test/spawned_test_server/spawned_test_server.h"
25 #include "net/url_request/url_request_test_util.h"
26 #include "net/websockets/websocket_channel.h"
27 #include "net/websockets/websocket_event_interface.h"
28 #include "testing/gtest/include/gtest/gtest.h"
29 #include "url/origin.h"
35 static const char kEchoServer
[] = "echo-with-no-extension";
37 // Simplify changing URL schemes.
38 GURL
ReplaceUrlScheme(const GURL
& in_url
, const base::StringPiece
& scheme
) {
39 GURL::Replacements replacements
;
40 replacements
.SetSchemeStr(scheme
);
41 return in_url
.ReplaceComponents(replacements
);
44 // An implementation of WebSocketEventInterface that waits for and records the
45 // results of the connect.
46 class ConnectTestingEventInterface
: public WebSocketEventInterface
{
48 ConnectTestingEventInterface();
50 void WaitForResponse();
52 bool failed() const { return failed_
; }
54 // Only set if the handshake failed, otherwise empty.
55 std::string
failure_message() const;
57 std::string
selected_subprotocol() const;
59 std::string
extensions() const;
61 // Implementation of WebSocketEventInterface.
62 ChannelState
OnAddChannelResponse(const std::string
& selected_subprotocol
,
63 const std::string
& extensions
) override
;
65 ChannelState
OnDataFrame(bool fin
,
66 WebSocketMessageType type
,
67 const std::vector
<char>& data
) override
;
69 ChannelState
OnFlowControl(int64 quota
) override
;
71 ChannelState
OnClosingHandshake() override
;
73 ChannelState
OnDropChannel(bool was_clean
,
75 const std::string
& reason
) override
;
77 ChannelState
OnFailChannel(const std::string
& message
) override
;
79 ChannelState
OnStartOpeningHandshake(
80 scoped_ptr
<WebSocketHandshakeRequestInfo
> request
) override
;
82 ChannelState
OnFinishOpeningHandshake(
83 scoped_ptr
<WebSocketHandshakeResponseInfo
> response
) override
;
85 ChannelState
OnSSLCertificateError(
86 scoped_ptr
<SSLErrorCallbacks
> ssl_error_callbacks
,
88 const SSLInfo
& ssl_info
,
92 void QuitNestedEventLoop();
94 // failed_ is true if the handshake failed (ie. OnFailChannel was called).
96 std::string selected_subprotocol_
;
97 std::string extensions_
;
98 std::string failure_message_
;
99 base::RunLoop run_loop_
;
101 DISALLOW_COPY_AND_ASSIGN(ConnectTestingEventInterface
);
104 ConnectTestingEventInterface::ConnectTestingEventInterface() : failed_(false) {
107 void ConnectTestingEventInterface::WaitForResponse() {
111 std::string
ConnectTestingEventInterface::failure_message() const {
112 return failure_message_
;
115 std::string
ConnectTestingEventInterface::selected_subprotocol() const {
116 return selected_subprotocol_
;
119 std::string
ConnectTestingEventInterface::extensions() const {
123 // Make the function definitions below less verbose.
124 typedef ConnectTestingEventInterface::ChannelState ChannelState
;
126 ChannelState
ConnectTestingEventInterface::OnAddChannelResponse(
127 const std::string
& selected_subprotocol
,
128 const std::string
& extensions
) {
129 selected_subprotocol_
= selected_subprotocol
;
130 extensions_
= extensions
;
131 QuitNestedEventLoop();
132 return CHANNEL_ALIVE
;
135 ChannelState
ConnectTestingEventInterface::OnDataFrame(
137 WebSocketMessageType type
,
138 const std::vector
<char>& data
) {
139 return CHANNEL_ALIVE
;
142 ChannelState
ConnectTestingEventInterface::OnFlowControl(int64 quota
) {
143 return CHANNEL_ALIVE
;
146 ChannelState
ConnectTestingEventInterface::OnClosingHandshake() {
147 return CHANNEL_ALIVE
;
150 ChannelState
ConnectTestingEventInterface::OnDropChannel(
153 const std::string
& reason
) {
154 return CHANNEL_DELETED
;
157 ChannelState
ConnectTestingEventInterface::OnFailChannel(
158 const std::string
& message
) {
160 failure_message_
= message
;
161 QuitNestedEventLoop();
162 return CHANNEL_DELETED
;
165 ChannelState
ConnectTestingEventInterface::OnStartOpeningHandshake(
166 scoped_ptr
<WebSocketHandshakeRequestInfo
> request
) {
167 return CHANNEL_ALIVE
;
170 ChannelState
ConnectTestingEventInterface::OnFinishOpeningHandshake(
171 scoped_ptr
<WebSocketHandshakeResponseInfo
> response
) {
172 return CHANNEL_ALIVE
;
175 ChannelState
ConnectTestingEventInterface::OnSSLCertificateError(
176 scoped_ptr
<SSLErrorCallbacks
> ssl_error_callbacks
,
178 const SSLInfo
& ssl_info
,
180 base::MessageLoop::current()->PostTask(
181 FROM_HERE
, base::Bind(&SSLErrorCallbacks::CancelSSLRequest
,
182 base::Owned(ssl_error_callbacks
.release()),
183 ERR_SSL_PROTOCOL_ERROR
, &ssl_info
));
184 return CHANNEL_ALIVE
;
187 void ConnectTestingEventInterface::QuitNestedEventLoop() {
191 // A subclass of TestNetworkDelegate that additionally implements the
192 // OnResolveProxy callback and records the information passed to it.
193 class TestNetworkDelegateWithProxyInfo
: public TestNetworkDelegate
{
195 TestNetworkDelegateWithProxyInfo() {}
197 struct ResolvedProxyInfo
{
199 ProxyInfo proxy_info
;
202 const ResolvedProxyInfo
& resolved_proxy_info() const {
203 return resolved_proxy_info_
;
207 void OnResolveProxy(const GURL
& url
,
209 const ProxyService
& proxy_service
,
210 ProxyInfo
* result
) override
{
211 resolved_proxy_info_
.url
= url
;
212 resolved_proxy_info_
.proxy_info
= *result
;
216 ResolvedProxyInfo resolved_proxy_info_
;
218 DISALLOW_COPY_AND_ASSIGN(TestNetworkDelegateWithProxyInfo
);
221 class WebSocketEndToEndTest
: public ::testing::Test
{
223 WebSocketEndToEndTest()
224 : event_interface_(),
225 network_delegate_(new TestNetworkDelegateWithProxyInfo
),
228 initialised_context_(false) {}
230 // Initialise the URLRequestContext. Normally done automatically by
231 // ConnectAndWait(). This method is for the use of tests that need the
232 // URLRequestContext initialised before calling ConnectAndWait().
233 void InitialiseContext() {
234 context_
.set_network_delegate(network_delegate_
.get());
236 initialised_context_
= true;
239 // Send the connect request to |socket_url| and wait for a response. Returns
240 // true if the handshake succeeded.
241 bool ConnectAndWait(const GURL
& socket_url
) {
242 if (!initialised_context_
) {
245 url::Origin
origin("http://localhost");
246 event_interface_
= new ConnectTestingEventInterface
;
248 new WebSocketChannel(make_scoped_ptr(event_interface_
), &context_
));
249 channel_
->SendAddChannelRequest(GURL(socket_url
), sub_protocols_
, origin
);
250 event_interface_
->WaitForResponse();
251 return !event_interface_
->failed();
254 ConnectTestingEventInterface
* event_interface_
; // owned by channel_
255 scoped_ptr
<TestNetworkDelegateWithProxyInfo
> network_delegate_
;
256 TestURLRequestContext context_
;
257 scoped_ptr
<WebSocketChannel
> channel_
;
258 std::vector
<std::string
> sub_protocols_
;
259 bool initialised_context_
;
262 // None of these tests work on Android.
263 // TODO(ricea): Make these tests work on Android. See crbug.com/441711.
264 #if defined(OS_ANDROID)
265 #define DISABLED_ON_ANDROID(test) DISABLED_##test
267 #define DISABLED_ON_ANDROID(test) test
270 // Basic test of connectivity. If this test fails, nothing else can be expected
272 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(BasicSmokeTest
)) {
273 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
274 SpawnedTestServer::kLocalhost
,
275 GetWebSocketTestDataDirectory());
276 ASSERT_TRUE(ws_server
.Start());
277 EXPECT_TRUE(ConnectAndWait(ws_server
.GetURL(kEchoServer
)));
280 // Test for issue crbug.com/433695 "Unencrypted WebSocket connection via
281 // authenticated proxy times out"
282 // TODO(ricea): Enable this when the issue is fixed.
283 TEST_F(WebSocketEndToEndTest
, DISABLED_HttpsProxyUnauthedFails
) {
284 SpawnedTestServer
proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY
,
285 SpawnedTestServer::kLocalhost
,
287 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
288 SpawnedTestServer::kLocalhost
,
289 GetWebSocketTestDataDirectory());
290 ASSERT_TRUE(proxy_server
.StartInBackground());
291 ASSERT_TRUE(ws_server
.StartInBackground());
292 ASSERT_TRUE(proxy_server
.BlockUntilStarted());
293 ASSERT_TRUE(ws_server
.BlockUntilStarted());
294 std::string proxy_config
=
295 "https=" + proxy_server
.host_port_pair().ToString();
296 scoped_ptr
<ProxyService
> proxy_service(
297 ProxyService::CreateFixed(proxy_config
));
298 ASSERT_TRUE(proxy_service
);
299 context_
.set_proxy_service(proxy_service
.get());
300 EXPECT_FALSE(ConnectAndWait(ws_server
.GetURL(kEchoServer
)));
301 EXPECT_EQ("Proxy authentication failed", event_interface_
->failure_message());
304 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(HttpsWssProxyUnauthedFails
)) {
305 SpawnedTestServer
proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY
,
306 SpawnedTestServer::kLocalhost
,
308 SpawnedTestServer
wss_server(SpawnedTestServer::TYPE_WSS
,
309 SpawnedTestServer::kLocalhost
,
310 GetWebSocketTestDataDirectory());
311 ASSERT_TRUE(proxy_server
.StartInBackground());
312 ASSERT_TRUE(wss_server
.StartInBackground());
313 ASSERT_TRUE(proxy_server
.BlockUntilStarted());
314 ASSERT_TRUE(wss_server
.BlockUntilStarted());
315 std::string proxy_config
=
316 "https=" + proxy_server
.host_port_pair().ToString();
317 scoped_ptr
<ProxyService
> proxy_service(
318 ProxyService::CreateFixed(proxy_config
));
319 ASSERT_TRUE(proxy_service
);
320 context_
.set_proxy_service(proxy_service
.get());
321 EXPECT_FALSE(ConnectAndWait(wss_server
.GetURL(kEchoServer
)));
322 EXPECT_EQ("Proxy authentication failed", event_interface_
->failure_message());
325 // Regression test for crbug/426736 "WebSocket connections not using configured
326 // system HTTPS Proxy".
327 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(HttpsProxyUsed
)) {
328 SpawnedTestServer
proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY
,
329 SpawnedTestServer::kLocalhost
,
331 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
332 SpawnedTestServer::kLocalhost
,
333 GetWebSocketTestDataDirectory());
334 ASSERT_TRUE(proxy_server
.StartInBackground());
335 ASSERT_TRUE(ws_server
.StartInBackground());
336 ASSERT_TRUE(proxy_server
.BlockUntilStarted());
337 ASSERT_TRUE(ws_server
.BlockUntilStarted());
338 std::string proxy_config
= "https=" +
339 proxy_server
.host_port_pair().ToString() + ";" +
340 "http=" + proxy_server
.host_port_pair().ToString();
341 scoped_ptr
<ProxyService
> proxy_service(
342 ProxyService::CreateFixed(proxy_config
));
343 context_
.set_proxy_service(proxy_service
.get());
346 // The test server doesn't have an unauthenticated proxy mode. WebSockets
347 // cannot provide auth information that isn't already cached, so it's
348 // necessary to preflight an HTTP request to authenticate against the proxy.
349 // It doesn't matter what the URL is, as long as it is an HTTP navigation.
351 ReplaceUrlScheme(ws_server
.GetURL("connect_check.html"), "http");
352 TestDelegate delegate
;
353 delegate
.set_credentials(
354 AuthCredentials(base::ASCIIToUTF16("foo"), base::ASCIIToUTF16("bar")));
356 scoped_ptr
<URLRequest
> request(
357 context_
.CreateRequest(http_page
, DEFAULT_PRIORITY
, &delegate
));
359 // TestDelegate exits the message loop when the request completes by
361 base::RunLoop().Run();
362 EXPECT_TRUE(delegate
.auth_required_called());
365 GURL ws_url
= ws_server
.GetURL(kEchoServer
);
366 EXPECT_TRUE(ConnectAndWait(ws_url
));
367 const TestNetworkDelegateWithProxyInfo::ResolvedProxyInfo
& info
=
368 network_delegate_
->resolved_proxy_info();
369 EXPECT_EQ(ws_url
, info
.url
);
370 EXPECT_TRUE(info
.proxy_info
.is_http());
373 // This is a regression test for crbug.com/408061 Crash in
374 // net::WebSocketBasicHandshakeStream::Upgrade.
375 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(TruncatedResponse
)) {
376 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
377 SpawnedTestServer::kLocalhost
,
378 GetWebSocketTestDataDirectory());
379 ASSERT_TRUE(ws_server
.Start());
382 GURL ws_url
= ws_server
.GetURL("truncated-headers");
383 EXPECT_FALSE(ConnectAndWait(ws_url
));
386 // Regression test for crbug.com/455215 "HSTS not applied to WebSocket"
387 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(HstsHttpsToWebSocket
)) {
388 SpawnedTestServer::SSLOptions
ssl_options(
389 SpawnedTestServer::SSLOptions::CERT_COMMON_NAME_IS_DOMAIN
);
390 SpawnedTestServer
https_server(
391 SpawnedTestServer::TYPE_HTTPS
, ssl_options
,
392 base::FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
393 SpawnedTestServer
wss_server(SpawnedTestServer::TYPE_WSS
, ssl_options
,
394 GetWebSocketTestDataDirectory());
396 ASSERT_TRUE(https_server
.StartInBackground());
397 ASSERT_TRUE(wss_server
.StartInBackground());
398 ASSERT_TRUE(https_server
.BlockUntilStarted());
399 ASSERT_TRUE(wss_server
.BlockUntilStarted());
401 // Set HSTS via https:
402 TestDelegate delegate
;
403 GURL https_page
= https_server
.GetURL("files/hsts-headers.html");
404 scoped_ptr
<URLRequest
> request(
405 context_
.CreateRequest(https_page
, DEFAULT_PRIORITY
, &delegate
));
407 // TestDelegate exits the message loop when the request completes.
408 base::RunLoop().Run();
409 EXPECT_TRUE(request
->status().is_success());
411 // Check HSTS with ws:
412 // Change the scheme from wss: to ws: to verify that it is switched back.
413 GURL ws_url
= ReplaceUrlScheme(wss_server
.GetURL(kEchoServer
), "ws");
414 EXPECT_TRUE(ConnectAndWait(ws_url
));
417 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(HstsWebSocketToHttps
)) {
418 SpawnedTestServer::SSLOptions
ssl_options(
419 SpawnedTestServer::SSLOptions::CERT_COMMON_NAME_IS_DOMAIN
);
420 SpawnedTestServer
https_server(
421 SpawnedTestServer::TYPE_HTTPS
, ssl_options
,
422 base::FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
423 SpawnedTestServer
wss_server(SpawnedTestServer::TYPE_WSS
, ssl_options
,
424 GetWebSocketTestDataDirectory());
425 ASSERT_TRUE(https_server
.StartInBackground());
426 ASSERT_TRUE(wss_server
.StartInBackground());
427 ASSERT_TRUE(https_server
.BlockUntilStarted());
428 ASSERT_TRUE(wss_server
.BlockUntilStarted());
431 GURL wss_url
= wss_server
.GetURL("set-hsts");
432 EXPECT_TRUE(ConnectAndWait(wss_url
));
435 TestDelegate delegate
;
437 ReplaceUrlScheme(https_server
.GetURL("files/simple.html"), "http");
438 scoped_ptr
<URLRequest
> request(
439 context_
.CreateRequest(http_page
, DEFAULT_PRIORITY
, &delegate
));
441 // TestDelegate exits the message loop when the request completes.
442 base::RunLoop().Run();
443 EXPECT_TRUE(request
->status().is_success());
444 EXPECT_TRUE(request
->url().SchemeIs("https"));
447 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(HstsWebSocketToWebSocket
)) {
448 SpawnedTestServer::SSLOptions
ssl_options(
449 SpawnedTestServer::SSLOptions::CERT_COMMON_NAME_IS_DOMAIN
);
450 SpawnedTestServer
wss_server(SpawnedTestServer::TYPE_WSS
, ssl_options
,
451 GetWebSocketTestDataDirectory());
452 ASSERT_TRUE(wss_server
.Start());
455 GURL wss_url
= wss_server
.GetURL("set-hsts");
456 EXPECT_TRUE(ConnectAndWait(wss_url
));
459 GURL ws_url
= ReplaceUrlScheme(wss_server
.GetURL(kEchoServer
), "ws");
460 EXPECT_TRUE(ConnectAndWait(ws_url
));
463 // Regression test for crbug.com/180504 "WebSocket handshake fails when HTTP
464 // headers have trailing LWS".
465 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(TrailingWhitespace
)) {
466 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
467 SpawnedTestServer::kLocalhost
,
468 GetWebSocketTestDataDirectory());
469 ASSERT_TRUE(ws_server
.Start());
471 GURL ws_url
= ws_server
.GetURL("trailing-whitespace");
472 sub_protocols_
.push_back("sip");
473 EXPECT_TRUE(ConnectAndWait(ws_url
));
474 EXPECT_EQ("sip", event_interface_
->selected_subprotocol());
477 // This is a regression test for crbug.com/169448 "WebSockets should support
478 // header continuations"
479 // TODO(ricea): HTTP continuation headers have been deprecated by RFC7230. If
480 // support for continuation headers is removed from Chrome, then this test will
481 // break and should be removed.
482 TEST_F(WebSocketEndToEndTest
, DISABLED_ON_ANDROID(HeaderContinuations
)) {
483 SpawnedTestServer
ws_server(SpawnedTestServer::TYPE_WS
,
484 SpawnedTestServer::kLocalhost
,
485 GetWebSocketTestDataDirectory());
486 ASSERT_TRUE(ws_server
.Start());
488 GURL ws_url
= ws_server
.GetURL("header-continuation");
490 EXPECT_TRUE(ConnectAndWait(ws_url
));
491 EXPECT_EQ("permessage-deflate; server_max_window_bits=10",
492 event_interface_
->extensions());