• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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