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