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