| // 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 <string_view> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/notreached.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "build/buildflag.h" |
| #include "net/base/proxy_server.h" |
| #include "net/base/url_util.h" |
| #include "net/http/http_util.h" |
| #include "net/net_buildflags.h" |
| #include "url/third_party/mozilla/url_parse.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| // Parses the proxy type from a PAC string, to a ProxyServer::Scheme. |
| // This mapping is case-insensitive. If no type could be matched |
| // returns SCHEME_INVALID. |
| ProxyServer::Scheme GetSchemeFromPacTypeInternal(std::string_view type) { |
| if (base::EqualsCaseInsensitiveASCII(type, "proxy")) { |
| return ProxyServer::SCHEME_HTTP; |
| } |
| if (base::EqualsCaseInsensitiveASCII(type, "socks")) { |
| // Default to v4 for compatibility. This is because the SOCKS4 vs SOCKS5 |
| // notation didn't originally exist, so if a client returns SOCKS they |
| // really meant SOCKS4. |
| return ProxyServer::SCHEME_SOCKS4; |
| } |
| if (base::EqualsCaseInsensitiveASCII(type, "socks4")) { |
| return ProxyServer::SCHEME_SOCKS4; |
| } |
| if (base::EqualsCaseInsensitiveASCII(type, "socks5")) { |
| return ProxyServer::SCHEME_SOCKS5; |
| } |
| if (base::EqualsCaseInsensitiveASCII(type, "https")) { |
| return ProxyServer::SCHEME_HTTPS; |
| } |
| |
| return ProxyServer::SCHEME_INVALID; |
| } |
| |
| std::string ConstructHostPortString(std::string_view hostname, uint16_t port) { |
| DCHECK(!hostname.empty()); |
| DCHECK((hostname.front() == '[' && hostname.back() == ']') || |
| hostname.find(":") == std::string_view::npos); |
| |
| return base::StrCat({hostname, ":", base::NumberToString(port)}); |
| } |
| |
| std::tuple<std::string_view, std::string_view> |
| PacResultElementToSchemeAndHostPort(std::string_view pac_result_element) { |
| // Trim the leading/trailing whitespace. |
| pac_result_element = HttpUtil::TrimLWS(pac_result_element); |
| |
| // Input should match: |
| // ( <type> 1*(LWS) <host-and-port> ) |
| |
| // Start by finding the first space (if any). |
| size_t space = 0; |
| for (; space < pac_result_element.size(); space++) { |
| if (HttpUtil::IsLWS(pac_result_element[space])) { |
| break; |
| } |
| } |
| // Everything to the left of the space is the scheme. |
| std::string_view scheme = pac_result_element.substr(0, space); |
| |
| // And everything to the right of the space is the |
| // <host>[":" <port>]. |
| std::string_view host_and_port = pac_result_element.substr(space); |
| return std::make_tuple(scheme, host_and_port); |
| } |
| |
| } // namespace |
| |
| ProxyChain PacResultElementToProxyChain(std::string_view pac_result_element) { |
| // Proxy chains are not supported in PAC strings, so this is just parsed |
| // as a single server. |
| auto [type, host_and_port] = |
| PacResultElementToSchemeAndHostPort(pac_result_element); |
| if (base::EqualsCaseInsensitiveASCII(type, "direct") && |
| host_and_port.empty()) { |
| return ProxyChain::Direct(); |
| } |
| return ProxyChain(PacResultElementToProxyServer(pac_result_element)); |
| } |
| |
| ProxyServer PacResultElementToProxyServer(std::string_view pac_result_element) { |
| auto [type, host_and_port] = |
| PacResultElementToSchemeAndHostPort(pac_result_element); |
| ProxyServer::Scheme scheme = GetSchemeFromPacTypeInternal(type); |
| return ProxySchemeHostAndPortToProxyServer(scheme, host_and_port); |
| } |
| |
| std::string ProxyServerToPacResultElement(const ProxyServer& proxy_server) { |
| switch (proxy_server.scheme()) { |
| case ProxyServer::SCHEME_HTTP: |
| return std::string("PROXY ") + |
| ConstructHostPortString(proxy_server.GetHost(), |
| proxy_server.GetPort()); |
| case ProxyServer::SCHEME_SOCKS4: |
| // For compatibility send SOCKS instead of SOCKS4. |
| return std::string("SOCKS ") + |
| ConstructHostPortString(proxy_server.GetHost(), |
| proxy_server.GetPort()); |
| case ProxyServer::SCHEME_SOCKS5: |
| return std::string("SOCKS5 ") + |
| ConstructHostPortString(proxy_server.GetHost(), |
| proxy_server.GetPort()); |
| case ProxyServer::SCHEME_HTTPS: |
| return std::string("HTTPS ") + |
| ConstructHostPortString(proxy_server.GetHost(), |
| proxy_server.GetPort()); |
| case ProxyServer::SCHEME_QUIC: |
| return std::string("QUIC ") + |
| ConstructHostPortString(proxy_server.GetHost(), |
| proxy_server.GetPort()); |
| default: |
| // Got called with an invalid scheme. |
| NOTREACHED(); |
| } |
| } |
| |
| ProxyChain ProxyUriToProxyChain(std::string_view uri, |
| ProxyServer::Scheme default_scheme, |
| bool is_quic_allowed) { |
| // If uri is direct, return direct proxy chain. |
| uri = HttpUtil::TrimLWS(uri); |
| size_t colon = uri.find("://"); |
| if (colon != std::string_view::npos && |
| base::EqualsCaseInsensitiveASCII(uri.substr(0, colon), "direct")) { |
| if (!uri.substr(colon + 3).empty()) { |
| return ProxyChain(); // Invalid -- Direct chain cannot have a host/port. |
| } |
| return ProxyChain::Direct(); |
| } |
| return ProxyChain( |
| ProxyUriToProxyServer(uri, default_scheme, is_quic_allowed)); |
| } |
| |
| ProxyServer ProxyUriToProxyServer(std::string_view uri, |
| ProxyServer::Scheme default_scheme, |
| bool is_quic_allowed) { |
| // We will default to |default_scheme| if no scheme specifier was given. |
| ProxyServer::Scheme scheme = default_scheme; |
| |
| // Trim the leading/trailing whitespace. |
| uri = HttpUtil::TrimLWS(uri); |
| |
| // Check for [<scheme> "://"] |
| size_t colon = uri.find(':'); |
| if (colon != std::string_view::npos && uri.size() - colon >= 3 && |
| uri[colon + 1] == '/' && uri[colon + 2] == '/') { |
| scheme = GetSchemeFromUriScheme(uri.substr(0, colon), is_quic_allowed); |
| uri = uri.substr(colon + 3); // Skip past the "://" |
| } |
| |
| // Now parse the <host>[":"<port>]. |
| return ProxySchemeHostAndPortToProxyServer(scheme, uri); |
| } |
| |
| std::string ProxyServerToProxyUri(const ProxyServer& proxy_server) { |
| switch (proxy_server.scheme()) { |
| case ProxyServer::SCHEME_HTTP: |
| // Leave off "http://" since it is our default scheme. |
| return ConstructHostPortString(proxy_server.GetHost(), |
| proxy_server.GetPort()); |
| case ProxyServer::SCHEME_SOCKS4: |
| return std::string("socks4://") + |
| ConstructHostPortString(proxy_server.GetHost(), |
| proxy_server.GetPort()); |
| case ProxyServer::SCHEME_SOCKS5: |
| return std::string("socks5://") + |
| ConstructHostPortString(proxy_server.GetHost(), |
| proxy_server.GetPort()); |
| case ProxyServer::SCHEME_HTTPS: |
| return std::string("https://") + |
| ConstructHostPortString(proxy_server.GetHost(), |
| proxy_server.GetPort()); |
| case ProxyServer::SCHEME_QUIC: |
| return std::string("quic://") + |
| ConstructHostPortString(proxy_server.GetHost(), |
| proxy_server.GetPort()); |
| default: |
| // Got called with an invalid scheme. |
| NOTREACHED(); |
| } |
| } |
| |
| ProxyServer ProxySchemeHostAndPortToProxyServer( |
| ProxyServer::Scheme scheme, |
| std::string_view host_and_port) { |
| // Trim leading/trailing space. |
| host_and_port = HttpUtil::TrimLWS(host_and_port); |
| |
| if (scheme == ProxyServer::SCHEME_INVALID) { |
| return ProxyServer(); |
| } |
| |
| url::Component username_component; |
| url::Component password_component; |
| url::Component hostname_component; |
| url::Component port_component; |
| url::ParseAuthority(host_and_port.data(), |
| url::Component(0, host_and_port.size()), |
| &username_component, &password_component, |
| &hostname_component, &port_component); |
| if (username_component.is_valid() || password_component.is_valid() || |
| hostname_component.is_empty()) { |
| return ProxyServer(); |
| } |
| |
| std::string_view hostname = |
| host_and_port.substr(hostname_component.begin, hostname_component.len); |
| |
| // Reject inputs like "foo:". /url parsing and canonicalization code generally |
| // allows it and treats it the same as a URL without a specified port, but |
| // Chrome has traditionally disallowed it in proxy specifications. |
| if (port_component.is_valid() && port_component.is_empty()) { |
| return ProxyServer(); |
| } |
| std::string_view port = |
| port_component.is_nonempty() |
| ? host_and_port.substr(port_component.begin, port_component.len) |
| : ""; |
| |
| return ProxyServer::FromSchemeHostAndPort(scheme, hostname, port); |
| } |
| |
| ProxyServer::Scheme GetSchemeFromUriScheme(std::string_view scheme, |
| bool is_quic_allowed) { |
| if (base::EqualsCaseInsensitiveASCII(scheme, "http")) { |
| return ProxyServer::SCHEME_HTTP; |
| } |
| if (base::EqualsCaseInsensitiveASCII(scheme, "socks4")) { |
| return ProxyServer::SCHEME_SOCKS4; |
| } |
| if (base::EqualsCaseInsensitiveASCII(scheme, "socks")) { |
| return ProxyServer::SCHEME_SOCKS5; |
| } |
| if (base::EqualsCaseInsensitiveASCII(scheme, "socks5")) { |
| return ProxyServer::SCHEME_SOCKS5; |
| } |
| if (base::EqualsCaseInsensitiveASCII(scheme, "https")) { |
| return ProxyServer::SCHEME_HTTPS; |
| } |
| #if BUILDFLAG(ENABLE_QUIC_PROXY_SUPPORT) |
| if (is_quic_allowed && base::EqualsCaseInsensitiveASCII(scheme, "quic")) { |
| return ProxyServer::SCHEME_QUIC; |
| } |
| #endif // BUILDFLAG(ENABLE_QUIC_PROXY_SUPPORT) |
| return ProxyServer::SCHEME_INVALID; |
| } |
| |
| ProxyChain MultiProxyUrisToProxyChain(std::string_view uris, |
| ProxyServer::Scheme default_scheme, |
| bool is_quic_allowed) { |
| #if BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS) |
| uris = HttpUtil::TrimLWS(uris); |
| if (uris.empty()) { |
| return ProxyChain(); |
| } |
| |
| bool has_multi_proxy_brackets = uris.front() == '[' && uris.back() == ']'; |
| // Remove `[]` if present |
| if (has_multi_proxy_brackets) { |
| uris = HttpUtil::TrimLWS(uris.substr(1, uris.size() - 2)); |
| } |
| |
| std::vector<ProxyServer> proxy_server_list; |
| std::vector<std::string_view> uris_list = base::SplitStringPiece( |
| uris, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| size_t number_of_proxy_uris = uris_list.size(); |
| bool has_invalid_format = |
| number_of_proxy_uris > 1 && !has_multi_proxy_brackets; |
| |
| // If uris list is empty or has invalid formatting for multi-proxy chains, an |
| // invalid `ProxyChain` should be returned. |
| if (uris_list.empty() || has_invalid_format) { |
| return ProxyChain(); |
| } |
| |
| for (const auto& uri : uris_list) { |
| // If direct is found, it MUST be the only uri in the list. Otherwise, it is |
| // an invalid `ProxyChain()`. |
| if (base::EqualsCaseInsensitiveASCII(uri, "direct://")) { |
| return number_of_proxy_uris > 1 ? ProxyChain() : ProxyChain::Direct(); |
| } |
| |
| proxy_server_list.push_back( |
| ProxyUriToProxyServer(uri, default_scheme, is_quic_allowed)); |
| } |
| |
| return ProxyChain(std::move(proxy_server_list)); |
| #else |
| // This function should not be called in non-debug modes. |
| NOTREACHED(); |
| #endif // !BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS) |
| } |
| } // namespace net |