910e62b5创建于 1月15日历史提交
// Copyright 2012 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_config.h"

#include "base/json/json_writer.h"
#include "base/test/gtest_util.h"
#include "base/values.h"
#include "build/buildflag.h"
#include "net/base/proxy_string_util.h"
#include "net/net_buildflags.h"
#include "net/proxy_resolution/proxy_config_service_common_unittest.h"
#include "net/proxy_resolution/proxy_info.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {
namespace {

void ExpectProxyServerEquals(const char* expectation,
                             const ProxyList& proxy_list) {
  if (expectation == nullptr) {
    EXPECT_TRUE(proxy_list.IsEmpty());
  } else {
    EXPECT_EQ(expectation, proxy_list.ToDebugString());
  }
}

TEST(ProxyConfigTest, Equals) {
  // Test |ProxyConfig::auto_detect|.

  ProxyConfig config1;
  config1.set_auto_detect(true);

  ProxyConfig config2;
  config2.set_auto_detect(false);

  EXPECT_FALSE(config1.Equals(config2));
  EXPECT_FALSE(config2.Equals(config1));

  config2.set_auto_detect(true);

  EXPECT_TRUE(config1.Equals(config2));
  EXPECT_TRUE(config2.Equals(config1));

  // Test |ProxyConfig::pac_url|.

  config2.set_pac_url(GURL("http://wpad/wpad.dat"));

  EXPECT_FALSE(config1.Equals(config2));
  EXPECT_FALSE(config2.Equals(config1));

  config1.set_pac_url(GURL("http://wpad/wpad.dat"));

  EXPECT_TRUE(config1.Equals(config2));
  EXPECT_TRUE(config2.Equals(config1));

  // Test |ProxyConfig::proxy_rules|.

  config2.proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST;
  config2.proxy_rules().single_proxies.SetSingleProxyServer(
      ProxyUriToProxyServer("myproxy:80", ProxyServer::SCHEME_HTTP));

  EXPECT_FALSE(config1.Equals(config2));
  EXPECT_FALSE(config2.Equals(config1));

  config1.proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST;
  config1.proxy_rules().single_proxies.SetSingleProxyServer(
      ProxyUriToProxyServer("myproxy:100", ProxyServer::SCHEME_HTTP));

  EXPECT_FALSE(config1.Equals(config2));
  EXPECT_FALSE(config2.Equals(config1));

  config1.proxy_rules().single_proxies.SetSingleProxyServer(
      ProxyUriToProxyServer("myproxy", ProxyServer::SCHEME_HTTP));

  EXPECT_TRUE(config1.Equals(config2));
  EXPECT_TRUE(config2.Equals(config1));

  // Test |ProxyConfig::bypass_rules|.

  config2.proxy_rules().bypass_rules.AddRuleFromString("*.google.com");

  EXPECT_FALSE(config1.Equals(config2));
  EXPECT_FALSE(config2.Equals(config1));

  config1.proxy_rules().bypass_rules.AddRuleFromString("*.google.com");

  EXPECT_TRUE(config1.Equals(config2));
  EXPECT_TRUE(config2.Equals(config1));

  // Test |ProxyConfig::proxy_rules.reverse_bypass|.

  config2.proxy_rules().reverse_bypass = true;

  EXPECT_FALSE(config1.Equals(config2));
  EXPECT_FALSE(config2.Equals(config1));

  config1.proxy_rules().reverse_bypass = true;

  EXPECT_TRUE(config1.Equals(config2));
  EXPECT_TRUE(config2.Equals(config1));
}

enum class TestCondition {
  kDefault,
  kHostMatches,
  kResultMatches,
  kNoConditions,
  kNoExcludeDestinationMatchers,
};

ProxyConfig::ProxyOverrideRule CreateOverrideRule(
    bool include_matchers,
    bool include_proxy_list,
    TestCondition test_condition) {
  ProxyConfig::ProxyOverrideRule rule;

  if (include_matchers) {
    rule.destination_matchers.AddRuleFromString("192.168.1.1");
    rule.destination_matchers.AddRuleFromString("[3ffe:2a00:100:7031:0:0::1]");
    rule.destination_matchers.AddRuleFromString("*.org:443");
    rule.destination_matchers.AddRuleFromString("*google.com");

    rule.exclude_destination_matchers.AddRuleFromString("mail.google.com");
    rule.exclude_destination_matchers.AddRuleFromString("http://*.org");
  }

  if (include_proxy_list) {
    rule.proxy_list.SetFromPacString("HTTPS foo:333; DIRECT");
  }

  auto condition = ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
      .host = url::SchemeHostPort("http", "ads.corps", 321),
      .result =
          ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::kNotFound,
  };

