1// Copyright (c) 2011 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/http/http_proxy_client_socket_pool.h"
6
7#include <algorithm>
8
9#include "base/time.h"
10#include "base/values.h"
11#include "googleurl/src/gurl.h"
12#include "net/base/load_flags.h"
13#include "net/base/net_errors.h"
14#include "net/http/http_network_session.h"
15#include "net/http/http_proxy_client_socket.h"
16#include "net/socket/client_socket_factory.h"
17#include "net/socket/client_socket_handle.h"
18#include "net/socket/client_socket_pool_base.h"
19#include "net/socket/ssl_client_socket.h"
20#include "net/socket/ssl_client_socket_pool.h"
21#include "net/socket/transport_client_socket_pool.h"
22#include "net/spdy/spdy_proxy_client_socket.h"
23#include "net/spdy/spdy_session.h"
24#include "net/spdy/spdy_session_pool.h"
25#include "net/spdy/spdy_settings_storage.h"
26#include "net/spdy/spdy_stream.h"
27
28namespace net {
29
30HttpProxySocketParams::HttpProxySocketParams(
31    const scoped_refptr<TransportSocketParams>& transport_params,
32    const scoped_refptr<SSLSocketParams>& ssl_params,
33    const GURL& request_url,
34    const std::string& user_agent,
35    HostPortPair endpoint,
36    HttpAuthCache* http_auth_cache,
37    HttpAuthHandlerFactory* http_auth_handler_factory,
38    SpdySessionPool* spdy_session_pool,
39    bool tunnel)
40    : transport_params_(transport_params),
41      ssl_params_(ssl_params),
42      spdy_session_pool_(spdy_session_pool),
43      request_url_(request_url),
44      user_agent_(user_agent),
45      endpoint_(endpoint),
46      http_auth_cache_(tunnel ? http_auth_cache : NULL),
47      http_auth_handler_factory_(tunnel ? http_auth_handler_factory : NULL),
48      tunnel_(tunnel) {
49  DCHECK((transport_params == NULL && ssl_params != NULL) ||
50         (transport_params != NULL && ssl_params == NULL));
51  if (transport_params_)
52    ignore_limits_ = transport_params->ignore_limits();
53  else
54    ignore_limits_ = ssl_params->ignore_limits();
55}
56
57const HostResolver::RequestInfo& HttpProxySocketParams::destination() const {
58  if (transport_params_ == NULL)
59    return ssl_params_->transport_params()->destination();
60  else
61    return transport_params_->destination();
62}
63
64HttpProxySocketParams::~HttpProxySocketParams() {}
65
66#ifdef ANDROID
67bool HttpProxySocketParams::getUID(uid_t *uid) const {
68  if (transport_params_ == NULL)
69    return ssl_params_->transport_params()->getUID(uid);
70  else
71    return transport_params_->getUID(uid);
72}
73
74void HttpProxySocketParams::setUID(uid_t uid) {
75  if (transport_params_ == NULL)
76    ssl_params_->transport_params()->setUID(uid);
77  else
78    transport_params_->setUID(uid);
79}
80#endif
81
82// HttpProxyConnectJobs will time out after this many seconds.  Note this is on
83// top of the timeout for the transport socket.
84static const int kHttpProxyConnectJobTimeoutInSeconds = 30;
85
86HttpProxyConnectJob::HttpProxyConnectJob(
87    const std::string& group_name,
88    const scoped_refptr<HttpProxySocketParams>& params,
89    const base::TimeDelta& timeout_duration,
90    TransportClientSocketPool* transport_pool,
91    SSLClientSocketPool* ssl_pool,
92    HostResolver* host_resolver,
93    Delegate* delegate,
94    NetLog* net_log)
95    : ConnectJob(group_name, timeout_duration, delegate,
96                 BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)),
97      params_(params),
98      transport_pool_(transport_pool),
99      ssl_pool_(ssl_pool),
100      resolver_(host_resolver),
101      ALLOW_THIS_IN_INITIALIZER_LIST(
102          callback_(this, &HttpProxyConnectJob::OnIOComplete)),
103      using_spdy_(false) {
104}
105
106HttpProxyConnectJob::~HttpProxyConnectJob() {}
107
108LoadState HttpProxyConnectJob::GetLoadState() const {
109  switch (next_state_) {
110    case STATE_TCP_CONNECT:
111    case STATE_TCP_CONNECT_COMPLETE:
112    case STATE_SSL_CONNECT:
113    case STATE_SSL_CONNECT_COMPLETE:
114      return transport_socket_handle_->GetLoadState();
115    case STATE_HTTP_PROXY_CONNECT:
116    case STATE_HTTP_PROXY_CONNECT_COMPLETE:
117    case STATE_SPDY_PROXY_CREATE_STREAM:
118    case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE:
119      return LOAD_STATE_ESTABLISHING_PROXY_TUNNEL;
120    default:
121      NOTREACHED();
122      return LOAD_STATE_IDLE;
123  }
124}
125
126void HttpProxyConnectJob::GetAdditionalErrorState(ClientSocketHandle * handle) {
127  if (error_response_info_.cert_request_info) {
128    handle->set_ssl_error_response_info(error_response_info_);
129    handle->set_is_ssl_error(true);
130  }
131}
132
133void HttpProxyConnectJob::OnIOComplete(int result) {
134  int rv = DoLoop(result);
135  if (rv != ERR_IO_PENDING)
136    NotifyDelegateOfCompletion(rv);  // Deletes |this|
137}
138
139int HttpProxyConnectJob::DoLoop(int result) {
140  DCHECK_NE(next_state_, STATE_NONE);
141
142  int rv = result;
143  do {
144    State state = next_state_;
145    next_state_ = STATE_NONE;
146    switch (state) {
147      case STATE_TCP_CONNECT:
148        DCHECK_EQ(OK, rv);
149        rv = DoTransportConnect();
150        break;
151      case STATE_TCP_CONNECT_COMPLETE:
152        rv = DoTransportConnectComplete(rv);
153        break;
154      case STATE_SSL_CONNECT:
155        DCHECK_EQ(OK, rv);
156        rv = DoSSLConnect();
157        break;
158      case STATE_SSL_CONNECT_COMPLETE:
159        rv = DoSSLConnectComplete(rv);
160        break;
161      case STATE_HTTP_PROXY_CONNECT:
162        DCHECK_EQ(OK, rv);
163        rv = DoHttpProxyConnect();
164        break;
165      case STATE_HTTP_PROXY_CONNECT_COMPLETE:
166        rv = DoHttpProxyConnectComplete(rv);
167        break;
168      case STATE_SPDY_PROXY_CREATE_STREAM:
169        DCHECK_EQ(OK, rv);
170        rv = DoSpdyProxyCreateStream();
171        break;
172      case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE:
173        rv = DoSpdyProxyCreateStreamComplete(rv);
174        break;
175      default:
176        NOTREACHED() << "bad state";
177        rv = ERR_FAILED;
178        break;
179    }
180  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
181
182  return rv;
183}
184
185int HttpProxyConnectJob::DoTransportConnect() {
186  next_state_ = STATE_TCP_CONNECT_COMPLETE;
187  transport_socket_handle_.reset(new ClientSocketHandle());
188  return transport_socket_handle_->Init(
189      group_name(),
190      params_->transport_params(),
191      params_->transport_params()->destination().priority(),
192      &callback_,
193      transport_pool_,
194      net_log());
195}
196
197int HttpProxyConnectJob::DoTransportConnectComplete(int result) {
198  if (result != OK)
199    return ERR_PROXY_CONNECTION_FAILED;
200
201  // Reset the timer to just the length of time allowed for HttpProxy handshake
202  // so that a fast TCP connection plus a slow HttpProxy failure doesn't take
203  // longer to timeout than it should.
204  ResetTimer(base::TimeDelta::FromSeconds(
205      kHttpProxyConnectJobTimeoutInSeconds));
206
207  next_state_ = STATE_HTTP_PROXY_CONNECT;
208  return result;
209}
210
211int HttpProxyConnectJob::DoSSLConnect() {
212  if (params_->tunnel()) {
213    HostPortProxyPair pair(params_->destination().host_port_pair(),
214                           ProxyServer::Direct());
215    if (params_->spdy_session_pool()->HasSession(pair)) {
216      using_spdy_ = true;
217      next_state_ = STATE_SPDY_PROXY_CREATE_STREAM;
218      return OK;
219    }
220  }
221  next_state_ = STATE_SSL_CONNECT_COMPLETE;
222  transport_socket_handle_.reset(new ClientSocketHandle());
223  return transport_socket_handle_->Init(
224      group_name(), params_->ssl_params(),
225      params_->ssl_params()->transport_params()->destination().priority(),
226      &callback_, ssl_pool_, net_log());
227}
228
229int HttpProxyConnectJob::DoSSLConnectComplete(int result) {
230  if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
231    error_response_info_ = transport_socket_handle_->ssl_error_response_info();
232    DCHECK(error_response_info_.cert_request_info.get());
233    return result;
234  }
235  if (IsCertificateError(result)) {
236    if (params_->ssl_params()->load_flags() & LOAD_IGNORE_ALL_CERT_ERRORS)
237      result = OK;
238    else
239      // TODO(rch): allow the user to deal with proxy cert errors in the
240      // same way as server cert errors.
241      return ERR_PROXY_CERTIFICATE_INVALID;
242  }
243  if (result < 0) {
244    if (transport_socket_handle_->socket())
245      transport_socket_handle_->socket()->Disconnect();
246    return ERR_PROXY_CONNECTION_FAILED;
247  }
248
249  SSLClientSocket* ssl =
250      static_cast<SSLClientSocket*>(transport_socket_handle_->socket());
251  using_spdy_ = ssl->was_spdy_negotiated();
252
253  // Reset the timer to just the length of time allowed for HttpProxy handshake
254  // so that a fast SSL connection plus a slow HttpProxy failure doesn't take
255  // longer to timeout than it should.
256  ResetTimer(base::TimeDelta::FromSeconds(
257      kHttpProxyConnectJobTimeoutInSeconds));
258  // TODO(rch): If we ever decide to implement a "trusted" SPDY proxy
259  // (one that we speak SPDY over SSL to, but to which we send HTTPS
260  // request directly instead of through CONNECT tunnels, then we
261  // need to add a predicate to this if statement so we fall through
262  // to the else case. (HttpProxyClientSocket currently acts as
263  // a "trusted" SPDY proxy).
264  if (using_spdy_ && params_->tunnel())
265    next_state_ = STATE_SPDY_PROXY_CREATE_STREAM;
266  else
267    next_state_ = STATE_HTTP_PROXY_CONNECT;
268  return result;
269}
270
271#ifdef ANDROID
272// TODO(kristianm): Find out if Connect should block
273#endif
274int HttpProxyConnectJob::DoHttpProxyConnect() {
275  next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
276  const HostResolver::RequestInfo& tcp_destination = params_->destination();
277  const HostPortPair& proxy_server = tcp_destination.host_port_pair();
278
279  // Add a HttpProxy connection on top of the tcp socket.
280  transport_socket_.reset(
281      new HttpProxyClientSocket(transport_socket_handle_.release(),
282                                params_->request_url(),
283                                params_->user_agent(),
284                                params_->endpoint(),
285                                proxy_server,
286                                params_->http_auth_cache(),
287                                params_->http_auth_handler_factory(),
288                                params_->tunnel(),
289                                using_spdy_,
290                                params_->ssl_params() != NULL));
291
292#ifdef ANDROID
293  uid_t calling_uid = 0;
294  bool valid_uid = params_->transport_params()->getUID(&calling_uid);
295#endif
296
297  return transport_socket_->Connect(&callback_
298#ifdef ANDROID
299                                    , false
300                                    , valid_uid
301                                    , calling_uid
302#endif
303                                   );
304}
305
306int HttpProxyConnectJob::DoHttpProxyConnectComplete(int result) {
307  if (result == OK || result == ERR_PROXY_AUTH_REQUESTED ||
308      result == ERR_HTTPS_PROXY_TUNNEL_RESPONSE) {
309      set_socket(transport_socket_.release());
310  }
311
312  return result;
313}
314
315int HttpProxyConnectJob::DoSpdyProxyCreateStream() {
316  DCHECK(using_spdy_);
317  DCHECK(params_->tunnel());
318
319  HostPortProxyPair pair(params_->destination().host_port_pair(),
320                         ProxyServer::Direct());
321  SpdySessionPool* spdy_pool = params_->spdy_session_pool();
322  scoped_refptr<SpdySession> spdy_session;
323  // It's possible that a session to the proxy has recently been created
324  if (spdy_pool->HasSession(pair)) {
325    if (transport_socket_handle_.get()) {
326      if (transport_socket_handle_->socket())
327        transport_socket_handle_->socket()->Disconnect();
328      transport_socket_handle_->Reset();
329    }
330    spdy_session = spdy_pool->Get(pair, net_log());
331  } else {
332    // Create a session direct to the proxy itself
333    int rv = spdy_pool->GetSpdySessionFromSocket(
334        pair, transport_socket_handle_.release(),
335        net_log(), OK, &spdy_session, /*using_ssl_*/ true);
336    if (rv < 0)
337      return rv;
338  }
339
340  next_state_ = STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE;
341  return spdy_session->CreateStream(params_->request_url(),
342                                    params_->destination().priority(),
343                                    &spdy_stream_, spdy_session->net_log(),
344                                    &callback_);
345}
346
347#ifdef ANDROID
348// TODO(kristianm): Find out if Connect should block
349#endif
350int HttpProxyConnectJob::DoSpdyProxyCreateStreamComplete(int result) {
351  if (result < 0)
352    return result;
353
354#ifdef ANDROID
355  uid_t calling_uid = 0;
356  bool valid_uid = params_->transport_params()->getUID(&calling_uid);
357#endif
358
359  next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
360  transport_socket_.reset(
361      new SpdyProxyClientSocket(spdy_stream_,
362                                params_->user_agent(),
363                                params_->endpoint(),
364                                params_->request_url(),
365                                params_->destination().host_port_pair(),
366                                params_->http_auth_cache(),
367                                params_->http_auth_handler_factory()));
368  return transport_socket_->Connect(&callback_
369#ifdef ANDROID
370                                    , false
371                                    , valid_uid
372                                    , calling_uid
373#endif
374                                   );
375}
376
377int HttpProxyConnectJob::ConnectInternal() {
378  if (params_->transport_params())
379    next_state_ = STATE_TCP_CONNECT;
380  else
381    next_state_ = STATE_SSL_CONNECT;
382  return DoLoop(OK);
383}
384
385HttpProxyClientSocketPool::
386HttpProxyConnectJobFactory::HttpProxyConnectJobFactory(
387    TransportClientSocketPool* transport_pool,
388    SSLClientSocketPool* ssl_pool,
389    HostResolver* host_resolver,
390    NetLog* net_log)
391    : transport_pool_(transport_pool),
392      ssl_pool_(ssl_pool),
393      host_resolver_(host_resolver),
394      net_log_(net_log) {
395  base::TimeDelta max_pool_timeout = base::TimeDelta();
396  if (transport_pool_)
397    max_pool_timeout = transport_pool_->ConnectionTimeout();
398  if (ssl_pool_)
399    max_pool_timeout = std::max(max_pool_timeout,
400                                ssl_pool_->ConnectionTimeout());
401  timeout_ = max_pool_timeout +
402    base::TimeDelta::FromSeconds(kHttpProxyConnectJobTimeoutInSeconds);
403}
404
405
406ConnectJob*
407HttpProxyClientSocketPool::HttpProxyConnectJobFactory::NewConnectJob(
408    const std::string& group_name,
409    const PoolBase::Request& request,
410    ConnectJob::Delegate* delegate) const {
411  return new HttpProxyConnectJob(group_name,
412                                 request.params(),
413                                 ConnectionTimeout(),
414                                 transport_pool_,
415                                 ssl_pool_,
416                                 host_resolver_,
417                                 delegate,
418                                 net_log_);
419}
420
421HttpProxyClientSocketPool::HttpProxyClientSocketPool(
422    int max_sockets,
423    int max_sockets_per_group,
424    ClientSocketPoolHistograms* histograms,
425    HostResolver* host_resolver,
426    TransportClientSocketPool* transport_pool,
427    SSLClientSocketPool* ssl_pool,
428    NetLog* net_log)
429    : transport_pool_(transport_pool),
430      ssl_pool_(ssl_pool),
431      base_(max_sockets, max_sockets_per_group, histograms,
432            base::TimeDelta::FromSeconds(
433                ClientSocketPool::unused_idle_socket_timeout()),
434            base::TimeDelta::FromSeconds(kUsedIdleSocketTimeout),
435            new HttpProxyConnectJobFactory(transport_pool,
436                                           ssl_pool,
437                                           host_resolver,
438                                           net_log)) {}
439
440HttpProxyClientSocketPool::~HttpProxyClientSocketPool() {}
441
442int HttpProxyClientSocketPool::RequestSocket(const std::string& group_name,
443                                             const void* socket_params,
444                                             RequestPriority priority,
445                                             ClientSocketHandle* handle,
446                                             CompletionCallback* callback,
447                                             const BoundNetLog& net_log) {
448  const scoped_refptr<HttpProxySocketParams>* casted_socket_params =
449      static_cast<const scoped_refptr<HttpProxySocketParams>*>(socket_params);
450
451  return base_.RequestSocket(group_name, *casted_socket_params, priority,
452                             handle, callback, net_log);
453}
454
455void HttpProxyClientSocketPool::RequestSockets(
456    const std::string& group_name,
457    const void* params,
458    int num_sockets,
459    const BoundNetLog& net_log) {
460  const scoped_refptr<HttpProxySocketParams>* casted_params =
461      static_cast<const scoped_refptr<HttpProxySocketParams>*>(params);
462
463  base_.RequestSockets(group_name, *casted_params, num_sockets, net_log);
464}
465
466void HttpProxyClientSocketPool::CancelRequest(
467    const std::string& group_name,
468    ClientSocketHandle* handle) {
469  base_.CancelRequest(group_name, handle);
470}
471
472void HttpProxyClientSocketPool::ReleaseSocket(const std::string& group_name,
473                                              ClientSocket* socket, int id) {
474  base_.ReleaseSocket(group_name, socket, id);
475}
476
477void HttpProxyClientSocketPool::Flush() {
478  base_.Flush();
479}
480
481void HttpProxyClientSocketPool::CloseIdleSockets() {
482  base_.CloseIdleSockets();
483}
484
485int HttpProxyClientSocketPool::IdleSocketCount() const {
486  return base_.idle_socket_count();
487}
488
489int HttpProxyClientSocketPool::IdleSocketCountInGroup(
490    const std::string& group_name) const {
491  return base_.IdleSocketCountInGroup(group_name);
492}
493
494LoadState HttpProxyClientSocketPool::GetLoadState(
495    const std::string& group_name, const ClientSocketHandle* handle) const {
496  return base_.GetLoadState(group_name, handle);
497}
498
499DictionaryValue* HttpProxyClientSocketPool::GetInfoAsValue(
500    const std::string& name,
501    const std::string& type,
502    bool include_nested_pools) const {
503  DictionaryValue* dict = base_.GetInfoAsValue(name, type);
504  if (include_nested_pools) {
505    ListValue* list = new ListValue();
506    if (transport_pool_) {
507      list->Append(transport_pool_->GetInfoAsValue("transport_socket_pool",
508                                                   "transport_socket_pool",
509                                                   true));
510    }
511    if (ssl_pool_) {
512      list->Append(ssl_pool_->GetInfoAsValue("ssl_socket_pool",
513                                             "ssl_socket_pool",
514                                             true));
515    }
516    dict->Set("nested_pools", list);
517  }
518  return dict;
519}
520
521base::TimeDelta HttpProxyClientSocketPool::ConnectionTimeout() const {
522  return base_.ConnectionTimeout();
523}
524
525ClientSocketPoolHistograms* HttpProxyClientSocketPool::histograms() const {
526  return base_.histograms();
527}
528
529}  // namespace net
530