Show Pages in chrome://md-settings
[chromium-blink-merge.git] / net / websockets / websocket_stream_test.cc
blob56f2e648c413c1ea92ea0610c972d2c71019ab06
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.
5 #include "net/websockets/websocket_stream.h"
7 #include <algorithm>
8 #include <string>
9 #include <utility>
10 #include <vector>
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"
38 namespace net {
39 namespace {
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();
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));
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) {}
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();
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());
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());
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());
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());
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();
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_() {}
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
163 return BuildSocketData(reads1_, writes1_);
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_);
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];
188 DISALLOW_COPY_AND_ASSIGN(CommonAuthTestHelper);
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));
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));
225 static const char kUnauthorizedResponse[];
227 CommonAuthTestHelper helper_;
230 class WebSocketStreamCreateDigestAuthTest : public WebSocketStreamCreateTest {
231 protected:
232 static const char kUnauthorizedResponse[];
233 static const char kAuthorizedRequest[];
235 CommonAuthTestHelper helper_;
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";
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";
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";
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,
284 class StreamCreation : public WebSocketStreamCreateTest {
285 void TestBody() override {}
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>();
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_);
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";
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]);
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());
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]);
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_);
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_);
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_);
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());
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());
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());
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());
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());
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());
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());
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();
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));
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());
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());
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());
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());
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());
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());
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());
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());
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());
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());
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());
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());
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());
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.
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());
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());
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());
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());
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());
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());
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());
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());
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());
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_);
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());
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());
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_);
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_);
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());
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());
947 weak_timer->Fire();
948 WaitUntilConnectDone();
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());
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();
961 CreateAndConnectStandard("ws://localhost/", "localhost", "/",
962 NoSubProtocols(), "http://localhost", "", "",
963 timer.Pass());
964 ASSERT_TRUE(weak_timer);
965 EXPECT_TRUE(weak_timer->IsRunning());
967 WaitUntilConnectDone();
968 EXPECT_FALSE(has_failed());
969 EXPECT_TRUE(stream_);
970 ASSERT_TRUE(weak_timer);
971 EXPECT_FALSE(weak_timer->IsRunning());
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());
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());
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_);
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_);
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"),
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_);
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);
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_);
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());
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());
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_);
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_);
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);
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_);
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));
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);
1196 TEST_F(WebSocketStreamCreateUMATest, Incomplete) {
1197 const std::string name("Net.WebSocket.HandshakeResult");
1198 scoped_ptr<base::HistogramSamples> original(GetSamples(name));
1201 StreamCreation creation;
1202 creation.CreateAndConnectStandard("ws://localhost/", "localhost", "/",
1203 creation.NoSubProtocols(),
1204 "http://localhost", "", "");
1207 scoped_ptr<base::HistogramSamples> samples(GetSamples(name));
1208 ASSERT_TRUE(samples);
1209 if (original) {
1210 samples->Subtract(*original); // Cancel the original values.
1212 EXPECT_EQ(1, samples->GetCount(INCOMPLETE));
1213 EXPECT_EQ(0, samples->GetCount(CONNECTED));
1214 EXPECT_EQ(0, samples->GetCount(FAILED));
1217 TEST_F(WebSocketStreamCreateUMATest, Connected) {
1218 const std::string name("Net.WebSocket.HandshakeResult");
1219 scoped_ptr<base::HistogramSamples> original(GetSamples(name));
1222 StreamCreation creation;
1223 creation.CreateAndConnectStandard("ws://localhost/", "localhost", "/",
1224 creation.NoSubProtocols(),
1225 "http://localhost", "", "");
1226 creation.WaitUntilConnectDone();
1229 scoped_ptr<base::HistogramSamples> samples(GetSamples(name));
1230 ASSERT_TRUE(samples);
1231 if (original) {
1232 samples->Subtract(*original); // Cancel the original values.
1234 EXPECT_EQ(0, samples->GetCount(INCOMPLETE));
1235 EXPECT_EQ(1, samples->GetCount(CONNECTED));
1236 EXPECT_EQ(0, samples->GetCount(FAILED));
1239 TEST_F(WebSocketStreamCreateUMATest, Failed) {
1240 const std::string name("Net.WebSocket.HandshakeResult");
1241 scoped_ptr<base::HistogramSamples> original(GetSamples(name));
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();
1257 scoped_ptr<base::HistogramSamples> samples(GetSamples(name));
1258 ASSERT_TRUE(samples);
1259 if (original) {
1260 samples->Subtract(*original); // Cancel the original values.
1262 EXPECT_EQ(1, samples->GetCount(INCOMPLETE));
1263 EXPECT_EQ(0, samples->GetCount(CONNECTED));
1264 EXPECT_EQ(0, samples->GetCount(FAILED));
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";
1275 std::string request =
1276 WebSocketStandardRequest("/", "localhost", "http://localhost", "");
1277 MockRead reads[] = {
1278 MockRead(SYNCHRONOUS, 1, kTruncatedResponse),
1279 MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED, 2),
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());
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";
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";
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());
1319 } // namespace
1320 } // namespace net