  // Only one condition is changed in the non `kDefault` cases to validate the
  // entire array is evaluated for equality.
  switch (test_condition) {
    case TestCondition::kDefault:
      break;
    case TestCondition::kHostMatches:
      condition.result =
          ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::kResolved;
      break;
    case TestCondition::kResultMatches:
      condition.host = url::SchemeHostPort("http", "other.corps", 321);
      break;
    case TestCondition::kNoConditions:
      return rule;
    case TestCondition::kNoExcludeDestinationMatchers:
      rule.exclude_destination_matchers.Clear();
      break;
  }
  rule.dns_conditions = {
      ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
          .host = url::SchemeHostPort("https", "corp.ads", 123),
          .result = ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::
              kResolved,
      },
      condition};

  return rule;
}

class ProxyConfigOverrideRulesEqualityTest
    : public testing::TestWithParam<testing::tuple<bool, bool, TestCondition>> {
};

INSTANTIATE_TEST_SUITE_P(
    All,
    ProxyConfigOverrideRulesEqualityTest,
    testing::Combine(
        testing::Bool(),
        testing::Bool(),
        testing::Values(TestCondition::kDefault,
                        TestCondition::kHostMatches,
                        TestCondition::kResultMatches,
                        TestCondition::kNoConditions,
                        TestCondition::kNoExcludeDestinationMatchers)));

TEST_P(ProxyConfigOverrideRulesEqualityTest, Equals) {
  ProxyConfig config1;
  ProxyConfig config2;

  config1.set_proxy_override_rules({CreateOverrideRule(
      /*include_matchers=*/true, /*include_proxy_list=*/true,
      TestCondition::kDefault)});
  config2.set_proxy_override_rules(
      {CreateOverrideRule(/*include_matchers=*/std::get<0>(GetParam()),
                          /*include_proxy_list=*/std::get<1>(GetParam()),
                          /*test_condition=*/std::get<2>(GetParam()))});

  if (std::get<0>(GetParam()) && std::get<1>(GetParam()) &&
      std::get<2>(GetParam()) == TestCondition::kDefault) {
    EXPECT_TRUE(config1.Equals(config2));
    EXPECT_TRUE(config2.Equals(config1));
  } else {
    EXPECT_FALSE(config1.Equals(config2));
    EXPECT_FALSE(config2.Equals(config1));
  }
}

TEST(ProxyConfigTest, OverrideRulesMatchesDestination) {
  ProxyConfig config;

  config.set_proxy_override_rules({CreateOverrideRule(
      /*include_matchers=*/true, /*include_proxy_list=*/true,
      TestCondition::kDefault)});
  const auto& rule = config.proxy_override_rules().at(0);

  // Rules are set to match the following URLs:
  // - 192.168.1.1
  // - [3ffe:2a00:100:7031:0:0::1]
  // - *.org:443
  // - *google.com
  EXPECT_TRUE(rule.MatchesDestination(GURL("http://192.168.1.1")));
  EXPECT_TRUE(rule.MatchesDestination(GURL("https://192.168.1.1")));
  EXPECT_TRUE(
      rule.MatchesDestination(GURL("http://[3ffe:2a00:100:7031:0:0::1]")));
  EXPECT_TRUE(
      rule.MatchesDestination(GURL("https://[3ffe:2a00:100:7031:0:0::1]")));
  EXPECT_TRUE(rule.MatchesDestination(GURL("http://google.com")));
  EXPECT_TRUE(rule.MatchesDestination(GURL("https://google.com")));
  EXPECT_TRUE(rule.MatchesDestination(GURL("https://calendar.google.com")));
  EXPECT_TRUE(rule.MatchesDestination(GURL("https://google.org:443")));

  EXPECT_FALSE(rule.MatchesDestination(GURL("http://192.168.1.2")));
  EXPECT_FALSE(rule.MatchesDestination(GURL("https://192.168.1.2")));
  EXPECT_FALSE(rule.MatchesDestination(GURL("[3ffe:ffff:100:7031:0:0::1]")));
  EXPECT_FALSE(rule.MatchesDestination(GURL("http://gooogle.com")));
  EXPECT_FALSE(rule.MatchesDestination(GURL("https://google.net")));
  EXPECT_FALSE(rule.MatchesDestination(GURL("https://google.org:123")));

  // The following patterns are exceptions:
  // - mail.google.com
  // - http://*.org
  EXPECT_FALSE(rule.MatchesDestination(GURL("http://mail.google.com")));
  EXPECT_FALSE(rule.MatchesDestination(GURL("https://mail.google.com")));
  EXPECT_FALSE(rule.MatchesDestination(GURL("http://google.org:443")));
}

