blob: fc42db9e0ac3d78cf1afbf7ef7647f4173d53a46 [file] [log] [blame]
ricea433bdab2015-01-26 07:25:371// 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.
4
5// End-to-end tests for WebSocket.
6//
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.
10
11#include <string>
12
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"
Adam Ricecb76ac62015-02-20 05:33:2519#include "base/strings/string_piece.h"
ricea433bdab2015-01-26 07:25:3720#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"
30
31namespace net {
32
33namespace {
34
35static const char kEchoServer[] = "echo-with-no-extension";
36
Adam Ricecb76ac62015-02-20 05:33:2537// Simplify changing URL schemes.
38GURL ReplaceUrlScheme(const GURL& in_url, const base::StringPiece& scheme) {
39 GURL::Replacements replacements;
40 replacements.SetSchemeStr(scheme);
41 return in_url.ReplaceComponents(replacements);
42}
43
ricea433bdab2015-01-26 07:25:3744// An implementation of WebSocketEventInterface that waits for and records the
45// results of the connect.
46class ConnectTestingEventInterface : public WebSocketEventInterface {
47 public:
48 ConnectTestingEventInterface();
49
50 void WaitForResponse();
51
52 bool failed() const { return failed_; }
53
54 // Only set if the handshake failed, otherwise empty.
55 std::string failure_message() const;
56
57 std::string selected_subprotocol() const;
58
59 std::string extensions() const;
60
61 // Implementation of WebSocketEventInterface.
62 ChannelState OnAddChannelResponse(bool fail,
63 const std::string& selected_subprotocol,
64 const std::string& extensions) override;
65
66 ChannelState OnDataFrame(bool fin,
67 WebSocketMessageType type,
68 const std::vector<char>& data) override;
69
70 ChannelState OnFlowControl(int64 quota) override;
71
72 ChannelState OnClosingHandshake() override;
73
74 ChannelState OnDropChannel(bool was_clean,
75 uint16 code,
76 const std::string& reason) override;
77
78 ChannelState OnFailChannel(const std::string& message) override;
79
80 ChannelState OnStartOpeningHandshake(
81 scoped_ptr<WebSocketHandshakeRequestInfo> request) override;
82
83 ChannelState OnFinishOpeningHandshake(
84 scoped_ptr<WebSocketHandshakeResponseInfo> response) override;
85
86 ChannelState OnSSLCertificateError(
87 scoped_ptr<SSLErrorCallbacks> ssl_error_callbacks,
88 const GURL& url,
89 const SSLInfo& ssl_info,
90 bool fatal) override;
91
92 private:
93 void QuitNestedEventLoop();
94
95 // failed_ is true if the handshake failed (ie. OnFailChannel was called).
96 bool failed_;
97 std::string selected_subprotocol_;
98 std::string extensions_;
99 std::string failure_message_;
100 base::RunLoop run_loop_;
101
102 DISALLOW_COPY_AND_ASSIGN(ConnectTestingEventInterface);
103};
104
105ConnectTestingEventInterface::ConnectTestingEventInterface() : failed_(true) {
106}
107
108void ConnectTestingEventInterface::WaitForResponse() {
109 run_loop_.Run();
110}
111
112std::string ConnectTestingEventInterface::failure_message() const {
113 return failure_message_;
114}
115
116std::string ConnectTestingEventInterface::selected_subprotocol() const {
117 return selected_subprotocol_;
118}
119
120std::string ConnectTestingEventInterface::extensions() const {
121 return extensions_;
122}
123
124// Make the function definitions below less verbose.
125typedef ConnectTestingEventInterface::ChannelState ChannelState;
126
127ChannelState ConnectTestingEventInterface::OnAddChannelResponse(
128 bool fail,
129 const std::string& selected_subprotocol,
130 const std::string& extensions) {
131 failed_ = fail;
132 selected_subprotocol_ = selected_subprotocol;
133 extensions_ = extensions;
134 QuitNestedEventLoop();
135 return fail ? CHANNEL_DELETED : CHANNEL_ALIVE;
136}
137
138ChannelState ConnectTestingEventInterface::OnDataFrame(
139 bool fin,
140 WebSocketMessageType type,
141 const std::vector<char>& data) {
142 return CHANNEL_ALIVE;
143}
144
145ChannelState ConnectTestingEventInterface::OnFlowControl(int64 quota) {
146 return CHANNEL_ALIVE;
147}
148
149ChannelState ConnectTestingEventInterface::OnClosingHandshake() {
150 return CHANNEL_ALIVE;
151}
152
153ChannelState ConnectTestingEventInterface::OnDropChannel(
154 bool was_clean,
155 uint16 code,
156 const std::string& reason) {
157 return CHANNEL_DELETED;
158}
159
160ChannelState ConnectTestingEventInterface::OnFailChannel(
161 const std::string& message) {
162 failed_ = true;
163 failure_message_ = message;
164 QuitNestedEventLoop();
165 return CHANNEL_DELETED;
166}
167
168ChannelState ConnectTestingEventInterface::OnStartOpeningHandshake(
169 scoped_ptr<WebSocketHandshakeRequestInfo> request) {
170 return CHANNEL_ALIVE;
171}
172
173ChannelState ConnectTestingEventInterface::OnFinishOpeningHandshake(
174 scoped_ptr<WebSocketHandshakeResponseInfo> response) {
175 return CHANNEL_ALIVE;
176}
177
178ChannelState ConnectTestingEventInterface::OnSSLCertificateError(
179 scoped_ptr<SSLErrorCallbacks> ssl_error_callbacks,
180 const GURL& url,
181 const SSLInfo& ssl_info,
182 bool fatal) {
183 base::MessageLoop::current()->PostTask(
184 FROM_HERE, base::Bind(&SSLErrorCallbacks::CancelSSLRequest,
185 base::Owned(ssl_error_callbacks.release()),
186 ERR_SSL_PROTOCOL_ERROR, &ssl_info));
187 return CHANNEL_ALIVE;
188}
189
190void ConnectTestingEventInterface::QuitNestedEventLoop() {
191 run_loop_.Quit();
192}
193
194// A subclass of TestNetworkDelegate that additionally implements the
195// OnResolveProxy callback and records the information passed to it.
196class TestNetworkDelegateWithProxyInfo : public TestNetworkDelegate {
197 public:
198 TestNetworkDelegateWithProxyInfo() {}
199
200 struct ResolvedProxyInfo {
201 GURL url;
202 ProxyInfo proxy_info;
203 };
204
205 const ResolvedProxyInfo& resolved_proxy_info() const {
206 return resolved_proxy_info_;
207 }
208
209 protected:
210 void OnResolveProxy(const GURL& url,
211 int load_flags,
212 const ProxyService& proxy_service,
213 ProxyInfo* result) override {
214 resolved_proxy_info_.url = url;
215 resolved_proxy_info_.proxy_info = *result;
216 }
217
218 private:
219 ResolvedProxyInfo resolved_proxy_info_;
220
221 DISALLOW_COPY_AND_ASSIGN(TestNetworkDelegateWithProxyInfo);
222};
223
224class WebSocketEndToEndTest : public ::testing::Test {
225 protected:
226 WebSocketEndToEndTest()
Adam Ricecb76ac62015-02-20 05:33:25227 : event_interface_(),
ricea433bdab2015-01-26 07:25:37228 network_delegate_(new TestNetworkDelegateWithProxyInfo),
229 context_(true),
Adam Ricecb76ac62015-02-20 05:33:25230 channel_(),
ricea433bdab2015-01-26 07:25:37231 initialised_context_(false) {}
232
233 // Initialise the URLRequestContext. Normally done automatically by
234 // ConnectAndWait(). This method is for the use of tests that need the
235 // URLRequestContext initialised before calling ConnectAndWait().
236 void InitialiseContext() {
237 context_.set_network_delegate(network_delegate_.get());
238 context_.Init();
239 initialised_context_ = true;
240 }
241
242 // Send the connect request to |socket_url| and wait for a response. Returns
243 // true if the handshake succeeded.
244 bool ConnectAndWait(const GURL& socket_url) {
245 if (!initialised_context_) {
246 InitialiseContext();
247 }
248 std::vector<std::string> sub_protocols;
249 url::Origin origin("http://localhost");
Adam Ricecb76ac62015-02-20 05:33:25250 event_interface_ = new ConnectTestingEventInterface;
251 channel_.reset(
252 new WebSocketChannel(make_scoped_ptr(event_interface_), &context_));
253 channel_->SendAddChannelRequest(GURL(socket_url), sub_protocols, origin);
ricea433bdab2015-01-26 07:25:37254 event_interface_->WaitForResponse();
255 return !event_interface_->failed();
256 }
257
258 ConnectTestingEventInterface* event_interface_; // owned by channel_
259 scoped_ptr<TestNetworkDelegateWithProxyInfo> network_delegate_;
260 TestURLRequestContext context_;
Adam Ricecb76ac62015-02-20 05:33:25261 scoped_ptr<WebSocketChannel> channel_;
ricea433bdab2015-01-26 07:25:37262 bool initialised_context_;
263};
264
265// None of these tests work on Android.
266// TODO(ricea): Make these tests work on Android. See crbug.com/441711.
267#if defined(OS_ANDROID)
268#define DISABLED_ON_ANDROID(test) DISABLED_##test
269#else
270#define DISABLED_ON_ANDROID(test) test
271#endif
272
273// Basic test of connectivity. If this test fails, nothing else can be expected
274// to work.
275TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(BasicSmokeTest)) {
276 SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
277 SpawnedTestServer::kLocalhost,
278 GetWebSocketTestDataDirectory());
279 ASSERT_TRUE(ws_server.Start());
280 EXPECT_TRUE(ConnectAndWait(ws_server.GetURL(kEchoServer)));
281}
282
283// Test for issue crbug.com/433695 "Unencrypted WebSocket connection via
284// authenticated proxy times out"
285// TODO(ricea): Enable this when the issue is fixed.
286TEST_F(WebSocketEndToEndTest, DISABLED_HttpsProxyUnauthedFails) {
287 SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
288 SpawnedTestServer::kLocalhost,
289 base::FilePath());
290 SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
291 SpawnedTestServer::kLocalhost,
292 GetWebSocketTestDataDirectory());
293 ASSERT_TRUE(proxy_server.StartInBackground());
294 ASSERT_TRUE(ws_server.StartInBackground());
295 ASSERT_TRUE(proxy_server.BlockUntilStarted());
296 ASSERT_TRUE(ws_server.BlockUntilStarted());
297 std::string proxy_config =
298 "https=" + proxy_server.host_port_pair().ToString();
299 scoped_ptr<ProxyService> proxy_service(
300 ProxyService::CreateFixed(proxy_config));
301 ASSERT_TRUE(proxy_service);
302 context_.set_proxy_service(proxy_service.get());
303 EXPECT_FALSE(ConnectAndWait(ws_server.GetURL(kEchoServer)));
304 EXPECT_EQ("Proxy authentication failed", event_interface_->failure_message());
305}
306
307TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(HttpsWssProxyUnauthedFails)) {
308 SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
309 SpawnedTestServer::kLocalhost,
310 base::FilePath());
311 SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS,
312 SpawnedTestServer::kLocalhost,
313 GetWebSocketTestDataDirectory());
314 ASSERT_TRUE(proxy_server.StartInBackground());
315 ASSERT_TRUE(wss_server.StartInBackground());
316 ASSERT_TRUE(proxy_server.BlockUntilStarted());
317 ASSERT_TRUE(wss_server.BlockUntilStarted());
318 std::string proxy_config =
319 "https=" + proxy_server.host_port_pair().ToString();
320 scoped_ptr<ProxyService> proxy_service(
321 ProxyService::CreateFixed(proxy_config));
322 ASSERT_TRUE(proxy_service);
323 context_.set_proxy_service(proxy_service.get());
324 EXPECT_FALSE(ConnectAndWait(wss_server.GetURL(kEchoServer)));
325 EXPECT_EQ("Proxy authentication failed", event_interface_->failure_message());
326}
327
328// Regression test for crbug/426736 "WebSocket connections not using configured
329// system HTTPS Proxy".
330TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(HttpsProxyUsed)) {
331 SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
332 SpawnedTestServer::kLocalhost,
333 base::FilePath());
334 SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
335 SpawnedTestServer::kLocalhost,
336 GetWebSocketTestDataDirectory());
337 ASSERT_TRUE(proxy_server.StartInBackground());
338 ASSERT_TRUE(ws_server.StartInBackground());
339 ASSERT_TRUE(proxy_server.BlockUntilStarted());
340 ASSERT_TRUE(ws_server.BlockUntilStarted());
341 std::string proxy_config = "https=" +
342 proxy_server.host_port_pair().ToString() + ";" +
343 "http=" + proxy_server.host_port_pair().ToString();
344 scoped_ptr<ProxyService> proxy_service(
345 ProxyService::CreateFixed(proxy_config));
346 context_.set_proxy_service(proxy_service.get());
347 InitialiseContext();
348
349 // The test server doesn't have an unauthenticated proxy mode. WebSockets
350 // cannot provide auth information that isn't already cached, so it's
351 // necessary to preflight an HTTP request to authenticate against the proxy.
ricea433bdab2015-01-26 07:25:37352 // It doesn't matter what the URL is, as long as it is an HTTP navigation.
353 GURL http_page =
Adam Ricecb76ac62015-02-20 05:33:25354 ReplaceUrlScheme(ws_server.GetURL("connect_check.html"), "http");
ricea433bdab2015-01-26 07:25:37355 TestDelegate delegate;
356 delegate.set_credentials(
357 AuthCredentials(base::ASCIIToUTF16("foo"), base::ASCIIToUTF16("bar")));
358 {
359 scoped_ptr<URLRequest> request(
360 context_.CreateRequest(http_page, DEFAULT_PRIORITY, &delegate, NULL));
361 request->Start();
362 // TestDelegate exits the message loop when the request completes by
363 // default.
364 base::RunLoop().Run();
365 EXPECT_TRUE(delegate.auth_required_called());
366 }
367
368 GURL ws_url = ws_server.GetURL(kEchoServer);
369 EXPECT_TRUE(ConnectAndWait(ws_url));
370 const TestNetworkDelegateWithProxyInfo::ResolvedProxyInfo& info =
371 network_delegate_->resolved_proxy_info();
372 EXPECT_EQ(ws_url, info.url);
373 EXPECT_TRUE(info.proxy_info.is_http());
374}
375
ricea23c3f942015-02-02 13:35:13376// This is a regression test for crbug.com/408061 Crash in
377// net::WebSocketBasicHandshakeStream::Upgrade.
378TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(TruncatedResponse)) {
379 SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
380 SpawnedTestServer::kLocalhost,
381 GetWebSocketTestDataDirectory());
382 ASSERT_TRUE(ws_server.Start());
383 InitialiseContext();
384
385 GURL ws_url = ws_server.GetURL("truncated-headers");
386 EXPECT_FALSE(ConnectAndWait(ws_url));
387}
388
Adam Ricecb76ac62015-02-20 05:33:25389// Regression test for crbug.com/455215 "HSTS not applied to WebSocket"
390TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(HstsHttpsToWebSocket)) {
391 SpawnedTestServer::SSLOptions ssl_options;
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());
397 ASSERT_TRUE(https_server.StartInBackground());
398 ASSERT_TRUE(wss_server.StartInBackground());
399 ASSERT_TRUE(https_server.BlockUntilStarted());
400 ASSERT_TRUE(wss_server.BlockUntilStarted());
401 InitialiseContext();
402 // Set HSTS via https:
403 TestDelegate delegate;
404 GURL https_page = https_server.GetURL("files/hsts-headers.html");
405 scoped_ptr<URLRequest> request(
406 context_.CreateRequest(https_page, DEFAULT_PRIORITY, &delegate, NULL));
407 request->Start();
408 // TestDelegate exits the message loop when the request completes.
409 base::RunLoop().Run();
410 EXPECT_TRUE(request->status().is_success());
411
412 // Check HSTS with ws:
413 // Change the scheme from wss: to ws: to verify that it is switched back.
414 GURL ws_url = ReplaceUrlScheme(wss_server.GetURL(kEchoServer), "ws");
415 EXPECT_TRUE(ConnectAndWait(ws_url));
416}
417
418TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(HstsWebSocketToHttps)) {
419 SpawnedTestServer::SSLOptions ssl_options;
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());
429 InitialiseContext();
430 // Set HSTS via wss:
431 GURL wss_url = wss_server.GetURL("set-hsts");
432 EXPECT_TRUE(ConnectAndWait(wss_url));
433
434 // Verify via http:
435 TestDelegate delegate;
436 GURL http_page =
437 ReplaceUrlScheme(https_server.GetURL("files/simple.html"), "http");
438 scoped_ptr<URLRequest> request(
439 context_.CreateRequest(http_page, DEFAULT_PRIORITY, &delegate, NULL));
440 request->Start();
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"));
445}
446
447TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(HstsWebSocketToWebSocket)) {
448 SpawnedTestServer::SSLOptions ssl_options;
449 SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS, ssl_options,
450 GetWebSocketTestDataDirectory());
451 ASSERT_TRUE(wss_server.Start());
452 InitialiseContext();
453 // Set HSTS via wss:
454 GURL wss_url = wss_server.GetURL("set-hsts");
455 EXPECT_TRUE(ConnectAndWait(wss_url));
456
457 // Verify via wss:
458 GURL ws_url = ReplaceUrlScheme(wss_server.GetURL(kEchoServer), "ws");
459 EXPECT_TRUE(ConnectAndWait(ws_url));
460}
461
ricea433bdab2015-01-26 07:25:37462} // namespace
463
464} // namespace net