1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/websockets/websocket_stream.h"
6
7 #include <optional>
8 #include <ostream>
9 #include <utility>
10
11 #include "base/check.h"
12 #include "base/check_op.h"
13 #include "base/functional/bind.h"
14 #include "base/location.h"
15 #include "base/logging.h"
16 #include "base/memory/raw_ptr.h"
17 #include "base/memory/weak_ptr.h"
18 #include "base/metrics/histogram_functions.h"
19 #include "base/notreached.h"
20 #include "base/strings/strcat.h"
21 #include "base/time/time.h"
22 #include "base/timer/timer.h"
23 #include "net/base/auth.h"
24 #include "net/base/isolation_info.h"
25 #include "net/base/load_flags.h"
26 #include "net/base/net_errors.h"
27 #include "net/base/request_priority.h"
28 #include "net/base/url_util.h"
29 #include "net/http/http_request_headers.h"
30 #include "net/http/http_response_headers.h"
31 #include "net/http/http_response_info.h"
32 #include "net/http/http_status_code.h"
33 #include "net/traffic_annotation/network_traffic_annotation.h"
34 #include "net/url_request/redirect_info.h"
35 #include "net/url_request/url_request.h"
36 #include "net/url_request/url_request_context.h"
37 #include "net/url_request/websocket_handshake_userdata_key.h"
38 #include "net/websockets/websocket_basic_handshake_stream.h"
39 #include "net/websockets/websocket_event_interface.h"
40 #include "net/websockets/websocket_handshake_constants.h"
41 #include "net/websockets/websocket_handshake_response_info.h"
42 #include "net/websockets/websocket_handshake_stream_base.h"
43 #include "net/websockets/websocket_handshake_stream_create_helper.h"
44 #include "net/websockets/websocket_http2_handshake_stream.h"
45 #include "net/websockets/websocket_http3_handshake_stream.h"
46 #include "url/gurl.h"
47 #include "url/origin.h"
48
49 namespace net {
50 class SSLCertRequestInfo;
51 class SSLInfo;
52 class SiteForCookies;
53
54 namespace {
55
56 // The timeout duration of WebSocket handshake.
57 // It is defined as the same value as the TCP connection timeout value in
58 // net/socket/websocket_transport_client_socket_pool.cc to make it hard for
59 // JavaScript programs to recognize the timeout cause.
60 constexpr int kHandshakeTimeoutIntervalInSeconds = 240;
61
62 class WebSocketStreamRequestImpl;
63
64 class Delegate : public URLRequest::Delegate {
65 public:
Delegate(WebSocketStreamRequestImpl * owner)66 explicit Delegate(WebSocketStreamRequestImpl* owner) : owner_(owner) {}
67 ~Delegate() override = default;
68
69 // Implementation of URLRequest::Delegate methods.
70 int OnConnected(URLRequest* request,
71 const TransportInfo& info,
72 CompletionOnceCallback callback) override;
73
74 void OnReceivedRedirect(URLRequest* request,
75 const RedirectInfo& redirect_info,
76 bool* defer_redirect) override;
77
78 void OnResponseStarted(URLRequest* request, int net_error) override;
79
80 void OnAuthRequired(URLRequest* request,
81 const AuthChallengeInfo& auth_info) override;
82
83 void OnCertificateRequested(URLRequest* request,
84 SSLCertRequestInfo* cert_request_info) override;
85
86 void OnSSLCertificateError(URLRequest* request,
87 int net_error,
88 const SSLInfo& ssl_info,
89 bool fatal) override;
90
91 void OnReadCompleted(URLRequest* request, int bytes_read) override;
92
93 private:
94 void OnAuthRequiredComplete(URLRequest* request,
95 const AuthCredentials* auth_credentials);
96
97 raw_ptr<WebSocketStreamRequestImpl> owner_;
98 };
99
100 class WebSocketStreamRequestImpl : public WebSocketStreamRequestAPI {
101 public:
WebSocketStreamRequestImpl(const GURL & url,const std::vector<std::string> & requested_subprotocols,const URLRequestContext * context,const url::Origin & origin,const SiteForCookies & site_for_cookies,bool has_storage_access,const IsolationInfo & isolation_info,const HttpRequestHeaders & additional_headers,NetworkTrafficAnnotationTag traffic_annotation,std::unique_ptr<WebSocketStream::ConnectDelegate> connect_delegate,std::unique_ptr<WebSocketStreamRequestAPI> api_delegate)102 WebSocketStreamRequestImpl(
103 const GURL& url,
104 const std::vector<std::string>& requested_subprotocols,
105 const URLRequestContext* context,
106 const url::Origin& origin,
107 const SiteForCookies& site_for_cookies,
108 bool has_storage_access,
109 const IsolationInfo& isolation_info,
110 const HttpRequestHeaders& additional_headers,
111 NetworkTrafficAnnotationTag traffic_annotation,
112 std::unique_ptr<WebSocketStream::ConnectDelegate> connect_delegate,
113 std::unique_ptr<WebSocketStreamRequestAPI> api_delegate)
114 : delegate_(this),
115 connect_delegate_(std::move(connect_delegate)),
116 url_request_(context->CreateRequest(url,
117 DEFAULT_PRIORITY,
118 &delegate_,
119 traffic_annotation,
120 /*is_for_websockets=*/true)),
121 api_delegate_(std::move(api_delegate)) {
122 DCHECK_EQ(IsolationInfo::RequestType::kOther,
123 isolation_info.request_type());
124
125 HttpRequestHeaders headers = additional_headers;
126 headers.SetHeader(websockets::kUpgrade, websockets::kWebSocketLowercase);
127 headers.SetHeader(HttpRequestHeaders::kConnection, websockets::kUpgrade);
128 headers.SetHeader(HttpRequestHeaders::kOrigin, origin.Serialize());
129 headers.SetHeader(websockets::kSecWebSocketVersion,
130 websockets::kSupportedVersion);
131
132 // Remove HTTP headers that are important to websocket connections: they
133 // will be added later.
134 headers.RemoveHeader(websockets::kSecWebSocketExtensions);
135 headers.RemoveHeader(websockets::kSecWebSocketKey);
136 headers.RemoveHeader(websockets::kSecWebSocketProtocol);
137
138 url_request_->SetExtraRequestHeaders(headers);
139 url_request_->set_initiator(origin);
140 url_request_->set_site_for_cookies(site_for_cookies);
141 url_request_->set_has_storage_access(has_storage_access);
142 url_request_->set_isolation_info(isolation_info);
143
144 auto create_helper = std::make_unique<WebSocketHandshakeStreamCreateHelper>(
145 connect_delegate_.get(), requested_subprotocols, this);
146 url_request_->SetUserData(kWebSocketHandshakeUserDataKey,
147 std::move(create_helper));
148 url_request_->SetLoadFlags(LOAD_DISABLE_CACHE | LOAD_BYPASS_CACHE);
149 connect_delegate_->OnCreateRequest(url_request_.get());
150 }
151
152 // Destroying this object destroys the URLRequest, which cancels the request
153 // and so terminates the handshake if it is incomplete.
~WebSocketStreamRequestImpl()154 ~WebSocketStreamRequestImpl() override {
155 if (ws_upgrade_success_) {
156 CHECK(url_request_);
157 // "Cancel" the request with an error code indicating the upgrade
158 // succeeded.
159 url_request_->CancelWithError(ERR_WS_UPGRADE);
160 }
161 }
162
OnBasicHandshakeStreamCreated(WebSocketBasicHandshakeStream * handshake_stream)163 void OnBasicHandshakeStreamCreated(
164 WebSocketBasicHandshakeStream* handshake_stream) override {
165 if (api_delegate_) {
166 api_delegate_->OnBasicHandshakeStreamCreated(handshake_stream);
167 }
168 OnHandshakeStreamCreated(handshake_stream);
169 }
170
OnHttp2HandshakeStreamCreated(WebSocketHttp2HandshakeStream * handshake_stream)171 void OnHttp2HandshakeStreamCreated(
172 WebSocketHttp2HandshakeStream* handshake_stream) override {
173 if (api_delegate_) {
174 api_delegate_->OnHttp2HandshakeStreamCreated(handshake_stream);
175 }
176 OnHandshakeStreamCreated(handshake_stream);
177 }
178
OnHttp3HandshakeStreamCreated(WebSocketHttp3HandshakeStream * handshake_stream)179 void OnHttp3HandshakeStreamCreated(
180 WebSocketHttp3HandshakeStream* handshake_stream) override {
181 if (api_delegate_) {
182 api_delegate_->OnHttp3HandshakeStreamCreated(handshake_stream);
183 }
184 OnHandshakeStreamCreated(handshake_stream);
185 }
186
OnFailure(const std::string & message,int net_error,std::optional<int> response_code)187 void OnFailure(const std::string& message,
188 int net_error,
189 std::optional<int> response_code) override {
190 if (api_delegate_)
191 api_delegate_->OnFailure(message, net_error, response_code);
192 failure_message_ = message;
193 failure_net_error_ = net_error;
194 failure_response_code_ = response_code;
195 }
196
Start(std::unique_ptr<base::OneShotTimer> timer)197 void Start(std::unique_ptr<base::OneShotTimer> timer) {
198 DCHECK(timer);
199 base::TimeDelta timeout(base::Seconds(kHandshakeTimeoutIntervalInSeconds));
200 timer_ = std::move(timer);
201 timer_->Start(FROM_HERE, timeout,
202 base::BindOnce(&WebSocketStreamRequestImpl::OnTimeout,
203 base::Unretained(this)));
204 url_request_->Start();
205 }
206
PerformUpgrade()207 void PerformUpgrade() {
208 DCHECK(timer_);
209 DCHECK(connect_delegate_);
210
211 timer_->Stop();
212
213 if (!handshake_stream_) {
214 ReportFailureWithMessage(
215 "No handshake stream has been created or handshake stream is already "
216 "destroyed.",
217 ERR_FAILED, std::nullopt);
218 return;
219 }
220
221 if (!handshake_stream_->CanReadFromStream()) {
222 ReportFailureWithMessage("Handshake stream is not readable.",
223 ERR_CONNECTION_CLOSED, std::nullopt);
224 return;
225 }
226
227 ws_upgrade_success_ = true;
228 WebSocketHandshakeStreamBase* handshake_stream = handshake_stream_.get();
229 handshake_stream_.reset();
230 auto handshake_response_info =
231 std::make_unique<WebSocketHandshakeResponseInfo>(
232 url_request_->url(), url_request_->response_headers(),
233 url_request_->GetResponseRemoteEndpoint(),
234 url_request_->response_time());
235 connect_delegate_->OnSuccess(handshake_stream->Upgrade(),
236 std::move(handshake_response_info));
237 }
238
FailureMessageFromNetError(int net_error)239 std::string FailureMessageFromNetError(int net_error) {
240 if (net_error == ERR_TUNNEL_CONNECTION_FAILED) {
241 // This error is common and confusing, so special-case it.
242 // TODO(ricea): Include the HostPortPair of the selected proxy server in
243 // the error message. This is not currently possible because it isn't set
244 // in HttpResponseInfo when a ERR_TUNNEL_CONNECTION_FAILED error happens.
245 return "Establishing a tunnel via proxy server failed.";
246 } else {
247 return base::StrCat(
248 {"Error in connection establishment: ", ErrorToString(net_error)});
249 }
250 }
251
ReportFailure(int net_error,std::optional<int> response_code)252 void ReportFailure(int net_error, std::optional<int> response_code) {
253 DCHECK(timer_);
254 timer_->Stop();
255 if (failure_message_.empty()) {
256 switch (net_error) {
257 case OK:
258 case ERR_IO_PENDING:
259 break;
260 case ERR_ABORTED:
261 failure_message_ = "WebSocket opening handshake was canceled";
262 break;
263 case ERR_TIMED_OUT:
264 failure_message_ = "WebSocket opening handshake timed out";
265 break;
266 default:
267 failure_message_ = FailureMessageFromNetError(net_error);
268 break;
269 }
270 }
271
272 ReportFailureWithMessage(
273 failure_message_, failure_net_error_.value_or(net_error),
274 failure_response_code_ ? failure_response_code_ : response_code);
275 }
276
ReportFailureWithMessage(const std::string & failure_message,int net_error,std::optional<int> response_code)277 void ReportFailureWithMessage(const std::string& failure_message,
278 int net_error,
279 std::optional<int> response_code) {
280 connect_delegate_->OnFailure(failure_message, net_error, response_code);
281 }
282
connect_delegate() const283 WebSocketStream::ConnectDelegate* connect_delegate() const {
284 return connect_delegate_.get();
285 }
286
OnTimeout()287 void OnTimeout() {
288 url_request_->CancelWithError(ERR_TIMED_OUT);
289 }
290
291 private:
OnHandshakeStreamCreated(WebSocketHandshakeStreamBase * handshake_stream)292 void OnHandshakeStreamCreated(
293 WebSocketHandshakeStreamBase* handshake_stream) {
294 DCHECK(handshake_stream);
295
296 handshake_stream_ = handshake_stream->GetWeakPtr();
297 }
298
299 // |delegate_| needs to be declared before |url_request_| so that it gets
300 // initialised first and destroyed second.
301 Delegate delegate_;
302
303 std::unique_ptr<WebSocketStream::ConnectDelegate> connect_delegate_;
304
305 // Deleting the WebSocketStreamRequestImpl object deletes this URLRequest
306 // object, cancelling the whole connection. Must be destroyed before
307 // `delegate_`, since `url_request_` has a pointer to it, and before
308 // `connect_delegate_`, because there may be a pointer to it further down the
309 // stack.
310 const std::unique_ptr<URLRequest> url_request_;
311
312 // This is owned by the caller of
313 // WebsocketHandshakeStreamCreateHelper::CreateBasicStream() or
314 // CreateHttp2Stream() or CreateHttp3Stream(). Both the stream and this
315 // object will be destroyed during the destruction of the URLRequest object
316 // associated with the handshake. This is only guaranteed to be a valid
317 // pointer if the handshake succeeded.
318 base::WeakPtr<WebSocketHandshakeStreamBase> handshake_stream_;
319
320 // The failure information supplied by WebSocketBasicHandshakeStream, if any.
321 std::string failure_message_;
322 std::optional<int> failure_net_error_;
323 std::optional<int> failure_response_code_;
324
325 // A timer for handshake timeout.
326 std::unique_ptr<base::OneShotTimer> timer_;
327
328 // Set to true if the websocket upgrade succeeded.
329 bool ws_upgrade_success_ = false;
330
331 // A delegate for On*HandshakeCreated and OnFailure calls.
332 std::unique_ptr<WebSocketStreamRequestAPI> api_delegate_;
333 };
334
335 class SSLErrorCallbacks : public WebSocketEventInterface::SSLErrorCallbacks {
336 public:
SSLErrorCallbacks(URLRequest * url_request)337 explicit SSLErrorCallbacks(URLRequest* url_request)
338 : url_request_(url_request->GetWeakPtr()) {}
339
CancelSSLRequest(int error,const SSLInfo * ssl_info)340 void CancelSSLRequest(int error, const SSLInfo* ssl_info) override {
341 if (!url_request_)
342 return;
343
344 if (ssl_info) {
345 url_request_->CancelWithSSLError(error, *ssl_info);
346 } else {
347 url_request_->CancelWithError(error);
348 }
349 }
350
ContinueSSLRequest()351 void ContinueSSLRequest() override {
352 if (url_request_)
353 url_request_->ContinueDespiteLastError();
354 }
355
356 private:
357 base::WeakPtr<URLRequest> url_request_;
358 };
359
OnConnected(URLRequest * request,const TransportInfo & info,CompletionOnceCallback callback)360 int Delegate::OnConnected(URLRequest* request,
361 const TransportInfo& info,
362 CompletionOnceCallback callback) {
363 owner_->connect_delegate()->OnURLRequestConnected(request, info);
364 return OK;
365 }
366
OnReceivedRedirect(URLRequest * request,const RedirectInfo & redirect_info,bool * defer_redirect)367 void Delegate::OnReceivedRedirect(URLRequest* request,
368 const RedirectInfo& redirect_info,
369 bool* defer_redirect) {
370 // This code should never be reached for externally generated redirects,
371 // as WebSocketBasicHandshakeStream is responsible for filtering out
372 // all response codes besides 101, 401, and 407. As such, the URLRequest
373 // should never see a redirect sent over the network. However, internal
374 // redirects also result in this method being called, such as those
375 // caused by HSTS.
376 // Because it's security critical to prevent externally-generated
377 // redirects in WebSockets, perform additional checks to ensure this
378 // is only internal.
379 GURL::Replacements replacements;
380 replacements.SetSchemeStr("wss");
381 GURL expected_url = request->original_url().ReplaceComponents(replacements);
382 if (redirect_info.new_method != "GET" ||
383 redirect_info.new_url != expected_url) {
384 // This should not happen.
385 DLOG(FATAL) << "Unauthorized WebSocket redirect to "
386 << redirect_info.new_method << " "
387 << redirect_info.new_url.spec();
388 request->Cancel();
389 }
390 }
391
OnResponseStarted(URLRequest * request,int net_error)392 void Delegate::OnResponseStarted(URLRequest* request, int net_error) {
393 DCHECK_NE(ERR_IO_PENDING, net_error);
394
395 const bool is_http2 =
396 request->response_info().connection_info == HttpConnectionInfo::kHTTP2;
397
398 // All error codes, including OK and ABORTED, as with
399 // Net.ErrorCodesForMainFrame4
400 base::UmaHistogramSparse("Net.WebSocket.ErrorCodes", -net_error);
401 if (is_http2) {
402 base::UmaHistogramSparse("Net.WebSocket.ErrorCodes.Http2", -net_error);
403 }
404 if (net::IsLocalhost(request->url())) {
405 base::UmaHistogramSparse("Net.WebSocket.ErrorCodes_Localhost", -net_error);
406 } else {
407 base::UmaHistogramSparse("Net.WebSocket.ErrorCodes_NotLocalhost",
408 -net_error);
409 }
410
411 if (net_error != OK) {
412 DVLOG(3) << "OnResponseStarted (request failed)";
413 owner_->ReportFailure(net_error, std::nullopt);
414 return;
415 }
416 const int response_code = request->GetResponseCode();
417 DVLOG(3) << "OnResponseStarted (response code " << response_code << ")";
418
419 if (is_http2) {
420 if (response_code == HTTP_OK) {
421 owner_->PerformUpgrade();
422 return;
423 }
424
425 owner_->ReportFailure(net_error, std::nullopt);
426 return;
427 }
428
429 switch (response_code) {
430 case HTTP_SWITCHING_PROTOCOLS:
431 owner_->PerformUpgrade();
432 return;
433
434 case HTTP_UNAUTHORIZED:
435 owner_->ReportFailureWithMessage(
436 "HTTP Authentication failed; no valid credentials available",
437 net_error, response_code);
438 return;
439
440 case HTTP_PROXY_AUTHENTICATION_REQUIRED:
441 owner_->ReportFailureWithMessage("Proxy authentication failed", net_error,
442 response_code);
443 return;
444
445 default:
446 owner_->ReportFailure(net_error, response_code);
447 }
448 }
449
OnAuthRequired(URLRequest * request,const AuthChallengeInfo & auth_info)450 void Delegate::OnAuthRequired(URLRequest* request,
451 const AuthChallengeInfo& auth_info) {
452 std::optional<AuthCredentials> credentials;
453 // This base::Unretained(this) relies on an assumption that |callback| can
454 // be called called during the opening handshake.
455 int rv = owner_->connect_delegate()->OnAuthRequired(
456 auth_info, request->response_headers(),
457 request->GetResponseRemoteEndpoint(),
458 base::BindOnce(&Delegate::OnAuthRequiredComplete, base::Unretained(this),
459 request),
460 &credentials);
461 request->LogBlockedBy("WebSocketStream::Delegate::OnAuthRequired");
462 if (rv == ERR_IO_PENDING)
463 return;
464 if (rv != OK) {
465 request->LogUnblocked();
466 owner_->ReportFailure(rv, std::nullopt);
467 return;
468 }
469 OnAuthRequiredComplete(request, nullptr);
470 }
471
OnAuthRequiredComplete(URLRequest * request,const AuthCredentials * credentials)472 void Delegate::OnAuthRequiredComplete(URLRequest* request,
473 const AuthCredentials* credentials) {
474 request->LogUnblocked();
475 if (!credentials) {
476 request->CancelAuth();
477 return;
478 }
479 request->SetAuth(*credentials);
480 }
481
OnCertificateRequested(URLRequest * request,SSLCertRequestInfo * cert_request_info)482 void Delegate::OnCertificateRequested(URLRequest* request,
483 SSLCertRequestInfo* cert_request_info) {
484 // This method is called when a client certificate is requested, and the
485 // request context does not already contain a client certificate selection for
486 // the endpoint. In this case, a main frame resource request would pop-up UI
487 // to permit selection of a client certificate, but since WebSockets are
488 // sub-resources they should not pop-up UI and so there is nothing more we can
489 // do.
490 request->Cancel();
491 }
492
OnSSLCertificateError(URLRequest * request,int net_error,const SSLInfo & ssl_info,bool fatal)493 void Delegate::OnSSLCertificateError(URLRequest* request,
494 int net_error,
495 const SSLInfo& ssl_info,
496 bool fatal) {
497 owner_->connect_delegate()->OnSSLCertificateError(
498 std::make_unique<SSLErrorCallbacks>(request), net_error, ssl_info, fatal);
499 }
500
OnReadCompleted(URLRequest * request,int bytes_read)501 void Delegate::OnReadCompleted(URLRequest* request, int bytes_read) {
502 NOTREACHED();
503 }
504
505 } // namespace
506
507 WebSocketStreamRequest::~WebSocketStreamRequest() = default;
508
509 WebSocketStream::WebSocketStream() = default;
510 WebSocketStream::~WebSocketStream() = default;
511
512 WebSocketStream::ConnectDelegate::~ConnectDelegate() = default;
513
CreateAndConnectStream(const GURL & socket_url,const std::vector<std::string> & requested_subprotocols,const url::Origin & origin,const SiteForCookies & site_for_cookies,bool has_storage_access,const IsolationInfo & isolation_info,const HttpRequestHeaders & additional_headers,URLRequestContext * url_request_context,const NetLogWithSource & net_log,NetworkTrafficAnnotationTag traffic_annotation,std::unique_ptr<ConnectDelegate> connect_delegate)514 std::unique_ptr<WebSocketStreamRequest> WebSocketStream::CreateAndConnectStream(
515 const GURL& socket_url,
516 const std::vector<std::string>& requested_subprotocols,
517 const url::Origin& origin,
518 const SiteForCookies& site_for_cookies,
519 bool has_storage_access,
520 const IsolationInfo& isolation_info,
521 const HttpRequestHeaders& additional_headers,
522 URLRequestContext* url_request_context,
523 const NetLogWithSource& net_log,
524 NetworkTrafficAnnotationTag traffic_annotation,
525 std::unique_ptr<ConnectDelegate> connect_delegate) {
526 auto request = std::make_unique<WebSocketStreamRequestImpl>(
527 socket_url, requested_subprotocols, url_request_context, origin,
528 site_for_cookies, has_storage_access, isolation_info, additional_headers,
529 traffic_annotation, std::move(connect_delegate), nullptr);
530 request->Start(std::make_unique<base::OneShotTimer>());
531 return std::move(request);
532 }
533
534 std::unique_ptr<WebSocketStreamRequest>
CreateAndConnectStreamForTesting(const GURL & socket_url,const std::vector<std::string> & requested_subprotocols,const url::Origin & origin,const SiteForCookies & site_for_cookies,bool has_storage_access,const IsolationInfo & isolation_info,const HttpRequestHeaders & additional_headers,URLRequestContext * url_request_context,const NetLogWithSource & net_log,NetworkTrafficAnnotationTag traffic_annotation,std::unique_ptr<WebSocketStream::ConnectDelegate> connect_delegate,std::unique_ptr<base::OneShotTimer> timer,std::unique_ptr<WebSocketStreamRequestAPI> api_delegate)535 WebSocketStream::CreateAndConnectStreamForTesting(
536 const GURL& socket_url,
537 const std::vector<std::string>& requested_subprotocols,
538 const url::Origin& origin,
539 const SiteForCookies& site_for_cookies,
540 bool has_storage_access,
541 const IsolationInfo& isolation_info,
542 const HttpRequestHeaders& additional_headers,
543 URLRequestContext* url_request_context,
544 const NetLogWithSource& net_log,
545 NetworkTrafficAnnotationTag traffic_annotation,
546 std::unique_ptr<WebSocketStream::ConnectDelegate> connect_delegate,
547 std::unique_ptr<base::OneShotTimer> timer,
548 std::unique_ptr<WebSocketStreamRequestAPI> api_delegate) {
549 auto request = std::make_unique<WebSocketStreamRequestImpl>(
550 socket_url, requested_subprotocols, url_request_context, origin,
551 site_for_cookies, has_storage_access, isolation_info, additional_headers,
552 traffic_annotation, std::move(connect_delegate), std::move(api_delegate));
553 request->Start(std::move(timer));
554 return std::move(request);
555 }
556
557 } // namespace net
558