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/strings/stringprintf.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"
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
;
61 void OnResponseStarted(URLRequest
* request
) override
;
63 void OnAuthRequired(URLRequest
* request
,
64 AuthChallengeInfo
* auth_info
) override
;
66 void OnCertificateRequested(URLRequest
* request
,
67 SSLCertRequestInfo
* cert_request_info
) override
;
69 void OnSSLCertificateError(URLRequest
* request
,
70 const SSLInfo
& ssl_info
,
73 void OnReadCompleted(URLRequest
* request
, int bytes_read
) override
;
76 StreamRequestImpl
* owner_
;
77 HandshakeResult result_
;
80 class StreamRequestImpl
: public WebSocketStreamRequest
{
84 const URLRequestContext
* context
,
85 const url::Origin
& origin
,
86 scoped_ptr
<WebSocketStream::ConnectDelegate
> connect_delegate
,
87 scoped_ptr
<WebSocketHandshakeStreamCreateHelper
> create_helper
)
88 : delegate_(new Delegate(this)),
90 context
->CreateRequest(url
, DEFAULT_PRIORITY
, delegate_
.get())),
91 connect_delegate_(connect_delegate
.Pass()),
92 create_helper_(create_helper
.release()) {
93 create_helper_
->set_failure_message(&failure_message_
);
94 HttpRequestHeaders headers
;
95 headers
.SetHeader(websockets::kUpgrade
, websockets::kWebSocketLowercase
);
96 headers
.SetHeader(HttpRequestHeaders::kConnection
, websockets::kUpgrade
);
97 headers
.SetHeader(HttpRequestHeaders::kOrigin
, origin
.string());
98 headers
.SetHeader(websockets::kSecWebSocketVersion
,
99 websockets::kSupportedVersion
);
100 url_request_
->SetExtraRequestHeaders(headers
);
102 // This passes the ownership of |create_helper_| to |url_request_|.
103 url_request_
->SetUserData(
104 WebSocketHandshakeStreamBase::CreateHelper::DataKey(),
106 url_request_
->SetLoadFlags(LOAD_DISABLE_CACHE
| LOAD_BYPASS_CACHE
);
109 // Destroying this object destroys the URLRequest, which cancels the request
110 // and so terminates the handshake if it is incomplete.
111 ~StreamRequestImpl() override
{}
113 void Start(scoped_ptr
<base::Timer
> timer
) {
115 base::TimeDelta
timeout(base::TimeDelta::FromSeconds(
116 kHandshakeTimeoutIntervalInSeconds
));
117 timer_
= timer
.Pass();
118 timer_
->Start(FROM_HERE
, timeout
,
119 base::Bind(&StreamRequestImpl::OnTimeout
,
120 base::Unretained(this)));
121 url_request_
->Start();
124 void PerformUpgrade() {
127 connect_delegate_
->OnSuccess(create_helper_
->Upgrade());
130 std::string
FailureMessageFromNetError() {
131 int error
= url_request_
->status().error();
132 if (error
== ERR_TUNNEL_CONNECTION_FAILED
) {
133 // This error is common and confusing, so special-case it.
134 // TODO(ricea): Include the HostPortPair of the selected proxy server in
135 // the error message. This is not currently possible because it isn't set
136 // in HttpResponseInfo when a ERR_TUNNEL_CONNECTION_FAILED error happens.
137 return "Establishing a tunnel via proxy server failed.";
139 return std::string("Error in connection establishment: ") +
140 ErrorToString(url_request_
->status().error());
144 void ReportFailure() {
147 if (failure_message_
.empty()) {
148 switch (url_request_
->status().status()) {
149 case URLRequestStatus::SUCCESS
:
150 case URLRequestStatus::IO_PENDING
:
152 case URLRequestStatus::CANCELED
:
153 if (url_request_
->status().error() == ERR_TIMED_OUT
)
154 failure_message_
= "WebSocket opening handshake timed out";
156 failure_message_
= "WebSocket opening handshake was canceled";
158 case URLRequestStatus::FAILED
:
159 failure_message_
= FailureMessageFromNetError();
163 ReportFailureWithMessage(failure_message_
);
166 void ReportFailureWithMessage(const std::string
& failure_message
) {
167 connect_delegate_
->OnFailure(failure_message
);
170 void OnFinishOpeningHandshake() {
171 WebSocketDispatchOnFinishOpeningHandshake(connect_delegate(),
173 url_request_
->response_headers(),
174 url_request_
->response_time());
177 WebSocketStream::ConnectDelegate
* connect_delegate() const {
178 return connect_delegate_
.get();
182 url_request_
->CancelWithError(ERR_TIMED_OUT
);
186 // |delegate_| needs to be declared before |url_request_| so that it gets
187 // initialised first.
188 scoped_ptr
<Delegate
> delegate_
;
190 // Deleting the StreamRequestImpl object deletes this URLRequest object,
191 // cancelling the whole connection.
192 scoped_ptr
<URLRequest
> url_request_
;
194 scoped_ptr
<WebSocketStream::ConnectDelegate
> connect_delegate_
;
196 // Owned by the URLRequest.
197 WebSocketHandshakeStreamCreateHelper
* create_helper_
;
199 // The failure message supplied by WebSocketBasicHandshakeStream, if any.
200 std::string failure_message_
;
202 // A timer for handshake timeout.
203 scoped_ptr
<base::Timer
> timer_
;
206 class SSLErrorCallbacks
: public WebSocketEventInterface::SSLErrorCallbacks
{
208 explicit SSLErrorCallbacks(URLRequest
* url_request
)
209 : url_request_(url_request
) {}
211 void CancelSSLRequest(int error
, const SSLInfo
* ssl_info
) override
{
213 url_request_
->CancelWithSSLError(error
, *ssl_info
);
215 url_request_
->CancelWithError(error
);
219 void ContinueSSLRequest() override
{
220 url_request_
->ContinueDespiteLastError();
224 URLRequest
* url_request_
;
227 void Delegate::OnReceivedRedirect(URLRequest
* request
,
228 const RedirectInfo
& redirect_info
,
229 bool* defer_redirect
) {
230 // This code should never be reached for externally generated redirects,
231 // as WebSocketBasicHandshakeStream is responsible for filtering out
232 // all response codes besides 101, 401, and 407. As such, the URLRequest
233 // should never see a redirect sent over the network. However, internal
234 // redirects also result in this method being called, such as those
236 // Because it's security critical to prevent externally-generated
237 // redirects in WebSockets, perform additional checks to ensure this
239 GURL::Replacements replacements
;
240 replacements
.SetSchemeStr("wss");
241 GURL expected_url
= request
->original_url().ReplaceComponents(replacements
);
242 if (redirect_info
.new_method
!= "GET" ||
243 redirect_info
.new_url
!= expected_url
) {
244 // This should not happen.
245 DLOG(FATAL
) << "Unauthorized WebSocket redirect to "
246 << redirect_info
.new_method
<< " "
247 << redirect_info
.new_url
.spec();
252 void Delegate::OnResponseStarted(URLRequest
* request
) {
253 // All error codes, including OK and ABORTED, as with
254 // Net.ErrorCodesForMainFrame3
255 UMA_HISTOGRAM_SPARSE_SLOWLY("Net.WebSocket.ErrorCodes",
256 -request
->status().error());
257 if (!request
->status().is_success()) {
258 DVLOG(3) << "OnResponseStarted (request failed)";
259 owner_
->ReportFailure();
262 const int response_code
= request
->GetResponseCode();
263 DVLOG(3) << "OnResponseStarted (response code " << response_code
<< ")";
264 switch (response_code
) {
265 case HTTP_SWITCHING_PROTOCOLS
:
267 owner_
->PerformUpgrade();
270 case HTTP_UNAUTHORIZED
:
272 owner_
->OnFinishOpeningHandshake();
273 owner_
->ReportFailureWithMessage(
274 "HTTP Authentication failed; no valid credentials available");
277 case HTTP_PROXY_AUTHENTICATION_REQUIRED
:
279 owner_
->OnFinishOpeningHandshake();
280 owner_
->ReportFailureWithMessage("Proxy authentication failed");
285 owner_
->ReportFailure();
289 void Delegate::OnAuthRequired(URLRequest
* request
,
290 AuthChallengeInfo
* auth_info
) {
291 // This should only be called if credentials are not already stored.
292 request
->CancelAuth();
295 void Delegate::OnCertificateRequested(URLRequest
* request
,
296 SSLCertRequestInfo
* cert_request_info
) {
297 // This method is called when a client certificate is requested, and the
298 // request context does not already contain a client certificate selection for
299 // the endpoint. In this case, a main frame resource request would pop-up UI
300 // to permit selection of a client certificate, but since WebSockets are
301 // sub-resources they should not pop-up UI and so there is nothing more we can
306 void Delegate::OnSSLCertificateError(URLRequest
* request
,
307 const SSLInfo
& ssl_info
,
309 owner_
->connect_delegate()->OnSSLCertificateError(
310 scoped_ptr
<WebSocketEventInterface::SSLErrorCallbacks
>(
311 new SSLErrorCallbacks(request
)),
316 void Delegate::OnReadCompleted(URLRequest
* request
, int bytes_read
) {
322 WebSocketStreamRequest::~WebSocketStreamRequest() {}
324 WebSocketStream::WebSocketStream() {}
325 WebSocketStream::~WebSocketStream() {}
327 WebSocketStream::ConnectDelegate::~ConnectDelegate() {}
329 scoped_ptr
<WebSocketStreamRequest
> WebSocketStream::CreateAndConnectStream(
330 const GURL
& socket_url
,
331 const std::vector
<std::string
>& requested_subprotocols
,
332 const url::Origin
& origin
,
333 URLRequestContext
* url_request_context
,
334 const BoundNetLog
& net_log
,
335 scoped_ptr
<ConnectDelegate
> connect_delegate
) {
336 scoped_ptr
<WebSocketHandshakeStreamCreateHelper
> create_helper(
337 new WebSocketHandshakeStreamCreateHelper(connect_delegate
.get(),
338 requested_subprotocols
));
339 scoped_ptr
<StreamRequestImpl
> request(
340 new StreamRequestImpl(socket_url
,
343 connect_delegate
.Pass(),
344 create_helper
.Pass()));
345 request
->Start(scoped_ptr
<base::Timer
>(new base::Timer(false, false)));
346 return request
.Pass();
349 // This is declared in websocket_test_util.h.
350 scoped_ptr
<WebSocketStreamRequest
> CreateAndConnectStreamForTesting(
351 const GURL
& socket_url
,
352 scoped_ptr
<WebSocketHandshakeStreamCreateHelper
> create_helper
,
353 const url::Origin
& origin
,
354 URLRequestContext
* url_request_context
,
355 const BoundNetLog
& net_log
,
356 scoped_ptr
<WebSocketStream::ConnectDelegate
> connect_delegate
,
357 scoped_ptr
<base::Timer
> timer
) {
358 scoped_ptr
<StreamRequestImpl
> request(
359 new StreamRequestImpl(socket_url
,
362 connect_delegate
.Pass(),
363 create_helper
.Pass()));
364 request
->Start(timer
.Pass());
365 return request
.Pass();
368 void WebSocketDispatchOnFinishOpeningHandshake(
369 WebSocketStream::ConnectDelegate
* connect_delegate
,
371 const scoped_refptr
<HttpResponseHeaders
>& headers
,
372 base::Time response_time
) {
373 DCHECK(connect_delegate
);
375 connect_delegate
->OnFinishOpeningHandshake(make_scoped_ptr(
376 new WebSocketHandshakeResponseInfo(url
,
377 headers
->response_code(),
378 headers
->GetStatusText(),