blob: 62ce35bb3e78921c6dab3f291fef3d3a7786cd92 [file] [log] [blame]
[email protected]473282d2013-07-02 11:57:071// Copyright 2013 The Chromium Authors. All rights reserved.
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
danakj9c5cab52016-04-16 00:54:337#include <memory>
dchengc7eeda422015-12-26 03:56:488#include <utility>
9
[email protected]473282d2013-07-02 11:57:0710#include "base/logging.h"
danakj9c5cab52016-04-16 00:54:3311#include "base/memory/ptr_util.h"
asvitkinec3c93722015-06-17 14:48:3712#include "base/metrics/histogram_macros.h"
[email protected]f7e98ca2014-06-19 12:05:4313#include "base/metrics/sparse_histogram.h"
ricea38fc268c2015-02-09 02:41:2914#include "base/strings/stringprintf.h"
yhirano569e09ce2014-09-11 12:07:2915#include "base/time/time.h"
16#include "base/timer/timer.h"
[email protected]d7599122014-05-24 03:37:2317#include "net/base/load_flags.h"
[email protected]efa9e732013-11-29 02:55:0518#include "net/http/http_request_headers.h"
[email protected]e69c1cd2014-07-29 07:42:2919#include "net/http/http_response_headers.h"
[email protected]efa9e732013-11-29 02:55:0520#include "net/http/http_status_code.h"
[email protected]cba24642014-08-15 20:49:5921#include "net/url_request/redirect_info.h"
[email protected]efa9e732013-11-29 02:55:0522#include "net/url_request/url_request.h"
23#include "net/url_request/url_request_context.h"
24#include "net/websockets/websocket_errors.h"
[email protected]a62449522014-06-05 11:11:1525#include "net/websockets/websocket_event_interface.h"
[email protected]efa9e732013-11-29 02:55:0526#include "net/websockets/websocket_handshake_constants.h"
27#include "net/websockets/websocket_handshake_stream_base.h"
28#include "net/websockets/websocket_handshake_stream_create_helper.h"
[email protected]efa9e732013-11-29 02:55:0529#include "url/gurl.h"
mkwst4997ce82015-07-25 12:00:0530#include "url/origin.h"
[email protected]473282d2013-07-02 11:57:0731
32namespace net {
[email protected]efa9e732013-11-29 02:55:0533namespace {
34
yhirano569e09ce2014-09-11 12:07:2935// The timeout duration of WebSocket handshake.
36// It is defined as the same value as the TCP connection timeout value in
37// net/socket/websocket_transport_client_socket_pool.cc to make it hard for
38// JavaScript programs to recognize the timeout cause.
39const int kHandshakeTimeoutIntervalInSeconds = 240;
40
tyoshinoccfcfde2016-07-21 14:06:5541class WebSocketStreamRequestImpl;
[email protected]efa9e732013-11-29 02:55:0542
43class Delegate : public URLRequest::Delegate {
44 public:
[email protected]1e0a16a2014-04-04 16:18:0445 enum HandshakeResult {
46 INCOMPLETE,
47 CONNECTED,
48 FAILED,
49 NUM_HANDSHAKE_RESULT_TYPES,
50 };
51
tyoshinoccfcfde2016-07-21 14:06:5552 explicit Delegate(WebSocketStreamRequestImpl* owner)
[email protected]1e0a16a2014-04-04 16:18:0453 : owner_(owner), result_(INCOMPLETE) {}
dchengb03027d2014-10-21 12:00:2054 ~Delegate() override {
[email protected]1e0a16a2014-04-04 16:18:0455 UMA_HISTOGRAM_ENUMERATION(
56 "Net.WebSocket.HandshakeResult", result_, NUM_HANDSHAKE_RESULT_TYPES);
57 }
[email protected]efa9e732013-11-29 02:55:0558
59 // Implementation of URLRequest::Delegate methods.
dchengb03027d2014-10-21 12:00:2060 void OnReceivedRedirect(URLRequest* request,
61 const RedirectInfo& redirect_info,
Adam Ricecb76ac62015-02-20 05:33:2562 bool* defer_redirect) override;
[email protected]a62449522014-06-05 11:11:1563
mmenkefe5d0b112016-09-06 20:46:5864 void OnResponseStarted(URLRequest* request) override;
[email protected]efa9e732013-11-29 02:55:0565
dchengb03027d2014-10-21 12:00:2066 void OnAuthRequired(URLRequest* request,
67 AuthChallengeInfo* auth_info) override;
[email protected]efa9e732013-11-29 02:55:0568
dchengb03027d2014-10-21 12:00:2069 void OnCertificateRequested(URLRequest* request,
70 SSLCertRequestInfo* cert_request_info) override;
[email protected]efa9e732013-11-29 02:55:0571
dchengb03027d2014-10-21 12:00:2072 void OnSSLCertificateError(URLRequest* request,
73 const SSLInfo& ssl_info,
74 bool fatal) override;
[email protected]efa9e732013-11-29 02:55:0575
dchengb03027d2014-10-21 12:00:2076 void OnReadCompleted(URLRequest* request, int bytes_read) override;
[email protected]efa9e732013-11-29 02:55:0577
78 private:
tyoshinoccfcfde2016-07-21 14:06:5579 WebSocketStreamRequestImpl* owner_;
[email protected]1e0a16a2014-04-04 16:18:0480 HandshakeResult result_;
[email protected]efa9e732013-11-29 02:55:0581};
82
tyoshinoccfcfde2016-07-21 14:06:5583class WebSocketStreamRequestImpl : public WebSocketStreamRequest {
[email protected]efa9e732013-11-29 02:55:0584 public:
tyoshinoccfcfde2016-07-21 14:06:5585 WebSocketStreamRequestImpl(
[email protected]efa9e732013-11-29 02:55:0586 const GURL& url,
87 const URLRequestContext* context,
mkwst4997ce82015-07-25 12:00:0588 const url::Origin& origin,
tyoshino8572d572016-07-13 06:29:4889 const GURL& first_party_for_cookies,
alladacef397d2016-06-29 17:52:2390 const std::string& additional_headers,
danakj9c5cab52016-04-16 00:54:3391 std::unique_ptr<WebSocketStream::ConnectDelegate> connect_delegate,
92 std::unique_ptr<WebSocketHandshakeStreamCreateHelper> create_helper)
[email protected]efa9e732013-11-29 02:55:0593 : delegate_(new Delegate(this)),
davidben151423e2015-03-23 18:48:3694 url_request_(
95 context->CreateRequest(url, DEFAULT_PRIORITY, delegate_.get())),
dchengc7eeda422015-12-26 03:56:4896 connect_delegate_(std::move(connect_delegate)),
tyoshinoccfcfde2016-07-21 14:06:5597 handshake_stream_create_helper_(create_helper.release()),
98 handshake_stream_(nullptr) {
99 handshake_stream_create_helper_->set_stream_request(this);
[email protected]490e38f42014-05-26 23:44:16100 HttpRequestHeaders headers;
101 headers.SetHeader(websockets::kUpgrade, websockets::kWebSocketLowercase);
102 headers.SetHeader(HttpRequestHeaders::kConnection, websockets::kUpgrade);
mkwst4997ce82015-07-25 12:00:05103 headers.SetHeader(HttpRequestHeaders::kOrigin, origin.Serialize());
[email protected]490e38f42014-05-26 23:44:16104 headers.SetHeader(websockets::kSecWebSocketVersion,
105 websockets::kSupportedVersion);
alladacef397d2016-06-29 17:52:23106
107 headers.AddHeadersFromString(additional_headers);
108
[email protected]f7022f32014-08-21 16:32:19109 url_request_->SetExtraRequestHeaders(headers);
tyoshino8572d572016-07-13 06:29:48110 url_request_->set_initiator(origin);
111 url_request_->set_first_party_for_cookies(first_party_for_cookies);
[email protected]490e38f42014-05-26 23:44:16112
tyoshinoccfcfde2016-07-21 14:06:55113 // This passes the ownership of |handshake_stream_create_helper_| to
114 // |url_request_|.
[email protected]f7022f32014-08-21 16:32:19115 url_request_->SetUserData(
[email protected]490e38f42014-05-26 23:44:16116 WebSocketHandshakeStreamBase::CreateHelper::DataKey(),
tyoshinoccfcfde2016-07-21 14:06:55117 handshake_stream_create_helper_);
baranovich1b32ffb2015-01-15 14:44:52118 url_request_->SetLoadFlags(LOAD_DISABLE_CACHE | LOAD_BYPASS_CACHE);
[email protected]490e38f42014-05-26 23:44:16119 }
[email protected]efa9e732013-11-29 02:55:05120
121 // Destroying this object destroys the URLRequest, which cancels the request
122 // and so terminates the handshake if it is incomplete.
tyoshinoccfcfde2016-07-21 14:06:55123 ~WebSocketStreamRequestImpl() override {}
124
125 void OnHandshakeStreamCreated(
126 WebSocketHandshakeStreamBase* handshake_stream) override {
127 handshake_stream_ = handshake_stream;
128 }
129
130 void OnFailure(const std::string& message) override {
131 failure_message_ = message;
132 }
[email protected]efa9e732013-11-29 02:55:05133
danakj9c5cab52016-04-16 00:54:33134 void Start(std::unique_ptr<base::Timer> timer) {
yhirano569e09ce2014-09-11 12:07:29135 DCHECK(timer);
brettwbc44c0a92015-02-20 22:30:39136 base::TimeDelta timeout(base::TimeDelta::FromSeconds(
yhirano569e09ce2014-09-11 12:07:29137 kHandshakeTimeoutIntervalInSeconds));
dchengc7eeda422015-12-26 03:56:48138 timer_ = std::move(timer);
yhirano569e09ce2014-09-11 12:07:29139 timer_->Start(FROM_HERE, timeout,
tyoshinoccfcfde2016-07-21 14:06:55140 base::Bind(&WebSocketStreamRequestImpl::OnTimeout,
yhirano569e09ce2014-09-11 12:07:29141 base::Unretained(this)));
[email protected]f7022f32014-08-21 16:32:19142 url_request_->Start();
[email protected]490e38f42014-05-26 23:44:16143 }
[email protected]efa9e732013-11-29 02:55:05144
145 void PerformUpgrade() {
yhirano569e09ce2014-09-11 12:07:29146 DCHECK(timer_);
tyoshinoccfcfde2016-07-21 14:06:55147 DCHECK(handshake_stream_);
148
yhirano569e09ce2014-09-11 12:07:29149 timer_->Stop();
tyoshinoccfcfde2016-07-21 14:06:55150
151 WebSocketHandshakeStreamBase* handshake_stream = handshake_stream_;
152 handshake_stream_ = nullptr;
153 connect_delegate_->OnSuccess(handshake_stream->Upgrade());
[email protected]efa9e732013-11-29 02:55:05154 }
155
mmenkefe5d0b112016-09-06 20:46:58156 std::string FailureMessageFromNetError() {
157 int error = url_request_->status().error();
158 if (error == ERR_TUNNEL_CONNECTION_FAILED) {
ricea38fc268c2015-02-09 02:41:29159 // This error is common and confusing, so special-case it.
160 // TODO(ricea): Include the HostPortPair of the selected proxy server in
161 // the error message. This is not currently possible because it isn't set
162 // in HttpResponseInfo when a ERR_TUNNEL_CONNECTION_FAILED error happens.
163 return "Establishing a tunnel via proxy server failed.";
164 } else {
165 return std::string("Error in connection establishment: ") +
mmenkefe5d0b112016-09-06 20:46:58166 ErrorToString(url_request_->status().error());
ricea38fc268c2015-02-09 02:41:29167 }
168 }
169
mmenkefe5d0b112016-09-06 20:46:58170 void ReportFailure() {
yhirano569e09ce2014-09-11 12:07:29171 DCHECK(timer_);
172 timer_->Stop();
[email protected]8aba0172014-07-03 12:09:53173 if (failure_message_.empty()) {
mmenkefe5d0b112016-09-06 20:46:58174 switch (url_request_->status().status()) {
175 case URLRequestStatus::SUCCESS:
176 case URLRequestStatus::IO_PENDING:
[email protected]96868202014-01-09 10:38:04177 break;
mmenkefe5d0b112016-09-06 20:46:58178 case URLRequestStatus::CANCELED:
179 if (url_request_->status().error() == ERR_TIMED_OUT)
180 failure_message_ = "WebSocket opening handshake timed out";
181 else
182 failure_message_ = "WebSocket opening handshake was canceled";
[email protected]96868202014-01-09 10:38:04183 break;
mmenkefe5d0b112016-09-06 20:46:58184 case URLRequestStatus::FAILED:
185 failure_message_ = FailureMessageFromNetError();
[email protected]96868202014-01-09 10:38:04186 break;
187 }
188 }
[email protected]e69c1cd2014-07-29 07:42:29189 ReportFailureWithMessage(failure_message_);
190 }
191
192 void ReportFailureWithMessage(const std::string& failure_message) {
193 connect_delegate_->OnFailure(failure_message);
194 }
195
196 void OnFinishOpeningHandshake() {
197 WebSocketDispatchOnFinishOpeningHandshake(connect_delegate(),
[email protected]f7022f32014-08-21 16:32:19198 url_request_->url(),
199 url_request_->response_headers(),
200 url_request_->response_time());
[email protected]efa9e732013-11-29 02:55:05201 }
202
[email protected]a62449522014-06-05 11:11:15203 WebSocketStream::ConnectDelegate* connect_delegate() const {
204 return connect_delegate_.get();
205 }
206
yhirano569e09ce2014-09-11 12:07:29207 void OnTimeout() {
208 url_request_->CancelWithError(ERR_TIMED_OUT);
209 }
210
[email protected]efa9e732013-11-29 02:55:05211 private:
212 // |delegate_| needs to be declared before |url_request_| so that it gets
213 // initialised first.
danakj9c5cab52016-04-16 00:54:33214 std::unique_ptr<Delegate> delegate_;
[email protected]efa9e732013-11-29 02:55:05215
tyoshinoccfcfde2016-07-21 14:06:55216 // Deleting the WebSocketStreamRequestImpl object deletes this URLRequest
217 // object, cancelling the whole connection.
danakj9c5cab52016-04-16 00:54:33218 std::unique_ptr<URLRequest> url_request_;
[email protected]efa9e732013-11-29 02:55:05219
danakj9c5cab52016-04-16 00:54:33220 std::unique_ptr<WebSocketStream::ConnectDelegate> connect_delegate_;
[email protected]efa9e732013-11-29 02:55:05221
222 // Owned by the URLRequest.
tyoshinoccfcfde2016-07-21 14:06:55223 WebSocketHandshakeStreamCreateHelper* handshake_stream_create_helper_;
224
225 // This is owned by the caller of CreateBaseStream() or
226 // CreateSpdyStream() of WebsocketHandshakeStreamCreateHelper. Both the
227 // stream and this object will be destroyed during the destruction of the
228 // URLRequest object associated with the handshake. This is only guaranteed
229 // to be a valid pointer if the handshake succeeded.
230 WebSocketHandshakeStreamBase* handshake_stream_;
[email protected]8aba0172014-07-03 12:09:53231
232 // The failure message supplied by WebSocketBasicHandshakeStream, if any.
233 std::string failure_message_;
yhirano569e09ce2014-09-11 12:07:29234
235 // A timer for handshake timeout.
danakj9c5cab52016-04-16 00:54:33236 std::unique_ptr<base::Timer> timer_;
[email protected]efa9e732013-11-29 02:55:05237};
238
[email protected]a62449522014-06-05 11:11:15239class SSLErrorCallbacks : public WebSocketEventInterface::SSLErrorCallbacks {
240 public:
241 explicit SSLErrorCallbacks(URLRequest* url_request)
242 : url_request_(url_request) {}
243
dchengb03027d2014-10-21 12:00:20244 void CancelSSLRequest(int error, const SSLInfo* ssl_info) override {
[email protected]a62449522014-06-05 11:11:15245 if (ssl_info) {
246 url_request_->CancelWithSSLError(error, *ssl_info);
247 } else {
248 url_request_->CancelWithError(error);
249 }
250 }
251
dchengb03027d2014-10-21 12:00:20252 void ContinueSSLRequest() override {
[email protected]a62449522014-06-05 11:11:15253 url_request_->ContinueDespiteLastError();
254 }
255
256 private:
257 URLRequest* url_request_;
258};
259
Adam Ricecb76ac62015-02-20 05:33:25260void Delegate::OnReceivedRedirect(URLRequest* request,
261 const RedirectInfo& redirect_info,
262 bool* defer_redirect) {
263 // This code should never be reached for externally generated redirects,
264 // as WebSocketBasicHandshakeStream is responsible for filtering out
265 // all response codes besides 101, 401, and 407. As such, the URLRequest
266 // should never see a redirect sent over the network. However, internal
267 // redirects also result in this method being called, such as those
268 // caused by HSTS.
269 // Because it's security critical to prevent externally-generated
270 // redirects in WebSockets, perform additional checks to ensure this
271 // is only internal.
272 GURL::Replacements replacements;
273 replacements.SetSchemeStr("wss");
274 GURL expected_url = request->original_url().ReplaceComponents(replacements);
275 if (redirect_info.new_method != "GET" ||
276 redirect_info.new_url != expected_url) {
277 // This should not happen.
278 DLOG(FATAL) << "Unauthorized WebSocket redirect to "
279 << redirect_info.new_method << " "
280 << redirect_info.new_url.spec();
281 request->Cancel();
282 }
283}
284
mmenkefe5d0b112016-09-06 20:46:58285void Delegate::OnResponseStarted(URLRequest* request) {
[email protected]f7e98ca2014-06-19 12:05:43286 // All error codes, including OK and ABORTED, as with
287 // Net.ErrorCodesForMainFrame3
mmenkefe5d0b112016-09-06 20:46:58288 UMA_HISTOGRAM_SPARSE_SLOWLY("Net.WebSocket.ErrorCodes",
289 -request->status().error());
290 if (!request->status().is_success()) {
[email protected]a62449522014-06-05 11:11:15291 DVLOG(3) << "OnResponseStarted (request failed)";
mmenkefe5d0b112016-09-06 20:46:58292 owner_->ReportFailure();
[email protected]a62449522014-06-05 11:11:15293 return;
294 }
[email protected]f7e98ca2014-06-19 12:05:43295 const int response_code = request->GetResponseCode();
296 DVLOG(3) << "OnResponseStarted (response code " << response_code << ")";
297 switch (response_code) {
[email protected]efa9e732013-11-29 02:55:05298 case HTTP_SWITCHING_PROTOCOLS:
[email protected]1e0a16a2014-04-04 16:18:04299 result_ = CONNECTED;
[email protected]efa9e732013-11-29 02:55:05300 owner_->PerformUpgrade();
301 return;
302
303 case HTTP_UNAUTHORIZED:
[email protected]e69c1cd2014-07-29 07:42:29304 result_ = FAILED;
305 owner_->OnFinishOpeningHandshake();
306 owner_->ReportFailureWithMessage(
307 "HTTP Authentication failed; no valid credentials available");
308 return;
309
[email protected]efa9e732013-11-29 02:55:05310 case HTTP_PROXY_AUTHENTICATION_REQUIRED:
[email protected]e69c1cd2014-07-29 07:42:29311 result_ = FAILED;
312 owner_->OnFinishOpeningHandshake();
313 owner_->ReportFailureWithMessage("Proxy authentication failed");
[email protected]efa9e732013-11-29 02:55:05314 return;
315
316 default:
[email protected]1e0a16a2014-04-04 16:18:04317 result_ = FAILED;
mmenkefe5d0b112016-09-06 20:46:58318 owner_->ReportFailure();
[email protected]efa9e732013-11-29 02:55:05319 }
320}
321
322void Delegate::OnAuthRequired(URLRequest* request,
323 AuthChallengeInfo* auth_info) {
[email protected]a62449522014-06-05 11:11:15324 // This should only be called if credentials are not already stored.
[email protected]efa9e732013-11-29 02:55:05325 request->CancelAuth();
326}
327
328void Delegate::OnCertificateRequested(URLRequest* request,
329 SSLCertRequestInfo* cert_request_info) {
[email protected]a62449522014-06-05 11:11:15330 // This method is called when a client certificate is requested, and the
331 // request context does not already contain a client certificate selection for
332 // the endpoint. In this case, a main frame resource request would pop-up UI
333 // to permit selection of a client certificate, but since WebSockets are
334 // sub-resources they should not pop-up UI and so there is nothing more we can
335 // do.
336 request->Cancel();
[email protected]efa9e732013-11-29 02:55:05337}
338
339void Delegate::OnSSLCertificateError(URLRequest* request,
340 const SSLInfo& ssl_info,
341 bool fatal) {
[email protected]a62449522014-06-05 11:11:15342 owner_->connect_delegate()->OnSSLCertificateError(
danakj9c5cab52016-04-16 00:54:33343 std::unique_ptr<WebSocketEventInterface::SSLErrorCallbacks>(
[email protected]a62449522014-06-05 11:11:15344 new SSLErrorCallbacks(request)),
danakj9c5cab52016-04-16 00:54:33345 ssl_info, fatal);
[email protected]efa9e732013-11-29 02:55:05346}
347
348void Delegate::OnReadCompleted(URLRequest* request, int bytes_read) {
349 NOTREACHED();
350}
351
[email protected]efa9e732013-11-29 02:55:05352} // namespace
[email protected]473282d2013-07-02 11:57:07353
354WebSocketStreamRequest::~WebSocketStreamRequest() {}
355
356WebSocketStream::WebSocketStream() {}
357WebSocketStream::~WebSocketStream() {}
358
359WebSocketStream::ConnectDelegate::~ConnectDelegate() {}
360
danakj9c5cab52016-04-16 00:54:33361std::unique_ptr<WebSocketStreamRequest> WebSocketStream::CreateAndConnectStream(
[email protected]473282d2013-07-02 11:57:07362 const GURL& socket_url,
tyoshinoccfcfde2016-07-21 14:06:55363 std::unique_ptr<WebSocketHandshakeStreamCreateHelper> create_helper,
mkwst4997ce82015-07-25 12:00:05364 const url::Origin& origin,
tyoshino8572d572016-07-13 06:29:48365 const GURL& first_party_for_cookies,
alladacef397d2016-06-29 17:52:23366 const std::string& additional_headers,
[email protected]473282d2013-07-02 11:57:07367 URLRequestContext* url_request_context,
368 const BoundNetLog& net_log,
danakj9c5cab52016-04-16 00:54:33369 std::unique_ptr<ConnectDelegate> connect_delegate) {
tyoshinoccfcfde2016-07-21 14:06:55370 std::unique_ptr<WebSocketStreamRequestImpl> request(
371 new WebSocketStreamRequestImpl(
372 socket_url, url_request_context, origin, first_party_for_cookies,
373 additional_headers, std::move(connect_delegate),
374 std::move(create_helper)));
danakj9c5cab52016-04-16 00:54:33375 request->Start(std::unique_ptr<base::Timer>(new base::Timer(false, false)));
dchengc7eeda422015-12-26 03:56:48376 return std::move(request);
[email protected]efa9e732013-11-29 02:55:05377}
378
tyoshinoccfcfde2016-07-21 14:06:55379std::unique_ptr<WebSocketStreamRequest>
380WebSocketStream::CreateAndConnectStreamForTesting(
[email protected]7824cf82014-03-13 10:22:57381 const GURL& socket_url,
danakj9c5cab52016-04-16 00:54:33382 std::unique_ptr<WebSocketHandshakeStreamCreateHelper> create_helper,
mkwst4997ce82015-07-25 12:00:05383 const url::Origin& origin,
tyoshino8572d572016-07-13 06:29:48384 const GURL& first_party_for_cookies,
alladacef397d2016-06-29 17:52:23385 const std::string& additional_headers,
[email protected]7824cf82014-03-13 10:22:57386 URLRequestContext* url_request_context,
387 const BoundNetLog& net_log,
danakj9c5cab52016-04-16 00:54:33388 std::unique_ptr<WebSocketStream::ConnectDelegate> connect_delegate,
389 std::unique_ptr<base::Timer> timer) {
tyoshinoccfcfde2016-07-21 14:06:55390 std::unique_ptr<WebSocketStreamRequestImpl> request(
391 new WebSocketStreamRequestImpl(
392 socket_url, url_request_context, origin, first_party_for_cookies,
393 additional_headers, std::move(connect_delegate),
394 std::move(create_helper)));
dchengc7eeda422015-12-26 03:56:48395 request->Start(std::move(timer));
396 return std::move(request);
[email protected]473282d2013-07-02 11:57:07397}
398
[email protected]e69c1cd2014-07-29 07:42:29399void WebSocketDispatchOnFinishOpeningHandshake(
400 WebSocketStream::ConnectDelegate* connect_delegate,
401 const GURL& url,
402 const scoped_refptr<HttpResponseHeaders>& headers,
403 base::Time response_time) {
404 DCHECK(connect_delegate);
dchengb206dc412014-08-26 19:46:23405 if (headers.get()) {
danakj9c5cab52016-04-16 00:54:33406 connect_delegate->OnFinishOpeningHandshake(
407 base::WrapUnique(new WebSocketHandshakeResponseInfo(
408 url, headers->response_code(), headers->GetStatusText(), headers,
409 response_time)));
[email protected]e69c1cd2014-07-29 07:42:29410 }
411}
412
[email protected]473282d2013-07-02 11:57:07413} // namespace net