• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2006-2008 The Chromium Authors
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/proxy_resolution/proxy_list.h"
6 
7 #include <vector>
8 
9 #include "net/base/net_errors.h"
10 #include "net/base/proxy_server.h"
11 #include "net/base/proxy_string_util.h"
12 #include "net/log/net_log_with_source.h"
13 #include "net/proxy_resolution/proxy_retry_info.h"
14 #include "net/test/gtest_util.h"
15 #include "testing/gmock/include/gmock/gmock.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17 
18 using net::test::IsOk;
19 
20 namespace net {
21 
22 namespace {
23 
24 // Test parsing from a PAC string.
TEST(ProxyListTest,SetFromPacString)25 TEST(ProxyListTest, SetFromPacString) {
26   const struct {
27     const char* pac_input;
28     const char* debug_output;
29   } tests[] = {
30     // Valid inputs:
31     {  "PROXY foopy:10",
32        "PROXY foopy:10",
33     },
34     {  " DIRECT",  // leading space.
35        "DIRECT",
36     },
37     {  "PROXY foopy1 ; proxy foopy2;\t DIRECT",
38        "PROXY foopy1:80;PROXY foopy2:80;DIRECT",
39     },
40     {  "proxy foopy1 ; SOCKS foopy2",
41        "PROXY foopy1:80;SOCKS foopy2:1080",
42     },
43     // Try putting DIRECT first.
44     {  "DIRECT ; proxy foopy1 ; DIRECT ; SOCKS5 foopy2;DIRECT ",
45        "DIRECT;PROXY foopy1:80;DIRECT;SOCKS5 foopy2:1080;DIRECT",
46     },
47     // Try putting DIRECT consecutively.
48     {  "DIRECT ; proxy foopy1:80; DIRECT ; DIRECT",
49        "DIRECT;PROXY foopy1:80;DIRECT;DIRECT",
50     },
51 
52     // Invalid inputs (parts which aren't understood get
53     // silently discarded):
54     //
55     // If the proxy list string parsed to empty, automatically fall-back to
56     // DIRECT.
57     {  "PROXY-foopy:10",
58        "DIRECT",
59     },
60     {  "PROXY",
61        "DIRECT",
62     },
63     {  "PROXY foopy1 ; JUNK ; JUNK ; SOCKS5 foopy2 ; ;",
64        "PROXY foopy1:80;SOCKS5 foopy2:1080",
65     },
66   };
67 
68   for (const auto& test : tests) {
69     ProxyList list;
70     list.SetFromPacString(test.pac_input);
71     EXPECT_EQ(test.debug_output, list.ToDebugString());
72     EXPECT_FALSE(list.IsEmpty());
73   }
74 }
75 
TEST(ProxyListTest,RemoveProxiesWithoutScheme)76 TEST(ProxyListTest, RemoveProxiesWithoutScheme) {
77   const struct {
78     const char* pac_input;
79     int filter;
80     const char* filtered_debug_output;
81   } tests[] = {
82     {  "PROXY foopy:10 ; SOCKS5 foopy2 ; SOCKS foopy11 ; PROXY foopy3 ; DIRECT",
83        // Remove anything that isn't HTTP or DIRECT.
84        ProxyServer::SCHEME_DIRECT | ProxyServer::SCHEME_HTTP,
85        "PROXY foopy:10;PROXY foopy3:80;DIRECT",
86     },
87     {  "PROXY foopy:10 ; SOCKS5 foopy2",
88        // Remove anything that isn't HTTP or SOCKS5.
89        ProxyServer::SCHEME_DIRECT | ProxyServer::SCHEME_SOCKS4,
90        "",
91     },
92   };
93 
94   for (const auto& test : tests) {
95     ProxyList list;
96     list.SetFromPacString(test.pac_input);
97     list.RemoveProxiesWithoutScheme(test.filter);
98     EXPECT_EQ(test.filtered_debug_output, list.ToDebugString());
99   }
100 }
101 
TEST(ProxyListTest,RemoveProxiesWithoutSchemeWithProxyChains)102 TEST(ProxyListTest, RemoveProxiesWithoutSchemeWithProxyChains) {
103   const ProxyChain kProxyChainFooHttps({
104       ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS,
105                                          "foo-a", 443),
106       ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS,
107                                          "foo-b", 443),
108   });
109   const ProxyChain kProxyChainBarMixed({
110       ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_SOCKS5,
111                                          "bar-a", 443),
112       ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS,
113                                          "bar-b", 443),
114   });
115   const ProxyChain kProxyChainGraultSocks = ProxyChain::FromSchemeHostAndPort(
116       ProxyServer::Scheme::SCHEME_SOCKS4, "grault", 443);
117 
118   ProxyList list;
119   list.AddProxyChain(kProxyChainFooHttps);
120   list.AddProxyChain(kProxyChainBarMixed);
121   list.AddProxyChain(kProxyChainGraultSocks);
122   list.AddProxyChain(ProxyChain::Direct());
123 
124   // Remove anything that isn't entirely HTTPS or DIRECT.
125   list.RemoveProxiesWithoutScheme(ProxyServer::SCHEME_DIRECT |
126                                   ProxyServer::SCHEME_HTTPS);
127 
128   std::vector<net::ProxyChain> expected = {
129       kProxyChainFooHttps,
130       ProxyChain::Direct(),
131   };
132   EXPECT_EQ(list.AllChains(), expected);
133 }
134 
TEST(ProxyListTest,DeprioritizeBadProxyChains)135 TEST(ProxyListTest, DeprioritizeBadProxyChains) {
136   // Retry info that marks a proxy as being bad for a *very* long time (to avoid
137   // the test depending on the current time.)
138   ProxyRetryInfo proxy_retry_info;
139   proxy_retry_info.bad_until = base::TimeTicks::Now() + base::Days(1);
140 
141   // Call DeprioritizeBadProxyChains with an empty map -- should have no effect.
142   {
143     ProxyList list;
144     list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
145 
146     ProxyRetryInfoMap retry_info_map;
147     list.DeprioritizeBadProxyChains(retry_info_map);
148     EXPECT_EQ("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80",
149               list.ToDebugString());
150   }
151 
152   // Call DeprioritizeBadProxyChains with 2 of the three chains marked as bad.
153   // These proxies should be retried last.
154   {
155     ProxyList list;
156     list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
157 
158     ProxyRetryInfoMap retry_info_map;
159     retry_info_map[ProxyUriToProxyChain(
160         "foopy1:80", ProxyServer::SCHEME_HTTP)] = proxy_retry_info;
161     retry_info_map[ProxyUriToProxyChain(
162         "foopy3:80", ProxyServer::SCHEME_HTTP)] = proxy_retry_info;
163     retry_info_map[ProxyUriToProxyChain("socks5://localhost:1080",
164                                         ProxyServer::SCHEME_HTTP)] =
165         proxy_retry_info;
166 
167     list.DeprioritizeBadProxyChains(retry_info_map);
168 
169     EXPECT_EQ("PROXY foopy2:80;PROXY foopy1:80;PROXY foopy3:80",
170               list.ToDebugString());
171   }
172 
173   // Call DeprioritizeBadProxyChains where ALL of the chains are marked as bad.
174   // This should have no effect on the order.
175   {
176     ProxyList list;
177     list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
178 
179     ProxyRetryInfoMap retry_info_map;
180     retry_info_map[ProxyUriToProxyChain(
181         "foopy1:80", ProxyServer::SCHEME_HTTP)] = proxy_retry_info;
182     retry_info_map[ProxyUriToProxyChain(
183         "foopy2:80", ProxyServer::SCHEME_HTTP)] = proxy_retry_info;
184     retry_info_map[ProxyUriToProxyChain(
185         "foopy3:80", ProxyServer::SCHEME_HTTP)] = proxy_retry_info;
186 
187     list.DeprioritizeBadProxyChains(retry_info_map);
188 
189     EXPECT_EQ("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80",
190               list.ToDebugString());
191   }
192 
193   // Call DeprioritizeBadProxyChains with 2 of the three chains marked as bad.
194   // Of the 2 bad proxies, one is to be reconsidered and should be retried last.
195   // The other is not to be reconsidered and should be removed from the list.
196   {
197     ProxyList list;
198     list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
199 
200     ProxyRetryInfoMap retry_info_map;
201     // |proxy_retry_info.reconsider defaults to true.
202     retry_info_map[ProxyUriToProxyChain(
203         "foopy1:80", ProxyServer::SCHEME_HTTP)] = proxy_retry_info;
204     proxy_retry_info.try_while_bad = false;
205     retry_info_map[ProxyUriToProxyChain(
206         "foopy3:80", ProxyServer::SCHEME_HTTP)] = proxy_retry_info;
207     proxy_retry_info.try_while_bad = true;
208     retry_info_map[ProxyUriToProxyChain("socks5://localhost:1080",
209                                         ProxyServer::SCHEME_SOCKS5)] =
210         proxy_retry_info;
211 
212     list.DeprioritizeBadProxyChains(retry_info_map);
213 
214     EXPECT_EQ("PROXY foopy2:80;PROXY foopy1:80", list.ToDebugString());
215   }
216 }
217 
TEST(ProxyListTest,UpdateRetryInfoOnFallback)218 TEST(ProxyListTest, UpdateRetryInfoOnFallback) {
219   // Retrying should put the first proxy on the retry list.
220   {
221     ProxyList list;
222     ProxyRetryInfoMap retry_info_map;
223     NetLogWithSource net_log;
224     ProxyChain proxy_chain(
225         ProxyUriToProxyChain("foopy1:80", ProxyServer::SCHEME_HTTP));
226     std::vector<ProxyChain> bad_proxies;
227     bad_proxies.push_back(proxy_chain);
228     list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
229     list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(60), true,
230                                    bad_proxies, ERR_PROXY_CONNECTION_FAILED,
231                                    net_log);
232     EXPECT_TRUE(retry_info_map.end() != retry_info_map.find(proxy_chain));
233     EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED,
234               retry_info_map[proxy_chain].net_error);
235     EXPECT_TRUE(retry_info_map.end() ==
236                 retry_info_map.find(ProxyUriToProxyChain(
237                     "foopy2:80", ProxyServer::SCHEME_HTTP)));
238     EXPECT_TRUE(retry_info_map.end() ==
239                 retry_info_map.find(ProxyUriToProxyChain(
240                     "foopy3:80", ProxyServer::SCHEME_HTTP)));
241   }
242   // Retrying should put the first proxy on the retry list, even if there
243   // was no network error.
244   {
245     ProxyList list;
246     ProxyRetryInfoMap retry_info_map;
247     NetLogWithSource net_log;
248     ProxyChain proxy_chain(
249         ProxyUriToProxyChain("foopy1:80", ProxyServer::SCHEME_HTTP));
250     std::vector<ProxyChain> bad_proxies;
251     bad_proxies.push_back(proxy_chain);
252     list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
253     list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(60), true,
254                                    bad_proxies, OK, net_log);
255     EXPECT_TRUE(retry_info_map.end() != retry_info_map.find(proxy_chain));
256     EXPECT_THAT(retry_info_map[proxy_chain].net_error, IsOk());
257     EXPECT_TRUE(retry_info_map.end() ==
258                 retry_info_map.find(ProxyUriToProxyChain(
259                     "foopy2:80", ProxyServer::SCHEME_HTTP)));
260     EXPECT_TRUE(retry_info_map.end() ==
261                 retry_info_map.find(ProxyUriToProxyChain(
262                     "foopy3:80", ProxyServer::SCHEME_HTTP)));
263   }
264   // Including another bad proxy should put both the first and the specified
265   // proxy on the retry list.
266   {
267     ProxyList list;
268     ProxyRetryInfoMap retry_info_map;
269     NetLogWithSource net_log;
270     ProxyChain proxy_chain(
271         ProxyUriToProxyChain("foopy3:80", ProxyServer::SCHEME_HTTP));
272     std::vector<ProxyChain> bad_proxies;
273     bad_proxies.push_back(proxy_chain);
274     list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
275     list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(60), true,
276                                    bad_proxies, ERR_NAME_RESOLUTION_FAILED,
277                                    net_log);
278     EXPECT_TRUE(retry_info_map.end() !=
279                 retry_info_map.find(ProxyUriToProxyChain(
280                     "foopy1:80", ProxyServer::SCHEME_HTTP)));
281     EXPECT_EQ(ERR_NAME_RESOLUTION_FAILED,
282               retry_info_map[proxy_chain].net_error);
283     EXPECT_TRUE(retry_info_map.end() ==
284                 retry_info_map.find(ProxyUriToProxyChain(
285                     "foopy2:80", ProxyServer::SCHEME_HTTP)));
286     EXPECT_TRUE(retry_info_map.end() != retry_info_map.find(proxy_chain));
287   }
288   // If the first proxy is DIRECT, nothing is added to the retry list, even
289   // if another bad proxy is specified.
290   {
291     ProxyList list;
292     ProxyRetryInfoMap retry_info_map;
293     NetLogWithSource net_log;
294     ProxyChain proxy_chain(
295         ProxyUriToProxyChain("foopy2:80", ProxyServer::SCHEME_HTTP));
296     std::vector<ProxyChain> bad_proxies;
297     bad_proxies.push_back(proxy_chain);
298     list.SetFromPacString("DIRECT;PROXY foopy2:80;PROXY foopy3:80");
299     list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(60), true,
300                                    bad_proxies, OK, net_log);
301     EXPECT_TRUE(retry_info_map.end() == retry_info_map.find(proxy_chain));
302     EXPECT_TRUE(retry_info_map.end() ==
303                 retry_info_map.find(ProxyUriToProxyChain(
304                     "foopy3:80", ProxyServer::SCHEME_HTTP)));
305   }
306   // If the bad proxy is already on the retry list, and the old retry info would
307   // cause the proxy to be retried later than the newly specified retry info,
308   // then the old retry info should be kept.
309   {
310     ProxyList list;
311     ProxyRetryInfoMap retry_info_map;
312     NetLogWithSource net_log;
313     list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
314 
315     // First, mark the proxy as bad for 60 seconds.
316     list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(60), true,
317                                    std::vector<ProxyChain>(),
318                                    ERR_PROXY_CONNECTION_FAILED, net_log);
319     // Next, mark the same proxy as bad for 1 second. This call should have no
320     // effect, since this would cause the bad proxy to be retried sooner than
321     // the existing retry info.
322     list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(1), false,
323                                    std::vector<ProxyChain>(), OK, net_log);
324     ProxyChain proxy_chain(
325         ProxyUriToProxyChain("foopy1:80", ProxyServer::SCHEME_HTTP));
326     EXPECT_TRUE(retry_info_map.end() != retry_info_map.find(proxy_chain));
327     EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED,
328               retry_info_map[proxy_chain].net_error);
329     EXPECT_TRUE(retry_info_map[proxy_chain].try_while_bad);
330     EXPECT_EQ(base::Seconds(60), retry_info_map[proxy_chain].current_delay);
331     EXPECT_GT(retry_info_map[proxy_chain].bad_until,
332               base::TimeTicks::Now() + base::Seconds(30));
333     EXPECT_TRUE(retry_info_map.end() ==
334                 retry_info_map.find(ProxyUriToProxyChain(
335                     "foopy2:80", ProxyServer::SCHEME_HTTP)));
336     EXPECT_TRUE(retry_info_map.end() ==
337                 retry_info_map.find(ProxyUriToProxyChain(
338                     "foopy3:80", ProxyServer::SCHEME_HTTP)));
339   }
340   // If the bad proxy is already on the retry list, and the newly specified
341   // retry info would cause the proxy to be retried later than the old retry
342   // info, then the old retry info should be replaced with the new retry info.
343   {
344     ProxyList list;
345     ProxyRetryInfoMap retry_info_map;
346     NetLogWithSource net_log;
347     list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
348 
349     // First, mark the proxy as bad for 1 second.
350     list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(1), false,
351                                    std::vector<ProxyChain>(), OK, net_log);
352     // Next, mark the same proxy as bad for 60 seconds. This call should replace
353     // the existing retry info with the new 60 second retry info.
354     list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(60), true,
355                                    std::vector<ProxyChain>(),
356                                    ERR_PROXY_CONNECTION_FAILED, net_log);
357     ProxyChain proxy_chain(
358         ProxyUriToProxyChain("foopy1:80", ProxyServer::SCHEME_HTTP));
359     EXPECT_TRUE(retry_info_map.end() != retry_info_map.find(proxy_chain));
360     EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED,
361               retry_info_map[proxy_chain].net_error);
362     EXPECT_TRUE(retry_info_map[proxy_chain].try_while_bad);
363     EXPECT_EQ(base::Seconds(60), retry_info_map[proxy_chain].current_delay);
364     EXPECT_GT(retry_info_map[proxy_chain].bad_until,
365               base::TimeTicks::Now() + base::Seconds(30));
366     EXPECT_TRUE(retry_info_map.end() ==
367                 retry_info_map.find(ProxyUriToProxyChain(
368                     "foopy2:80", ProxyServer::SCHEME_HTTP)));
369     EXPECT_TRUE(retry_info_map.end() ==
370                 retry_info_map.find(ProxyUriToProxyChain(
371                     "foopy3:80", ProxyServer::SCHEME_HTTP)));
372   }
373 }
374 
TEST(ProxyListTest,ToPacString)375 TEST(ProxyListTest, ToPacString) {
376   ProxyList list;
377   list.AddProxyChain(ProxyChain::FromSchemeHostAndPort(
378       ProxyServer::Scheme::SCHEME_HTTPS, "foo", 443));
379   EXPECT_EQ(list.ToPacString(), "HTTPS foo:443");
380 
381   // ToPacString should fail for proxy chains.
382   list.AddProxyChain(ProxyChain({
383       ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS,
384                                          "foo-a", 443),
385       ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS,
386                                          "foo-b", 443),
387   }));
388   EXPECT_DEATH_IF_SUPPORTED(list.ToPacString(), "");
389 }
390 
TEST(ProxyListTest,ToDebugString)391 TEST(ProxyListTest, ToDebugString) {
392   ProxyList list;
393   list.AddProxyChain(ProxyChain::FromSchemeHostAndPort(
394       ProxyServer::Scheme::SCHEME_HTTPS, "foo", 443));
395   list.AddProxyChain(ProxyChain({
396       ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS,
397                                          "foo-a", 443),
398       ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS,
399                                          "foo-b", 443),
400   }));
401 
402   EXPECT_EQ(list.ToDebugString(),
403             "HTTPS foo:443;[https://foo-a:443, https://foo-b:443]");
404 }
405 
TEST(ProxyListTest,ToValue)406 TEST(ProxyListTest, ToValue) {
407   ProxyList list;
408   list.AddProxyChain(ProxyChain::FromSchemeHostAndPort(
409       ProxyServer::Scheme::SCHEME_HTTPS, "foo", 443));
410   list.AddProxyChain(ProxyChain({
411       ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS,
412                                          "foo-a", 443),
413       ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS,
414                                          "foo-b", 443),
415   }));
416 
417   base::Value expected(base::Value::Type::LIST);
418   base::Value::List& exp_list = expected.GetList();
419   exp_list.Append("[https://foo:443]");
420   exp_list.Append("[https://foo-a:443, https://foo-b:443]");
421 
422   EXPECT_EQ(list.ToValue(), expected);
423 }
424 
425 }  // anonymous namespace
426 
427 }  // namespace net
428