// 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 #include #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 ["://"][":"]. 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 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 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 expected_uris; ProxyServer::Scheme default_scheme; const std::vector 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 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