| // Copyright 2006-2008 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/proxy_resolution/proxy_list.h" |
| |
| #include <vector> |
| |
| #include "net/base/net_errors.h" |
| #include "net/base/proxy_server.h" |
| #include "net/base/proxy_string_util.h" |
| #include "net/log/net_log_with_source.h" |
| #include "net/proxy_resolution/proxy_retry_info.h" |
| #include "net/test/gtest_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using net::test::IsOk; |
| |
| namespace net { |
| |
| namespace { |
| |
| // Test parsing from a PAC string. |
| TEST(ProxyListTest, SetFromPacString) { |
| const struct { |
| const char* pac_input; |
| const char* debug_output; |
| } tests[] = { |
| // Valid inputs: |
| { "PROXY foopy:10", |
| "PROXY foopy:10", |
| }, |
| { " DIRECT", // leading space. |
| "DIRECT", |
| }, |
| { "PROXY foopy1 ; proxy foopy2;\t DIRECT", |
| "PROXY foopy1:80;PROXY foopy2:80;DIRECT", |
| }, |
| { "proxy foopy1 ; SOCKS foopy2", |
| "PROXY foopy1:80;SOCKS foopy2:1080", |
| }, |
| // Try putting DIRECT first. |
| { "DIRECT ; proxy foopy1 ; DIRECT ; SOCKS5 foopy2;DIRECT ", |
| "DIRECT;PROXY foopy1:80;DIRECT;SOCKS5 foopy2:1080;DIRECT", |
| }, |
| // Try putting DIRECT consecutively. |
| { "DIRECT ; proxy foopy1:80; DIRECT ; DIRECT", |
| "DIRECT;PROXY foopy1:80;DIRECT;DIRECT", |
| }, |
| |
| // Invalid inputs (parts which aren't understood get |
| // silently discarded): |
| // |
| // If the proxy list string parsed to empty, automatically fall-back to |
| // DIRECT. |
| { "PROXY-foopy:10", |
| "DIRECT", |
| }, |
| { "PROXY", |
| "DIRECT", |
| }, |
| { "PROXY foopy1 ; JUNK ; JUNK ; SOCKS5 foopy2 ; ;", |
| "PROXY foopy1:80;SOCKS5 foopy2:1080", |
| }, |
| }; |
| |
| for (const auto& test : tests) { |
| ProxyList list; |
| list.SetFromPacString(test.pac_input); |
| EXPECT_EQ(test.debug_output, list.ToDebugString()); |
| EXPECT_FALSE(list.IsEmpty()); |
| } |
| } |
| |
| TEST(ProxyListTest, RemoveProxiesWithoutScheme) { |
| const struct { |
| const char* pac_input; |
| int filter; |
| const char* filtered_debug_output; |
| } tests[] = { |
| { |
| "PROXY foopy:10 ; SOCKS5 foopy2 ; SOCKS foopy11 ; PROXY foopy3 ; " |
| "DIRECT", |
| // Remove anything that isn't HTTP. |
| ProxyServer::SCHEME_HTTP, |
| "PROXY foopy:10;PROXY foopy3:80;DIRECT", |
| }, |
| { |
| "PROXY foopy:10 ; SOCKS5 foopy2", |
| // Remove anything that isn't HTTP or SOCKS5. |
| ProxyServer::SCHEME_SOCKS4, |
| "", |
| }, |
| }; |
| |
| for (const auto& test : tests) { |
| ProxyList list; |
| list.SetFromPacString(test.pac_input); |
| list.RemoveProxiesWithoutScheme(test.filter); |
| EXPECT_EQ(test.filtered_debug_output, list.ToDebugString()); |
| } |
| } |
| |
| TEST(ProxyListTest, RemoveProxiesWithoutSchemeWithProxyChains) { |
| const ProxyChain kProxyChainFooHttps({ |
| ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS, |
| "foo-a", 443), |
| ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS, |
| "foo-b", 443), |
| }); |
| const ProxyChain kProxyChainBarMixed({ |
| ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_SOCKS5, |
| "bar-a", 443), |
| ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS, |
| "bar-b", 443), |
| }); |
| const ProxyChain kProxyChainGraultSocks = ProxyChain::FromSchemeHostAndPort( |
| ProxyServer::Scheme::SCHEME_SOCKS4, "grault", 443); |
| |
| ProxyList list; |
| list.AddProxyChain(kProxyChainFooHttps); |
| list.AddProxyChain(kProxyChainBarMixed); |
| list.AddProxyChain(kProxyChainGraultSocks); |
| list.AddProxyChain(ProxyChain::Direct()); |
| |
| // Remove anything that isn't entirely HTTPS. |
| list.RemoveProxiesWithoutScheme(ProxyServer::SCHEME_HTTPS); |
| |
| std::vector<net::ProxyChain> expected = { |
| kProxyChainFooHttps, |
| ProxyChain::Direct(), |
| }; |
| EXPECT_EQ(list.AllChains(), expected); |
| } |
| |
| TEST(ProxyListTest, DeprioritizeBadProxyChains) { |
| // Retry info that marks a proxy as being bad for a *very* long time (to avoid |
| // the test depending on the current time.) |
| ProxyRetryInfo proxy_retry_info; |
| proxy_retry_info.bad_until = base::TimeTicks::Now() + base::Days(1); |
| |
| // Call DeprioritizeBadProxyChains with an empty map -- should have no effect. |
| { |
| ProxyList list; |
| list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); |
| |
| ProxyRetryInfoMap retry_info_map; |
| list.DeprioritizeBadProxyChains(retry_info_map); |
| EXPECT_EQ("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80", |
| list.ToDebugString()); |
| } |
| |
| // Call DeprioritizeBadProxyChains with 2 of the three chains marked as bad. |
| // These proxies should be retried last. |
| { |
| ProxyList list; |
| list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); |
| |
| ProxyRetryInfoMap retry_info_map; |
| retry_info_map[ProxyUriToProxyChain( |
| "foopy1:80", ProxyServer::SCHEME_HTTP)] = proxy_retry_info; |
| retry_info_map[ProxyUriToProxyChain( |
| "foopy3:80", ProxyServer::SCHEME_HTTP)] = proxy_retry_info; |
| retry_info_map[ProxyUriToProxyChain("socks5://localhost:1080", |
| ProxyServer::SCHEME_HTTP)] = |
| proxy_retry_info; |
| |
| list.DeprioritizeBadProxyChains(retry_info_map); |
| |
| EXPECT_EQ("PROXY foopy2:80;PROXY foopy1:80;PROXY foopy3:80", |
| list.ToDebugString()); |
| } |
| |
| // Call DeprioritizeBadProxyChains where ALL of the chains are marked as bad. |
| // This should have no effect on the order. |
| { |
| ProxyList list; |
| list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); |
| |
| ProxyRetryInfoMap retry_info_map; |
| retry_info_map[ProxyUriToProxyChain( |
| "foopy1:80", ProxyServer::SCHEME_HTTP)] = proxy_retry_info; |
| retry_info_map[ProxyUriToProxyChain( |
| "foopy2:80", ProxyServer::SCHEME_HTTP)] = proxy_retry_info; |
| retry_info_map[ProxyUriToProxyChain( |
| "foopy3:80", ProxyServer::SCHEME_HTTP)] = proxy_retry_info; |
| |
| list.DeprioritizeBadProxyChains(retry_info_map); |
| |
| EXPECT_EQ("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80", |
| list.ToDebugString()); |
| } |
| |
| // Call DeprioritizeBadProxyChains with 2 of the three chains marked as bad. |
| // Of the 2 bad proxies, one is to be reconsidered and should be retried last. |
| // The other is not to be reconsidered and should be removed from the list. |
| { |
| ProxyList list; |
| list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); |
| |
| ProxyRetryInfoMap retry_info_map; |
| // |proxy_retry_info.reconsider defaults to true. |
| retry_info_map[ProxyUriToProxyChain( |
| "foopy1:80", ProxyServer::SCHEME_HTTP)] = proxy_retry_info; |
| proxy_retry_info.try_while_bad = false; |
| retry_info_map[ProxyUriToProxyChain( |
| "foopy3:80", ProxyServer::SCHEME_HTTP)] = proxy_retry_info; |
| proxy_retry_info.try_while_bad = true; |
| retry_info_map[ProxyUriToProxyChain("socks5://localhost:1080", |
| ProxyServer::SCHEME_SOCKS5)] = |
| proxy_retry_info; |
| |
| list.DeprioritizeBadProxyChains(retry_info_map); |
| |
| EXPECT_EQ("PROXY foopy2:80;PROXY foopy1:80", list.ToDebugString()); |
| } |
| } |
| |
| TEST(ProxyListTest, UpdateRetryInfoOnFallback) { |
| // Retrying should put the first proxy on the retry list. |
| { |
| ProxyList list; |
| ProxyRetryInfoMap retry_info_map; |
| NetLogWithSource net_log; |
| ProxyChain proxy_chain( |
| ProxyUriToProxyChain("foopy1:80", ProxyServer::SCHEME_HTTP)); |
| std::vector<ProxyChain> bad_proxies; |
| bad_proxies.push_back(proxy_chain); |
| list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); |
| list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(60), true, |
| bad_proxies, ERR_PROXY_CONNECTION_FAILED, |
| net_log); |
| EXPECT_TRUE(retry_info_map.end() != retry_info_map.find(proxy_chain)); |
| EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, |
| retry_info_map[proxy_chain].net_error); |
| EXPECT_TRUE(retry_info_map.end() == |
| retry_info_map.find(ProxyUriToProxyChain( |
| "foopy2:80", ProxyServer::SCHEME_HTTP))); |
| EXPECT_TRUE(retry_info_map.end() == |
| retry_info_map.find(ProxyUriToProxyChain( |
| "foopy3:80", ProxyServer::SCHEME_HTTP))); |
| } |
| // Retrying should put the first proxy on the retry list, even if there |
| // was no network error. |
| { |
| ProxyList list; |
| ProxyRetryInfoMap retry_info_map; |
| NetLogWithSource net_log; |
| ProxyChain proxy_chain( |
| ProxyUriToProxyChain("foopy1:80", ProxyServer::SCHEME_HTTP)); |
| std::vector<ProxyChain> bad_proxies; |
| bad_proxies.push_back(proxy_chain); |
| list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); |
| list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(60), true, |
| bad_proxies, OK, net_log); |
| EXPECT_TRUE(retry_info_map.end() != retry_info_map.find(proxy_chain)); |
| EXPECT_THAT(retry_info_map[proxy_chain].net_error, IsOk()); |
| EXPECT_TRUE(retry_info_map.end() == |
| retry_info_map.find(ProxyUriToProxyChain( |
| "foopy2:80", ProxyServer::SCHEME_HTTP))); |
| EXPECT_TRUE(retry_info_map.end() == |
| retry_info_map.find(ProxyUriToProxyChain( |
| "foopy3:80", ProxyServer::SCHEME_HTTP))); |
| } |
| // Including another bad proxy should put both the first and the specified |
| // proxy on the retry list. |
| { |
| ProxyList list; |
| ProxyRetryInfoMap retry_info_map; |
| NetLogWithSource net_log; |
| ProxyChain proxy_chain( |
| ProxyUriToProxyChain("foopy3:80", ProxyServer::SCHEME_HTTP)); |
| std::vector<ProxyChain> bad_proxies; |
| bad_proxies.push_back(proxy_chain); |
| list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); |
| list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(60), true, |
| bad_proxies, ERR_NAME_RESOLUTION_FAILED, |
| net_log); |
| EXPECT_TRUE(retry_info_map.end() != |
| retry_info_map.find(ProxyUriToProxyChain( |
| "foopy1:80", ProxyServer::SCHEME_HTTP))); |
| EXPECT_EQ(ERR_NAME_RESOLUTION_FAILED, |
| retry_info_map[proxy_chain].net_error); |
| EXPECT_TRUE(retry_info_map.end() == |
| retry_info_map.find(ProxyUriToProxyChain( |
| "foopy2:80", ProxyServer::SCHEME_HTTP))); |
| EXPECT_TRUE(retry_info_map.end() != retry_info_map.find(proxy_chain)); |
| } |
| // If the first proxy is DIRECT, nothing is added to the retry list, even |
| // if another bad proxy is specified. |
| { |
| ProxyList list; |
| ProxyRetryInfoMap retry_info_map; |
| NetLogWithSource net_log; |
| ProxyChain proxy_chain( |
| ProxyUriToProxyChain("foopy2:80", ProxyServer::SCHEME_HTTP)); |
| std::vector<ProxyChain> bad_proxies; |
| bad_proxies.push_back(proxy_chain); |
| list.SetFromPacString("DIRECT;PROXY foopy2:80;PROXY foopy3:80"); |
| list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(60), true, |
| bad_proxies, OK, net_log); |
| EXPECT_TRUE(retry_info_map.end() == retry_info_map.find(proxy_chain)); |
| EXPECT_TRUE(retry_info_map.end() == |
| retry_info_map.find(ProxyUriToProxyChain( |
| "foopy3:80", ProxyServer::SCHEME_HTTP))); |
| } |
| // If the bad proxy is already on the retry list, and the old retry info would |
| // cause the proxy to be retried later than the newly specified retry info, |
| // then the old retry info should be kept. |
| { |
| ProxyList list; |
| ProxyRetryInfoMap retry_info_map; |
| NetLogWithSource net_log; |
| list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); |
| |
| // First, mark the proxy as bad for 60 seconds. |
| list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(60), true, |
| std::vector<ProxyChain>(), |
| ERR_PROXY_CONNECTION_FAILED, net_log); |
| // Next, mark the same proxy as bad for 1 second. This call should have no |
| // effect, since this would cause the bad proxy to be retried sooner than |
| // the existing retry info. |
| list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(1), false, |
| std::vector<ProxyChain>(), OK, net_log); |
| ProxyChain proxy_chain( |
| ProxyUriToProxyChain("foopy1:80", ProxyServer::SCHEME_HTTP)); |
| EXPECT_TRUE(retry_info_map.end() != retry_info_map.find(proxy_chain)); |
| EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, |
| retry_info_map[proxy_chain].net_error); |
| EXPECT_TRUE(retry_info_map[proxy_chain].try_while_bad); |
| EXPECT_EQ(base::Seconds(60), retry_info_map[proxy_chain].current_delay); |
| EXPECT_GT(retry_info_map[proxy_chain].bad_until, |
| base::TimeTicks::Now() + base::Seconds(30)); |
| EXPECT_TRUE(retry_info_map.end() == |
| retry_info_map.find(ProxyUriToProxyChain( |
| "foopy2:80", ProxyServer::SCHEME_HTTP))); |
| EXPECT_TRUE(retry_info_map.end() == |
| retry_info_map.find(ProxyUriToProxyChain( |
| "foopy3:80", ProxyServer::SCHEME_HTTP))); |
| } |
| // If the bad proxy is already on the retry list, and the newly specified |
| // retry info would cause the proxy to be retried later than the old retry |
| // info, then the old retry info should be replaced with the new retry info. |
| { |
| ProxyList list; |
| ProxyRetryInfoMap retry_info_map; |
| NetLogWithSource net_log; |
| list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80"); |
| |
| // First, mark the proxy as bad for 1 second. |
| list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(1), false, |
| std::vector<ProxyChain>(), OK, net_log); |
| // Next, mark the same proxy as bad for 60 seconds. This call should replace |
| // the existing retry info with the new 60 second retry info. |
| list.UpdateRetryInfoOnFallback(&retry_info_map, base::Seconds(60), true, |
| std::vector<ProxyChain>(), |
| ERR_PROXY_CONNECTION_FAILED, net_log); |
| ProxyChain proxy_chain( |
| ProxyUriToProxyChain("foopy1:80", ProxyServer::SCHEME_HTTP)); |
| EXPECT_TRUE(retry_info_map.end() != retry_info_map.find(proxy_chain)); |
| EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, |
| retry_info_map[proxy_chain].net_error); |
| EXPECT_TRUE(retry_info_map[proxy_chain].try_while_bad); |
| EXPECT_EQ(base::Seconds(60), retry_info_map[proxy_chain].current_delay); |
| EXPECT_GT(retry_info_map[proxy_chain].bad_until, |
| base::TimeTicks::Now() + base::Seconds(30)); |
| EXPECT_TRUE(retry_info_map.end() == |
| retry_info_map.find(ProxyUriToProxyChain( |
| "foopy2:80", ProxyServer::SCHEME_HTTP))); |
| EXPECT_TRUE(retry_info_map.end() == |
| retry_info_map.find(ProxyUriToProxyChain( |
| "foopy3:80", ProxyServer::SCHEME_HTTP))); |
| } |
| } |
| |
| TEST(ProxyListTest, ToPacString) { |
| ProxyList list; |
| list.AddProxyChain(ProxyChain::FromSchemeHostAndPort( |
| ProxyServer::Scheme::SCHEME_HTTPS, "foo", 443)); |
| EXPECT_EQ(list.ToPacString(), "HTTPS foo:443"); |
| |
| // ToPacString should fail for proxy chains. |
| list.AddProxyChain(ProxyChain({ |
| ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS, |
| "foo-a", 443), |
| ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS, |
| "foo-b", 443), |
| })); |
| EXPECT_DEATH_IF_SUPPORTED(list.ToPacString(), ""); |
| } |
| |
| TEST(ProxyListTest, ToDebugString) { |
| ProxyList list; |
| list.AddProxyChain(ProxyChain::FromSchemeHostAndPort( |
| ProxyServer::Scheme::SCHEME_HTTPS, "foo", 443)); |
| list.AddProxyChain(ProxyChain({ |
| ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS, |
| "foo-a", 443), |
| ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS, |
| "foo-b", 443), |
| })); |
| |
| EXPECT_EQ(list.ToDebugString(), |
| "HTTPS foo:443;[https://foo-a:443, https://foo-b:443]"); |
| } |
| |
| TEST(ProxyListTest, ToValue) { |
| ProxyList list; |
| list.AddProxyChain(ProxyChain::FromSchemeHostAndPort( |
| ProxyServer::Scheme::SCHEME_HTTPS, "foo", 443)); |
| list.AddProxyChain(ProxyChain({ |
| ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS, |
| "foo-a", 443), |
| ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS, |
| "foo-b", 443), |
| })); |
| |
| base::Value expected(base::Value::Type::LIST); |
| base::Value::List& exp_list = expected.GetList(); |
| exp_list.Append("[https://foo:443]"); |
| exp_list.Append("[https://foo-a:443, https://foo-b:443]"); |
| |
| EXPECT_EQ(list.ToValue(), expected); |
| } |
| |
| } // anonymous namespace |
| |
| } // namespace net |