// Copyright 2021 The Chromium Authors. All rights reserved. // 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 "base/notreached.h" #include "base/strings/strcat.h" #include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "net/base/proxy_server.h" #include "net/base/url_util.h" #include "net/http/http_util.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(base::StringPiece 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, "direct")) return ProxyServer::SCHEME_DIRECT; if (base::EqualsCaseInsensitiveASCII(type, "https")) return ProxyServer::SCHEME_HTTPS; if (base::EqualsCaseInsensitiveASCII(type, "quic")) return ProxyServer::SCHEME_QUIC; return ProxyServer::SCHEME_INVALID; } ProxyServer FromSchemeHostAndPort(ProxyServer::Scheme scheme, base::StringPiece host_and_port) { // Trim leading/trailing space. host_and_port = HttpUtil::TrimLWS(host_and_port); if (scheme == ProxyServer::SCHEME_INVALID) return ProxyServer(); if (scheme == ProxyServer::SCHEME_DIRECT) { if (!host_and_port.empty()) return ProxyServer(); // Invalid -- DIRECT cannot have a host/port. return ProxyServer::Direct(); } 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_nonempty()) { return ProxyServer(); } base::StringPiece 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_nonempty()) return ProxyServer(); base::StringPiece port = port_component.is_nonempty() ? host_and_port.substr(port_component.begin, port_component.len) : ""; return ProxyServer::FromSchemeHostAndPort(scheme, hostname, port); } std::string ConstructHostPortString(base::StringPiece hostname, uint16_t port) { DCHECK(!hostname.empty()); DCHECK((hostname.front() == '[' && hostname.back() == ']') || hostname.find(":") == base::StringPiece::npos); return base::StrCat({hostname, ":", base::NumberToString(port)}); } } // namespace ProxyServer PacResultElementToProxyServer( base::StringPiece pac_result_element) { // Trim the leading/trailing whitespace. pac_result_element = HttpUtil::TrimLWS(pac_result_element); // Input should match: // "DIRECT" | ( 1*(LWS) ) // 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. ProxyServer::Scheme scheme = GetSchemeFromPacTypeInternal(pac_result_element.substr(0, space)); // And everything to the right of the space is the // [":" ]. return FromSchemeHostAndPort(scheme, pac_result_element.substr(space)); } std::string ProxyServerToPacResultElement(const ProxyServer& proxy_server) { switch (proxy_server.scheme()) { case ProxyServer::SCHEME_DIRECT: return "DIRECT"; 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(); return std::string(); } } ProxyServer ProxyUriToProxyServer(base::StringPiece uri, ProxyServer::Scheme default_scheme) { // 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 [ "://"] size_t colon = uri.find(':'); if (colon != base::StringPiece::npos && uri.size() - colon >= 3 && uri[colon + 1] == '/' && uri[colon + 2] == '/') { scheme = GetSchemeFromUriScheme(uri.substr(0, colon)); uri = uri.substr(colon + 3); // Skip past the "://" } // Now parse the [":"]. return FromSchemeHostAndPort(scheme, uri); } std::string ProxyServerToProxyUri(const ProxyServer& proxy_server) { switch (proxy_server.scheme()) { case ProxyServer::SCHEME_DIRECT: return "direct://"; 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(); return std::string(); } } ProxyServer::Scheme GetSchemeFromUriScheme(base::StringPiece scheme) { 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, "direct")) return ProxyServer::SCHEME_DIRECT; if (base::EqualsCaseInsensitiveASCII(scheme, "https")) return ProxyServer::SCHEME_HTTPS; if (base::EqualsCaseInsensitiveASCII(scheme, "quic")) return ProxyServer::SCHEME_QUIC; return ProxyServer::SCHEME_INVALID; } } // namespace net