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