OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "net/websockets/websocket_stream.h" | |
6 | |
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/strings/stringprintf.h" | |
13 #include "base/time/time.h" | |
14 #include "base/timer/timer.h" | |
15 #include "net/base/load_flags.h" | |
16 #include "net/http/http_request_headers.h" | |
17 #include "net/http/http_response_headers.h" | |
18 #include "net/http/http_status_code.h" | |
19 #include "net/url_request/redirect_info.h" | |
20 #include "net/url_request/url_request.h" | |
21 #include "net/url_request/url_request_context.h" | |
22 #include "net/websockets/websocket_errors.h" | |
23 #include "net/websockets/websocket_event_interface.h" | |
24 #include "net/websockets/websocket_handshake_constants.h" | |
25 #include "net/websockets/websocket_handshake_stream_base.h" | |
26 #include "net/websockets/websocket_handshake_stream_create_helper.h" | |
27 #include "net/websockets/websocket_test_util.h" | |
28 #include "url/gurl.h" | |
29 #include "url/origin.h" | |
30 | |
31 namespace net { | |
32 namespace { | |
33 | |
34 // The timeout duration of WebSocket handshake. | |
35 // It is defined as the same value as the TCP connection timeout value in | |
36 // net/socket/websocket_transport_client_socket_pool.cc to make it hard for | |
37 // JavaScript programs to recognize the timeout cause. | |
38 const int kHandshakeTimeoutIntervalInSeconds = 240; | |
39 | |
40 class StreamRequestImpl; | |
41 | |
42 class Delegate : public URLRequest::Delegate { | |
43 public: | |
44 enum HandshakeResult { | |
45 INCOMPLETE, | |
46 CONNECTED, | |
47 FAILED, | |
48 NUM_HANDSHAKE_RESULT_TYPES, | |
49 }; | |
50 | |
51 explicit Delegate(StreamRequestImpl* owner) | |
52 : owner_(owner), result_(INCOMPLETE) {} | |
53 ~Delegate() override { | |
54 UMA_HISTOGRAM_ENUMERATION( | |
55 "Net.WebSocket.HandshakeResult", result_, NUM_HANDSHAKE_RESULT_TYPES); | |
56 } | |
57 | |
58 // Implementation of URLRequest::Delegate methods. | |
59 void OnReceivedRedirect(URLRequest* request, | |
60 const RedirectInfo& redirect_info, | |
61 bool* defer_redirect) override { | |
62 // HTTP status codes returned by HttpStreamParser are filtered by | |
63 // WebSocketBasicHandshakeStream, and only 101, 401 and 407 are permitted | |
64 // back up the stack to HttpNetworkTransaction. In particular, redirect | |
65 // codes are never allowed, and so URLRequest never sees a redirect on a | |
66 // WebSocket request. | |
67 NOTREACHED(); | |
68 } | |
69 | |
70 void OnResponseStarted(URLRequest* request) override; | |
71 | |
72 void OnAuthRequired(URLRequest* request, | |
73 AuthChallengeInfo* auth_info) override; | |
74 | |
75 void OnCertificateRequested(URLRequest* request, | |
76 SSLCertRequestInfo* cert_request_info) override; | |
77 | |
78 void OnSSLCertificateError(URLRequest* request, | |
79 const SSLInfo& ssl_info, | |
80 bool fatal) override; | |
81 | |
82 void OnReadCompleted(URLRequest* request, int bytes_read) override; | |
83 | |
84 private: | |
85 StreamRequestImpl* owner_; | |
86 HandshakeResult result_; | |
87 }; | |
88 | |
89 class StreamRequestImpl : public WebSocketStreamRequest { | |
90 public: | |
91 StreamRequestImpl( | |
92 const GURL& url, | |
93 const URLRequestContext* context, | |
94 const url::Origin& origin, | |
95 scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate, | |
96 scoped_ptr<WebSocketHandshakeStreamCreateHelper> create_helper) | |
97 : delegate_(new Delegate(this)), | |
98 url_request_(context->CreateRequest(url, DEFAULT_PRIORITY, | |
99 delegate_.get(), NULL)), | |
100 connect_delegate_(connect_delegate.Pass()), | |
101 create_helper_(create_helper.release()) { | |
102 create_helper_->set_failure_message(&failure_message_); | |
103 HttpRequestHeaders headers; | |
104 headers.SetHeader(websockets::kUpgrade, websockets::kWebSocketLowercase); | |
105 headers.SetHeader(HttpRequestHeaders::kConnection, websockets::kUpgrade); | |
106 headers.SetHeader(HttpRequestHeaders::kOrigin, origin.string()); | |
107 headers.SetHeader(websockets::kSecWebSocketVersion, | |
108 websockets::kSupportedVersion); | |
109 url_request_->SetExtraRequestHeaders(headers); | |
110 | |
111 // This passes the ownership of |create_helper_| to |url_request_|. | |
112 url_request_->SetUserData( | |
113 WebSocketHandshakeStreamBase::CreateHelper::DataKey(), | |
114 create_helper_); | |
115 url_request_->SetLoadFlags(LOAD_DISABLE_CACHE | LOAD_BYPASS_CACHE); | |
116 } | |
117 | |
118 // Destroying this object destroys the URLRequest, which cancels the request | |
119 // and so terminates the handshake if it is incomplete. | |
120 ~StreamRequestImpl() override {} | |
121 | |
122 void Start(scoped_ptr<base::Timer> timer) { | |
123 DCHECK(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(); | |
131 } | |
132 | |
133 void PerformUpgrade() { | |
134 DCHECK(timer_); | |
135 timer_->Stop(); | |
136 connect_delegate_->OnSuccess(create_helper_->Upgrade()); | |
137 } | |
138 | |
139 std::string FailureMessageFromNetError() { | |
140 int error = url_request_->status().error(); | |
141 if (error == ERR_TUNNEL_CONNECTION_FAILED) { | |
142 // This error is common and confusing, so special-case it. | |
143 // TODO(ricea): Include the HostPortPair of the selected proxy server in | |
144 // the error message. This is not currently possible because it isn't set | |
145 // in HttpResponseInfo when a ERR_TUNNEL_CONNECTION_FAILED error happens. | |
146 return "Establishing a tunnel via proxy server failed."; | |
147 } else { | |
148 return std::string("Error in connection establishment: ") + | |
149 ErrorToString(url_request_->status().error()); | |
150 } | |
151 } | |
152 | |
153 void ReportFailure() { | |
154 DCHECK(timer_); | |
155 timer_->Stop(); | |
156 if (failure_message_.empty()) { | |
157 switch (url_request_->status().status()) { | |
158 case URLRequestStatus::SUCCESS: | |
159 case URLRequestStatus::IO_PENDING: | |
160 break; | |
161 case URLRequestStatus::CANCELED: | |
162 if (url_request_->status().error() == ERR_TIMED_OUT) | |
163 failure_message_ = "WebSocket opening handshake timed out"; | |
164 else | |
165 failure_message_ = "WebSocket opening handshake was canceled"; | |
166 break; | |
167 case URLRequestStatus::FAILED: | |
168 failure_message_ = FailureMessageFromNetError(); | |
169 break; | |
170 } | |
171 } | |
172 ReportFailureWithMessage(failure_message_); | |
173 } | |
174 | |
175 void ReportFailureWithMessage(const std::string& failure_message) { | |
176 connect_delegate_->OnFailure(failure_message); | |
177 } | |
178 | |
179 void OnFinishOpeningHandshake() { | |
180 WebSocketDispatchOnFinishOpeningHandshake(connect_delegate(), | |
181 url_request_->url(), | |
182 url_request_->response_headers(), | |
183 url_request_->response_time()); | |
184 } | |
185 | |
186 WebSocketStream::ConnectDelegate* connect_delegate() const { | |
187 return connect_delegate_.get(); | |
188 } | |
189 | |
190 void OnTimeout() { | |
191 url_request_->CancelWithError(ERR_TIMED_OUT); | |
192 } | |
193 | |
194 private: | |
195 // |delegate_| needs to be declared before |url_request_| so that it gets | |
196 // initialised first. | |
197 scoped_ptr<Delegate> delegate_; | |
198 | |
199 // Deleting the StreamRequestImpl object deletes this URLRequest object, | |
200 // cancelling the whole connection. | |
201 scoped_ptr<URLRequest> url_request_; | |
202 | |
203 scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate_; | |
204 | |
205 // Owned by the URLRequest. | |
206 WebSocketHandshakeStreamCreateHelper* create_helper_; | |
207 | |
208 // The failure message supplied by WebSocketBasicHandshakeStream, if any. | |
209 std::string failure_message_; | |
210 | |
211 // A timer for handshake timeout. | |
212 scoped_ptr<base::Timer> timer_; | |
213 }; | |
214 | |
215 class SSLErrorCallbacks : public WebSocketEventInterface::SSLErrorCallbacks { | |
216 public: | |
217 explicit SSLErrorCallbacks(URLRequest* url_request) | |
218 : url_request_(url_request) {} | |
219 | |
220 void CancelSSLRequest(int error, const SSLInfo* ssl_info) override { | |
221 if (ssl_info) { | |
222 url_request_->CancelWithSSLError(error, *ssl_info); | |
223 } else { | |
224 url_request_->CancelWithError(error); | |
225 } | |
226 } | |
227 | |
228 void ContinueSSLRequest() override { | |
229 url_request_->ContinueDespiteLastError(); | |
230 } | |
231 | |
232 private: | |
233 URLRequest* url_request_; | |
234 }; | |
235 | |
236 void Delegate::OnResponseStarted(URLRequest* request) { | |
237 // TODO(vadimt): Remove ScopedTracker below once crbug.com/423948 is fixed. | |
238 tracked_objects::ScopedTracker tracking_profile( | |
239 FROM_HERE_WITH_EXPLICIT_FUNCTION("423948 Delegate::OnResponseStarted")); | |
240 | |
241 // All error codes, including OK and ABORTED, as with | |
242 // Net.ErrorCodesForMainFrame3 | |
243 UMA_HISTOGRAM_SPARSE_SLOWLY("Net.WebSocket.ErrorCodes", | |
244 -request->status().error()); | |
245 if (!request->status().is_success()) { | |
246 DVLOG(3) << "OnResponseStarted (request failed)"; | |
247 owner_->ReportFailure(); | |
248 return; | |
249 } | |
250 const int response_code = request->GetResponseCode(); | |
251 DVLOG(3) << "OnResponseStarted (response code " << response_code << ")"; | |
252 switch (response_code) { | |
253 case HTTP_SWITCHING_PROTOCOLS: | |
254 result_ = CONNECTED; | |
255 owner_->PerformUpgrade(); | |
256 return; | |
257 | |
258 case HTTP_UNAUTHORIZED: | |
259 result_ = FAILED; | |
260 owner_->OnFinishOpeningHandshake(); | |
261 owner_->ReportFailureWithMessage( | |
262 "HTTP Authentication failed; no valid credentials available"); | |
263 return; | |
264 | |
265 case HTTP_PROXY_AUTHENTICATION_REQUIRED: | |
266 result_ = FAILED; | |
267 owner_->OnFinishOpeningHandshake(); | |
268 owner_->ReportFailureWithMessage("Proxy authentication failed"); | |
269 return; | |
270 | |
271 default: | |
272 result_ = FAILED; | |
273 owner_->ReportFailure(); | |
274 } | |
275 } | |
276 | |
277 void Delegate::OnAuthRequired(URLRequest* request, | |
278 AuthChallengeInfo* auth_info) { | |
279 // This should only be called if credentials are not already stored. | |
280 request->CancelAuth(); | |
281 } | |
282 | |
283 void Delegate::OnCertificateRequested(URLRequest* request, | |
284 SSLCertRequestInfo* cert_request_info) { | |
285 // This method is called when a client certificate is requested, and the | |
286 // request context does not already contain a client certificate selection for | |
287 // the endpoint. In this case, a main frame resource request would pop-up UI | |
288 // to permit selection of a client certificate, but since WebSockets are | |
289 // sub-resources they should not pop-up UI and so there is nothing more we can | |
290 // do. | |
291 request->Cancel(); | |
292 } | |
293 | |
294 void Delegate::OnSSLCertificateError(URLRequest* request, | |
295 const SSLInfo& ssl_info, | |
296 bool fatal) { | |
297 owner_->connect_delegate()->OnSSLCertificateError( | |
298 scoped_ptr<WebSocketEventInterface::SSLErrorCallbacks>( | |
299 new SSLErrorCallbacks(request)), | |
300 ssl_info, | |
301 fatal); | |
302 } | |
303 | |
304 void Delegate::OnReadCompleted(URLRequest* request, int bytes_read) { | |
305 NOTREACHED(); | |
306 } | |
307 | |
308 } // namespace | |
309 | |
310 WebSocketStreamRequest::~WebSocketStreamRequest() {} | |
311 | |
312 WebSocketStream::WebSocketStream() {} | |
313 WebSocketStream::~WebSocketStream() {} | |
314 | |
315 WebSocketStream::ConnectDelegate::~ConnectDelegate() {} | |
316 | |
317 scoped_ptr<WebSocketStreamRequest> WebSocketStream::CreateAndConnectStream( | |
318 const GURL& socket_url, | |
319 const std::vector<std::string>& requested_subprotocols, | |
320 const url::Origin& origin, | |
321 URLRequestContext* url_request_context, | |
322 const BoundNetLog& net_log, | |
323 scoped_ptr<ConnectDelegate> connect_delegate) { | |
324 scoped_ptr<WebSocketHandshakeStreamCreateHelper> create_helper( | |
325 new WebSocketHandshakeStreamCreateHelper(connect_delegate.get(), | |
326 requested_subprotocols)); | |
327 scoped_ptr<StreamRequestImpl> request( | |
328 new StreamRequestImpl(socket_url, | |
329 url_request_context, | |
330 origin, | |
331 connect_delegate.Pass(), | |
332 create_helper.Pass())); | |
333 request->Start(scoped_ptr<base::Timer>(new base::Timer(false, false))); | |
334 return request.Pass(); | |
335 } | |
336 | |
337 // This is declared in websocket_test_util.h. | |
338 scoped_ptr<WebSocketStreamRequest> CreateAndConnectStreamForTesting( | |
339 const GURL& socket_url, | |
340 scoped_ptr<WebSocketHandshakeStreamCreateHelper> create_helper, | |
341 const url::Origin& origin, | |
342 URLRequestContext* url_request_context, | |
343 const BoundNetLog& net_log, | |
344 scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate, | |
345 scoped_ptr<base::Timer> timer) { | |
346 scoped_ptr<StreamRequestImpl> request( | |
347 new StreamRequestImpl(socket_url, | |
348 url_request_context, | |
349 origin, | |
350 connect_delegate.Pass(), | |
351 create_helper.Pass())); | |
352 request->Start(timer.Pass()); | |
353 return request.Pass(); | |
354 } | |
355 | |
356 void WebSocketDispatchOnFinishOpeningHandshake( | |
357 WebSocketStream::ConnectDelegate* connect_delegate, | |
358 const GURL& url, | |
359 const scoped_refptr<HttpResponseHeaders>& headers, | |
360 base::Time response_time) { | |
361 DCHECK(connect_delegate); | |
362 if (headers.get()) { | |
363 connect_delegate->OnFinishOpeningHandshake(make_scoped_ptr( | |
364 new WebSocketHandshakeResponseInfo(url, | |
365 headers->response_code(), | |
366 headers->GetStatusText(), | |
367 headers, | |
368 response_time))); | |
369 } | |
370 } | |
371 | |
372 } // namespace net | |
OLD | NEW |