OLD | NEW |
| (Empty) |
1 // Copyright 2013 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 #include "net/websockets/websocket_stream.h" | |
6 | |
7 #include <algorithm> | |
8 #include <string> | |
9 #include <utility> | |
10 #include <vector> | |
11 | |
12 #include "base/compiler_specific.h" | |
13 #include "base/memory/scoped_vector.h" | |
14 #include "base/metrics/histogram.h" | |
15 #include "base/metrics/histogram_samples.h" | |
16 #include "base/metrics/statistics_recorder.h" | |
17 #include "base/run_loop.h" | |
18 #include "base/strings/stringprintf.h" | |
19 #include "base/timer/mock_timer.h" | |
20 #include "base/timer/timer.h" | |
21 #include "net/base/net_errors.h" | |
22 #include "net/base/test_data_directory.h" | |
23 #include "net/http/http_request_headers.h" | |
24 #include "net/http/http_response_headers.h" | |
25 #include "net/proxy/proxy_service.h" | |
26 #include "net/socket/client_socket_handle.h" | |
27 #include "net/socket/socket_test_util.h" | |
28 #include "net/test/cert_test_util.h" | |
29 #include "net/url_request/url_request_test_util.h" | |
30 #include "net/websockets/websocket_basic_handshake_stream.h" | |
31 #include "net/websockets/websocket_frame.h" | |
32 #include "net/websockets/websocket_stream_create_test_base.h" | |
33 #include "net/websockets/websocket_test_util.h" | |
34 #include "testing/gtest/include/gtest/gtest.h" | |
35 #include "url/gurl.h" | |
36 #include "url/origin.h" | |
37 | |
38 namespace net { | |
39 namespace { | |
40 | |
41 // Simple builder for a DeterministicSocketData object to save repetitive code. | |
42 // It always sets the connect data to MockConnect(SYNCHRONOUS, OK), so it cannot | |
43 // be used in tests where the connect fails. In practice, those tests never have | |
44 // any read/write data and so can't benefit from it anyway. The arrays are not | |
45 // copied. It is up to the caller to ensure they stay in scope until the test | |
46 // ends. | |
47 template <size_t reads_count, size_t writes_count> | |
48 scoped_ptr<DeterministicSocketData> BuildSocketData( | |
49 MockRead (&reads)[reads_count], | |
50 MockWrite (&writes)[writes_count]) { | |
51 scoped_ptr<DeterministicSocketData> socket_data( | |
52 new DeterministicSocketData(reads, reads_count, writes, writes_count)); | |
53 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); | |
54 socket_data->SetStop(reads_count + writes_count); | |
55 return socket_data.Pass(); | |
56 } | |
57 | |
58 // Builder for a DeterministicSocketData that expects nothing. This does not | |
59 // set the connect data, so the calling code must do that explicitly. | |
60 scoped_ptr<DeterministicSocketData> BuildNullSocketData() { | |
61 return make_scoped_ptr(new DeterministicSocketData(NULL, 0, NULL, 0)); | |
62 } | |
63 | |
64 class MockWeakTimer : public base::MockTimer, | |
65 public base::SupportsWeakPtr<MockWeakTimer> { | |
66 public: | |
67 MockWeakTimer(bool retain_user_task, bool is_repeating) | |
68 : MockTimer(retain_user_task, is_repeating) {} | |
69 }; | |
70 | |
71 class WebSocketStreamCreateTest : public ::testing::Test, | |
72 public WebSocketStreamCreateTestBase { | |
73 public: | |
74 ~WebSocketStreamCreateTest() override { | |
75 // Permit any endpoint locks to be released. | |
76 stream_request_.reset(); | |
77 stream_.reset(); | |
78 base::RunLoop().RunUntilIdle(); | |
79 } | |
80 | |
81 void CreateAndConnectCustomResponse( | |
82 const std::string& socket_url, | |
83 const std::string& socket_host, | |
84 const std::string& socket_path, | |
85 const std::vector<std::string>& sub_protocols, | |
86 const std::string& origin, | |
87 const std::string& extra_request_headers, | |
88 const std::string& response_body, | |
89 scoped_ptr<base::Timer> timer = scoped_ptr<base::Timer>()) { | |
90 url_request_context_host_.SetExpectations( | |
91 WebSocketStandardRequest(socket_path, socket_host, origin, | |
92 extra_request_headers), | |
93 response_body); | |
94 CreateAndConnectStream(socket_url, sub_protocols, origin, timer.Pass()); | |
95 } | |
96 | |
97 // |extra_request_headers| and |extra_response_headers| must end in "\r\n" or | |
98 // errors like "Unable to perform synchronous IO while stopped" will occur. | |
99 void CreateAndConnectStandard( | |
100 const std::string& socket_url, | |
101 const std::string& socket_host, | |
102 const std::string& socket_path, | |
103 const std::vector<std::string>& sub_protocols, | |
104 const std::string& origin, | |
105 const std::string& extra_request_headers, | |
106 const std::string& extra_response_headers, | |
107 scoped_ptr<base::Timer> timer = scoped_ptr<base::Timer>()) { | |
108 CreateAndConnectCustomResponse( | |
109 socket_url, socket_host, socket_path, sub_protocols, origin, | |
110 extra_request_headers, | |
111 WebSocketStandardResponse(extra_response_headers), timer.Pass()); | |
112 } | |
113 | |
114 void CreateAndConnectRawExpectations( | |
115 const std::string& socket_url, | |
116 const std::vector<std::string>& sub_protocols, | |
117 const std::string& origin, | |
118 scoped_ptr<DeterministicSocketData> socket_data, | |
119 scoped_ptr<base::Timer> timer = scoped_ptr<base::Timer>()) { | |
120 AddRawExpectations(socket_data.Pass()); | |
121 CreateAndConnectStream(socket_url, sub_protocols, origin, timer.Pass()); | |
122 } | |
123 | |
124 // Add additional raw expectations for sockets created before the final one. | |
125 void AddRawExpectations(scoped_ptr<DeterministicSocketData> socket_data) { | |
126 url_request_context_host_.AddRawExpectations(socket_data.Pass()); | |
127 } | |
128 }; | |
129 | |
130 // There are enough tests of the Sec-WebSocket-Extensions header that they | |
131 // deserve their own test fixture. | |
132 class WebSocketStreamCreateExtensionTest : public WebSocketStreamCreateTest { | |
133 public: | |
134 // Performs a standard connect, with the value of the Sec-WebSocket-Extensions | |
135 // header in the response set to |extensions_header_value|. Runs the event | |
136 // loop to allow the connect to complete. | |
137 void CreateAndConnectWithExtensions( | |
138 const std::string& extensions_header_value) { | |
139 CreateAndConnectStandard( | |
140 "ws://localhost/testing_path", "localhost", "/testing_path", | |
141 NoSubProtocols(), "http://localhost", "", | |
142 "Sec-WebSocket-Extensions: " + extensions_header_value + "\r\n"); | |
143 WaitUntilConnectDone(); | |
144 } | |
145 }; | |
146 | |
147 // Common code to construct expectations for authentication tests that receive | |
148 // the auth challenge on one connection and then create a second connection to | |
149 // send the authenticated request on. | |
150 class CommonAuthTestHelper { | |
151 public: | |
152 CommonAuthTestHelper() : reads1_(), writes1_(), reads2_(), writes2_() {} | |
153 | |
154 scoped_ptr<DeterministicSocketData> BuildSocketData1( | |
155 const std::string& response) { | |
156 request1_ = | |
157 WebSocketStandardRequest("/", "localhost", "http://localhost", ""); | |
158 writes1_[0] = MockWrite(SYNCHRONOUS, 0, request1_.c_str()); | |
159 response1_ = response; | |
160 reads1_[0] = MockRead(SYNCHRONOUS, 1, response1_.c_str()); | |
161 reads1_[1] = MockRead(SYNCHRONOUS, OK, 2); // Close connection | |
162 | |
163 return BuildSocketData(reads1_, writes1_); | |
164 } | |
165 | |
166 scoped_ptr<DeterministicSocketData> BuildSocketData2( | |
167 const std::string& request, | |
168 const std::string& response) { | |
169 request2_ = request; | |
170 response2_ = response; | |
171 writes2_[0] = MockWrite(SYNCHRONOUS, 0, request2_.c_str()); | |
172 reads2_[0] = MockRead(SYNCHRONOUS, 1, response2_.c_str()); | |
173 return BuildSocketData(reads2_, writes2_); | |
174 } | |
175 | |
176 private: | |
177 // These need to be object-scoped since they have to remain valid until all | |
178 // socket operations in the test are complete. | |
179 std::string request1_; | |
180 std::string request2_; | |
181 std::string response1_; | |
182 std::string response2_; | |
183 MockRead reads1_[2]; | |
184 MockWrite writes1_[1]; | |
185 MockRead reads2_[1]; | |
186 MockWrite writes2_[1]; | |
187 | |
188 DISALLOW_COPY_AND_ASSIGN(CommonAuthTestHelper); | |
189 }; | |
190 | |
191 // Data and methods for BasicAuth tests. | |
192 class WebSocketStreamCreateBasicAuthTest : public WebSocketStreamCreateTest { | |
193 protected: | |
194 void CreateAndConnectAuthHandshake(const std::string& url, | |
195 const std::string& base64_user_pass, | |
196 const std::string& response2) { | |
197 AddRawExpectations(helper_.BuildSocketData1(kUnauthorizedResponse)); | |
198 | |
199 static const char request2format[] = | |
200 "GET / HTTP/1.1\r\n" | |
201 "Host: localhost\r\n" | |
202 "Connection: Upgrade\r\n" | |
203 "Pragma: no-cache\r\n" | |
204 "Cache-Control: no-cache\r\n" | |
205 "Authorization: Basic %s\r\n" | |
206 "Upgrade: websocket\r\n" | |
207 "Origin: http://localhost\r\n" | |
208 "Sec-WebSocket-Version: 13\r\n" | |
209 "User-Agent:\r\n" | |
210 "Accept-Encoding: gzip, deflate\r\n" | |
211 "Accept-Language: en-us,fr\r\n" | |
212 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" | |
213 "Sec-WebSocket-Extensions: permessage-deflate; " | |
214 "client_max_window_bits\r\n" | |
215 "\r\n"; | |
216 const std::string request = | |
217 base::StringPrintf(request2format, base64_user_pass.c_str()); | |
218 CreateAndConnectRawExpectations( | |
219 url, | |
220 NoSubProtocols(), | |
221 "http://localhost", | |
222 helper_.BuildSocketData2(request, response2)); | |
223 } | |
224 | |
225 static const char kUnauthorizedResponse[]; | |
226 | |
227 CommonAuthTestHelper helper_; | |
228 }; | |
229 | |
230 class WebSocketStreamCreateDigestAuthTest : public WebSocketStreamCreateTest { | |
231 protected: | |
232 static const char kUnauthorizedResponse[]; | |
233 static const char kAuthorizedRequest[]; | |
234 | |
235 CommonAuthTestHelper helper_; | |
236 }; | |
237 | |
238 const char WebSocketStreamCreateBasicAuthTest::kUnauthorizedResponse[] = | |
239 "HTTP/1.1 401 Unauthorized\r\n" | |
240 "Content-Length: 0\r\n" | |
241 "WWW-Authenticate: Basic realm=\"camelot\"\r\n" | |
242 "\r\n"; | |
243 | |
244 // These negotiation values are borrowed from | |
245 // http_auth_handler_digest_unittest.cc. Feel free to come up with new ones if | |
246 // you are bored. Only the weakest (no qop) variants of Digest authentication | |
247 // can be tested by this method, because the others involve random input. | |
248 const char WebSocketStreamCreateDigestAuthTest::kUnauthorizedResponse[] = | |
249 "HTTP/1.1 401 Unauthorized\r\n" | |
250 "Content-Length: 0\r\n" | |
251 "WWW-Authenticate: Digest realm=\"Oblivion\", nonce=\"nonce-value\"\r\n" | |
252 "\r\n"; | |
253 | |
254 const char WebSocketStreamCreateDigestAuthTest::kAuthorizedRequest[] = | |
255 "GET / HTTP/1.1\r\n" | |
256 "Host: localhost\r\n" | |
257 "Connection: Upgrade\r\n" | |
258 "Pragma: no-cache\r\n" | |
259 "Cache-Control: no-cache\r\n" | |
260 "Authorization: Digest username=\"FooBar\", realm=\"Oblivion\", " | |
261 "nonce=\"nonce-value\", uri=\"/\", " | |
262 "response=\"f72ff54ebde2f928860f806ec04acd1b\"\r\n" | |
263 "Upgrade: websocket\r\n" | |
264 "Origin: http://localhost\r\n" | |
265 "Sec-WebSocket-Version: 13\r\n" | |
266 "User-Agent:\r\n" | |
267 "Accept-Encoding: gzip, deflate\r\n" | |
268 "Accept-Language: en-us,fr\r\n" | |
269 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" | |
270 "Sec-WebSocket-Extensions: permessage-deflate; " | |
271 "client_max_window_bits\r\n" | |
272 "\r\n"; | |
273 | |
274 class WebSocketStreamCreateUMATest : public ::testing::Test { | |
275 public: | |
276 // This enum should match with the enum in Delegate in websocket_stream.cc. | |
277 enum HandshakeResult { | |
278 INCOMPLETE, | |
279 CONNECTED, | |
280 FAILED, | |
281 NUM_HANDSHAKE_RESULT_TYPES, | |
282 }; | |
283 | |
284 class StreamCreation : public WebSocketStreamCreateTest { | |
285 void TestBody() override {} | |
286 }; | |
287 | |
288 scoped_ptr<base::HistogramSamples> GetSamples(const std::string& name) { | |
289 base::HistogramBase* histogram = | |
290 base::StatisticsRecorder::FindHistogram(name); | |
291 return histogram ? histogram->SnapshotSamples() | |
292 : scoped_ptr<base::HistogramSamples>(); | |
293 } | |
294 }; | |
295 | |
296 // Confirm that the basic case works as expected. | |
297 TEST_F(WebSocketStreamCreateTest, SimpleSuccess) { | |
298 CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
299 NoSubProtocols(), "http://localhost", "", ""); | |
300 EXPECT_FALSE(request_info_); | |
301 EXPECT_FALSE(response_info_); | |
302 WaitUntilConnectDone(); | |
303 EXPECT_FALSE(has_failed()); | |
304 EXPECT_TRUE(stream_); | |
305 EXPECT_TRUE(request_info_); | |
306 EXPECT_TRUE(response_info_); | |
307 } | |
308 | |
309 TEST_F(WebSocketStreamCreateTest, HandshakeInfo) { | |
310 static const char kResponse[] = | |
311 "HTTP/1.1 101 Switching Protocols\r\n" | |
312 "Upgrade: websocket\r\n" | |
313 "Connection: Upgrade\r\n" | |
314 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
315 "foo: bar, baz\r\n" | |
316 "hoge: fuga\r\n" | |
317 "hoge: piyo\r\n" | |
318 "\r\n"; | |
319 | |
320 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
321 NoSubProtocols(), "http://localhost", "", | |
322 kResponse); | |
323 EXPECT_FALSE(request_info_); | |
324 EXPECT_FALSE(response_info_); | |
325 WaitUntilConnectDone(); | |
326 EXPECT_TRUE(stream_); | |
327 ASSERT_TRUE(request_info_); | |
328 ASSERT_TRUE(response_info_); | |
329 std::vector<HeaderKeyValuePair> request_headers = | |
330 RequestHeadersToVector(request_info_->headers); | |
331 // We examine the contents of request_info_ and response_info_ | |
332 // mainly only in this test case. | |
333 EXPECT_EQ(GURL("ws://localhost/"), request_info_->url); | |
334 EXPECT_EQ(GURL("ws://localhost/"), response_info_->url); | |
335 EXPECT_EQ(101, response_info_->status_code); | |
336 EXPECT_EQ("Switching Protocols", response_info_->status_text); | |
337 ASSERT_EQ(12u, request_headers.size()); | |
338 EXPECT_EQ(HeaderKeyValuePair("Host", "localhost"), request_headers[0]); | |
339 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), request_headers[1]); | |
340 EXPECT_EQ(HeaderKeyValuePair("Pragma", "no-cache"), request_headers[2]); | |
341 EXPECT_EQ(HeaderKeyValuePair("Cache-Control", "no-cache"), | |
342 request_headers[3]); | |
343 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), request_headers[4]); | |
344 EXPECT_EQ(HeaderKeyValuePair("Origin", "http://localhost"), | |
345 request_headers[5]); | |
346 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Version", "13"), | |
347 request_headers[6]); | |
348 EXPECT_EQ(HeaderKeyValuePair("User-Agent", ""), request_headers[7]); | |
349 EXPECT_EQ(HeaderKeyValuePair("Accept-Encoding", "gzip, deflate"), | |
350 request_headers[8]); | |
351 EXPECT_EQ(HeaderKeyValuePair("Accept-Language", "en-us,fr"), | |
352 request_headers[9]); | |
353 EXPECT_EQ("Sec-WebSocket-Key", request_headers[10].first); | |
354 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Extensions", | |
355 "permessage-deflate; client_max_window_bits"), | |
356 request_headers[11]); | |
357 | |
358 std::vector<HeaderKeyValuePair> response_headers = | |
359 ResponseHeadersToVector(*response_info_->headers.get()); | |
360 ASSERT_EQ(6u, response_headers.size()); | |
361 // Sort the headers for ease of verification. | |
362 std::sort(response_headers.begin(), response_headers.end()); | |
363 | |
364 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), response_headers[0]); | |
365 EXPECT_EQ("Sec-WebSocket-Accept", response_headers[1].first); | |
366 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), response_headers[2]); | |
367 EXPECT_EQ(HeaderKeyValuePair("foo", "bar, baz"), response_headers[3]); | |
368 EXPECT_EQ(HeaderKeyValuePair("hoge", "fuga"), response_headers[4]); | |
369 EXPECT_EQ(HeaderKeyValuePair("hoge", "piyo"), response_headers[5]); | |
370 } | |
371 | |
372 // Confirm that the stream isn't established until the message loop runs. | |
373 TEST_F(WebSocketStreamCreateTest, NeedsToRunLoop) { | |
374 CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
375 NoSubProtocols(), "http://localhost", "", ""); | |
376 EXPECT_FALSE(has_failed()); | |
377 EXPECT_FALSE(stream_); | |
378 } | |
379 | |
380 // Check the path is used. | |
381 TEST_F(WebSocketStreamCreateTest, PathIsUsed) { | |
382 CreateAndConnectStandard("ws://localhost/testing_path", "localhost", | |
383 "/testing_path", NoSubProtocols(), | |
384 "http://localhost", "", ""); | |
385 WaitUntilConnectDone(); | |
386 EXPECT_FALSE(has_failed()); | |
387 EXPECT_TRUE(stream_); | |
388 } | |
389 | |
390 // Check that the origin is used. | |
391 TEST_F(WebSocketStreamCreateTest, OriginIsUsed) { | |
392 CreateAndConnectStandard("ws://localhost/testing_path", "localhost", | |
393 "/testing_path", NoSubProtocols(), | |
394 "http://google.com", "", ""); | |
395 WaitUntilConnectDone(); | |
396 EXPECT_FALSE(has_failed()); | |
397 EXPECT_TRUE(stream_); | |
398 } | |
399 | |
400 // Check that sub-protocols are sent and parsed. | |
401 TEST_F(WebSocketStreamCreateTest, SubProtocolIsUsed) { | |
402 std::vector<std::string> sub_protocols; | |
403 sub_protocols.push_back("chatv11.chromium.org"); | |
404 sub_protocols.push_back("chatv20.chromium.org"); | |
405 CreateAndConnectStandard("ws://localhost/testing_path", "localhost", | |
406 "/testing_path", sub_protocols, "http://google.com", | |
407 "Sec-WebSocket-Protocol: chatv11.chromium.org, " | |
408 "chatv20.chromium.org\r\n", | |
409 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n"); | |
410 WaitUntilConnectDone(); | |
411 EXPECT_TRUE(stream_); | |
412 EXPECT_FALSE(has_failed()); | |
413 EXPECT_EQ("chatv20.chromium.org", stream_->GetSubProtocol()); | |
414 } | |
415 | |
416 // Unsolicited sub-protocols are rejected. | |
417 TEST_F(WebSocketStreamCreateTest, UnsolicitedSubProtocol) { | |
418 CreateAndConnectStandard("ws://localhost/testing_path", "localhost", | |
419 "/testing_path", NoSubProtocols(), | |
420 "http://google.com", "", | |
421 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n"); | |
422 WaitUntilConnectDone(); | |
423 EXPECT_FALSE(stream_); | |
424 EXPECT_TRUE(has_failed()); | |
425 EXPECT_EQ("Error during WebSocket handshake: " | |
426 "Response must not include 'Sec-WebSocket-Protocol' header " | |
427 "if not present in request: chatv20.chromium.org", | |
428 failure_message()); | |
429 } | |
430 | |
431 // Missing sub-protocol response is rejected. | |
432 TEST_F(WebSocketStreamCreateTest, UnacceptedSubProtocol) { | |
433 std::vector<std::string> sub_protocols; | |
434 sub_protocols.push_back("chat.example.com"); | |
435 CreateAndConnectStandard("ws://localhost/testing_path", "localhost", | |
436 "/testing_path", sub_protocols, "http://localhost", | |
437 "Sec-WebSocket-Protocol: chat.example.com\r\n", ""); | |
438 WaitUntilConnectDone(); | |
439 EXPECT_FALSE(stream_); | |
440 EXPECT_TRUE(has_failed()); | |
441 EXPECT_EQ("Error during WebSocket handshake: " | |
442 "Sent non-empty 'Sec-WebSocket-Protocol' header " | |
443 "but no response was received", | |
444 failure_message()); | |
445 } | |
446 | |
447 // Only one sub-protocol can be accepted. | |
448 TEST_F(WebSocketStreamCreateTest, MultipleSubProtocolsInResponse) { | |
449 std::vector<std::string> sub_protocols; | |
450 sub_protocols.push_back("chatv11.chromium.org"); | |
451 sub_protocols.push_back("chatv20.chromium.org"); | |
452 CreateAndConnectStandard("ws://localhost/testing_path", "localhost", | |
453 "/testing_path", sub_protocols, "http://google.com", | |
454 "Sec-WebSocket-Protocol: chatv11.chromium.org, " | |
455 "chatv20.chromium.org\r\n", | |
456 "Sec-WebSocket-Protocol: chatv11.chromium.org, " | |
457 "chatv20.chromium.org\r\n"); | |
458 WaitUntilConnectDone(); | |
459 EXPECT_FALSE(stream_); | |
460 EXPECT_TRUE(has_failed()); | |
461 EXPECT_EQ("Error during WebSocket handshake: " | |
462 "'Sec-WebSocket-Protocol' header must not appear " | |
463 "more than once in a response", | |
464 failure_message()); | |
465 } | |
466 | |
467 // Unmatched sub-protocol should be rejected. | |
468 TEST_F(WebSocketStreamCreateTest, UnmatchedSubProtocolInResponse) { | |
469 std::vector<std::string> sub_protocols; | |
470 sub_protocols.push_back("chatv11.chromium.org"); | |
471 sub_protocols.push_back("chatv20.chromium.org"); | |
472 CreateAndConnectStandard("ws://localhost/testing_path", "localhost", | |
473 "/testing_path", sub_protocols, "http://google.com", | |
474 "Sec-WebSocket-Protocol: chatv11.chromium.org, " | |
475 "chatv20.chromium.org\r\n", | |
476 "Sec-WebSocket-Protocol: chatv21.chromium.org\r\n"); | |
477 WaitUntilConnectDone(); | |
478 EXPECT_FALSE(stream_); | |
479 EXPECT_TRUE(has_failed()); | |
480 EXPECT_EQ("Error during WebSocket handshake: " | |
481 "'Sec-WebSocket-Protocol' header value 'chatv21.chromium.org' " | |
482 "in response does not match any of sent values", | |
483 failure_message()); | |
484 } | |
485 | |
486 // permessage-deflate extension basic success case. | |
487 TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateSuccess) { | |
488 CreateAndConnectWithExtensions("permessage-deflate"); | |
489 EXPECT_TRUE(stream_); | |
490 EXPECT_FALSE(has_failed()); | |
491 } | |
492 | |
493 // permessage-deflate extensions success with all parameters. | |
494 TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateParamsSuccess) { | |
495 CreateAndConnectWithExtensions( | |
496 "permessage-deflate; client_no_context_takeover; " | |
497 "server_max_window_bits=11; client_max_window_bits=13; " | |
498 "server_no_context_takeover"); | |
499 EXPECT_TRUE(stream_); | |
500 EXPECT_FALSE(has_failed()); | |
501 } | |
502 | |
503 // Verify that incoming messages are actually decompressed with | |
504 // permessage-deflate enabled. | |
505 TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateInflates) { | |
506 CreateAndConnectCustomResponse( | |
507 "ws://localhost/testing_path", "localhost", "/testing_path", | |
508 NoSubProtocols(), "http://localhost", "", | |
509 WebSocketStandardResponse( | |
510 "Sec-WebSocket-Extensions: permessage-deflate\r\n") + | |
511 std::string( | |
512 "\xc1\x07" // WebSocket header (FIN + RSV1, Text payload 7 bytes) | |
513 "\xf2\x48\xcd\xc9\xc9\x07\x00", // "Hello" DEFLATE compressed | |
514 9)); | |
515 WaitUntilConnectDone(); | |
516 | |
517 ASSERT_TRUE(stream_); | |
518 ScopedVector<WebSocketFrame> frames; | |
519 CompletionCallback callback; | |
520 ASSERT_EQ(OK, stream_->ReadFrames(&frames, callback)); | |
521 ASSERT_EQ(1U, frames.size()); | |
522 ASSERT_EQ(5U, frames[0]->header.payload_length); | |
523 EXPECT_EQ("Hello", std::string(frames[0]->data->data(), 5)); | |
524 } | |
525 | |
526 // Unknown extension in the response is rejected | |
527 TEST_F(WebSocketStreamCreateExtensionTest, UnknownExtension) { | |
528 CreateAndConnectWithExtensions("x-unknown-extension"); | |
529 EXPECT_FALSE(stream_); | |
530 EXPECT_TRUE(has_failed()); | |
531 EXPECT_EQ("Error during WebSocket handshake: " | |
532 "Found an unsupported extension 'x-unknown-extension' " | |
533 "in 'Sec-WebSocket-Extensions' header", | |
534 failure_message()); | |
535 } | |
536 | |
537 // Malformed extensions are rejected (this file does not cover all possible | |
538 // parse failures, as the parser is covered thoroughly by its own unit tests). | |
539 TEST_F(WebSocketStreamCreateExtensionTest, MalformedExtension) { | |
540 CreateAndConnectWithExtensions(";"); | |
541 EXPECT_FALSE(stream_); | |
542 EXPECT_TRUE(has_failed()); | |
543 EXPECT_EQ( | |
544 "Error during WebSocket handshake: 'Sec-WebSocket-Extensions' header " | |
545 "value is rejected by the parser: ;", | |
546 failure_message()); | |
547 } | |
548 | |
549 // The permessage-deflate extension may only be specified once. | |
550 TEST_F(WebSocketStreamCreateExtensionTest, OnlyOnePerMessageDeflateAllowed) { | |
551 CreateAndConnectWithExtensions( | |
552 "permessage-deflate, permessage-deflate; client_max_window_bits=10"); | |
553 EXPECT_FALSE(stream_); | |
554 EXPECT_TRUE(has_failed()); | |
555 EXPECT_EQ( | |
556 "Error during WebSocket handshake: " | |
557 "Received duplicate permessage-deflate response", | |
558 failure_message()); | |
559 } | |
560 | |
561 // permessage-deflate parameters may not be duplicated. | |
562 TEST_F(WebSocketStreamCreateExtensionTest, NoDuplicateParameters) { | |
563 CreateAndConnectWithExtensions( | |
564 "permessage-deflate; client_no_context_takeover; " | |
565 "client_no_context_takeover"); | |
566 EXPECT_FALSE(stream_); | |
567 EXPECT_TRUE(has_failed()); | |
568 EXPECT_EQ( | |
569 "Error during WebSocket handshake: Error in permessage-deflate: " | |
570 "Received duplicate permessage-deflate extension parameter " | |
571 "client_no_context_takeover", | |
572 failure_message()); | |
573 } | |
574 | |
575 // permessage-deflate parameters must start with "client_" or "server_" | |
576 TEST_F(WebSocketStreamCreateExtensionTest, BadParameterPrefix) { | |
577 CreateAndConnectWithExtensions( | |
578 "permessage-deflate; absurd_no_context_takeover"); | |
579 EXPECT_FALSE(stream_); | |
580 EXPECT_TRUE(has_failed()); | |
581 EXPECT_EQ( | |
582 "Error during WebSocket handshake: Error in permessage-deflate: " | |
583 "Received an unexpected permessage-deflate extension parameter", | |
584 failure_message()); | |
585 } | |
586 | |
587 // permessage-deflate parameters must be either *_no_context_takeover or | |
588 // *_max_window_bits | |
589 TEST_F(WebSocketStreamCreateExtensionTest, BadParameterSuffix) { | |
590 CreateAndConnectWithExtensions( | |
591 "permessage-deflate; client_max_content_bits=5"); | |
592 EXPECT_FALSE(stream_); | |
593 EXPECT_TRUE(has_failed()); | |
594 EXPECT_EQ( | |
595 "Error during WebSocket handshake: Error in permessage-deflate: " | |
596 "Received an unexpected permessage-deflate extension parameter", | |
597 failure_message()); | |
598 } | |
599 | |
600 // *_no_context_takeover parameters must not have an argument | |
601 TEST_F(WebSocketStreamCreateExtensionTest, BadParameterValue) { | |
602 CreateAndConnectWithExtensions( | |
603 "permessage-deflate; client_no_context_takeover=true"); | |
604 EXPECT_FALSE(stream_); | |
605 EXPECT_TRUE(has_failed()); | |
606 EXPECT_EQ( | |
607 "Error during WebSocket handshake: Error in permessage-deflate: " | |
608 "Received invalid client_no_context_takeover parameter", | |
609 failure_message()); | |
610 } | |
611 | |
612 // *_max_window_bits must have an argument | |
613 TEST_F(WebSocketStreamCreateExtensionTest, NoMaxWindowBitsArgument) { | |
614 CreateAndConnectWithExtensions("permessage-deflate; client_max_window_bits"); | |
615 EXPECT_FALSE(stream_); | |
616 EXPECT_TRUE(has_failed()); | |
617 EXPECT_EQ( | |
618 "Error during WebSocket handshake: Error in permessage-deflate: " | |
619 "client_max_window_bits must have value", | |
620 failure_message()); | |
621 } | |
622 | |
623 // *_max_window_bits must be an integer | |
624 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueInteger) { | |
625 CreateAndConnectWithExtensions( | |
626 "permessage-deflate; server_max_window_bits=banana"); | |
627 EXPECT_FALSE(stream_); | |
628 EXPECT_TRUE(has_failed()); | |
629 EXPECT_EQ( | |
630 "Error during WebSocket handshake: Error in permessage-deflate: " | |
631 "Received invalid server_max_window_bits parameter", | |
632 failure_message()); | |
633 } | |
634 | |
635 // *_max_window_bits must be >= 8 | |
636 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueTooSmall) { | |
637 CreateAndConnectWithExtensions( | |
638 "permessage-deflate; server_max_window_bits=7"); | |
639 EXPECT_FALSE(stream_); | |
640 EXPECT_TRUE(has_failed()); | |
641 EXPECT_EQ( | |
642 "Error during WebSocket handshake: Error in permessage-deflate: " | |
643 "Received invalid server_max_window_bits parameter", | |
644 failure_message()); | |
645 } | |
646 | |
647 // *_max_window_bits must be <= 15 | |
648 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueTooBig) { | |
649 CreateAndConnectWithExtensions( | |
650 "permessage-deflate; client_max_window_bits=16"); | |
651 EXPECT_FALSE(stream_); | |
652 EXPECT_TRUE(has_failed()); | |
653 EXPECT_EQ( | |
654 "Error during WebSocket handshake: Error in permessage-deflate: " | |
655 "Received invalid client_max_window_bits parameter", | |
656 failure_message()); | |
657 } | |
658 | |
659 // *_max_window_bits must not start with 0 | |
660 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueStartsWithZero) { | |
661 CreateAndConnectWithExtensions( | |
662 "permessage-deflate; client_max_window_bits=08"); | |
663 EXPECT_FALSE(stream_); | |
664 EXPECT_TRUE(has_failed()); | |
665 EXPECT_EQ( | |
666 "Error during WebSocket handshake: Error in permessage-deflate: " | |
667 "Received invalid client_max_window_bits parameter", | |
668 failure_message()); | |
669 } | |
670 | |
671 // *_max_window_bits must not start with + | |
672 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueStartsWithPlus) { | |
673 CreateAndConnectWithExtensions( | |
674 "permessage-deflate; server_max_window_bits=+9"); | |
675 EXPECT_FALSE(stream_); | |
676 EXPECT_TRUE(has_failed()); | |
677 EXPECT_EQ( | |
678 "Error during WebSocket handshake: Error in permessage-deflate: " | |
679 "Received invalid server_max_window_bits parameter", | |
680 failure_message()); | |
681 } | |
682 | |
683 // TODO(ricea): Check that WebSocketDeflateStream is initialised with the | |
684 // arguments from the server. This is difficult because the data written to the | |
685 // socket is randomly masked. | |
686 | |
687 // Additional Sec-WebSocket-Accept headers should be rejected. | |
688 TEST_F(WebSocketStreamCreateTest, DoubleAccept) { | |
689 CreateAndConnectStandard( | |
690 "ws://localhost/", "localhost", "/", NoSubProtocols(), "http://localhost", | |
691 "", "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"); | |
692 WaitUntilConnectDone(); | |
693 EXPECT_FALSE(stream_); | |
694 EXPECT_TRUE(has_failed()); | |
695 EXPECT_EQ("Error during WebSocket handshake: " | |
696 "'Sec-WebSocket-Accept' header must not appear " | |
697 "more than once in a response", | |
698 failure_message()); | |
699 } | |
700 | |
701 // Response code 200 must be rejected. | |
702 TEST_F(WebSocketStreamCreateTest, InvalidStatusCode) { | |
703 static const char kInvalidStatusCodeResponse[] = | |
704 "HTTP/1.1 200 OK\r\n" | |
705 "Upgrade: websocket\r\n" | |
706 "Connection: Upgrade\r\n" | |
707 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
708 "\r\n"; | |
709 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
710 NoSubProtocols(), "http://localhost", "", | |
711 kInvalidStatusCodeResponse); | |
712 WaitUntilConnectDone(); | |
713 EXPECT_TRUE(has_failed()); | |
714 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 200", | |
715 failure_message()); | |
716 } | |
717 | |
718 // Redirects are not followed (according to the WHATWG WebSocket API, which | |
719 // overrides RFC6455 for browser applications). | |
720 TEST_F(WebSocketStreamCreateTest, RedirectsRejected) { | |
721 static const char kRedirectResponse[] = | |
722 "HTTP/1.1 302 Moved Temporarily\r\n" | |
723 "Content-Type: text/html\r\n" | |
724 "Content-Length: 34\r\n" | |
725 "Connection: keep-alive\r\n" | |
726 "Location: ws://localhost/other\r\n" | |
727 "\r\n" | |
728 "<title>Moved</title><h1>Moved</h1>"; | |
729 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
730 NoSubProtocols(), "http://localhost", "", | |
731 kRedirectResponse); | |
732 WaitUntilConnectDone(); | |
733 EXPECT_TRUE(has_failed()); | |
734 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 302", | |
735 failure_message()); | |
736 } | |
737 | |
738 // Malformed responses should be rejected. HttpStreamParser will accept just | |
739 // about any garbage in the middle of the headers. To make it give up, the junk | |
740 // has to be at the start of the response. Even then, it just gets treated as an | |
741 // HTTP/0.9 response. | |
742 TEST_F(WebSocketStreamCreateTest, MalformedResponse) { | |
743 static const char kMalformedResponse[] = | |
744 "220 mx.google.com ESMTP\r\n" | |
745 "HTTP/1.1 101 OK\r\n" | |
746 "Upgrade: websocket\r\n" | |
747 "Connection: Upgrade\r\n" | |
748 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
749 "\r\n"; | |
750 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
751 NoSubProtocols(), "http://localhost", "", | |
752 kMalformedResponse); | |
753 WaitUntilConnectDone(); | |
754 EXPECT_TRUE(has_failed()); | |
755 EXPECT_EQ("Error during WebSocket handshake: Invalid status line", | |
756 failure_message()); | |
757 } | |
758 | |
759 // Upgrade header must be present. | |
760 TEST_F(WebSocketStreamCreateTest, MissingUpgradeHeader) { | |
761 static const char kMissingUpgradeResponse[] = | |
762 "HTTP/1.1 101 Switching Protocols\r\n" | |
763 "Connection: Upgrade\r\n" | |
764 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
765 "\r\n"; | |
766 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
767 NoSubProtocols(), "http://localhost", "", | |
768 kMissingUpgradeResponse); | |
769 WaitUntilConnectDone(); | |
770 EXPECT_TRUE(has_failed()); | |
771 EXPECT_EQ("Error during WebSocket handshake: 'Upgrade' header is missing", | |
772 failure_message()); | |
773 } | |
774 | |
775 // There must only be one upgrade header. | |
776 TEST_F(WebSocketStreamCreateTest, DoubleUpgradeHeader) { | |
777 CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
778 NoSubProtocols(), "http://localhost", "", | |
779 "Upgrade: HTTP/2.0\r\n"); | |
780 WaitUntilConnectDone(); | |
781 EXPECT_TRUE(has_failed()); | |
782 EXPECT_EQ("Error during WebSocket handshake: " | |
783 "'Upgrade' header must not appear more than once in a response", | |
784 failure_message()); | |
785 } | |
786 | |
787 // There must only be one correct upgrade header. | |
788 TEST_F(WebSocketStreamCreateTest, IncorrectUpgradeHeader) { | |
789 static const char kMissingUpgradeResponse[] = | |
790 "HTTP/1.1 101 Switching Protocols\r\n" | |
791 "Connection: Upgrade\r\n" | |
792 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
793 "Upgrade: hogefuga\r\n" | |
794 "\r\n"; | |
795 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
796 NoSubProtocols(), "http://localhost", "", | |
797 kMissingUpgradeResponse); | |
798 WaitUntilConnectDone(); | |
799 EXPECT_TRUE(has_failed()); | |
800 EXPECT_EQ("Error during WebSocket handshake: " | |
801 "'Upgrade' header value is not 'WebSocket': hogefuga", | |
802 failure_message()); | |
803 } | |
804 | |
805 // Connection header must be present. | |
806 TEST_F(WebSocketStreamCreateTest, MissingConnectionHeader) { | |
807 static const char kMissingConnectionResponse[] = | |
808 "HTTP/1.1 101 Switching Protocols\r\n" | |
809 "Upgrade: websocket\r\n" | |
810 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
811 "\r\n"; | |
812 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
813 NoSubProtocols(), "http://localhost", "", | |
814 kMissingConnectionResponse); | |
815 WaitUntilConnectDone(); | |
816 EXPECT_TRUE(has_failed()); | |
817 EXPECT_EQ("Error during WebSocket handshake: " | |
818 "'Connection' header is missing", | |
819 failure_message()); | |
820 } | |
821 | |
822 // Connection header must contain "Upgrade". | |
823 TEST_F(WebSocketStreamCreateTest, IncorrectConnectionHeader) { | |
824 static const char kMissingConnectionResponse[] = | |
825 "HTTP/1.1 101 Switching Protocols\r\n" | |
826 "Upgrade: websocket\r\n" | |
827 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
828 "Connection: hogefuga\r\n" | |
829 "\r\n"; | |
830 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
831 NoSubProtocols(), "http://localhost", "", | |
832 kMissingConnectionResponse); | |
833 WaitUntilConnectDone(); | |
834 EXPECT_TRUE(has_failed()); | |
835 EXPECT_EQ("Error during WebSocket handshake: " | |
836 "'Connection' header value must contain 'Upgrade'", | |
837 failure_message()); | |
838 } | |
839 | |
840 // Connection header is permitted to contain other tokens. | |
841 TEST_F(WebSocketStreamCreateTest, AdditionalTokenInConnectionHeader) { | |
842 static const char kAdditionalConnectionTokenResponse[] = | |
843 "HTTP/1.1 101 Switching Protocols\r\n" | |
844 "Upgrade: websocket\r\n" | |
845 "Connection: Upgrade, Keep-Alive\r\n" | |
846 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
847 "\r\n"; | |
848 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
849 NoSubProtocols(), "http://localhost", "", | |
850 kAdditionalConnectionTokenResponse); | |
851 WaitUntilConnectDone(); | |
852 EXPECT_FALSE(has_failed()); | |
853 EXPECT_TRUE(stream_); | |
854 } | |
855 | |
856 // Sec-WebSocket-Accept header must be present. | |
857 TEST_F(WebSocketStreamCreateTest, MissingSecWebSocketAccept) { | |
858 static const char kMissingAcceptResponse[] = | |
859 "HTTP/1.1 101 Switching Protocols\r\n" | |
860 "Upgrade: websocket\r\n" | |
861 "Connection: Upgrade\r\n" | |
862 "\r\n"; | |
863 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
864 NoSubProtocols(), "http://localhost", "", | |
865 kMissingAcceptResponse); | |
866 WaitUntilConnectDone(); | |
867 EXPECT_TRUE(has_failed()); | |
868 EXPECT_EQ("Error during WebSocket handshake: " | |
869 "'Sec-WebSocket-Accept' header is missing", | |
870 failure_message()); | |
871 } | |
872 | |
873 // Sec-WebSocket-Accept header must match the key that was sent. | |
874 TEST_F(WebSocketStreamCreateTest, WrongSecWebSocketAccept) { | |
875 static const char kIncorrectAcceptResponse[] = | |
876 "HTTP/1.1 101 Switching Protocols\r\n" | |
877 "Upgrade: websocket\r\n" | |
878 "Connection: Upgrade\r\n" | |
879 "Sec-WebSocket-Accept: x/byyPZ2tOFvJCGkkugcKvqhhPk=\r\n" | |
880 "\r\n"; | |
881 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
882 NoSubProtocols(), "http://localhost", "", | |
883 kIncorrectAcceptResponse); | |
884 WaitUntilConnectDone(); | |
885 EXPECT_TRUE(has_failed()); | |
886 EXPECT_EQ("Error during WebSocket handshake: " | |
887 "Incorrect 'Sec-WebSocket-Accept' header value", | |
888 failure_message()); | |
889 } | |
890 | |
891 // Cancellation works. | |
892 TEST_F(WebSocketStreamCreateTest, Cancellation) { | |
893 CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
894 NoSubProtocols(), "http://localhost", "", ""); | |
895 stream_request_.reset(); | |
896 // WaitUntilConnectDone doesn't work in this case. | |
897 base::RunLoop().RunUntilIdle(); | |
898 EXPECT_FALSE(has_failed()); | |
899 EXPECT_FALSE(stream_); | |
900 EXPECT_FALSE(request_info_); | |
901 EXPECT_FALSE(response_info_); | |
902 } | |
903 | |
904 // Connect failure must look just like negotiation failure. | |
905 TEST_F(WebSocketStreamCreateTest, ConnectionFailure) { | |
906 scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData()); | |
907 socket_data->set_connect_data( | |
908 MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED)); | |
909 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), | |
910 "http://localhost", socket_data.Pass()); | |
911 WaitUntilConnectDone(); | |
912 EXPECT_TRUE(has_failed()); | |
913 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED", | |
914 failure_message()); | |
915 EXPECT_FALSE(request_info_); | |
916 EXPECT_FALSE(response_info_); | |
917 } | |
918 | |
919 // Connect timeout must look just like any other failure. | |
920 TEST_F(WebSocketStreamCreateTest, ConnectionTimeout) { | |
921 scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData()); | |
922 socket_data->set_connect_data( | |
923 MockConnect(ASYNC, ERR_CONNECTION_TIMED_OUT)); | |
924 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), | |
925 "http://localhost", socket_data.Pass()); | |
926 WaitUntilConnectDone(); | |
927 EXPECT_TRUE(has_failed()); | |
928 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_TIMED_OUT", | |
929 failure_message()); | |
930 } | |
931 | |
932 // The server doesn't respond to the opening handshake. | |
933 TEST_F(WebSocketStreamCreateTest, HandshakeTimeout) { | |
934 scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData()); | |
935 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING)); | |
936 scoped_ptr<MockWeakTimer> timer(new MockWeakTimer(false, false)); | |
937 base::WeakPtr<MockWeakTimer> weak_timer = timer->AsWeakPtr(); | |
938 CreateAndConnectRawExpectations("ws://localhost/", | |
939 NoSubProtocols(), | |
940 "http://localhost", | |
941 socket_data.Pass(), | |
942 timer.Pass()); | |
943 EXPECT_FALSE(has_failed()); | |
944 ASSERT_TRUE(weak_timer.get()); | |
945 EXPECT_TRUE(weak_timer->IsRunning()); | |
946 | |
947 weak_timer->Fire(); | |
948 WaitUntilConnectDone(); | |
949 | |
950 EXPECT_TRUE(has_failed()); | |
951 EXPECT_EQ("WebSocket opening handshake timed out", failure_message()); | |
952 ASSERT_TRUE(weak_timer.get()); | |
953 EXPECT_FALSE(weak_timer->IsRunning()); | |
954 } | |
955 | |
956 // When the connection establishes the timer should be stopped. | |
957 TEST_F(WebSocketStreamCreateTest, HandshakeTimerOnSuccess) { | |
958 scoped_ptr<MockWeakTimer> timer(new MockWeakTimer(false, false)); | |
959 base::WeakPtr<MockWeakTimer> weak_timer = timer->AsWeakPtr(); | |
960 | |
961 CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
962 NoSubProtocols(), "http://localhost", "", "", | |
963 timer.Pass()); | |
964 ASSERT_TRUE(weak_timer); | |
965 EXPECT_TRUE(weak_timer->IsRunning()); | |
966 | |
967 WaitUntilConnectDone(); | |
968 EXPECT_FALSE(has_failed()); | |
969 EXPECT_TRUE(stream_); | |
970 ASSERT_TRUE(weak_timer); | |
971 EXPECT_FALSE(weak_timer->IsRunning()); | |
972 } | |
973 | |
974 // When the connection fails the timer should be stopped. | |
975 TEST_F(WebSocketStreamCreateTest, HandshakeTimerOnFailure) { | |
976 scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData()); | |
977 socket_data->set_connect_data( | |
978 MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED)); | |
979 scoped_ptr<MockWeakTimer> timer(new MockWeakTimer(false, false)); | |
980 base::WeakPtr<MockWeakTimer> weak_timer = timer->AsWeakPtr(); | |
981 CreateAndConnectRawExpectations("ws://localhost/", | |
982 NoSubProtocols(), | |
983 "http://localhost", | |
984 socket_data.Pass(), | |
985 timer.Pass()); | |
986 ASSERT_TRUE(weak_timer.get()); | |
987 EXPECT_TRUE(weak_timer->IsRunning()); | |
988 | |
989 WaitUntilConnectDone(); | |
990 EXPECT_TRUE(has_failed()); | |
991 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED", | |
992 failure_message()); | |
993 ASSERT_TRUE(weak_timer.get()); | |
994 EXPECT_FALSE(weak_timer->IsRunning()); | |
995 } | |
996 | |
997 // Cancellation during connect works. | |
998 TEST_F(WebSocketStreamCreateTest, CancellationDuringConnect) { | |
999 scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData()); | |
1000 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING)); | |
1001 CreateAndConnectRawExpectations("ws://localhost/", | |
1002 NoSubProtocols(), | |
1003 "http://localhost", | |
1004 socket_data.Pass()); | |
1005 stream_request_.reset(); | |
1006 // WaitUntilConnectDone doesn't work in this case. | |
1007 base::RunLoop().RunUntilIdle(); | |
1008 EXPECT_FALSE(has_failed()); | |
1009 EXPECT_FALSE(stream_); | |
1010 } | |
1011 | |
1012 // Cancellation during write of the request headers works. | |
1013 TEST_F(WebSocketStreamCreateTest, CancellationDuringWrite) { | |
1014 // We seem to need at least two operations in order to use SetStop(). | |
1015 MockWrite writes[] = {MockWrite(ASYNC, 0, "GET / HTTP/"), | |
1016 MockWrite(ASYNC, 1, "1.1\r\n")}; | |
1017 // We keep a copy of the pointer so that we can call RunFor() on it later. | |
1018 DeterministicSocketData* socket_data( | |
1019 new DeterministicSocketData(NULL, 0, writes, arraysize(writes))); | |
1020 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); | |
1021 socket_data->SetStop(1); | |
1022 CreateAndConnectRawExpectations("ws://localhost/", | |
1023 NoSubProtocols(), | |
1024 "http://localhost", | |
1025 make_scoped_ptr(socket_data)); | |
1026 socket_data->Run(); | |
1027 stream_request_.reset(); | |
1028 // WaitUntilConnectDone doesn't work in this case. | |
1029 base::RunLoop().RunUntilIdle(); | |
1030 EXPECT_FALSE(has_failed()); | |
1031 EXPECT_FALSE(stream_); | |
1032 EXPECT_TRUE(request_info_); | |
1033 EXPECT_FALSE(response_info_); | |
1034 } | |
1035 | |
1036 // Cancellation during read of the response headers works. | |
1037 TEST_F(WebSocketStreamCreateTest, CancellationDuringRead) { | |
1038 std::string request = | |
1039 WebSocketStandardRequest("/", "localhost", "http://localhost", ""); | |
1040 MockWrite writes[] = {MockWrite(ASYNC, 0, request.c_str())}; | |
1041 MockRead reads[] = { | |
1042 MockRead(ASYNC, 1, "HTTP/1.1 101 Switching Protocols\r\nUpgr"), | |
1043 }; | |
1044 scoped_ptr<DeterministicSocketData> socket_data( | |
1045 BuildSocketData(reads, writes)); | |
1046 socket_data->SetStop(1); | |
1047 DeterministicSocketData* socket_data_raw_ptr = socket_data.get(); | |
1048 CreateAndConnectRawExpectations("ws://localhost/", | |
1049 NoSubProtocols(), | |
1050 "http://localhost", | |
1051 socket_data.Pass()); | |
1052 socket_data_raw_ptr->Run(); | |
1053 stream_request_.reset(); | |
1054 // WaitUntilConnectDone doesn't work in this case. | |
1055 base::RunLoop().RunUntilIdle(); | |
1056 EXPECT_FALSE(has_failed()); | |
1057 EXPECT_FALSE(stream_); | |
1058 EXPECT_TRUE(request_info_); | |
1059 EXPECT_FALSE(response_info_); | |
1060 } | |
1061 | |
1062 // Over-size response headers (> 256KB) should not cause a crash. This is a | |
1063 // regression test for crbug.com/339456. It is based on the layout test | |
1064 // "cookie-flood.html". | |
1065 TEST_F(WebSocketStreamCreateTest, VeryLargeResponseHeaders) { | |
1066 std::string set_cookie_headers; | |
1067 set_cookie_headers.reserve(45 * 10000); | |
1068 for (int i = 0; i < 10000; ++i) { | |
1069 set_cookie_headers += | |
1070 base::StringPrintf("Set-Cookie: WK-websocket-test-flood-%d=1\r\n", i); | |
1071 } | |
1072 CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
1073 NoSubProtocols(), "http://localhost", "", | |
1074 set_cookie_headers); | |
1075 WaitUntilConnectDone(); | |
1076 EXPECT_TRUE(has_failed()); | |
1077 EXPECT_FALSE(response_info_); | |
1078 } | |
1079 | |
1080 // If the remote host closes the connection without sending headers, we should | |
1081 // log the console message "Connection closed before receiving a handshake | |
1082 // response". | |
1083 TEST_F(WebSocketStreamCreateTest, NoResponse) { | |
1084 std::string request = | |
1085 WebSocketStandardRequest("/", "localhost", "http://localhost", ""); | |
1086 MockWrite writes[] = {MockWrite(ASYNC, request.data(), request.size(), 0)}; | |
1087 MockRead reads[] = {MockRead(ASYNC, 0, 1)}; | |
1088 scoped_ptr<DeterministicSocketData> socket_data( | |
1089 BuildSocketData(reads, writes)); | |
1090 DeterministicSocketData* socket_data_raw_ptr = socket_data.get(); | |
1091 CreateAndConnectRawExpectations("ws://localhost/", | |
1092 NoSubProtocols(), | |
1093 "http://localhost", | |
1094 socket_data.Pass()); | |
1095 socket_data_raw_ptr->RunFor(2); | |
1096 EXPECT_TRUE(has_failed()); | |
1097 EXPECT_FALSE(stream_); | |
1098 EXPECT_FALSE(response_info_); | |
1099 EXPECT_EQ("Connection closed before receiving a handshake response", | |
1100 failure_message()); | |
1101 } | |
1102 | |
1103 TEST_F(WebSocketStreamCreateTest, SelfSignedCertificateFailure) { | |
1104 ssl_data_.push_back( | |
1105 new SSLSocketDataProvider(ASYNC, ERR_CERT_AUTHORITY_INVALID)); | |
1106 ssl_data_[0]->cert = | |
1107 ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der"); | |
1108 ASSERT_TRUE(ssl_data_[0]->cert.get()); | |
1109 scoped_ptr<DeterministicSocketData> raw_socket_data(BuildNullSocketData()); | |
1110 CreateAndConnectRawExpectations("wss://localhost/", | |
1111 NoSubProtocols(), | |
1112 "http://localhost", | |
1113 raw_socket_data.Pass()); | |
1114 // WaitUntilConnectDone doesn't work in this case. | |
1115 base::RunLoop().RunUntilIdle(); | |
1116 EXPECT_FALSE(has_failed()); | |
1117 ASSERT_TRUE(ssl_error_callbacks_); | |
1118 ssl_error_callbacks_->CancelSSLRequest(ERR_CERT_AUTHORITY_INVALID, | |
1119 &ssl_info_); | |
1120 WaitUntilConnectDone(); | |
1121 EXPECT_TRUE(has_failed()); | |
1122 } | |
1123 | |
1124 TEST_F(WebSocketStreamCreateTest, SelfSignedCertificateSuccess) { | |
1125 scoped_ptr<SSLSocketDataProvider> ssl_data( | |
1126 new SSLSocketDataProvider(ASYNC, ERR_CERT_AUTHORITY_INVALID)); | |
1127 ssl_data->cert = | |
1128 ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der"); | |
1129 ASSERT_TRUE(ssl_data->cert.get()); | |
1130 ssl_data_.push_back(ssl_data.release()); | |
1131 ssl_data.reset(new SSLSocketDataProvider(ASYNC, OK)); | |
1132 ssl_data_.push_back(ssl_data.release()); | |
1133 url_request_context_host_.AddRawExpectations(BuildNullSocketData()); | |
1134 CreateAndConnectStandard("wss://localhost/", "localhost", "/", | |
1135 NoSubProtocols(), "http://localhost", "", ""); | |
1136 // WaitUntilConnectDone doesn't work in this case. | |
1137 base::RunLoop().RunUntilIdle(); | |
1138 ASSERT_TRUE(ssl_error_callbacks_); | |
1139 ssl_error_callbacks_->ContinueSSLRequest(); | |
1140 WaitUntilConnectDone(); | |
1141 EXPECT_FALSE(has_failed()); | |
1142 EXPECT_TRUE(stream_); | |
1143 } | |
1144 | |
1145 // If the server requests authorisation, but we have no credentials, the | |
1146 // connection should fail cleanly. | |
1147 TEST_F(WebSocketStreamCreateBasicAuthTest, FailureNoCredentials) { | |
1148 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
1149 NoSubProtocols(), "http://localhost", "", | |
1150 kUnauthorizedResponse); | |
1151 WaitUntilConnectDone(); | |
1152 EXPECT_TRUE(has_failed()); | |
1153 EXPECT_EQ("HTTP Authentication failed; no valid credentials available", | |
1154 failure_message()); | |
1155 EXPECT_TRUE(response_info_); | |
1156 } | |
1157 | |
1158 TEST_F(WebSocketStreamCreateBasicAuthTest, SuccessPasswordInUrl) { | |
1159 CreateAndConnectAuthHandshake("ws://foo:bar@localhost/", | |
1160 "Zm9vOmJhcg==", | |
1161 WebSocketStandardResponse(std::string())); | |
1162 WaitUntilConnectDone(); | |
1163 EXPECT_FALSE(has_failed()); | |
1164 EXPECT_TRUE(stream_); | |
1165 ASSERT_TRUE(response_info_); | |
1166 EXPECT_EQ(101, response_info_->status_code); | |
1167 } | |
1168 | |
1169 TEST_F(WebSocketStreamCreateBasicAuthTest, FailureIncorrectPasswordInUrl) { | |
1170 CreateAndConnectAuthHandshake( | |
1171 "ws://foo:baz@localhost/", "Zm9vOmJheg==", kUnauthorizedResponse); | |
1172 WaitUntilConnectDone(); | |
1173 EXPECT_TRUE(has_failed()); | |
1174 EXPECT_TRUE(response_info_); | |
1175 } | |
1176 | |
1177 // Digest auth has the same connection semantics as Basic auth, so we can | |
1178 // generally assume that whatever works for Basic auth will also work for | |
1179 // Digest. There's just one test here, to confirm that it works at all. | |
1180 TEST_F(WebSocketStreamCreateDigestAuthTest, DigestPasswordInUrl) { | |
1181 AddRawExpectations(helper_.BuildSocketData1(kUnauthorizedResponse)); | |
1182 | |
1183 CreateAndConnectRawExpectations( | |
1184 "ws://FooBar:pass@localhost/", | |
1185 NoSubProtocols(), | |
1186 "http://localhost", | |
1187 helper_.BuildSocketData2(kAuthorizedRequest, | |
1188 WebSocketStandardResponse(std::string()))); | |
1189 WaitUntilConnectDone(); | |
1190 EXPECT_FALSE(has_failed()); | |
1191 EXPECT_TRUE(stream_); | |
1192 ASSERT_TRUE(response_info_); | |
1193 EXPECT_EQ(101, response_info_->status_code); | |
1194 } | |
1195 | |
1196 TEST_F(WebSocketStreamCreateUMATest, Incomplete) { | |
1197 const std::string name("Net.WebSocket.HandshakeResult"); | |
1198 scoped_ptr<base::HistogramSamples> original(GetSamples(name)); | |
1199 | |
1200 { | |
1201 StreamCreation creation; | |
1202 creation.CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
1203 creation.NoSubProtocols(), | |
1204 "http://localhost", "", ""); | |
1205 } | |
1206 | |
1207 scoped_ptr<base::HistogramSamples> samples(GetSamples(name)); | |
1208 ASSERT_TRUE(samples); | |
1209 if (original) { | |
1210 samples->Subtract(*original); // Cancel the original values. | |
1211 } | |
1212 EXPECT_EQ(1, samples->GetCount(INCOMPLETE)); | |
1213 EXPECT_EQ(0, samples->GetCount(CONNECTED)); | |
1214 EXPECT_EQ(0, samples->GetCount(FAILED)); | |
1215 } | |
1216 | |
1217 TEST_F(WebSocketStreamCreateUMATest, Connected) { | |
1218 const std::string name("Net.WebSocket.HandshakeResult"); | |
1219 scoped_ptr<base::HistogramSamples> original(GetSamples(name)); | |
1220 | |
1221 { | |
1222 StreamCreation creation; | |
1223 creation.CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
1224 creation.NoSubProtocols(), | |
1225 "http://localhost", "", ""); | |
1226 creation.WaitUntilConnectDone(); | |
1227 } | |
1228 | |
1229 scoped_ptr<base::HistogramSamples> samples(GetSamples(name)); | |
1230 ASSERT_TRUE(samples); | |
1231 if (original) { | |
1232 samples->Subtract(*original); // Cancel the original values. | |
1233 } | |
1234 EXPECT_EQ(0, samples->GetCount(INCOMPLETE)); | |
1235 EXPECT_EQ(1, samples->GetCount(CONNECTED)); | |
1236 EXPECT_EQ(0, samples->GetCount(FAILED)); | |
1237 } | |
1238 | |
1239 TEST_F(WebSocketStreamCreateUMATest, Failed) { | |
1240 const std::string name("Net.WebSocket.HandshakeResult"); | |
1241 scoped_ptr<base::HistogramSamples> original(GetSamples(name)); | |
1242 | |
1243 { | |
1244 StreamCreation creation; | |
1245 static const char kInvalidStatusCodeResponse[] = | |
1246 "HTTP/1.1 200 OK\r\n" | |
1247 "Upgrade: websocket\r\n" | |
1248 "Connection: Upgrade\r\n" | |
1249 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
1250 "\r\n"; | |
1251 creation.CreateAndConnectCustomResponse( | |
1252 "ws://localhost/", "localhost", "/", creation.NoSubProtocols(), | |
1253 "http://localhost", "", kInvalidStatusCodeResponse); | |
1254 creation.WaitUntilConnectDone(); | |
1255 } | |
1256 | |
1257 scoped_ptr<base::HistogramSamples> samples(GetSamples(name)); | |
1258 ASSERT_TRUE(samples); | |
1259 if (original) { | |
1260 samples->Subtract(*original); // Cancel the original values. | |
1261 } | |
1262 EXPECT_EQ(1, samples->GetCount(INCOMPLETE)); | |
1263 EXPECT_EQ(0, samples->GetCount(CONNECTED)); | |
1264 EXPECT_EQ(0, samples->GetCount(FAILED)); | |
1265 } | |
1266 | |
1267 TEST_F(WebSocketStreamCreateTest, HandleErrConnectionClosed) { | |
1268 static const char kTruncatedResponse[] = | |
1269 "HTTP/1.1 101 Switching Protocols\r\n" | |
1270 "Upgrade: websocket\r\n" | |
1271 "Connection: Upgrade\r\n" | |
1272 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
1273 "Cache-Control: no-sto"; | |
1274 | |
1275 std::string request = | |
1276 WebSocketStandardRequest("/", "localhost", "http://localhost", ""); | |
1277 MockRead reads[] = { | |
1278 MockRead(SYNCHRONOUS, 1, kTruncatedResponse), | |
1279 MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED, 2), | |
1280 }; | |
1281 MockWrite writes[] = {MockWrite(SYNCHRONOUS, 0, request.c_str())}; | |
1282 scoped_ptr<DeterministicSocketData> socket_data( | |
1283 BuildSocketData(reads, writes)); | |
1284 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); | |
1285 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), | |
1286 "http://localhost", socket_data.Pass()); | |
1287 WaitUntilConnectDone(); | |
1288 EXPECT_TRUE(has_failed()); | |
1289 } | |
1290 | |
1291 TEST_F(WebSocketStreamCreateTest, HandleErrTunnelConnectionFailed) { | |
1292 static const char kConnectRequest[] = | |
1293 "CONNECT localhost:80 HTTP/1.1\r\n" | |
1294 "Host: localhost\r\n" | |
1295 "Proxy-Connection: keep-alive\r\n" | |
1296 "\r\n"; | |
1297 | |
1298 static const char kProxyResponse[] = | |
1299 "HTTP/1.1 403 Forbidden\r\n" | |
1300 "Content-Type: text/html\r\n" | |
1301 "Content-Length: 9\r\n" | |
1302 "Connection: keep-alive\r\n" | |
1303 "\r\n" | |
1304 "Forbidden"; | |
1305 | |
1306 MockRead reads[] = {MockRead(SYNCHRONOUS, 1, kProxyResponse)}; | |
1307 MockWrite writes[] = {MockWrite(SYNCHRONOUS, 0, kConnectRequest)}; | |
1308 scoped_ptr<DeterministicSocketData> socket_data( | |
1309 BuildSocketData(reads, writes)); | |
1310 url_request_context_host_.SetProxyConfig("https=proxy:8000"); | |
1311 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), | |
1312 "http://localhost", socket_data.Pass()); | |
1313 WaitUntilConnectDone(); | |
1314 EXPECT_TRUE(has_failed()); | |
1315 EXPECT_EQ("Establishing a tunnel via proxy server failed.", | |
1316 failure_message()); | |
1317 } | |
1318 | |
1319 } // namespace | |
1320 } // namespace net | |
OLD | NEW |