Fix infinite recursion on hiding panel when created during fullscreen mode.
[chromium-blink-merge.git] / net / websockets / websocket_stream_test.cc
blob8947d33e1f5dec9a28710a1360435b3172b2305a
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/run_loop.h"
14 #include "base/strings/stringprintf.h"
15 #include "net/base/net_errors.h"
16 #include "net/http/http_request_headers.h"
17 #include "net/http/http_response_headers.h"
18 #include "net/socket/client_socket_handle.h"
19 #include "net/socket/socket_test_util.h"
20 #include "net/url_request/url_request_test_util.h"
21 #include "net/websockets/websocket_basic_handshake_stream.h"
22 #include "net/websockets/websocket_frame.h"
23 #include "net/websockets/websocket_handshake_request_info.h"
24 #include "net/websockets/websocket_handshake_response_info.h"
25 #include "net/websockets/websocket_handshake_stream_create_helper.h"
26 #include "net/websockets/websocket_test_util.h"
27 #include "testing/gtest/include/gtest/gtest.h"
28 #include "url/gurl.h"
29 #include "url/origin.h"
31 namespace net {
32 namespace {
34 typedef std::pair<std::string, std::string> HeaderKeyValuePair;
36 std::vector<HeaderKeyValuePair> ToVector(const HttpRequestHeaders& headers) {
37 HttpRequestHeaders::Iterator it(headers);
38 std::vector<HeaderKeyValuePair> result;
39 while (it.GetNext())
40 result.push_back(HeaderKeyValuePair(it.name(), it.value()));
41 return result;
44 std::vector<HeaderKeyValuePair> ToVector(const HttpResponseHeaders& headers) {
45 void* iter = NULL;
46 std::string name, value;
47 std::vector<HeaderKeyValuePair> result;
48 while (headers.EnumerateHeaderLines(&iter, &name, &value))
49 result.push_back(HeaderKeyValuePair(name, value));
50 return result;
53 // A sub-class of WebSocketHandshakeStreamCreateHelper which always sets a
54 // deterministic key to use in the WebSocket handshake.
55 class DeterministicKeyWebSocketHandshakeStreamCreateHelper
56 : public WebSocketHandshakeStreamCreateHelper {
57 public:
58 DeterministicKeyWebSocketHandshakeStreamCreateHelper(
59 WebSocketStream::ConnectDelegate* connect_delegate,
60 const std::vector<std::string>& requested_subprotocols)
61 : WebSocketHandshakeStreamCreateHelper(connect_delegate,
62 requested_subprotocols) {}
64 virtual WebSocketHandshakeStreamBase* CreateBasicStream(
65 scoped_ptr<ClientSocketHandle> connection,
66 bool using_proxy) OVERRIDE {
67 WebSocketHandshakeStreamCreateHelper::CreateBasicStream(connection.Pass(),
68 using_proxy);
69 // This will break in an obvious way if the type created by
70 // CreateBasicStream() changes.
71 static_cast<WebSocketBasicHandshakeStream*>(stream())
72 ->SetWebSocketKeyForTesting("dGhlIHNhbXBsZSBub25jZQ==");
73 return stream();
77 class WebSocketStreamCreateTest : public ::testing::Test {
78 protected:
79 WebSocketStreamCreateTest(): has_failed_(false) {}
81 void CreateAndConnectCustomResponse(
82 const std::string& socket_url,
83 const std::string& socket_path,
84 const std::vector<std::string>& sub_protocols,
85 const std::string& origin,
86 const std::string& extra_request_headers,
87 const std::string& response_body) {
88 url_request_context_host_.SetExpectations(
89 WebSocketStandardRequest(socket_path, origin, extra_request_headers),
90 response_body);
91 CreateAndConnectStream(socket_url, sub_protocols, origin);
94 // |extra_request_headers| and |extra_response_headers| must end in "\r\n" or
95 // errors like "Unable to perform synchronous IO while stopped" will occur.
96 void CreateAndConnectStandard(const std::string& socket_url,
97 const std::string& socket_path,
98 const std::vector<std::string>& sub_protocols,
99 const std::string& origin,
100 const std::string& extra_request_headers,
101 const std::string& extra_response_headers) {
102 CreateAndConnectCustomResponse(
103 socket_url,
104 socket_path,
105 sub_protocols,
106 origin,
107 extra_request_headers,
108 WebSocketStandardResponse(extra_response_headers));
111 void CreateAndConnectRawExpectations(
112 const std::string& socket_url,
113 const std::vector<std::string>& sub_protocols,
114 const std::string& origin,
115 scoped_ptr<DeterministicSocketData> socket_data) {
116 url_request_context_host_.SetRawExpectations(socket_data.Pass());
117 CreateAndConnectStream(socket_url, sub_protocols, origin);
120 // A wrapper for CreateAndConnectStreamForTesting that knows about our default
121 // parameters.
122 void CreateAndConnectStream(const std::string& socket_url,
123 const std::vector<std::string>& sub_protocols,
124 const std::string& origin) {
125 scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate(
126 new TestConnectDelegate(this));
127 WebSocketStream::ConnectDelegate* delegate = connect_delegate.get();
128 stream_request_ = ::net::CreateAndConnectStreamForTesting(
129 GURL(socket_url),
130 scoped_ptr<WebSocketHandshakeStreamCreateHelper>(
131 new DeterministicKeyWebSocketHandshakeStreamCreateHelper(
132 delegate, sub_protocols)),
133 url::Origin(origin),
134 url_request_context_host_.GetURLRequestContext(),
135 BoundNetLog(),
136 connect_delegate.Pass());
139 static void RunUntilIdle() { base::RunLoop().RunUntilIdle(); }
141 // A simple function to make the tests more readable. Creates an empty vector.
142 static std::vector<std::string> NoSubProtocols() {
143 return std::vector<std::string>();
146 const std::string& failure_message() const { return failure_message_; }
147 bool has_failed() const { return has_failed_; }
149 class TestConnectDelegate : public WebSocketStream::ConnectDelegate {
150 public:
151 explicit TestConnectDelegate(WebSocketStreamCreateTest* owner)
152 : owner_(owner) {}
154 virtual void OnSuccess(scoped_ptr<WebSocketStream> stream) OVERRIDE {
155 stream.swap(owner_->stream_);
158 virtual void OnFailure(const std::string& message) OVERRIDE {
159 owner_->has_failed_ = true;
160 owner_->failure_message_ = message;
163 virtual void OnStartOpeningHandshake(
164 scoped_ptr<WebSocketHandshakeRequestInfo> request) OVERRIDE {
165 if (owner_->request_info_)
166 ADD_FAILURE();
167 owner_->request_info_ = request.Pass();
169 virtual void OnFinishOpeningHandshake(
170 scoped_ptr<WebSocketHandshakeResponseInfo> response) OVERRIDE {
171 if (owner_->response_info_)
172 ADD_FAILURE();
173 owner_->response_info_ = response.Pass();
176 private:
177 WebSocketStreamCreateTest* owner_;
180 WebSocketTestURLRequestContextHost url_request_context_host_;
181 scoped_ptr<WebSocketStreamRequest> stream_request_;
182 // Only set if the connection succeeded.
183 scoped_ptr<WebSocketStream> stream_;
184 // Only set if the connection failed.
185 std::string failure_message_;
186 bool has_failed_;
187 scoped_ptr<WebSocketHandshakeRequestInfo> request_info_;
188 scoped_ptr<WebSocketHandshakeResponseInfo> response_info_;
191 // There are enough tests of the Sec-WebSocket-Extensions header that they
192 // deserve their own test fixture.
193 class WebSocketStreamCreateExtensionTest : public WebSocketStreamCreateTest {
194 public:
195 // Performs a standard connect, with the value of the Sec-WebSocket-Extensions
196 // header in the response set to |extensions_header_value|. Runs the event
197 // loop to allow the connect to complete.
198 void CreateAndConnectWithExtensions(
199 const std::string& extensions_header_value) {
200 CreateAndConnectStandard(
201 "ws://localhost/testing_path",
202 "/testing_path",
203 NoSubProtocols(),
204 "http://localhost",
206 "Sec-WebSocket-Extensions: " + extensions_header_value + "\r\n");
207 RunUntilIdle();
211 // Confirm that the basic case works as expected.
212 TEST_F(WebSocketStreamCreateTest, SimpleSuccess) {
213 CreateAndConnectStandard(
214 "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
215 EXPECT_FALSE(request_info_);
216 EXPECT_FALSE(response_info_);
217 RunUntilIdle();
218 EXPECT_FALSE(has_failed());
219 EXPECT_TRUE(stream_);
220 EXPECT_TRUE(request_info_);
221 EXPECT_TRUE(response_info_);
224 TEST_F(WebSocketStreamCreateTest, HandshakeInfo) {
225 static const char kResponse[] =
226 "HTTP/1.1 101 Switching Protocols\r\n"
227 "Upgrade: websocket\r\n"
228 "Connection: Upgrade\r\n"
229 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
230 "foo: bar, baz\r\n"
231 "hoge: fuga\r\n"
232 "hoge: piyo\r\n"
233 "\r\n";
235 CreateAndConnectCustomResponse(
236 "ws://localhost/",
237 "/",
238 NoSubProtocols(),
239 "http://localhost",
241 kResponse);
242 EXPECT_FALSE(request_info_);
243 EXPECT_FALSE(response_info_);
244 RunUntilIdle();
245 EXPECT_TRUE(stream_);
246 ASSERT_TRUE(request_info_);
247 ASSERT_TRUE(response_info_);
248 std::vector<HeaderKeyValuePair> request_headers =
249 ToVector(request_info_->headers);
250 // We examine the contents of request_info_ and response_info_
251 // mainly only in this test case.
252 EXPECT_EQ(GURL("ws://localhost/"), request_info_->url);
253 EXPECT_EQ(GURL("ws://localhost/"), response_info_->url);
254 EXPECT_EQ(101, response_info_->status_code);
255 EXPECT_EQ("Switching Protocols", response_info_->status_text);
256 ASSERT_EQ(12u, request_headers.size());
257 EXPECT_EQ(HeaderKeyValuePair("Host", "localhost"), request_headers[0]);
258 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), request_headers[1]);
259 EXPECT_EQ(HeaderKeyValuePair("Pragma", "no-cache"), request_headers[2]);
260 EXPECT_EQ(HeaderKeyValuePair("Cache-Control", "no-cache"),
261 request_headers[3]);
262 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), request_headers[4]);
263 EXPECT_EQ(HeaderKeyValuePair("Origin", "http://localhost"),
264 request_headers[5]);
265 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Version", "13"),
266 request_headers[6]);
267 EXPECT_EQ(HeaderKeyValuePair("User-Agent", ""), request_headers[7]);
268 EXPECT_EQ(HeaderKeyValuePair("Accept-Encoding", "gzip,deflate"),
269 request_headers[8]);
270 EXPECT_EQ(HeaderKeyValuePair("Accept-Language", "en-us,fr"),
271 request_headers[9]);
272 EXPECT_EQ("Sec-WebSocket-Key", request_headers[10].first);
273 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Extensions",
274 "permessage-deflate; client_max_window_bits"),
275 request_headers[11]);
277 std::vector<HeaderKeyValuePair> response_headers =
278 ToVector(*response_info_->headers);
279 ASSERT_EQ(6u, response_headers.size());
280 // Sort the headers for ease of verification.
281 std::sort(response_headers.begin(), response_headers.end());
283 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), response_headers[0]);
284 EXPECT_EQ("Sec-WebSocket-Accept", response_headers[1].first);
285 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), response_headers[2]);
286 EXPECT_EQ(HeaderKeyValuePair("foo", "bar, baz"), response_headers[3]);
287 EXPECT_EQ(HeaderKeyValuePair("hoge", "fuga"), response_headers[4]);
288 EXPECT_EQ(HeaderKeyValuePair("hoge", "piyo"), response_headers[5]);
291 // Confirm that the stream isn't established until the message loop runs.
292 TEST_F(WebSocketStreamCreateTest, NeedsToRunLoop) {
293 CreateAndConnectStandard(
294 "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
295 EXPECT_FALSE(has_failed());
296 EXPECT_FALSE(stream_);
299 // Check the path is used.
300 TEST_F(WebSocketStreamCreateTest, PathIsUsed) {
301 CreateAndConnectStandard("ws://localhost/testing_path",
302 "/testing_path",
303 NoSubProtocols(),
304 "http://localhost",
306 "");
307 RunUntilIdle();
308 EXPECT_FALSE(has_failed());
309 EXPECT_TRUE(stream_);
312 // Check that the origin is used.
313 TEST_F(WebSocketStreamCreateTest, OriginIsUsed) {
314 CreateAndConnectStandard("ws://localhost/testing_path",
315 "/testing_path",
316 NoSubProtocols(),
317 "http://google.com",
319 "");
320 RunUntilIdle();
321 EXPECT_FALSE(has_failed());
322 EXPECT_TRUE(stream_);
325 // Check that sub-protocols are sent and parsed.
326 TEST_F(WebSocketStreamCreateTest, SubProtocolIsUsed) {
327 std::vector<std::string> sub_protocols;
328 sub_protocols.push_back("chatv11.chromium.org");
329 sub_protocols.push_back("chatv20.chromium.org");
330 CreateAndConnectStandard("ws://localhost/testing_path",
331 "/testing_path",
332 sub_protocols,
333 "http://google.com",
334 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
335 "chatv20.chromium.org\r\n",
336 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n");
337 RunUntilIdle();
338 EXPECT_TRUE(stream_);
339 EXPECT_FALSE(has_failed());
340 EXPECT_EQ("chatv20.chromium.org", stream_->GetSubProtocol());
343 // Unsolicited sub-protocols are rejected.
344 TEST_F(WebSocketStreamCreateTest, UnsolicitedSubProtocol) {
345 CreateAndConnectStandard("ws://localhost/testing_path",
346 "/testing_path",
347 NoSubProtocols(),
348 "http://google.com",
350 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n");
351 RunUntilIdle();
352 EXPECT_FALSE(stream_);
353 EXPECT_TRUE(has_failed());
354 EXPECT_EQ("Error during WebSocket handshake: "
355 "Response must not include 'Sec-WebSocket-Protocol' header "
356 "if not present in request: chatv20.chromium.org",
357 failure_message());
360 // Missing sub-protocol response is rejected.
361 TEST_F(WebSocketStreamCreateTest, UnacceptedSubProtocol) {
362 std::vector<std::string> sub_protocols;
363 sub_protocols.push_back("chat.example.com");
364 CreateAndConnectStandard("ws://localhost/testing_path",
365 "/testing_path",
366 sub_protocols,
367 "http://localhost",
368 "Sec-WebSocket-Protocol: chat.example.com\r\n",
369 "");
370 RunUntilIdle();
371 EXPECT_FALSE(stream_);
372 EXPECT_TRUE(has_failed());
373 EXPECT_EQ("Error during WebSocket handshake: "
374 "Sent non-empty 'Sec-WebSocket-Protocol' header "
375 "but no response was received",
376 failure_message());
379 // Only one sub-protocol can be accepted.
380 TEST_F(WebSocketStreamCreateTest, MultipleSubProtocolsInResponse) {
381 std::vector<std::string> sub_protocols;
382 sub_protocols.push_back("chatv11.chromium.org");
383 sub_protocols.push_back("chatv20.chromium.org");
384 CreateAndConnectStandard("ws://localhost/testing_path",
385 "/testing_path",
386 sub_protocols,
387 "http://google.com",
388 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
389 "chatv20.chromium.org\r\n",
390 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
391 "chatv20.chromium.org\r\n");
392 RunUntilIdle();
393 EXPECT_FALSE(stream_);
394 EXPECT_TRUE(has_failed());
395 EXPECT_EQ("Error during WebSocket handshake: "
396 "'Sec-WebSocket-Protocol' header must not appear "
397 "more than once in a response",
398 failure_message());
401 // Unmatched sub-protocol should be rejected.
402 TEST_F(WebSocketStreamCreateTest, UnmatchedSubProtocolInResponse) {
403 std::vector<std::string> sub_protocols;
404 sub_protocols.push_back("chatv11.chromium.org");
405 sub_protocols.push_back("chatv20.chromium.org");
406 CreateAndConnectStandard("ws://localhost/testing_path",
407 "/testing_path",
408 sub_protocols,
409 "http://google.com",
410 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
411 "chatv20.chromium.org\r\n",
412 "Sec-WebSocket-Protocol: chatv21.chromium.org\r\n");
413 RunUntilIdle();
414 EXPECT_FALSE(stream_);
415 EXPECT_TRUE(has_failed());
416 EXPECT_EQ("Error during WebSocket handshake: "
417 "'Sec-WebSocket-Protocol' header value 'chatv21.chromium.org' "
418 "in response does not match any of sent values",
419 failure_message());
422 // permessage-deflate extension basic success case.
423 TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateSuccess) {
424 CreateAndConnectWithExtensions("permessage-deflate");
425 EXPECT_TRUE(stream_);
426 EXPECT_FALSE(has_failed());
429 // permessage-deflate extensions success with all parameters.
430 TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateParamsSuccess) {
431 CreateAndConnectWithExtensions(
432 "permessage-deflate; client_no_context_takeover; "
433 "server_max_window_bits=11; client_max_window_bits=13; "
434 "server_no_context_takeover");
435 EXPECT_TRUE(stream_);
436 EXPECT_FALSE(has_failed());
439 // Verify that incoming messages are actually decompressed with
440 // permessage-deflate enabled.
441 TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateInflates) {
442 CreateAndConnectCustomResponse(
443 "ws://localhost/testing_path",
444 "/testing_path",
445 NoSubProtocols(),
446 "http://localhost",
448 WebSocketStandardResponse(
449 "Sec-WebSocket-Extensions: permessage-deflate\r\n") +
450 std::string(
451 "\xc1\x07" // WebSocket header (FIN + RSV1, Text payload 7 bytes)
452 "\xf2\x48\xcd\xc9\xc9\x07\x00", // "Hello" DEFLATE compressed
453 9));
454 RunUntilIdle();
456 ASSERT_TRUE(stream_);
457 ScopedVector<WebSocketFrame> frames;
458 CompletionCallback callback;
459 ASSERT_EQ(OK, stream_->ReadFrames(&frames, callback));
460 ASSERT_EQ(1U, frames.size());
461 ASSERT_EQ(5U, frames[0]->header.payload_length);
462 EXPECT_EQ("Hello", std::string(frames[0]->data->data(), 5));
465 // Unknown extension in the response is rejected
466 TEST_F(WebSocketStreamCreateExtensionTest, UnknownExtension) {
467 CreateAndConnectWithExtensions("x-unknown-extension");
468 EXPECT_FALSE(stream_);
469 EXPECT_TRUE(has_failed());
470 EXPECT_EQ("Error during WebSocket handshake: "
471 "Found an unsupported extension 'x-unknown-extension' "
472 "in 'Sec-WebSocket-Extensions' header",
473 failure_message());
476 // Malformed extensions are rejected (this file does not cover all possible
477 // parse failures, as the parser is covered thoroughly by its own unit tests).
478 TEST_F(WebSocketStreamCreateExtensionTest, MalformedExtension) {
479 CreateAndConnectWithExtensions(";");
480 EXPECT_FALSE(stream_);
481 EXPECT_TRUE(has_failed());
482 EXPECT_EQ(
483 "Error during WebSocket handshake: 'Sec-WebSocket-Extensions' header "
484 "value is rejected by the parser: ;",
485 failure_message());
488 // The permessage-deflate extension may only be specified once.
489 TEST_F(WebSocketStreamCreateExtensionTest, OnlyOnePerMessageDeflateAllowed) {
490 CreateAndConnectWithExtensions(
491 "permessage-deflate, permessage-deflate; client_max_window_bits=10");
492 EXPECT_FALSE(stream_);
493 EXPECT_TRUE(has_failed());
494 EXPECT_EQ(
495 "Error during WebSocket handshake: "
496 "Received duplicate permessage-deflate response",
497 failure_message());
500 // permessage-deflate parameters may not be duplicated.
501 TEST_F(WebSocketStreamCreateExtensionTest, NoDuplicateParameters) {
502 CreateAndConnectWithExtensions(
503 "permessage-deflate; client_no_context_takeover; "
504 "client_no_context_takeover");
505 EXPECT_FALSE(stream_);
506 EXPECT_TRUE(has_failed());
507 EXPECT_EQ(
508 "Error during WebSocket handshake: Error in permessage-deflate: "
509 "Received duplicate permessage-deflate extension parameter "
510 "client_no_context_takeover",
511 failure_message());
514 // permessage-deflate parameters must start with "client_" or "server_"
515 TEST_F(WebSocketStreamCreateExtensionTest, BadParameterPrefix) {
516 CreateAndConnectWithExtensions(
517 "permessage-deflate; absurd_no_context_takeover");
518 EXPECT_FALSE(stream_);
519 EXPECT_TRUE(has_failed());
520 EXPECT_EQ(
521 "Error during WebSocket handshake: Error in permessage-deflate: "
522 "Received an unexpected permessage-deflate extension parameter",
523 failure_message());
526 // permessage-deflate parameters must be either *_no_context_takeover or
527 // *_max_window_bits
528 TEST_F(WebSocketStreamCreateExtensionTest, BadParameterSuffix) {
529 CreateAndConnectWithExtensions(
530 "permessage-deflate; client_max_content_bits=5");
531 EXPECT_FALSE(stream_);
532 EXPECT_TRUE(has_failed());
533 EXPECT_EQ(
534 "Error during WebSocket handshake: Error in permessage-deflate: "
535 "Received an unexpected permessage-deflate extension parameter",
536 failure_message());
539 // *_no_context_takeover parameters must not have an argument
540 TEST_F(WebSocketStreamCreateExtensionTest, BadParameterValue) {
541 CreateAndConnectWithExtensions(
542 "permessage-deflate; client_no_context_takeover=true");
543 EXPECT_FALSE(stream_);
544 EXPECT_TRUE(has_failed());
545 EXPECT_EQ(
546 "Error during WebSocket handshake: Error in permessage-deflate: "
547 "Received invalid client_no_context_takeover parameter",
548 failure_message());
551 // *_max_window_bits must have an argument
552 TEST_F(WebSocketStreamCreateExtensionTest, NoMaxWindowBitsArgument) {
553 CreateAndConnectWithExtensions("permessage-deflate; client_max_window_bits");
554 EXPECT_FALSE(stream_);
555 EXPECT_TRUE(has_failed());
556 EXPECT_EQ(
557 "Error during WebSocket handshake: Error in permessage-deflate: "
558 "client_max_window_bits must have value",
559 failure_message());
562 // *_max_window_bits must be an integer
563 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueInteger) {
564 CreateAndConnectWithExtensions(
565 "permessage-deflate; server_max_window_bits=banana");
566 EXPECT_FALSE(stream_);
567 EXPECT_TRUE(has_failed());
568 EXPECT_EQ(
569 "Error during WebSocket handshake: Error in permessage-deflate: "
570 "Received invalid server_max_window_bits parameter",
571 failure_message());
574 // *_max_window_bits must be >= 8
575 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueTooSmall) {
576 CreateAndConnectWithExtensions(
577 "permessage-deflate; server_max_window_bits=7");
578 EXPECT_FALSE(stream_);
579 EXPECT_TRUE(has_failed());
580 EXPECT_EQ(
581 "Error during WebSocket handshake: Error in permessage-deflate: "
582 "Received invalid server_max_window_bits parameter",
583 failure_message());
586 // *_max_window_bits must be <= 15
587 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueTooBig) {
588 CreateAndConnectWithExtensions(
589 "permessage-deflate; client_max_window_bits=16");
590 EXPECT_FALSE(stream_);
591 EXPECT_TRUE(has_failed());
592 EXPECT_EQ(
593 "Error during WebSocket handshake: Error in permessage-deflate: "
594 "Received invalid client_max_window_bits parameter",
595 failure_message());
598 // *_max_window_bits must not start with 0
599 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueStartsWithZero) {
600 CreateAndConnectWithExtensions(
601 "permessage-deflate; client_max_window_bits=08");
602 EXPECT_FALSE(stream_);
603 EXPECT_TRUE(has_failed());
604 EXPECT_EQ(
605 "Error during WebSocket handshake: Error in permessage-deflate: "
606 "Received invalid client_max_window_bits parameter",
607 failure_message());
610 // *_max_window_bits must not start with +
611 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueStartsWithPlus) {
612 CreateAndConnectWithExtensions(
613 "permessage-deflate; server_max_window_bits=+9");
614 EXPECT_FALSE(stream_);
615 EXPECT_TRUE(has_failed());
616 EXPECT_EQ(
617 "Error during WebSocket handshake: Error in permessage-deflate: "
618 "Received invalid server_max_window_bits parameter",
619 failure_message());
622 // TODO(ricea): Check that WebSocketDeflateStream is initialised with the
623 // arguments from the server. This is difficult because the data written to the
624 // socket is randomly masked.
626 // Additional Sec-WebSocket-Accept headers should be rejected.
627 TEST_F(WebSocketStreamCreateTest, DoubleAccept) {
628 CreateAndConnectStandard(
629 "ws://localhost/",
630 "/",
631 NoSubProtocols(),
632 "http://localhost",
634 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n");
635 RunUntilIdle();
636 EXPECT_FALSE(stream_);
637 EXPECT_TRUE(has_failed());
638 EXPECT_EQ("Error during WebSocket handshake: "
639 "'Sec-WebSocket-Accept' header must not appear "
640 "more than once in a response",
641 failure_message());
644 // Response code 200 must be rejected.
645 TEST_F(WebSocketStreamCreateTest, InvalidStatusCode) {
646 static const char kInvalidStatusCodeResponse[] =
647 "HTTP/1.1 200 OK\r\n"
648 "Upgrade: websocket\r\n"
649 "Connection: Upgrade\r\n"
650 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
651 "\r\n";
652 CreateAndConnectCustomResponse("ws://localhost/",
653 "/",
654 NoSubProtocols(),
655 "http://localhost",
657 kInvalidStatusCodeResponse);
658 RunUntilIdle();
659 EXPECT_TRUE(has_failed());
660 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 200",
661 failure_message());
664 // Redirects are not followed (according to the WHATWG WebSocket API, which
665 // overrides RFC6455 for browser applications).
666 TEST_F(WebSocketStreamCreateTest, RedirectsRejected) {
667 static const char kRedirectResponse[] =
668 "HTTP/1.1 302 Moved Temporarily\r\n"
669 "Content-Type: text/html\r\n"
670 "Content-Length: 34\r\n"
671 "Connection: keep-alive\r\n"
672 "Location: ws://localhost/other\r\n"
673 "\r\n"
674 "<title>Moved</title><h1>Moved</h1>";
675 CreateAndConnectCustomResponse("ws://localhost/",
676 "/",
677 NoSubProtocols(),
678 "http://localhost",
680 kRedirectResponse);
681 RunUntilIdle();
682 EXPECT_TRUE(has_failed());
683 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 302",
684 failure_message());
687 // Malformed responses should be rejected. HttpStreamParser will accept just
688 // about any garbage in the middle of the headers. To make it give up, the junk
689 // has to be at the start of the response. Even then, it just gets treated as an
690 // HTTP/0.9 response.
691 TEST_F(WebSocketStreamCreateTest, MalformedResponse) {
692 static const char kMalformedResponse[] =
693 "220 mx.google.com ESMTP\r\n"
694 "HTTP/1.1 101 OK\r\n"
695 "Upgrade: websocket\r\n"
696 "Connection: Upgrade\r\n"
697 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
698 "\r\n";
699 CreateAndConnectCustomResponse("ws://localhost/",
700 "/",
701 NoSubProtocols(),
702 "http://localhost",
704 kMalformedResponse);
705 RunUntilIdle();
706 EXPECT_TRUE(has_failed());
707 EXPECT_EQ("Error during WebSocket handshake: Invalid status line",
708 failure_message());
711 // Upgrade header must be present.
712 TEST_F(WebSocketStreamCreateTest, MissingUpgradeHeader) {
713 static const char kMissingUpgradeResponse[] =
714 "HTTP/1.1 101 Switching Protocols\r\n"
715 "Connection: Upgrade\r\n"
716 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
717 "\r\n";
718 CreateAndConnectCustomResponse("ws://localhost/",
719 "/",
720 NoSubProtocols(),
721 "http://localhost",
723 kMissingUpgradeResponse);
724 RunUntilIdle();
725 EXPECT_TRUE(has_failed());
726 EXPECT_EQ("Error during WebSocket handshake: 'Upgrade' header is missing",
727 failure_message());
730 // There must only be one upgrade header.
731 TEST_F(WebSocketStreamCreateTest, DoubleUpgradeHeader) {
732 CreateAndConnectStandard(
733 "ws://localhost/",
734 "/",
735 NoSubProtocols(),
736 "http://localhost",
737 "", "Upgrade: HTTP/2.0\r\n");
738 RunUntilIdle();
739 EXPECT_TRUE(has_failed());
740 EXPECT_EQ("Error during WebSocket handshake: "
741 "'Upgrade' header must not appear more than once in a response",
742 failure_message());
745 // There must only be one correct upgrade header.
746 TEST_F(WebSocketStreamCreateTest, IncorrectUpgradeHeader) {
747 static const char kMissingUpgradeResponse[] =
748 "HTTP/1.1 101 Switching Protocols\r\n"
749 "Connection: Upgrade\r\n"
750 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
751 "Upgrade: hogefuga\r\n"
752 "\r\n";
753 CreateAndConnectCustomResponse("ws://localhost/",
754 "/",
755 NoSubProtocols(),
756 "http://localhost",
758 kMissingUpgradeResponse);
759 RunUntilIdle();
760 EXPECT_TRUE(has_failed());
761 EXPECT_EQ("Error during WebSocket handshake: "
762 "'Upgrade' header value is not 'WebSocket': hogefuga",
763 failure_message());
766 // Connection header must be present.
767 TEST_F(WebSocketStreamCreateTest, MissingConnectionHeader) {
768 static const char kMissingConnectionResponse[] =
769 "HTTP/1.1 101 Switching Protocols\r\n"
770 "Upgrade: websocket\r\n"
771 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
772 "\r\n";
773 CreateAndConnectCustomResponse("ws://localhost/",
774 "/",
775 NoSubProtocols(),
776 "http://localhost",
778 kMissingConnectionResponse);
779 RunUntilIdle();
780 EXPECT_TRUE(has_failed());
781 EXPECT_EQ("Error during WebSocket handshake: "
782 "'Connection' header is missing",
783 failure_message());
786 // Connection header must contain "Upgrade".
787 TEST_F(WebSocketStreamCreateTest, IncorrectConnectionHeader) {
788 static const char kMissingConnectionResponse[] =
789 "HTTP/1.1 101 Switching Protocols\r\n"
790 "Upgrade: websocket\r\n"
791 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
792 "Connection: hogefuga\r\n"
793 "\r\n";
794 CreateAndConnectCustomResponse("ws://localhost/",
795 "/",
796 NoSubProtocols(),
797 "http://localhost",
799 kMissingConnectionResponse);
800 RunUntilIdle();
801 EXPECT_TRUE(has_failed());
802 EXPECT_EQ("Error during WebSocket handshake: "
803 "'Connection' header value must contain 'Upgrade'",
804 failure_message());
807 // Connection header is permitted to contain other tokens.
808 TEST_F(WebSocketStreamCreateTest, AdditionalTokenInConnectionHeader) {
809 static const char kAdditionalConnectionTokenResponse[] =
810 "HTTP/1.1 101 Switching Protocols\r\n"
811 "Upgrade: websocket\r\n"
812 "Connection: Upgrade, Keep-Alive\r\n"
813 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
814 "\r\n";
815 CreateAndConnectCustomResponse("ws://localhost/",
816 "/",
817 NoSubProtocols(),
818 "http://localhost",
820 kAdditionalConnectionTokenResponse);
821 RunUntilIdle();
822 EXPECT_FALSE(has_failed());
823 EXPECT_TRUE(stream_);
826 // Sec-WebSocket-Accept header must be present.
827 TEST_F(WebSocketStreamCreateTest, MissingSecWebSocketAccept) {
828 static const char kMissingAcceptResponse[] =
829 "HTTP/1.1 101 Switching Protocols\r\n"
830 "Upgrade: websocket\r\n"
831 "Connection: Upgrade\r\n"
832 "\r\n";
833 CreateAndConnectCustomResponse("ws://localhost/",
834 "/",
835 NoSubProtocols(),
836 "http://localhost",
838 kMissingAcceptResponse);
839 RunUntilIdle();
840 EXPECT_TRUE(has_failed());
841 EXPECT_EQ("Error during WebSocket handshake: "
842 "'Sec-WebSocket-Accept' header is missing",
843 failure_message());
846 // Sec-WebSocket-Accept header must match the key that was sent.
847 TEST_F(WebSocketStreamCreateTest, WrongSecWebSocketAccept) {
848 static const char kIncorrectAcceptResponse[] =
849 "HTTP/1.1 101 Switching Protocols\r\n"
850 "Upgrade: websocket\r\n"
851 "Connection: Upgrade\r\n"
852 "Sec-WebSocket-Accept: x/byyPZ2tOFvJCGkkugcKvqhhPk=\r\n"
853 "\r\n";
854 CreateAndConnectCustomResponse("ws://localhost/",
855 "/",
856 NoSubProtocols(),
857 "http://localhost",
859 kIncorrectAcceptResponse);
860 RunUntilIdle();
861 EXPECT_TRUE(has_failed());
862 EXPECT_EQ("Error during WebSocket handshake: "
863 "Incorrect 'Sec-WebSocket-Accept' header value",
864 failure_message());
867 // Cancellation works.
868 TEST_F(WebSocketStreamCreateTest, Cancellation) {
869 CreateAndConnectStandard(
870 "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
871 stream_request_.reset();
872 RunUntilIdle();
873 EXPECT_FALSE(has_failed());
874 EXPECT_FALSE(stream_);
875 EXPECT_FALSE(request_info_);
876 EXPECT_FALSE(response_info_);
879 // Connect failure must look just like negotiation failure.
880 TEST_F(WebSocketStreamCreateTest, ConnectionFailure) {
881 scoped_ptr<DeterministicSocketData> socket_data(
882 new DeterministicSocketData(NULL, 0, NULL, 0));
883 socket_data->set_connect_data(
884 MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED));
885 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
886 "http://localhost", socket_data.Pass());
887 RunUntilIdle();
888 EXPECT_TRUE(has_failed());
889 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED",
890 failure_message());
891 EXPECT_FALSE(request_info_);
892 EXPECT_FALSE(response_info_);
895 // Connect timeout must look just like any other failure.
896 TEST_F(WebSocketStreamCreateTest, ConnectionTimeout) {
897 scoped_ptr<DeterministicSocketData> socket_data(
898 new DeterministicSocketData(NULL, 0, NULL, 0));
899 socket_data->set_connect_data(
900 MockConnect(ASYNC, ERR_CONNECTION_TIMED_OUT));
901 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
902 "http://localhost", socket_data.Pass());
903 RunUntilIdle();
904 EXPECT_TRUE(has_failed());
905 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_TIMED_OUT",
906 failure_message());
909 // Cancellation during connect works.
910 TEST_F(WebSocketStreamCreateTest, CancellationDuringConnect) {
911 scoped_ptr<DeterministicSocketData> socket_data(
912 new DeterministicSocketData(NULL, 0, NULL, 0));
913 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING));
914 CreateAndConnectRawExpectations("ws://localhost/",
915 NoSubProtocols(),
916 "http://localhost",
917 socket_data.Pass());
918 stream_request_.reset();
919 RunUntilIdle();
920 EXPECT_FALSE(has_failed());
921 EXPECT_FALSE(stream_);
924 // Cancellation during write of the request headers works.
925 TEST_F(WebSocketStreamCreateTest, CancellationDuringWrite) {
926 // We seem to need at least two operations in order to use SetStop().
927 MockWrite writes[] = {MockWrite(ASYNC, 0, "GET / HTTP/"),
928 MockWrite(ASYNC, 1, "1.1\r\n")};
929 // We keep a copy of the pointer so that we can call RunFor() on it later.
930 DeterministicSocketData* socket_data(
931 new DeterministicSocketData(NULL, 0, writes, arraysize(writes)));
932 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
933 socket_data->SetStop(1);
934 CreateAndConnectRawExpectations("ws://localhost/",
935 NoSubProtocols(),
936 "http://localhost",
937 make_scoped_ptr(socket_data));
938 socket_data->Run();
939 stream_request_.reset();
940 RunUntilIdle();
941 EXPECT_FALSE(has_failed());
942 EXPECT_FALSE(stream_);
943 EXPECT_TRUE(request_info_);
944 EXPECT_FALSE(response_info_);
947 // Cancellation during read of the response headers works.
948 TEST_F(WebSocketStreamCreateTest, CancellationDuringRead) {
949 std::string request = WebSocketStandardRequest("/", "http://localhost", "");
950 MockWrite writes[] = {MockWrite(ASYNC, 0, request.c_str())};
951 MockRead reads[] = {
952 MockRead(ASYNC, 1, "HTTP/1.1 101 Switching Protocols\r\nUpgr"),
954 DeterministicSocketData* socket_data(new DeterministicSocketData(
955 reads, arraysize(reads), writes, arraysize(writes)));
956 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
957 socket_data->SetStop(1);
958 CreateAndConnectRawExpectations("ws://localhost/",
959 NoSubProtocols(),
960 "http://localhost",
961 make_scoped_ptr(socket_data));
962 socket_data->Run();
963 stream_request_.reset();
964 RunUntilIdle();
965 EXPECT_FALSE(has_failed());
966 EXPECT_FALSE(stream_);
967 EXPECT_TRUE(request_info_);
968 EXPECT_FALSE(response_info_);
971 // Over-size response headers (> 256KB) should not cause a crash. This is a
972 // regression test for crbug.com/339456. It is based on the layout test
973 // "cookie-flood.html".
974 TEST_F(WebSocketStreamCreateTest, VeryLargeResponseHeaders) {
975 std::string set_cookie_headers;
976 set_cookie_headers.reserve(45 * 10000);
977 for (int i = 0; i < 10000; ++i) {
978 set_cookie_headers +=
979 base::StringPrintf("Set-Cookie: WK-websocket-test-flood-%d=1\r\n", i);
981 CreateAndConnectStandard("ws://localhost/", "/", NoSubProtocols(),
982 "http://localhost", "", set_cookie_headers);
983 RunUntilIdle();
984 EXPECT_TRUE(has_failed());
985 EXPECT_FALSE(response_info_);
988 // If the remote host closes the connection without sending headers, we should
989 // log the console message "Connection closed before receiving a handshake
990 // response".
991 TEST_F(WebSocketStreamCreateTest, NoResponse) {
992 std::string request = WebSocketStandardRequest("/", "http://localhost", "");
993 MockWrite writes[] = {MockWrite(ASYNC, request.data(), request.size(), 0)};
994 MockRead reads[] = {MockRead(ASYNC, 0, 1)};
995 DeterministicSocketData* socket_data(new DeterministicSocketData(
996 reads, arraysize(reads), writes, arraysize(writes)));
997 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
998 CreateAndConnectRawExpectations("ws://localhost/",
999 NoSubProtocols(),
1000 "http://localhost",
1001 make_scoped_ptr(socket_data));
1002 socket_data->RunFor(2);
1003 EXPECT_TRUE(has_failed());
1004 EXPECT_FALSE(stream_);
1005 EXPECT_FALSE(response_info_);
1006 EXPECT_EQ("Connection closed before receiving a handshake response",
1007 failure_message());
1010 } // namespace
1011 } // namespace net