1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/websockets/websocket_stream.h"
6
7 #include <algorithm>
8 #include <iterator>
9 #include <string>
10 #include <string_view>
11 #include <tuple>
12 #include <utility>
13 #include <vector>
14
15 #include "base/check_op.h"
16 #include "base/containers/span.h"
17 #include "base/memory/weak_ptr.h"
18 #include "base/metrics/histogram_samples.h"
19 #include "base/run_loop.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/test/metrics/histogram_tester.h"
23 #include "base/test/scoped_feature_list.h"
24 #include "base/timer/mock_timer.h"
25 #include "base/timer/timer.h"
26 #include "net/base/auth.h"
27 #include "net/base/features.h"
28 #include "net/base/isolation_info.h"
29 #include "net/base/net_errors.h"
30 #include "net/base/request_priority.h"
31 #include "net/base/test_completion_callback.h"
32 #include "net/base/url_util.h"
33 #include "net/cookies/cookie_setting_override.h"
34 #include "net/cookies/site_for_cookies.h"
35 #include "net/http/http_network_session.h"
36 #include "net/http/http_request_headers.h"
37 #include "net/http/http_response_headers.h"
38 #include "net/log/net_log_with_source.h"
39 #include "net/socket/next_proto.h"
40 #include "net/socket/socket_test_util.h"
41 #include "net/spdy/spdy_test_util_common.h"
42 #include "net/ssl/ssl_info.h"
43 #include "net/test/cert_test_util.h"
44 #include "net/test/gtest_util.h"
45 #include "net/test/test_data_directory.h"
46 #include "net/third_party/quiche/src/quiche/spdy/core/http2_header_block.h"
47 #include "net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.h"
48 #include "net/third_party/quiche/src/quiche/spdy/test_tools/spdy_test_utils.h"
49 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
50 #include "net/url_request/url_request.h"
51 #include "net/url_request/url_request_context.h"
52 #include "net/url_request/url_request_test_util.h"
53 #include "net/websockets/websocket_frame.h"
54 #include "net/websockets/websocket_handshake_request_info.h"
55 #include "net/websockets/websocket_handshake_response_info.h"
56 #include "net/websockets/websocket_handshake_stream_base.h"
57 #include "net/websockets/websocket_stream_create_test_base.h"
58 #include "net/websockets/websocket_test_util.h"
59 #include "testing/gmock/include/gmock/gmock.h"
60 #include "testing/gtest/include/gtest/gtest.h"
61 #include "url/gurl.h"
62 #include "url/origin.h"
63
64 using ::net::test::IsError;
65 using ::net::test::IsOk;
66 using ::testing::TestWithParam;
67 using ::testing::Values;
68
69 namespace net {
70 namespace {
71
72 enum HandshakeStreamType { BASIC_HANDSHAKE_STREAM, HTTP2_HANDSHAKE_STREAM };
73
74 // Simple builder for a SequencedSocketData object to save repetitive code.
75 // It always sets the connect data to MockConnect(SYNCHRONOUS, OK), so it cannot
76 // be used in tests where the connect fails. In practice, those tests never have
77 // any read/write data and so can't benefit from it anyway. The arrays are not
78 // copied. It is up to the caller to ensure they stay in scope until the test
79 // ends.
BuildSocketData(base::span<MockRead> reads,base::span<MockWrite> writes)80 std::unique_ptr<SequencedSocketData> BuildSocketData(
81 base::span<MockRead> reads,
82 base::span<MockWrite> writes) {
83 auto socket_data = std::make_unique<SequencedSocketData>(reads, writes);
84 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
85 return socket_data;
86 }
87
88 // Builder for a SequencedSocketData that expects nothing. This does not
89 // set the connect data, so the calling code must do that explicitly.
BuildNullSocketData()90 std::unique_ptr<SequencedSocketData> BuildNullSocketData() {
91 return std::make_unique<SequencedSocketData>();
92 }
93
94 class MockWeakTimer : public base::MockOneShotTimer {
95 public:
96 MockWeakTimer() = default;
97
AsWeakPtr()98 base::WeakPtr<MockWeakTimer> AsWeakPtr() {
99 return weak_ptr_factory_.GetWeakPtr();
100 }
101
102 private:
103 base::WeakPtrFactory<MockWeakTimer> weak_ptr_factory_{this};
104 };
105
106 constexpr char kOrigin[] = "http://www.example.org";
107
Origin()108 static url::Origin Origin() {
109 return url::Origin::Create(GURL(kOrigin));
110 }
111
SiteForCookies()112 static net::SiteForCookies SiteForCookies() {
113 return net::SiteForCookies::FromOrigin(Origin());
114 }
115
CreateIsolationInfo()116 static IsolationInfo CreateIsolationInfo() {
117 url::Origin origin = Origin();
118 return IsolationInfo::Create(IsolationInfo::RequestType::kOther, origin,
119 origin, SiteForCookies::FromOrigin(origin));
120 }
121
122 class WebSocketStreamCreateTest
123 : public TestWithParam<std::tuple<HandshakeStreamType, bool>>,
124 public WebSocketStreamCreateTestBase {
125 protected:
WebSocketStreamCreateTest()126 WebSocketStreamCreateTest()
127 : stream_type_(std::get<HandshakeStreamType>(GetParam())),
128 spdy_util_(/*use_priority_header=*/true) {
129 // Make sure these tests all pass with connection partitioning enabled. The
130 // disabled case is less interesting, and is tested more directly at lower
131 // layers.
132 if (PriorityHeaderEnabled()) {
133 feature_list_.InitWithFeatures(
134 {features::kPartitionConnectionsByNetworkIsolationKey,
135 net::features::kPriorityHeader},
136 {});
137 } else {
138 feature_list_.InitWithFeatures(
139 {features::kPartitionConnectionsByNetworkIsolationKey},
140 {net::features::kPriorityHeader});
141 }
142 }
143
~WebSocketStreamCreateTest()144 ~WebSocketStreamCreateTest() override {
145 // Permit any endpoint locks to be released.
146 stream_request_.reset();
147 stream_.reset();
148 base::RunLoop().RunUntilIdle();
149 }
150
151 // Normally it's easier to use CreateAndConnectRawExpectations() instead. This
152 // method is only needed when multiple sockets are involved.
AddRawExpectations(std::unique_ptr<SequencedSocketData> socket_data)153 void AddRawExpectations(std::unique_ptr<SequencedSocketData> socket_data) {
154 url_request_context_host_.AddRawExpectations(std::move(socket_data));
155 }
156
AddSSLData()157 void AddSSLData() {
158 auto ssl_data = std::make_unique<SSLSocketDataProvider>(ASYNC, OK);
159 ssl_data->ssl_info.cert =
160 ImportCertFromFile(GetTestCertsDirectory(), "wildcard.pem");
161 if (stream_type_ == HTTP2_HANDSHAKE_STREAM)
162 ssl_data->next_proto = kProtoHTTP2;
163 ASSERT_TRUE(ssl_data->ssl_info.cert.get());
164 url_request_context_host_.AddSSLSocketDataProvider(std::move(ssl_data));
165 }
166
SetTimer(std::unique_ptr<base::OneShotTimer> timer)167 void SetTimer(std::unique_ptr<base::OneShotTimer> timer) {
168 timer_ = std::move(timer);
169 }
170
SetAdditionalResponseData(std::string additional_data)171 void SetAdditionalResponseData(std::string additional_data) {
172 additional_data_ = std::move(additional_data);
173 }
174
SetHttp2ResponseStatus(const char * const http2_response_status)175 void SetHttp2ResponseStatus(const char* const http2_response_status) {
176 http2_response_status_ = http2_response_status;
177 }
178
SetResetWebSocketHttp2Stream(bool reset_websocket_http2_stream)179 void SetResetWebSocketHttp2Stream(bool reset_websocket_http2_stream) {
180 reset_websocket_http2_stream_ = reset_websocket_http2_stream;
181 }
182
183 // Set up mock data and start websockets request, either for WebSocket
184 // upgraded from an HTTP/1 connection, or for a WebSocket request over HTTP/2.
CreateAndConnectStandard(std::string_view url,const std::vector<std::string> & sub_protocols,const WebSocketExtraHeaders & send_additional_request_headers,const WebSocketExtraHeaders & extra_request_headers,const WebSocketExtraHeaders & extra_response_headers,bool has_storage_access=false)185 void CreateAndConnectStandard(
186 std::string_view url,
187 const std::vector<std::string>& sub_protocols,
188 const WebSocketExtraHeaders& send_additional_request_headers,
189 const WebSocketExtraHeaders& extra_request_headers,
190 const WebSocketExtraHeaders& extra_response_headers,
191 bool has_storage_access = false) {
192 const GURL socket_url(url);
193 const std::string socket_host = GetHostAndOptionalPort(socket_url);
194 const std::string socket_path = socket_url.path();
195
196 if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
197 url_request_context_host_.SetExpectations(
198 WebSocketStandardRequest(socket_path, socket_host, Origin(),
199 send_additional_request_headers,
200 extra_request_headers),
201 WebSocketStandardResponse(
202 WebSocketExtraHeadersToString(extra_response_headers)) +
203 additional_data_);
204 CreateAndConnectStream(socket_url, sub_protocols, Origin(),
205 SiteForCookies(), has_storage_access,
206 CreateIsolationInfo(),
207 WebSocketExtraHeadersToHttpRequestHeaders(
208 send_additional_request_headers),
209 std::move(timer_));
210 return;
211 }
212
213 DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
214
215 // TODO(bnc): Find a way to clear
216 // spdy_session_pool.enable_sending_initial_data_ to avoid sending
217 // connection preface, initial settings, and window update.
218
219 // HTTP/2 connection preface.
220 frames_.emplace_back(spdy::test::MakeSerializedFrame(
221 const_cast<char*>(spdy::kHttp2ConnectionHeaderPrefix),
222 spdy::kHttp2ConnectionHeaderPrefixSize));
223 AddWrite(&frames_.back());
224
225 // Server advertises WebSockets over HTTP/2 support.
226 spdy::SettingsMap read_settings;
227 read_settings[spdy::SETTINGS_ENABLE_CONNECT_PROTOCOL] = 1;
228 frames_.push_back(spdy_util_.ConstructSpdySettings(read_settings));
229 AddRead(&frames_.back());
230
231 // Initial SETTINGS frame.
232 spdy::SettingsMap write_settings;
233 write_settings[spdy::SETTINGS_HEADER_TABLE_SIZE] = kSpdyMaxHeaderTableSize;
234 write_settings[spdy::SETTINGS_INITIAL_WINDOW_SIZE] = 6 * 1024 * 1024;
235 write_settings[spdy::SETTINGS_MAX_HEADER_LIST_SIZE] =
236 kSpdyMaxHeaderListSize;
237 write_settings[spdy::SETTINGS_ENABLE_PUSH] = 0;
238 frames_.push_back(spdy_util_.ConstructSpdySettings(write_settings));
239 AddWrite(&frames_.back());
240
241 // Initial window update frame.
242 frames_.push_back(spdy_util_.ConstructSpdyWindowUpdate(0, 0x00ef0001));
243 AddWrite(&frames_.back());
244
245 // SETTINGS ACK sent as a response to server's SETTINGS frame.
246 frames_.push_back(spdy_util_.ConstructSpdySettingsAck());
247 AddWrite(&frames_.back());
248
249 // First request. This is necessary, because a WebSockets request currently
250 // does not open a new HTTP/2 connection, it only uses an existing one.
251 const char* const kExtraRequestHeaders[] = {
252 "user-agent", "", "accept-encoding", "gzip, deflate",
253 "accept-language", "en-us,fr"};
254 frames_.push_back(spdy_util_.ConstructSpdyGet(
255 kExtraRequestHeaders, std::size(kExtraRequestHeaders) / 2, 1,
256 DEFAULT_PRIORITY));
257 AddWrite(&frames_.back());
258
259 // SETTINGS ACK frame sent by the server in response to the client's
260 // initial SETTINGS frame.
261 frames_.push_back(spdy_util_.ConstructSpdySettingsAck());
262 AddRead(&frames_.back());
263
264 // Response headers to first request.
265 frames_.push_back(spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1));
266 AddRead(&frames_.back());
267
268 // Response body to first request.
269 frames_.push_back(spdy_util_.ConstructSpdyDataFrame(1, true));
270 AddRead(&frames_.back());
271
272 // First request is closed.
273 spdy_util_.UpdateWithStreamDestruction(1);
274
275 // WebSocket request.
276 spdy::Http2HeaderBlock request_headers = WebSocketHttp2Request(
277 socket_path, socket_host, kOrigin, extra_request_headers);
278 frames_.push_back(spdy_util_.ConstructSpdyHeaders(
279 3, std::move(request_headers), DEFAULT_PRIORITY, false));
280 AddWrite(&frames_.back());
281
282 if (reset_websocket_http2_stream_) {
283 frames_.push_back(
284 spdy_util_.ConstructSpdyRstStream(3, spdy::ERROR_CODE_CANCEL));
285 AddRead(&frames_.back());
286 } else {
287 // Response to WebSocket request.
288 std::vector<std::string> extra_response_header_keys;
289 std::vector<const char*> extra_response_headers_vector;
290 for (const auto& extra_header : extra_response_headers) {
291 // Save a lowercase copy of the header key.
292 extra_response_header_keys.push_back(
293 base::ToLowerASCII(extra_header.first));
294 // Save a pointer to this lowercase copy.
295 extra_response_headers_vector.push_back(
296 extra_response_header_keys.back().c_str());
297 // Save a pointer to the original header value provided by the caller.
298 extra_response_headers_vector.push_back(extra_header.second.c_str());
299 }
300 frames_.push_back(spdy_util_.ConstructSpdyReplyError(
301 http2_response_status_, extra_response_headers_vector.data(),
302 extra_response_headers_vector.size() / 2, 3));
303 AddRead(&frames_.back());
304
305 // WebSocket data received.
306 if (!additional_data_.empty()) {
307 frames_.push_back(
308 spdy_util_.ConstructSpdyDataFrame(3, additional_data_, true));
309 AddRead(&frames_.back());
310 }
311
312 // Client cancels HTTP/2 stream when request is destroyed.
313 frames_.push_back(
314 spdy_util_.ConstructSpdyRstStream(3, spdy::ERROR_CODE_CANCEL));
315 AddWrite(&frames_.back());
316 }
317
318 // EOF.
319 reads_.emplace_back(ASYNC, 0, sequence_number_++);
320
321 auto socket_data = std::make_unique<SequencedSocketData>(reads_, writes_);
322 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
323 AddRawExpectations(std::move(socket_data));
324
325 // Send first request. This makes sure server's
326 // spdy::SETTINGS_ENABLE_CONNECT_PROTOCOL advertisement is read.
327 URLRequestContext* context =
328 url_request_context_host_.GetURLRequestContext();
329 TestDelegate delegate;
330 std::unique_ptr<URLRequest> request = context->CreateRequest(
331 GURL("https://www.example.org/"), DEFAULT_PRIORITY, &delegate,
332 TRAFFIC_ANNOTATION_FOR_TESTS, /*is_for_websockets=*/false);
333 // The IsolationInfo has to match for a socket to be reused.
334 request->set_isolation_info(CreateIsolationInfo());
335 request->Start();
336 EXPECT_TRUE(request->is_pending());
337 delegate.RunUntilComplete();
338 EXPECT_FALSE(request->is_pending());
339
340 CreateAndConnectStream(socket_url, sub_protocols, Origin(),
341 SiteForCookies(), has_storage_access,
342 CreateIsolationInfo(),
343 WebSocketExtraHeadersToHttpRequestHeaders(
344 send_additional_request_headers),
345 std::move(timer_));
346 }
347
348 // Like CreateAndConnectStandard(), but allow for arbitrary response body.
349 // Only for HTTP/1-based WebSockets.
CreateAndConnectCustomResponse(std::string_view url,const std::vector<std::string> & sub_protocols,const WebSocketExtraHeaders & send_additional_request_headers,const WebSocketExtraHeaders & extra_request_headers,const std::string & response_body,bool has_storage_access=false)350 void CreateAndConnectCustomResponse(
351 std::string_view url,
352 const std::vector<std::string>& sub_protocols,
353 const WebSocketExtraHeaders& send_additional_request_headers,
354 const WebSocketExtraHeaders& extra_request_headers,
355 const std::string& response_body,
356 bool has_storage_access = false) {
357 ASSERT_EQ(BASIC_HANDSHAKE_STREAM, stream_type_);
358
359 const GURL socket_url(url);
360 const std::string socket_host = GetHostAndOptionalPort(socket_url);
361 const std::string socket_path = socket_url.path();
362
363 url_request_context_host_.SetExpectations(
364 WebSocketStandardRequest(socket_path, socket_host, Origin(),
365 send_additional_request_headers,
366 extra_request_headers),
367 response_body);
368 CreateAndConnectStream(socket_url, sub_protocols, Origin(),
369 SiteForCookies(), has_storage_access,
370 CreateIsolationInfo(),
371 WebSocketExtraHeadersToHttpRequestHeaders(
372 send_additional_request_headers),
373 nullptr);
374 }
375
376 // Like CreateAndConnectStandard(), but take extra response headers as a
377 // string. This can save space in case of a very large response.
378 // Only for HTTP/1-based WebSockets.
CreateAndConnectStringResponse(std::string_view url,const std::vector<std::string> & sub_protocols,const std::string & extra_response_headers,bool has_storage_access=false)379 void CreateAndConnectStringResponse(
380 std::string_view url,
381 const std::vector<std::string>& sub_protocols,
382 const std::string& extra_response_headers,
383 bool has_storage_access = false) {
384 ASSERT_EQ(BASIC_HANDSHAKE_STREAM, stream_type_);
385
386 const GURL socket_url(url);
387 const std::string socket_host = GetHostAndOptionalPort(socket_url);
388 const std::string socket_path = socket_url.path();
389
390 url_request_context_host_.SetExpectations(
391 WebSocketStandardRequest(socket_path, socket_host, Origin(),
392 /*send_additional_request_headers=*/{},
393 /*extra_headers=*/{}),
394 WebSocketStandardResponse(extra_response_headers));
395 CreateAndConnectStream(socket_url, sub_protocols, Origin(),
396 SiteForCookies(), has_storage_access,
397 CreateIsolationInfo(), HttpRequestHeaders(),
398 nullptr);
399 }
400
401 // Like CreateAndConnectStandard(), but take raw mock data.
CreateAndConnectRawExpectations(std::string_view url,const std::vector<std::string> & sub_protocols,const HttpRequestHeaders & additional_headers,std::unique_ptr<SequencedSocketData> socket_data,bool has_storage_access=false)402 void CreateAndConnectRawExpectations(
403 std::string_view url,
404 const std::vector<std::string>& sub_protocols,
405 const HttpRequestHeaders& additional_headers,
406 std::unique_ptr<SequencedSocketData> socket_data,
407 bool has_storage_access = false) {
408 ASSERT_EQ(BASIC_HANDSHAKE_STREAM, stream_type_);
409
410 AddRawExpectations(std::move(socket_data));
411 CreateAndConnectStream(GURL(url), sub_protocols, Origin(), SiteForCookies(),
412 has_storage_access, CreateIsolationInfo(),
413 additional_headers, std::move(timer_));
414 }
415
PriorityHeaderEnabled() const416 bool PriorityHeaderEnabled() const { return std::get<bool>(GetParam()); }
417
418 private:
AddWrite(const spdy::SpdySerializedFrame * frame)419 void AddWrite(const spdy::SpdySerializedFrame* frame) {
420 writes_.emplace_back(ASYNC, frame->data(), frame->size(),
421 sequence_number_++);
422 }
423
AddRead(const spdy::SpdySerializedFrame * frame)424 void AddRead(const spdy::SpdySerializedFrame* frame) {
425 reads_.emplace_back(ASYNC, frame->data(), frame->size(),
426 sequence_number_++);
427 }
428
429 protected:
430 const HandshakeStreamType stream_type_;
431
432 private:
433 base::test::ScopedFeatureList feature_list_;
434
435 std::unique_ptr<base::OneShotTimer> timer_;
436 std::string additional_data_;
437 const char* http2_response_status_ = "200";
438 bool reset_websocket_http2_stream_ = false;
439 SpdyTestUtil spdy_util_;
440 NetLogWithSource log_;
441
442 int sequence_number_ = 0;
443
444 // Store mock HTTP/2 data.
445 std::vector<spdy::SpdySerializedFrame> frames_;
446
447 // Store MockRead and MockWrite objects that have pointers to above data.
448 std::vector<MockRead> reads_;
449 std::vector<MockWrite> writes_;
450 };
451
452 INSTANTIATE_TEST_SUITE_P(All,
453 WebSocketStreamCreateTest,
454 testing::Combine(Values(BASIC_HANDSHAKE_STREAM),
455 testing::Bool()));
456
457 using WebSocketMultiProtocolStreamCreateTest = WebSocketStreamCreateTest;
458
459 INSTANTIATE_TEST_SUITE_P(All,
460 WebSocketMultiProtocolStreamCreateTest,
461 testing::Combine(Values(BASIC_HANDSHAKE_STREAM,
462 HTTP2_HANDSHAKE_STREAM),
463 testing::Bool()));
464
465 // There are enough tests of the Sec-WebSocket-Extensions header that they
466 // deserve their own test fixture.
467 class WebSocketStreamCreateExtensionTest
468 : public WebSocketMultiProtocolStreamCreateTest {
469 protected:
470 // Performs a standard connect, with the value of the Sec-WebSocket-Extensions
471 // header in the response set to |extensions_header_value|. Runs the event
472 // loop to allow the connect to complete.
CreateAndConnectWithExtensions(const std::string & extensions_header_value)473 void CreateAndConnectWithExtensions(
474 const std::string& extensions_header_value) {
475 AddSSLData();
476 CreateAndConnectStandard(
477 "wss://www.example.org/testing_path", NoSubProtocols(), {}, {},
478 {{"Sec-WebSocket-Extensions", extensions_header_value}});
479 WaitUntilConnectDone();
480 }
481 };
482
483 INSTANTIATE_TEST_SUITE_P(All,
484 WebSocketStreamCreateExtensionTest,
485 testing::Combine(Values(BASIC_HANDSHAKE_STREAM,
486 HTTP2_HANDSHAKE_STREAM),
487 testing::Bool()));
488
489 // Common code to construct expectations for authentication tests that receive
490 // the auth challenge on one connection and then create a second connection to
491 // send the authenticated request on.
492 class CommonAuthTestHelper {
493 public:
CommonAuthTestHelper()494 CommonAuthTestHelper() : reads_(), writes_() {}
495
496 CommonAuthTestHelper(const CommonAuthTestHelper&) = delete;
497 CommonAuthTestHelper& operator=(const CommonAuthTestHelper&) = delete;
498
BuildAuthSocketData(std::string response1,std::string request2,std::string response2)499 std::unique_ptr<SequencedSocketData> BuildAuthSocketData(
500 std::string response1,
501 std::string request2,
502 std::string response2) {
503 request1_ = WebSocketStandardRequest("/", "www.example.org", Origin(),
504 /*send_additional_request_headers=*/{},
505 /*extra_headers=*/{});
506 response1_ = std::move(response1);
507 request2_ = std::move(request2);
508 response2_ = std::move(response2);
509 writes_[0] = MockWrite(SYNCHRONOUS, 0, request1_.c_str());
510 reads_[0] = MockRead(SYNCHRONOUS, 1, response1_.c_str());
511 writes_[1] = MockWrite(SYNCHRONOUS, 2, request2_.c_str());
512 reads_[1] = MockRead(SYNCHRONOUS, 3, response2_.c_str());
513 reads_[2] = MockRead(SYNCHRONOUS, OK, 4); // Close connection
514
515 return BuildSocketData(reads_, writes_);
516 }
517
518 private:
519 // These need to be object-scoped since they have to remain valid until all
520 // socket operations in the test are complete.
521 std::string request1_;
522 std::string request2_;
523 std::string response1_;
524 std::string response2_;
525 MockRead reads_[3];
526 MockWrite writes_[2];
527 };
528
529 // Data and methods for BasicAuth tests.
530 class WebSocketStreamCreateBasicAuthTest : public WebSocketStreamCreateTest {
531 protected:
CreateAndConnectAuthHandshake(std::string_view url,std::string_view base64_user_pass,std::string_view response2)532 void CreateAndConnectAuthHandshake(std::string_view url,
533 std::string_view base64_user_pass,
534 std::string_view response2) {
535 CreateAndConnectRawExpectations(
536 url, NoSubProtocols(), HttpRequestHeaders(),
537 helper_.BuildAuthSocketData(kUnauthorizedResponse,
538 RequestExpectation(base64_user_pass),
539 std::string(response2)));
540 }
541
RequestExpectation(std::string_view base64_user_pass)542 static std::string RequestExpectation(std::string_view base64_user_pass) {
543 // Copy base64_user_pass to a std::string in case it is not nul-terminated.
544 std::string base64_user_pass_string(base64_user_pass);
545 return base::StringPrintf(
546 "GET / HTTP/1.1\r\n"
547 "Host: www.example.org\r\n"
548 "Connection: Upgrade\r\n"
549 "Pragma: no-cache\r\n"
550 "Cache-Control: no-cache\r\n"
551 "Authorization: Basic %s\r\n"
552 "Upgrade: websocket\r\n"
553 "Origin: http://www.example.org\r\n"
554 "Sec-WebSocket-Version: 13\r\n"
555 "User-Agent: \r\n"
556 "Accept-Encoding: gzip, deflate\r\n"
557 "Accept-Language: en-us,fr\r\n"
558 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
559 "Sec-WebSocket-Extensions: permessage-deflate; "
560 "client_max_window_bits\r\n"
561 "\r\n",
562 base64_user_pass_string.c_str());
563 }
564
565 static const char kUnauthorizedResponse[];
566
567 CommonAuthTestHelper helper_;
568 };
569
570 INSTANTIATE_TEST_SUITE_P(All,
571 WebSocketStreamCreateBasicAuthTest,
572 testing::Combine(Values(BASIC_HANDSHAKE_STREAM),
573 testing::Bool()));
574
575 class WebSocketStreamCreateDigestAuthTest : public WebSocketStreamCreateTest {
576 protected:
577 static const char kUnauthorizedResponse[];
578 static const char kAuthorizedRequest[];
579
580 CommonAuthTestHelper helper_;
581 };
582
583 INSTANTIATE_TEST_SUITE_P(All,
584 WebSocketStreamCreateDigestAuthTest,
585 testing::Combine(Values(BASIC_HANDSHAKE_STREAM),
586 testing::Bool()));
587
588 const char WebSocketStreamCreateBasicAuthTest::kUnauthorizedResponse[] =
589 "HTTP/1.1 401 Unauthorized\r\n"
590 "Content-Length: 0\r\n"
591 "WWW-Authenticate: Basic realm=\"camelot\"\r\n"
592 "\r\n";
593
594 // These negotiation values are borrowed from
595 // http_auth_handler_digest_unittest.cc. Feel free to come up with new ones if
596 // you are bored. Only the weakest (no qop) variants of Digest authentication
597 // can be tested by this method, because the others involve random input.
598 const char WebSocketStreamCreateDigestAuthTest::kUnauthorizedResponse[] =
599 "HTTP/1.1 401 Unauthorized\r\n"
600 "Content-Length: 0\r\n"
601 "WWW-Authenticate: Digest realm=\"Oblivion\", nonce=\"nonce-value\"\r\n"
602 "\r\n";
603
604 const char WebSocketStreamCreateDigestAuthTest::kAuthorizedRequest[] =
605 "GET / HTTP/1.1\r\n"
606 "Host: www.example.org\r\n"
607 "Connection: Upgrade\r\n"
608 "Pragma: no-cache\r\n"
609 "Cache-Control: no-cache\r\n"
610 "Authorization: Digest username=\"FooBar\", realm=\"Oblivion\", "
611 "nonce=\"nonce-value\", uri=\"/\", "
612 "response=\"f72ff54ebde2f928860f806ec04acd1b\"\r\n"
613 "Upgrade: websocket\r\n"
614 "Origin: http://www.example.org\r\n"
615 "Sec-WebSocket-Version: 13\r\n"
616 "User-Agent: \r\n"
617 "Accept-Encoding: gzip, deflate\r\n"
618 "Accept-Language: en-us,fr\r\n"
619 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
620 "Sec-WebSocket-Extensions: permessage-deflate; "
621 "client_max_window_bits\r\n"
622 "\r\n";
623
624 // Confirm that the basic case works as expected.
TEST_P(WebSocketMultiProtocolStreamCreateTest,SimpleSuccess)625 TEST_P(WebSocketMultiProtocolStreamCreateTest, SimpleSuccess) {
626 base::HistogramTester histogram_tester;
627
628 AddSSLData();
629 EXPECT_FALSE(url_request_);
630 CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {},
631 {});
632 EXPECT_FALSE(request_info_);
633 EXPECT_FALSE(response_info_);
634 EXPECT_TRUE(url_request_);
635 WaitUntilConnectDone();
636 EXPECT_FALSE(has_failed());
637 EXPECT_TRUE(stream_);
638 EXPECT_TRUE(request_info_);
639 EXPECT_TRUE(response_info_);
640
641 // Histograms are only updated on stream request destruction.
642 stream_request_.reset();
643 stream_.reset();
644
645 EXPECT_EQ(ERR_WS_UPGRADE,
646 url_request_context_host_.network_delegate().last_error());
647
648 auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
649 "Net.WebSocket.HandshakeResult2");
650 EXPECT_EQ(1, samples->TotalCount());
651 if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
652 EXPECT_EQ(1,
653 samples->GetCount(static_cast<int>(
654 WebSocketHandshakeStreamBase::HandshakeResult::CONNECTED)));
655 } else {
656 DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
657 EXPECT_EQ(
658 1,
659 samples->GetCount(static_cast<int>(
660 WebSocketHandshakeStreamBase::HandshakeResult::HTTP2_CONNECTED)));
661 }
662 }
663
TEST_P(WebSocketStreamCreateTest,HandshakeInfo)664 TEST_P(WebSocketStreamCreateTest, HandshakeInfo) {
665 static constexpr char kResponse[] =
666 "HTTP/1.1 101 Switching Protocols\r\n"
667 "Upgrade: websocket\r\n"
668 "Connection: Upgrade\r\n"
669 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
670 "foo: bar, baz\r\n"
671 "hoge: fuga\r\n"
672 "hoge: piyo\r\n"
673 "\r\n";
674
675 CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
676 {}, kResponse);
677 EXPECT_FALSE(request_info_);
678 EXPECT_FALSE(response_info_);
679 WaitUntilConnectDone();
680 EXPECT_TRUE(stream_);
681 ASSERT_TRUE(request_info_);
682 ASSERT_TRUE(response_info_);
683 std::vector<HeaderKeyValuePair> request_headers =
684 RequestHeadersToVector(request_info_->headers);
685 // We examine the contents of request_info_ and response_info_
686 // mainly only in this test case.
687 EXPECT_EQ(GURL("ws://www.example.org/"), request_info_->url);
688 EXPECT_EQ(GURL("ws://www.example.org/"), response_info_->url);
689 EXPECT_EQ(101, response_info_->headers->response_code());
690 EXPECT_EQ("Switching Protocols", response_info_->headers->GetStatusText());
691 ASSERT_EQ(12u, request_headers.size());
692 EXPECT_EQ(HeaderKeyValuePair("Host", "www.example.org"), request_headers[0]);
693 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), request_headers[1]);
694 EXPECT_EQ(HeaderKeyValuePair("Pragma", "no-cache"), request_headers[2]);
695 EXPECT_EQ(HeaderKeyValuePair("Cache-Control", "no-cache"),
696 request_headers[3]);
697 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), request_headers[4]);
698 EXPECT_EQ(HeaderKeyValuePair("Origin", "http://www.example.org"),
699 request_headers[5]);
700 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Version", "13"),
701 request_headers[6]);
702 EXPECT_EQ(HeaderKeyValuePair("User-Agent", ""), request_headers[7]);
703 EXPECT_EQ(HeaderKeyValuePair("Accept-Encoding", "gzip, deflate"),
704 request_headers[8]);
705 EXPECT_EQ(HeaderKeyValuePair("Accept-Language", "en-us,fr"),
706 request_headers[9]);
707 EXPECT_EQ("Sec-WebSocket-Key", request_headers[10].first);
708 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Extensions",
709 "permessage-deflate; client_max_window_bits"),
710 request_headers[11]);
711
712 std::vector<HeaderKeyValuePair> response_headers =
713 ResponseHeadersToVector(*response_info_->headers.get());
714 ASSERT_EQ(6u, response_headers.size());
715 // Sort the headers for ease of verification.
716 std::sort(response_headers.begin(), response_headers.end());
717
718 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), response_headers[0]);
719 EXPECT_EQ("Sec-WebSocket-Accept", response_headers[1].first);
720 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), response_headers[2]);
721 EXPECT_EQ(HeaderKeyValuePair("foo", "bar, baz"), response_headers[3]);
722 EXPECT_EQ(HeaderKeyValuePair("hoge", "fuga"), response_headers[4]);
723 EXPECT_EQ(HeaderKeyValuePair("hoge", "piyo"), response_headers[5]);
724 }
725
726 // Confirms that request headers are overriden/added after handshake
TEST_P(WebSocketStreamCreateTest,HandshakeOverrideHeaders)727 TEST_P(WebSocketStreamCreateTest, HandshakeOverrideHeaders) {
728 WebSocketExtraHeaders additional_headers(
729 {{"User-Agent", "OveRrIde"}, {"rAnDomHeader", "foobar"}});
730 CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(),
731 additional_headers, additional_headers, {});
732 EXPECT_FALSE(request_info_);
733 EXPECT_FALSE(response_info_);
734 WaitUntilConnectDone();
735 EXPECT_FALSE(has_failed());
736 EXPECT_TRUE(stream_);
737 EXPECT_TRUE(request_info_);
738 EXPECT_TRUE(response_info_);
739
740 std::vector<HeaderKeyValuePair> request_headers =
741 RequestHeadersToVector(request_info_->headers);
742 EXPECT_EQ(HeaderKeyValuePair("User-Agent", "OveRrIde"), request_headers[4]);
743 EXPECT_EQ(HeaderKeyValuePair("rAnDomHeader", "foobar"), request_headers[5]);
744 }
745
TEST_P(WebSocketStreamCreateTest,OmitsHasStorageAccess)746 TEST_P(WebSocketStreamCreateTest, OmitsHasStorageAccess) {
747 CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(), {}, {},
748 {}, /*has_storage_access=*/false);
749 WaitUntilConnectDone();
750
751 EXPECT_THAT(
752 url_request_context_host_.network_delegate()
753 .cookie_setting_overrides_records(),
754 testing::ElementsAre(CookieSettingOverrides(), CookieSettingOverrides()));
755 }
756
TEST_P(WebSocketStreamCreateTest,PlumbsHasStorageAccess)757 TEST_P(WebSocketStreamCreateTest, PlumbsHasStorageAccess) {
758 CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(), {}, {},
759 {}, /*has_storage_access=*/true);
760 WaitUntilConnectDone();
761
762 CookieSettingOverrides expected_overrides;
763 expected_overrides.Put(CookieSettingOverride::kStorageAccessGrantEligible);
764
765 EXPECT_THAT(url_request_context_host_.network_delegate()
766 .cookie_setting_overrides_records(),
767 testing::ElementsAre(expected_overrides, expected_overrides));
768 }
769
770 // Confirm that the stream isn't established until the message loop runs.
TEST_P(WebSocketStreamCreateTest,NeedsToRunLoop)771 TEST_P(WebSocketStreamCreateTest, NeedsToRunLoop) {
772 CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(), {}, {},
773 {});
774 EXPECT_FALSE(has_failed());
775 EXPECT_FALSE(stream_);
776 }
777
778 // Check the path is used.
TEST_P(WebSocketMultiProtocolStreamCreateTest,PathIsUsed)779 TEST_P(WebSocketMultiProtocolStreamCreateTest, PathIsUsed) {
780 AddSSLData();
781 CreateAndConnectStandard("wss://www.example.org/testing_path",
782 NoSubProtocols(), {}, {}, {});
783 WaitUntilConnectDone();
784 EXPECT_FALSE(has_failed());
785 EXPECT_TRUE(stream_);
786 }
787
788 // Check that sub-protocols are sent and parsed.
TEST_P(WebSocketMultiProtocolStreamCreateTest,SubProtocolIsUsed)789 TEST_P(WebSocketMultiProtocolStreamCreateTest, SubProtocolIsUsed) {
790 AddSSLData();
791 std::vector<std::string> sub_protocols;
792 sub_protocols.push_back("chatv11.chromium.org");
793 sub_protocols.push_back("chatv20.chromium.org");
794 CreateAndConnectStandard(
795 "wss://www.example.org/testing_path", sub_protocols, {},
796 {{"Sec-WebSocket-Protocol",
797 "chatv11.chromium.org, chatv20.chromium.org"}},
798 {{"Sec-WebSocket-Protocol", "chatv20.chromium.org"}});
799 WaitUntilConnectDone();
800 ASSERT_TRUE(stream_);
801 EXPECT_FALSE(has_failed());
802 EXPECT_EQ("chatv20.chromium.org", stream_->GetSubProtocol());
803 }
804
805 // Unsolicited sub-protocols are rejected.
TEST_P(WebSocketMultiProtocolStreamCreateTest,UnsolicitedSubProtocol)806 TEST_P(WebSocketMultiProtocolStreamCreateTest, UnsolicitedSubProtocol) {
807 base::HistogramTester histogram_tester;
808
809 AddSSLData();
810 CreateAndConnectStandard(
811 "wss://www.example.org/testing_path", NoSubProtocols(), {}, {},
812 {{"Sec-WebSocket-Protocol", "chatv20.chromium.org"}});
813 WaitUntilConnectDone();
814 EXPECT_FALSE(stream_);
815 EXPECT_TRUE(has_failed());
816 EXPECT_EQ("Error during WebSocket handshake: "
817 "Response must not include 'Sec-WebSocket-Protocol' header "
818 "if not present in request: chatv20.chromium.org",
819 failure_message());
820 EXPECT_EQ(ERR_INVALID_RESPONSE,
821 url_request_context_host_.network_delegate().last_error());
822
823 stream_request_.reset();
824
825 auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
826 "Net.WebSocket.HandshakeResult2");
827 EXPECT_EQ(1, samples->TotalCount());
828 if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
829 EXPECT_EQ(
830 1,
831 samples->GetCount(static_cast<int>(
832 WebSocketHandshakeStreamBase::HandshakeResult::FAILED_SUBPROTO)));
833 } else {
834 DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
835 EXPECT_EQ(1, samples->GetCount(static_cast<int>(
836 WebSocketHandshakeStreamBase::HandshakeResult::
837 HTTP2_FAILED_SUBPROTO)));
838 }
839 }
840
841 // Missing sub-protocol response is rejected.
TEST_P(WebSocketMultiProtocolStreamCreateTest,UnacceptedSubProtocol)842 TEST_P(WebSocketMultiProtocolStreamCreateTest, UnacceptedSubProtocol) {
843 AddSSLData();
844 std::vector<std::string> sub_protocols;
845 sub_protocols.push_back("chat.example.com");
846 CreateAndConnectStandard("wss://www.example.org/testing_path", sub_protocols,
847 {}, {{"Sec-WebSocket-Protocol", "chat.example.com"}},
848 {});
849 WaitUntilConnectDone();
850 EXPECT_FALSE(stream_);
851 EXPECT_TRUE(has_failed());
852 EXPECT_EQ("Error during WebSocket handshake: "
853 "Sent non-empty 'Sec-WebSocket-Protocol' header "
854 "but no response was received",
855 failure_message());
856 }
857
858 // Only one sub-protocol can be accepted.
TEST_P(WebSocketMultiProtocolStreamCreateTest,MultipleSubProtocolsInResponse)859 TEST_P(WebSocketMultiProtocolStreamCreateTest, MultipleSubProtocolsInResponse) {
860 AddSSLData();
861 std::vector<std::string> sub_protocols;
862 sub_protocols.push_back("chatv11.chromium.org");
863 sub_protocols.push_back("chatv20.chromium.org");
864 CreateAndConnectStandard("wss://www.example.org/testing_path", sub_protocols,
865 {},
866 {{"Sec-WebSocket-Protocol",
867 "chatv11.chromium.org, chatv20.chromium.org"}},
868 {{"Sec-WebSocket-Protocol",
869 "chatv11.chromium.org, chatv20.chromium.org"}});
870 WaitUntilConnectDone();
871 EXPECT_FALSE(stream_);
872 EXPECT_TRUE(has_failed());
873 EXPECT_EQ(
874 "Error during WebSocket handshake: "
875 "'Sec-WebSocket-Protocol' header must not appear "
876 "more than once in a response",
877 failure_message());
878 }
879
880 // Unmatched sub-protocol should be rejected.
TEST_P(WebSocketMultiProtocolStreamCreateTest,UnmatchedSubProtocolInResponse)881 TEST_P(WebSocketMultiProtocolStreamCreateTest, UnmatchedSubProtocolInResponse) {
882 AddSSLData();
883 std::vector<std::string> sub_protocols;
884 sub_protocols.push_back("chatv11.chromium.org");
885 sub_protocols.push_back("chatv20.chromium.org");
886 CreateAndConnectStandard(
887 "wss://www.example.org/testing_path", sub_protocols, {},
888 {{"Sec-WebSocket-Protocol",
889 "chatv11.chromium.org, chatv20.chromium.org"}},
890 {{"Sec-WebSocket-Protocol", "chatv21.chromium.org"}});
891 WaitUntilConnectDone();
892 EXPECT_FALSE(stream_);
893 EXPECT_TRUE(has_failed());
894 EXPECT_EQ("Error during WebSocket handshake: "
895 "'Sec-WebSocket-Protocol' header value 'chatv21.chromium.org' "
896 "in response does not match any of sent values",
897 failure_message());
898 }
899
900 // permessage-deflate extension basic success case.
TEST_P(WebSocketStreamCreateExtensionTest,PerMessageDeflateSuccess)901 TEST_P(WebSocketStreamCreateExtensionTest, PerMessageDeflateSuccess) {
902 CreateAndConnectWithExtensions("permessage-deflate");
903 EXPECT_TRUE(stream_);
904 EXPECT_FALSE(has_failed());
905 }
906
907 // permessage-deflate extensions success with all parameters.
TEST_P(WebSocketStreamCreateExtensionTest,PerMessageDeflateParamsSuccess)908 TEST_P(WebSocketStreamCreateExtensionTest, PerMessageDeflateParamsSuccess) {
909 CreateAndConnectWithExtensions(
910 "permessage-deflate; client_no_context_takeover; "
911 "server_max_window_bits=11; client_max_window_bits=13; "
912 "server_no_context_takeover");
913 EXPECT_TRUE(stream_);
914 EXPECT_FALSE(has_failed());
915 }
916
917 // Verify that incoming messages are actually decompressed with
918 // permessage-deflate enabled.
TEST_P(WebSocketStreamCreateExtensionTest,PerMessageDeflateInflates)919 TEST_P(WebSocketStreamCreateExtensionTest, PerMessageDeflateInflates) {
920 AddSSLData();
921 SetAdditionalResponseData(std::string(
922 "\xc1\x07" // WebSocket header (FIN + RSV1, Text payload 7 bytes)
923 "\xf2\x48\xcd\xc9\xc9\x07\x00", // "Hello" DEFLATE compressed
924 9));
925 CreateAndConnectStandard(
926 "wss://www.example.org/testing_path", NoSubProtocols(), {}, {},
927 {{"Sec-WebSocket-Extensions", "permessage-deflate"}});
928 WaitUntilConnectDone();
929
930 ASSERT_TRUE(stream_);
931 std::vector<std::unique_ptr<WebSocketFrame>> frames;
932 TestCompletionCallback callback;
933 int rv = stream_->ReadFrames(&frames, callback.callback());
934 rv = callback.GetResult(rv);
935 ASSERT_THAT(rv, IsOk());
936 ASSERT_EQ(1U, frames.size());
937 ASSERT_EQ(5U, frames[0]->header.payload_length);
938 EXPECT_EQ(std::string("Hello"),
939 std::string(frames[0]->payload, frames[0]->header.payload_length));
940 }
941
942 // Unknown extension in the response is rejected
TEST_P(WebSocketStreamCreateExtensionTest,UnknownExtension)943 TEST_P(WebSocketStreamCreateExtensionTest, UnknownExtension) {
944 CreateAndConnectWithExtensions("x-unknown-extension");
945 EXPECT_FALSE(stream_);
946 EXPECT_TRUE(has_failed());
947 EXPECT_EQ("Error during WebSocket handshake: "
948 "Found an unsupported extension 'x-unknown-extension' "
949 "in 'Sec-WebSocket-Extensions' header",
950 failure_message());
951 }
952
953 // Malformed extensions are rejected (this file does not cover all possible
954 // parse failures, as the parser is covered thoroughly by its own unit tests).
TEST_P(WebSocketStreamCreateExtensionTest,MalformedExtension)955 TEST_P(WebSocketStreamCreateExtensionTest, MalformedExtension) {
956 CreateAndConnectWithExtensions(";");
957 EXPECT_FALSE(stream_);
958 EXPECT_TRUE(has_failed());
959 EXPECT_EQ(
960 "Error during WebSocket handshake: 'Sec-WebSocket-Extensions' header "
961 "value is rejected by the parser: ;",
962 failure_message());
963 }
964
965 // The permessage-deflate extension may only be specified once.
TEST_P(WebSocketStreamCreateExtensionTest,OnlyOnePerMessageDeflateAllowed)966 TEST_P(WebSocketStreamCreateExtensionTest, OnlyOnePerMessageDeflateAllowed) {
967 base::HistogramTester histogram_tester;
968
969 CreateAndConnectWithExtensions(
970 "permessage-deflate, permessage-deflate; client_max_window_bits=10");
971 EXPECT_FALSE(stream_);
972 EXPECT_TRUE(has_failed());
973 EXPECT_EQ(
974 "Error during WebSocket handshake: "
975 "Received duplicate permessage-deflate response",
976 failure_message());
977
978 stream_request_.reset();
979
980 auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
981 "Net.WebSocket.HandshakeResult2");
982 EXPECT_EQ(1, samples->TotalCount());
983 if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
984 EXPECT_EQ(
985 1,
986 samples->GetCount(static_cast<int>(
987 WebSocketHandshakeStreamBase::HandshakeResult::FAILED_EXTENSIONS)));
988 } else {
989 DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
990 EXPECT_EQ(1, samples->GetCount(static_cast<int>(
991 WebSocketHandshakeStreamBase::HandshakeResult::
992 HTTP2_FAILED_EXTENSIONS)));
993 }
994 }
995
996 // client_max_window_bits must have an argument
TEST_P(WebSocketStreamCreateExtensionTest,NoMaxWindowBitsArgument)997 TEST_P(WebSocketStreamCreateExtensionTest, NoMaxWindowBitsArgument) {
998 CreateAndConnectWithExtensions("permessage-deflate; client_max_window_bits");
999 EXPECT_FALSE(stream_);
1000 EXPECT_TRUE(has_failed());
1001 EXPECT_EQ(
1002 "Error during WebSocket handshake: Error in permessage-deflate: "
1003 "client_max_window_bits must have value",
1004 failure_message());
1005 }
1006
1007 // Other cases for permessage-deflate parameters are tested in
1008 // websocket_deflate_parameters_test.cc.
1009
1010 // TODO(ricea): Check that WebSocketDeflateStream is initialised with the
1011 // arguments from the server. This is difficult because the data written to the
1012 // socket is randomly masked.
1013
1014 // Additional Sec-WebSocket-Accept headers should be rejected.
TEST_P(WebSocketStreamCreateTest,DoubleAccept)1015 TEST_P(WebSocketStreamCreateTest, DoubleAccept) {
1016 CreateAndConnectStandard(
1017 "ws://www.example.org/", NoSubProtocols(), {}, {},
1018 {{"Sec-WebSocket-Accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}});
1019 WaitUntilConnectDone();
1020 EXPECT_FALSE(stream_);
1021 EXPECT_TRUE(has_failed());
1022 EXPECT_EQ("Error during WebSocket handshake: "
1023 "'Sec-WebSocket-Accept' header must not appear "
1024 "more than once in a response",
1025 failure_message());
1026 }
1027
1028 // When upgrading an HTTP/1 connection, response code 200 is invalid and must be
1029 // rejected. Response code 101 means success. On the other hand, when
1030 // requesting a WebSocket stream over HTTP/2, response code 101 is invalid and
1031 // must be rejected. Response code 200 means success.
TEST_P(WebSocketMultiProtocolStreamCreateTest,InvalidStatusCode)1032 TEST_P(WebSocketMultiProtocolStreamCreateTest, InvalidStatusCode) {
1033 base::HistogramTester histogram_tester;
1034
1035 AddSSLData();
1036 if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
1037 static constexpr char kInvalidStatusCodeResponse[] =
1038 "HTTP/1.1 200 OK\r\n"
1039 "Upgrade: websocket\r\n"
1040 "Connection: Upgrade\r\n"
1041 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
1042 "\r\n";
1043 CreateAndConnectCustomResponse("wss://www.example.org/", NoSubProtocols(),
1044 {}, {}, kInvalidStatusCodeResponse);
1045 } else {
1046 DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
1047 SetHttp2ResponseStatus("101");
1048 CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {},
1049 {});
1050 }
1051
1052 WaitUntilConnectDone();
1053 stream_request_.reset();
1054 EXPECT_TRUE(has_failed());
1055 auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
1056 "Net.WebSocket.HandshakeResult2");
1057 EXPECT_EQ(1, samples->TotalCount());
1058
1059 if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
1060 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 200",
1061 failure_message());
1062 EXPECT_EQ(failure_response_code(), 200);
1063 EXPECT_EQ(
1064 1, samples->GetCount(static_cast<int>(
1065 WebSocketHandshakeStreamBase::HandshakeResult::INVALID_STATUS)));
1066 } else {
1067 DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
1068 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 101",
1069 failure_message());
1070 EXPECT_EQ(failure_response_code(), 101);
1071 EXPECT_EQ(1, samples->GetCount(static_cast<int>(
1072 WebSocketHandshakeStreamBase::HandshakeResult::
1073 HTTP2_INVALID_STATUS)));
1074 }
1075 }
1076
1077 // Redirects are not followed (according to the WHATWG WebSocket API, which
1078 // overrides RFC6455 for browser applications).
TEST_P(WebSocketMultiProtocolStreamCreateTest,RedirectsRejected)1079 TEST_P(WebSocketMultiProtocolStreamCreateTest, RedirectsRejected) {
1080 AddSSLData();
1081 if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
1082 static constexpr char kRedirectResponse[] =
1083 "HTTP/1.1 302 Moved Temporarily\r\n"
1084 "Content-Type: text/html\r\n"
1085 "Content-Length: 34\r\n"
1086 "Connection: keep-alive\r\n"
1087 "Location: wss://www.example.org/other\r\n"
1088 "\r\n"
1089 "<title>Moved</title><h1>Moved</h1>";
1090 CreateAndConnectCustomResponse("wss://www.example.org/", NoSubProtocols(),
1091 {}, {}, kRedirectResponse);
1092 } else {
1093 DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
1094 SetHttp2ResponseStatus("302");
1095 CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {},
1096 {});
1097 }
1098 WaitUntilConnectDone();
1099
1100 EXPECT_TRUE(has_failed());
1101 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 302",
1102 failure_message());
1103 }
1104
1105 // Malformed responses should be rejected. HttpStreamParser will accept just
1106 // about any garbage in the middle of the headers. To make it give up, the junk
1107 // has to be at the start of the response. Even then, it just gets treated as an
1108 // HTTP/0.9 response.
TEST_P(WebSocketStreamCreateTest,MalformedResponse)1109 TEST_P(WebSocketStreamCreateTest, MalformedResponse) {
1110 static constexpr char kMalformedResponse[] =
1111 "220 mx.google.com ESMTP\r\n"
1112 "HTTP/1.1 101 OK\r\n"
1113 "Upgrade: websocket\r\n"
1114 "Connection: Upgrade\r\n"
1115 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
1116 "\r\n";
1117 CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
1118 {}, kMalformedResponse);
1119 WaitUntilConnectDone();
1120 EXPECT_TRUE(has_failed());
1121 EXPECT_EQ("Error during WebSocket handshake: Invalid status line",
1122 failure_message());
1123 }
1124
1125 // Upgrade header must be present.
TEST_P(WebSocketStreamCreateTest,MissingUpgradeHeader)1126 TEST_P(WebSocketStreamCreateTest, MissingUpgradeHeader) {
1127 base::HistogramTester histogram_tester;
1128
1129 static constexpr char kMissingUpgradeResponse[] =
1130 "HTTP/1.1 101 Switching Protocols\r\n"
1131 "Connection: Upgrade\r\n"
1132 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
1133 "\r\n";
1134 CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
1135 {}, kMissingUpgradeResponse);
1136 WaitUntilConnectDone();
1137 EXPECT_TRUE(has_failed());
1138 EXPECT_EQ("Error during WebSocket handshake: 'Upgrade' header is missing",
1139 failure_message());
1140
1141 stream_request_.reset();
1142
1143 auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
1144 "Net.WebSocket.HandshakeResult2");
1145 EXPECT_EQ(1, samples->TotalCount());
1146 EXPECT_EQ(
1147 1, samples->GetCount(static_cast<int>(
1148 WebSocketHandshakeStreamBase::HandshakeResult::FAILED_UPGRADE)));
1149 }
1150
1151 // There must only be one upgrade header.
TEST_P(WebSocketStreamCreateTest,DoubleUpgradeHeader)1152 TEST_P(WebSocketStreamCreateTest, DoubleUpgradeHeader) {
1153 CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(), {}, {},
1154 {{"Upgrade", "HTTP/2.0"}});
1155 WaitUntilConnectDone();
1156 EXPECT_TRUE(has_failed());
1157 EXPECT_EQ("Error during WebSocket handshake: "
1158 "'Upgrade' header must not appear more than once in a response",
1159 failure_message());
1160 }
1161
1162 // There must only be one correct upgrade header.
TEST_P(WebSocketStreamCreateTest,IncorrectUpgradeHeader)1163 TEST_P(WebSocketStreamCreateTest, IncorrectUpgradeHeader) {
1164 static constexpr char kMissingUpgradeResponse[] =
1165 "HTTP/1.1 101 Switching Protocols\r\n"
1166 "Connection: Upgrade\r\n"
1167 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
1168 "Upgrade: hogefuga\r\n"
1169 "\r\n";
1170 CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
1171 {}, kMissingUpgradeResponse);
1172 WaitUntilConnectDone();
1173 EXPECT_TRUE(has_failed());
1174 EXPECT_EQ("Error during WebSocket handshake: "
1175 "'Upgrade' header value is not 'WebSocket': hogefuga",
1176 failure_message());
1177 }
1178
1179 // Connection header must be present.
TEST_P(WebSocketStreamCreateTest,MissingConnectionHeader)1180 TEST_P(WebSocketStreamCreateTest, MissingConnectionHeader) {
1181 base::HistogramTester histogram_tester;
1182
1183 static constexpr char kMissingConnectionResponse[] =
1184 "HTTP/1.1 101 Switching Protocols\r\n"
1185 "Upgrade: websocket\r\n"
1186 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
1187 "\r\n";
1188 CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
1189 {}, kMissingConnectionResponse);
1190 WaitUntilConnectDone();
1191 EXPECT_TRUE(has_failed());
1192 EXPECT_EQ("Error during WebSocket handshake: "
1193 "'Connection' header is missing",
1194 failure_message());
1195
1196 stream_request_.reset();
1197
1198 auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
1199 "Net.WebSocket.HandshakeResult2");
1200 EXPECT_EQ(1, samples->TotalCount());
1201 EXPECT_EQ(
1202 1,
1203 samples->GetCount(static_cast<int>(
1204 WebSocketHandshakeStreamBase::HandshakeResult::FAILED_CONNECTION)));
1205 }
1206
1207 // Connection header must contain "Upgrade".
TEST_P(WebSocketStreamCreateTest,IncorrectConnectionHeader)1208 TEST_P(WebSocketStreamCreateTest, IncorrectConnectionHeader) {
1209 static constexpr char kMissingConnectionResponse[] =
1210 "HTTP/1.1 101 Switching Protocols\r\n"
1211 "Upgrade: websocket\r\n"
1212 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
1213 "Connection: hogefuga\r\n"
1214 "\r\n";
1215 CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
1216 {}, kMissingConnectionResponse);
1217 WaitUntilConnectDone();
1218 EXPECT_TRUE(has_failed());
1219 EXPECT_EQ("Error during WebSocket handshake: "
1220 "'Connection' header value must contain 'Upgrade'",
1221 failure_message());
1222 }
1223
1224 // Connection header is permitted to contain other tokens.
TEST_P(WebSocketStreamCreateTest,AdditionalTokenInConnectionHeader)1225 TEST_P(WebSocketStreamCreateTest, AdditionalTokenInConnectionHeader) {
1226 static constexpr char kAdditionalConnectionTokenResponse[] =
1227 "HTTP/1.1 101 Switching Protocols\r\n"
1228 "Upgrade: websocket\r\n"
1229 "Connection: Upgrade, Keep-Alive\r\n"
1230 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
1231 "\r\n";
1232 CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
1233 {}, kAdditionalConnectionTokenResponse);
1234 WaitUntilConnectDone();
1235 EXPECT_FALSE(has_failed());
1236 EXPECT_TRUE(stream_);
1237 }
1238
1239 // Sec-WebSocket-Accept header must be present.
TEST_P(WebSocketStreamCreateTest,MissingSecWebSocketAccept)1240 TEST_P(WebSocketStreamCreateTest, MissingSecWebSocketAccept) {
1241 base::HistogramTester histogram_tester;
1242
1243 static constexpr char kMissingAcceptResponse[] =
1244 "HTTP/1.1 101 Switching Protocols\r\n"
1245 "Upgrade: websocket\r\n"
1246 "Connection: Upgrade\r\n"
1247 "\r\n";
1248 CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
1249 {}, kMissingAcceptResponse);
1250 WaitUntilConnectDone();
1251 EXPECT_TRUE(has_failed());
1252 EXPECT_EQ("Error during WebSocket handshake: "
1253 "'Sec-WebSocket-Accept' header is missing",
1254 failure_message());
1255
1256 stream_request_.reset();
1257
1258 auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
1259 "Net.WebSocket.HandshakeResult2");
1260 EXPECT_EQ(1, samples->TotalCount());
1261 EXPECT_EQ(1,
1262 samples->GetCount(static_cast<int>(
1263 WebSocketHandshakeStreamBase::HandshakeResult::FAILED_ACCEPT)));
1264 }
1265
1266 // Sec-WebSocket-Accept header must match the key that was sent.
TEST_P(WebSocketStreamCreateTest,WrongSecWebSocketAccept)1267 TEST_P(WebSocketStreamCreateTest, WrongSecWebSocketAccept) {
1268 static constexpr char kIncorrectAcceptResponse[] =
1269 "HTTP/1.1 101 Switching Protocols\r\n"
1270 "Upgrade: websocket\r\n"
1271 "Connection: Upgrade\r\n"
1272 "Sec-WebSocket-Accept: x/byyPZ2tOFvJCGkkugcKvqhhPk=\r\n"
1273 "\r\n";
1274 CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
1275 {}, kIncorrectAcceptResponse);
1276 WaitUntilConnectDone();
1277 EXPECT_TRUE(has_failed());
1278 EXPECT_EQ("Error during WebSocket handshake: "
1279 "Incorrect 'Sec-WebSocket-Accept' header value",
1280 failure_message());
1281 }
1282
1283 // Cancellation works.
TEST_P(WebSocketStreamCreateTest,Cancellation)1284 TEST_P(WebSocketStreamCreateTest, Cancellation) {
1285 CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(), {}, {},
1286 {});
1287 stream_request_.reset();
1288 // WaitUntilConnectDone doesn't work in this case.
1289 base::RunLoop().RunUntilIdle();
1290 EXPECT_FALSE(has_failed());
1291 EXPECT_FALSE(stream_);
1292 EXPECT_FALSE(request_info_);
1293 EXPECT_FALSE(response_info_);
1294 }
1295
1296 // Connect failure must look just like negotiation failure.
TEST_P(WebSocketStreamCreateTest,ConnectionFailure)1297 TEST_P(WebSocketStreamCreateTest, ConnectionFailure) {
1298 std::unique_ptr<SequencedSocketData> socket_data(BuildNullSocketData());
1299 socket_data->set_connect_data(
1300 MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED));
1301 CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
1302 HttpRequestHeaders(), std::move(socket_data));
1303 WaitUntilConnectDone();
1304 EXPECT_TRUE(has_failed());
1305 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED",
1306 failure_message());
1307 EXPECT_FALSE(request_info_);
1308 EXPECT_FALSE(response_info_);
1309 }
1310
1311 // Connect timeout must look just like any other failure.
TEST_P(WebSocketStreamCreateTest,ConnectionTimeout)1312 TEST_P(WebSocketStreamCreateTest, ConnectionTimeout) {
1313 std::unique_ptr<SequencedSocketData> socket_data(BuildNullSocketData());
1314 socket_data->set_connect_data(
1315 MockConnect(ASYNC, ERR_CONNECTION_TIMED_OUT));
1316 CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
1317 HttpRequestHeaders(), std::move(socket_data));
1318 WaitUntilConnectDone();
1319 EXPECT_TRUE(has_failed());
1320 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_TIMED_OUT",
1321 failure_message());
1322 }
1323
1324 // The server doesn't respond to the opening handshake.
TEST_P(WebSocketStreamCreateTest,HandshakeTimeout)1325 TEST_P(WebSocketStreamCreateTest, HandshakeTimeout) {
1326 std::unique_ptr<SequencedSocketData> socket_data(BuildNullSocketData());
1327 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING));
1328 auto timer = std::make_unique<MockWeakTimer>();
1329 base::WeakPtr<MockWeakTimer> weak_timer = timer->AsWeakPtr();
1330 SetTimer(std::move(timer));
1331 CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
1332 HttpRequestHeaders(), std::move(socket_data));
1333 EXPECT_FALSE(has_failed());
1334 ASSERT_TRUE(weak_timer.get());
1335 EXPECT_TRUE(weak_timer->IsRunning());
1336
1337 weak_timer->Fire();
1338 WaitUntilConnectDone();
1339
1340 EXPECT_TRUE(has_failed());
1341 EXPECT_EQ("WebSocket opening handshake timed out", failure_message());
1342 ASSERT_TRUE(weak_timer.get());
1343 EXPECT_FALSE(weak_timer->IsRunning());
1344 }
1345
1346 // When the connection establishes the timer should be stopped.
TEST_P(WebSocketStreamCreateTest,HandshakeTimerOnSuccess)1347 TEST_P(WebSocketStreamCreateTest, HandshakeTimerOnSuccess) {
1348 auto timer = std::make_unique<MockWeakTimer>();
1349 base::WeakPtr<MockWeakTimer> weak_timer = timer->AsWeakPtr();
1350
1351 SetTimer(std::move(timer));
1352 CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(), {}, {},
1353 {});
1354 ASSERT_TRUE(weak_timer);
1355 EXPECT_TRUE(weak_timer->IsRunning());
1356
1357 WaitUntilConnectDone();
1358 EXPECT_FALSE(has_failed());
1359 EXPECT_TRUE(stream_);
1360 ASSERT_TRUE(weak_timer);
1361 EXPECT_FALSE(weak_timer->IsRunning());
1362 }
1363
1364 // When the connection fails the timer should be stopped.
TEST_P(WebSocketStreamCreateTest,HandshakeTimerOnFailure)1365 TEST_P(WebSocketStreamCreateTest, HandshakeTimerOnFailure) {
1366 std::unique_ptr<SequencedSocketData> socket_data(BuildNullSocketData());
1367 socket_data->set_connect_data(
1368 MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED));
1369 auto timer = std::make_unique<MockWeakTimer>();
1370 base::WeakPtr<MockWeakTimer> weak_timer = timer->AsWeakPtr();
1371 SetTimer(std::move(timer));
1372 CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
1373 HttpRequestHeaders(), std::move(socket_data));
1374 ASSERT_TRUE(weak_timer.get());
1375 EXPECT_TRUE(weak_timer->IsRunning());
1376
1377 WaitUntilConnectDone();
1378 EXPECT_TRUE(has_failed());
1379 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED",
1380 failure_message());
1381 ASSERT_TRUE(weak_timer.get());
1382 EXPECT_FALSE(weak_timer->IsRunning());
1383 }
1384
1385 // Cancellation during connect works.
TEST_P(WebSocketStreamCreateTest,CancellationDuringConnect)1386 TEST_P(WebSocketStreamCreateTest, CancellationDuringConnect) {
1387 std::unique_ptr<SequencedSocketData> socket_data(BuildNullSocketData());
1388 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING));
1389 CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
1390 HttpRequestHeaders(), std::move(socket_data));
1391 stream_request_.reset();
1392 // WaitUntilConnectDone doesn't work in this case.
1393 base::RunLoop().RunUntilIdle();
1394 EXPECT_FALSE(has_failed());
1395 EXPECT_FALSE(stream_);
1396 }
1397
1398 // Cancellation during write of the request headers works.
TEST_P(WebSocketStreamCreateTest,CancellationDuringWrite)1399 TEST_P(WebSocketStreamCreateTest, CancellationDuringWrite) {
1400 // First write never completes.
1401 MockWrite writes[] = {MockWrite(SYNCHRONOUS, ERR_IO_PENDING, 0)};
1402 auto socket_data =
1403 std::make_unique<SequencedSocketData>(base::span<MockRead>(), writes);
1404 auto* socket_data_ptr = socket_data.get();
1405 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
1406 CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
1407 HttpRequestHeaders(), std::move(socket_data));
1408 base::RunLoop().RunUntilIdle();
1409 EXPECT_TRUE(socket_data_ptr->AllWriteDataConsumed());
1410 stream_request_.reset();
1411 // WaitUntilConnectDone doesn't work in this case.
1412 base::RunLoop().RunUntilIdle();
1413 EXPECT_FALSE(has_failed());
1414 EXPECT_FALSE(stream_);
1415 EXPECT_TRUE(request_info_);
1416 EXPECT_FALSE(response_info_);
1417 }
1418
1419 // Cancellation during read of the response headers works.
TEST_P(WebSocketStreamCreateTest,CancellationDuringRead)1420 TEST_P(WebSocketStreamCreateTest, CancellationDuringRead) {
1421 std::string request = WebSocketStandardRequest(
1422 "/", "www.example.org", Origin(), /*send_additional_request_headers=*/{},
1423 /*extra_headers=*/{});
1424 MockWrite writes[] = {MockWrite(ASYNC, 0, request.c_str())};
1425 MockRead reads[] = {
1426 MockRead(SYNCHRONOUS, ERR_IO_PENDING, 1),
1427 };
1428 std::unique_ptr<SequencedSocketData> socket_data(
1429 BuildSocketData(reads, writes));
1430 SequencedSocketData* socket_data_raw_ptr = socket_data.get();
1431 CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
1432 HttpRequestHeaders(), std::move(socket_data));
1433 base::RunLoop().RunUntilIdle();
1434 EXPECT_TRUE(socket_data_raw_ptr->AllReadDataConsumed());
1435 stream_request_.reset();
1436 // WaitUntilConnectDone doesn't work in this case.
1437 base::RunLoop().RunUntilIdle();
1438 EXPECT_FALSE(has_failed());
1439 EXPECT_FALSE(stream_);
1440 EXPECT_TRUE(request_info_);
1441 EXPECT_FALSE(response_info_);
1442 }
1443
1444 // Over-size response headers (> 256KB) should not cause a crash. This is a
1445 // regression test for crbug.com/339456. It is based on the layout test
1446 // "cookie-flood.html".
TEST_P(WebSocketStreamCreateTest,VeryLargeResponseHeaders)1447 TEST_P(WebSocketStreamCreateTest, VeryLargeResponseHeaders) {
1448 base::HistogramTester histogram_tester;
1449
1450 std::string set_cookie_headers;
1451 set_cookie_headers.reserve(24 * 20000);
1452 for (int i = 0; i < 20000; ++i) {
1453 set_cookie_headers += base::StringPrintf("Set-Cookie: ws-%d=1\r\n", i);
1454 }
1455 ASSERT_GT(set_cookie_headers.size(), 256U * 1024U);
1456 CreateAndConnectStringResponse("ws://www.example.org/", NoSubProtocols(),
1457 set_cookie_headers);
1458 WaitUntilConnectDone();
1459 EXPECT_TRUE(has_failed());
1460 EXPECT_FALSE(response_info_);
1461
1462 stream_request_.reset();
1463
1464 auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
1465 "Net.WebSocket.HandshakeResult2");
1466 EXPECT_EQ(1, samples->TotalCount());
1467 EXPECT_EQ(1, samples->GetCount(static_cast<int>(
1468 WebSocketHandshakeStreamBase::HandshakeResult::FAILED)));
1469 }
1470
1471 // If the remote host closes the connection without sending headers, we should
1472 // log the console message "Connection closed before receiving a handshake
1473 // response".
TEST_P(WebSocketStreamCreateTest,NoResponse)1474 TEST_P(WebSocketStreamCreateTest, NoResponse) {
1475 base::HistogramTester histogram_tester;
1476
1477 std::string request = WebSocketStandardRequest(
1478 "/", "www.example.org", Origin(), /*send_additional_request_headers=*/{},
1479 /*extra_headers=*/{});
1480 MockWrite writes[] = {MockWrite(ASYNC, request.data(), request.size(), 0)};
1481 MockRead reads[] = {MockRead(ASYNC, 0, 1)};
1482 std::unique_ptr<SequencedSocketData> socket_data(
1483 BuildSocketData(reads, writes));
1484 SequencedSocketData* socket_data_raw_ptr = socket_data.get();
1485 CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
1486 HttpRequestHeaders(), std::move(socket_data));
1487 base::RunLoop().RunUntilIdle();
1488 EXPECT_TRUE(socket_data_raw_ptr->AllReadDataConsumed());
1489 EXPECT_TRUE(has_failed());
1490 EXPECT_FALSE(stream_);
1491 EXPECT_FALSE(response_info_);
1492 EXPECT_EQ("Connection closed before receiving a handshake response",
1493 failure_message());
1494
1495 stream_request_.reset();
1496
1497 auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
1498 "Net.WebSocket.HandshakeResult2");
1499 EXPECT_EQ(1, samples->TotalCount());
1500 EXPECT_EQ(
1501 1, samples->GetCount(static_cast<int>(
1502 WebSocketHandshakeStreamBase::HandshakeResult::EMPTY_RESPONSE)));
1503 }
1504
TEST_P(WebSocketStreamCreateTest,SelfSignedCertificateFailure)1505 TEST_P(WebSocketStreamCreateTest, SelfSignedCertificateFailure) {
1506 auto ssl_socket_data = std::make_unique<SSLSocketDataProvider>(
1507 ASYNC, ERR_CERT_AUTHORITY_INVALID);
1508 ssl_socket_data->ssl_info.cert =
1509 ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der");
1510 ASSERT_TRUE(ssl_socket_data->ssl_info.cert.get());
1511 url_request_context_host_.AddSSLSocketDataProvider(
1512 std::move(ssl_socket_data));
1513 std::unique_ptr<SequencedSocketData> raw_socket_data(BuildNullSocketData());
1514 CreateAndConnectRawExpectations("wss://www.example.org/", NoSubProtocols(),
1515 HttpRequestHeaders(),
1516 std::move(raw_socket_data));
1517 // WaitUntilConnectDone doesn't work in this case.
1518 base::RunLoop().RunUntilIdle();
1519 EXPECT_FALSE(has_failed());
1520 ASSERT_TRUE(ssl_error_callbacks_);
1521 ssl_error_callbacks_->CancelSSLRequest(ERR_CERT_AUTHORITY_INVALID,
1522 &ssl_info_);
1523 WaitUntilConnectDone();
1524 EXPECT_TRUE(has_failed());
1525 }
1526
TEST_P(WebSocketStreamCreateTest,SelfSignedCertificateSuccess)1527 TEST_P(WebSocketStreamCreateTest, SelfSignedCertificateSuccess) {
1528 auto ssl_socket_data = std::make_unique<SSLSocketDataProvider>(
1529 ASYNC, ERR_CERT_AUTHORITY_INVALID);
1530 ssl_socket_data->ssl_info.cert =
1531 ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der");
1532 ASSERT_TRUE(ssl_socket_data->ssl_info.cert.get());
1533 url_request_context_host_.AddSSLSocketDataProvider(
1534 std::move(ssl_socket_data));
1535 url_request_context_host_.AddSSLSocketDataProvider(
1536 std::make_unique<SSLSocketDataProvider>(ASYNC, OK));
1537 AddRawExpectations(BuildNullSocketData());
1538 CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {},
1539 {});
1540 // WaitUntilConnectDone doesn't work in this case.
1541 base::RunLoop().RunUntilIdle();
1542 ASSERT_TRUE(ssl_error_callbacks_);
1543 ssl_error_callbacks_->ContinueSSLRequest();
1544 WaitUntilConnectDone();
1545 EXPECT_FALSE(has_failed());
1546 EXPECT_TRUE(stream_);
1547 }
1548
1549 // If the server requests authorisation, but we have no credentials, the
1550 // connection should fail cleanly.
TEST_P(WebSocketStreamCreateBasicAuthTest,FailureNoCredentials)1551 TEST_P(WebSocketStreamCreateBasicAuthTest, FailureNoCredentials) {
1552 CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
1553 {}, kUnauthorizedResponse);
1554 WaitUntilConnectDone();
1555 EXPECT_TRUE(has_failed());
1556 EXPECT_EQ("HTTP Authentication failed; no valid credentials available",
1557 failure_message());
1558 EXPECT_FALSE(response_info_);
1559 }
1560
TEST_P(WebSocketStreamCreateBasicAuthTest,SuccessPasswordInUrl)1561 TEST_P(WebSocketStreamCreateBasicAuthTest, SuccessPasswordInUrl) {
1562 CreateAndConnectAuthHandshake("ws://foo:[email protected]/", "Zm9vOmJhcg==",
1563 WebSocketStandardResponse(std::string()));
1564 WaitUntilConnectDone();
1565 EXPECT_FALSE(has_failed());
1566 EXPECT_TRUE(stream_);
1567 ASSERT_TRUE(response_info_);
1568 EXPECT_EQ(101, response_info_->headers->response_code());
1569 }
1570
TEST_P(WebSocketStreamCreateBasicAuthTest,FailureIncorrectPasswordInUrl)1571 TEST_P(WebSocketStreamCreateBasicAuthTest, FailureIncorrectPasswordInUrl) {
1572 CreateAndConnectAuthHandshake("ws://foo:[email protected]/",
1573 "Zm9vOmJheg==", kUnauthorizedResponse);
1574 WaitUntilConnectDone();
1575 EXPECT_TRUE(has_failed());
1576 EXPECT_FALSE(response_info_);
1577 }
1578
TEST_P(WebSocketStreamCreateBasicAuthTest,SuccessfulConnectionReuse)1579 TEST_P(WebSocketStreamCreateBasicAuthTest, SuccessfulConnectionReuse) {
1580 std::string request1 = WebSocketStandardRequest(
1581 "/", "www.example.org", Origin(), /*send_additional_request_headers=*/{},
1582 /*extra_headers=*/{});
1583 std::string response1 = kUnauthorizedResponse;
1584 std::string request2 = WebSocketStandardRequest(
1585 "/", "www.example.org", Origin(),
1586 {{"Authorization", "Basic Zm9vOmJhcg=="}}, /*extra_headers=*/{});
1587 std::string response2 = WebSocketStandardResponse(std::string());
1588 MockWrite writes[] = {
1589 MockWrite(SYNCHRONOUS, 0, request1.c_str()),
1590 MockWrite(SYNCHRONOUS, 2, request2.c_str()),
1591 };
1592 MockRead reads[3] = {
1593 MockRead(SYNCHRONOUS, 1, response1.c_str()),
1594 MockRead(SYNCHRONOUS, 3, response2.c_str()),
1595 MockRead(SYNCHRONOUS, ERR_IO_PENDING, 4),
1596 };
1597 CreateAndConnectRawExpectations("ws://foo:[email protected]/",
1598 NoSubProtocols(), HttpRequestHeaders(),
1599 BuildSocketData(reads, writes));
1600 WaitUntilConnectDone();
1601 EXPECT_FALSE(has_failed());
1602 EXPECT_TRUE(stream_);
1603 ASSERT_TRUE(response_info_);
1604 EXPECT_EQ(101, response_info_->headers->response_code());
1605 }
1606
TEST_P(WebSocketStreamCreateBasicAuthTest,OnAuthRequiredCancelAuth)1607 TEST_P(WebSocketStreamCreateBasicAuthTest, OnAuthRequiredCancelAuth) {
1608 CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
1609 {}, kUnauthorizedResponse);
1610
1611 EXPECT_FALSE(request_info_);
1612 EXPECT_FALSE(response_info_);
1613 on_auth_required_rv_ = ERR_IO_PENDING;
1614 WaitUntilOnAuthRequired();
1615
1616 EXPECT_FALSE(stream_);
1617 EXPECT_FALSE(has_failed());
1618
1619 std::move(on_auth_required_callback_).Run(nullptr);
1620 WaitUntilConnectDone();
1621 EXPECT_FALSE(stream_);
1622 EXPECT_TRUE(has_failed());
1623 }
1624
TEST_P(WebSocketStreamCreateBasicAuthTest,OnAuthRequiredSetAuth)1625 TEST_P(WebSocketStreamCreateBasicAuthTest, OnAuthRequiredSetAuth) {
1626 CreateAndConnectRawExpectations(
1627 "ws://www.example.org/", NoSubProtocols(), HttpRequestHeaders(),
1628 helper_.BuildAuthSocketData(kUnauthorizedResponse,
1629 RequestExpectation("Zm9vOmJheg=="),
1630 WebSocketStandardResponse(std::string())));
1631
1632 EXPECT_FALSE(request_info_);
1633 EXPECT_FALSE(response_info_);
1634 on_auth_required_rv_ = ERR_IO_PENDING;
1635 WaitUntilOnAuthRequired();
1636
1637 EXPECT_FALSE(stream_);
1638 EXPECT_FALSE(has_failed());
1639
1640 AuthCredentials credentials(u"foo", u"baz");
1641 std::move(on_auth_required_callback_).Run(&credentials);
1642
1643 WaitUntilConnectDone();
1644 EXPECT_TRUE(stream_);
1645 EXPECT_FALSE(has_failed());
1646 }
1647
1648 // Digest auth has the same connection semantics as Basic auth, so we can
1649 // generally assume that whatever works for Basic auth will also work for
1650 // Digest. There's just one test here, to confirm that it works at all.
TEST_P(WebSocketStreamCreateDigestAuthTest,DigestPasswordInUrl)1651 TEST_P(WebSocketStreamCreateDigestAuthTest, DigestPasswordInUrl) {
1652 CreateAndConnectRawExpectations(
1653 "ws://FooBar:[email protected]/", NoSubProtocols(),
1654 HttpRequestHeaders(),
1655 helper_.BuildAuthSocketData(kUnauthorizedResponse, kAuthorizedRequest,
1656 WebSocketStandardResponse(std::string())));
1657 WaitUntilConnectDone();
1658 EXPECT_FALSE(has_failed());
1659 EXPECT_TRUE(stream_);
1660 ASSERT_TRUE(response_info_);
1661 EXPECT_EQ(101, response_info_->headers->response_code());
1662 }
1663
TEST_P(WebSocketMultiProtocolStreamCreateTest,Incomplete)1664 TEST_P(WebSocketMultiProtocolStreamCreateTest, Incomplete) {
1665 base::HistogramTester histogram_tester;
1666
1667 AddSSLData();
1668 if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
1669 std::string request = WebSocketStandardRequest(
1670 "/", "www.example.org", Origin(),
1671 /*send_additional_request_headers=*/{}, /*extra_headers=*/{});
1672 MockRead reads[] = {MockRead(ASYNC, ERR_IO_PENDING, 0)};
1673 MockWrite writes[] = {MockWrite(ASYNC, 1, request.c_str())};
1674 CreateAndConnectRawExpectations("wss://www.example.org/", NoSubProtocols(),
1675 HttpRequestHeaders(),
1676 BuildSocketData(reads, writes));
1677 base::RunLoop().RunUntilIdle();
1678 stream_request_.reset();
1679
1680 auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
1681 "Net.WebSocket.HandshakeResult2");
1682 EXPECT_EQ(1, samples->TotalCount());
1683 EXPECT_EQ(1,
1684 samples->GetCount(static_cast<int>(
1685 WebSocketHandshakeStreamBase::HandshakeResult::INCOMPLETE)));
1686 } else {
1687 DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
1688 CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {},
1689 {});
1690 stream_request_.reset();
1691
1692 auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
1693 "Net.WebSocket.HandshakeResult2");
1694 EXPECT_EQ(1, samples->TotalCount());
1695 EXPECT_EQ(
1696 1,
1697 samples->GetCount(static_cast<int>(
1698 WebSocketHandshakeStreamBase::HandshakeResult::HTTP2_INCOMPLETE)));
1699 }
1700 }
1701
TEST_P(WebSocketMultiProtocolStreamCreateTest,Http2StreamReset)1702 TEST_P(WebSocketMultiProtocolStreamCreateTest, Http2StreamReset) {
1703 AddSSLData();
1704
1705 if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
1706 // This is a dummy transaction to avoid crash in ~URLRequestContext().
1707 CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {},
1708 {});
1709 } else {
1710 DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
1711 base::HistogramTester histogram_tester;
1712
1713 SetResetWebSocketHttp2Stream(true);
1714 CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {},
1715 {});
1716 base::RunLoop().RunUntilIdle();
1717 stream_request_.reset();
1718
1719 EXPECT_TRUE(has_failed());
1720 EXPECT_EQ("Stream closed with error: net::ERR_HTTP2_PROTOCOL_ERROR",
1721 failure_message());
1722
1723 auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
1724 "Net.WebSocket.HandshakeResult2");
1725 EXPECT_EQ(1, samples->TotalCount());
1726 EXPECT_EQ(
1727 1, samples->GetCount(static_cast<int>(
1728 WebSocketHandshakeStreamBase::HandshakeResult::HTTP2_FAILED)));
1729 }
1730 }
1731
TEST_P(WebSocketStreamCreateTest,HandleErrConnectionClosed)1732 TEST_P(WebSocketStreamCreateTest, HandleErrConnectionClosed) {
1733 base::HistogramTester histogram_tester;
1734
1735 static constexpr char kTruncatedResponse[] =
1736 "HTTP/1.1 101 Switching Protocols\r\n"
1737 "Upgrade: websocket\r\n"
1738 "Connection: Upgrade\r\n"
1739 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
1740 "Cache-Control: no-sto";
1741
1742 std::string request = WebSocketStandardRequest(
1743 "/", "www.example.org", Origin(), /*send_additional_request_headers=*/{},
1744 /*extra_headers=*/{});
1745 MockRead reads[] = {
1746 MockRead(SYNCHRONOUS, 1, kTruncatedResponse),
1747 MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED, 2),
1748 };
1749 MockWrite writes[] = {MockWrite(SYNCHRONOUS, 0, request.c_str())};
1750 std::unique_ptr<SequencedSocketData> socket_data(
1751 BuildSocketData(reads, writes));
1752 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
1753 CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
1754 HttpRequestHeaders(), std::move(socket_data));
1755 WaitUntilConnectDone();
1756 EXPECT_TRUE(has_failed());
1757
1758 stream_request_.reset();
1759
1760 auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
1761 "Net.WebSocket.HandshakeResult2");
1762 EXPECT_EQ(1, samples->TotalCount());
1763 EXPECT_EQ(1, samples->GetCount(static_cast<int>(
1764 WebSocketHandshakeStreamBase::HandshakeResult::
1765 FAILED_SWITCHING_PROTOCOLS)));
1766 }
1767
TEST_P(WebSocketStreamCreateTest,HandleErrTunnelConnectionFailed)1768 TEST_P(WebSocketStreamCreateTest, HandleErrTunnelConnectionFailed) {
1769 static constexpr char kConnectRequest[] =
1770 "CONNECT www.example.org:80 HTTP/1.1\r\n"
1771 "Host: www.example.org:80\r\n"
1772 "Proxy-Connection: keep-alive\r\n"
1773 "\r\n";
1774
1775 static constexpr char kProxyResponse[] =
1776 "HTTP/1.1 403 Forbidden\r\n"
1777 "Content-Type: text/html\r\n"
1778 "Content-Length: 9\r\n"
1779 "Connection: keep-alive\r\n"
1780 "\r\n"
1781 "Forbidden";
1782
1783 MockRead reads[] = {MockRead(SYNCHRONOUS, 1, kProxyResponse)};
1784 MockWrite writes[] = {MockWrite(SYNCHRONOUS, 0, kConnectRequest)};
1785 std::unique_ptr<SequencedSocketData> socket_data(
1786 BuildSocketData(reads, writes));
1787 url_request_context_host_.SetProxyConfig("https=proxy:8000");
1788 CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
1789 HttpRequestHeaders(), std::move(socket_data));
1790 WaitUntilConnectDone();
1791 EXPECT_TRUE(has_failed());
1792 EXPECT_EQ("Establishing a tunnel via proxy server failed.",
1793 failure_message());
1794 }
1795
TEST_P(WebSocketStreamCreateTest,CancelSSLRequestAfterDelete)1796 TEST_P(WebSocketStreamCreateTest, CancelSSLRequestAfterDelete) {
1797 auto ssl_socket_data = std::make_unique<SSLSocketDataProvider>(
1798 ASYNC, ERR_CERT_AUTHORITY_INVALID);
1799 ssl_socket_data->ssl_info.cert =
1800 ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der");
1801 ASSERT_TRUE(ssl_socket_data->ssl_info.cert.get());
1802 url_request_context_host_.AddSSLSocketDataProvider(
1803 std::move(ssl_socket_data));
1804
1805 MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_CONNECTION_RESET, 0)};
1806 MockWrite writes[] = {MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET, 1)};
1807 std::unique_ptr<SequencedSocketData> raw_socket_data(
1808 BuildSocketData(reads, writes));
1809 CreateAndConnectRawExpectations("wss://www.example.org/", NoSubProtocols(),
1810 HttpRequestHeaders(),
1811 std::move(raw_socket_data));
1812 base::RunLoop().RunUntilIdle();
1813 EXPECT_FALSE(has_failed());
1814 ASSERT_TRUE(ssl_error_callbacks_);
1815 stream_request_.reset();
1816 ssl_error_callbacks_->CancelSSLRequest(ERR_CERT_AUTHORITY_INVALID,
1817 &ssl_info_);
1818 }
1819
TEST_P(WebSocketStreamCreateTest,ContinueSSLRequestAfterDelete)1820 TEST_P(WebSocketStreamCreateTest, ContinueSSLRequestAfterDelete) {
1821 auto ssl_socket_data = std::make_unique<SSLSocketDataProvider>(
1822 ASYNC, ERR_CERT_AUTHORITY_INVALID);
1823 ssl_socket_data->ssl_info.cert =
1824 ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der");
1825 ASSERT_TRUE(ssl_socket_data->ssl_info.cert.get());
1826 url_request_context_host_.AddSSLSocketDataProvider(
1827 std::move(ssl_socket_data));
1828
1829 MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_CONNECTION_RESET, 0)};
1830 MockWrite writes[] = {MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET, 1)};
1831 std::unique_ptr<SequencedSocketData> raw_socket_data(
1832 BuildSocketData(reads, writes));
1833 CreateAndConnectRawExpectations("wss://www.example.org/", NoSubProtocols(),
1834 HttpRequestHeaders(),
1835 std::move(raw_socket_data));
1836 base::RunLoop().RunUntilIdle();
1837 EXPECT_FALSE(has_failed());
1838 ASSERT_TRUE(ssl_error_callbacks_);
1839 stream_request_.reset();
1840 ssl_error_callbacks_->ContinueSSLRequest();
1841 }
1842
TEST_P(WebSocketStreamCreateTest,HandleConnectionCloseInFirstSegment)1843 TEST_P(WebSocketStreamCreateTest, HandleConnectionCloseInFirstSegment) {
1844 std::string request = WebSocketStandardRequest(
1845 "/", "www.example.org", Origin(), /*send_additional_request_headers=*/{},
1846 /*extra_headers=*/{});
1847
1848 // The response headers are immediately followed by a close frame, length 11,
1849 // code 1013, reason "Try Again".
1850 std::string close_body = "\x03\xf5Try Again";
1851 std::string response = WebSocketStandardResponse(std::string()) + "\x88" +
1852 static_cast<char>(close_body.size()) + close_body;
1853 MockRead reads[] = {
1854 MockRead(SYNCHRONOUS, response.data(), response.size(), 1),
1855 MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED, 2),
1856 };
1857 MockWrite writes[] = {MockWrite(SYNCHRONOUS, 0, request.c_str())};
1858 std::unique_ptr<SequencedSocketData> socket_data(
1859 BuildSocketData(reads, writes));
1860 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
1861 CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
1862 HttpRequestHeaders(), std::move(socket_data));
1863 WaitUntilConnectDone();
1864 ASSERT_TRUE(stream_);
1865
1866 std::vector<std::unique_ptr<WebSocketFrame>> frames;
1867 TestCompletionCallback callback1;
1868 int rv1 = stream_->ReadFrames(&frames, callback1.callback());
1869 rv1 = callback1.GetResult(rv1);
1870 ASSERT_THAT(rv1, IsOk());
1871 ASSERT_EQ(1U, frames.size());
1872 EXPECT_EQ(frames[0]->header.opcode, WebSocketFrameHeader::kOpCodeClose);
1873 EXPECT_TRUE(frames[0]->header.final);
1874 EXPECT_EQ(close_body,
1875 std::string(frames[0]->payload, frames[0]->header.payload_length));
1876
1877 std::vector<std::unique_ptr<WebSocketFrame>> empty_frames;
1878 TestCompletionCallback callback2;
1879 int rv2 = stream_->ReadFrames(&empty_frames, callback2.callback());
1880 rv2 = callback2.GetResult(rv2);
1881 ASSERT_THAT(rv2, IsError(ERR_CONNECTION_CLOSED));
1882 }
1883
1884 } // namespace
1885 } // namespace net
1886