#if BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)
TEST(ProxyConfigTest, EqualsMultiProxyChains) {
  ProxyConfig config1;
  ProxyConfig config2;

  config2.proxy_rules().type =
      ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME;
  config2.proxy_rules().proxies_for_https.SetSingleProxyChain(
      MultiProxyUrisToProxyChain("[https://foopy:443 https://hoopy:443]",
                                 ProxyServer::SCHEME_HTTPS));

  EXPECT_FALSE(config1.Equals(config2));
  EXPECT_FALSE(config2.Equals(config1));

  config1.proxy_rules().type =
      ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME;
  config1.proxy_rules().proxies_for_https.SetSingleProxyChain(
      MultiProxyUrisToProxyChain("[https://foopy:80 https://hoopy:80]",
                                 ProxyServer::SCHEME_HTTPS));

  EXPECT_FALSE(config1.Equals(config2));
  EXPECT_FALSE(config2.Equals(config1));

  config1.proxy_rules().proxies_for_https.SetSingleProxyChain(
      MultiProxyUrisToProxyChain("[https://foopy https://hoopy]",
                                 ProxyServer::SCHEME_HTTPS));

  EXPECT_TRUE(config1.Equals(config2));
  EXPECT_TRUE(config2.Equals(config1));
}
#endif  // BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)

struct ProxyConfigToValueTestCase {
  ProxyConfig config;
  const char* expected_value_json;
};

class ProxyConfigToValueTest
    : public ::testing::TestWithParam<ProxyConfigToValueTestCase> {};

TEST_P(ProxyConfigToValueTest, ToValueJSON) {
  const ProxyConfigToValueTestCase& test_case = GetParam();

  base::Value value = test_case.config.ToValue();

  std::string json_string;
  ASSERT_TRUE(base::JSONWriter::Write(value, &json_string));

  EXPECT_EQ(std::string(test_case.expected_value_json), json_string);
}

ProxyConfigToValueTestCase GetTestCaseDirect() {
  return {ProxyConfig::CreateDirect(), "{}"};
}

ProxyConfigToValueTestCase GetTestCaseAutoDetect() {
  return {ProxyConfig::CreateAutoDetect(), "{\"auto_detect\":true}"};
}

ProxyConfigToValueTestCase GetTestCasePacUrl() {
  ProxyConfig config;
  config.set_pac_url(GURL("http://www.example.com/test.pac"));

  return {std::move(config),
          "{\"pac_url\":\"http://www.example.com/test.pac\"}"};
}

ProxyConfigToValueTestCase GetTestCasePacUrlMandatory() {
  ProxyConfig config;
  config.set_pac_url(GURL("http://www.example.com/test.pac"));
  config.set_pac_mandatory(true);

  return {std::move(config),
          "{\"pac_mandatory\":true,\"pac_url\":\"http://www.example.com/"
          "test.pac\"}"};
}

ProxyConfigToValueTestCase GetTestCasePacUrlAndAutoDetect() {
  ProxyConfig config = ProxyConfig::CreateAutoDetect();
  config.set_pac_url(GURL("http://www.example.com/test.pac"));

  return {
      std::move(config),
      "{\"auto_detect\":true,\"pac_url\":\"http://www.example.com/test.pac\"}"};
}

ProxyConfigToValueTestCase GetTestCaseSingleProxy() {
  ProxyConfig config;
  config.proxy_rules().ParseFromString("https://proxy1:8080");

  return {std::move(config), "{\"single_proxy\":[\"[https://proxy1:8080]\"]}"};
}

ProxyConfigToValueTestCase GetTestCaseSingleProxyWithBypass() {
  ProxyConfig config;
  config.proxy_rules().ParseFromString("https://proxy1:8080");
  config.proxy_rules().bypass_rules.AddRuleFromString("*.google.com");
  config.proxy_rules().bypass_rules.AddRuleFromString("192.168.0.1/16");

  return {std::move(config),
          "{\"bypass_list\":[\"*.google.com\",\"192.168.0.1/"
          "16\"],\"single_proxy\":[\"[https://proxy1:8080]\"]}"};
}

ProxyConfigToValueTestCase GetTestCaseSingleProxyWithReversedBypass() {
  ProxyConfig config;
  config.proxy_rules().ParseFromString("https://proxy1:8080");
  config.proxy_rules().bypass_rules.AddRuleFromString("*.google.com");
  config.proxy_rules().reverse_bypass = true;

  return {std::move(config),
          "{\"bypass_list\":[\"*.google.com\"],\"reverse_bypass\":true,"
          "\"single_proxy\":[\"[https://proxy1:8080]\"]}"};
}

ProxyConfigToValueTestCase GetTestCaseProxyPerScheme() {
  ProxyConfig config;
  config.proxy_rules().ParseFromString(
      "http=https://proxy1:8080;https=socks5://proxy2");
  config.proxy_rules().bypass_rules.AddRuleFromString("*.google.com");
  config.set_pac_url(GURL("http://wpad/wpad.dat"));
  config.set_auto_detect(true);

  return {
      std::move(config),
      "{\"auto_detect\":true,\"bypass_list\":[\"*.google.com\"],\"pac_url\":"
      "\"http://wpad/wpad.dat\",\"proxy_per_scheme\":{\"http\":[\"[https://"
      "proxy1:8080]\"],\"https\":[\"[socks5://proxy2:1080]\"]}}"};
}

