910e62b5创建于 1月15日历史提交
// 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 "base/logging.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "build/buildflag.h"
#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/net_buildflags.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 {

// Returns a ProxyRetryInfo that marks a proxy as being bad for a *very* long
// time (to avoid the test depending on the current time.)
ProxyRetryInfo GetProxyRetryInfo() {
  ProxyRetryInfo proxy_retry_info;
  proxy_retry_info.bad_until = base::TimeTicks::Now() + base::Days(1);
  return proxy_retry_info;
}

// 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 auto kProxyChainFooHttps = ProxyChain::ForIpProtection({
      ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS,
                                         "foo-a", 443),
      ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_HTTPS,
                                         "foo-b", 443),
  });
  const auto kProxyChainBarMixed = ProxyChain::ForIpProtection({
      ProxyServer::FromSchemeHostAndPort(ProxyServer::Scheme::SCHEME_QUIC,
                                         "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_EmptyMap) {
  // 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());
}

TEST(ProxyListTest, DeprioritizeBadProxyChains_SomeBad) {
  // 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)] =
      GetProxyRetryInfo();
  retry_info_map[ProxyUriToProxyChain("foopy3:80", ProxyServer::SCHEME_HTTP)] =
      GetProxyRetryInfo();

  list.DeprioritizeBadProxyChains(retry_info_map);

  EXPECT_EQ("PROXY foopy2:80;PROXY foopy1:80;PROXY foopy3:80",
            list.ToDebugString());
}

TEST(ProxyListTest, DeprioritizeBadProxyChains_AllBad) {
  // 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)] =
      GetProxyRetryInfo();
  retry_info_map[ProxyUriToProxyChain("foopy2:80", ProxyServer::SCHEME_HTTP)] =
      GetProxyRetryInfo();
  retry_info_map[ProxyUriToProxyChain("foopy3:80", ProxyServer::SCHEME_HTTP)] =
      GetProxyRetryInfo();

  list.DeprioritizeBadProxyChains(retry_info_map);

  EXPECT_EQ("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80",
            list.ToDebugString());
}

TEST(ProxyListTest, DeprioritizeBadProxyChains_RemoveBad) {
  // Call DeprioritizeBadProxyChains with 2 of the three chains marked as bad.
  // These proxies should be removed from the list.
  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)] =
      GetProxyRetryInfo();
  retry_info_map[ProxyUriToProxyChain("foopy3:80", ProxyServer::SCHEME_HTTP)] =
      GetProxyRetryInfo();

  list.DeprioritizeBadProxyChains(retry_info_map,
                                  /*remove_bad_proxy_chains=*/true);

  EXPECT_EQ("PROXY foopy2:80", list.ToDebugString());
}

TEST(ProxyListTest, Fallback_Basic) {
  // Falling back 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));
  list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
  list.Fallback(&retry_info_map, 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)));
}

TEST(ProxyListTest, Fallback_NoNetworkError) {
  // Falling back 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));
  list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
  list.Fallback(&retry_info_map, 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)));
}

TEST(ProxyListTest, Fallback_Direct) {
  // If the first proxy is DIRECT, nothing is added to the retry list.
  ProxyList list;
  ProxyRetryInfoMap retry_info_map;
  NetLogWithSource net_log;
  list.SetFromPacString("DIRECT;PROXY foopy2:80;PROXY foopy3:80");
  list.Fallback(&retry_info_map, OK, net_log);
  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, Fallback_RetryInfoExists) {
  base::test::TaskEnvironment task_environment(
      base::test::TaskEnvironment::TimeSource::MOCK_TIME);

  ProxyRetryInfoMap retry_info_map;
  NetLogWithSource net_log;
  ProxyChain proxy_chain(
      ProxyUriToProxyChain("foopy1:80", ProxyServer::SCHEME_HTTP));
  static constexpr base::TimeDelta kDefaultRetryDelay = base::Minutes(5);

  // 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;
    list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
    // First, mark the proxy as bad.
    list.Fallback(&retry_info_map, OK, net_log);
    task_environment.AdvanceClock(base::Minutes(1));
    EXPECT_EQ(retry_info_map[proxy_chain].bad_until,
              base::TimeTicks::Now() + kDefaultRetryDelay - base::Minutes(1));
  }

  ProxyList list;
  list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
  // Next, mark the same proxy as bad. This call should replace the existing
  // retry info with the new retry info.
  list.Fallback(&retry_info_map, 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_EQ(retry_info_map[proxy_chain].bad_until,
            base::TimeTicks::Now() + kDefaultRetryDelay);
  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::ForIpProtection({
      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::ForIpProtection({
      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] (IP Protection)");
}

TEST(ProxyListTest, ToValue) {
  ProxyList list;
  list.AddProxyChain(ProxyChain::FromSchemeHostAndPort(
      ProxyServer::Scheme::SCHEME_HTTPS, "foo", 443));
  list.AddProxyChain(ProxyChain::ForIpProtection({
      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] (IP Protection)");

  EXPECT_EQ(list.ToValue(), expected);
}

#if BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)
// The following tests are for non-release builds where multi-proxy chains are
// permitted outside of Ip Protection.

TEST(ProxyListTest,
     NonIpProtectionMultiProxyChainRemoveProxiesWithoutSchemeWithProxyChains) {
  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_QUIC,
                                         "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);
}

// `ToPacString` should only be called if the list contains no multi-proxy
// chains, as those cannot be represented in PAC syntax. This is not an issue in
// release builds because a `ProxyChain` constructed with multiple proxy servers
// would automatically default to an empty, invalid
// `ProxyChain` (unless for Ip Protection); however, in non-release builds,
// multi-proxy chains are permitted which means they must be CHECKED when this
// function is called.
TEST(ProxyListTest, NonIpProtectionMultiProxyChainToPacString) {
  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, NonIpProtectionMultiProxyChainToDebugString) {
  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, NonIpProtectionMultiProxyChainToValue) {
  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);
}
#endif  // BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)

}  // anonymous namespace

}  // namespace net