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