ProxyConfigToValueTestCase GetTestCaseSingleProxyList() {
  ProxyConfig config;
  config.proxy_rules().ParseFromString(
      "https://proxy1:8080,http://proxy2,direct://");

  return {
      std::move(config),
      "{\"single_proxy\":[\"[https://proxy1:8080]\",\"[proxy2:80]\",\"direct://"
      "\"]}"};
}

#if BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)
// Multi-proxy chains present
ProxyConfigToValueTestCase GetTestCaseMultiProxyChainProxyPerScheme() {
  ProxyConfig config;
  config.proxy_rules().ParseFromString(
      "http=[https://proxy1:8080 https://proxy2:8080];https=socks5://proxy2",
      /*allow_bracketed_proxy_chains=*/true);
  config.proxy_rules().bypass_rules.AddRuleFromString("*.google.com");
  config.set_pac_url(GURL("http://wpad/wpad.dat"));
  config.set_auto_detect(true);

  return {std::move(config),
          "{\"auto_detect\":true,\"bypass_list\":[\"*.google.com\"],\"pac_"
          "url\":\"http://wpad/"
          "wpad.dat\",\"proxy_per_scheme\":{\"http\":[\"[https://proxy1:8080, "
          "https://proxy2:8080]\"],\"https\":[\"[socks5://proxy2:1080]\"]}}"};
}
#endif  // BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)

ProxyConfigToValueTestCase GetTestCaseOverrideRule() {
  ProxyConfig::ProxyOverrideRule rule;
  rule.destination_matchers.AddRuleFromString("http://www.example.com");
  rule.proxy_list.SetFromPacString("HTTPS foo:333; DIRECT");
  rule.dns_conditions = {
      ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
          .host = url::SchemeHostPort("http", "ads.corps", 321),
          .result = ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::
              kNotFound},
      ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
          .host = url::SchemeHostPort("https", "ads2.corps", 443),
          .result = ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::
              kResolved},
  };

  auto config = ProxyConfig::CreateDirect();
  config.set_proxy_override_rules({std::move(rule)});
  return {std::move(config),
          "{\"override_rules\":[{\"destination_matchers\":\"http://"
          "www.example.com;\",\"dns_conditions\":[{\"host\":\"http://"
          "ads.corps:321\",\"result\":\"NotFound\"},{\"host\":\"https://"
          "ads2.corps\",\"result\":\"Resolved\"}],\"proxy_list\":[\"[https://"
          "foo:333]\",\"direct://\"]}]}"};
}

INSTANTIATE_TEST_SUITE_P(
    All,
    ProxyConfigToValueTest,
    testing::Values(GetTestCaseDirect(),
                    GetTestCaseAutoDetect(),
                    GetTestCasePacUrl(),
                    GetTestCasePacUrlMandatory(),
                    GetTestCasePacUrlAndAutoDetect(),
                    GetTestCaseSingleProxy(),
                    GetTestCaseSingleProxyWithBypass(),
                    GetTestCaseSingleProxyWithReversedBypass(),
                    GetTestCaseProxyPerScheme(),
#if BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)
                    GetTestCaseMultiProxyChainProxyPerScheme(),
#endif  // BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)
                    GetTestCaseSingleProxyList(),
                    GetTestCaseOverrideRule()));

