blob: abfc61f51a9f920ef84ea48bf1a04ae718376e74 [file] [log] [blame]
// Copyright 2021 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/base/proxy_string_util.h"
#include <string>
#include <vector>
#include "build/buildflag.h"
#include "net/base/proxy_chain.h"
#include "net/base/proxy_server.h"
#include "net/net_buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
namespace {
// Test the creation of ProxyServer using ProxyUriToProxyServer, which parses
// inputs of the form [<scheme>"://"]<host>[":"<port>]. Verify that each part
// was labelled correctly, and the accessors all give the right data.
TEST(ProxySpecificationUtilTest, ProxyUriToProxyServer) {
const struct {
const char* const input_uri;
const char* const expected_uri;
ProxyServer::Scheme expected_scheme;
const char* const expected_host;
int expected_port;
const char* const expected_pac_string;
} tests[] = {
// HTTP proxy URIs:
{"foopy:10", // No scheme.
"foopy:10", ProxyServer::SCHEME_HTTP, "foopy", 10, "PROXY foopy:10"},
{"http://foopy", // No port.
"foopy:80", ProxyServer::SCHEME_HTTP, "foopy", 80, "PROXY foopy:80"},
{"http://foopy:10", "foopy:10", ProxyServer::SCHEME_HTTP, "foopy", 10,
"PROXY foopy:10"},
// IPv6 HTTP proxy URIs:
{"[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:10", // No scheme.
"[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:10", ProxyServer::SCHEME_HTTP,
"fedc:ba98:7654:3210:fedc:ba98:7654:3210", 10,
"PROXY [fedc:ba98:7654:3210:fedc:ba98:7654:3210]:10"},
{"http://[3ffe:2a00:100:7031::1]", // No port.
"[3ffe:2a00:100:7031::1]:80", ProxyServer::SCHEME_HTTP,
"3ffe:2a00:100:7031::1", 80, "PROXY [3ffe:2a00:100:7031::1]:80"},
// SOCKS4 proxy URIs:
{"socks4://foopy", // No port.
"socks4://foopy:1080", ProxyServer::SCHEME_SOCKS4, "foopy", 1080,
"SOCKS foopy:1080"},
{"socks4://foopy:10", "socks4://foopy:10", ProxyServer::SCHEME_SOCKS4,
"foopy", 10, "SOCKS foopy:10"},
// SOCKS5 proxy URIs:
{"socks5://foopy", // No port.
"socks5://foopy:1080", ProxyServer::SCHEME_SOCKS5, "foopy", 1080,
"SOCKS5 foopy:1080"},
{"socks5://foopy:10", "socks5://foopy:10", ProxyServer::SCHEME_SOCKS5,
"foopy", 10, "SOCKS5 foopy:10"},
// SOCKS proxy URIs (should default to SOCKS5)
{"socks://foopy", // No port.
"socks5://foopy:1080", ProxyServer::SCHEME_SOCKS5, "foopy", 1080,
"SOCKS5 foopy:1080"},
{"socks://foopy:10", "socks5://foopy:10", ProxyServer::SCHEME_SOCKS5,
"foopy", 10, "SOCKS5 foopy:10"},
// HTTPS proxy URIs:
{"https://foopy", // No port
"https://foopy:443", ProxyServer::SCHEME_HTTPS, "foopy", 443,
"HTTPS foopy:443"},
{"https://foopy:10", // Non-standard port
"https://foopy:10", ProxyServer::SCHEME_HTTPS, "foopy", 10,
"HTTPS foopy:10"},
{"https://1.2.3.4:10", // IP Address
"https://1.2.3.4:10", ProxyServer::SCHEME_HTTPS, "1.2.3.4", 10,
"HTTPS 1.2.3.4:10"},
#if BUILDFLAG(ENABLE_QUIC_PROXY_SUPPORT)
// QUIC proxy URIs:
{"quic://foopy", // no port
"quic://foopy:443", ProxyServer::SCHEME_QUIC, "foopy", 443,
"QUIC foopy:443"},
{"quic://foopy:80", "quic://foopy:80", ProxyServer::SCHEME_QUIC, "foopy",
80, "QUIC foopy:80"},
#endif // BUILDFLAG(ENABLE_QUIC_PROXY_SUPPORT)
// Hostname canonicalization:
{"[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:10", // No scheme.
"[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:10", ProxyServer::SCHEME_HTTP,
"fedc:ba98:7654:3210:fedc:ba98:7654:3210", 10,
"PROXY [fedc:ba98:7654:3210:fedc:ba98:7654:3210]:10"},
{"http://[::192.9.5.5]", "[::c009:505]:80", ProxyServer::SCHEME_HTTP,
"::c009:505", 80, "PROXY [::c009:505]:80"},
{"http://[::FFFF:129.144.52.38]:80", "[::ffff:8190:3426]:80",
ProxyServer::SCHEME_HTTP, "::ffff:8190:3426", 80,
"PROXY [::ffff:8190:3426]:80"},
{"http://f\u00fcpy:85", "xn--fpy-hoa:85", ProxyServer::SCHEME_HTTP,
"xn--fpy-hoa", 85, "PROXY xn--fpy-hoa:85"},
{"https://0xA.020.3.4:443", "https://10.16.3.4:443",
ProxyServer::SCHEME_HTTPS, "10.16.3.4", 443, "HTTPS 10.16.3.4:443"},
{"http://FoO.tEsT:80", "foo.test:80", ProxyServer::SCHEME_HTTP,
"foo.test", 80, "PROXY foo.test:80"},
};
for (const auto& test : tests) {
ProxyServer uri = ProxyUriToProxyServer(
test.input_uri, ProxyServer::SCHEME_HTTP, /*is_quic_allowed=*/true);
EXPECT_TRUE(uri.is_valid());
EXPECT_EQ(test.expected_uri, ProxyServerToProxyUri(uri));
EXPECT_EQ(test.expected_scheme, uri.scheme());
EXPECT_EQ(test.expected_host, uri.host_port_pair().host());
EXPECT_EQ(test.expected_port, uri.host_port_pair().port());
EXPECT_EQ(test.expected_pac_string, ProxyServerToPacResultElement(uri));
}
}
#if BUILDFLAG(ENABLE_QUIC_PROXY_SUPPORT)
// In a build where the quic proxy support build flag is enabled, if the
// boolean for allowing quic proxy support is false, it will be considered in an
// invalid scheme as QUIC should not be parsed.
TEST(ProxySpecificationUtilTest,
ProxyUriToProxyServerBuildFlagEnabledQuicDisallowedIsInvalid) {
ProxyServer proxy_server = ProxyUriToProxyServer(
"quic://foopy:443", ProxyServer::SCHEME_HTTP, /*is_quic_allowed=*/false);
EXPECT_FALSE(proxy_server.is_valid());
EXPECT_EQ(ProxyServer::SCHEME_INVALID, proxy_server.scheme());
}
#else
// In a build where the quic proxy support build flag is disabled, if the
// boolean for allowing quic proxy support is true, it will be considered in an
// invalid scheme as QUIC is not allowed in this type of build.
TEST(ProxySpecificationUtilTest,
ProxyUriToProxyServerBuildFlagDisabledQuicAllowedIsInvalid) {
ProxyServer proxy_server = ProxyUriToProxyServer(
"quic://foopy:443", ProxyServer::SCHEME_HTTP, /*is_quic_allowed=*/true);
EXPECT_FALSE(proxy_server.is_valid());
EXPECT_EQ(ProxyServer::SCHEME_INVALID, proxy_server.scheme());
}
#endif // BUILDFLAG(ENABLE_QUIC_PROXY_SUPPORT)
// Test parsing of the special URI form "direct://".
TEST(ProxySpecificationUtilTest, DirectProxyUriToProxyChain) {
const char* const uris[] = {
"direct://",
"DIRECT://",
"DiReCt://",
};
for (const char* uri : uris) {
ProxyChain valid_uri = ProxyUriToProxyChain(uri, ProxyServer::SCHEME_HTTP);
EXPECT_TRUE(valid_uri.IsValid());
EXPECT_TRUE(valid_uri.is_direct());
}
// Direct is not allowed a host/port.
ProxyChain invalid_uri =
ProxyUriToProxyChain("direct://xyz", ProxyServer::SCHEME_HTTP);
EXPECT_FALSE(invalid_uri.IsValid());
EXPECT_FALSE(invalid_uri.is_direct());
}
// A multi-proxy string containing URIs is not acceptable input for the
// ProxyUriToProxyChain function and should return an invalid `ProxyChain()`.
TEST(ProxySpecificationUtilTest, ProxyUriToProxyChainWithBracketsInvalid) {
// Release builds should return an invalid proxy chain for multi-proxy chains.
const char* const invalid_multi_proxy_uris[] = {
"[]",
"[direct://]",
"[https://foopy]",
"[https://foopy https://hoopy]",
};
for (const char* uri : invalid_multi_proxy_uris) {
ProxyChain multi_proxy_uri =
ProxyUriToProxyChain(uri, ProxyServer::SCHEME_HTTP);
EXPECT_FALSE(multi_proxy_uri.IsValid());
EXPECT_FALSE(multi_proxy_uri.is_direct());
}
}
// Test parsing some invalid inputs.
TEST(ProxySpecificationUtilTest, InvalidProxyUriToProxyServer) {
const char* const tests[] = {
"",
" ",
"dddf:", // not a valid port
"dddd:d", // not a valid port
"http://", // not a valid host/port.
"direct://", // direct is not a valid proxy server.
"http:/", // ambiguous, but will fail because of bad port.
"http:", // ambiguous, but will fail because of bad port.
"foopy.111", // Interpreted as invalid IPv4 address.
"foo.test/" // Paths disallowed.
"foo.test:123/" // Paths disallowed.
"foo.test/foo" // Paths disallowed.
};
for (const char* test : tests) {
SCOPED_TRACE(test);
ProxyServer uri = ProxyUriToProxyServer(test, ProxyServer::SCHEME_HTTP);
EXPECT_FALSE(uri.is_valid());
EXPECT_FALSE(uri.is_http());
EXPECT_FALSE(uri.is_socks());
}
}
// Test that LWS (SP | HT) is disregarded from the ends.
TEST(ProxySpecificationUtilTest, WhitespaceProxyUriToProxyServer) {
const char* const tests[] = {
" foopy:80",
"foopy:80 \t",
" \tfoopy:80 ",
};
for (const char* test : tests) {
ProxyServer uri = ProxyUriToProxyServer(test, ProxyServer::SCHEME_HTTP);
EXPECT_EQ("foopy:80", ProxyServerToProxyUri(uri));
}
}
// Test parsing a ProxyServer from a PAC representation.
TEST(ProxySpecificationUtilTest, PacResultElementToProxyServer) {
const struct {
const char* const input_pac;
const char* const expected_uri;
} tests[] = {
{
"PROXY foopy:10",
"foopy:10",
},
{
" PROXY foopy:10 ",
"foopy:10",
},
{
"pRoXy foopy:10",
"foopy:10",
},
{
"PROXY foopy", // No port.
"foopy:80",
},
{
"socks foopy",
"socks4://foopy:1080",
},
{
"socks4 foopy",
"socks4://foopy:1080",
},
{
"socks5 foopy",
"socks5://foopy:1080",
},
{
"socks5 foopy:11",
"socks5://foopy:11",
},
{
"https foopy",
"https://foopy:443",
},
{
"https foopy:10",
"https://foopy:10",
},
{"PROXY [FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:10",
"[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:10"},
{"PROXY f\u00fcpy:85", "xn--fpy-hoa:85"},
};
for (const auto& test : tests) {
SCOPED_TRACE(test.input_pac);
ProxyServer server = PacResultElementToProxyServer(test.input_pac);
EXPECT_TRUE(server.is_valid());
EXPECT_EQ(test.expected_uri, ProxyServerToProxyUri(server));
ProxyChain chain = PacResultElementToProxyChain(test.input_pac);
EXPECT_TRUE(chain.IsValid());
if (!chain.is_direct()) {
EXPECT_EQ(test.expected_uri, ProxyServerToProxyUri(chain.First()));
}
}
}
// Test parsing a ProxyServer from an invalid PAC representation.
TEST(ProxySpecificationUtilTest, InvalidPacResultElementToProxyServer) {
const char* const tests[] = {
"PROXY", // missing host/port.
"HTTPS", // missing host/port.
"SOCKS", // missing host/port.
"DIRECT foopy:10", // direct cannot have host/port.
"INVALIDSCHEME", // unrecognized scheme.
"INVALIDSCHEME foopy:10", // unrecognized scheme.
"HTTP foopy:10", // http scheme should be "PROXY"
};
for (const char* test : tests) {
SCOPED_TRACE(test);
ProxyServer server = PacResultElementToProxyServer(test);
EXPECT_FALSE(server.is_valid());
ProxyChain chain = PacResultElementToProxyChain(test);
EXPECT_FALSE(chain.IsValid());
}
}
#if BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)
// A multi-proxy chain that contains any mention of direct will be considered an
// invalid `ProxyChain()`.
TEST(ProxySpecificationUtilTest,
MultiProxyUrisToProxyChainMultiProxyDirectIsInvalid) {
const char* const invalid_multi_proxy_uris[] = {
"[direct://xyz]", // direct with ports
"[direct:// direct://]", // Two directs in chain
"[direct:// https://foopy]", // direct first in chain
"[https://foopy direct://]", // direct later in chain
};
for (const char* uri : invalid_multi_proxy_uris) {
ProxyChain multi_proxy_uri =
MultiProxyUrisToProxyChain(uri, ProxyServer::SCHEME_HTTPS);
EXPECT_FALSE(multi_proxy_uri.IsValid());
EXPECT_FALSE(multi_proxy_uri.is_direct());
}
}
// A input containing a single uri of direct will be valid.
TEST(ProxySpecificationUtilTest,
MultiProxyUrisToProxyChainSingleDirectIsValid) {
const char* const valid_direct_uris[] = {
"direct://", // non-bracketed direct
"[direct://]", // bracketed direct
};
for (const char* uri : valid_direct_uris) {
ProxyChain multi_proxy_uri =
MultiProxyUrisToProxyChain(uri, ProxyServer::SCHEME_HTTPS);
EXPECT_TRUE(multi_proxy_uri.IsValid());
EXPECT_TRUE(multi_proxy_uri.is_direct());
}
}
TEST(ProxySpecificationUtilTest, MultiProxyUrisToProxyChainValid) {
const struct {
const char* const input_uri;
const std::vector<std::string> expected_uris;
ProxyServer::Scheme expected_scheme;
} tests[] = {
// 1 Proxy (w/ and w/o brackets):
{"[https://foopy:443]", {"https://foopy:443"}, ProxyServer::SCHEME_HTTPS},
{"https://foopy:443", {"https://foopy:443"}, ProxyServer::SCHEME_HTTPS},
// 2 Proxies:
{"[https://foopy:443 https://hoopy:443]",
{"https://foopy:443", "https://hoopy:443"},
ProxyServer::SCHEME_HTTPS},
// Extra padding in uris string ignored:
{" [https://foopy:443 https://hoopy:443] ",
{"https://foopy:443", "https://hoopy:443"},
ProxyServer::SCHEME_HTTPS},
{"[\thttps://foopy:443 https://hoopy:443\t ] ",
{"https://foopy:443", "https://hoopy:443"},
ProxyServer::SCHEME_HTTPS},
{" \t[ https://foopy:443 https://hoopy:443\t ]",
{"https://foopy:443", "https://hoopy:443"},
ProxyServer::SCHEME_HTTPS},
{"[https://foopy:443 https://hoopy:443]",
{"https://foopy:443", "https://hoopy:443"},
ProxyServer::SCHEME_HTTPS}, // Delimiter is two spaces.
{"[https://foopy \thttps://hoopy]",
{"https://foopy:443", "https://hoopy:443"},
ProxyServer::SCHEME_HTTPS}, // Delimiter is followed by tab.
// 3 Proxies:
{"[https://foopy:443 https://hoopy:443 https://loopy:443]",
{"https://foopy:443", "https://hoopy:443", "https://loopy:443"},
ProxyServer::SCHEME_HTTPS},
};
for (const auto& test : tests) {
ProxyChain proxy_chain =
MultiProxyUrisToProxyChain(test.input_uri, test.expected_scheme);
EXPECT_TRUE(proxy_chain.IsValid());
EXPECT_EQ(proxy_chain.length(), test.expected_uris.size());
std::vector<ProxyServer> proxies = proxy_chain.proxy_servers();
for (size_t i = 0; i < proxies.size(); i++) {
const ProxyServer& proxy = proxies[i];
EXPECT_TRUE(proxy.is_valid());
EXPECT_EQ(test.expected_uris[i], ProxyServerToProxyUri(proxy));
}
}
}
#if BUILDFLAG(ENABLE_QUIC_PROXY_SUPPORT)
// Quic proxy schemes are parsed properly
TEST(ProxySpecificationUtilTest, MultiProxyUrisToProxyChainValidQuic) {
const struct {
const char* const input_uri;
const std::vector<std::string> expected_uris;
ProxyServer::Scheme default_scheme;
const std::vector<ProxyServer::Scheme> expected_schemes;
} tests[] = {
// single quic proxy scheme (unbracketed)
{"quic://foopy", // missing port number
{"quic://foopy:443"},
ProxyServer::SCHEME_HTTP,
{ProxyServer::SCHEME_QUIC}},
{"quic://foopy:80",
{"quic://foopy:80"},
ProxyServer::SCHEME_HTTP,
{ProxyServer::SCHEME_QUIC}},
// single quic proxy scheme (bracketed)
{"[quic://foopy:80]",
{"quic://foopy:80"},
ProxyServer::SCHEME_HTTP,
{ProxyServer::SCHEME_QUIC}},
// multi-proxy chain
// 2 quic schemes in a row
{"[quic://foopy:80 quic://loopy:80]",
{"quic://foopy:80", "quic://loopy:80"},
ProxyServer::SCHEME_HTTP,
{ProxyServer::SCHEME_QUIC, ProxyServer::SCHEME_QUIC}},
// Quic scheme followed by HTTPS in a row
{"[quic://foopy:80 https://loopy:80]",
{"quic://foopy:80", "https://loopy:80"},
ProxyServer::SCHEME_HTTP,
{ProxyServer::SCHEME_QUIC, ProxyServer::SCHEME_HTTPS}},
};
for (const auto& test : tests) {
ProxyChain proxy_chain = MultiProxyUrisToProxyChain(
test.input_uri, test.default_scheme, /*is_quic_allowed=*/true);
EXPECT_TRUE(proxy_chain.IsValid());
EXPECT_EQ(proxy_chain.length(), test.expected_uris.size());
std::vector<ProxyServer> proxies = proxy_chain.proxy_servers();
for (size_t i = 0; i < proxies.size(); i++) {
const ProxyServer& proxy = proxies[i];
EXPECT_TRUE(proxy.is_valid());
EXPECT_EQ(test.expected_uris[i], ProxyServerToProxyUri(proxy));
EXPECT_EQ(test.expected_schemes[i], proxy.scheme());
}
}
}
// If a multi-proxy chain contains a quic scheme proxy, it must only be followed
// by another quic or https proxy. This ensures this logic still applies.
TEST(ProxySpecificationUtilTest, MultiProxyUrisToProxyChainInvalidQuicCombo) {
ProxyChain proxy_chain = MultiProxyUrisToProxyChain(
"[https://loopy:80 quic://foopy:80]", ProxyServer::SCHEME_HTTP);
EXPECT_FALSE(proxy_chain.IsValid());
}
#endif // BUILDFLAG(ENABLE_QUIC_PROXY_SUPPORT)
// If the input URIs is invalid, an invalid `ProxyChain()` will be returned.
TEST(ProxySpecificationUtilTest,
MultiProxyUrisToProxyChainInvalidFormatReturnsInvalidProxyChain) {
const char* const invalid_multi_proxy_uris[] = {
"", // Empty string
" ", // String with only spaces
"[]", // No proxies within brackets
"https://foopy https://hoopy", // Missing brackets
"[https://foopy https://hoopy", // Missing bracket
"https://foopy https://hoopy]", // Missing bracket
"https://foopy \t https://hoopy" // Missing brackets and bad delimiter
};
for (const char* uri : invalid_multi_proxy_uris) {
ProxyChain multi_proxy_uri =
MultiProxyUrisToProxyChain(uri, ProxyServer::SCHEME_HTTPS);
EXPECT_FALSE(multi_proxy_uri.IsValid());
EXPECT_FALSE(multi_proxy_uri.is_direct());
}
}
#endif // BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)
} // namespace
} // namespace net