12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808 |
- // Copyright 2013 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "net/websockets/websocket_stream.h"
- #include <algorithm>
- #include <string>
- #include <utility>
- #include <vector>
- #include "base/compiler_specific.h"
- #include "base/containers/span.h"
- #include "base/memory/ptr_util.h"
- #include "base/metrics/histogram.h"
- #include "base/metrics/histogram_samples.h"
- #include "base/metrics/statistics_recorder.h"
- #include "base/run_loop.h"
- #include "base/strings/string_piece.h"
- #include "base/strings/stringprintf.h"
- #include "base/test/metrics/histogram_tester.h"
- #include "base/test/scoped_feature_list.h"
- #include "base/timer/mock_timer.h"
- #include "base/timer/timer.h"
- #include "net/base/features.h"
- #include "net/base/isolation_info.h"
- #include "net/base/net_errors.h"
- #include "net/base/url_util.h"
- #include "net/http/http_request_headers.h"
- #include "net/http/http_response_headers.h"
- #include "net/proxy_resolution/proxy_resolution_service.h"
- #include "net/socket/client_socket_handle.h"
- #include "net/socket/socket_test_util.h"
- #include "net/spdy/spdy_test_util_common.h"
- #include "net/test/cert_test_util.h"
- #include "net/test/gtest_util.h"
- #include "net/test/test_data_directory.h"
- #include "net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.h"
- #include "net/url_request/url_request_test_util.h"
- #include "net/websockets/websocket_basic_handshake_stream.h"
- #include "net/websockets/websocket_frame.h"
- #include "net/websockets/websocket_stream_create_test_base.h"
- #include "net/websockets/websocket_test_util.h"
- #include "testing/gmock/include/gmock/gmock.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #include "url/gurl.h"
- #include "url/origin.h"
- using ::net::test::IsError;
- using ::net::test::IsOk;
- using ::testing::TestWithParam;
- using ::testing::Values;
- namespace net {
- namespace {
- enum HandshakeStreamType { BASIC_HANDSHAKE_STREAM, HTTP2_HANDSHAKE_STREAM };
- // Simple builder for a SequencedSocketData object to save repetitive code.
- // It always sets the connect data to MockConnect(SYNCHRONOUS, OK), so it cannot
- // be used in tests where the connect fails. In practice, those tests never have
- // any read/write data and so can't benefit from it anyway. The arrays are not
- // copied. It is up to the caller to ensure they stay in scope until the test
- // ends.
- std::unique_ptr<SequencedSocketData> BuildSocketData(
- base::span<MockRead> reads,
- base::span<MockWrite> writes) {
- auto socket_data = std::make_unique<SequencedSocketData>(reads, writes);
- socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
- return socket_data;
- }
- // Builder for a SequencedSocketData that expects nothing. This does not
- // set the connect data, so the calling code must do that explicitly.
- std::unique_ptr<SequencedSocketData> BuildNullSocketData() {
- return std::make_unique<SequencedSocketData>();
- }
- class MockWeakTimer : public base::MockOneShotTimer,
- public base::SupportsWeakPtr<MockWeakTimer> {
- public:
- MockWeakTimer() = default;
- };
- const char kOrigin[] = "http://www.example.org";
- static url::Origin Origin() {
- return url::Origin::Create(GURL(kOrigin));
- }
- static net::SiteForCookies SiteForCookies() {
- return net::SiteForCookies::FromOrigin(Origin());
- }
- static IsolationInfo CreateIsolationInfo() {
- url::Origin origin = Origin();
- return IsolationInfo::Create(IsolationInfo::RequestType::kOther, origin,
- origin, SiteForCookies::FromOrigin(origin));
- }
- class WebSocketStreamCreateTest : public TestWithParam<HandshakeStreamType>,
- public WebSocketStreamCreateTestBase {
- protected:
- WebSocketStreamCreateTest() : stream_type_(GetParam()) {
- // Make sure these tests all pass with connection partitioning enabled. The
- // disabled case is less interesting, and is tested more directly at lower
- // layers.
- feature_list_.InitAndEnableFeature(
- features::kPartitionConnectionsByNetworkIsolationKey);
- }
- ~WebSocketStreamCreateTest() override {
- // Permit any endpoint locks to be released.
- stream_request_.reset();
- stream_.reset();
- base::RunLoop().RunUntilIdle();
- }
- // Normally it's easier to use CreateAndConnectRawExpectations() instead. This
- // method is only needed when multiple sockets are involved.
- void AddRawExpectations(std::unique_ptr<SequencedSocketData> socket_data) {
- url_request_context_host_.AddRawExpectations(std::move(socket_data));
- }
- void AddSSLData() {
- auto ssl_data = std::make_unique<SSLSocketDataProvider>(ASYNC, OK);
- ssl_data->ssl_info.cert =
- ImportCertFromFile(GetTestCertsDirectory(), "wildcard.pem");
- if (stream_type_ == HTTP2_HANDSHAKE_STREAM)
- ssl_data->next_proto = kProtoHTTP2;
- ASSERT_TRUE(ssl_data->ssl_info.cert.get());
- url_request_context_host_.AddSSLSocketDataProvider(std::move(ssl_data));
- }
- void SetTimer(std::unique_ptr<base::OneShotTimer> timer) {
- timer_ = std::move(timer);
- }
- void SetAdditionalResponseData(std::string additional_data) {
- additional_data_ = std::move(additional_data);
- }
- void SetHttp2ResponseStatus(const char* const http2_response_status) {
- http2_response_status_ = http2_response_status;
- }
- void SetResetWebSocketHttp2Stream(bool reset_websocket_http2_stream) {
- reset_websocket_http2_stream_ = reset_websocket_http2_stream;
- }
- // Set up mock data and start websockets request, either for WebSocket
- // upgraded from an HTTP/1 connection, or for a WebSocket request over HTTP/2.
- void 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) {
- const GURL socket_url(url);
- const std::string socket_host = GetHostAndOptionalPort(socket_url);
- const std::string socket_path = socket_url.path();
- if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
- url_request_context_host_.SetExpectations(
- WebSocketStandardRequest(socket_path, socket_host, Origin(),
- send_additional_request_headers,
- extra_request_headers),
- WebSocketStandardResponse(
- WebSocketExtraHeadersToString(extra_response_headers)) +
- additional_data_);
- CreateAndConnectStream(socket_url, sub_protocols, Origin(),
- SiteForCookies(), CreateIsolationInfo(),
- WebSocketExtraHeadersToHttpRequestHeaders(
- send_additional_request_headers),
- std::move(timer_));
- return;
- }
- DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
- // TODO(bnc): Find a way to clear
- // spdy_session_pool.enable_sending_initial_data_ to avoid sending
- // connection preface, initial settings, and window update.
- // HTTP/2 connection preface.
- frames_.emplace_back(const_cast<char*>(spdy::kHttp2ConnectionHeaderPrefix),
- spdy::kHttp2ConnectionHeaderPrefixSize,
- /* owns_buffer = */ false);
- AddWrite(&frames_.back());
- // Server advertises WebSockets over HTTP/2 support.
- spdy::SettingsMap read_settings;
- read_settings[spdy::SETTINGS_ENABLE_CONNECT_PROTOCOL] = 1;
- frames_.push_back(spdy_util_.ConstructSpdySettings(read_settings));
- AddRead(&frames_.back());
- // Initial SETTINGS frame.
- spdy::SettingsMap write_settings;
- write_settings[spdy::SETTINGS_HEADER_TABLE_SIZE] = kSpdyMaxHeaderTableSize;
- write_settings[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] =
- kSpdyMaxConcurrentPushedStreams;
- write_settings[spdy::SETTINGS_INITIAL_WINDOW_SIZE] = 6 * 1024 * 1024;
- write_settings[spdy::SETTINGS_MAX_HEADER_LIST_SIZE] =
- kSpdyMaxHeaderListSize;
- frames_.push_back(spdy_util_.ConstructSpdySettings(write_settings));
- AddWrite(&frames_.back());
- // Initial window update frame.
- frames_.push_back(spdy_util_.ConstructSpdyWindowUpdate(0, 0x00ef0001));
- AddWrite(&frames_.back());
- // SETTINGS ACK sent as a response to server's SETTINGS frame.
- frames_.push_back(spdy_util_.ConstructSpdySettingsAck());
- AddWrite(&frames_.back());
- // First request. This is necessary, because a WebSockets request currently
- // does not open a new HTTP/2 connection, it only uses an existing one.
- const char* const kExtraRequestHeaders[] = {
- "user-agent", "", "accept-encoding", "gzip, deflate",
- "accept-language", "en-us,fr"};
- frames_.push_back(spdy_util_.ConstructSpdyGet(
- kExtraRequestHeaders, std::size(kExtraRequestHeaders) / 2, 1,
- DEFAULT_PRIORITY));
- AddWrite(&frames_.back());
- // SETTINGS ACK frame sent by the server in response to the client's
- // initial SETTINGS frame.
- frames_.push_back(spdy_util_.ConstructSpdySettingsAck());
- AddRead(&frames_.back());
- // Response headers to first request.
- frames_.push_back(spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1));
- AddRead(&frames_.back());
- // Response body to first request.
- frames_.push_back(spdy_util_.ConstructSpdyDataFrame(1, true));
- AddRead(&frames_.back());
- // First request is closed.
- spdy_util_.UpdateWithStreamDestruction(1);
- // WebSocket request.
- spdy::Http2HeaderBlock request_headers = WebSocketHttp2Request(
- socket_path, socket_host, kOrigin, extra_request_headers);
- frames_.push_back(spdy_util_.ConstructSpdyHeaders(
- 3, std::move(request_headers), DEFAULT_PRIORITY, false));
- AddWrite(&frames_.back());
- if (reset_websocket_http2_stream_) {
- frames_.push_back(
- spdy_util_.ConstructSpdyRstStream(3, spdy::ERROR_CODE_CANCEL));
- AddRead(&frames_.back());
- } else {
- // Response to WebSocket request.
- std::vector<std::string> extra_response_header_keys;
- std::vector<const char*> extra_response_headers_vector;
- for (const auto& extra_header : extra_response_headers) {
- // Save a lowercase copy of the header key.
- extra_response_header_keys.push_back(
- base::ToLowerASCII(extra_header.first));
- // Save a pointer to this lowercase copy.
- extra_response_headers_vector.push_back(
- extra_response_header_keys.back().c_str());
- // Save a pointer to the original header value provided by the caller.
- extra_response_headers_vector.push_back(extra_header.second.c_str());
- }
- frames_.push_back(spdy_util_.ConstructSpdyReplyError(
- http2_response_status_, extra_response_headers_vector.data(),
- extra_response_headers_vector.size() / 2, 3));
- AddRead(&frames_.back());
- // WebSocket data received.
- if (!additional_data_.empty()) {
- frames_.push_back(
- spdy_util_.ConstructSpdyDataFrame(3, additional_data_, true));
- AddRead(&frames_.back());
- }
- // Client cancels HTTP/2 stream when request is destroyed.
- frames_.push_back(
- spdy_util_.ConstructSpdyRstStream(3, spdy::ERROR_CODE_CANCEL));
- AddWrite(&frames_.back());
- }
- // EOF.
- reads_.emplace_back(ASYNC, 0, sequence_number_++);
- auto socket_data = std::make_unique<SequencedSocketData>(reads_, writes_);
- socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
- AddRawExpectations(std::move(socket_data));
- // Send first request. This makes sure server's
- // spdy::SETTINGS_ENABLE_CONNECT_PROTOCOL advertisement is read.
- URLRequestContext* context =
- url_request_context_host_.GetURLRequestContext();
- TestDelegate delegate;
- std::unique_ptr<URLRequest> request = context->CreateRequest(
- GURL("https://www.example.org/"), DEFAULT_PRIORITY, &delegate,
- TRAFFIC_ANNOTATION_FOR_TESTS, /*is_for_websockets=*/false);
- // The IsolationInfo has to match for a socket to be reused.
- request->set_isolation_info(CreateIsolationInfo());
- request->Start();
- EXPECT_TRUE(request->is_pending());
- delegate.RunUntilComplete();
- EXPECT_FALSE(request->is_pending());
- CreateAndConnectStream(socket_url, sub_protocols, Origin(),
- SiteForCookies(), CreateIsolationInfo(),
- WebSocketExtraHeadersToHttpRequestHeaders(
- send_additional_request_headers),
- std::move(timer_));
- }
- // Like CreateAndConnectStandard(), but allow for arbitrary response body.
- // Only for HTTP/1-based WebSockets.
- void 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) {
- ASSERT_EQ(BASIC_HANDSHAKE_STREAM, stream_type_);
- const GURL socket_url(url);
- const std::string socket_host = GetHostAndOptionalPort(socket_url);
- const std::string socket_path = socket_url.path();
- url_request_context_host_.SetExpectations(
- WebSocketStandardRequest(socket_path, socket_host, Origin(),
- send_additional_request_headers,
- extra_request_headers),
- response_body);
- CreateAndConnectStream(socket_url, sub_protocols, Origin(),
- SiteForCookies(), CreateIsolationInfo(),
- WebSocketExtraHeadersToHttpRequestHeaders(
- send_additional_request_headers),
- nullptr);
- }
- // Like CreateAndConnectStandard(), but take extra response headers as a
- // string. This can save space in case of a very large response.
- // Only for HTTP/1-based WebSockets.
- void CreateAndConnectStringResponse(
- base::StringPiece url,
- const std::vector<std::string>& sub_protocols,
- const std::string& extra_response_headers) {
- ASSERT_EQ(BASIC_HANDSHAKE_STREAM, stream_type_);
- const GURL socket_url(url);
- const std::string socket_host = GetHostAndOptionalPort(socket_url);
- const std::string socket_path = socket_url.path();
- url_request_context_host_.SetExpectations(
- WebSocketStandardRequest(socket_path, socket_host, Origin(),
- /*send_additional_request_headers=*/{},
- /*extra_headers=*/{}),
- WebSocketStandardResponse(extra_response_headers));
- CreateAndConnectStream(socket_url, sub_protocols, Origin(),
- SiteForCookies(), CreateIsolationInfo(),
- HttpRequestHeaders(), nullptr);
- }
- // Like CreateAndConnectStandard(), but take raw mock data.
- void CreateAndConnectRawExpectations(
- base::StringPiece url,
- const std::vector<std::string>& sub_protocols,
- const HttpRequestHeaders& additional_headers,
- std::unique_ptr<SequencedSocketData> socket_data) {
- ASSERT_EQ(BASIC_HANDSHAKE_STREAM, stream_type_);
- AddRawExpectations(std::move(socket_data));
- CreateAndConnectStream(GURL(url), sub_protocols, Origin(), SiteForCookies(),
- CreateIsolationInfo(), additional_headers,
- std::move(timer_));
- }
- private:
- void AddWrite(const spdy::SpdySerializedFrame* frame) {
- writes_.emplace_back(ASYNC, frame->data(), frame->size(),
- sequence_number_++);
- }
- void AddRead(const spdy::SpdySerializedFrame* frame) {
- reads_.emplace_back(ASYNC, frame->data(), frame->size(),
- sequence_number_++);
- }
- protected:
- const HandshakeStreamType stream_type_;
- private:
- base::test::ScopedFeatureList feature_list_;
- std::unique_ptr<base::OneShotTimer> timer_;
- std::string additional_data_;
- const char* http2_response_status_ = "200";
- bool reset_websocket_http2_stream_ = false;
- SpdyTestUtil spdy_util_;
- NetLogWithSource log_;
- int sequence_number_ = 0;
- // Store mock HTTP/2 data.
- std::vector<spdy::SpdySerializedFrame> frames_;
- // Store MockRead and MockWrite objects that have pointers to above data.
- std::vector<MockRead> reads_;
- std::vector<MockWrite> writes_;
- };
- INSTANTIATE_TEST_SUITE_P(All,
- WebSocketStreamCreateTest,
- Values(BASIC_HANDSHAKE_STREAM));
- using WebSocketMultiProtocolStreamCreateTest = WebSocketStreamCreateTest;
- INSTANTIATE_TEST_SUITE_P(All,
- WebSocketMultiProtocolStreamCreateTest,
- Values(BASIC_HANDSHAKE_STREAM,
- HTTP2_HANDSHAKE_STREAM));
- // There are enough tests of the Sec-WebSocket-Extensions header that they
- // deserve their own test fixture.
- class WebSocketStreamCreateExtensionTest
- : public WebSocketMultiProtocolStreamCreateTest {
- protected:
- // Performs a standard connect, with the value of the Sec-WebSocket-Extensions
- // header in the response set to |extensions_header_value|. Runs the event
- // loop to allow the connect to complete.
- void CreateAndConnectWithExtensions(
- const std::string& extensions_header_value) {
- AddSSLData();
- CreateAndConnectStandard(
- "wss://www.example.org/testing_path", NoSubProtocols(), {}, {},
- {{"Sec-WebSocket-Extensions", extensions_header_value}});
- WaitUntilConnectDone();
- }
- };
- INSTANTIATE_TEST_SUITE_P(All,
- WebSocketStreamCreateExtensionTest,
- Values(BASIC_HANDSHAKE_STREAM,
- HTTP2_HANDSHAKE_STREAM));
- // Common code to construct expectations for authentication tests that receive
- // the auth challenge on one connection and then create a second connection to
- // send the authenticated request on.
- class CommonAuthTestHelper {
- public:
- CommonAuthTestHelper() : reads_(), writes_() {}
- CommonAuthTestHelper(const CommonAuthTestHelper&) = delete;
- CommonAuthTestHelper& operator=(const CommonAuthTestHelper&) = delete;
- std::unique_ptr<SequencedSocketData> BuildAuthSocketData(
- std::string response1,
- std::string request2,
- std::string response2) {
- request1_ = WebSocketStandardRequest("/", "www.example.org", Origin(),
- /*send_additional_request_headers=*/{},
- /*extra_headers=*/{});
- response1_ = std::move(response1);
- request2_ = std::move(request2);
- response2_ = std::move(response2);
- writes_[0] = MockWrite(SYNCHRONOUS, 0, request1_.c_str());
- reads_[0] = MockRead(SYNCHRONOUS, 1, response1_.c_str());
- writes_[1] = MockWrite(SYNCHRONOUS, 2, request2_.c_str());
- reads_[1] = MockRead(SYNCHRONOUS, 3, response2_.c_str());
- reads_[2] = MockRead(SYNCHRONOUS, OK, 4); // Close connection
- return BuildSocketData(reads_, writes_);
- }
- private:
- // These need to be object-scoped since they have to remain valid until all
- // socket operations in the test are complete.
- std::string request1_;
- std::string request2_;
- std::string response1_;
- std::string response2_;
- MockRead reads_[3];
- MockWrite writes_[2];
- };
- // Data and methods for BasicAuth tests.
- class WebSocketStreamCreateBasicAuthTest : public WebSocketStreamCreateTest {
- protected:
- void CreateAndConnectAuthHandshake(base::StringPiece url,
- base::StringPiece base64_user_pass,
- base::StringPiece response2) {
- CreateAndConnectRawExpectations(
- url, NoSubProtocols(), HttpRequestHeaders(),
- helper_.BuildAuthSocketData(kUnauthorizedResponse,
- RequestExpectation(base64_user_pass),
- std::string(response2)));
- }
- static std::string RequestExpectation(base::StringPiece base64_user_pass) {
- static const char request2format[] =
- "GET / HTTP/1.1\r\n"
- "Host: www.example.org\r\n"
- "Connection: Upgrade\r\n"
- "Pragma: no-cache\r\n"
- "Cache-Control: no-cache\r\n"
- "Authorization: Basic %s\r\n"
- "Upgrade: websocket\r\n"
- "Origin: http://www.example.org\r\n"
- "Sec-WebSocket-Version: 13\r\n"
- "User-Agent: \r\n"
- "Accept-Encoding: gzip, deflate\r\n"
- "Accept-Language: en-us,fr\r\n"
- "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
- "Sec-WebSocket-Extensions: permessage-deflate; "
- "client_max_window_bits\r\n"
- "\r\n";
- return base::StringPrintf(request2format, base64_user_pass.data());
- }
- static const char kUnauthorizedResponse[];
- CommonAuthTestHelper helper_;
- };
- INSTANTIATE_TEST_SUITE_P(All,
- WebSocketStreamCreateBasicAuthTest,
- Values(BASIC_HANDSHAKE_STREAM));
- class WebSocketStreamCreateDigestAuthTest : public WebSocketStreamCreateTest {
- protected:
- static const char kUnauthorizedResponse[];
- static const char kAuthorizedRequest[];
- CommonAuthTestHelper helper_;
- };
- INSTANTIATE_TEST_SUITE_P(All,
- WebSocketStreamCreateDigestAuthTest,
- Values(BASIC_HANDSHAKE_STREAM));
- const char WebSocketStreamCreateBasicAuthTest::kUnauthorizedResponse[] =
- "HTTP/1.1 401 Unauthorized\r\n"
- "Content-Length: 0\r\n"
- "WWW-Authenticate: Basic realm=\"camelot\"\r\n"
- "\r\n";
- // These negotiation values are borrowed from
- // http_auth_handler_digest_unittest.cc. Feel free to come up with new ones if
- // you are bored. Only the weakest (no qop) variants of Digest authentication
- // can be tested by this method, because the others involve random input.
- const char WebSocketStreamCreateDigestAuthTest::kUnauthorizedResponse[] =
- "HTTP/1.1 401 Unauthorized\r\n"
- "Content-Length: 0\r\n"
- "WWW-Authenticate: Digest realm=\"Oblivion\", nonce=\"nonce-value\"\r\n"
- "\r\n";
- const char WebSocketStreamCreateDigestAuthTest::kAuthorizedRequest[] =
- "GET / HTTP/1.1\r\n"
- "Host: www.example.org\r\n"
- "Connection: Upgrade\r\n"
- "Pragma: no-cache\r\n"
- "Cache-Control: no-cache\r\n"
- "Authorization: Digest username=\"FooBar\", realm=\"Oblivion\", "
- "nonce=\"nonce-value\", uri=\"/\", "
- "response=\"f72ff54ebde2f928860f806ec04acd1b\"\r\n"
- "Upgrade: websocket\r\n"
- "Origin: http://www.example.org\r\n"
- "Sec-WebSocket-Version: 13\r\n"
- "User-Agent: \r\n"
- "Accept-Encoding: gzip, deflate\r\n"
- "Accept-Language: en-us,fr\r\n"
- "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
- "Sec-WebSocket-Extensions: permessage-deflate; "
- "client_max_window_bits\r\n"
- "\r\n";
- // Confirm that the basic case works as expected.
- TEST_P(WebSocketMultiProtocolStreamCreateTest, SimpleSuccess) {
- base::HistogramTester histogram_tester;
- AddSSLData();
- EXPECT_FALSE(url_request_);
- CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {},
- {});
- EXPECT_FALSE(request_info_);
- EXPECT_FALSE(response_info_);
- EXPECT_TRUE(url_request_);
- WaitUntilConnectDone();
- EXPECT_FALSE(has_failed());
- EXPECT_TRUE(stream_);
- EXPECT_TRUE(request_info_);
- EXPECT_TRUE(response_info_);
- EXPECT_EQ(ERR_WS_UPGRADE,
- url_request_context_host_.network_delegate().last_error());
- auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
- "Net.WebSocket.HandshakeResult2");
- EXPECT_EQ(1, samples->TotalCount());
- if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
- EXPECT_EQ(1,
- samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::CONNECTED)));
- } else {
- DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
- EXPECT_EQ(
- 1,
- samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::HTTP2_CONNECTED)));
- }
- }
- TEST_P(WebSocketStreamCreateTest, HandshakeInfo) {
- static const char kResponse[] =
- "HTTP/1.1 101 Switching Protocols\r\n"
- "Upgrade: websocket\r\n"
- "Connection: Upgrade\r\n"
- "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
- "foo: bar, baz\r\n"
- "hoge: fuga\r\n"
- "hoge: piyo\r\n"
- "\r\n";
- CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
- {}, kResponse);
- EXPECT_FALSE(request_info_);
- EXPECT_FALSE(response_info_);
- WaitUntilConnectDone();
- EXPECT_TRUE(stream_);
- ASSERT_TRUE(request_info_);
- ASSERT_TRUE(response_info_);
- std::vector<HeaderKeyValuePair> request_headers =
- RequestHeadersToVector(request_info_->headers);
- // We examine the contents of request_info_ and response_info_
- // mainly only in this test case.
- EXPECT_EQ(GURL("ws://www.example.org/"), request_info_->url);
- EXPECT_EQ(GURL("ws://www.example.org/"), response_info_->url);
- EXPECT_EQ(101, response_info_->headers->response_code());
- EXPECT_EQ("Switching Protocols", response_info_->headers->GetStatusText());
- ASSERT_EQ(12u, request_headers.size());
- EXPECT_EQ(HeaderKeyValuePair("Host", "www.example.org"), request_headers[0]);
- EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), request_headers[1]);
- EXPECT_EQ(HeaderKeyValuePair("Pragma", "no-cache"), request_headers[2]);
- EXPECT_EQ(HeaderKeyValuePair("Cache-Control", "no-cache"),
- request_headers[3]);
- EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), request_headers[4]);
- EXPECT_EQ(HeaderKeyValuePair("Origin", "http://www.example.org"),
- request_headers[5]);
- EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Version", "13"),
- request_headers[6]);
- EXPECT_EQ(HeaderKeyValuePair("User-Agent", ""), request_headers[7]);
- EXPECT_EQ(HeaderKeyValuePair("Accept-Encoding", "gzip, deflate"),
- request_headers[8]);
- EXPECT_EQ(HeaderKeyValuePair("Accept-Language", "en-us,fr"),
- request_headers[9]);
- EXPECT_EQ("Sec-WebSocket-Key", request_headers[10].first);
- EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Extensions",
- "permessage-deflate; client_max_window_bits"),
- request_headers[11]);
- std::vector<HeaderKeyValuePair> response_headers =
- ResponseHeadersToVector(*response_info_->headers.get());
- ASSERT_EQ(6u, response_headers.size());
- // Sort the headers for ease of verification.
- std::sort(response_headers.begin(), response_headers.end());
- EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), response_headers[0]);
- EXPECT_EQ("Sec-WebSocket-Accept", response_headers[1].first);
- EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), response_headers[2]);
- EXPECT_EQ(HeaderKeyValuePair("foo", "bar, baz"), response_headers[3]);
- EXPECT_EQ(HeaderKeyValuePair("hoge", "fuga"), response_headers[4]);
- EXPECT_EQ(HeaderKeyValuePair("hoge", "piyo"), response_headers[5]);
- }
- // Confirms that request headers are overriden/added after handshake
- TEST_P(WebSocketStreamCreateTest, HandshakeOverrideHeaders) {
- WebSocketExtraHeaders additional_headers(
- {{"User-Agent", "OveRrIde"}, {"rAnDomHeader", "foobar"}});
- CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(),
- additional_headers, additional_headers, {});
- EXPECT_FALSE(request_info_);
- EXPECT_FALSE(response_info_);
- WaitUntilConnectDone();
- EXPECT_FALSE(has_failed());
- EXPECT_TRUE(stream_);
- EXPECT_TRUE(request_info_);
- EXPECT_TRUE(response_info_);
- std::vector<HeaderKeyValuePair> request_headers =
- RequestHeadersToVector(request_info_->headers);
- EXPECT_EQ(HeaderKeyValuePair("User-Agent", "OveRrIde"), request_headers[4]);
- EXPECT_EQ(HeaderKeyValuePair("rAnDomHeader", "foobar"), request_headers[5]);
- }
- // Confirm that the stream isn't established until the message loop runs.
- TEST_P(WebSocketStreamCreateTest, NeedsToRunLoop) {
- CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(), {}, {},
- {});
- EXPECT_FALSE(has_failed());
- EXPECT_FALSE(stream_);
- }
- // Check the path is used.
- TEST_P(WebSocketMultiProtocolStreamCreateTest, PathIsUsed) {
- AddSSLData();
- CreateAndConnectStandard("wss://www.example.org/testing_path",
- NoSubProtocols(), {}, {}, {});
- WaitUntilConnectDone();
- EXPECT_FALSE(has_failed());
- EXPECT_TRUE(stream_);
- }
- // Check that sub-protocols are sent and parsed.
- TEST_P(WebSocketMultiProtocolStreamCreateTest, SubProtocolIsUsed) {
- AddSSLData();
- std::vector<std::string> sub_protocols;
- sub_protocols.push_back("chatv11.chromium.org");
- sub_protocols.push_back("chatv20.chromium.org");
- CreateAndConnectStandard(
- "wss://www.example.org/testing_path", sub_protocols, {},
- {{"Sec-WebSocket-Protocol",
- "chatv11.chromium.org, chatv20.chromium.org"}},
- {{"Sec-WebSocket-Protocol", "chatv20.chromium.org"}});
- WaitUntilConnectDone();
- ASSERT_TRUE(stream_);
- EXPECT_FALSE(has_failed());
- EXPECT_EQ("chatv20.chromium.org", stream_->GetSubProtocol());
- }
- // Unsolicited sub-protocols are rejected.
- TEST_P(WebSocketMultiProtocolStreamCreateTest, UnsolicitedSubProtocol) {
- base::HistogramTester histogram_tester;
- AddSSLData();
- CreateAndConnectStandard(
- "wss://www.example.org/testing_path", NoSubProtocols(), {}, {},
- {{"Sec-WebSocket-Protocol", "chatv20.chromium.org"}});
- WaitUntilConnectDone();
- EXPECT_FALSE(stream_);
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error during WebSocket handshake: "
- "Response must not include 'Sec-WebSocket-Protocol' header "
- "if not present in request: chatv20.chromium.org",
- failure_message());
- EXPECT_EQ(ERR_INVALID_RESPONSE,
- url_request_context_host_.network_delegate().last_error());
- stream_request_.reset();
- auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
- "Net.WebSocket.HandshakeResult2");
- EXPECT_EQ(1, samples->TotalCount());
- if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
- EXPECT_EQ(
- 1,
- samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::FAILED_SUBPROTO)));
- } else {
- DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
- EXPECT_EQ(1, samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::
- HTTP2_FAILED_SUBPROTO)));
- }
- }
- // Missing sub-protocol response is rejected.
- TEST_P(WebSocketMultiProtocolStreamCreateTest, UnacceptedSubProtocol) {
- AddSSLData();
- std::vector<std::string> sub_protocols;
- sub_protocols.push_back("chat.example.com");
- CreateAndConnectStandard("wss://www.example.org/testing_path", sub_protocols,
- {}, {{"Sec-WebSocket-Protocol", "chat.example.com"}},
- {});
- WaitUntilConnectDone();
- EXPECT_FALSE(stream_);
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error during WebSocket handshake: "
- "Sent non-empty 'Sec-WebSocket-Protocol' header "
- "but no response was received",
- failure_message());
- }
- // Only one sub-protocol can be accepted.
- TEST_P(WebSocketMultiProtocolStreamCreateTest, MultipleSubProtocolsInResponse) {
- AddSSLData();
- std::vector<std::string> sub_protocols;
- sub_protocols.push_back("chatv11.chromium.org");
- sub_protocols.push_back("chatv20.chromium.org");
- CreateAndConnectStandard("wss://www.example.org/testing_path", sub_protocols,
- {},
- {{"Sec-WebSocket-Protocol",
- "chatv11.chromium.org, chatv20.chromium.org"}},
- {{"Sec-WebSocket-Protocol",
- "chatv11.chromium.org, chatv20.chromium.org"}});
- WaitUntilConnectDone();
- EXPECT_FALSE(stream_);
- EXPECT_TRUE(has_failed());
- EXPECT_EQ(
- "Error during WebSocket handshake: "
- "'Sec-WebSocket-Protocol' header must not appear "
- "more than once in a response",
- failure_message());
- }
- // Unmatched sub-protocol should be rejected.
- TEST_P(WebSocketMultiProtocolStreamCreateTest, UnmatchedSubProtocolInResponse) {
- AddSSLData();
- std::vector<std::string> sub_protocols;
- sub_protocols.push_back("chatv11.chromium.org");
- sub_protocols.push_back("chatv20.chromium.org");
- CreateAndConnectStandard(
- "wss://www.example.org/testing_path", sub_protocols, {},
- {{"Sec-WebSocket-Protocol",
- "chatv11.chromium.org, chatv20.chromium.org"}},
- {{"Sec-WebSocket-Protocol", "chatv21.chromium.org"}});
- WaitUntilConnectDone();
- EXPECT_FALSE(stream_);
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error during WebSocket handshake: "
- "'Sec-WebSocket-Protocol' header value 'chatv21.chromium.org' "
- "in response does not match any of sent values",
- failure_message());
- }
- // permessage-deflate extension basic success case.
- TEST_P(WebSocketStreamCreateExtensionTest, PerMessageDeflateSuccess) {
- CreateAndConnectWithExtensions("permessage-deflate");
- EXPECT_TRUE(stream_);
- EXPECT_FALSE(has_failed());
- }
- // permessage-deflate extensions success with all parameters.
- TEST_P(WebSocketStreamCreateExtensionTest, PerMessageDeflateParamsSuccess) {
- CreateAndConnectWithExtensions(
- "permessage-deflate; client_no_context_takeover; "
- "server_max_window_bits=11; client_max_window_bits=13; "
- "server_no_context_takeover");
- EXPECT_TRUE(stream_);
- EXPECT_FALSE(has_failed());
- }
- // Verify that incoming messages are actually decompressed with
- // permessage-deflate enabled.
- TEST_P(WebSocketStreamCreateExtensionTest, PerMessageDeflateInflates) {
- AddSSLData();
- SetAdditionalResponseData(std::string(
- "\xc1\x07" // WebSocket header (FIN + RSV1, Text payload 7 bytes)
- "\xf2\x48\xcd\xc9\xc9\x07\x00", // "Hello" DEFLATE compressed
- 9));
- CreateAndConnectStandard(
- "wss://www.example.org/testing_path", NoSubProtocols(), {}, {},
- {{"Sec-WebSocket-Extensions", "permessage-deflate"}});
- WaitUntilConnectDone();
- ASSERT_TRUE(stream_);
- std::vector<std::unique_ptr<WebSocketFrame>> frames;
- TestCompletionCallback callback;
- int rv = stream_->ReadFrames(&frames, callback.callback());
- rv = callback.GetResult(rv);
- ASSERT_THAT(rv, IsOk());
- ASSERT_EQ(1U, frames.size());
- ASSERT_EQ(5U, frames[0]->header.payload_length);
- EXPECT_EQ(std::string("Hello"),
- std::string(frames[0]->payload, frames[0]->header.payload_length));
- }
- // Unknown extension in the response is rejected
- TEST_P(WebSocketStreamCreateExtensionTest, UnknownExtension) {
- CreateAndConnectWithExtensions("x-unknown-extension");
- EXPECT_FALSE(stream_);
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error during WebSocket handshake: "
- "Found an unsupported extension 'x-unknown-extension' "
- "in 'Sec-WebSocket-Extensions' header",
- failure_message());
- }
- // Malformed extensions are rejected (this file does not cover all possible
- // parse failures, as the parser is covered thoroughly by its own unit tests).
- TEST_P(WebSocketStreamCreateExtensionTest, MalformedExtension) {
- CreateAndConnectWithExtensions(";");
- EXPECT_FALSE(stream_);
- EXPECT_TRUE(has_failed());
- EXPECT_EQ(
- "Error during WebSocket handshake: 'Sec-WebSocket-Extensions' header "
- "value is rejected by the parser: ;",
- failure_message());
- }
- // The permessage-deflate extension may only be specified once.
- TEST_P(WebSocketStreamCreateExtensionTest, OnlyOnePerMessageDeflateAllowed) {
- base::HistogramTester histogram_tester;
- CreateAndConnectWithExtensions(
- "permessage-deflate, permessage-deflate; client_max_window_bits=10");
- EXPECT_FALSE(stream_);
- EXPECT_TRUE(has_failed());
- EXPECT_EQ(
- "Error during WebSocket handshake: "
- "Received duplicate permessage-deflate response",
- failure_message());
- stream_request_.reset();
- auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
- "Net.WebSocket.HandshakeResult2");
- EXPECT_EQ(1, samples->TotalCount());
- if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
- EXPECT_EQ(
- 1,
- samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::FAILED_EXTENSIONS)));
- } else {
- DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
- EXPECT_EQ(1, samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::
- HTTP2_FAILED_EXTENSIONS)));
- }
- }
- // client_max_window_bits must have an argument
- TEST_P(WebSocketStreamCreateExtensionTest, NoMaxWindowBitsArgument) {
- CreateAndConnectWithExtensions("permessage-deflate; client_max_window_bits");
- EXPECT_FALSE(stream_);
- EXPECT_TRUE(has_failed());
- EXPECT_EQ(
- "Error during WebSocket handshake: Error in permessage-deflate: "
- "client_max_window_bits must have value",
- failure_message());
- }
- // Other cases for permessage-deflate parameters are tested in
- // websocket_deflate_parameters_test.cc.
- // TODO(ricea): Check that WebSocketDeflateStream is initialised with the
- // arguments from the server. This is difficult because the data written to the
- // socket is randomly masked.
- // Additional Sec-WebSocket-Accept headers should be rejected.
- TEST_P(WebSocketStreamCreateTest, DoubleAccept) {
- CreateAndConnectStandard(
- "ws://www.example.org/", NoSubProtocols(), {}, {},
- {{"Sec-WebSocket-Accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}});
- WaitUntilConnectDone();
- EXPECT_FALSE(stream_);
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error during WebSocket handshake: "
- "'Sec-WebSocket-Accept' header must not appear "
- "more than once in a response",
- failure_message());
- }
- // When upgrading an HTTP/1 connection, response code 200 is invalid and must be
- // rejected. Response code 101 means success. On the other hand, when
- // requesting a WebSocket stream over HTTP/2, response code 101 is invalid and
- // must be rejected. Response code 200 means success.
- TEST_P(WebSocketMultiProtocolStreamCreateTest, InvalidStatusCode) {
- base::HistogramTester histogram_tester;
- AddSSLData();
- if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
- static const char kInvalidStatusCodeResponse[] =
- "HTTP/1.1 200 OK\r\n"
- "Upgrade: websocket\r\n"
- "Connection: Upgrade\r\n"
- "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
- "\r\n";
- CreateAndConnectCustomResponse("wss://www.example.org/", NoSubProtocols(),
- {}, {}, kInvalidStatusCodeResponse);
- } else {
- DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
- SetHttp2ResponseStatus("101");
- CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {},
- {});
- }
- WaitUntilConnectDone();
- stream_request_.reset();
- EXPECT_TRUE(has_failed());
- auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
- "Net.WebSocket.HandshakeResult2");
- EXPECT_EQ(1, samples->TotalCount());
- if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
- EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 200",
- failure_message());
- EXPECT_EQ(failure_response_code(), 200);
- EXPECT_EQ(
- 1, samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::INVALID_STATUS)));
- } else {
- DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
- EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 101",
- failure_message());
- EXPECT_EQ(failure_response_code(), 101);
- EXPECT_EQ(1, samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::
- HTTP2_INVALID_STATUS)));
- }
- }
- // Redirects are not followed (according to the WHATWG WebSocket API, which
- // overrides RFC6455 for browser applications).
- TEST_P(WebSocketMultiProtocolStreamCreateTest, RedirectsRejected) {
- AddSSLData();
- if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
- static const char kRedirectResponse[] =
- "HTTP/1.1 302 Moved Temporarily\r\n"
- "Content-Type: text/html\r\n"
- "Content-Length: 34\r\n"
- "Connection: keep-alive\r\n"
- "Location: wss://www.example.org/other\r\n"
- "\r\n"
- "<title>Moved</title><h1>Moved</h1>";
- CreateAndConnectCustomResponse("wss://www.example.org/", NoSubProtocols(),
- {}, {}, kRedirectResponse);
- } else {
- DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
- SetHttp2ResponseStatus("302");
- CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {},
- {});
- }
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 302",
- failure_message());
- }
- // Malformed responses should be rejected. HttpStreamParser will accept just
- // about any garbage in the middle of the headers. To make it give up, the junk
- // has to be at the start of the response. Even then, it just gets treated as an
- // HTTP/0.9 response.
- TEST_P(WebSocketStreamCreateTest, MalformedResponse) {
- static const char kMalformedResponse[] =
- "220 mx.google.com ESMTP\r\n"
- "HTTP/1.1 101 OK\r\n"
- "Upgrade: websocket\r\n"
- "Connection: Upgrade\r\n"
- "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
- "\r\n";
- CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
- {}, kMalformedResponse);
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error during WebSocket handshake: Invalid status line",
- failure_message());
- }
- // Upgrade header must be present.
- TEST_P(WebSocketStreamCreateTest, MissingUpgradeHeader) {
- base::HistogramTester histogram_tester;
- static const char kMissingUpgradeResponse[] =
- "HTTP/1.1 101 Switching Protocols\r\n"
- "Connection: Upgrade\r\n"
- "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
- "\r\n";
- CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
- {}, kMissingUpgradeResponse);
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error during WebSocket handshake: 'Upgrade' header is missing",
- failure_message());
- stream_request_.reset();
- auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
- "Net.WebSocket.HandshakeResult2");
- EXPECT_EQ(1, samples->TotalCount());
- EXPECT_EQ(
- 1, samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::FAILED_UPGRADE)));
- }
- // There must only be one upgrade header.
- TEST_P(WebSocketStreamCreateTest, DoubleUpgradeHeader) {
- CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(), {}, {},
- {{"Upgrade", "HTTP/2.0"}});
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error during WebSocket handshake: "
- "'Upgrade' header must not appear more than once in a response",
- failure_message());
- }
- // There must only be one correct upgrade header.
- TEST_P(WebSocketStreamCreateTest, IncorrectUpgradeHeader) {
- static const char kMissingUpgradeResponse[] =
- "HTTP/1.1 101 Switching Protocols\r\n"
- "Connection: Upgrade\r\n"
- "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
- "Upgrade: hogefuga\r\n"
- "\r\n";
- CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
- {}, kMissingUpgradeResponse);
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error during WebSocket handshake: "
- "'Upgrade' header value is not 'WebSocket': hogefuga",
- failure_message());
- }
- // Connection header must be present.
- TEST_P(WebSocketStreamCreateTest, MissingConnectionHeader) {
- base::HistogramTester histogram_tester;
- static const char kMissingConnectionResponse[] =
- "HTTP/1.1 101 Switching Protocols\r\n"
- "Upgrade: websocket\r\n"
- "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
- "\r\n";
- CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
- {}, kMissingConnectionResponse);
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error during WebSocket handshake: "
- "'Connection' header is missing",
- failure_message());
- stream_request_.reset();
- auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
- "Net.WebSocket.HandshakeResult2");
- EXPECT_EQ(1, samples->TotalCount());
- EXPECT_EQ(
- 1,
- samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::FAILED_CONNECTION)));
- }
- // Connection header must contain "Upgrade".
- TEST_P(WebSocketStreamCreateTest, IncorrectConnectionHeader) {
- static const char kMissingConnectionResponse[] =
- "HTTP/1.1 101 Switching Protocols\r\n"
- "Upgrade: websocket\r\n"
- "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
- "Connection: hogefuga\r\n"
- "\r\n";
- CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
- {}, kMissingConnectionResponse);
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error during WebSocket handshake: "
- "'Connection' header value must contain 'Upgrade'",
- failure_message());
- }
- // Connection header is permitted to contain other tokens.
- TEST_P(WebSocketStreamCreateTest, AdditionalTokenInConnectionHeader) {
- static const char kAdditionalConnectionTokenResponse[] =
- "HTTP/1.1 101 Switching Protocols\r\n"
- "Upgrade: websocket\r\n"
- "Connection: Upgrade, Keep-Alive\r\n"
- "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
- "\r\n";
- CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
- {}, kAdditionalConnectionTokenResponse);
- WaitUntilConnectDone();
- EXPECT_FALSE(has_failed());
- EXPECT_TRUE(stream_);
- }
- // Sec-WebSocket-Accept header must be present.
- TEST_P(WebSocketStreamCreateTest, MissingSecWebSocketAccept) {
- base::HistogramTester histogram_tester;
- static const char kMissingAcceptResponse[] =
- "HTTP/1.1 101 Switching Protocols\r\n"
- "Upgrade: websocket\r\n"
- "Connection: Upgrade\r\n"
- "\r\n";
- CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
- {}, kMissingAcceptResponse);
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error during WebSocket handshake: "
- "'Sec-WebSocket-Accept' header is missing",
- failure_message());
- stream_request_.reset();
- auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
- "Net.WebSocket.HandshakeResult2");
- EXPECT_EQ(1, samples->TotalCount());
- EXPECT_EQ(1,
- samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::FAILED_ACCEPT)));
- }
- // Sec-WebSocket-Accept header must match the key that was sent.
- TEST_P(WebSocketStreamCreateTest, WrongSecWebSocketAccept) {
- static const char kIncorrectAcceptResponse[] =
- "HTTP/1.1 101 Switching Protocols\r\n"
- "Upgrade: websocket\r\n"
- "Connection: Upgrade\r\n"
- "Sec-WebSocket-Accept: x/byyPZ2tOFvJCGkkugcKvqhhPk=\r\n"
- "\r\n";
- CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
- {}, kIncorrectAcceptResponse);
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error during WebSocket handshake: "
- "Incorrect 'Sec-WebSocket-Accept' header value",
- failure_message());
- }
- // Cancellation works.
- TEST_P(WebSocketStreamCreateTest, Cancellation) {
- CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(), {}, {},
- {});
- stream_request_.reset();
- // WaitUntilConnectDone doesn't work in this case.
- base::RunLoop().RunUntilIdle();
- EXPECT_FALSE(has_failed());
- EXPECT_FALSE(stream_);
- EXPECT_FALSE(request_info_);
- EXPECT_FALSE(response_info_);
- }
- // Connect failure must look just like negotiation failure.
- TEST_P(WebSocketStreamCreateTest, ConnectionFailure) {
- std::unique_ptr<SequencedSocketData> socket_data(BuildNullSocketData());
- socket_data->set_connect_data(
- MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED));
- CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(), std::move(socket_data));
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED",
- failure_message());
- EXPECT_FALSE(request_info_);
- EXPECT_FALSE(response_info_);
- }
- // Connect timeout must look just like any other failure.
- TEST_P(WebSocketStreamCreateTest, ConnectionTimeout) {
- std::unique_ptr<SequencedSocketData> socket_data(BuildNullSocketData());
- socket_data->set_connect_data(
- MockConnect(ASYNC, ERR_CONNECTION_TIMED_OUT));
- CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(), std::move(socket_data));
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_TIMED_OUT",
- failure_message());
- }
- // The server doesn't respond to the opening handshake.
- TEST_P(WebSocketStreamCreateTest, HandshakeTimeout) {
- std::unique_ptr<SequencedSocketData> socket_data(BuildNullSocketData());
- socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING));
- auto timer = std::make_unique<MockWeakTimer>();
- base::WeakPtr<MockWeakTimer> weak_timer = timer->AsWeakPtr();
- SetTimer(std::move(timer));
- CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(), std::move(socket_data));
- EXPECT_FALSE(has_failed());
- ASSERT_TRUE(weak_timer.get());
- EXPECT_TRUE(weak_timer->IsRunning());
- weak_timer->Fire();
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("WebSocket opening handshake timed out", failure_message());
- ASSERT_TRUE(weak_timer.get());
- EXPECT_FALSE(weak_timer->IsRunning());
- }
- // When the connection establishes the timer should be stopped.
- TEST_P(WebSocketStreamCreateTest, HandshakeTimerOnSuccess) {
- auto timer = std::make_unique<MockWeakTimer>();
- base::WeakPtr<MockWeakTimer> weak_timer = timer->AsWeakPtr();
- SetTimer(std::move(timer));
- CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(), {}, {},
- {});
- ASSERT_TRUE(weak_timer);
- EXPECT_TRUE(weak_timer->IsRunning());
- WaitUntilConnectDone();
- EXPECT_FALSE(has_failed());
- EXPECT_TRUE(stream_);
- ASSERT_TRUE(weak_timer);
- EXPECT_FALSE(weak_timer->IsRunning());
- }
- // When the connection fails the timer should be stopped.
- TEST_P(WebSocketStreamCreateTest, HandshakeTimerOnFailure) {
- std::unique_ptr<SequencedSocketData> socket_data(BuildNullSocketData());
- socket_data->set_connect_data(
- MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED));
- auto timer = std::make_unique<MockWeakTimer>();
- base::WeakPtr<MockWeakTimer> weak_timer = timer->AsWeakPtr();
- SetTimer(std::move(timer));
- CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(), std::move(socket_data));
- ASSERT_TRUE(weak_timer.get());
- EXPECT_TRUE(weak_timer->IsRunning());
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED",
- failure_message());
- ASSERT_TRUE(weak_timer.get());
- EXPECT_FALSE(weak_timer->IsRunning());
- }
- // Cancellation during connect works.
- TEST_P(WebSocketStreamCreateTest, CancellationDuringConnect) {
- std::unique_ptr<SequencedSocketData> socket_data(BuildNullSocketData());
- socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING));
- CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(), std::move(socket_data));
- stream_request_.reset();
- // WaitUntilConnectDone doesn't work in this case.
- base::RunLoop().RunUntilIdle();
- EXPECT_FALSE(has_failed());
- EXPECT_FALSE(stream_);
- }
- // Cancellation during write of the request headers works.
- TEST_P(WebSocketStreamCreateTest, CancellationDuringWrite) {
- // First write never completes.
- MockWrite writes[] = {MockWrite(SYNCHRONOUS, ERR_IO_PENDING, 0)};
- auto socket_data =
- std::make_unique<SequencedSocketData>(base::span<MockRead>(), writes);
- auto* socket_data_ptr = socket_data.get();
- socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
- CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(), std::move(socket_data));
- base::RunLoop().RunUntilIdle();
- EXPECT_TRUE(socket_data_ptr->AllWriteDataConsumed());
- stream_request_.reset();
- // WaitUntilConnectDone doesn't work in this case.
- base::RunLoop().RunUntilIdle();
- EXPECT_FALSE(has_failed());
- EXPECT_FALSE(stream_);
- EXPECT_TRUE(request_info_);
- EXPECT_FALSE(response_info_);
- }
- // Cancellation during read of the response headers works.
- TEST_P(WebSocketStreamCreateTest, CancellationDuringRead) {
- std::string request = WebSocketStandardRequest(
- "/", "www.example.org", Origin(), /*send_additional_request_headers=*/{},
- /*extra_headers=*/{});
- MockWrite writes[] = {MockWrite(ASYNC, 0, request.c_str())};
- MockRead reads[] = {
- MockRead(SYNCHRONOUS, ERR_IO_PENDING, 1),
- };
- std::unique_ptr<SequencedSocketData> socket_data(
- BuildSocketData(reads, writes));
- SequencedSocketData* socket_data_raw_ptr = socket_data.get();
- CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(), std::move(socket_data));
- base::RunLoop().RunUntilIdle();
- EXPECT_TRUE(socket_data_raw_ptr->AllReadDataConsumed());
- stream_request_.reset();
- // WaitUntilConnectDone doesn't work in this case.
- base::RunLoop().RunUntilIdle();
- EXPECT_FALSE(has_failed());
- EXPECT_FALSE(stream_);
- EXPECT_TRUE(request_info_);
- EXPECT_FALSE(response_info_);
- }
- // Over-size response headers (> 256KB) should not cause a crash. This is a
- // regression test for crbug.com/339456. It is based on the layout test
- // "cookie-flood.html".
- TEST_P(WebSocketStreamCreateTest, VeryLargeResponseHeaders) {
- base::HistogramTester histogram_tester;
- std::string set_cookie_headers;
- set_cookie_headers.reserve(24 * 20000);
- for (int i = 0; i < 20000; ++i) {
- set_cookie_headers += base::StringPrintf("Set-Cookie: ws-%d=1\r\n", i);
- }
- ASSERT_GT(set_cookie_headers.size(), 256U * 1024U);
- CreateAndConnectStringResponse("ws://www.example.org/", NoSubProtocols(),
- set_cookie_headers);
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_FALSE(response_info_);
- stream_request_.reset();
- auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
- "Net.WebSocket.HandshakeResult2");
- EXPECT_EQ(1, samples->TotalCount());
- EXPECT_EQ(1, samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::FAILED)));
- }
- // If the remote host closes the connection without sending headers, we should
- // log the console message "Connection closed before receiving a handshake
- // response".
- TEST_P(WebSocketStreamCreateTest, NoResponse) {
- base::HistogramTester histogram_tester;
- std::string request = WebSocketStandardRequest(
- "/", "www.example.org", Origin(), /*send_additional_request_headers=*/{},
- /*extra_headers=*/{});
- MockWrite writes[] = {MockWrite(ASYNC, request.data(), request.size(), 0)};
- MockRead reads[] = {MockRead(ASYNC, 0, 1)};
- std::unique_ptr<SequencedSocketData> socket_data(
- BuildSocketData(reads, writes));
- SequencedSocketData* socket_data_raw_ptr = socket_data.get();
- CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(), std::move(socket_data));
- base::RunLoop().RunUntilIdle();
- EXPECT_TRUE(socket_data_raw_ptr->AllReadDataConsumed());
- EXPECT_TRUE(has_failed());
- EXPECT_FALSE(stream_);
- EXPECT_FALSE(response_info_);
- EXPECT_EQ("Connection closed before receiving a handshake response",
- failure_message());
- stream_request_.reset();
- auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
- "Net.WebSocket.HandshakeResult2");
- EXPECT_EQ(1, samples->TotalCount());
- EXPECT_EQ(
- 1, samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::EMPTY_RESPONSE)));
- }
- TEST_P(WebSocketStreamCreateTest, SelfSignedCertificateFailure) {
- auto ssl_socket_data = std::make_unique<SSLSocketDataProvider>(
- ASYNC, ERR_CERT_AUTHORITY_INVALID);
- ssl_socket_data->ssl_info.cert =
- ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der");
- ASSERT_TRUE(ssl_socket_data->ssl_info.cert.get());
- url_request_context_host_.AddSSLSocketDataProvider(
- std::move(ssl_socket_data));
- std::unique_ptr<SequencedSocketData> raw_socket_data(BuildNullSocketData());
- CreateAndConnectRawExpectations("wss://www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(),
- std::move(raw_socket_data));
- // WaitUntilConnectDone doesn't work in this case.
- base::RunLoop().RunUntilIdle();
- EXPECT_FALSE(has_failed());
- ASSERT_TRUE(ssl_error_callbacks_);
- ssl_error_callbacks_->CancelSSLRequest(ERR_CERT_AUTHORITY_INVALID,
- &ssl_info_);
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- }
- TEST_P(WebSocketStreamCreateTest, SelfSignedCertificateSuccess) {
- auto ssl_socket_data = std::make_unique<SSLSocketDataProvider>(
- ASYNC, ERR_CERT_AUTHORITY_INVALID);
- ssl_socket_data->ssl_info.cert =
- ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der");
- ASSERT_TRUE(ssl_socket_data->ssl_info.cert.get());
- url_request_context_host_.AddSSLSocketDataProvider(
- std::move(ssl_socket_data));
- url_request_context_host_.AddSSLSocketDataProvider(
- std::make_unique<SSLSocketDataProvider>(ASYNC, OK));
- AddRawExpectations(BuildNullSocketData());
- CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {},
- {});
- // WaitUntilConnectDone doesn't work in this case.
- base::RunLoop().RunUntilIdle();
- ASSERT_TRUE(ssl_error_callbacks_);
- ssl_error_callbacks_->ContinueSSLRequest();
- WaitUntilConnectDone();
- EXPECT_FALSE(has_failed());
- EXPECT_TRUE(stream_);
- }
- // If the server requests authorisation, but we have no credentials, the
- // connection should fail cleanly.
- TEST_P(WebSocketStreamCreateBasicAuthTest, FailureNoCredentials) {
- CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
- {}, kUnauthorizedResponse);
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("HTTP Authentication failed; no valid credentials available",
- failure_message());
- EXPECT_FALSE(response_info_);
- }
- TEST_P(WebSocketStreamCreateBasicAuthTest, SuccessPasswordInUrl) {
- CreateAndConnectAuthHandshake("ws://foo:bar@www.example.org/", "Zm9vOmJhcg==",
- WebSocketStandardResponse(std::string()));
- WaitUntilConnectDone();
- EXPECT_FALSE(has_failed());
- EXPECT_TRUE(stream_);
- ASSERT_TRUE(response_info_);
- EXPECT_EQ(101, response_info_->headers->response_code());
- }
- TEST_P(WebSocketStreamCreateBasicAuthTest, FailureIncorrectPasswordInUrl) {
- CreateAndConnectAuthHandshake("ws://foo:baz@www.example.org/",
- "Zm9vOmJheg==", kUnauthorizedResponse);
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_FALSE(response_info_);
- }
- TEST_P(WebSocketStreamCreateBasicAuthTest, SuccessfulConnectionReuse) {
- std::string request1 = WebSocketStandardRequest(
- "/", "www.example.org", Origin(), /*send_additional_request_headers=*/{},
- /*extra_headers=*/{});
- std::string response1 = kUnauthorizedResponse;
- std::string request2 = WebSocketStandardRequest(
- "/", "www.example.org", Origin(),
- {{"Authorization", "Basic Zm9vOmJhcg=="}}, /*extra_headers=*/{});
- std::string response2 = WebSocketStandardResponse(std::string());
- MockWrite writes[] = {
- MockWrite(SYNCHRONOUS, 0, request1.c_str()),
- MockWrite(SYNCHRONOUS, 2, request2.c_str()),
- };
- MockRead reads[3] = {
- MockRead(SYNCHRONOUS, 1, response1.c_str()),
- MockRead(SYNCHRONOUS, 3, response2.c_str()),
- MockRead(SYNCHRONOUS, ERR_IO_PENDING, 4),
- };
- CreateAndConnectRawExpectations("ws://foo:bar@www.example.org/",
- NoSubProtocols(), HttpRequestHeaders(),
- BuildSocketData(reads, writes));
- WaitUntilConnectDone();
- EXPECT_FALSE(has_failed());
- EXPECT_TRUE(stream_);
- ASSERT_TRUE(response_info_);
- EXPECT_EQ(101, response_info_->headers->response_code());
- }
- TEST_P(WebSocketStreamCreateBasicAuthTest, OnAuthRequiredCancelAuth) {
- CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {},
- {}, kUnauthorizedResponse);
- EXPECT_FALSE(request_info_);
- EXPECT_FALSE(response_info_);
- on_auth_required_rv_ = ERR_IO_PENDING;
- WaitUntilOnAuthRequired();
- EXPECT_FALSE(stream_);
- EXPECT_FALSE(has_failed());
- std::move(on_auth_required_callback_).Run(nullptr);
- WaitUntilConnectDone();
- EXPECT_FALSE(stream_);
- EXPECT_TRUE(has_failed());
- }
- TEST_P(WebSocketStreamCreateBasicAuthTest, OnAuthRequiredSetAuth) {
- CreateAndConnectRawExpectations(
- "ws://www.example.org/", NoSubProtocols(), HttpRequestHeaders(),
- helper_.BuildAuthSocketData(kUnauthorizedResponse,
- RequestExpectation("Zm9vOmJheg=="),
- WebSocketStandardResponse(std::string())));
- EXPECT_FALSE(request_info_);
- EXPECT_FALSE(response_info_);
- on_auth_required_rv_ = ERR_IO_PENDING;
- WaitUntilOnAuthRequired();
- EXPECT_FALSE(stream_);
- EXPECT_FALSE(has_failed());
- AuthCredentials credentials(u"foo", u"baz");
- std::move(on_auth_required_callback_).Run(&credentials);
- WaitUntilConnectDone();
- EXPECT_TRUE(stream_);
- EXPECT_FALSE(has_failed());
- }
- // Digest auth has the same connection semantics as Basic auth, so we can
- // generally assume that whatever works for Basic auth will also work for
- // Digest. There's just one test here, to confirm that it works at all.
- TEST_P(WebSocketStreamCreateDigestAuthTest, DigestPasswordInUrl) {
- CreateAndConnectRawExpectations(
- "ws://FooBar:pass@www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(),
- helper_.BuildAuthSocketData(kUnauthorizedResponse, kAuthorizedRequest,
- WebSocketStandardResponse(std::string())));
- WaitUntilConnectDone();
- EXPECT_FALSE(has_failed());
- EXPECT_TRUE(stream_);
- ASSERT_TRUE(response_info_);
- EXPECT_EQ(101, response_info_->headers->response_code());
- }
- TEST_P(WebSocketMultiProtocolStreamCreateTest, Incomplete) {
- base::HistogramTester histogram_tester;
- AddSSLData();
- if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
- std::string request = WebSocketStandardRequest(
- "/", "www.example.org", Origin(),
- /*send_additional_request_headers=*/{}, /*extra_headers=*/{});
- MockRead reads[] = {MockRead(ASYNC, ERR_IO_PENDING, 0)};
- MockWrite writes[] = {MockWrite(ASYNC, 1, request.c_str())};
- CreateAndConnectRawExpectations("wss://www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(),
- BuildSocketData(reads, writes));
- base::RunLoop().RunUntilIdle();
- stream_request_.reset();
- auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
- "Net.WebSocket.HandshakeResult2");
- EXPECT_EQ(1, samples->TotalCount());
- EXPECT_EQ(1,
- samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::INCOMPLETE)));
- } else {
- DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
- CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {},
- {});
- stream_request_.reset();
- auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
- "Net.WebSocket.HandshakeResult2");
- EXPECT_EQ(1, samples->TotalCount());
- EXPECT_EQ(
- 1,
- samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::HTTP2_INCOMPLETE)));
- }
- }
- TEST_P(WebSocketMultiProtocolStreamCreateTest, Http2StreamReset) {
- AddSSLData();
- if (stream_type_ == BASIC_HANDSHAKE_STREAM) {
- // This is a dummy transaction to avoid crash in ~URLRequestContext().
- CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {},
- {});
- } else {
- DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM);
- base::HistogramTester histogram_tester;
- SetResetWebSocketHttp2Stream(true);
- CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {},
- {});
- base::RunLoop().RunUntilIdle();
- stream_request_.reset();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Stream closed with error: net::ERR_HTTP2_PROTOCOL_ERROR",
- failure_message());
- auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
- "Net.WebSocket.HandshakeResult2");
- EXPECT_EQ(1, samples->TotalCount());
- EXPECT_EQ(
- 1, samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::HTTP2_FAILED)));
- }
- }
- TEST_P(WebSocketStreamCreateTest, HandleErrConnectionClosed) {
- base::HistogramTester histogram_tester;
- static const char kTruncatedResponse[] =
- "HTTP/1.1 101 Switching Protocols\r\n"
- "Upgrade: websocket\r\n"
- "Connection: Upgrade\r\n"
- "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
- "Cache-Control: no-sto";
- std::string request = WebSocketStandardRequest(
- "/", "www.example.org", Origin(), /*send_additional_request_headers=*/{},
- /*extra_headers=*/{});
- MockRead reads[] = {
- MockRead(SYNCHRONOUS, 1, kTruncatedResponse),
- MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED, 2),
- };
- MockWrite writes[] = {MockWrite(SYNCHRONOUS, 0, request.c_str())};
- std::unique_ptr<SequencedSocketData> socket_data(
- BuildSocketData(reads, writes));
- socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
- CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(), std::move(socket_data));
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- stream_request_.reset();
- auto samples = histogram_tester.GetHistogramSamplesSinceCreation(
- "Net.WebSocket.HandshakeResult2");
- EXPECT_EQ(1, samples->TotalCount());
- EXPECT_EQ(1, samples->GetCount(static_cast<int>(
- WebSocketHandshakeStreamBase::HandshakeResult::
- FAILED_SWITCHING_PROTOCOLS)));
- }
- TEST_P(WebSocketStreamCreateTest, HandleErrTunnelConnectionFailed) {
- static const char kConnectRequest[] =
- "CONNECT www.example.org:80 HTTP/1.1\r\n"
- "Host: www.example.org:80\r\n"
- "Proxy-Connection: keep-alive\r\n"
- "\r\n";
- static const char kProxyResponse[] =
- "HTTP/1.1 403 Forbidden\r\n"
- "Content-Type: text/html\r\n"
- "Content-Length: 9\r\n"
- "Connection: keep-alive\r\n"
- "\r\n"
- "Forbidden";
- MockRead reads[] = {MockRead(SYNCHRONOUS, 1, kProxyResponse)};
- MockWrite writes[] = {MockWrite(SYNCHRONOUS, 0, kConnectRequest)};
- std::unique_ptr<SequencedSocketData> socket_data(
- BuildSocketData(reads, writes));
- url_request_context_host_.SetProxyConfig("https=proxy:8000");
- CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(), std::move(socket_data));
- WaitUntilConnectDone();
- EXPECT_TRUE(has_failed());
- EXPECT_EQ("Establishing a tunnel via proxy server failed.",
- failure_message());
- }
- TEST_P(WebSocketStreamCreateTest, CancelSSLRequestAfterDelete) {
- auto ssl_socket_data = std::make_unique<SSLSocketDataProvider>(
- ASYNC, ERR_CERT_AUTHORITY_INVALID);
- ssl_socket_data->ssl_info.cert =
- ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der");
- ASSERT_TRUE(ssl_socket_data->ssl_info.cert.get());
- url_request_context_host_.AddSSLSocketDataProvider(
- std::move(ssl_socket_data));
- MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_CONNECTION_RESET, 0)};
- MockWrite writes[] = {MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET, 1)};
- std::unique_ptr<SequencedSocketData> raw_socket_data(
- BuildSocketData(reads, writes));
- CreateAndConnectRawExpectations("wss://www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(),
- std::move(raw_socket_data));
- base::RunLoop().RunUntilIdle();
- EXPECT_FALSE(has_failed());
- ASSERT_TRUE(ssl_error_callbacks_);
- stream_request_.reset();
- ssl_error_callbacks_->CancelSSLRequest(ERR_CERT_AUTHORITY_INVALID,
- &ssl_info_);
- }
- TEST_P(WebSocketStreamCreateTest, ContinueSSLRequestAfterDelete) {
- auto ssl_socket_data = std::make_unique<SSLSocketDataProvider>(
- ASYNC, ERR_CERT_AUTHORITY_INVALID);
- ssl_socket_data->ssl_info.cert =
- ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der");
- ASSERT_TRUE(ssl_socket_data->ssl_info.cert.get());
- url_request_context_host_.AddSSLSocketDataProvider(
- std::move(ssl_socket_data));
- MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_CONNECTION_RESET, 0)};
- MockWrite writes[] = {MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET, 1)};
- std::unique_ptr<SequencedSocketData> raw_socket_data(
- BuildSocketData(reads, writes));
- CreateAndConnectRawExpectations("wss://www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(),
- std::move(raw_socket_data));
- base::RunLoop().RunUntilIdle();
- EXPECT_FALSE(has_failed());
- ASSERT_TRUE(ssl_error_callbacks_);
- stream_request_.reset();
- ssl_error_callbacks_->ContinueSSLRequest();
- }
- TEST_P(WebSocketStreamCreateTest, HandleConnectionCloseInFirstSegment) {
- std::string request = WebSocketStandardRequest(
- "/", "www.example.org", Origin(), /*send_additional_request_headers=*/{},
- /*extra_headers=*/{});
- // The response headers are immediately followed by a close frame, length 11,
- // code 1013, reason "Try Again".
- std::string close_body = "\x03\xf5Try Again";
- std::string response = WebSocketStandardResponse(std::string()) + "\x88" +
- static_cast<char>(close_body.size()) + close_body;
- MockRead reads[] = {
- MockRead(SYNCHRONOUS, response.data(), response.size(), 1),
- MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED, 2),
- };
- MockWrite writes[] = {MockWrite(SYNCHRONOUS, 0, request.c_str())};
- std::unique_ptr<SequencedSocketData> socket_data(
- BuildSocketData(reads, writes));
- socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
- CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(),
- HttpRequestHeaders(), std::move(socket_data));
- WaitUntilConnectDone();
- ASSERT_TRUE(stream_);
- std::vector<std::unique_ptr<WebSocketFrame>> frames;
- TestCompletionCallback callback1;
- int rv1 = stream_->ReadFrames(&frames, callback1.callback());
- rv1 = callback1.GetResult(rv1);
- ASSERT_THAT(rv1, IsOk());
- ASSERT_EQ(1U, frames.size());
- EXPECT_EQ(frames[0]->header.opcode, WebSocketFrameHeader::kOpCodeClose);
- EXPECT_TRUE(frames[0]->header.final);
- EXPECT_EQ(close_body,
- std::string(frames[0]->payload, frames[0]->header.payload_length));
- std::vector<std::unique_ptr<WebSocketFrame>> empty_frames;
- TestCompletionCallback callback2;
- int rv2 = stream_->ReadFrames(&empty_frames, callback2.callback());
- rv2 = callback2.GetResult(rv2);
- ASSERT_THAT(rv2, IsError(ERR_CONNECTION_CLOSED));
- }
- } // namespace
- } // namespace net
|