TEST(ProxyConfigTest, ParseProxyRules) {
  const struct {
    const char* proxy_rules;

    ProxyConfig::ProxyRules::Type type;
    // These will be PAC-stle strings, eg 'PROXY foo.com'
    const char* single_proxy;
    const char* proxy_for_http;
    const char* proxy_for_https;
    const char* proxy_for_ftp;
    const char* fallback_proxy;
  } tests[] = {
      // One HTTP proxy for all schemes.
      {
          "myproxy:80",

          ProxyConfig::ProxyRules::Type::PROXY_LIST,
          "PROXY myproxy:80",
          nullptr,
          nullptr,
          nullptr,
          nullptr,
      },

      // Multiple HTTP proxies for all schemes.
      {
          "myproxy:80,https://myotherproxy",

          ProxyConfig::ProxyRules::Type::PROXY_LIST,
          "PROXY myproxy:80;HTTPS myotherproxy:443",
          nullptr,
          nullptr,
          nullptr,
          nullptr,
      },

      // Only specify a proxy server for "http://" urls.
      {
          "http=myproxy:80",

          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          "PROXY myproxy:80",
          nullptr,
          nullptr,
          nullptr,
      },

      // Specify an HTTP proxy for "ftp://" and a SOCKS proxy for "https://"
      // urls.
      {
          "ftp=ftp-proxy ; https=socks4://foopy",

          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          nullptr,
          "SOCKS foopy:1080",
          "PROXY ftp-proxy:80",
          nullptr,
      },

      // Give a scheme-specific proxy as well as a non-scheme specific.
      // The first entry "foopy" takes precedence marking this list as
      // Type::PROXY_LIST.
      {
          "foopy ; ftp=ftp-proxy",

          ProxyConfig::ProxyRules::Type::PROXY_LIST,
          "PROXY foopy:80",
          nullptr,
          nullptr,
          nullptr,
          nullptr,
      },

      // Give a scheme-specific proxy as well as a non-scheme specific.
      // The first entry "ftp=ftp-proxy" takes precedence marking this list as
      // Type::PROXY_LIST_PER_SCHEME.
      {
          "ftp=ftp-proxy ; foopy",

          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          nullptr,
          nullptr,
          "PROXY ftp-proxy:80",
          nullptr,
      },

      // Include a list of entries for a single scheme.
      {
          "ftp=ftp1,ftp2,ftp3",

          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          nullptr,
          nullptr,
          "PROXY ftp1:80;PROXY ftp2:80;PROXY ftp3:80",
          nullptr,
      },

      // Include multiple entries for the same scheme -- they accumulate.
      {
          "http=http1,http2; http=http3",

          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          "PROXY http1:80;PROXY http2:80;PROXY http3:80",
          nullptr,
          nullptr,
          nullptr,
      },

      // Include lists of entries for multiple schemes.
      {
          "ftp=ftp1,ftp2,ftp3 ; http=http1,http2; ",

          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          "PROXY http1:80;PROXY http2:80",
          nullptr,
          "PROXY ftp1:80;PROXY ftp2:80;PROXY ftp3:80",
          nullptr,
      },

      // Include non-default proxy schemes.
      {
          "http=https://secure_proxy; ftp=socks4://socks_proxy; "
          "https=socks://foo",

          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          "HTTPS secure_proxy:443",
          "SOCKS5 foo:1080",
          "SOCKS socks_proxy:1080",
          nullptr,
      },

      // Only SOCKS proxy present, others being blank.
      {
          "socks=foopy",

          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          nullptr,
          nullptr,
          nullptr,
          "SOCKS foopy:1080",
      },

      // SOCKS proxy present along with other proxies too
      {
          "http=httpproxy ; https=httpsproxy ; ftp=ftpproxy ; socks=foopy ",

          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          "PROXY httpproxy:80",
          "PROXY httpsproxy:80",
          "PROXY ftpproxy:80",
          "SOCKS foopy:1080",
      },

      // SOCKS proxy (with modifier) present along with some proxies
      // (FTP being blank)
      {
          "http=httpproxy ; https=httpsproxy ; socks=socks5://foopy ",

          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          "PROXY httpproxy:80",
          "PROXY httpsproxy:80",
          nullptr,
          "SOCKS5 foopy:1080",
      },

      // Include unsupported schemes -- they are discarded.
      {
          "crazy=foopy ; foo=bar ; https=myhttpsproxy",

          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          nullptr,
          "PROXY myhttpsproxy:80",
          nullptr,
          nullptr,
      },

      // direct:// as first option for a scheme.
      {
          "http=direct://,myhttpproxy; https=direct://",

          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          "DIRECT;PROXY myhttpproxy:80",
          "DIRECT",
          nullptr,
          nullptr,
      },

      // direct:// as a second option for a scheme.
      {
          "http=myhttpproxy,direct://",

          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          "PROXY myhttpproxy:80;DIRECT",
          nullptr,
          nullptr,
          nullptr,
      },

      // Multi-proxy bracketed URIs will result in no proxy being set
      {
          "http=[https://proxy1:8080 https://proxy2:8080]",
          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          nullptr,
          nullptr,
          nullptr,
          nullptr,
      }

  };

  ProxyConfig config;

  for (const auto& test : tests) {
    config.proxy_rules().ParseFromString(test.proxy_rules);
    EXPECT_EQ(test.type, config.proxy_rules().type);
    ExpectProxyServerEquals(test.single_proxy,
                            config.proxy_rules().single_proxies);
    ExpectProxyServerEquals(test.proxy_for_http,
                            config.proxy_rules().proxies_for_http);
    ExpectProxyServerEquals(test.proxy_for_https,
                            config.proxy_rules().proxies_for_https);
    ExpectProxyServerEquals(test.proxy_for_ftp,
                            config.proxy_rules().proxies_for_ftp);
    ExpectProxyServerEquals(test.fallback_proxy,
                            config.proxy_rules().fallback_proxies);
  }
}

