1 // 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.
5 #include "net/websockets/websocket_stream.h"
7 #include "base/logging.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/metrics/histogram.h"
10 #include "base/metrics/sparse_histogram.h"
11 #include "base/profiler/scoped_tracker.h"
12 #include "base/time/time.h"
13 #include "base/timer/timer.h"
14 #include "net/base/load_flags.h"
15 #include "net/http/http_request_headers.h"
16 #include "net/http/http_response_headers.h"
17 #include "net/http/http_status_code.h"
18 #include "net/url_request/redirect_info.h"
19 #include "net/url_request/url_request.h"
20 #include "net/url_request/url_request_context.h"
21 #include "net/websockets/websocket_errors.h"
22 #include "net/websockets/websocket_event_interface.h"
23 #include "net/websockets/websocket_handshake_constants.h"
24 #include "net/websockets/websocket_handshake_stream_base.h"
25 #include "net/websockets/websocket_handshake_stream_create_helper.h"
26 #include "net/websockets/websocket_test_util.h"
28 #include "url/origin.h"
33 // The timeout duration of WebSocket handshake.
34 // It is defined as the same value as the TCP connection timeout value in
35 // net/socket/websocket_transport_client_socket_pool.cc to make it hard for
36 // JavaScript programs to recognize the timeout cause.
37 const int kHandshakeTimeoutIntervalInSeconds
= 240;
39 class StreamRequestImpl
;
41 class Delegate
: public URLRequest::Delegate
{
43 enum HandshakeResult
{
47 NUM_HANDSHAKE_RESULT_TYPES
,
50 explicit Delegate(StreamRequestImpl
* owner
)
51 : owner_(owner
), result_(INCOMPLETE
) {}
52 ~Delegate() override
{
53 UMA_HISTOGRAM_ENUMERATION(
54 "Net.WebSocket.HandshakeResult", result_
, NUM_HANDSHAKE_RESULT_TYPES
);
57 // Implementation of URLRequest::Delegate methods.
58 void OnReceivedRedirect(URLRequest
* request
,
59 const RedirectInfo
& redirect_info
,
60 bool* defer_redirect
) override
{
61 // HTTP status codes returned by HttpStreamParser are filtered by
62 // WebSocketBasicHandshakeStream, and only 101, 401 and 407 are permitted
63 // back up the stack to HttpNetworkTransaction. In particular, redirect
64 // codes are never allowed, and so URLRequest never sees a redirect on a
69 void OnResponseStarted(URLRequest
* request
) override
;
71 void OnAuthRequired(URLRequest
* request
,
72 AuthChallengeInfo
* auth_info
) override
;
74 void OnCertificateRequested(URLRequest
* request
,
75 SSLCertRequestInfo
* cert_request_info
) override
;
77 void OnSSLCertificateError(URLRequest
* request
,
78 const SSLInfo
& ssl_info
,
81 void OnReadCompleted(URLRequest
* request
, int bytes_read
) override
;
84 StreamRequestImpl
* owner_
;
85 HandshakeResult result_
;
88 class StreamRequestImpl
: public WebSocketStreamRequest
{
92 const URLRequestContext
* context
,
93 const url::Origin
& origin
,
94 scoped_ptr
<WebSocketStream::ConnectDelegate
> connect_delegate
,
95 scoped_ptr
<WebSocketHandshakeStreamCreateHelper
> create_helper
)
96 : delegate_(new Delegate(this)),
97 url_request_(context
->CreateRequest(url
, DEFAULT_PRIORITY
,
98 delegate_
.get(), NULL
)),
99 connect_delegate_(connect_delegate
.Pass()),
100 create_helper_(create_helper
.release()) {
101 create_helper_
->set_failure_message(&failure_message_
);
102 HttpRequestHeaders headers
;
103 headers
.SetHeader(websockets::kUpgrade
, websockets::kWebSocketLowercase
);
104 headers
.SetHeader(HttpRequestHeaders::kConnection
, websockets::kUpgrade
);
105 headers
.SetHeader(HttpRequestHeaders::kOrigin
, origin
.string());
106 headers
.SetHeader(websockets::kSecWebSocketVersion
,
107 websockets::kSupportedVersion
);
108 url_request_
->SetExtraRequestHeaders(headers
);
110 // This passes the ownership of |create_helper_| to |url_request_|.
111 url_request_
->SetUserData(
112 WebSocketHandshakeStreamBase::CreateHelper::DataKey(),
114 url_request_
->SetLoadFlags(LOAD_DISABLE_CACHE
|
116 LOAD_DO_NOT_PROMPT_FOR_LOGIN
);
119 // Destroying this object destroys the URLRequest, which cancels the request
120 // and so terminates the handshake if it is incomplete.
121 ~StreamRequestImpl() override
{}
123 void Start(scoped_ptr
<base::Timer
> timer
) {
125 TimeDelta
timeout(TimeDelta::FromSeconds(
126 kHandshakeTimeoutIntervalInSeconds
));
127 timer_
= timer
.Pass();
128 timer_
->Start(FROM_HERE
, timeout
,
129 base::Bind(&StreamRequestImpl::OnTimeout
,
130 base::Unretained(this)));
131 url_request_
->Start();
134 void PerformUpgrade() {
137 connect_delegate_
->OnSuccess(create_helper_
->Upgrade());
140 void ReportFailure() {
143 if (failure_message_
.empty()) {
144 switch (url_request_
->status().status()) {
145 case URLRequestStatus::SUCCESS
:
146 case URLRequestStatus::IO_PENDING
:
148 case URLRequestStatus::CANCELED
:
149 if (url_request_
->status().error() == ERR_TIMED_OUT
)
150 failure_message_
= "WebSocket opening handshake timed out";
152 failure_message_
= "WebSocket opening handshake was canceled";
154 case URLRequestStatus::FAILED
:
156 std::string("Error in connection establishment: ") +
157 ErrorToString(url_request_
->status().error());
161 ReportFailureWithMessage(failure_message_
);
164 void ReportFailureWithMessage(const std::string
& failure_message
) {
165 connect_delegate_
->OnFailure(failure_message
);
168 void OnFinishOpeningHandshake() {
169 WebSocketDispatchOnFinishOpeningHandshake(connect_delegate(),
171 url_request_
->response_headers(),
172 url_request_
->response_time());
175 WebSocketStream::ConnectDelegate
* connect_delegate() const {
176 return connect_delegate_
.get();
180 url_request_
->CancelWithError(ERR_TIMED_OUT
);
184 // |delegate_| needs to be declared before |url_request_| so that it gets
185 // initialised first.
186 scoped_ptr
<Delegate
> delegate_
;
188 // Deleting the StreamRequestImpl object deletes this URLRequest object,
189 // cancelling the whole connection.
190 scoped_ptr
<URLRequest
> url_request_
;
192 scoped_ptr
<WebSocketStream::ConnectDelegate
> connect_delegate_
;
194 // Owned by the URLRequest.
195 WebSocketHandshakeStreamCreateHelper
* create_helper_
;
197 // The failure message supplied by WebSocketBasicHandshakeStream, if any.
198 std::string failure_message_
;
200 // A timer for handshake timeout.
201 scoped_ptr
<base::Timer
> timer_
;
204 class SSLErrorCallbacks
: public WebSocketEventInterface::SSLErrorCallbacks
{
206 explicit SSLErrorCallbacks(URLRequest
* url_request
)
207 : url_request_(url_request
) {}
209 void CancelSSLRequest(int error
, const SSLInfo
* ssl_info
) override
{
211 url_request_
->CancelWithSSLError(error
, *ssl_info
);
213 url_request_
->CancelWithError(error
);
217 void ContinueSSLRequest() override
{
218 url_request_
->ContinueDespiteLastError();
222 URLRequest
* url_request_
;
225 void Delegate::OnResponseStarted(URLRequest
* request
) {
226 // TODO(vadimt): Remove ScopedTracker below once crbug.com/423948 is fixed.
227 tracked_objects::ScopedTracker
tracking_profile(
228 FROM_HERE_WITH_EXPLICIT_FUNCTION("423948 Delegate::OnResponseStarted"));
230 // All error codes, including OK and ABORTED, as with
231 // Net.ErrorCodesForMainFrame3
232 UMA_HISTOGRAM_SPARSE_SLOWLY("Net.WebSocket.ErrorCodes",
233 -request
->status().error());
234 if (!request
->status().is_success()) {
235 DVLOG(3) << "OnResponseStarted (request failed)";
236 owner_
->ReportFailure();
239 const int response_code
= request
->GetResponseCode();
240 DVLOG(3) << "OnResponseStarted (response code " << response_code
<< ")";
241 switch (response_code
) {
242 case HTTP_SWITCHING_PROTOCOLS
:
244 owner_
->PerformUpgrade();
247 case HTTP_UNAUTHORIZED
:
249 owner_
->OnFinishOpeningHandshake();
250 owner_
->ReportFailureWithMessage(
251 "HTTP Authentication failed; no valid credentials available");
254 case HTTP_PROXY_AUTHENTICATION_REQUIRED
:
256 owner_
->OnFinishOpeningHandshake();
257 owner_
->ReportFailureWithMessage("Proxy authentication failed");
262 owner_
->ReportFailure();
266 void Delegate::OnAuthRequired(URLRequest
* request
,
267 AuthChallengeInfo
* auth_info
) {
268 // This should only be called if credentials are not already stored.
269 request
->CancelAuth();
272 void Delegate::OnCertificateRequested(URLRequest
* request
,
273 SSLCertRequestInfo
* cert_request_info
) {
274 // This method is called when a client certificate is requested, and the
275 // request context does not already contain a client certificate selection for
276 // the endpoint. In this case, a main frame resource request would pop-up UI
277 // to permit selection of a client certificate, but since WebSockets are
278 // sub-resources they should not pop-up UI and so there is nothing more we can
283 void Delegate::OnSSLCertificateError(URLRequest
* request
,
284 const SSLInfo
& ssl_info
,
286 owner_
->connect_delegate()->OnSSLCertificateError(
287 scoped_ptr
<WebSocketEventInterface::SSLErrorCallbacks
>(
288 new SSLErrorCallbacks(request
)),
293 void Delegate::OnReadCompleted(URLRequest
* request
, int bytes_read
) {
299 WebSocketStreamRequest::~WebSocketStreamRequest() {}
301 WebSocketStream::WebSocketStream() {}
302 WebSocketStream::~WebSocketStream() {}
304 WebSocketStream::ConnectDelegate::~ConnectDelegate() {}
306 scoped_ptr
<WebSocketStreamRequest
> WebSocketStream::CreateAndConnectStream(
307 const GURL
& socket_url
,
308 const std::vector
<std::string
>& requested_subprotocols
,
309 const url::Origin
& origin
,
310 URLRequestContext
* url_request_context
,
311 const BoundNetLog
& net_log
,
312 scoped_ptr
<ConnectDelegate
> connect_delegate
) {
313 scoped_ptr
<WebSocketHandshakeStreamCreateHelper
> create_helper(
314 new WebSocketHandshakeStreamCreateHelper(connect_delegate
.get(),
315 requested_subprotocols
));
316 scoped_ptr
<StreamRequestImpl
> request(
317 new StreamRequestImpl(socket_url
,
320 connect_delegate
.Pass(),
321 create_helper
.Pass()));
322 request
->Start(scoped_ptr
<base::Timer
>(new base::Timer(false, false)));
323 return request
.Pass();
326 // This is declared in websocket_test_util.h.
327 scoped_ptr
<WebSocketStreamRequest
> CreateAndConnectStreamForTesting(
328 const GURL
& socket_url
,
329 scoped_ptr
<WebSocketHandshakeStreamCreateHelper
> create_helper
,
330 const url::Origin
& origin
,
331 URLRequestContext
* url_request_context
,
332 const BoundNetLog
& net_log
,
333 scoped_ptr
<WebSocketStream::ConnectDelegate
> connect_delegate
,
334 scoped_ptr
<base::Timer
> timer
) {
335 scoped_ptr
<StreamRequestImpl
> request(
336 new StreamRequestImpl(socket_url
,
339 connect_delegate
.Pass(),
340 create_helper
.Pass()));
341 request
->Start(timer
.Pass());
342 return request
.Pass();
345 void WebSocketDispatchOnFinishOpeningHandshake(
346 WebSocketStream::ConnectDelegate
* connect_delegate
,
348 const scoped_refptr
<HttpResponseHeaders
>& headers
,
349 base::Time response_time
) {
350 DCHECK(connect_delegate
);
352 connect_delegate
->OnFinishOpeningHandshake(make_scoped_ptr(
353 new WebSocketHandshakeResponseInfo(url
,
354 headers
->response_code(),
355 headers
->GetStatusText(),