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/time/time.h"
12 #include "base/timer/timer.h"
13 #include "net/base/load_flags.h"
14 #include "net/http/http_request_headers.h"
15 #include "net/http/http_response_headers.h"
16 #include "net/http/http_status_code.h"
17 #include "net/url_request/redirect_info.h"
18 #include "net/url_request/url_request.h"
19 #include "net/url_request/url_request_context.h"
20 #include "net/websockets/websocket_errors.h"
21 #include "net/websockets/websocket_event_interface.h"
22 #include "net/websockets/websocket_handshake_constants.h"
23 #include "net/websockets/websocket_handshake_stream_base.h"
24 #include "net/websockets/websocket_handshake_stream_create_helper.h"
25 #include "net/websockets/websocket_test_util.h"
27 #include "url/origin.h"
32 // The timeout duration of WebSocket handshake.
33 // It is defined as the same value as the TCP connection timeout value in
34 // net/socket/websocket_transport_client_socket_pool.cc to make it hard for
35 // JavaScript programs to recognize the timeout cause.
36 const int kHandshakeTimeoutIntervalInSeconds
= 240;
38 class StreamRequestImpl
;
40 class Delegate
: public URLRequest::Delegate
{
42 enum HandshakeResult
{
46 NUM_HANDSHAKE_RESULT_TYPES
,
49 explicit Delegate(StreamRequestImpl
* owner
)
50 : owner_(owner
), result_(INCOMPLETE
) {}
51 ~Delegate() override
{
52 UMA_HISTOGRAM_ENUMERATION(
53 "Net.WebSocket.HandshakeResult", result_
, NUM_HANDSHAKE_RESULT_TYPES
);
56 // Implementation of URLRequest::Delegate methods.
57 void OnReceivedRedirect(URLRequest
* request
,
58 const RedirectInfo
& redirect_info
,
59 bool* defer_redirect
) override
{
60 // HTTP status codes returned by HttpStreamParser are filtered by
61 // WebSocketBasicHandshakeStream, and only 101, 401 and 407 are permitted
62 // back up the stack to HttpNetworkTransaction. In particular, redirect
63 // codes are never allowed, and so URLRequest never sees a redirect on a
68 void OnResponseStarted(URLRequest
* request
) override
;
70 void OnAuthRequired(URLRequest
* request
,
71 AuthChallengeInfo
* auth_info
) override
;
73 void OnCertificateRequested(URLRequest
* request
,
74 SSLCertRequestInfo
* cert_request_info
) override
;
76 void OnSSLCertificateError(URLRequest
* request
,
77 const SSLInfo
& ssl_info
,
80 void OnReadCompleted(URLRequest
* request
, int bytes_read
) override
;
83 StreamRequestImpl
* owner_
;
84 HandshakeResult result_
;
87 class StreamRequestImpl
: public WebSocketStreamRequest
{
91 const URLRequestContext
* context
,
92 const url::Origin
& origin
,
93 scoped_ptr
<WebSocketStream::ConnectDelegate
> connect_delegate
,
94 scoped_ptr
<WebSocketHandshakeStreamCreateHelper
> create_helper
)
95 : delegate_(new Delegate(this)),
96 url_request_(context
->CreateRequest(url
, DEFAULT_PRIORITY
,
97 delegate_
.get(), NULL
)),
98 connect_delegate_(connect_delegate
.Pass()),
99 create_helper_(create_helper
.release()) {
100 create_helper_
->set_failure_message(&failure_message_
);
101 HttpRequestHeaders headers
;
102 headers
.SetHeader(websockets::kUpgrade
, websockets::kWebSocketLowercase
);
103 headers
.SetHeader(HttpRequestHeaders::kConnection
, websockets::kUpgrade
);
104 headers
.SetHeader(HttpRequestHeaders::kOrigin
, origin
.string());
105 headers
.SetHeader(websockets::kSecWebSocketVersion
,
106 websockets::kSupportedVersion
);
107 url_request_
->SetExtraRequestHeaders(headers
);
109 // This passes the ownership of |create_helper_| to |url_request_|.
110 url_request_
->SetUserData(
111 WebSocketHandshakeStreamBase::CreateHelper::DataKey(),
113 url_request_
->SetLoadFlags(LOAD_DISABLE_CACHE
|
115 LOAD_DO_NOT_PROMPT_FOR_LOGIN
);
118 // Destroying this object destroys the URLRequest, which cancels the request
119 // and so terminates the handshake if it is incomplete.
120 ~StreamRequestImpl() override
{}
122 void Start(scoped_ptr
<base::Timer
> timer
) {
124 TimeDelta
timeout(TimeDelta::FromSeconds(
125 kHandshakeTimeoutIntervalInSeconds
));
126 timer_
= timer
.Pass();
127 timer_
->Start(FROM_HERE
, timeout
,
128 base::Bind(&StreamRequestImpl::OnTimeout
,
129 base::Unretained(this)));
130 url_request_
->Start();
133 void PerformUpgrade() {
136 connect_delegate_
->OnSuccess(create_helper_
->Upgrade());
139 void ReportFailure() {
142 if (failure_message_
.empty()) {
143 switch (url_request_
->status().status()) {
144 case URLRequestStatus::SUCCESS
:
145 case URLRequestStatus::IO_PENDING
:
147 case URLRequestStatus::CANCELED
:
148 if (url_request_
->status().error() == ERR_TIMED_OUT
)
149 failure_message_
= "WebSocket opening handshake timed out";
151 failure_message_
= "WebSocket opening handshake was canceled";
153 case URLRequestStatus::FAILED
:
155 std::string("Error in connection establishment: ") +
156 ErrorToString(url_request_
->status().error());
160 ReportFailureWithMessage(failure_message_
);
163 void ReportFailureWithMessage(const std::string
& failure_message
) {
164 connect_delegate_
->OnFailure(failure_message
);
167 void OnFinishOpeningHandshake() {
168 WebSocketDispatchOnFinishOpeningHandshake(connect_delegate(),
170 url_request_
->response_headers(),
171 url_request_
->response_time());
174 WebSocketStream::ConnectDelegate
* connect_delegate() const {
175 return connect_delegate_
.get();
179 url_request_
->CancelWithError(ERR_TIMED_OUT
);
183 // |delegate_| needs to be declared before |url_request_| so that it gets
184 // initialised first.
185 scoped_ptr
<Delegate
> delegate_
;
187 // Deleting the StreamRequestImpl object deletes this URLRequest object,
188 // cancelling the whole connection.
189 scoped_ptr
<URLRequest
> url_request_
;
191 scoped_ptr
<WebSocketStream::ConnectDelegate
> connect_delegate_
;
193 // Owned by the URLRequest.
194 WebSocketHandshakeStreamCreateHelper
* create_helper_
;
196 // The failure message supplied by WebSocketBasicHandshakeStream, if any.
197 std::string failure_message_
;
199 // A timer for handshake timeout.
200 scoped_ptr
<base::Timer
> timer_
;
203 class SSLErrorCallbacks
: public WebSocketEventInterface::SSLErrorCallbacks
{
205 explicit SSLErrorCallbacks(URLRequest
* url_request
)
206 : url_request_(url_request
) {}
208 void CancelSSLRequest(int error
, const SSLInfo
* ssl_info
) override
{
210 url_request_
->CancelWithSSLError(error
, *ssl_info
);
212 url_request_
->CancelWithError(error
);
216 void ContinueSSLRequest() override
{
217 url_request_
->ContinueDespiteLastError();
221 URLRequest
* url_request_
;
224 void Delegate::OnResponseStarted(URLRequest
* request
) {
225 // All error codes, including OK and ABORTED, as with
226 // Net.ErrorCodesForMainFrame3
227 UMA_HISTOGRAM_SPARSE_SLOWLY("Net.WebSocket.ErrorCodes",
228 -request
->status().error());
229 if (!request
->status().is_success()) {
230 DVLOG(3) << "OnResponseStarted (request failed)";
231 owner_
->ReportFailure();
234 const int response_code
= request
->GetResponseCode();
235 DVLOG(3) << "OnResponseStarted (response code " << response_code
<< ")";
236 switch (response_code
) {
237 case HTTP_SWITCHING_PROTOCOLS
:
239 owner_
->PerformUpgrade();
242 case HTTP_UNAUTHORIZED
:
244 owner_
->OnFinishOpeningHandshake();
245 owner_
->ReportFailureWithMessage(
246 "HTTP Authentication failed; no valid credentials available");
249 case HTTP_PROXY_AUTHENTICATION_REQUIRED
:
251 owner_
->OnFinishOpeningHandshake();
252 owner_
->ReportFailureWithMessage("Proxy authentication failed");
257 owner_
->ReportFailure();
261 void Delegate::OnAuthRequired(URLRequest
* request
,
262 AuthChallengeInfo
* auth_info
) {
263 // This should only be called if credentials are not already stored.
264 request
->CancelAuth();
267 void Delegate::OnCertificateRequested(URLRequest
* request
,
268 SSLCertRequestInfo
* cert_request_info
) {
269 // This method is called when a client certificate is requested, and the
270 // request context does not already contain a client certificate selection for
271 // the endpoint. In this case, a main frame resource request would pop-up UI
272 // to permit selection of a client certificate, but since WebSockets are
273 // sub-resources they should not pop-up UI and so there is nothing more we can
278 void Delegate::OnSSLCertificateError(URLRequest
* request
,
279 const SSLInfo
& ssl_info
,
281 owner_
->connect_delegate()->OnSSLCertificateError(
282 scoped_ptr
<WebSocketEventInterface::SSLErrorCallbacks
>(
283 new SSLErrorCallbacks(request
)),
288 void Delegate::OnReadCompleted(URLRequest
* request
, int bytes_read
) {
294 WebSocketStreamRequest::~WebSocketStreamRequest() {}
296 WebSocketStream::WebSocketStream() {}
297 WebSocketStream::~WebSocketStream() {}
299 WebSocketStream::ConnectDelegate::~ConnectDelegate() {}
301 scoped_ptr
<WebSocketStreamRequest
> WebSocketStream::CreateAndConnectStream(
302 const GURL
& socket_url
,
303 const std::vector
<std::string
>& requested_subprotocols
,
304 const url::Origin
& origin
,
305 URLRequestContext
* url_request_context
,
306 const BoundNetLog
& net_log
,
307 scoped_ptr
<ConnectDelegate
> connect_delegate
) {
308 scoped_ptr
<WebSocketHandshakeStreamCreateHelper
> create_helper(
309 new WebSocketHandshakeStreamCreateHelper(connect_delegate
.get(),
310 requested_subprotocols
));
311 scoped_ptr
<StreamRequestImpl
> request(
312 new StreamRequestImpl(socket_url
,
315 connect_delegate
.Pass(),
316 create_helper
.Pass()));
317 request
->Start(scoped_ptr
<base::Timer
>(new base::Timer(false, false)));
318 return request
.Pass();
321 // This is declared in websocket_test_util.h.
322 scoped_ptr
<WebSocketStreamRequest
> CreateAndConnectStreamForTesting(
323 const GURL
& socket_url
,
324 scoped_ptr
<WebSocketHandshakeStreamCreateHelper
> create_helper
,
325 const url::Origin
& origin
,
326 URLRequestContext
* url_request_context
,
327 const BoundNetLog
& net_log
,
328 scoped_ptr
<WebSocketStream::ConnectDelegate
> connect_delegate
,
329 scoped_ptr
<base::Timer
> timer
) {
330 scoped_ptr
<StreamRequestImpl
> request(
331 new StreamRequestImpl(socket_url
,
334 connect_delegate
.Pass(),
335 create_helper
.Pass()));
336 request
->Start(timer
.Pass());
337 return request
.Pass();
340 void WebSocketDispatchOnFinishOpeningHandshake(
341 WebSocketStream::ConnectDelegate
* connect_delegate
,
343 const scoped_refptr
<HttpResponseHeaders
>& headers
,
344 base::Time response_time
) {
345 DCHECK(connect_delegate
);
347 connect_delegate
->OnFinishOpeningHandshake(make_scoped_ptr(
348 new WebSocketHandshakeResponseInfo(url
,
349 headers
->response_code(),
350 headers
->GetStatusText(),