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