OLD | NEW |
| (Empty) |
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. | |
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" | |
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" | |
29 | |
30 namespace net { | |
31 | |
32 namespace { | |
33 | |
34 static const char kEchoServer[] = "echo-with-no-extension"; | |
35 | |
36 // An implementation of WebSocketEventInterface that waits for and records the | |
37 // results of the connect. | |
38 class ConnectTestingEventInterface : public WebSocketEventInterface { | |
39 public: | |
40 ConnectTestingEventInterface(); | |
41 | |
42 void WaitForResponse(); | |
43 | |
44 bool failed() const { return failed_; } | |
45 | |
46 // Only set if the handshake failed, otherwise empty. | |
47 std::string failure_message() const; | |
48 | |
49 std::string selected_subprotocol() const; | |
50 | |
51 std::string extensions() const; | |
52 | |
53 // Implementation of WebSocketEventInterface. | |
54 ChannelState OnAddChannelResponse(bool fail, | |
55 const std::string& selected_subprotocol, | |
56 const std::string& extensions) override; | |
57 | |
58 ChannelState OnDataFrame(bool fin, | |
59 WebSocketMessageType type, | |
60 const std::vector<char>& data) override; | |
61 | |
62 ChannelState OnFlowControl(int64 quota) override; | |
63 | |
64 ChannelState OnClosingHandshake() override; | |
65 | |
66 ChannelState OnDropChannel(bool was_clean, | |
67 uint16 code, | |
68 const std::string& reason) override; | |
69 | |
70 ChannelState OnFailChannel(const std::string& message) override; | |
71 | |
72 ChannelState OnStartOpeningHandshake( | |
73 scoped_ptr<WebSocketHandshakeRequestInfo> request) override; | |
74 | |
75 ChannelState OnFinishOpeningHandshake( | |
76 scoped_ptr<WebSocketHandshakeResponseInfo> response) override; | |
77 | |
78 ChannelState OnSSLCertificateError( | |
79 scoped_ptr<SSLErrorCallbacks> ssl_error_callbacks, | |
80 const GURL& url, | |
81 const SSLInfo& ssl_info, | |
82 bool fatal) override; | |
83 | |
84 private: | |
85 void QuitNestedEventLoop(); | |
86 | |
87 // failed_ is true if the handshake failed (ie. OnFailChannel was called). | |
88 bool failed_; | |
89 std::string selected_subprotocol_; | |
90 std::string extensions_; | |
91 std::string failure_message_; | |
92 base::RunLoop run_loop_; | |
93 | |
94 DISALLOW_COPY_AND_ASSIGN(ConnectTestingEventInterface); | |
95 }; | |
96 | |
97 ConnectTestingEventInterface::ConnectTestingEventInterface() : failed_(true) { | |
98 } | |
99 | |
100 void ConnectTestingEventInterface::WaitForResponse() { | |
101 run_loop_.Run(); | |
102 } | |
103 | |
104 std::string ConnectTestingEventInterface::failure_message() const { | |
105 return failure_message_; | |
106 } | |
107 | |
108 std::string ConnectTestingEventInterface::selected_subprotocol() const { | |
109 return selected_subprotocol_; | |
110 } | |
111 | |
112 std::string ConnectTestingEventInterface::extensions() const { | |
113 return extensions_; | |
114 } | |
115 | |
116 // Make the function definitions below less verbose. | |
117 typedef ConnectTestingEventInterface::ChannelState ChannelState; | |
118 | |
119 ChannelState ConnectTestingEventInterface::OnAddChannelResponse( | |
120 bool fail, | |
121 const std::string& selected_subprotocol, | |
122 const std::string& extensions) { | |
123 failed_ = fail; | |
124 selected_subprotocol_ = selected_subprotocol; | |
125 extensions_ = extensions; | |
126 QuitNestedEventLoop(); | |
127 return fail ? CHANNEL_DELETED : CHANNEL_ALIVE; | |
128 } | |
129 | |
130 ChannelState ConnectTestingEventInterface::OnDataFrame( | |
131 bool fin, | |
132 WebSocketMessageType type, | |
133 const std::vector<char>& data) { | |
134 return CHANNEL_ALIVE; | |
135 } | |
136 | |
137 ChannelState ConnectTestingEventInterface::OnFlowControl(int64 quota) { | |
138 return CHANNEL_ALIVE; | |
139 } | |
140 | |
141 ChannelState ConnectTestingEventInterface::OnClosingHandshake() { | |
142 return CHANNEL_ALIVE; | |
143 } | |
144 | |
145 ChannelState ConnectTestingEventInterface::OnDropChannel( | |
146 bool was_clean, | |
147 uint16 code, | |
148 const std::string& reason) { | |
149 return CHANNEL_DELETED; | |
150 } | |
151 | |
152 ChannelState ConnectTestingEventInterface::OnFailChannel( | |
153 const std::string& message) { | |
154 failed_ = true; | |
155 failure_message_ = message; | |
156 QuitNestedEventLoop(); | |
157 return CHANNEL_DELETED; | |
158 } | |
159 | |
160 ChannelState ConnectTestingEventInterface::OnStartOpeningHandshake( | |
161 scoped_ptr<WebSocketHandshakeRequestInfo> request) { | |
162 return CHANNEL_ALIVE; | |
163 } | |
164 | |
165 ChannelState ConnectTestingEventInterface::OnFinishOpeningHandshake( | |
166 scoped_ptr<WebSocketHandshakeResponseInfo> response) { | |
167 return CHANNEL_ALIVE; | |
168 } | |
169 | |
170 ChannelState ConnectTestingEventInterface::OnSSLCertificateError( | |
171 scoped_ptr<SSLErrorCallbacks> ssl_error_callbacks, | |
172 const GURL& url, | |
173 const SSLInfo& ssl_info, | |
174 bool fatal) { | |
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; | |
180 } | |
181 | |
182 void ConnectTestingEventInterface::QuitNestedEventLoop() { | |
183 run_loop_.Quit(); | |
184 } | |
185 | |
186 // A subclass of TestNetworkDelegate that additionally implements the | |
187 // OnResolveProxy callback and records the information passed to it. | |
188 class TestNetworkDelegateWithProxyInfo : public TestNetworkDelegate { | |
189 public: | |
190 TestNetworkDelegateWithProxyInfo() {} | |
191 | |
192 struct ResolvedProxyInfo { | |
193 GURL url; | |
194 ProxyInfo proxy_info; | |
195 }; | |
196 | |
197 const ResolvedProxyInfo& resolved_proxy_info() const { | |
198 return resolved_proxy_info_; | |
199 } | |
200 | |
201 protected: | |
202 void OnResolveProxy(const GURL& url, | |
203 int load_flags, | |
204 const ProxyService& proxy_service, | |
205 ProxyInfo* result) override { | |
206 resolved_proxy_info_.url = url; | |
207 resolved_proxy_info_.proxy_info = *result; | |
208 } | |
209 | |
210 private: | |
211 ResolvedProxyInfo resolved_proxy_info_; | |
212 | |
213 DISALLOW_COPY_AND_ASSIGN(TestNetworkDelegateWithProxyInfo); | |
214 }; | |
215 | |
216 class WebSocketEndToEndTest : public ::testing::Test { | |
217 protected: | |
218 WebSocketEndToEndTest() | |
219 : event_interface_(new ConnectTestingEventInterface), | |
220 network_delegate_(new TestNetworkDelegateWithProxyInfo), | |
221 context_(true), | |
222 channel_(make_scoped_ptr(event_interface_), &context_), | |
223 initialised_context_(false) {} | |
224 | |
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()); | |
230 context_.Init(); | |
231 initialised_context_ = true; | |
232 } | |
233 | |
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_) { | |
238 InitialiseContext(); | |
239 } | |
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(); | |
245 } | |
246 | |
247 ConnectTestingEventInterface* event_interface_; // owned by channel_ | |
248 scoped_ptr<TestNetworkDelegateWithProxyInfo> network_delegate_; | |
249 TestURLRequestContext context_; | |
250 WebSocketChannel channel_; | |
251 bool initialised_context_; | |
252 }; | |
253 | |
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 | |
258 #else | |
259 #define DISABLED_ON_ANDROID(test) test | |
260 #endif | |
261 | |
262 // Basic test of connectivity. If this test fails, nothing else can be expected | |
263 // to work. | |
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))); | |
270 } | |
271 | |
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, | |
278 base::FilePath()); | |
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()); | |
294 } | |
295 | |
296 TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(HttpsWssProxyUnauthedFails)) { | |
297 SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY, | |
298 SpawnedTestServer::kLocalhost, | |
299 base::FilePath()); | |
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()); | |
315 } | |
316 | |
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, | |
322 base::FilePath()); | |
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()); | |
336 InitialiseContext(); | |
337 | |
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. | |
344 GURL http_page = | |
345 ws_server.GetURL("connect_check.html").ReplaceComponents(replacements); | |
346 TestDelegate delegate; | |
347 delegate.set_credentials( | |
348 AuthCredentials(base::ASCIIToUTF16("foo"), base::ASCIIToUTF16("bar"))); | |
349 { | |
350 scoped_ptr<URLRequest> request( | |
351 context_.CreateRequest(http_page, DEFAULT_PRIORITY, &delegate, NULL)); | |
352 request->Start(); | |
353 // TestDelegate exits the message loop when the request completes by | |
354 // default. | |
355 base::RunLoop().Run(); | |
356 EXPECT_TRUE(delegate.auth_required_called()); | |
357 } | |
358 | |
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()); | |
365 } | |
366 | |
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()); | |
374 InitialiseContext(); | |
375 | |
376 GURL ws_url = ws_server.GetURL("truncated-headers"); | |
377 EXPECT_FALSE(ConnectAndWait(ws_url)); | |
378 } | |
379 | |
380 } // namespace | |
381 | |
382 } // namespace net | |
OLD | NEW |