#if BUILDFLAG(ENABLE_QUIC_PROXY_SUPPORT)
// When the bool to allow quic proxy support is false, there should be no valid
// proxies in the config.
TEST(ProxyConfigTest, ParseProxyRulesQuicIsNotAllowed) {
  ProxyConfig config;

  config.proxy_rules().ParseFromString("quic://foopy:443",
                                       /*allow_bracketed_proxy_chains=*/false,
                                       /*is_quic_allowed=*/false);

  EXPECT_EQ(ProxyConfig::ProxyRules::Type::PROXY_LIST,
            config.proxy_rules().type);
  ExpectProxyServerEquals(nullptr, config.proxy_rules().single_proxies);
  ExpectProxyServerEquals(nullptr, config.proxy_rules().proxies_for_http);
  ExpectProxyServerEquals(nullptr, config.proxy_rules().proxies_for_https);
  ExpectProxyServerEquals(nullptr, config.proxy_rules().proxies_for_ftp);
  ExpectProxyServerEquals(nullptr, config.proxy_rules().fallback_proxies);
}

// When the bool to allow quic proxy support is true, a valid quic proxy should
// be found in the config.
TEST(ProxyConfigTest, ParseProxyRulesQuicIsAllowed) {
  ProxyConfig config;

  config.proxy_rules().ParseFromString("quic://foopy:443",
                                       /*allow_bracketed_proxy_chains=*/false,
                                       /*is_quic_allowed=*/true);

  EXPECT_EQ(ProxyConfig::ProxyRules::Type::PROXY_LIST,
            config.proxy_rules().type);
  ExpectProxyServerEquals("QUIC foopy:443",
                          config.proxy_rules().single_proxies);
  ExpectProxyServerEquals(nullptr, config.proxy_rules().proxies_for_http);
  ExpectProxyServerEquals(nullptr, config.proxy_rules().proxies_for_https);
  ExpectProxyServerEquals(nullptr, config.proxy_rules().proxies_for_ftp);
  ExpectProxyServerEquals(nullptr, config.proxy_rules().fallback_proxies);
}
#endif  // BUILDFLAG(ENABLE_QUIC_PROXY_SUPPORT)

#if !BUILDFLAG(ENABLE_QUIC_PROXY_SUPPORT)
// When the build flag is disabled for QUIC support, `ParseFromString` should
// not allow QUIC proxy support by setting bool to true. A true value should
// crash.
TEST(ProxyConfigTest,
     ParseProxyRulesDisallowQuicProxySupportIfBuildFlagDisabled) {
  ProxyConfig config;

  EXPECT_CHECK_DEATH(config.proxy_rules().ParseFromString(
      "quic://foopy:443",
      /*allow_bracketed_proxy_chains=*/false, /*is_quic_allowed=*/true));
}
#endif  // !BUILDFLAG(ENABLE_QUIC_PROXY_SUPPORT)

#if !BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)
// In release builds, `ParseFromString` should not allow parsing of multi-proxy
// chains by setting bool to true. A true value should crash.
TEST(ProxyConfigTest, ParseProxyRulesDisallowMultiProxyChainsInReleaseBuilds) {
  ProxyConfig config;

  EXPECT_CHECK_DEATH(config.proxy_rules().ParseFromString(
      "http=[https://proxy1:8080 https://proxy2:8080]", true));
}
#endif  // !BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)

