1 // Copyright 2014 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 // End-to-end tests for WebSocket.
6 //
7 // A python server is (re)started for each test, which is moderately
8 // inefficient. However, it makes these tests a good fit for scenarios which
9 // require special server configurations.
10
11 #include <stdint.h>
12
13 #include <memory>
14 #include <optional>
15 #include <string>
16 #include <string_view>
17 #include <utility>
18 #include <vector>
19
20 #include "base/check.h"
21 #include "base/containers/span.h"
22 #include "base/files/file_path.h"
23 #include "base/functional/bind.h"
24 #include "base/functional/callback.h"
25 #include "base/location.h"
26 #include "base/logging.h"
27 #include "base/memory/raw_ptr.h"
28 #include "base/memory/scoped_refptr.h"
29 #include "base/run_loop.h"
30 #include "base/strings/strcat.h"
31 #include "base/strings/string_number_conversions.h"
32 #include "base/strings/stringprintf.h"
33 #include "base/task/single_thread_task_runner.h"
34 #include "base/test/scoped_feature_list.h"
35 #include "base/test/test_future.h"
36 #include "build/build_config.h"
37 #include "net/base/auth.h"
38 #include "net/base/connection_endpoint_metadata.h"
39 #include "net/base/features.h"
40 #include "net/base/host_port_pair.h"
41 #include "net/base/ip_address.h"
42 #include "net/base/ip_endpoint.h"
43 #include "net/base/isolation_info.h"
44 #include "net/base/net_errors.h"
45 #include "net/base/proxy_chain.h"
46 #include "net/base/proxy_delegate.h"
47 #include "net/base/request_priority.h"
48 #include "net/base/url_util.h"
49 #include "net/cookies/site_for_cookies.h"
50 #include "net/dns/host_resolver.h"
51 #include "net/dns/mock_host_resolver.h"
52 #include "net/dns/public/host_resolver_results.h"
53 #include "net/http/http_request_headers.h"
54 #include "net/log/net_log.h"
55 #include "net/proxy_resolution/configured_proxy_resolution_service.h"
56 #include "net/proxy_resolution/proxy_bypass_rules.h"
57 #include "net/proxy_resolution/proxy_config.h"
58 #include "net/proxy_resolution/proxy_config_service.h"
59 #include "net/proxy_resolution/proxy_config_service_fixed.h"
60 #include "net/proxy_resolution/proxy_config_with_annotation.h"
61 #include "net/proxy_resolution/proxy_info.h"
62 #include "net/proxy_resolution/proxy_resolution_service.h"
63 #include "net/proxy_resolution/proxy_retry_info.h"
64 #include "net/ssl/ssl_server_config.h"
65 #include "net/storage_access_api/status.h"
66 #include "net/test/embedded_test_server/embedded_test_server.h"
67 #include "net/test/embedded_test_server/http_request.h"
68 #include "net/test/embedded_test_server/http_response.h"
69 #include "net/test/embedded_test_server/install_default_websocket_handlers.h"
70 #include "net/test/spawned_test_server/spawned_test_server.h"
71 #include "net/test/ssl_test_util.h"
72 #include "net/test/test_data_directory.h"
73 #include "net/test/test_with_task_environment.h"
74 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
75 #include "net/url_request/url_request.h"
76 #include "net/url_request/url_request_context.h"
77 #include "net/url_request/url_request_context_builder.h"
78 #include "net/url_request/url_request_test_util.h"
79 #include "net/websockets/websocket_channel.h"
80 #include "net/websockets/websocket_event_interface.h"
81 #include "net/websockets/websocket_handshake_response_info.h"
82 #include "testing/gtest/include/gtest/gtest.h"
83 #include "third_party/abseil-cpp/absl/types/variant.h"
84 #include "url/gurl.h"
85 #include "url/origin.h"
86 #include "url/url_constants.h"
87
88 namespace net {
89 class HttpResponseHeaders;
90 class ProxyServer;
91 class SSLInfo;
92 struct WebSocketHandshakeRequestInfo;
93
94 namespace {
95
96 using test_server::BasicHttpResponse;
97 using test_server::HttpRequest;
98 using test_server::HttpResponse;
99
100 static constexpr char kEchoServer[] = "echo-with-no-extension";
101
102 // Simplify changing URL schemes.
ReplaceUrlScheme(const GURL & in_url,std::string_view scheme)103 GURL ReplaceUrlScheme(const GURL& in_url, std::string_view scheme) {
104 GURL::Replacements replacements;
105 replacements.SetSchemeStr(scheme);
106 return in_url.ReplaceComponents(replacements);
107 }
108
109 // An implementation of WebSocketEventInterface that waits for and records the
110 // results of the connect.
111 class ConnectTestingEventInterface : public WebSocketEventInterface {
112 public:
113 ConnectTestingEventInterface();
114
115 ConnectTestingEventInterface(const ConnectTestingEventInterface&) = delete;
116 ConnectTestingEventInterface& operator=(const ConnectTestingEventInterface&) =
117 delete;
118
119 void WaitForResponse();
120
failed() const121 bool failed() const { return failed_; }
122
response() const123 const std::unique_ptr<WebSocketHandshakeResponseInfo>& response() const {
124 return response_;
125 }
126
127 // Only set if the handshake failed, otherwise empty.
128 std::string failure_message() const;
129
130 std::string selected_subprotocol() const;
131
132 std::string extensions() const;
133
134 // Implementation of WebSocketEventInterface.
OnCreateURLRequest(URLRequest * request)135 void OnCreateURLRequest(URLRequest* request) override {}
136
OnURLRequestConnected(net::URLRequest * request,const net::TransportInfo & info)137 void OnURLRequestConnected(net::URLRequest* request,
138 const net::TransportInfo& info) override {}
139
140 void OnAddChannelResponse(
141 std::unique_ptr<WebSocketHandshakeResponseInfo> response,
142 const std::string& selected_subprotocol,
143 const std::string& extensions) override;
144
145 void OnDataFrame(bool fin,
146 WebSocketMessageType type,
147 base::span<const char> payload) override;
148
HasPendingDataFrames()149 bool HasPendingDataFrames() override { return false; }
150
151 void OnSendDataFrameDone() override;
152
153 void OnClosingHandshake() override;
154
155 void OnDropChannel(bool was_clean,
156 uint16_t code,
157 const std::string& reason) override;
158
159 void OnFailChannel(const std::string& message,
160 int net_error,
161 std::optional<int> response_code) override;
162
163 void OnStartOpeningHandshake(
164 std::unique_ptr<WebSocketHandshakeRequestInfo> request) override;
165
166 void OnSSLCertificateError(
167 std::unique_ptr<SSLErrorCallbacks> ssl_error_callbacks,
168 const GURL& url,
169 int net_error,
170 const SSLInfo& ssl_info,
171 bool fatal) override;
172
173 int OnAuthRequired(const AuthChallengeInfo& auth_info,
174 scoped_refptr<HttpResponseHeaders> response_headers,
175 const IPEndPoint& remote_endpoint,
176 base::OnceCallback<void(const AuthCredentials*)> callback,
177 std::optional<AuthCredentials>* credentials) override;
178
179 std::string GetDataFramePayload();
180
WaitForDropChannel()181 void WaitForDropChannel() { drop_channel_future_.Get(); }
182
183 private:
184 void QuitLoop();
185 void RunNewLoop();
186 void SetReceivedMessageFuture(std::string received_message);
187
188 // failed_ is true if the handshake failed (ie. OnFailChannel was called).
189 bool failed_ = false;
190 std::unique_ptr<WebSocketHandshakeResponseInfo> response_;
191 std::string selected_subprotocol_;
192 std::string extensions_;
193 std::string failure_message_;
194 std::optional<base::RunLoop> run_loop_;
195
196 base::test::TestFuture<std::string> received_message_future_;
197 base::test::TestFuture<void> drop_channel_future_;
198 };
199
200 ConnectTestingEventInterface::ConnectTestingEventInterface() = default;
201
WaitForResponse()202 void ConnectTestingEventInterface::WaitForResponse() {
203 RunNewLoop();
204 }
205
failure_message() const206 std::string ConnectTestingEventInterface::failure_message() const {
207 return failure_message_;
208 }
209
selected_subprotocol() const210 std::string ConnectTestingEventInterface::selected_subprotocol() const {
211 return selected_subprotocol_;
212 }
213
extensions() const214 std::string ConnectTestingEventInterface::extensions() const {
215 return extensions_;
216 }
217
OnAddChannelResponse(std::unique_ptr<WebSocketHandshakeResponseInfo> response,const std::string & selected_subprotocol,const std::string & extensions)218 void ConnectTestingEventInterface::OnAddChannelResponse(
219 std::unique_ptr<WebSocketHandshakeResponseInfo> response,
220 const std::string& selected_subprotocol,
221 const std::string& extensions) {
222 response_ = std::move(response);
223 selected_subprotocol_ = selected_subprotocol;
224 extensions_ = extensions;
225 QuitLoop();
226 }
227
OnDataFrame(bool fin,WebSocketMessageType type,base::span<const char> payload)228 void ConnectTestingEventInterface::OnDataFrame(bool fin,
229 WebSocketMessageType type,
230 base::span<const char> payload) {
231 DVLOG(3) << "Received WebSocket data frame with message:"
232 << std::string(payload.begin(), payload.end());
233 SetReceivedMessageFuture(std::string(base::as_string_view(payload)));
234 }
235
OnSendDataFrameDone()236 void ConnectTestingEventInterface::OnSendDataFrameDone() {}
237
OnClosingHandshake()238 void ConnectTestingEventInterface::OnClosingHandshake() {
239 DVLOG(3) << "OnClosingHandeshake() invoked.";
240 }
241
OnDropChannel(bool was_clean,uint16_t code,const std::string & reason)242 void ConnectTestingEventInterface::OnDropChannel(bool was_clean,
243 uint16_t code,
244 const std::string& reason) {
245 DVLOG(3) << "OnDropChannel() invoked, was_clean: " << was_clean
246 << ", code: " << code << ", reason: " << reason;
247 if (was_clean) {
248 drop_channel_future_.SetValue();
249 } else {
250 DVLOG(1) << "OnDropChannel() did not receive a clean close.";
251 }
252 }
253
OnFailChannel(const std::string & message,int net_error,std::optional<int> response_code)254 void ConnectTestingEventInterface::OnFailChannel(
255 const std::string& message,
256 int net_error,
257 std::optional<int> response_code) {
258 DVLOG(3) << "OnFailChannel invoked with message: " << message;
259 failed_ = true;
260 failure_message_ = message;
261 QuitLoop();
262 }
263
OnStartOpeningHandshake(std::unique_ptr<WebSocketHandshakeRequestInfo> request)264 void ConnectTestingEventInterface::OnStartOpeningHandshake(
265 std::unique_ptr<WebSocketHandshakeRequestInfo> request) {}
266
OnSSLCertificateError(std::unique_ptr<SSLErrorCallbacks> ssl_error_callbacks,const GURL & url,int net_error,const SSLInfo & ssl_info,bool fatal)267 void ConnectTestingEventInterface::OnSSLCertificateError(
268 std::unique_ptr<SSLErrorCallbacks> ssl_error_callbacks,
269 const GURL& url,
270 int net_error,
271 const SSLInfo& ssl_info,
272 bool fatal) {
273 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
274 FROM_HERE, base::BindOnce(&SSLErrorCallbacks::CancelSSLRequest,
275 base::Owned(ssl_error_callbacks.release()),
276 ERR_SSL_PROTOCOL_ERROR, &ssl_info));
277 }
278
OnAuthRequired(const AuthChallengeInfo & auth_info,scoped_refptr<HttpResponseHeaders> response_headers,const IPEndPoint & remote_endpoint,base::OnceCallback<void (const AuthCredentials *)> callback,std::optional<AuthCredentials> * credentials)279 int ConnectTestingEventInterface::OnAuthRequired(
280 const AuthChallengeInfo& auth_info,
281 scoped_refptr<HttpResponseHeaders> response_headers,
282 const IPEndPoint& remote_endpoint,
283 base::OnceCallback<void(const AuthCredentials*)> callback,
284 std::optional<AuthCredentials>* credentials) {
285 *credentials = std::nullopt;
286 return OK;
287 }
288
QuitLoop()289 void ConnectTestingEventInterface::QuitLoop() {
290 if (!run_loop_) {
291 DVLOG(3) << "No active run loop to quit.";
292 return;
293 }
294 run_loop_->Quit();
295 }
296
RunNewLoop()297 void ConnectTestingEventInterface::RunNewLoop() {
298 run_loop_.emplace();
299 run_loop_->Run();
300 }
301
SetReceivedMessageFuture(std::string received_message)302 void ConnectTestingEventInterface::SetReceivedMessageFuture(
303 std::string received_message) {
304 received_message_future_.SetValue(received_message);
305 }
306
GetDataFramePayload()307 std::string ConnectTestingEventInterface::GetDataFramePayload() {
308 return received_message_future_.Get();
309 }
310
311 // A subclass of TestNetworkDelegate that additionally implements the
312 // OnResolveProxy callback and records the information passed to it.
313 class TestProxyDelegateWithProxyInfo : public ProxyDelegate {
314 public:
315 TestProxyDelegateWithProxyInfo() = default;
316
317 TestProxyDelegateWithProxyInfo(const TestProxyDelegateWithProxyInfo&) =
318 delete;
319 TestProxyDelegateWithProxyInfo& operator=(
320 const TestProxyDelegateWithProxyInfo&) = delete;
321
322 struct ResolvedProxyInfo {
323 GURL url;
324 ProxyInfo proxy_info;
325 };
326
resolved_proxy_info() const327 const ResolvedProxyInfo& resolved_proxy_info() const {
328 return resolved_proxy_info_;
329 }
330
331 protected:
OnResolveProxy(const GURL & url,const NetworkAnonymizationKey & network_anonymization_key,const std::string & method,const ProxyRetryInfoMap & proxy_retry_info,ProxyInfo * result)332 void OnResolveProxy(const GURL& url,
333 const NetworkAnonymizationKey& network_anonymization_key,
334 const std::string& method,
335 const ProxyRetryInfoMap& proxy_retry_info,
336 ProxyInfo* result) override {
337 resolved_proxy_info_.url = url;
338 resolved_proxy_info_.proxy_info = *result;
339 }
340
OnSuccessfulRequestAfterFailures(const ProxyRetryInfoMap & proxy_retry_info)341 void OnSuccessfulRequestAfterFailures(
342 const ProxyRetryInfoMap& proxy_retry_info) override {}
343
OnFallback(const ProxyChain & bad_chain,int net_error)344 void OnFallback(const ProxyChain& bad_chain, int net_error) override {}
345
OnBeforeTunnelRequest(const ProxyChain & proxy_chain,size_t chain_index,HttpRequestHeaders * extra_headers)346 Error OnBeforeTunnelRequest(const ProxyChain& proxy_chain,
347 size_t chain_index,
348 HttpRequestHeaders* extra_headers) override {
349 return OK;
350 }
351
OnTunnelHeadersReceived(const ProxyChain & proxy_chain,size_t chain_index,const HttpResponseHeaders & response_headers)352 Error OnTunnelHeadersReceived(
353 const ProxyChain& proxy_chain,
354 size_t chain_index,
355 const HttpResponseHeaders& response_headers) override {
356 return OK;
357 }
358
SetProxyResolutionService(ProxyResolutionService * proxy_resolution_service)359 void SetProxyResolutionService(
360 ProxyResolutionService* proxy_resolution_service) override {}
361
362 private:
363 ResolvedProxyInfo resolved_proxy_info_;
364 };
365
366 class WebSocketEndToEndTest : public TestWithTaskEnvironment {
367 protected:
WebSocketEndToEndTest()368 WebSocketEndToEndTest()
369 : proxy_delegate_(std::make_unique<TestProxyDelegateWithProxyInfo>()),
370 context_builder_(CreateTestURLRequestContextBuilder()) {}
371
372 // Initialise the URLRequestContext. Normally done automatically by
373 // ConnectAndWait(). This method is for the use of tests that need the
374 // URLRequestContext initialised before calling ConnectAndWait().
InitialiseContext()375 void InitialiseContext() {
376 DCHECK(!context_);
377 context_ = context_builder_->Build();
378 context_->proxy_resolution_service()->SetProxyDelegate(
379 proxy_delegate_.get());
380 }
381
382 // Send the connect request to |socket_url| and wait for a response. Returns
383 // true if the handshake succeeded.
ConnectAndWait(const GURL & socket_url)384 bool ConnectAndWait(const GURL& socket_url) {
385 if (!context_) {
386 InitialiseContext();
387 }
388 url::Origin origin = url::Origin::Create(GURL("http://localhost"));
389 net::SiteForCookies site_for_cookies =
390 net::SiteForCookies::FromOrigin(origin);
391 IsolationInfo isolation_info =
392 IsolationInfo::Create(IsolationInfo::RequestType::kOther, origin,
393 origin, SiteForCookies::FromOrigin(origin));
394 auto event_interface = std::make_unique<ConnectTestingEventInterface>();
395 event_interface_ = event_interface.get();
396 channel_ = std::make_unique<WebSocketChannel>(std::move(event_interface),
397 context_.get());
398 channel_->SendAddChannelRequest(
399 GURL(socket_url), sub_protocols_, origin, site_for_cookies,
400 StorageAccessApiStatus::kNone, isolation_info, HttpRequestHeaders(),
401 TRAFFIC_ANNOTATION_FOR_TESTS);
402 event_interface_->WaitForResponse();
403 return !event_interface_->failed();
404 }
405
SendMessage(const std::string & message)406 [[nodiscard]] WebSocketChannel::ChannelState SendMessage(
407 const std::string& message) {
408 scoped_refptr<IOBufferWithSize> buffer =
409 base::MakeRefCounted<IOBufferWithSize>(message.size());
410
411 buffer->span().copy_from(base::as_byte_span(message));
412 return channel_->SendFrame(true, WebSocketFrameHeader::kOpCodeText, buffer,
413 message.size());
414 }
415
ReceiveMessage()416 std::string ReceiveMessage() {
417 auto channel_state = channel_->ReadFrames();
418 if (channel_state != WebSocketChannel::ChannelState::CHANNEL_ALIVE) {
419 ADD_FAILURE()
420 << "WebSocket channel is no longer alive after reading frames. State:"
421 << channel_state;
422 return {};
423 }
424 return event_interface_->GetDataFramePayload();
425 }
426
CloseWebSocket()427 void CloseWebSocket() {
428 const uint16_t close_code = 1000;
429 const std::string close_reason = "Closing connection";
430
431 DVLOG(3) << "Sending close handshake with code: " << close_code
432 << " and reason: " << close_reason;
433
434 auto channel_state =
435 channel_->StartClosingHandshake(close_code, close_reason);
436
437 EXPECT_EQ(channel_state, WebSocketChannel::ChannelState::CHANNEL_ALIVE)
438 << "WebSocket channel is no longer alive after sending the "
439 "Close frame. State: "
440 << channel_state;
441 }
442
CloseWebSocketSuccessfully()443 void CloseWebSocketSuccessfully() {
444 CloseWebSocket();
445 event_interface_->WaitForDropChannel();
446 }
447
RunBasicSmokeTest(net::EmbeddedTestServer::Type server_type)448 void RunBasicSmokeTest(net::EmbeddedTestServer::Type server_type) {
449 test_server::EmbeddedTestServer embedded_test_server(server_type);
450
451 test_server::InstallDefaultWebSocketHandlers(&embedded_test_server);
452
453 ASSERT_TRUE(embedded_test_server.Start());
454
455 GURL echo_url = test_server::ToWebSocketUrl(
456 embedded_test_server.GetURL("/echo-with-no-extension"));
457 EXPECT_TRUE(ConnectAndWait(echo_url));
458 }
459
460 raw_ptr<ConnectTestingEventInterface, DanglingUntriaged>
461 event_interface_; // owned by channel_
462 std::unique_ptr<TestProxyDelegateWithProxyInfo> proxy_delegate_;
463 std::unique_ptr<URLRequestContextBuilder> context_builder_;
464 std::unique_ptr<URLRequestContext> context_;
465 std::unique_ptr<WebSocketChannel> channel_;
466 std::vector<std::string> sub_protocols_;
467 };
468
469 // Basic test of connectivity. If this test fails, nothing else can be expected
470 // to work.
TEST_F(WebSocketEndToEndTest,BasicSmokeTest)471 TEST_F(WebSocketEndToEndTest, BasicSmokeTest) {
472 RunBasicSmokeTest(net::EmbeddedTestServer::TYPE_HTTP);
473 }
474
TEST_F(WebSocketEndToEndTest,BasicSmokeTestSSL)475 TEST_F(WebSocketEndToEndTest, BasicSmokeTestSSL) {
476 RunBasicSmokeTest(net::EmbeddedTestServer::TYPE_HTTPS);
477 }
478
TEST_F(WebSocketEndToEndTest,WebSocketEchoHandlerTest)479 TEST_F(WebSocketEndToEndTest, WebSocketEchoHandlerTest) {
480 test_server::EmbeddedTestServer embedded_test_server(
481 test_server::EmbeddedTestServer::TYPE_HTTP);
482
483 test_server::InstallDefaultWebSocketHandlers(&embedded_test_server);
484
485 ASSERT_TRUE(embedded_test_server.Start());
486
487 GURL echo_url = test_server::ToWebSocketUrl(
488 embedded_test_server.GetURL("/echo-with-no-extension"));
489 ASSERT_TRUE(ConnectAndWait(echo_url));
490
491 const std::string test_message = "hello echo";
492
493 auto channel_state = SendMessage(test_message);
494
495 ASSERT_EQ(channel_state, WebSocketChannel::ChannelState::CHANNEL_ALIVE);
496
497 std::string received_message = ReceiveMessage();
498
499 EXPECT_EQ(test_message, received_message);
500 CloseWebSocketSuccessfully();
501 }
502
503 // These test are not compatible with RemoteTestServer because RemoteTestServer
504 // doesn't support TYPE_BASIC_AUTH_PROXY.
505 // TODO(ricea): Make these tests work. See crbug.com/441711.
506 constexpr bool kHasBasicAuthProxy =
507 !(BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA));
508
509 // Test for issue crbug.com/433695 "Unencrypted WebSocket connection via
510 // authenticated proxy times out".
TEST_F(WebSocketEndToEndTest,HttpsProxyUnauthedFails)511 TEST_F(WebSocketEndToEndTest, HttpsProxyUnauthedFails) {
512 if (!kHasBasicAuthProxy) {
513 GTEST_SKIP() << "Test not supported on this platform";
514 }
515
516 SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
517 base::FilePath());
518 SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
519 GetWebSocketTestDataDirectory());
520 ASSERT_TRUE(proxy_server.StartInBackground());
521 ASSERT_TRUE(ws_server.StartInBackground());
522 ASSERT_TRUE(proxy_server.BlockUntilStarted());
523 ASSERT_TRUE(ws_server.BlockUntilStarted());
524 ProxyConfig proxy_config;
525 proxy_config.proxy_rules().ParseFromString(
526 "https=" + proxy_server.host_port_pair().ToString());
527 // TODO(crbug.com/40600992): Don't rely on proxying localhost.
528 proxy_config.proxy_rules().bypass_rules.AddRulesToSubtractImplicit();
529
530 std::unique_ptr<ProxyResolutionService> proxy_resolution_service(
531 ConfiguredProxyResolutionService::CreateFixedForTest(
532 ProxyConfigWithAnnotation(proxy_config,
533 TRAFFIC_ANNOTATION_FOR_TESTS)));
534 ASSERT_TRUE(proxy_resolution_service);
535 context_builder_->set_proxy_resolution_service(
536 std::move(proxy_resolution_service));
537
538 EXPECT_FALSE(ConnectAndWait(ws_server.GetURL(kEchoServer)));
539 EXPECT_EQ("Proxy authentication failed", event_interface_->failure_message());
540 }
541
TEST_F(WebSocketEndToEndTest,HttpsWssProxyUnauthedFails)542 TEST_F(WebSocketEndToEndTest, HttpsWssProxyUnauthedFails) {
543 if (!kHasBasicAuthProxy) {
544 GTEST_SKIP() << "Test not supported on this platform";
545 }
546
547 SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
548 base::FilePath());
549 SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS,
550 GetWebSocketTestDataDirectory());
551 ASSERT_TRUE(proxy_server.StartInBackground());
552 ASSERT_TRUE(wss_server.StartInBackground());
553 ASSERT_TRUE(proxy_server.BlockUntilStarted());
554 ASSERT_TRUE(wss_server.BlockUntilStarted());
555 ProxyConfig proxy_config;
556 proxy_config.proxy_rules().ParseFromString(
557 "https=" + proxy_server.host_port_pair().ToString());
558 // TODO(crbug.com/40600992): Don't rely on proxying localhost.
559 proxy_config.proxy_rules().bypass_rules.AddRulesToSubtractImplicit();
560
561 std::unique_ptr<ProxyResolutionService> proxy_resolution_service(
562 ConfiguredProxyResolutionService::CreateFixedForTest(
563 ProxyConfigWithAnnotation(proxy_config,
564 TRAFFIC_ANNOTATION_FOR_TESTS)));
565 ASSERT_TRUE(proxy_resolution_service);
566 context_builder_->set_proxy_resolution_service(
567 std::move(proxy_resolution_service));
568 EXPECT_FALSE(ConnectAndWait(wss_server.GetURL(kEchoServer)));
569 EXPECT_EQ("Proxy authentication failed", event_interface_->failure_message());
570 }
571
572 // Regression test for crbug.com/426736 "WebSocket connections not using
573 // configured system HTTPS Proxy".
TEST_F(WebSocketEndToEndTest,HttpsProxyUsed)574 TEST_F(WebSocketEndToEndTest, HttpsProxyUsed) {
575 if (!kHasBasicAuthProxy) {
576 GTEST_SKIP() << "Test not supported on this platform";
577 }
578
579 SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_PROXY,
580 base::FilePath());
581 SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
582 GetWebSocketTestDataDirectory());
583 ASSERT_TRUE(proxy_server.StartInBackground());
584 ASSERT_TRUE(ws_server.StartInBackground());
585 ASSERT_TRUE(proxy_server.BlockUntilStarted());
586 ASSERT_TRUE(ws_server.BlockUntilStarted());
587 ProxyConfig proxy_config;
588 proxy_config.proxy_rules().ParseFromString(
589 "https=" + proxy_server.host_port_pair().ToString() + ";" +
590 "http=" + proxy_server.host_port_pair().ToString());
591 // TODO(crbug.com/40600992): Don't rely on proxying localhost.
592 proxy_config.proxy_rules().bypass_rules.AddRulesToSubtractImplicit();
593
594 std::unique_ptr<ProxyResolutionService> proxy_resolution_service(
595 ConfiguredProxyResolutionService::CreateFixedForTest(
596 ProxyConfigWithAnnotation(proxy_config,
597 TRAFFIC_ANNOTATION_FOR_TESTS)));
598 context_builder_->set_proxy_resolution_service(
599 std::move(proxy_resolution_service));
600 InitialiseContext();
601
602 GURL ws_url = ws_server.GetURL(kEchoServer);
603 EXPECT_TRUE(ConnectAndWait(ws_url));
604 const TestProxyDelegateWithProxyInfo::ResolvedProxyInfo& info =
605 proxy_delegate_->resolved_proxy_info();
606 EXPECT_EQ(ws_url, info.url);
607 EXPECT_EQ(info.proxy_info.ToDebugString(),
608 base::StrCat({"PROXY ", proxy_server.host_port_pair().ToString()}));
609 }
610
ProxyPacHandler(const HttpRequest & request)611 std::unique_ptr<HttpResponse> ProxyPacHandler(const HttpRequest& request) {
612 GURL url = request.GetURL();
613 EXPECT_EQ(url.path_piece(), "/proxy.pac");
614 EXPECT_TRUE(url.has_query());
615 std::string proxy;
616 EXPECT_TRUE(GetValueForKeyInQuery(url, "proxy", &proxy));
617 auto response = std::make_unique<BasicHttpResponse>();
618 response->set_content_type("application/x-ns-proxy-autoconfig");
619 response->set_content(
620 base::StringPrintf("function FindProxyForURL(url, host) {\n"
621 " return 'PROXY %s';\n"
622 "}\n",
623 proxy.c_str()));
624 return response;
625 }
626
627 // This tests the proxy.pac resolver that is built into the system. This is not
628 // the one that Chrome normally uses. Chrome's normal implementation is defined
629 // as a mojo service. It is outside //net and we can't use it from here. This
630 // tests the alternative implementations that are selected when the
631 // --winhttp-proxy-resolver flag is provided to Chrome. These only exist on OS X
632 // and Windows.
633 // TODO(ricea): Remove this test if --winhttp-proxy-resolver flag is removed.
634 // See crbug.com/644030.
TEST_F(WebSocketEndToEndTest,ProxyPacUsed)635 TEST_F(WebSocketEndToEndTest, ProxyPacUsed) {
636 if constexpr (!BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_APPLE)) {
637 GTEST_SKIP() << "Test not supported on this platform";
638 }
639
640 EmbeddedTestServer proxy_pac_server(net::EmbeddedTestServer::Type::TYPE_HTTP);
641 SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_PROXY,
642 base::FilePath());
643 SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
644 GetWebSocketTestDataDirectory());
645 proxy_pac_server.RegisterRequestHandler(base::BindRepeating(ProxyPacHandler));
646 proxy_server.set_redirect_connect_to_localhost(true);
647
648 ASSERT_TRUE(proxy_pac_server.Start());
649 ASSERT_TRUE(proxy_server.StartInBackground());
650 ASSERT_TRUE(ws_server.StartInBackground());
651 ASSERT_TRUE(proxy_server.BlockUntilStarted());
652 ASSERT_TRUE(ws_server.BlockUntilStarted());
653
654 ProxyConfig proxy_config =
655 ProxyConfig::CreateFromCustomPacURL(proxy_pac_server.GetURL(base::StrCat(
656 {"/proxy.pac?proxy=", proxy_server.host_port_pair().ToString()})));
657 proxy_config.set_pac_mandatory(true);
658 auto proxy_config_service = std::make_unique<ProxyConfigServiceFixed>(
659 ProxyConfigWithAnnotation(proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS));
660 std::unique_ptr<ProxyResolutionService> proxy_resolution_service(
661 ConfiguredProxyResolutionService::CreateUsingSystemProxyResolver(
662 std::move(proxy_config_service), NetLog::Get(),
663 /*quick_check_enabled=*/true));
664 ASSERT_EQ(ws_server.host_port_pair().host(), "127.0.0.1");
665 context_builder_->set_proxy_resolution_service(
666 std::move(proxy_resolution_service));
667 InitialiseContext();
668
669 // Use a name other than localhost, since localhost implicitly bypasses the
670 // use of proxy.pac.
671 HostPortPair fake_ws_host_port_pair("stealth-localhost",
672 ws_server.host_port_pair().port());
673
674 GURL ws_url(base::StrCat(
675 {"ws://", fake_ws_host_port_pair.ToString(), "/", kEchoServer}));
676 EXPECT_TRUE(ConnectAndWait(ws_url));
677 const auto& info = proxy_delegate_->resolved_proxy_info();
678 EXPECT_EQ(ws_url, info.url);
679 EXPECT_EQ(info.proxy_info.ToDebugString(),
680 base::StrCat({"PROXY ", proxy_server.host_port_pair().ToString()}));
681 }
682
683 // This is a regression test for crbug.com/408061 Crash in
684 // net::WebSocketBasicHandshakeStream::Upgrade.
TEST_F(WebSocketEndToEndTest,TruncatedResponse)685 TEST_F(WebSocketEndToEndTest, TruncatedResponse) {
686 SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
687 GetWebSocketTestDataDirectory());
688 ASSERT_TRUE(ws_server.Start());
689 InitialiseContext();
690
691 GURL ws_url = ws_server.GetURL("truncated-headers");
692 EXPECT_FALSE(ConnectAndWait(ws_url));
693 }
694
695 // Regression test for crbug.com/455215 "HSTS not applied to WebSocket"
TEST_F(WebSocketEndToEndTest,HstsHttpsToWebSocket)696 TEST_F(WebSocketEndToEndTest, HstsHttpsToWebSocket) {
697 EmbeddedTestServer https_server(net::EmbeddedTestServer::Type::TYPE_HTTPS);
698 std::string test_server_hostname = "a.test";
699 https_server.SetCertHostnames({test_server_hostname});
700 https_server.ServeFilesFromSourceDirectory("net/data/url_request_unittest");
701
702 SpawnedTestServer::SSLOptions ssl_options(
703 SpawnedTestServer::SSLOptions::CERT_TEST_NAMES);
704 SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS, ssl_options,
705 GetWebSocketTestDataDirectory());
706
707 ASSERT_TRUE(https_server.Start());
708 ASSERT_TRUE(wss_server.Start());
709 InitialiseContext();
710
711 // Set HSTS via https:
712 TestDelegate delegate;
713 GURL https_page =
714 https_server.GetURL(test_server_hostname, "/hsts-headers.html");
715 std::unique_ptr<URLRequest> request(context_->CreateRequest(
716 https_page, DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
717 request->Start();
718 delegate.RunUntilComplete();
719 EXPECT_EQ(OK, delegate.request_status());
720
721 // Check HSTS with ws:
722 // Change the scheme from wss: to ws: to verify that it is switched back.
723 GURL ws_url = ReplaceUrlScheme(
724 wss_server.GetURL(test_server_hostname, kEchoServer), "ws");
725 EXPECT_TRUE(ConnectAndWait(ws_url));
726 }
727
TEST_F(WebSocketEndToEndTest,HstsWebSocketToHttps)728 TEST_F(WebSocketEndToEndTest, HstsWebSocketToHttps) {
729 EmbeddedTestServer https_server(net::EmbeddedTestServer::Type::TYPE_HTTPS);
730 std::string test_server_hostname = "a.test";
731 https_server.SetCertHostnames({test_server_hostname});
732 https_server.ServeFilesFromSourceDirectory("net/data/url_request_unittest");
733
734 SpawnedTestServer::SSLOptions ssl_options(
735 SpawnedTestServer::SSLOptions::CERT_TEST_NAMES);
736 SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS, ssl_options,
737 GetWebSocketTestDataDirectory());
738 ASSERT_TRUE(https_server.Start());
739 ASSERT_TRUE(wss_server.Start());
740 InitialiseContext();
741 // Set HSTS via wss:
742 GURL wss_url = wss_server.GetURL(test_server_hostname, "set-hsts");
743 EXPECT_TRUE(ConnectAndWait(wss_url));
744
745 // Verify via http:
746 TestDelegate delegate;
747 GURL http_page = ReplaceUrlScheme(
748 https_server.GetURL(test_server_hostname, "/simple.html"), "http");
749 std::unique_ptr<URLRequest> request(context_->CreateRequest(
750 http_page, DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
751 request->Start();
752 delegate.RunUntilComplete();
753 EXPECT_EQ(OK, delegate.request_status());
754 EXPECT_TRUE(request->url().SchemeIs("https"));
755 }
756
TEST_F(WebSocketEndToEndTest,HstsWebSocketToWebSocket)757 TEST_F(WebSocketEndToEndTest, HstsWebSocketToWebSocket) {
758 std::string test_server_hostname = "a.test";
759 SpawnedTestServer::SSLOptions ssl_options(
760 SpawnedTestServer::SSLOptions::CERT_TEST_NAMES);
761 SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS, ssl_options,
762 GetWebSocketTestDataDirectory());
763 ASSERT_TRUE(wss_server.Start());
764 InitialiseContext();
765 // Set HSTS via wss:
766 GURL wss_url = wss_server.GetURL(test_server_hostname, "set-hsts");
767 EXPECT_TRUE(ConnectAndWait(wss_url));
768
769 // Verify via ws:
770 GURL ws_url = ReplaceUrlScheme(
771 wss_server.GetURL(test_server_hostname, kEchoServer), "ws");
772 EXPECT_TRUE(ConnectAndWait(ws_url));
773 }
774
775 // Regression test for crbug.com/180504 "WebSocket handshake fails when HTTP
776 // headers have trailing LWS".
TEST_F(WebSocketEndToEndTest,TrailingWhitespace)777 TEST_F(WebSocketEndToEndTest, TrailingWhitespace) {
778 SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
779 GetWebSocketTestDataDirectory());
780 ASSERT_TRUE(ws_server.Start());
781
782 GURL ws_url = ws_server.GetURL("trailing-whitespace");
783 sub_protocols_.push_back("sip");
784 EXPECT_TRUE(ConnectAndWait(ws_url));
785 EXPECT_EQ("sip", event_interface_->selected_subprotocol());
786 }
787
788 // This is a regression test for crbug.com/169448 "WebSockets should support
789 // header continuations"
790 // TODO(ricea): HTTP continuation headers have been deprecated by RFC7230. If
791 // support for continuation headers is removed from Chrome, then this test will
792 // break and should be removed.
TEST_F(WebSocketEndToEndTest,HeaderContinuations)793 TEST_F(WebSocketEndToEndTest, HeaderContinuations) {
794 SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
795 GetWebSocketTestDataDirectory());
796 ASSERT_TRUE(ws_server.Start());
797
798 GURL ws_url = ws_server.GetURL("header-continuation");
799
800 EXPECT_TRUE(ConnectAndWait(ws_url));
801 EXPECT_EQ("permessage-deflate; server_max_window_bits=10",
802 event_interface_->extensions());
803 }
804
805 // Test that ws->wss scheme upgrade is supported on receiving a DNS HTTPS
806 // record.
TEST_F(WebSocketEndToEndTest,DnsSchemeUpgradeSupported)807 TEST_F(WebSocketEndToEndTest, DnsSchemeUpgradeSupported) {
808 SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS,
809 SpawnedTestServer::SSLOptions(base::FilePath(
810 FILE_PATH_LITERAL("test_names.pem"))),
811 GetWebSocketTestDataDirectory());
812 ASSERT_TRUE(wss_server.Start());
813
814 GURL wss_url("wss://a.test:" +
815 base::NumberToString(wss_server.host_port_pair().port()) + "/" +
816 kEchoServer);
817 GURL::Replacements replacements;
818 replacements.SetSchemeStr(url::kWsScheme);
819 GURL ws_url = wss_url.ReplaceComponents(replacements);
820
821 // Note that due to socket pool behavior, HostResolver will see the ws/wss
822 // requests as http/https.
823 auto host_resolver = std::make_unique<MockHostResolver>();
824 MockHostResolverBase::RuleResolver::RuleKey unencrypted_resolve_key;
825 unencrypted_resolve_key.scheme = url::kHttpScheme;
826 host_resolver->rules()->AddRule(std::move(unencrypted_resolve_key),
827 ERR_DNS_NAME_HTTPS_ONLY);
828 MockHostResolverBase::RuleResolver::RuleKey encrypted_resolve_key;
829 encrypted_resolve_key.scheme = url::kHttpsScheme;
830 host_resolver->rules()->AddRule(std::move(encrypted_resolve_key),
831 "127.0.0.1");
832 context_builder_->set_host_resolver(std::move(host_resolver));
833
834 EXPECT_TRUE(ConnectAndWait(ws_url));
835
836 // Expect request to have reached the server using the upgraded URL.
837 EXPECT_EQ(event_interface_->response()->url, wss_url);
838 }
839
840 // Test that wss connections can use HostResolverEndpointResults from DNS.
TEST_F(WebSocketEndToEndTest,HostResolverEndpointResult)841 TEST_F(WebSocketEndToEndTest, HostResolverEndpointResult) {
842 base::test::ScopedFeatureList features;
843 features.InitAndEnableFeature(features::kUseDnsHttpsSvcb);
844
845 SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS,
846 SpawnedTestServer::SSLOptions(base::FilePath(
847 FILE_PATH_LITERAL("test_names.pem"))),
848 GetWebSocketTestDataDirectory());
849 ASSERT_TRUE(wss_server.Start());
850
851 uint16_t port = wss_server.host_port_pair().port();
852 GURL wss_url("wss://a.test:" + base::NumberToString(port) + "/" +
853 kEchoServer);
854
855 auto host_resolver = std::make_unique<MockHostResolver>();
856 MockHostResolverBase::RuleResolver::RuleKey resolve_key;
857 // The DNS query itself is made with the https scheme rather than wss.
858 resolve_key.scheme = url::kHttpsScheme;
859 resolve_key.hostname_pattern = "a.test";
860 resolve_key.port = port;
861 HostResolverEndpointResult result;
862 result.ip_endpoints = {IPEndPoint(IPAddress::IPv4Localhost(), port)};
863 result.metadata.supported_protocol_alpns = {"http/1.1"};
864 host_resolver->rules()->AddRule(
865 std::move(resolve_key),
866 MockHostResolverBase::RuleResolver::RuleResult(std::vector{result}));
867 context_builder_->set_host_resolver(std::move(host_resolver));
868
869 EXPECT_TRUE(ConnectAndWait(wss_url));
870
871 // Expect request to have reached the server using the upgraded URL.
872 EXPECT_EQ(event_interface_->response()->url, wss_url);
873 }
874
875 // Test that wss connections can use EncryptedClientHello.
TEST_F(WebSocketEndToEndTest,EncryptedClientHello)876 TEST_F(WebSocketEndToEndTest, EncryptedClientHello) {
877 base::test::ScopedFeatureList features;
878 features.InitAndEnableFeature(features::kUseDnsHttpsSvcb);
879
880 // SpawnedTestServer does not support ECH, while EmbeddedTestServer does not
881 // support WebSockets (https://crbug.com/1281277). Until that is fixed, test
882 // ECH by configuring a non-WebSockets HTTPS server. The WebSockets handshake
883 // will fail, but getting that far tests that ECH worked.
884
885 // Configure a test server that speaks ECH.
886 static constexpr char kRealName[] = "secret.example";
887 static constexpr char kPublicName[] = "public.example";
888 EmbeddedTestServer::ServerCertificateConfig server_cert_config;
889 server_cert_config.dns_names = {kRealName};
890 SSLServerConfig ssl_server_config;
891 std::vector<uint8_t> ech_config_list;
892 ssl_server_config.ech_keys =
893 MakeTestEchKeys(kPublicName, /*max_name_len=*/128, &ech_config_list);
894 ASSERT_TRUE(ssl_server_config.ech_keys);
895
896 EmbeddedTestServer test_server(EmbeddedTestServer::TYPE_HTTPS);
897 test_server.SetSSLConfig(server_cert_config, ssl_server_config);
898 ASSERT_TRUE(test_server.Start());
899
900 GURL https_url = test_server.GetURL(kRealName, "/");
901 GURL::Replacements replacements;
902 replacements.SetSchemeStr(url::kWssScheme);
903 GURL wss_url = https_url.ReplaceComponents(replacements);
904
905 auto host_resolver = std::make_unique<MockHostResolver>();
906 MockHostResolverBase::RuleResolver::RuleKey resolve_key;
907 // The DNS query itself is made with the https scheme rather than wss.
908 resolve_key.scheme = url::kHttpsScheme;
909 resolve_key.hostname_pattern = wss_url.host();
910 resolve_key.port = wss_url.IntPort();
911 HostResolverEndpointResult result;
912 result.ip_endpoints = {
913 IPEndPoint(IPAddress::IPv4Localhost(), wss_url.IntPort())};
914 result.metadata.supported_protocol_alpns = {"http/1.1"};
915 result.metadata.ech_config_list = ech_config_list;
916 host_resolver->rules()->AddRule(
917 std::move(resolve_key),
918 MockHostResolverBase::RuleResolver::RuleResult(std::vector{result}));
919 context_builder_->set_host_resolver(std::move(host_resolver));
920
921 EXPECT_FALSE(ConnectAndWait(wss_url));
922 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 404",
923 event_interface_->failure_message());
924 }
925 } // namespace
926
927 } // namespace net
928