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