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 "base/callback.h"
8#include "base/compiler_specific.h"
9#include "base/string_util.h"
10#include "base/utf_string_conversions.h"
11#include "net/base/mock_host_resolver.h"
12#include "net/base/net_errors.h"
13#include "net/base/ssl_config_service_defaults.h"
14#include "net/base/test_completion_callback.h"
15#include "net/http/http_auth_handler_factory.h"
16#include "net/http/http_network_session.h"
17#include "net/http/http_proxy_client_socket.h"
18#include "net/proxy/proxy_service.h"
19#include "net/socket/client_socket_handle.h"
20#include "net/socket/client_socket_pool_histograms.h"
21#include "net/socket/socket_test_util.h"
22#include "net/spdy/spdy_protocol.h"
23#include "net/spdy/spdy_test_util.h"
24#include "testing/gtest/include/gtest/gtest.h"
25
26namespace net {
27
28namespace {
29
30const int kMaxSockets = 32;
31const int kMaxSocketsPerGroup = 6;
32const char * const kAuthHeaders[] = {
33  "proxy-authorization", "Basic Zm9vOmJhcg=="
34};
35const int kAuthHeadersSize = arraysize(kAuthHeaders) / 2;
36
37enum HttpProxyType {
38  HTTP,
39  HTTPS,
40  SPDY
41};
42
43typedef ::testing::TestWithParam<HttpProxyType> TestWithHttpParam;
44
45}  // namespace
46
47class HttpProxyClientSocketPoolTest : public TestWithHttpParam {
48 protected:
49  HttpProxyClientSocketPoolTest()
50      : ssl_config_(),
51        ignored_transport_socket_params_(new TransportSocketParams(
52            HostPortPair("proxy", 80), LOWEST, GURL(), false, false)),
53        ignored_ssl_socket_params_(new SSLSocketParams(
54            ignored_transport_socket_params_, NULL, NULL,
55            ProxyServer::SCHEME_DIRECT, HostPortPair("www.google.com", 443),
56            ssl_config_, 0, false, false)),
57        tcp_histograms_("MockTCP"),
58        transport_socket_pool_(
59            kMaxSockets, kMaxSocketsPerGroup,
60            &tcp_histograms_,
61            &socket_factory_),
62        ssl_histograms_("MockSSL"),
63        proxy_service_(ProxyService::CreateDirect()),
64        ssl_config_service_(new SSLConfigServiceDefaults),
65        ssl_socket_pool_(kMaxSockets, kMaxSocketsPerGroup,
66                         &ssl_histograms_,
67                         &host_resolver_,
68                         &cert_verifier_,
69                         NULL /* dnsrr_resolver */,
70                         NULL /* dns_cert_checker */,
71                         NULL /* ssl_host_info_factory */,
72                         &socket_factory_,
73                         &transport_socket_pool_,
74                         NULL,
75                         NULL,
76                         ssl_config_service_.get(),
77                         BoundNetLog().net_log()),
78        http_auth_handler_factory_(
79            HttpAuthHandlerFactory::CreateDefault(&host_resolver_)),
80        session_(CreateNetworkSession()),
81        http_proxy_histograms_("HttpProxyUnitTest"),
82        ssl_data_(NULL),
83        data_(NULL),
84        pool_(kMaxSockets, kMaxSocketsPerGroup,
85              &http_proxy_histograms_,
86              NULL,
87              &transport_socket_pool_,
88              &ssl_socket_pool_,
89              NULL) {
90  }
91
92  virtual ~HttpProxyClientSocketPoolTest() {
93  }
94
95  void AddAuthToCache() {
96    const string16 kFoo(ASCIIToUTF16("foo"));
97    const string16 kBar(ASCIIToUTF16("bar"));
98    session_->http_auth_cache()->Add(GURL("http://proxy/"),
99                                     "MyRealm1",
100                                     HttpAuth::AUTH_SCHEME_BASIC,
101                                     "Basic realm=MyRealm1",
102                                     kFoo,
103                                     kBar,
104                                     "/");
105  }
106
107  scoped_refptr<TransportSocketParams> GetTcpParams() {
108    if (GetParam() != HTTP)
109      return scoped_refptr<TransportSocketParams>();
110    return ignored_transport_socket_params_;
111  }
112
113  scoped_refptr<SSLSocketParams> GetSslParams() {
114    if (GetParam() == HTTP)
115      return scoped_refptr<SSLSocketParams>();
116    return ignored_ssl_socket_params_;
117  }
118
119  // Returns the a correctly constructed HttpProxyParms
120  // for the HTTP or HTTPS proxy.
121  scoped_refptr<HttpProxySocketParams> GetParams(bool tunnel) {
122    return scoped_refptr<HttpProxySocketParams>(
123        new HttpProxySocketParams(
124            GetTcpParams(),
125            GetSslParams(),
126            GURL(tunnel ? "https://www.google.com/" : "http://www.google.com"),
127            "",
128            HostPortPair("www.google.com", tunnel ? 443 : 80),
129            session_->http_auth_cache(),
130            session_->http_auth_handler_factory(),
131            session_->spdy_session_pool(),
132            tunnel));
133  }
134
135  scoped_refptr<HttpProxySocketParams> GetTunnelParams() {
136    return GetParams(true);
137  }
138
139  scoped_refptr<HttpProxySocketParams> GetNoTunnelParams() {
140    return GetParams(false);
141  }
142
143  DeterministicMockClientSocketFactory& socket_factory() {
144    return socket_factory_;
145  }
146
147  void Initialize(bool async, MockRead* reads, size_t reads_count,
148                  MockWrite* writes, size_t writes_count,
149                  MockRead* spdy_reads, size_t spdy_reads_count,
150                  MockWrite* spdy_writes, size_t spdy_writes_count) {
151    if (GetParam() == SPDY)
152      data_ = new DeterministicSocketData(spdy_reads, spdy_reads_count,
153                                          spdy_writes, spdy_writes_count);
154    else
155      data_ = new DeterministicSocketData(reads, reads_count, writes,
156                                          writes_count);
157
158    data_->set_connect_data(MockConnect(async, 0));
159    data_->StopAfter(2);  // Request / Response
160
161    socket_factory_.AddSocketDataProvider(data_.get());
162
163    if (GetParam() != HTTP) {
164      ssl_data_.reset(new SSLSocketDataProvider(async, OK));
165      if (GetParam() == SPDY) {
166        InitializeSpdySsl();
167      }
168      socket_factory_.AddSSLSocketDataProvider(ssl_data_.get());
169    }
170  }
171
172  void InitializeSpdySsl() {
173    spdy::SpdyFramer::set_enable_compression_default(false);
174    ssl_data_->next_proto_status = SSLClientSocket::kNextProtoNegotiated;
175    ssl_data_->next_proto = "spdy/2";
176    ssl_data_->was_npn_negotiated = true;
177  }
178
179  HttpNetworkSession* CreateNetworkSession() {
180    HttpNetworkSession::Params params;
181    params.host_resolver = &host_resolver_;
182    params.cert_verifier = &cert_verifier_;
183    params.proxy_service = proxy_service_;
184    params.client_socket_factory = &socket_factory_;
185    params.ssl_config_service = ssl_config_service_;
186    params.http_auth_handler_factory = http_auth_handler_factory_.get();
187    return new HttpNetworkSession(params);
188  }
189
190 private:
191  SSLConfig ssl_config_;
192
193  scoped_refptr<TransportSocketParams> ignored_transport_socket_params_;
194  scoped_refptr<SSLSocketParams> ignored_ssl_socket_params_;
195  ClientSocketPoolHistograms tcp_histograms_;
196  DeterministicMockClientSocketFactory socket_factory_;
197  MockTransportClientSocketPool transport_socket_pool_;
198  ClientSocketPoolHistograms ssl_histograms_;
199  MockHostResolver host_resolver_;
200  CertVerifier cert_verifier_;
201  const scoped_refptr<ProxyService> proxy_service_;
202  const scoped_refptr<SSLConfigService> ssl_config_service_;
203  SSLClientSocketPool ssl_socket_pool_;
204
205  const scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory_;
206  const scoped_refptr<HttpNetworkSession> session_;
207  ClientSocketPoolHistograms http_proxy_histograms_;
208
209 protected:
210  scoped_ptr<SSLSocketDataProvider> ssl_data_;
211  scoped_refptr<DeterministicSocketData> data_;
212  HttpProxyClientSocketPool pool_;
213  ClientSocketHandle handle_;
214  TestCompletionCallback callback_;
215};
216
217//-----------------------------------------------------------------------------
218// All tests are run with three different proxy types: HTTP, HTTPS (non-SPDY)
219// and SPDY.
220INSTANTIATE_TEST_CASE_P(HttpProxyClientSocketPoolTests,
221                        HttpProxyClientSocketPoolTest,
222                        ::testing::Values(HTTP, HTTPS, SPDY));
223
224TEST_P(HttpProxyClientSocketPoolTest, NoTunnel) {
225  Initialize(false, NULL, 0, NULL, 0, NULL, 0, NULL, 0);
226
227  int rv = handle_.Init("a", GetNoTunnelParams(), LOW, NULL, &pool_,
228                       BoundNetLog());
229  EXPECT_EQ(OK, rv);
230  EXPECT_TRUE(handle_.is_initialized());
231  ASSERT_TRUE(handle_.socket());
232  HttpProxyClientSocket* tunnel_socket =
233          static_cast<HttpProxyClientSocket*>(handle_.socket());
234  EXPECT_TRUE(tunnel_socket->IsConnected());
235}
236
237TEST_P(HttpProxyClientSocketPoolTest, NeedAuth) {
238  MockWrite writes[] = {
239    MockWrite(true, 0, "CONNECT www.google.com:443 HTTP/1.1\r\n"
240              "Host: www.google.com\r\n"
241              "Proxy-Connection: keep-alive\r\n\r\n"),
242  };
243  MockRead reads[] = {
244    // No credentials.
245    MockRead(true, 1, "HTTP/1.1 407 Proxy Authentication Required\r\n"),
246    MockRead(true, 2, "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
247    MockRead(true, 3, "Content-Length: 10\r\n\r\n"),
248    MockRead(true, 4, "0123456789"),
249  };
250  scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(NULL, 0, 1));
251  scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(1, spdy::CANCEL));
252  MockWrite spdy_writes[] = {
253    CreateMockWrite(*req, 0, true),
254    CreateMockWrite(*rst, 2, true),
255  };
256  scoped_ptr<spdy::SpdyFrame> resp(
257      ConstructSpdySynReplyError(
258          "407 Proxy Authentication Required", NULL, 0, 1));
259  MockRead spdy_reads[] = {
260    CreateMockWrite(*resp, 1, true),
261    MockRead(true, 0, 3)
262  };
263
264  Initialize(false, reads, arraysize(reads), writes, arraysize(writes),
265             spdy_reads, arraysize(spdy_reads), spdy_writes,
266             arraysize(spdy_writes));
267
268  data_->StopAfter(4);
269  int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
270                       BoundNetLog());
271  EXPECT_EQ(ERR_IO_PENDING, rv);
272  EXPECT_FALSE(handle_.is_initialized());
273  EXPECT_FALSE(handle_.socket());
274
275  data_->RunFor(4);
276  rv = callback_.WaitForResult();
277  if (GetParam() != SPDY) {
278    EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, rv);
279    EXPECT_TRUE(handle_.is_initialized());
280    ASSERT_TRUE(handle_.socket());
281    HttpProxyClientSocket* tunnel_socket =
282            static_cast<HttpProxyClientSocket*>(handle_.socket());
283    EXPECT_FALSE(tunnel_socket->IsConnected());
284    EXPECT_FALSE(tunnel_socket->using_spdy());
285  } else {
286    // Proxy auth is not really implemented for SPDY yet
287    EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
288    EXPECT_FALSE(handle_.is_initialized());
289    EXPECT_FALSE(handle_.socket());
290  }
291}
292
293TEST_P(HttpProxyClientSocketPoolTest, HaveAuth) {
294  // It's pretty much impossible to make the SPDY case becave synchronously
295  // so we skip this test for SPDY
296  if (GetParam() == SPDY)
297    return;
298  MockWrite writes[] = {
299    MockWrite(false, 0,
300              "CONNECT www.google.com:443 HTTP/1.1\r\n"
301              "Host: www.google.com\r\n"
302              "Proxy-Connection: keep-alive\r\n"
303              "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
304  };
305  MockRead reads[] = {
306    MockRead(false, 1, "HTTP/1.1 200 Connection Established\r\n\r\n"),
307  };
308
309  Initialize(false, reads, arraysize(reads), writes, arraysize(writes), NULL, 0,
310             NULL, 0);
311  AddAuthToCache();
312
313  int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
314                       BoundNetLog());
315  EXPECT_EQ(OK, rv);
316  EXPECT_TRUE(handle_.is_initialized());
317  ASSERT_TRUE(handle_.socket());
318  HttpProxyClientSocket* tunnel_socket =
319          static_cast<HttpProxyClientSocket*>(handle_.socket());
320  EXPECT_TRUE(tunnel_socket->IsConnected());
321}
322
323TEST_P(HttpProxyClientSocketPoolTest, AsyncHaveAuth) {
324  MockWrite writes[] = {
325    MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
326              "Host: www.google.com\r\n"
327              "Proxy-Connection: keep-alive\r\n"
328              "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
329  };
330  MockRead reads[] = {
331    MockRead(false, "HTTP/1.1 200 Connection Established\r\n\r\n"),
332  };
333
334  scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(kAuthHeaders,
335                                                       kAuthHeadersSize, 1));
336  MockWrite spdy_writes[] = {
337    CreateMockWrite(*req, 0, true)
338  };
339  scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
340  MockRead spdy_reads[] = {
341    CreateMockRead(*resp, 1, true),
342    MockRead(true, 0, 2)
343  };
344
345  Initialize(false, reads, arraysize(reads), writes, arraysize(writes),
346             spdy_reads, arraysize(spdy_reads), spdy_writes,
347             arraysize(spdy_writes));
348  AddAuthToCache();
349
350  int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
351                       BoundNetLog());
352  EXPECT_EQ(ERR_IO_PENDING, rv);
353  EXPECT_FALSE(handle_.is_initialized());
354  EXPECT_FALSE(handle_.socket());
355
356  data_->RunFor(2);
357  EXPECT_EQ(OK, callback_.WaitForResult());
358  EXPECT_TRUE(handle_.is_initialized());
359  ASSERT_TRUE(handle_.socket());
360  HttpProxyClientSocket* tunnel_socket =
361          static_cast<HttpProxyClientSocket*>(handle_.socket());
362  EXPECT_TRUE(tunnel_socket->IsConnected());
363}
364
365TEST_P(HttpProxyClientSocketPoolTest, TCPError) {
366  if (GetParam() == SPDY) return;
367  data_ = new DeterministicSocketData(NULL, 0, NULL, 0);
368  data_->set_connect_data(MockConnect(true, ERR_CONNECTION_CLOSED));
369
370  socket_factory().AddSocketDataProvider(data_.get());
371
372  int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
373                       BoundNetLog());
374  EXPECT_EQ(ERR_IO_PENDING, rv);
375  EXPECT_FALSE(handle_.is_initialized());
376  EXPECT_FALSE(handle_.socket());
377
378  EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, callback_.WaitForResult());
379
380  EXPECT_FALSE(handle_.is_initialized());
381  EXPECT_FALSE(handle_.socket());
382}
383
384TEST_P(HttpProxyClientSocketPoolTest, SSLError) {
385  if (GetParam() == HTTP) return;
386  data_ = new DeterministicSocketData(NULL, 0, NULL, 0);
387  data_->set_connect_data(MockConnect(true, OK));
388  socket_factory().AddSocketDataProvider(data_.get());
389
390  ssl_data_.reset(new SSLSocketDataProvider(true,
391                                            ERR_CERT_AUTHORITY_INVALID));
392  if (GetParam() == SPDY) {
393    InitializeSpdySsl();
394  }
395  socket_factory().AddSSLSocketDataProvider(ssl_data_.get());
396
397  int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
398                        BoundNetLog());
399  EXPECT_EQ(ERR_IO_PENDING, rv);
400  EXPECT_FALSE(handle_.is_initialized());
401  EXPECT_FALSE(handle_.socket());
402
403  EXPECT_EQ(ERR_PROXY_CERTIFICATE_INVALID, callback_.WaitForResult());
404
405  EXPECT_FALSE(handle_.is_initialized());
406  EXPECT_FALSE(handle_.socket());
407}
408
409TEST_P(HttpProxyClientSocketPoolTest, SslClientAuth) {
410  if (GetParam() == HTTP) return;
411  data_ = new DeterministicSocketData(NULL, 0, NULL, 0);
412  data_->set_connect_data(MockConnect(true, OK));
413  socket_factory().AddSocketDataProvider(data_.get());
414
415  ssl_data_.reset(new SSLSocketDataProvider(true,
416                                            ERR_SSL_CLIENT_AUTH_CERT_NEEDED));
417  if (GetParam() == SPDY) {
418    InitializeSpdySsl();
419  }
420  socket_factory().AddSSLSocketDataProvider(ssl_data_.get());
421
422  int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
423                       BoundNetLog());
424  EXPECT_EQ(ERR_IO_PENDING, rv);
425  EXPECT_FALSE(handle_.is_initialized());
426  EXPECT_FALSE(handle_.socket());
427
428  EXPECT_EQ(ERR_SSL_CLIENT_AUTH_CERT_NEEDED, callback_.WaitForResult());
429
430  EXPECT_FALSE(handle_.is_initialized());
431  EXPECT_FALSE(handle_.socket());
432}
433
434TEST_P(HttpProxyClientSocketPoolTest, TunnelUnexpectedClose) {
435  MockWrite writes[] = {
436    MockWrite(true, 0,
437              "CONNECT www.google.com:443 HTTP/1.1\r\n"
438              "Host: www.google.com\r\n"
439              "Proxy-Connection: keep-alive\r\n"
440              "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
441  };
442  MockRead reads[] = {
443    MockRead(true, 1, "HTTP/1.1 200 Conn"),
444    MockRead(true, ERR_CONNECTION_CLOSED, 2),
445  };
446  scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(kAuthHeaders,
447                                                       kAuthHeadersSize, 1));
448  MockWrite spdy_writes[] = {
449    CreateMockWrite(*req, 0, true)
450  };
451  MockRead spdy_reads[] = {
452    MockRead(true, ERR_CONNECTION_CLOSED, 1),
453  };
454
455  Initialize(false, reads, arraysize(reads), writes, arraysize(writes),
456             spdy_reads, arraysize(spdy_reads), spdy_writes,
457             arraysize(spdy_writes));
458  AddAuthToCache();
459
460  int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
461                       BoundNetLog());
462  EXPECT_EQ(ERR_IO_PENDING, rv);
463  EXPECT_FALSE(handle_.is_initialized());
464  EXPECT_FALSE(handle_.socket());
465
466  data_->RunFor(3);
467  EXPECT_EQ(ERR_CONNECTION_CLOSED, callback_.WaitForResult());
468  EXPECT_FALSE(handle_.is_initialized());
469  EXPECT_FALSE(handle_.socket());
470}
471
472TEST_P(HttpProxyClientSocketPoolTest, TunnelSetupError) {
473  MockWrite writes[] = {
474    MockWrite(true, 0,
475              "CONNECT www.google.com:443 HTTP/1.1\r\n"
476              "Host: www.google.com\r\n"
477              "Proxy-Connection: keep-alive\r\n"
478              "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
479  };
480  MockRead reads[] = {
481    MockRead(true, 1, "HTTP/1.1 304 Not Modified\r\n\r\n"),
482  };
483  scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(kAuthHeaders,
484                                                       kAuthHeadersSize, 1));
485  scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(1, spdy::CANCEL));
486  MockWrite spdy_writes[] = {
487    CreateMockWrite(*req, 0, true),
488    CreateMockWrite(*rst, 2, true),
489  };
490  scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdySynReplyError(1));
491  MockRead spdy_reads[] = {
492    CreateMockRead(*resp, 1, true),
493    MockRead(true, 0, 3),
494  };
495
496  Initialize(false, reads, arraysize(reads), writes, arraysize(writes),
497             spdy_reads, arraysize(spdy_reads), spdy_writes,
498             arraysize(spdy_writes));
499  AddAuthToCache();
500
501  int rv = handle_.Init("a", GetTunnelParams(), LOW, &callback_, &pool_,
502                       BoundNetLog());
503  EXPECT_EQ(ERR_IO_PENDING, rv);
504  EXPECT_FALSE(handle_.is_initialized());
505  EXPECT_FALSE(handle_.socket());
506
507  data_->RunFor(2);
508
509  rv = callback_.WaitForResult();
510  if (GetParam() == HTTP) {
511    // HTTP Proxy CONNECT responses are not trustworthy
512    EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
513    EXPECT_FALSE(handle_.is_initialized());
514    EXPECT_FALSE(handle_.socket());
515  } else {
516    // HTTPS or SPDY Proxy CONNECT responses are trustworthy
517    EXPECT_EQ(ERR_HTTPS_PROXY_TUNNEL_RESPONSE, rv);
518    EXPECT_TRUE(handle_.is_initialized());
519    EXPECT_TRUE(handle_.socket());
520  }
521}
522
523// It would be nice to also test the timeouts in HttpProxyClientSocketPool.
524
525}  // namespace net
526