GoogleURLTrackerInfoBarDelegate: Initialize uninitialized member in constructor.
[chromium-blink-merge.git] / net / websockets / websocket_stream_test.cc
blob4ea8538b76273c28af1164db13b8e145192a3770
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/memory/scoped_vector.h"
13 #include "base/metrics/histogram.h"
14 #include "base/metrics/histogram_samples.h"
15 #include "base/metrics/statistics_recorder.h"
16 #include "base/run_loop.h"
17 #include "base/strings/stringprintf.h"
18 #include "net/base/net_errors.h"
19 #include "net/http/http_request_headers.h"
20 #include "net/http/http_response_headers.h"
21 #include "net/socket/client_socket_handle.h"
22 #include "net/socket/socket_test_util.h"
23 #include "net/url_request/url_request_test_util.h"
24 #include "net/websockets/websocket_basic_handshake_stream.h"
25 #include "net/websockets/websocket_frame.h"
26 #include "net/websockets/websocket_handshake_request_info.h"
27 #include "net/websockets/websocket_handshake_response_info.h"
28 #include "net/websockets/websocket_handshake_stream_create_helper.h"
29 #include "net/websockets/websocket_test_util.h"
30 #include "testing/gtest/include/gtest/gtest.h"
31 #include "url/gurl.h"
32 #include "url/origin.h"
34 namespace net {
35 namespace {
37 typedef std::pair<std::string, std::string> HeaderKeyValuePair;
39 std::vector<HeaderKeyValuePair> ToVector(const HttpRequestHeaders& headers) {
40 HttpRequestHeaders::Iterator it(headers);
41 std::vector<HeaderKeyValuePair> result;
42 while (it.GetNext())
43 result.push_back(HeaderKeyValuePair(it.name(), it.value()));
44 return result;
47 std::vector<HeaderKeyValuePair> ToVector(const HttpResponseHeaders& headers) {
48 void* iter = NULL;
49 std::string name, value;
50 std::vector<HeaderKeyValuePair> result;
51 while (headers.EnumerateHeaderLines(&iter, &name, &value))
52 result.push_back(HeaderKeyValuePair(name, value));
53 return result;
56 // A sub-class of WebSocketHandshakeStreamCreateHelper which always sets a
57 // deterministic key to use in the WebSocket handshake.
58 class DeterministicKeyWebSocketHandshakeStreamCreateHelper
59 : public WebSocketHandshakeStreamCreateHelper {
60 public:
61 DeterministicKeyWebSocketHandshakeStreamCreateHelper(
62 WebSocketStream::ConnectDelegate* connect_delegate,
63 const std::vector<std::string>& requested_subprotocols)
64 : WebSocketHandshakeStreamCreateHelper(connect_delegate,
65 requested_subprotocols) {}
67 virtual WebSocketHandshakeStreamBase* CreateBasicStream(
68 scoped_ptr<ClientSocketHandle> connection,
69 bool using_proxy) OVERRIDE {
70 WebSocketHandshakeStreamCreateHelper::CreateBasicStream(connection.Pass(),
71 using_proxy);
72 // This will break in an obvious way if the type created by
73 // CreateBasicStream() changes.
74 static_cast<WebSocketBasicHandshakeStream*>(stream())
75 ->SetWebSocketKeyForTesting("dGhlIHNhbXBsZSBub25jZQ==");
76 return stream();
80 class WebSocketStreamCreateTest : public ::testing::Test {
81 public:
82 WebSocketStreamCreateTest(): has_failed_(false) {}
84 void CreateAndConnectCustomResponse(
85 const std::string& socket_url,
86 const std::string& socket_path,
87 const std::vector<std::string>& sub_protocols,
88 const std::string& origin,
89 const std::string& extra_request_headers,
90 const std::string& response_body) {
91 url_request_context_host_.SetExpectations(
92 WebSocketStandardRequest(socket_path, origin, extra_request_headers),
93 response_body);
94 CreateAndConnectStream(socket_url, sub_protocols, origin);
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(const std::string& socket_url,
100 const std::string& socket_path,
101 const std::vector<std::string>& sub_protocols,
102 const std::string& origin,
103 const std::string& extra_request_headers,
104 const std::string& extra_response_headers) {
105 CreateAndConnectCustomResponse(
106 socket_url,
107 socket_path,
108 sub_protocols,
109 origin,
110 extra_request_headers,
111 WebSocketStandardResponse(extra_response_headers));
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 url_request_context_host_.SetRawExpectations(socket_data.Pass());
120 CreateAndConnectStream(socket_url, sub_protocols, origin);
123 // A wrapper for CreateAndConnectStreamForTesting that knows about our default
124 // parameters.
125 void CreateAndConnectStream(const std::string& socket_url,
126 const std::vector<std::string>& sub_protocols,
127 const std::string& origin) {
128 scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate(
129 new TestConnectDelegate(this));
130 WebSocketStream::ConnectDelegate* delegate = connect_delegate.get();
131 stream_request_ = ::net::CreateAndConnectStreamForTesting(
132 GURL(socket_url),
133 scoped_ptr<WebSocketHandshakeStreamCreateHelper>(
134 new DeterministicKeyWebSocketHandshakeStreamCreateHelper(
135 delegate, sub_protocols)),
136 url::Origin(origin),
137 url_request_context_host_.GetURLRequestContext(),
138 BoundNetLog(),
139 connect_delegate.Pass());
142 static void RunUntilIdle() { base::RunLoop().RunUntilIdle(); }
144 // A simple function to make the tests more readable. Creates an empty vector.
145 static std::vector<std::string> NoSubProtocols() {
146 return std::vector<std::string>();
149 const std::string& failure_message() const { return failure_message_; }
150 bool has_failed() const { return has_failed_; }
152 class TestConnectDelegate : public WebSocketStream::ConnectDelegate {
153 public:
154 explicit TestConnectDelegate(WebSocketStreamCreateTest* owner)
155 : owner_(owner) {}
157 virtual void OnSuccess(scoped_ptr<WebSocketStream> stream) OVERRIDE {
158 stream.swap(owner_->stream_);
161 virtual void OnFailure(const std::string& message) OVERRIDE {
162 owner_->has_failed_ = true;
163 owner_->failure_message_ = message;
166 virtual void OnStartOpeningHandshake(
167 scoped_ptr<WebSocketHandshakeRequestInfo> request) OVERRIDE {
168 if (owner_->request_info_)
169 ADD_FAILURE();
170 owner_->request_info_ = request.Pass();
172 virtual void OnFinishOpeningHandshake(
173 scoped_ptr<WebSocketHandshakeResponseInfo> response) OVERRIDE {
174 if (owner_->response_info_)
175 ADD_FAILURE();
176 owner_->response_info_ = response.Pass();
179 private:
180 WebSocketStreamCreateTest* owner_;
183 WebSocketTestURLRequestContextHost url_request_context_host_;
184 scoped_ptr<WebSocketStreamRequest> stream_request_;
185 // Only set if the connection succeeded.
186 scoped_ptr<WebSocketStream> stream_;
187 // Only set if the connection failed.
188 std::string failure_message_;
189 bool has_failed_;
190 scoped_ptr<WebSocketHandshakeRequestInfo> request_info_;
191 scoped_ptr<WebSocketHandshakeResponseInfo> response_info_;
194 // There are enough tests of the Sec-WebSocket-Extensions header that they
195 // deserve their own test fixture.
196 class WebSocketStreamCreateExtensionTest : public WebSocketStreamCreateTest {
197 public:
198 // Performs a standard connect, with the value of the Sec-WebSocket-Extensions
199 // header in the response set to |extensions_header_value|. Runs the event
200 // loop to allow the connect to complete.
201 void CreateAndConnectWithExtensions(
202 const std::string& extensions_header_value) {
203 CreateAndConnectStandard(
204 "ws://localhost/testing_path",
205 "/testing_path",
206 NoSubProtocols(),
207 "http://localhost",
209 "Sec-WebSocket-Extensions: " + extensions_header_value + "\r\n");
210 RunUntilIdle();
214 class WebSocketStreamCreateUMATest : public ::testing::Test {
215 public:
216 // This enum should match with the enum in Delegate in websocket_stream.cc.
217 enum HandshakeResult {
218 INCOMPLETE,
219 CONNECTED,
220 FAILED,
221 NUM_HANDSHAKE_RESULT_TYPES,
224 class StreamCreation : public WebSocketStreamCreateTest {
225 virtual void TestBody() OVERRIDE {}
228 scoped_ptr<base::HistogramSamples> GetSamples(const std::string& name) {
229 base::HistogramBase* histogram =
230 base::StatisticsRecorder::FindHistogram(name);
231 return histogram ? histogram->SnapshotSamples()
232 : scoped_ptr<base::HistogramSamples>();
236 // Confirm that the basic case works as expected.
237 TEST_F(WebSocketStreamCreateTest, SimpleSuccess) {
238 CreateAndConnectStandard(
239 "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
240 EXPECT_FALSE(request_info_);
241 EXPECT_FALSE(response_info_);
242 RunUntilIdle();
243 EXPECT_FALSE(has_failed());
244 EXPECT_TRUE(stream_);
245 EXPECT_TRUE(request_info_);
246 EXPECT_TRUE(response_info_);
249 TEST_F(WebSocketStreamCreateTest, HandshakeInfo) {
250 static const char kResponse[] =
251 "HTTP/1.1 101 Switching Protocols\r\n"
252 "Upgrade: websocket\r\n"
253 "Connection: Upgrade\r\n"
254 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
255 "foo: bar, baz\r\n"
256 "hoge: fuga\r\n"
257 "hoge: piyo\r\n"
258 "\r\n";
260 CreateAndConnectCustomResponse(
261 "ws://localhost/",
262 "/",
263 NoSubProtocols(),
264 "http://localhost",
266 kResponse);
267 EXPECT_FALSE(request_info_);
268 EXPECT_FALSE(response_info_);
269 RunUntilIdle();
270 EXPECT_TRUE(stream_);
271 ASSERT_TRUE(request_info_);
272 ASSERT_TRUE(response_info_);
273 std::vector<HeaderKeyValuePair> request_headers =
274 ToVector(request_info_->headers);
275 // We examine the contents of request_info_ and response_info_
276 // mainly only in this test case.
277 EXPECT_EQ(GURL("ws://localhost/"), request_info_->url);
278 EXPECT_EQ(GURL("ws://localhost/"), response_info_->url);
279 EXPECT_EQ(101, response_info_->status_code);
280 EXPECT_EQ("Switching Protocols", response_info_->status_text);
281 ASSERT_EQ(12u, request_headers.size());
282 EXPECT_EQ(HeaderKeyValuePair("Host", "localhost"), request_headers[0]);
283 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), request_headers[1]);
284 EXPECT_EQ(HeaderKeyValuePair("Pragma", "no-cache"), request_headers[2]);
285 EXPECT_EQ(HeaderKeyValuePair("Cache-Control", "no-cache"),
286 request_headers[3]);
287 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), request_headers[4]);
288 EXPECT_EQ(HeaderKeyValuePair("Origin", "http://localhost"),
289 request_headers[5]);
290 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Version", "13"),
291 request_headers[6]);
292 EXPECT_EQ(HeaderKeyValuePair("User-Agent", ""), request_headers[7]);
293 EXPECT_EQ(HeaderKeyValuePair("Accept-Encoding", "gzip,deflate"),
294 request_headers[8]);
295 EXPECT_EQ(HeaderKeyValuePair("Accept-Language", "en-us,fr"),
296 request_headers[9]);
297 EXPECT_EQ("Sec-WebSocket-Key", request_headers[10].first);
298 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Extensions",
299 "permessage-deflate; client_max_window_bits"),
300 request_headers[11]);
302 std::vector<HeaderKeyValuePair> response_headers =
303 ToVector(*response_info_->headers);
304 ASSERT_EQ(6u, response_headers.size());
305 // Sort the headers for ease of verification.
306 std::sort(response_headers.begin(), response_headers.end());
308 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), response_headers[0]);
309 EXPECT_EQ("Sec-WebSocket-Accept", response_headers[1].first);
310 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), response_headers[2]);
311 EXPECT_EQ(HeaderKeyValuePair("foo", "bar, baz"), response_headers[3]);
312 EXPECT_EQ(HeaderKeyValuePair("hoge", "fuga"), response_headers[4]);
313 EXPECT_EQ(HeaderKeyValuePair("hoge", "piyo"), response_headers[5]);
316 // Confirm that the stream isn't established until the message loop runs.
317 TEST_F(WebSocketStreamCreateTest, NeedsToRunLoop) {
318 CreateAndConnectStandard(
319 "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
320 EXPECT_FALSE(has_failed());
321 EXPECT_FALSE(stream_);
324 // Check the path is used.
325 TEST_F(WebSocketStreamCreateTest, PathIsUsed) {
326 CreateAndConnectStandard("ws://localhost/testing_path",
327 "/testing_path",
328 NoSubProtocols(),
329 "http://localhost",
331 "");
332 RunUntilIdle();
333 EXPECT_FALSE(has_failed());
334 EXPECT_TRUE(stream_);
337 // Check that the origin is used.
338 TEST_F(WebSocketStreamCreateTest, OriginIsUsed) {
339 CreateAndConnectStandard("ws://localhost/testing_path",
340 "/testing_path",
341 NoSubProtocols(),
342 "http://google.com",
344 "");
345 RunUntilIdle();
346 EXPECT_FALSE(has_failed());
347 EXPECT_TRUE(stream_);
350 // Check that sub-protocols are sent and parsed.
351 TEST_F(WebSocketStreamCreateTest, SubProtocolIsUsed) {
352 std::vector<std::string> sub_protocols;
353 sub_protocols.push_back("chatv11.chromium.org");
354 sub_protocols.push_back("chatv20.chromium.org");
355 CreateAndConnectStandard("ws://localhost/testing_path",
356 "/testing_path",
357 sub_protocols,
358 "http://google.com",
359 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
360 "chatv20.chromium.org\r\n",
361 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n");
362 RunUntilIdle();
363 EXPECT_TRUE(stream_);
364 EXPECT_FALSE(has_failed());
365 EXPECT_EQ("chatv20.chromium.org", stream_->GetSubProtocol());
368 // Unsolicited sub-protocols are rejected.
369 TEST_F(WebSocketStreamCreateTest, UnsolicitedSubProtocol) {
370 CreateAndConnectStandard("ws://localhost/testing_path",
371 "/testing_path",
372 NoSubProtocols(),
373 "http://google.com",
375 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n");
376 RunUntilIdle();
377 EXPECT_FALSE(stream_);
378 EXPECT_TRUE(has_failed());
379 EXPECT_EQ("Error during WebSocket handshake: "
380 "Response must not include 'Sec-WebSocket-Protocol' header "
381 "if not present in request: chatv20.chromium.org",
382 failure_message());
385 // Missing sub-protocol response is rejected.
386 TEST_F(WebSocketStreamCreateTest, UnacceptedSubProtocol) {
387 std::vector<std::string> sub_protocols;
388 sub_protocols.push_back("chat.example.com");
389 CreateAndConnectStandard("ws://localhost/testing_path",
390 "/testing_path",
391 sub_protocols,
392 "http://localhost",
393 "Sec-WebSocket-Protocol: chat.example.com\r\n",
394 "");
395 RunUntilIdle();
396 EXPECT_FALSE(stream_);
397 EXPECT_TRUE(has_failed());
398 EXPECT_EQ("Error during WebSocket handshake: "
399 "Sent non-empty 'Sec-WebSocket-Protocol' header "
400 "but no response was received",
401 failure_message());
404 // Only one sub-protocol can be accepted.
405 TEST_F(WebSocketStreamCreateTest, MultipleSubProtocolsInResponse) {
406 std::vector<std::string> sub_protocols;
407 sub_protocols.push_back("chatv11.chromium.org");
408 sub_protocols.push_back("chatv20.chromium.org");
409 CreateAndConnectStandard("ws://localhost/testing_path",
410 "/testing_path",
411 sub_protocols,
412 "http://google.com",
413 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
414 "chatv20.chromium.org\r\n",
415 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
416 "chatv20.chromium.org\r\n");
417 RunUntilIdle();
418 EXPECT_FALSE(stream_);
419 EXPECT_TRUE(has_failed());
420 EXPECT_EQ("Error during WebSocket handshake: "
421 "'Sec-WebSocket-Protocol' header must not appear "
422 "more than once in a response",
423 failure_message());
426 // Unmatched sub-protocol should be rejected.
427 TEST_F(WebSocketStreamCreateTest, UnmatchedSubProtocolInResponse) {
428 std::vector<std::string> sub_protocols;
429 sub_protocols.push_back("chatv11.chromium.org");
430 sub_protocols.push_back("chatv20.chromium.org");
431 CreateAndConnectStandard("ws://localhost/testing_path",
432 "/testing_path",
433 sub_protocols,
434 "http://google.com",
435 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
436 "chatv20.chromium.org\r\n",
437 "Sec-WebSocket-Protocol: chatv21.chromium.org\r\n");
438 RunUntilIdle();
439 EXPECT_FALSE(stream_);
440 EXPECT_TRUE(has_failed());
441 EXPECT_EQ("Error during WebSocket handshake: "
442 "'Sec-WebSocket-Protocol' header value 'chatv21.chromium.org' "
443 "in response does not match any of sent values",
444 failure_message());
447 // permessage-deflate extension basic success case.
448 TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateSuccess) {
449 CreateAndConnectWithExtensions("permessage-deflate");
450 EXPECT_TRUE(stream_);
451 EXPECT_FALSE(has_failed());
454 // permessage-deflate extensions success with all parameters.
455 TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateParamsSuccess) {
456 CreateAndConnectWithExtensions(
457 "permessage-deflate; client_no_context_takeover; "
458 "server_max_window_bits=11; client_max_window_bits=13; "
459 "server_no_context_takeover");
460 EXPECT_TRUE(stream_);
461 EXPECT_FALSE(has_failed());
464 // Verify that incoming messages are actually decompressed with
465 // permessage-deflate enabled.
466 TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateInflates) {
467 CreateAndConnectCustomResponse(
468 "ws://localhost/testing_path",
469 "/testing_path",
470 NoSubProtocols(),
471 "http://localhost",
473 WebSocketStandardResponse(
474 "Sec-WebSocket-Extensions: permessage-deflate\r\n") +
475 std::string(
476 "\xc1\x07" // WebSocket header (FIN + RSV1, Text payload 7 bytes)
477 "\xf2\x48\xcd\xc9\xc9\x07\x00", // "Hello" DEFLATE compressed
478 9));
479 RunUntilIdle();
481 ASSERT_TRUE(stream_);
482 ScopedVector<WebSocketFrame> frames;
483 CompletionCallback callback;
484 ASSERT_EQ(OK, stream_->ReadFrames(&frames, callback));
485 ASSERT_EQ(1U, frames.size());
486 ASSERT_EQ(5U, frames[0]->header.payload_length);
487 EXPECT_EQ("Hello", std::string(frames[0]->data->data(), 5));
490 // Unknown extension in the response is rejected
491 TEST_F(WebSocketStreamCreateExtensionTest, UnknownExtension) {
492 CreateAndConnectWithExtensions("x-unknown-extension");
493 EXPECT_FALSE(stream_);
494 EXPECT_TRUE(has_failed());
495 EXPECT_EQ("Error during WebSocket handshake: "
496 "Found an unsupported extension 'x-unknown-extension' "
497 "in 'Sec-WebSocket-Extensions' header",
498 failure_message());
501 // Malformed extensions are rejected (this file does not cover all possible
502 // parse failures, as the parser is covered thoroughly by its own unit tests).
503 TEST_F(WebSocketStreamCreateExtensionTest, MalformedExtension) {
504 CreateAndConnectWithExtensions(";");
505 EXPECT_FALSE(stream_);
506 EXPECT_TRUE(has_failed());
507 EXPECT_EQ(
508 "Error during WebSocket handshake: 'Sec-WebSocket-Extensions' header "
509 "value is rejected by the parser: ;",
510 failure_message());
513 // The permessage-deflate extension may only be specified once.
514 TEST_F(WebSocketStreamCreateExtensionTest, OnlyOnePerMessageDeflateAllowed) {
515 CreateAndConnectWithExtensions(
516 "permessage-deflate, permessage-deflate; client_max_window_bits=10");
517 EXPECT_FALSE(stream_);
518 EXPECT_TRUE(has_failed());
519 EXPECT_EQ(
520 "Error during WebSocket handshake: "
521 "Received duplicate permessage-deflate response",
522 failure_message());
525 // permessage-deflate parameters may not be duplicated.
526 TEST_F(WebSocketStreamCreateExtensionTest, NoDuplicateParameters) {
527 CreateAndConnectWithExtensions(
528 "permessage-deflate; client_no_context_takeover; "
529 "client_no_context_takeover");
530 EXPECT_FALSE(stream_);
531 EXPECT_TRUE(has_failed());
532 EXPECT_EQ(
533 "Error during WebSocket handshake: Error in permessage-deflate: "
534 "Received duplicate permessage-deflate extension parameter "
535 "client_no_context_takeover",
536 failure_message());
539 // permessage-deflate parameters must start with "client_" or "server_"
540 TEST_F(WebSocketStreamCreateExtensionTest, BadParameterPrefix) {
541 CreateAndConnectWithExtensions(
542 "permessage-deflate; absurd_no_context_takeover");
543 EXPECT_FALSE(stream_);
544 EXPECT_TRUE(has_failed());
545 EXPECT_EQ(
546 "Error during WebSocket handshake: Error in permessage-deflate: "
547 "Received an unexpected permessage-deflate extension parameter",
548 failure_message());
551 // permessage-deflate parameters must be either *_no_context_takeover or
552 // *_max_window_bits
553 TEST_F(WebSocketStreamCreateExtensionTest, BadParameterSuffix) {
554 CreateAndConnectWithExtensions(
555 "permessage-deflate; client_max_content_bits=5");
556 EXPECT_FALSE(stream_);
557 EXPECT_TRUE(has_failed());
558 EXPECT_EQ(
559 "Error during WebSocket handshake: Error in permessage-deflate: "
560 "Received an unexpected permessage-deflate extension parameter",
561 failure_message());
564 // *_no_context_takeover parameters must not have an argument
565 TEST_F(WebSocketStreamCreateExtensionTest, BadParameterValue) {
566 CreateAndConnectWithExtensions(
567 "permessage-deflate; client_no_context_takeover=true");
568 EXPECT_FALSE(stream_);
569 EXPECT_TRUE(has_failed());
570 EXPECT_EQ(
571 "Error during WebSocket handshake: Error in permessage-deflate: "
572 "Received invalid client_no_context_takeover parameter",
573 failure_message());
576 // *_max_window_bits must have an argument
577 TEST_F(WebSocketStreamCreateExtensionTest, NoMaxWindowBitsArgument) {
578 CreateAndConnectWithExtensions("permessage-deflate; client_max_window_bits");
579 EXPECT_FALSE(stream_);
580 EXPECT_TRUE(has_failed());
581 EXPECT_EQ(
582 "Error during WebSocket handshake: Error in permessage-deflate: "
583 "client_max_window_bits must have value",
584 failure_message());
587 // *_max_window_bits must be an integer
588 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueInteger) {
589 CreateAndConnectWithExtensions(
590 "permessage-deflate; server_max_window_bits=banana");
591 EXPECT_FALSE(stream_);
592 EXPECT_TRUE(has_failed());
593 EXPECT_EQ(
594 "Error during WebSocket handshake: Error in permessage-deflate: "
595 "Received invalid server_max_window_bits parameter",
596 failure_message());
599 // *_max_window_bits must be >= 8
600 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueTooSmall) {
601 CreateAndConnectWithExtensions(
602 "permessage-deflate; server_max_window_bits=7");
603 EXPECT_FALSE(stream_);
604 EXPECT_TRUE(has_failed());
605 EXPECT_EQ(
606 "Error during WebSocket handshake: Error in permessage-deflate: "
607 "Received invalid server_max_window_bits parameter",
608 failure_message());
611 // *_max_window_bits must be <= 15
612 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueTooBig) {
613 CreateAndConnectWithExtensions(
614 "permessage-deflate; client_max_window_bits=16");
615 EXPECT_FALSE(stream_);
616 EXPECT_TRUE(has_failed());
617 EXPECT_EQ(
618 "Error during WebSocket handshake: Error in permessage-deflate: "
619 "Received invalid client_max_window_bits parameter",
620 failure_message());
623 // *_max_window_bits must not start with 0
624 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueStartsWithZero) {
625 CreateAndConnectWithExtensions(
626 "permessage-deflate; client_max_window_bits=08");
627 EXPECT_FALSE(stream_);
628 EXPECT_TRUE(has_failed());
629 EXPECT_EQ(
630 "Error during WebSocket handshake: Error in permessage-deflate: "
631 "Received invalid client_max_window_bits parameter",
632 failure_message());
635 // *_max_window_bits must not start with +
636 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueStartsWithPlus) {
637 CreateAndConnectWithExtensions(
638 "permessage-deflate; server_max_window_bits=+9");
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 // TODO(ricea): Check that WebSocketDeflateStream is initialised with the
648 // arguments from the server. This is difficult because the data written to the
649 // socket is randomly masked.
651 // Additional Sec-WebSocket-Accept headers should be rejected.
652 TEST_F(WebSocketStreamCreateTest, DoubleAccept) {
653 CreateAndConnectStandard(
654 "ws://localhost/",
655 "/",
656 NoSubProtocols(),
657 "http://localhost",
659 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n");
660 RunUntilIdle();
661 EXPECT_FALSE(stream_);
662 EXPECT_TRUE(has_failed());
663 EXPECT_EQ("Error during WebSocket handshake: "
664 "'Sec-WebSocket-Accept' header must not appear "
665 "more than once in a response",
666 failure_message());
669 // Response code 200 must be rejected.
670 TEST_F(WebSocketStreamCreateTest, InvalidStatusCode) {
671 static const char kInvalidStatusCodeResponse[] =
672 "HTTP/1.1 200 OK\r\n"
673 "Upgrade: websocket\r\n"
674 "Connection: Upgrade\r\n"
675 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
676 "\r\n";
677 CreateAndConnectCustomResponse("ws://localhost/",
678 "/",
679 NoSubProtocols(),
680 "http://localhost",
682 kInvalidStatusCodeResponse);
683 RunUntilIdle();
684 EXPECT_TRUE(has_failed());
685 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 200",
686 failure_message());
689 // Redirects are not followed (according to the WHATWG WebSocket API, which
690 // overrides RFC6455 for browser applications).
691 TEST_F(WebSocketStreamCreateTest, RedirectsRejected) {
692 static const char kRedirectResponse[] =
693 "HTTP/1.1 302 Moved Temporarily\r\n"
694 "Content-Type: text/html\r\n"
695 "Content-Length: 34\r\n"
696 "Connection: keep-alive\r\n"
697 "Location: ws://localhost/other\r\n"
698 "\r\n"
699 "<title>Moved</title><h1>Moved</h1>";
700 CreateAndConnectCustomResponse("ws://localhost/",
701 "/",
702 NoSubProtocols(),
703 "http://localhost",
705 kRedirectResponse);
706 RunUntilIdle();
707 EXPECT_TRUE(has_failed());
708 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 302",
709 failure_message());
712 // Malformed responses should be rejected. HttpStreamParser will accept just
713 // about any garbage in the middle of the headers. To make it give up, the junk
714 // has to be at the start of the response. Even then, it just gets treated as an
715 // HTTP/0.9 response.
716 TEST_F(WebSocketStreamCreateTest, MalformedResponse) {
717 static const char kMalformedResponse[] =
718 "220 mx.google.com ESMTP\r\n"
719 "HTTP/1.1 101 OK\r\n"
720 "Upgrade: websocket\r\n"
721 "Connection: Upgrade\r\n"
722 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
723 "\r\n";
724 CreateAndConnectCustomResponse("ws://localhost/",
725 "/",
726 NoSubProtocols(),
727 "http://localhost",
729 kMalformedResponse);
730 RunUntilIdle();
731 EXPECT_TRUE(has_failed());
732 EXPECT_EQ("Error during WebSocket handshake: Invalid status line",
733 failure_message());
736 // Upgrade header must be present.
737 TEST_F(WebSocketStreamCreateTest, MissingUpgradeHeader) {
738 static const char kMissingUpgradeResponse[] =
739 "HTTP/1.1 101 Switching Protocols\r\n"
740 "Connection: Upgrade\r\n"
741 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
742 "\r\n";
743 CreateAndConnectCustomResponse("ws://localhost/",
744 "/",
745 NoSubProtocols(),
746 "http://localhost",
748 kMissingUpgradeResponse);
749 RunUntilIdle();
750 EXPECT_TRUE(has_failed());
751 EXPECT_EQ("Error during WebSocket handshake: 'Upgrade' header is missing",
752 failure_message());
755 // There must only be one upgrade header.
756 TEST_F(WebSocketStreamCreateTest, DoubleUpgradeHeader) {
757 CreateAndConnectStandard(
758 "ws://localhost/",
759 "/",
760 NoSubProtocols(),
761 "http://localhost",
762 "", "Upgrade: HTTP/2.0\r\n");
763 RunUntilIdle();
764 EXPECT_TRUE(has_failed());
765 EXPECT_EQ("Error during WebSocket handshake: "
766 "'Upgrade' header must not appear more than once in a response",
767 failure_message());
770 // There must only be one correct upgrade header.
771 TEST_F(WebSocketStreamCreateTest, IncorrectUpgradeHeader) {
772 static const char kMissingUpgradeResponse[] =
773 "HTTP/1.1 101 Switching Protocols\r\n"
774 "Connection: Upgrade\r\n"
775 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
776 "Upgrade: hogefuga\r\n"
777 "\r\n";
778 CreateAndConnectCustomResponse("ws://localhost/",
779 "/",
780 NoSubProtocols(),
781 "http://localhost",
783 kMissingUpgradeResponse);
784 RunUntilIdle();
785 EXPECT_TRUE(has_failed());
786 EXPECT_EQ("Error during WebSocket handshake: "
787 "'Upgrade' header value is not 'WebSocket': hogefuga",
788 failure_message());
791 // Connection header must be present.
792 TEST_F(WebSocketStreamCreateTest, MissingConnectionHeader) {
793 static const char kMissingConnectionResponse[] =
794 "HTTP/1.1 101 Switching Protocols\r\n"
795 "Upgrade: websocket\r\n"
796 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
797 "\r\n";
798 CreateAndConnectCustomResponse("ws://localhost/",
799 "/",
800 NoSubProtocols(),
801 "http://localhost",
803 kMissingConnectionResponse);
804 RunUntilIdle();
805 EXPECT_TRUE(has_failed());
806 EXPECT_EQ("Error during WebSocket handshake: "
807 "'Connection' header is missing",
808 failure_message());
811 // Connection header must contain "Upgrade".
812 TEST_F(WebSocketStreamCreateTest, IncorrectConnectionHeader) {
813 static const char kMissingConnectionResponse[] =
814 "HTTP/1.1 101 Switching Protocols\r\n"
815 "Upgrade: websocket\r\n"
816 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
817 "Connection: hogefuga\r\n"
818 "\r\n";
819 CreateAndConnectCustomResponse("ws://localhost/",
820 "/",
821 NoSubProtocols(),
822 "http://localhost",
824 kMissingConnectionResponse);
825 RunUntilIdle();
826 EXPECT_TRUE(has_failed());
827 EXPECT_EQ("Error during WebSocket handshake: "
828 "'Connection' header value must contain 'Upgrade'",
829 failure_message());
832 // Connection header is permitted to contain other tokens.
833 TEST_F(WebSocketStreamCreateTest, AdditionalTokenInConnectionHeader) {
834 static const char kAdditionalConnectionTokenResponse[] =
835 "HTTP/1.1 101 Switching Protocols\r\n"
836 "Upgrade: websocket\r\n"
837 "Connection: Upgrade, Keep-Alive\r\n"
838 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
839 "\r\n";
840 CreateAndConnectCustomResponse("ws://localhost/",
841 "/",
842 NoSubProtocols(),
843 "http://localhost",
845 kAdditionalConnectionTokenResponse);
846 RunUntilIdle();
847 EXPECT_FALSE(has_failed());
848 EXPECT_TRUE(stream_);
851 // Sec-WebSocket-Accept header must be present.
852 TEST_F(WebSocketStreamCreateTest, MissingSecWebSocketAccept) {
853 static const char kMissingAcceptResponse[] =
854 "HTTP/1.1 101 Switching Protocols\r\n"
855 "Upgrade: websocket\r\n"
856 "Connection: Upgrade\r\n"
857 "\r\n";
858 CreateAndConnectCustomResponse("ws://localhost/",
859 "/",
860 NoSubProtocols(),
861 "http://localhost",
863 kMissingAcceptResponse);
864 RunUntilIdle();
865 EXPECT_TRUE(has_failed());
866 EXPECT_EQ("Error during WebSocket handshake: "
867 "'Sec-WebSocket-Accept' header is missing",
868 failure_message());
871 // Sec-WebSocket-Accept header must match the key that was sent.
872 TEST_F(WebSocketStreamCreateTest, WrongSecWebSocketAccept) {
873 static const char kIncorrectAcceptResponse[] =
874 "HTTP/1.1 101 Switching Protocols\r\n"
875 "Upgrade: websocket\r\n"
876 "Connection: Upgrade\r\n"
877 "Sec-WebSocket-Accept: x/byyPZ2tOFvJCGkkugcKvqhhPk=\r\n"
878 "\r\n";
879 CreateAndConnectCustomResponse("ws://localhost/",
880 "/",
881 NoSubProtocols(),
882 "http://localhost",
884 kIncorrectAcceptResponse);
885 RunUntilIdle();
886 EXPECT_TRUE(has_failed());
887 EXPECT_EQ("Error during WebSocket handshake: "
888 "Incorrect 'Sec-WebSocket-Accept' header value",
889 failure_message());
892 // Cancellation works.
893 TEST_F(WebSocketStreamCreateTest, Cancellation) {
894 CreateAndConnectStandard(
895 "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
896 stream_request_.reset();
897 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(
907 new DeterministicSocketData(NULL, 0, NULL, 0));
908 socket_data->set_connect_data(
909 MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED));
910 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
911 "http://localhost", socket_data.Pass());
912 RunUntilIdle();
913 EXPECT_TRUE(has_failed());
914 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED",
915 failure_message());
916 EXPECT_FALSE(request_info_);
917 EXPECT_FALSE(response_info_);
920 // Connect timeout must look just like any other failure.
921 TEST_F(WebSocketStreamCreateTest, ConnectionTimeout) {
922 scoped_ptr<DeterministicSocketData> socket_data(
923 new DeterministicSocketData(NULL, 0, NULL, 0));
924 socket_data->set_connect_data(
925 MockConnect(ASYNC, ERR_CONNECTION_TIMED_OUT));
926 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
927 "http://localhost", socket_data.Pass());
928 RunUntilIdle();
929 EXPECT_TRUE(has_failed());
930 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_TIMED_OUT",
931 failure_message());
934 // Cancellation during connect works.
935 TEST_F(WebSocketStreamCreateTest, CancellationDuringConnect) {
936 scoped_ptr<DeterministicSocketData> socket_data(
937 new DeterministicSocketData(NULL, 0, NULL, 0));
938 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING));
939 CreateAndConnectRawExpectations("ws://localhost/",
940 NoSubProtocols(),
941 "http://localhost",
942 socket_data.Pass());
943 stream_request_.reset();
944 RunUntilIdle();
945 EXPECT_FALSE(has_failed());
946 EXPECT_FALSE(stream_);
949 // Cancellation during write of the request headers works.
950 TEST_F(WebSocketStreamCreateTest, CancellationDuringWrite) {
951 // We seem to need at least two operations in order to use SetStop().
952 MockWrite writes[] = {MockWrite(ASYNC, 0, "GET / HTTP/"),
953 MockWrite(ASYNC, 1, "1.1\r\n")};
954 // We keep a copy of the pointer so that we can call RunFor() on it later.
955 DeterministicSocketData* socket_data(
956 new DeterministicSocketData(NULL, 0, writes, arraysize(writes)));
957 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
958 socket_data->SetStop(1);
959 CreateAndConnectRawExpectations("ws://localhost/",
960 NoSubProtocols(),
961 "http://localhost",
962 make_scoped_ptr(socket_data));
963 socket_data->Run();
964 stream_request_.reset();
965 RunUntilIdle();
966 EXPECT_FALSE(has_failed());
967 EXPECT_FALSE(stream_);
968 EXPECT_TRUE(request_info_);
969 EXPECT_FALSE(response_info_);
972 // Cancellation during read of the response headers works.
973 TEST_F(WebSocketStreamCreateTest, CancellationDuringRead) {
974 std::string request = WebSocketStandardRequest("/", "http://localhost", "");
975 MockWrite writes[] = {MockWrite(ASYNC, 0, request.c_str())};
976 MockRead reads[] = {
977 MockRead(ASYNC, 1, "HTTP/1.1 101 Switching Protocols\r\nUpgr"),
979 DeterministicSocketData* socket_data(new DeterministicSocketData(
980 reads, arraysize(reads), writes, arraysize(writes)));
981 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
982 socket_data->SetStop(1);
983 CreateAndConnectRawExpectations("ws://localhost/",
984 NoSubProtocols(),
985 "http://localhost",
986 make_scoped_ptr(socket_data));
987 socket_data->Run();
988 stream_request_.reset();
989 RunUntilIdle();
990 EXPECT_FALSE(has_failed());
991 EXPECT_FALSE(stream_);
992 EXPECT_TRUE(request_info_);
993 EXPECT_FALSE(response_info_);
996 // Over-size response headers (> 256KB) should not cause a crash. This is a
997 // regression test for crbug.com/339456. It is based on the layout test
998 // "cookie-flood.html".
999 TEST_F(WebSocketStreamCreateTest, VeryLargeResponseHeaders) {
1000 std::string set_cookie_headers;
1001 set_cookie_headers.reserve(45 * 10000);
1002 for (int i = 0; i < 10000; ++i) {
1003 set_cookie_headers +=
1004 base::StringPrintf("Set-Cookie: WK-websocket-test-flood-%d=1\r\n", i);
1006 CreateAndConnectStandard("ws://localhost/", "/", NoSubProtocols(),
1007 "http://localhost", "", set_cookie_headers);
1008 RunUntilIdle();
1009 EXPECT_TRUE(has_failed());
1010 EXPECT_FALSE(response_info_);
1013 // If the remote host closes the connection without sending headers, we should
1014 // log the console message "Connection closed before receiving a handshake
1015 // response".
1016 TEST_F(WebSocketStreamCreateTest, NoResponse) {
1017 std::string request = WebSocketStandardRequest("/", "http://localhost", "");
1018 MockWrite writes[] = {MockWrite(ASYNC, request.data(), request.size(), 0)};
1019 MockRead reads[] = {MockRead(ASYNC, 0, 1)};
1020 DeterministicSocketData* socket_data(new DeterministicSocketData(
1021 reads, arraysize(reads), writes, arraysize(writes)));
1022 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
1023 CreateAndConnectRawExpectations("ws://localhost/",
1024 NoSubProtocols(),
1025 "http://localhost",
1026 make_scoped_ptr(socket_data));
1027 socket_data->RunFor(2);
1028 EXPECT_TRUE(has_failed());
1029 EXPECT_FALSE(stream_);
1030 EXPECT_FALSE(response_info_);
1031 EXPECT_EQ("Connection closed before receiving a handshake response",
1032 failure_message());
1035 TEST_F(WebSocketStreamCreateUMATest, Incomplete) {
1036 const std::string name("Net.WebSocket.HandshakeResult");
1037 scoped_ptr<base::HistogramSamples> original(GetSamples(name));
1040 StreamCreation creation;
1041 creation.CreateAndConnectStandard("ws://localhost/",
1042 "/",
1043 creation.NoSubProtocols(),
1044 "http://localhost",
1046 "");
1049 scoped_ptr<base::HistogramSamples> samples(GetSamples(name));
1050 ASSERT_TRUE(samples);
1051 if (original) {
1052 samples->Subtract(*original); // Cancel the original values.
1054 EXPECT_EQ(1, samples->GetCount(INCOMPLETE));
1055 EXPECT_EQ(0, samples->GetCount(CONNECTED));
1056 EXPECT_EQ(0, samples->GetCount(FAILED));
1059 TEST_F(WebSocketStreamCreateUMATest, Connected) {
1060 const std::string name("Net.WebSocket.HandshakeResult");
1061 scoped_ptr<base::HistogramSamples> original(GetSamples(name));
1064 StreamCreation creation;
1065 creation.CreateAndConnectStandard("ws://localhost/",
1066 "/",
1067 creation.NoSubProtocols(),
1068 "http://localhost",
1070 "");
1071 creation.RunUntilIdle();
1074 scoped_ptr<base::HistogramSamples> samples(GetSamples(name));
1075 ASSERT_TRUE(samples);
1076 if (original) {
1077 samples->Subtract(*original); // Cancel the original values.
1079 EXPECT_EQ(0, samples->GetCount(INCOMPLETE));
1080 EXPECT_EQ(1, samples->GetCount(CONNECTED));
1081 EXPECT_EQ(0, samples->GetCount(FAILED));
1084 TEST_F(WebSocketStreamCreateUMATest, Failed) {
1085 const std::string name("Net.WebSocket.HandshakeResult");
1086 scoped_ptr<base::HistogramSamples> original(GetSamples(name));
1089 StreamCreation creation;
1090 static const char kInvalidStatusCodeResponse[] =
1091 "HTTP/1.1 200 OK\r\n"
1092 "Upgrade: websocket\r\n"
1093 "Connection: Upgrade\r\n"
1094 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
1095 "\r\n";
1096 creation.CreateAndConnectCustomResponse("ws://localhost/",
1097 "/",
1098 creation.NoSubProtocols(),
1099 "http://localhost",
1101 kInvalidStatusCodeResponse);
1102 creation.RunUntilIdle();
1105 scoped_ptr<base::HistogramSamples> samples(GetSamples(name));
1106 ASSERT_TRUE(samples);
1107 if (original) {
1108 samples->Subtract(*original); // Cancel the original values.
1110 EXPECT_EQ(0, samples->GetCount(INCOMPLETE));
1111 EXPECT_EQ(0, samples->GetCount(CONNECTED));
1112 EXPECT_EQ(1, samples->GetCount(FAILED));
1115 } // namespace
1116 } // namespace net