#if BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)
// Tests for multi-proxy chains which are currently only allowed in debug mode.
TEST(ProxyConfigTest, MultiProxyChainsParseProxyRules) {
  const struct {
    const char* proxy_rules;

    ProxyConfig::ProxyRules::Type type;
    // For multi-proxy chains, proxies within a single chain will be formatted
    // within a bracket separated by a space and comma.
    const char* single_proxy;
    const char* proxy_for_http;
    const char* proxy_for_https;
    const char* proxy_for_ftp;
    const char* fallback_proxy;
  } tests[] = {

      // One HTTP proxy for all schemes.
      {
          "[https://proxy1:8080]",
          ProxyConfig::ProxyRules::Type::PROXY_LIST,
          "HTTPS proxy1:8080",
          nullptr,
          nullptr,
          nullptr,
          nullptr,
      },

      // Multiple proxies for all schemes.
      {
          "[https://proxy1:8080 https://proxy2:8080],[https://proxy3:8080]",
          ProxyConfig::ProxyRules::Type::PROXY_LIST,
          "[https://proxy1:8080, https://proxy2:8080];HTTPS proxy3:8080",
          nullptr,
          nullptr,
          nullptr,
          nullptr,
      },

      // Only specify a proxy chain for "http://" urls.
      {
          "http=[https://proxy1:8080 https://proxy2:8080]",
          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          "[https://proxy1:8080, https://proxy2:8080]",
          nullptr,
          nullptr,
          nullptr,
      },

      // Specify different multi-proxy chains for different schemes.
      {
          "http=[https://proxy1:8080 https://proxy2:8080] ; "
          "https=[https://proxy3:8080 https://proxy4:8080]",
          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          "[https://proxy1:8080, https://proxy2:8080]",
          "[https://proxy3:8080, https://proxy4:8080]",
          nullptr,
          nullptr,
      },

      // Give a scheme-specific proxy as well as a non-scheme specific.
      // The first entry takes precedence marking this list as Type::PROXY_LIST.
      {
          "[https://proxy1:8080 https://proxy2:8080] ; "
          "http=[https://proxy3:8080 https://proxy4:8080]",
          ProxyConfig::ProxyRules::Type::PROXY_LIST,
          "[https://proxy1:8080, https://proxy2:8080]",
          nullptr,
          nullptr,
          nullptr,
          nullptr,
      },

      // Give a scheme-specific proxy as well as a non-scheme specific.
      // The first entry takes precedence marking this list as
      // Type::PROXY_LIST_PER_SCHEME.
      {
          "http=[https://proxy3:8080 https://proxy4:8080] ; "
          "[https://proxy1:8080 https://proxy2:8080]",
          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          "[https://proxy3:8080, https://proxy4:8080]",
          nullptr,
          nullptr,
          nullptr,
      },

      // Include a list of entries for a single scheme.
      {
          "ftp=[https://proxy1:80 https://proxy2:80],[https://proxy3:80 "
          "https://proxy4:80],[https://proxy5:80 https://proxy6:80]",
          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          nullptr,
          nullptr,
          "[https://proxy1:80, https://proxy2:80];[https://proxy3:80, "
          "https://proxy4:80];[https://proxy5:80, https://proxy6:80]",
          nullptr,
      },

      // Include multiple entries for the same scheme -- they accumulate.
      {
          "http=[https://proxy1:80 https://proxy2:80]; http=[https://proxy3:80 "
          "https://proxy4:80],[https://proxy5:80 https://proxy6:80]",
          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          "[https://proxy1:80, https://proxy2:80];[https://proxy3:80, "
          "https://proxy4:80];[https://proxy5:80, https://proxy6:80]",
          nullptr,
          nullptr,
          nullptr,
      },

      // Include lists of entries for multiple schemes.
      {
          "http=[https://proxy1:80 https://proxy2:80]; ftp=[https://proxy3:80 "
          "https://proxy4:80],[https://proxy5:80 https://proxy6:80]",
          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          "[https://proxy1:80, https://proxy2:80]",
          nullptr,
          "[https://proxy3:80, https://proxy4:80];[https://proxy5:80, "
          "https://proxy6:80]",
          nullptr,
      },

      // Include unsupported schemes -- they are discarded.
      {
          "crazy=[https://proxy1:80 https://proxy2:80] ; foo=bar ; "
          "https=[https://proxy3:80 https://proxy4:80]",

          ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME,
          nullptr,
          nullptr,
          "[https://proxy3:80, https://proxy4:80]",
          nullptr,
          nullptr,
      },
  };

  ProxyConfig config;

  for (const auto& test : tests) {
    config.proxy_rules().ParseFromString(test.proxy_rules, true);
    EXPECT_EQ(test.type, config.proxy_rules().type);
    ExpectProxyServerEquals(test.single_proxy,
                            config.proxy_rules().single_proxies);
    ExpectProxyServerEquals(test.proxy_for_http,
                            config.proxy_rules().proxies_for_http);
    ExpectProxyServerEquals(test.proxy_for_https,
                            config.proxy_rules().proxies_for_https);
    ExpectProxyServerEquals(test.proxy_for_ftp,
                            config.proxy_rules().proxies_for_ftp);
    ExpectProxyServerEquals(test.fallback_proxy,
                            config.proxy_rules().fallback_proxies);
  }
}
#endif  // BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)

TEST(ProxyConfigTest, ProxyRulesSetBypassFlag) {
  // Test whether the did_bypass_proxy() flag is set in proxy info correctly.
  ProxyConfig::ProxyRules rules;
  ProxyInfo result;

  rules.ParseFromString("http=httpproxy:80");
  rules.bypass_rules.AddRuleFromString(".com");

  rules.Apply(GURL("http://example.com"), &result);
  EXPECT_TRUE(result.is_direct_only());
  EXPECT_TRUE(result.did_bypass_proxy());

  rules.Apply(GURL("http://example.org"), &result);
  EXPECT_FALSE(result.is_direct());
  EXPECT_FALSE(result.did_bypass_proxy());

  // Try with reversed bypass rules.
  rules.reverse_bypass = true;

  rules.Apply(GURL("http://example.org"), &result);
  EXPECT_TRUE(result.is_direct_only());
  EXPECT_TRUE(result.did_bypass_proxy());

  rules.Apply(GURL("http://example.com"), &result);
  EXPECT_FALSE(result.is_direct());
  EXPECT_FALSE(result.did_bypass_proxy());
}

static const char kWsUrl[] = "ws://example.com/echo";
static const char kWssUrl[] = "wss://example.com/echo";

