OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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/spdy/spdy_proxy_client_socket.h" | |
6 | |
7 #include <algorithm> // min | |
8 #include <memory> | |
9 #include <utility> | |
10 | |
11 #include "base/bind.h" | |
12 #include "base/bind_helpers.h" | |
13 #include "base/callback_helpers.h" | |
14 #include "base/location.h" | |
15 #include "base/logging.h" | |
16 #include "base/single_thread_task_runner.h" | |
17 #include "base/strings/string_util.h" | |
18 #include "base/threading/thread_task_runner_handle.h" | |
19 #include "base/values.h" | |
20 #include "net/base/auth.h" | |
21 #include "net/base/io_buffer.h" | |
22 #include "net/http/http_auth_cache.h" | |
23 #include "net/http/http_auth_handler_factory.h" | |
24 #include "net/http/http_request_info.h" | |
25 #include "net/http/http_response_headers.h" | |
26 #include "net/http/proxy_connect_redirect_http_stream.h" | |
27 #include "net/log/net_log_event_type.h" | |
28 #include "net/log/net_log_source_type.h" | |
29 #include "net/spdy/spdy_http_utils.h" | |
30 #include "url/gurl.h" | |
31 | |
32 namespace net { | |
33 | |
34 SpdyProxyClientSocket::SpdyProxyClientSocket( | |
35 const base::WeakPtr<SpdyStream>& spdy_stream, | |
36 const SpdyString& user_agent, | |
37 const HostPortPair& endpoint, | |
38 const HostPortPair& proxy_server, | |
39 const NetLogWithSource& source_net_log, | |
40 HttpAuthController* auth_controller) | |
41 : next_state_(STATE_DISCONNECTED), | |
42 spdy_stream_(spdy_stream), | |
43 endpoint_(endpoint), | |
44 auth_(auth_controller), | |
45 user_agent_(user_agent), | |
46 user_buffer_len_(0), | |
47 write_buffer_len_(0), | |
48 was_ever_used_(false), | |
49 redirect_has_load_timing_info_(false), | |
50 net_log_(NetLogWithSource::Make(spdy_stream->net_log().net_log(), | |
51 NetLogSourceType::PROXY_CLIENT_SOCKET)), | |
52 source_dependency_(source_net_log.source()), | |
53 weak_factory_(this), | |
54 write_callback_weak_factory_(this) { | |
55 request_.method = "CONNECT"; | |
56 request_.url = GURL("https://" + endpoint.ToString()); | |
57 net_log_.BeginEvent(NetLogEventType::SOCKET_ALIVE, | |
58 source_net_log.source().ToEventParametersCallback()); | |
59 net_log_.AddEvent( | |
60 NetLogEventType::HTTP2_PROXY_CLIENT_SESSION, | |
61 spdy_stream->net_log().source().ToEventParametersCallback()); | |
62 | |
63 spdy_stream_->SetDelegate(this); | |
64 was_ever_used_ = spdy_stream_->WasEverUsed(); | |
65 } | |
66 | |
67 SpdyProxyClientSocket::~SpdyProxyClientSocket() { | |
68 Disconnect(); | |
69 net_log_.EndEvent(NetLogEventType::SOCKET_ALIVE); | |
70 } | |
71 | |
72 const HttpResponseInfo* SpdyProxyClientSocket::GetConnectResponseInfo() const { | |
73 return response_.headers.get() ? &response_ : NULL; | |
74 } | |
75 | |
76 const scoped_refptr<HttpAuthController>& | |
77 SpdyProxyClientSocket::GetAuthController() const { | |
78 return auth_; | |
79 } | |
80 | |
81 int SpdyProxyClientSocket::RestartWithAuth(const CompletionCallback& callback) { | |
82 // A SPDY Stream can only handle a single request, so the underlying | |
83 // stream may not be reused and a new SpdyProxyClientSocket must be | |
84 // created (possibly on top of the same SPDY Session). | |
85 next_state_ = STATE_DISCONNECTED; | |
86 return OK; | |
87 } | |
88 | |
89 bool SpdyProxyClientSocket::IsUsingSpdy() const { | |
90 return true; | |
91 } | |
92 | |
93 NextProto SpdyProxyClientSocket::GetProxyNegotiatedProtocol() const { | |
94 return spdy_stream_->GetNegotiatedProtocol(); | |
95 } | |
96 | |
97 HttpStream* SpdyProxyClientSocket::CreateConnectResponseStream() { | |
98 return new ProxyConnectRedirectHttpStream( | |
99 redirect_has_load_timing_info_ ? &redirect_load_timing_info_ : NULL); | |
100 } | |
101 | |
102 // Sends a HEADERS frame to the proxy with a CONNECT request | |
103 // for the specified endpoint. Waits for the server to send back | |
104 // a HEADERS frame. OK will be returned if the status is 200. | |
105 // ERR_TUNNEL_CONNECTION_FAILED will be returned for any other status. | |
106 // In any of these cases, Read() may be called to retrieve the HTTP | |
107 // response body. Any other return values should be considered fatal. | |
108 // TODO(rch): handle 407 proxy auth requested correctly, perhaps | |
109 // by creating a new stream for the subsequent request. | |
110 // TODO(rch): create a more appropriate error code to disambiguate | |
111 // the HTTPS Proxy tunnel failure from an HTTP Proxy tunnel failure. | |
112 int SpdyProxyClientSocket::Connect(const CompletionCallback& callback) { | |
113 DCHECK(read_callback_.is_null()); | |
114 if (next_state_ == STATE_OPEN) | |
115 return OK; | |
116 | |
117 DCHECK_EQ(STATE_DISCONNECTED, next_state_); | |
118 next_state_ = STATE_GENERATE_AUTH_TOKEN; | |
119 | |
120 int rv = DoLoop(OK); | |
121 if (rv == ERR_IO_PENDING) | |
122 read_callback_ = callback; | |
123 return rv; | |
124 } | |
125 | |
126 void SpdyProxyClientSocket::Disconnect() { | |
127 read_buffer_queue_.Clear(); | |
128 user_buffer_ = NULL; | |
129 user_buffer_len_ = 0; | |
130 read_callback_.Reset(); | |
131 | |
132 write_buffer_len_ = 0; | |
133 write_callback_.Reset(); | |
134 write_callback_weak_factory_.InvalidateWeakPtrs(); | |
135 | |
136 next_state_ = STATE_DISCONNECTED; | |
137 | |
138 if (spdy_stream_.get()) { | |
139 // This will cause OnClose to be invoked, which takes care of | |
140 // cleaning up all the internal state. | |
141 spdy_stream_->Cancel(); | |
142 DCHECK(!spdy_stream_.get()); | |
143 } | |
144 } | |
145 | |
146 bool SpdyProxyClientSocket::IsConnected() const { | |
147 return next_state_ == STATE_OPEN; | |
148 } | |
149 | |
150 bool SpdyProxyClientSocket::IsConnectedAndIdle() const { | |
151 return IsConnected() && read_buffer_queue_.IsEmpty() && | |
152 spdy_stream_->IsOpen(); | |
153 } | |
154 | |
155 const NetLogWithSource& SpdyProxyClientSocket::NetLog() const { | |
156 return net_log_; | |
157 } | |
158 | |
159 void SpdyProxyClientSocket::SetSubresourceSpeculation() { | |
160 // TODO(rch): what should this implementation be? | |
161 } | |
162 | |
163 void SpdyProxyClientSocket::SetOmniboxSpeculation() { | |
164 // TODO(rch): what should this implementation be? | |
165 } | |
166 | |
167 bool SpdyProxyClientSocket::WasEverUsed() const { | |
168 return was_ever_used_ || (spdy_stream_.get() && spdy_stream_->WasEverUsed()); | |
169 } | |
170 | |
171 bool SpdyProxyClientSocket::WasAlpnNegotiated() const { | |
172 return false; | |
173 } | |
174 | |
175 NextProto SpdyProxyClientSocket::GetNegotiatedProtocol() const { | |
176 return kProtoUnknown; | |
177 } | |
178 | |
179 bool SpdyProxyClientSocket::GetSSLInfo(SSLInfo* ssl_info) { | |
180 return spdy_stream_->GetSSLInfo(ssl_info); | |
181 } | |
182 | |
183 void SpdyProxyClientSocket::GetConnectionAttempts( | |
184 ConnectionAttempts* out) const { | |
185 out->clear(); | |
186 } | |
187 | |
188 int64_t SpdyProxyClientSocket::GetTotalReceivedBytes() const { | |
189 NOTIMPLEMENTED(); | |
190 return 0; | |
191 } | |
192 | |
193 int SpdyProxyClientSocket::Read(IOBuffer* buf, int buf_len, | |
194 const CompletionCallback& callback) { | |
195 DCHECK(read_callback_.is_null()); | |
196 DCHECK(!user_buffer_.get()); | |
197 | |
198 if (next_state_ == STATE_DISCONNECTED) | |
199 return ERR_SOCKET_NOT_CONNECTED; | |
200 | |
201 if (next_state_ == STATE_CLOSED && read_buffer_queue_.IsEmpty()) { | |
202 return 0; | |
203 } | |
204 | |
205 DCHECK(next_state_ == STATE_OPEN || next_state_ == STATE_CLOSED); | |
206 DCHECK(buf); | |
207 size_t result = PopulateUserReadBuffer(buf->data(), buf_len); | |
208 if (result == 0) { | |
209 user_buffer_ = buf; | |
210 user_buffer_len_ = static_cast<size_t>(buf_len); | |
211 DCHECK(!callback.is_null()); | |
212 read_callback_ = callback; | |
213 return ERR_IO_PENDING; | |
214 } | |
215 user_buffer_ = NULL; | |
216 return result; | |
217 } | |
218 | |
219 size_t SpdyProxyClientSocket::PopulateUserReadBuffer(char* data, size_t len) { | |
220 return read_buffer_queue_.Dequeue(data, len); | |
221 } | |
222 | |
223 int SpdyProxyClientSocket::Write(IOBuffer* buf, int buf_len, | |
224 const CompletionCallback& callback) { | |
225 DCHECK(write_callback_.is_null()); | |
226 if (next_state_ != STATE_OPEN) | |
227 return ERR_SOCKET_NOT_CONNECTED; | |
228 | |
229 DCHECK(spdy_stream_.get()); | |
230 spdy_stream_->SendData(buf, buf_len, MORE_DATA_TO_SEND); | |
231 net_log_.AddByteTransferEvent(NetLogEventType::SOCKET_BYTES_SENT, buf_len, | |
232 buf->data()); | |
233 write_callback_ = callback; | |
234 write_buffer_len_ = buf_len; | |
235 return ERR_IO_PENDING; | |
236 } | |
237 | |
238 int SpdyProxyClientSocket::SetReceiveBufferSize(int32_t size) { | |
239 // Since this StreamSocket sits on top of a shared SpdySession, it | |
240 // is not safe for callers to change this underlying socket. | |
241 return ERR_NOT_IMPLEMENTED; | |
242 } | |
243 | |
244 int SpdyProxyClientSocket::SetSendBufferSize(int32_t size) { | |
245 // Since this StreamSocket sits on top of a shared SpdySession, it | |
246 // is not safe for callers to change this underlying socket. | |
247 return ERR_NOT_IMPLEMENTED; | |
248 } | |
249 | |
250 int SpdyProxyClientSocket::GetPeerAddress(IPEndPoint* address) const { | |
251 if (!IsConnected()) | |
252 return ERR_SOCKET_NOT_CONNECTED; | |
253 return spdy_stream_->GetPeerAddress(address); | |
254 } | |
255 | |
256 int SpdyProxyClientSocket::GetLocalAddress(IPEndPoint* address) const { | |
257 if (!IsConnected()) | |
258 return ERR_SOCKET_NOT_CONNECTED; | |
259 return spdy_stream_->GetLocalAddress(address); | |
260 } | |
261 | |
262 void SpdyProxyClientSocket::LogBlockedTunnelResponse() const { | |
263 ProxyClientSocket::LogBlockedTunnelResponse( | |
264 response_.headers->response_code(), | |
265 /* is_https_proxy = */ true); | |
266 } | |
267 | |
268 void SpdyProxyClientSocket::RunCallback(const CompletionCallback& callback, | |
269 int result) const { | |
270 callback.Run(result); | |
271 } | |
272 | |
273 void SpdyProxyClientSocket::OnIOComplete(int result) { | |
274 DCHECK_NE(STATE_DISCONNECTED, next_state_); | |
275 int rv = DoLoop(result); | |
276 if (rv != ERR_IO_PENDING) { | |
277 CompletionCallback c = read_callback_; | |
278 read_callback_.Reset(); | |
279 c.Run(rv); | |
280 } | |
281 } | |
282 | |
283 int SpdyProxyClientSocket::DoLoop(int last_io_result) { | |
284 DCHECK_NE(next_state_, STATE_DISCONNECTED); | |
285 int rv = last_io_result; | |
286 do { | |
287 State state = next_state_; | |
288 next_state_ = STATE_DISCONNECTED; | |
289 switch (state) { | |
290 case STATE_GENERATE_AUTH_TOKEN: | |
291 DCHECK_EQ(OK, rv); | |
292 rv = DoGenerateAuthToken(); | |
293 break; | |
294 case STATE_GENERATE_AUTH_TOKEN_COMPLETE: | |
295 rv = DoGenerateAuthTokenComplete(rv); | |
296 break; | |
297 case STATE_SEND_REQUEST: | |
298 DCHECK_EQ(OK, rv); | |
299 net_log_.BeginEvent( | |
300 NetLogEventType::HTTP_TRANSACTION_TUNNEL_SEND_REQUEST); | |
301 rv = DoSendRequest(); | |
302 break; | |
303 case STATE_SEND_REQUEST_COMPLETE: | |
304 net_log_.EndEventWithNetErrorCode( | |
305 NetLogEventType::HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, rv); | |
306 rv = DoSendRequestComplete(rv); | |
307 if (rv >= 0 || rv == ERR_IO_PENDING) { | |
308 // Emit extra event so can use the same events as | |
309 // HttpProxyClientSocket. | |
310 net_log_.BeginEvent( | |
311 NetLogEventType::HTTP_TRANSACTION_TUNNEL_READ_HEADERS); | |
312 } | |
313 break; | |
314 case STATE_READ_REPLY_COMPLETE: | |
315 rv = DoReadReplyComplete(rv); | |
316 net_log_.EndEventWithNetErrorCode( | |
317 NetLogEventType::HTTP_TRANSACTION_TUNNEL_READ_HEADERS, rv); | |
318 break; | |
319 default: | |
320 NOTREACHED() << "bad state"; | |
321 rv = ERR_UNEXPECTED; | |
322 break; | |
323 } | |
324 } while (rv != ERR_IO_PENDING && next_state_ != STATE_DISCONNECTED && | |
325 next_state_ != STATE_OPEN); | |
326 return rv; | |
327 } | |
328 | |
329 int SpdyProxyClientSocket::DoGenerateAuthToken() { | |
330 next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE; | |
331 return auth_->MaybeGenerateAuthToken( | |
332 &request_, | |
333 base::Bind(&SpdyProxyClientSocket::OnIOComplete, | |
334 weak_factory_.GetWeakPtr()), | |
335 net_log_); | |
336 } | |
337 | |
338 int SpdyProxyClientSocket::DoGenerateAuthTokenComplete(int result) { | |
339 DCHECK_NE(ERR_IO_PENDING, result); | |
340 if (result == OK) | |
341 next_state_ = STATE_SEND_REQUEST; | |
342 return result; | |
343 } | |
344 | |
345 int SpdyProxyClientSocket::DoSendRequest() { | |
346 next_state_ = STATE_SEND_REQUEST_COMPLETE; | |
347 | |
348 // Add Proxy-Authentication header if necessary. | |
349 HttpRequestHeaders authorization_headers; | |
350 if (auth_->HaveAuth()) { | |
351 auth_->AddAuthorizationHeader(&authorization_headers); | |
352 } | |
353 | |
354 SpdyString request_line; | |
355 BuildTunnelRequest(endpoint_, authorization_headers, user_agent_, | |
356 &request_line, &request_.extra_headers); | |
357 | |
358 net_log_.AddEvent( | |
359 NetLogEventType::HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, | |
360 base::Bind(&HttpRequestHeaders::NetLogCallback, | |
361 base::Unretained(&request_.extra_headers), &request_line)); | |
362 | |
363 SpdyHeaderBlock headers; | |
364 CreateSpdyHeadersFromHttpRequest(request_, request_.extra_headers, true, | |
365 &headers); | |
366 | |
367 return spdy_stream_->SendRequestHeaders(std::move(headers), | |
368 MORE_DATA_TO_SEND); | |
369 } | |
370 | |
371 int SpdyProxyClientSocket::DoSendRequestComplete(int result) { | |
372 if (result < 0) | |
373 return result; | |
374 | |
375 // Wait for HEADERS frame from the server | |
376 next_state_ = STATE_READ_REPLY_COMPLETE; | |
377 return ERR_IO_PENDING; | |
378 } | |
379 | |
380 int SpdyProxyClientSocket::DoReadReplyComplete(int result) { | |
381 // We enter this method directly from DoSendRequestComplete, since | |
382 // we are notified by a callback when the HEADERS frame arrives. | |
383 | |
384 if (result < 0) | |
385 return result; | |
386 | |
387 // Require the "HTTP/1.x" status line for SSL CONNECT. | |
388 if (response_.headers->GetHttpVersion() < HttpVersion(1, 0)) | |
389 return ERR_TUNNEL_CONNECTION_FAILED; | |
390 | |
391 net_log_.AddEvent( | |
392 NetLogEventType::HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, | |
393 base::Bind(&HttpResponseHeaders::NetLogCallback, response_.headers)); | |
394 | |
395 switch (response_.headers->response_code()) { | |
396 case 200: // OK | |
397 next_state_ = STATE_OPEN; | |
398 return OK; | |
399 | |
400 case 302: // Found / Moved Temporarily | |
401 // Try to return a sanitized response so we can follow auth redirects. | |
402 // If we can't, fail the tunnel connection. | |
403 if (!SanitizeProxyRedirect(&response_)) { | |
404 LogBlockedTunnelResponse(); | |
405 return ERR_TUNNEL_CONNECTION_FAILED; | |
406 } | |
407 | |
408 redirect_has_load_timing_info_ = | |
409 spdy_stream_->GetLoadTimingInfo(&redirect_load_timing_info_); | |
410 // Note that this triggers a ERROR_CODE_CANCEL. | |
411 spdy_stream_->DetachDelegate(); | |
412 next_state_ = STATE_DISCONNECTED; | |
413 return ERR_HTTPS_PROXY_TUNNEL_RESPONSE; | |
414 | |
415 case 407: // Proxy Authentication Required | |
416 next_state_ = STATE_OPEN; | |
417 if (!SanitizeProxyAuth(&response_)) { | |
418 LogBlockedTunnelResponse(); | |
419 return ERR_TUNNEL_CONNECTION_FAILED; | |
420 } | |
421 return HandleProxyAuthChallenge(auth_.get(), &response_, net_log_); | |
422 | |
423 default: | |
424 // Ignore response to avoid letting the proxy impersonate the target | |
425 // server. (See http://crbug.com/137891.) | |
426 LogBlockedTunnelResponse(); | |
427 return ERR_TUNNEL_CONNECTION_FAILED; | |
428 } | |
429 } | |
430 | |
431 // SpdyStream::Delegate methods: | |
432 // Called when SYN frame has been sent. | |
433 // Returns true if no more data to be sent after SYN frame. | |
434 void SpdyProxyClientSocket::OnHeadersSent() { | |
435 DCHECK_EQ(next_state_, STATE_SEND_REQUEST_COMPLETE); | |
436 | |
437 OnIOComplete(OK); | |
438 } | |
439 | |
440 void SpdyProxyClientSocket::OnHeadersReceived( | |
441 const SpdyHeaderBlock& response_headers) { | |
442 // If we've already received the reply, existing headers are too late. | |
443 // TODO(mbelshe): figure out a way to make HEADERS frames useful after the | |
444 // initial response. | |
445 if (next_state_ != STATE_READ_REPLY_COMPLETE) | |
446 return; | |
447 | |
448 // Save the response | |
449 const bool headers_valid = | |
450 SpdyHeadersToHttpResponse(response_headers, &response_); | |
451 DCHECK(headers_valid); | |
452 | |
453 OnIOComplete(OK); | |
454 } | |
455 | |
456 // Called when data is received or on EOF (if |buffer| is NULL). | |
457 void SpdyProxyClientSocket::OnDataReceived(std::unique_ptr<SpdyBuffer> buffer) { | |
458 if (buffer) { | |
459 net_log_.AddByteTransferEvent(NetLogEventType::SOCKET_BYTES_RECEIVED, | |
460 buffer->GetRemainingSize(), | |
461 buffer->GetRemainingData()); | |
462 read_buffer_queue_.Enqueue(std::move(buffer)); | |
463 } else { | |
464 net_log_.AddByteTransferEvent(NetLogEventType::SOCKET_BYTES_RECEIVED, 0, | |
465 NULL); | |
466 } | |
467 | |
468 if (!read_callback_.is_null()) { | |
469 int rv = PopulateUserReadBuffer(user_buffer_->data(), user_buffer_len_); | |
470 CompletionCallback c = read_callback_; | |
471 read_callback_.Reset(); | |
472 user_buffer_ = NULL; | |
473 user_buffer_len_ = 0; | |
474 c.Run(rv); | |
475 } | |
476 } | |
477 | |
478 void SpdyProxyClientSocket::OnDataSent() { | |
479 DCHECK(!write_callback_.is_null()); | |
480 | |
481 int rv = write_buffer_len_; | |
482 write_buffer_len_ = 0; | |
483 | |
484 // Proxy write callbacks result in deep callback chains. Post to allow the | |
485 // stream's write callback chain to unwind (see crbug.com/355511). | |
486 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
487 FROM_HERE, base::Bind(&SpdyProxyClientSocket::RunCallback, | |
488 write_callback_weak_factory_.GetWeakPtr(), | |
489 base::ResetAndReturn(&write_callback_), rv)); | |
490 } | |
491 | |
492 void SpdyProxyClientSocket::OnTrailers(const SpdyHeaderBlock& trailers) { | |
493 // |spdy_stream_| is of type SPDY_BIDIRECTIONAL_STREAM, so trailers are | |
494 // combined with response headers and this method will not be calld. | |
495 NOTREACHED(); | |
496 } | |
497 | |
498 void SpdyProxyClientSocket::OnClose(int status) { | |
499 was_ever_used_ = spdy_stream_->WasEverUsed(); | |
500 spdy_stream_.reset(); | |
501 | |
502 bool connecting = next_state_ != STATE_DISCONNECTED && | |
503 next_state_ < STATE_OPEN; | |
504 if (next_state_ == STATE_OPEN) | |
505 next_state_ = STATE_CLOSED; | |
506 else | |
507 next_state_ = STATE_DISCONNECTED; | |
508 | |
509 base::WeakPtr<SpdyProxyClientSocket> weak_ptr = weak_factory_.GetWeakPtr(); | |
510 CompletionCallback write_callback = write_callback_; | |
511 write_callback_.Reset(); | |
512 write_buffer_len_ = 0; | |
513 | |
514 // If we're in the middle of connecting, we need to make sure | |
515 // we invoke the connect callback. | |
516 if (connecting) { | |
517 DCHECK(!read_callback_.is_null()); | |
518 CompletionCallback read_callback = read_callback_; | |
519 read_callback_.Reset(); | |
520 read_callback.Run(status); | |
521 } else if (!read_callback_.is_null()) { | |
522 // If we have a read_callback_, the we need to make sure we call it back. | |
523 OnDataReceived(std::unique_ptr<SpdyBuffer>()); | |
524 } | |
525 // This may have been deleted by read_callback_, so check first. | |
526 if (weak_ptr.get() && !write_callback.is_null()) | |
527 write_callback.Run(ERR_CONNECTION_CLOSED); | |
528 } | |
529 | |
530 NetLogSource SpdyProxyClientSocket::source_dependency() const { | |
531 return source_dependency_; | |
532 } | |
533 | |
534 } // namespace net | |
OLD | NEW |