class ProxyConfigWebSocketTest : public ::testing::Test {
 protected:
  void ParseFromString(const std::string& rules) {
    rules_.ParseFromString(rules);
  }
  void Apply(const GURL& gurl) { rules_.Apply(gurl, &info_); }
  std::string ToDebugString() const { return info_.ToDebugString(); }

  static GURL WsUrl() { return GURL(kWsUrl); }
  static GURL WssUrl() { return GURL(kWssUrl); }

  ProxyConfig::ProxyRules rules_;
  ProxyInfo info_;
};

// If a single proxy is set for all protocols, WebSocket uses it.
TEST_F(ProxyConfigWebSocketTest, UsesProxy) {
  ParseFromString("proxy:3128");
  Apply(WsUrl());
  EXPECT_EQ("PROXY proxy:3128", ToDebugString());
}

// See RFC6455 Section 4.1. item 3, "_Proxy Usage_". Note that this favors a
// SOCKSv4 proxy (although technically the spec only notes SOCKSv5).
TEST_F(ProxyConfigWebSocketTest, PrefersSocksV4) {
  ParseFromString(
      "http=proxy:3128 ; https=sslproxy:3128 ; socks=socksproxy:1080");
  Apply(WsUrl());
  EXPECT_EQ("SOCKS socksproxy:1080", ToDebugString());
}

// See RFC6455 Section 4.1. item 3, "_Proxy Usage_".
TEST_F(ProxyConfigWebSocketTest, PrefersSocksV5) {
  ParseFromString(
      "http=proxy:3128 ; https=sslproxy:3128 ; socks=socks5://socksproxy:1080");
  Apply(WsUrl());
  EXPECT_EQ("SOCKS5 socksproxy:1080", ToDebugString());
}

TEST_F(ProxyConfigWebSocketTest, PrefersHttpsToHttp) {
  ParseFromString("http=proxy:3128 ; https=sslproxy:3128");
  Apply(WssUrl());
  EXPECT_EQ("PROXY sslproxy:3128", ToDebugString());
}

// Tests when a proxy-per-url-scheme configuration was used, and proxies are
// specified for http://, https://, and a fallback proxy (non-SOCKS).
// Even though the fallback proxy is not SOCKS, it is still favored over the
// proxy for http://* and https://*.
TEST_F(ProxyConfigWebSocketTest, PrefersNonSocksFallbackOverHttps) {
  // The notation for "socks=" is abused to set the "fallback proxy".
  ParseFromString(
      "http=proxy:3128 ; https=sslproxy:3128; socks=https://httpsproxy");
  EXPECT_EQ("HTTPS httpsproxy:443", rules_.fallback_proxies.ToDebugString());
  Apply(WssUrl());
  EXPECT_EQ("HTTPS httpsproxy:443", ToDebugString());
}

// Tests when a proxy-per-url-scheme configuration was used, and the fallback
// proxy is a non-SOCKS proxy, and no proxy was given for https://* or
// http://*. The fallback proxy is used.
TEST_F(ProxyConfigWebSocketTest, UsesNonSocksFallbackProxy) {
  // The notation for "socks=" is abused to set the "fallback proxy".
  ParseFromString("ftp=ftpproxy:3128; socks=https://httpsproxy");
  EXPECT_EQ("HTTPS httpsproxy:443", rules_.fallback_proxies.ToDebugString());
  Apply(WssUrl());
  EXPECT_EQ("HTTPS httpsproxy:443", ToDebugString());
}

TEST_F(ProxyConfigWebSocketTest, PrefersHttpsEvenForWs) {
  ParseFromString("http=proxy:3128 ; https=sslproxy:3128");
  Apply(WsUrl());
  EXPECT_EQ("PROXY sslproxy:3128", ToDebugString());
}

TEST_F(ProxyConfigWebSocketTest, PrefersHttpToDirect) {
  ParseFromString("http=proxy:3128");
  Apply(WssUrl());
  EXPECT_EQ("PROXY proxy:3128", ToDebugString());
}

TEST_F(ProxyConfigWebSocketTest, IgnoresFtpProxy) {
  ParseFromString("ftp=ftpproxy:3128");
  Apply(WssUrl());
  EXPECT_EQ("DIRECT", ToDebugString());
}

TEST_F(ProxyConfigWebSocketTest, ObeysBypassRules) {
  ParseFromString("http=proxy:3128 ; https=sslproxy:3128");
  rules_.bypass_rules.AddRuleFromString(".chromium.org");
  Apply(GURL("wss://codereview.chromium.org/feed"));
  EXPECT_EQ("DIRECT", ToDebugString());
}

TEST_F(ProxyConfigWebSocketTest, ObeysLocalBypass) {
  ParseFromString("http=proxy:3128 ; https=sslproxy:3128");
  rules_.bypass_rules.AddRuleFromString("<local>");
  Apply(GURL("ws://localhost/feed"));
  EXPECT_EQ("DIRECT", ToDebugString());
}

}  // namespace
}  // namespace net